summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-03-05 14:36:22 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-03-05 14:37:32 +0100
commit28db9b54de6402bd38770ecc1d620255e9d1e78f (patch)
tree469a957ff6b9b6d0ee9fb4074b9139cbaa050443
parent3239a38a9b35d29e483b7bd67b786b4f9d109908 (diff)
parent248b70b82a40964d5594eb04feca0fa36716185d (diff)
downloadqtwebengine-chromium-28db9b54de6402bd38770ecc1d620255e9d1e78f.tar.gz
Merge remote-tracking branch 'origin/upstream-master' into 79-based
Conflicts: chromium/chrome/common/pref_names.cc chromium/chrome/common/pref_names.h Change-Id: I9be20fb8dfd946e3db1fa298dce076db5fd1f397
-rw-r--r--chromium/DEPS2
-rw-r--r--chromium/build/util/LASTCHANGE2
-rw-r--r--chromium/build/util/LASTCHANGE.committime2
-rw-r--r--chromium/chrome/VERSION2
-rw-r--r--chromium/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java235
-rw-r--r--chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintShareActivity.java30
-rw-r--r--chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java82
-rw-r--r--chromium/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java227
-rw-r--r--chromium/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java449
-rw-r--r--chromium/chrome/android/webapk/shell_apk/manifest/bound_manifest_config.json40
-rw-r--r--chromium/chrome/android/webapk/shell_apk/manifest/javatest_manifest_config_delta.json5
-rw-r--r--chromium/chrome/android/webapk/shell_apk/manifest/maps_go_manifest_config.json24
-rw-r--r--chromium/chrome/android/webapk/shell_apk/manifest/unbound_manifest_config.json22
-rw-r--r--chromium/chrome/browser/extensions/default_extensions/external_extensions.json5
-rw-r--r--chromium/chrome/browser/flag-metadata.json3537
-rw-r--r--chromium/chrome/browser/flag-never-expire-list.json90
-rw-r--r--chromium/chrome/browser/media/webrtc/DEPS25
-rw-r--r--chromium/chrome/browser/media/webrtc/OWNERS24
-rw-r--r--chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc168
-rw-r--r--chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h96
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc598
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h105
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc254
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc198
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h31
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc166
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h54
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc96
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc186
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_base.h102
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h28
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker.cc10
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker.h88
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc109
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h91
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc154
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc9
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h33
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc91
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h35
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc34
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h51
-rw-r--r--chromium/chrome/browser/media/webrtc/display_media_access_handler.cc270
-rw-r--r--chromium/chrome/browser/media/webrtc/display_media_access_handler.h89
-rw-r--r--chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc286
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc88
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h43
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc108
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h53
-rw-r--r--chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h26
-rw-r--r--chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc493
-rw-r--r--chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h209
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc470
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h136
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc101
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h46
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc136
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc54
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h26
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc605
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h138
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc963
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc168
-rw-r--r--chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc407
-rw-r--r--chromium/chrome/browser/media/webrtc/native_desktop_media_list.h67
-rw-r--r--chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc554
-rw-r--r--chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc324
-rw-r--r--chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h67
-rw-r--r--chromium/chrome/browser/media/webrtc/rtp_dump_type.h12
-rw-r--r--chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc109
-rw-r--r--chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h52
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h57
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm246
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h36
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm196
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc92
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h31
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc163
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h31
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc372
-rw-r--r--chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc216
-rw-r--r--chromium/chrome/browser/media/webrtc/test_stats_dictionary.h85
-rw-r--r--chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc167
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc259
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc344
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc651
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h257
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc180
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h67
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc338
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h44
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc96
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc99
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc423
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h121
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc1086
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h429
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc1013
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h543
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc655
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc36
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h43
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc40
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h44
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc252
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h98
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc1376
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h487
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc4934
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc98
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h82
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc383
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h148
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc366
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc196
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc360
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc78
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc309
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc42
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h47
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc633
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h236
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc316
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_util.cc36
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_util.h15
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc589
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h242
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc336
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h139
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc432
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc451
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h148
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc375
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc68
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc391
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc539
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h149
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc497
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc163
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc367
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc118
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util.h14
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc21
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm78
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc17
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_win.cc40
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc74
-rw-r--r--chromium/chrome/browser/net/DEPS5
-rw-r--r--chromium/chrome/browser/net/OWNERS7
-rw-r--r--chromium/chrome/browser/net/chrome_accept_header_browsertest.cc58
-rw-r--r--chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory_browsertest.cc247
-rw-r--r--chromium/chrome/browser/net/chrome_network_delegate.cc136
-rw-r--r--chromium/chrome/browser/net/chrome_network_delegate.h33
-rw-r--r--chromium/chrome/browser/net/chrome_network_delegate_browsertest.cc90
-rw-r--r--chromium/chrome/browser/net/chrome_network_delegate_unittest.cc102
-rw-r--r--chromium/chrome/browser/net/chrome_network_service_browsertest.cc130
-rw-r--r--chromium/chrome/browser/net/chrome_network_service_restart_browsertest.cc101
-rw-r--r--chromium/chrome/browser/net/chrome_report_sender.cc99
-rw-r--r--chromium/chrome/browser/net/chrome_report_sender.h31
-rw-r--r--chromium/chrome/browser/net/cookie_policy_browsertest.cc515
-rw-r--r--chromium/chrome/browser/net/disk_cache_dir_policy_handler.cc34
-rw-r--r--chromium/chrome/browser/net/disk_cache_dir_policy_handler.h30
-rw-r--r--chromium/chrome/browser/net/disk_cache_dir_policy_handler_unittest.cc54
-rw-r--r--chromium/chrome/browser/net/dns_probe_browsertest.cc956
-rw-r--r--chromium/chrome/browser/net/dns_probe_runner.cc139
-rw-r--r--chromium/chrome/browser/net/dns_probe_runner.h100
-rw-r--r--chromium/chrome/browser/net/dns_probe_runner_unittest.cc204
-rw-r--r--chromium/chrome/browser/net/dns_probe_service.h37
-rw-r--r--chromium/chrome/browser/net/dns_probe_service_factory.cc375
-rw-r--r--chromium/chrome/browser/net/dns_probe_service_factory.h73
-rw-r--r--chromium/chrome/browser/net/dns_probe_service_factory_unittest.cc200
-rw-r--r--chromium/chrome/browser/net/dns_probe_test_util.cc139
-rw-r--r--chromium/chrome/browser/net/dns_probe_test_util.h126
-rw-r--r--chromium/chrome/browser/net/dns_util.cc59
-rw-r--r--chromium/chrome/browser/net/dns_util.h32
-rw-r--r--chromium/chrome/browser/net/dns_util_unittest.cc49
-rw-r--r--chromium/chrome/browser/net/errorpage_browsertest.cc1451
-rw-r--r--chromium/chrome/browser/net/file_downloader.cc106
-rw-r--r--chromium/chrome/browser/net/file_downloader.h72
-rw-r--r--chromium/chrome/browser/net/file_downloader_unittest.cc124
-rw-r--r--chromium/chrome/browser/net/ftp_browsertest.cc116
-rw-r--r--chromium/chrome/browser/net/load_timing_browsertest.cc204
-rw-r--r--chromium/chrome/browser/net/log_net_log_browsertest.cc101
-rw-r--r--chromium/chrome/browser/net/net_error_diagnostics_dialog.h27
-rw-r--r--chromium/chrome/browser/net/net_error_diagnostics_dialog_posix.cc16
-rw-r--r--chromium/chrome/browser/net/net_error_diagnostics_dialog_win.cc102
-rw-r--r--chromium/chrome/browser/net/net_error_tab_helper.cc309
-rw-r--r--chromium/chrome/browser/net/net_error_tab_helper.h170
-rw-r--r--chromium/chrome/browser/net/net_error_tab_helper_unittest.cc404
-rw-r--r--chromium/chrome/browser/net/net_export_helper.cc106
-rw-r--r--chromium/chrome/browser/net/net_export_helper.h32
-rw-r--r--chromium/chrome/browser/net/netinfo_network_quality_estimator_holdback_browsertest.cc220
-rw-r--r--chromium/chrome/browser/net/network_connection_tracker_browsertest.cc215
-rw-r--r--chromium/chrome/browser/net/network_context_configuration_browsertest.cc2273
-rw-r--r--chromium/chrome/browser/net/network_quality_estimator_browsertest.cc170
-rw-r--r--chromium/chrome/browser/net/network_quality_estimator_prefs_browsertest.cc245
-rw-r--r--chromium/chrome/browser/net/network_quality_netinfo_browsertest.cc163
-rw-r--r--chromium/chrome/browser/net/network_quality_tracker_browsertest.cc277
-rw-r--r--chromium/chrome/browser/net/network_request_metrics_browsertest.cc620
-rw-r--r--chromium/chrome/browser/net/nss_context.cc60
-rw-r--r--chromium/chrome/browser/net/nss_context.h53
-rw-r--r--chromium/chrome/browser/net/nss_context_chromeos.cc137
-rw-r--r--chromium/chrome/browser/net/nss_context_chromeos_browsertest.cc208
-rw-r--r--chromium/chrome/browser/net/nss_context_linux.cc31
-rw-r--r--chromium/chrome/browser/net/prediction_options.cc69
-rw-r--r--chromium/chrome/browser/net/prediction_options.h47
-rw-r--r--chromium/chrome/browser/net/probe_message.cc128
-rw-r--r--chromium/chrome/browser/net/probe_message.h63
-rw-r--r--chromium/chrome/browser/net/probe_message.proto48
-rw-r--r--chromium/chrome/browser/net/probe_message_unittest.cc107
-rw-r--r--chromium/chrome/browser/net/profile_network_context_service.cc704
-rw-r--r--chromium/chrome/browser/net/profile_network_context_service.h162
-rw-r--r--chromium/chrome/browser/net/profile_network_context_service_browsertest.cc588
-rw-r--r--chromium/chrome/browser/net/profile_network_context_service_factory.cc40
-rw-r--r--chromium/chrome/browser/net/profile_network_context_service_factory.h46
-rw-r--r--chromium/chrome/browser/net/proxy_browsertest.cc302
-rw-r--r--chromium/chrome/browser/net/proxy_config_monitor.cc158
-rw-r--r--chromium/chrome/browser/net/proxy_config_monitor.h100
-rw-r--r--chromium/chrome/browser/net/proxy_service_factory.cc75
-rw-r--r--chromium/chrome/browser/net/proxy_service_factory.h44
-rw-r--r--chromium/chrome/browser/net/proxy_test_utils.cc49
-rw-r--r--chromium/chrome/browser/net/proxy_test_utils.h33
-rw-r--r--chromium/chrome/browser/net/referrer.cc167
-rw-r--r--chromium/chrome/browser/net/referrer.h133
-rw-r--r--chromium/chrome/browser/net/reporting_browsertest.cc268
-rw-r--r--chromium/chrome/browser/net/secure_dns_policy_handler.cc68
-rw-r--r--chromium/chrome/browser/net/secure_dns_policy_handler.h33
-rw-r--r--chromium/chrome/browser/net/secure_dns_policy_handler_unittest.cc183
-rw-r--r--chromium/chrome/browser/net/service_providers_win.cc107
-rw-r--r--chromium/chrome/browser/net/service_providers_win.h42
-rw-r--r--chromium/chrome/browser/net/system_network_context_manager.cc863
-rw-r--r--chromium/chrome/browser/net/system_network_context_manager.h195
-rw-r--r--chromium/chrome/browser/net/system_network_context_manager_browsertest.cc499
-rw-r--r--chromium/chrome/browser/net/trial_comparison_cert_verifier_browsertest.cc136
-rw-r--r--chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.cc145
-rw-r--r--chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.h82
-rw-r--r--chromium/chrome/browser/net/trial_comparison_cert_verifier_controller_unittest.cc548
-rw-r--r--chromium/chrome/browser/net/variations_http_headers_browsertest.cc482
-rw-r--r--chromium/chrome/browser/net/websocket_browsertest.cc357
-rw-r--r--chromium/chrome/browser/prefs/DEPS9
-rw-r--r--chromium/chrome/browser/prefs/OWNERS7
-rw-r--r--chromium/chrome/browser/prefs/browser_prefs.cc1223
-rw-r--r--chromium/chrome/browser/prefs/browser_prefs.h53
-rw-r--r--chromium/chrome/browser/prefs/chrome_command_line_pref_store.cc177
-rw-r--r--chromium/chrome/browser/prefs/chrome_command_line_pref_store.h53
-rw-r--r--chromium/chrome/browser/prefs/chrome_command_line_pref_store_proxy_unittest.cc198
-rw-r--r--chromium/chrome/browser/prefs/chrome_command_line_pref_store_ssl_manager_unittest.cc71
-rw-r--r--chromium/chrome/browser/prefs/chrome_command_line_pref_store_unittest.cc222
-rw-r--r--chromium/chrome/browser/prefs/chrome_pref_model_associator_client.cc57
-rw-r--r--chromium/chrome/browser/prefs/chrome_pref_model_associator_client.h42
-rw-r--r--chromium/chrome/browser/prefs/chrome_pref_service_factory.cc540
-rw-r--r--chromium/chrome/browser/prefs/chrome_pref_service_factory.h130
-rw-r--r--chromium/chrome/browser/prefs/chrome_pref_service_unittest.cc109
-rw-r--r--chromium/chrome/browser/prefs/in_process_service_factory_factory.cc41
-rw-r--r--chromium/chrome/browser/prefs/in_process_service_factory_factory.h43
-rw-r--r--chromium/chrome/browser/prefs/incognito_mode_prefs.cc236
-rw-r--r--chromium/chrome/browser/prefs/incognito_mode_prefs.h98
-rw-r--r--chromium/chrome/browser/prefs/incognito_mode_prefs_unittest.cc69
-rw-r--r--chromium/chrome/browser/prefs/origin_trial_prefs.cc15
-rw-r--r--chromium/chrome/browser/prefs/origin_trial_prefs.h15
-rw-r--r--chromium/chrome/browser/prefs/pref_functional_browsertest.cc246
-rw-r--r--chromium/chrome/browser/prefs/pref_metrics_service.cc189
-rw-r--r--chromium/chrome/browser/prefs/pref_metrics_service.h60
-rw-r--r--chromium/chrome/browser/prefs/pref_service_browsertest.cc135
-rw-r--r--chromium/chrome/browser/prefs/pref_service_incognito_whitelist.cc229
-rw-r--r--chromium/chrome/browser/prefs/pref_service_incognito_whitelist.h19
-rw-r--r--chromium/chrome/browser/prefs/pref_service_syncable_util.cc29
-rw-r--r--chromium/chrome/browser/prefs/pref_service_syncable_util.h41
-rw-r--r--chromium/chrome/browser/prefs/profile_pref_store_manager.cc161
-rw-r--r--chromium/chrome/browser/prefs/profile_pref_store_manager.h132
-rw-r--r--chromium/chrome/browser/prefs/profile_pref_store_manager_unittest.cc562
-rw-r--r--chromium/chrome/browser/prefs/proxy_policy_unittest.cc254
-rw-r--r--chromium/chrome/browser/prefs/session_startup_pref.cc175
-rw-r--r--chromium/chrome/browser/prefs/session_startup_pref.h85
-rw-r--r--chromium/chrome/browser/prefs/session_startup_pref_unittest.cc66
-rw-r--r--chromium/chrome/browser/prefs/synced_pref_change_registrar_browsertest.cc205
-rw-r--r--chromium/chrome/browser/prefs/tracked/pref_hash_browsertest.cc1284
-rw-r--r--chromium/chrome/browser/renderer_host/DEPS11
-rw-r--r--chromium/chrome/browser/renderer_host/OWNERS19
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_extension_message_filter.cc255
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_extension_message_filter.h98
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.cc116
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.h92
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_message_filter.cc379
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_message_filter.h133
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.h36
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm307
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h235
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.mm614
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm821
-rw-r--r--chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm656
-rw-r--r--chromium/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc786
-rw-r--r--chromium/chrome/browser/resources/chromeos/arc_support/manifest.json23
-rw-r--r--chromium/chrome/browser/resources/chromeos/braille_ime/manifest.json26
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/manifest.json18
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/am/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ar/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/bg/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/bn/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ca/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/cs/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/da/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/de/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/el/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json356
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/en_GB/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/es/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/es_419/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/et/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/fa/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/fi/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/fil/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/fr/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/gu/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/he/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/hi/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/hr/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/hu/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/id/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/it/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ja/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/kn/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ko/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/lt/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/lv/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ml/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/mr/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ms/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/nl/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/no/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/pl/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_BR/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_PT/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ro/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ru/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/sk/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/sl/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/sr/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/sv/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/sw/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/ta/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/te/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/th/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/tr/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/uk/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/vi/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_CN/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_TW/messages.json206
-rw-r--r--chromium/chrome/browser/resources/chromeos/camera/src/manifest.json38
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/classic_keymap.json558
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/experimental.json431
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/flat_keymap.json621
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json1047
-rw-r--r--chromium/chrome/browser/resources/chromeos/connectivity_diagnostics/manifest.json53
-rw-r--r--chromium/chrome/browser/resources/chromeos/connectivity_diagnostics_launcher/manifest.json12
-rw-r--r--chromium/chrome/browser/resources/chromeos/crosh_builtin/manifest.json24
-rw-r--r--chromium/chrome/browser/resources/chromeos/demo_app/manifest.json21
-rw-r--r--chromium/chrome/browser/resources/chromeos/echo/manifest.json60
-rw-r--r--chromium/chrome/browser/resources/chromeos/first_run/app/manifest.json29
-rw-r--r--chromium/chrome/browser/resources/chromeos/genius_app/manifest.json70
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json31
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json1945
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json98
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json232
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json45
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json33
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json883
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json33
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/discover/manifest.json13
-rw-r--r--chromium/chrome/browser/resources/chromeos/mobile_app/manifest.json10
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json38
-rw-r--r--chromium/chrome/browser/resources/chromeos/zip_archiver/manifest.json73
-rw-r--r--chromium/chrome/browser/resources/settings/OWNERS12
-rw-r--r--chromium/chrome/browser/resources/settings/PRESUBMIT.py24
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html123
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js173
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js35
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.html110
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.js254
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/externs.js28
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html255
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js246
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.html91
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.js199
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.html168
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.js421
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/about_page.html315
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/about_page.js745
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.js342
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.html71
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.js160
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/detailed_build_info.html64
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/detailed_build_info.js99
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.html28
-rw-r--r--chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.js55
-rw-r--r--chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.js57
-rw-r--r--chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.html78
-rw-r--r--chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.js76
-rw-r--r--chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.html58
-rw-r--r--chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.js110
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js82
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html142
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js157
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html193
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/appearance_page.js352
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.js56
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/home_url_input.html44
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/home_url_input.js156
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.js48
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html127
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js345
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/autofill_page.html71
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/autofill_page.js68
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/autofill_section.html100
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/autofill_section.js254
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.html2
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.js44
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html107
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.js164
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.html34
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.js30
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html58
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.js50
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html86
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js41
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/password_list_item.html97
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/password_list_item.js51
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js248
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html95
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.js264
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/passwords_section.html201
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/passwords_section.js495
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/passwords_shared_css.html64
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/payments_section.html114
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/payments_section.js411
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.html6
-rw-r--r--chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.js102
-rw-r--r--chromium/chrome/browser/resources/settings/basic_page/basic_page.html342
-rw-r--r--chromium/chrome/browser/resources/settings/basic_page/basic_page.js451
-rw-r--r--chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html52
-rw-r--r--chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js156
-rw-r--r--chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html87
-rw-r--r--chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js261
-rw-r--r--chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html116
-rw-r--r--chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js590
-rw-r--r--chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.js40
-rw-r--r--chromium/chrome/browser/resources/settings/change_password_page/change_password_page.html50
-rw-r--r--chromium/chrome/browser/resources/settings/change_password_page/change_password_page.js17
-rw-r--r--chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.html122
-rw-r--r--chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js734
-rw-r--r--chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js110
-rw-r--r--chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.html68
-rw-r--r--chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.js153
-rw-r--r--chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.js52
-rw-r--r--chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.html288
-rw-r--r--chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js448
-rw-r--r--chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.html22
-rw-r--r--chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.js26
-rw-r--r--chromium/chrome/browser/resources/settings/controls/controlled_button.html58
-rw-r--r--chromium/chrome/browser/resources/settings/controls/controlled_button.js73
-rw-r--r--chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html61
-rw-r--r--chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js41
-rw-r--r--chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html40
-rw-r--r--chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.js42
-rw-r--r--chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.html50
-rw-r--r--chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.js160
-rw-r--r--chromium/chrome/browser/resources/settings/controls/pref_control_behavior.html4
-rw-r--r--chromium/chrome/browser/resources/settings/controls/pref_control_behavior.js47
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html6
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js138
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_checkbox.html51
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_checkbox.js64
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.html52
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.js173
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_idle_load.html8
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_idle_load.js83
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_radio_group.html19
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_radio_group.js50
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_slider.html72
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_slider.js172
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_textarea.html38
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_textarea.js85
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html74
-rw-r--r--chromium/chrome/browser/resources/settings/controls/settings_toggle_button.js95
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js138
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.html41
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.js57
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.html26
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.js28
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_page.html95
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_page.js97
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.html37
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js71
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.html42
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.js51
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.html45
-rw-r--r--chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.js83
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html87
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js139
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_types.html2
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_types.js33
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.html50
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.js173
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.html57
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.js65
-rw-r--r--chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js58
-rw-r--r--chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.html43
-rw-r--r--chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.js74
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/device_page.html108
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/device_page.js225
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js273
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/display.html295
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/display.js973
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/display_layout.html80
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/display_layout.js270
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.html87
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.js155
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/drag_behavior.html1
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/drag_behavior.js244
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/keyboard.html139
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/keyboard.js172
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/layout_behavior.html1
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/layout_behavior.js727
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/night_light_slider.html196
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/night_light_slider.js678
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/pointers.html110
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/pointers.js80
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/power.html65
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/power.js288
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/storage.html228
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/storage.js314
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/storage_external.html32
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/storage_external.js59
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/storage_external_entry.html25
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/storage_external_entry.js72
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/stylus.html118
-rw-r--r--chromium/chrome/browser/resources/settings/device_page/stylus.js202
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.js53
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html89
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js120
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.html44
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.js46
-rw-r--r--chromium/chrome/browser/resources/settings/extension_control_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/extension_control_browser_proxy.js38
-rw-r--r--chromium/chrome/browser/resources/settings/global_scroll_target_behavior.html3
-rw-r--r--chromium/chrome/browser/resources/settings/global_scroll_target_behavior.js112
-rw-r--r--chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.js51
-rw-r--r--chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html120
-rw-r--r--chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js227
-rw-r--r--chromium/chrome/browser/resources/settings/i18n_setup.html2
-rw-r--r--chromium/chrome/browser/resources/settings/icons.html98
-rw-r--r--chromium/chrome/browser/resources/settings/images/settings_icon_add_circle.svg1
-rw-r--r--chromium/chrome/browser/resources/settings/images/settings_icon_add_wifi.svg1
-rw-r--r--chromium/chrome/browser/resources/settings/images/sync_banner.svg1
-rw-r--r--chromium/chrome/browser/resources/settings/images/sync_banner_dark.svg1
-rw-r--r--chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.html25
-rw-r--r--chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.js106
-rw-r--r--chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.js123
-rw-r--r--chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.html60
-rw-r--r--chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.js144
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_config.html63
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_config.js131
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html368
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js1682
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html94
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js240
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_page.html135
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_page.js557
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.js86
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_shared_css.html30
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html213
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js731
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.html103
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.js152
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary.html21
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary.js294
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html88
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js398
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html123
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.js153
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.html74
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.js171
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html75
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js209
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages.html9
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages.js1090
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.js74
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages_page.html495
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages_page.js870
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/languages_types.js234
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.html38
-rw-r--r--chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.js229
-rw-r--r--chromium/chrome/browser/resources/settings/lazy_load.html19
-rw-r--r--chromium/chrome/browser/resources/settings/lifetime_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/lifetime_browser_proxy.js61
-rw-r--r--chromium/chrome/browser/resources/settings/manifest.json13
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/OWNERS3
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js138
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.html2
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js82
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.html7
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.js191
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.html83
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.js67
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.html18
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.js76
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.html126
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.js369
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.html60
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.js22
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html65
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js189
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html131
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js126
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html30
-rw-r--r--chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js170
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.js31
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.html51
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.js58
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.html32
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.js132
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.html46
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.js61
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.html84
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js103
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.html4
-rw-r--r--chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.js87
-rw-r--r--chromium/chrome/browser/resources/settings/open_window_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/open_window_proxy.js36
-rw-r--r--chromium/chrome/browser/resources/settings/os_settings_manifest.json13
-rw-r--r--chromium/chrome/browser/resources/settings/page_visibility.html3
-rw-r--r--chromium/chrome/browser/resources/settings/page_visibility.js218
-rw-r--r--chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.js45
-rw-r--r--chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.html48
-rw-r--r--chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.js87
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/account_manager.html207
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/account_manager.js217
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js109
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/change_picture.html118
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/change_picture.js334
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.js119
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.js165
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html82
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/fingerprint_list.js207
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.js87
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/import_data_dialog.html130
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/import_data_dialog.js144
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.html184
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.js201
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js171
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html168
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js421
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/lock_screen.html175
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/lock_screen.js322
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.html16
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.js120
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.html7
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.js123
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/manage_profile.html46
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/manage_profile.js157
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.js112
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/people_page.html428
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/people_page.js699
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.js57
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/settings_icon_camera_alt.svg1
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html105
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js370
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.html53
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.js121
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/signout_dialog.html84
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/signout_dialog.js121
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_account_control.html245
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_account_control.js471
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.html3
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.js324
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_controls.html185
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_controls.js197
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_page.html362
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/sync_page.js710
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/user_list.html67
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/user_list.js136
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.html38
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.js159
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/users_page.html76
-rw-r--r--chromium/chrome/browser/resources/settings/people_page/users_page.js95
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.js46
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.html45
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.js40
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.html34
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.js69
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.html21
-rw-r--r--chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.js25
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/pref_util.html2
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/pref_util.js58
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs.html7
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs.js335
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs_behavior.html1
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs_behavior.js71
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs_types.html1
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs_types.js56
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/OWNERS2
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cloud_printers.html32
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cloud_printers.js19
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html288
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js808
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.html56
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.js54
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html161
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js464
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html40
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js192
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html2
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.js189
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printer_shared_css.html122
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.html2
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.js25
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers.html207
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers.js275
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js283
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.html45
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.js67
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html2
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js98
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html5
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js182
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.html40
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.js106
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.html87
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.js295
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.js35
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/printing_page.html58
-rw-r--r--chromium/chrome/browser/resources/settings/printing_page/printing_page.js66
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/personalization_options.html113
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/personalization_options.js249
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html722
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js439
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js68
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html152
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js321
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js363
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.html130
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js274
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.html35
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.js175
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.html60
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js141
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.html141
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js465
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html69
-rw-r--r--chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.js97
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html33
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.js37
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.js120
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_page.html81
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_page.js125
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.html32
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.js38
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.html59
-rw-r--r--chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.js147
-rw-r--r--chromium/chrome/browser/resources/settings/route.html5
-rw-r--r--chromium/chrome/browser/resources/settings/route.js899
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.html48
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js52
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.html46
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js157
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html79
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js99
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.html10
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js126
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.html69
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.js37
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html88
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js204
-rw-r--r--chromium/chrome/browser/resources/settings/search_page/search_page.html124
-rw-r--r--chromium/chrome/browser/resources/settings/search_page/search_page.js140
-rw-r--r--chromium/chrome/browser/resources/settings/search_settings.js601
-rw-r--r--chromium/chrome/browser/resources/settings/settings.html34
-rw-r--r--chromium/chrome/browser/resources/settings/settings_icons_css.html17
-rw-r--r--chromium/chrome/browser/resources/settings/settings_main/settings_main.html91
-rw-r--r--chromium/chrome/browser/resources/settings/settings_main/settings_main.js240
-rw-r--r--chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html240
-rw-r--r--chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js107
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.html5
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.js388
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.html19
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.js237
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_section.html47
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_section.js62
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html102
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_subpage.js164
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page_css.html36
-rw-r--r--chromium/chrome/browser/resources/settings/settings_shared_css.html357
-rw-r--r--chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html168
-rw-r--r--chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js363
-rw-r--r--chromium/chrome/browser/resources/settings/settings_vars_css.html57
-rw-r--r--chromium/chrome/browser/resources/settings/site_favicon.html22
-rw-r--r--chromium/chrome/browser/resources/settings/site_favicon.js65
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.html50
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.js125
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/all_sites.html137
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/all_sites.js511
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/category_default_setting.html28
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/category_default_setting.js237
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html35
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js114
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.html39
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.js192
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.html35
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.js25
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/constants.html2
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/constants.js131
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/cookie_info.html1
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/cookie_info.js95
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.html32
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.js93
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.js168
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/media_picker.html24
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/media_picker.js68
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/pdf_documents.html19
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/pdf_documents.js20
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html103
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js222
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_data.html101
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_data.js290
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.html48
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.js162
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_data_entry.html35
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_data_entry.js40
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_details.html232
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_details.js294
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_details_permission.html80
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_details_permission.js377
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_entry.html139
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_entry.js454
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_list.html103
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_list.js499
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_list_entry.html73
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_list_entry.js226
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.html8
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.js219
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html1
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js533
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.html4
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.js121
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html61
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings/zoom_levels.js63
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.html272
-rw-r--r--chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.js244
-rw-r--r--chromium/chrome/browser/resources/settings/system_page/system_page.html67
-rw-r--r--chromium/chrome/browser/resources/settings/system_page/system_page.js80
-rw-r--r--chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.html2
-rw-r--r--chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.js41
-rw-r--r--chromium/chrome/common/DEPS70
-rw-r--r--chromium/chrome/common/OWNERS64
-rw-r--r--chromium/chrome/common/all_messages.h28
-rw-r--r--chromium/chrome/common/apps/OWNERS2
-rw-r--r--chromium/chrome/common/apps/platform_apps/OWNERS2
-rw-r--r--chromium/chrome/common/apps/platform_apps/api/arc_apps_private.idl33
-rw-r--r--chromium/chrome/common/apps/platform_apps/api/browser.idl26
-rw-r--r--chromium/chrome/common/apps/platform_apps/api/media_galleries.idl255
-rw-r--r--chromium/chrome/common/apps/platform_apps/api/music_manager_private.idl16
-rw-r--r--chromium/chrome/common/apps/platform_apps/api/sync_file_system.idl199
-rw-r--r--chromium/chrome/common/apps/platform_apps/api/webstore_widget_private.idl24
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc45
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.h19
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.cc65
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.h36
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc30
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.h11
-rw-r--r--chromium/chrome/common/apps/platform_apps/chrome_apps_messages.h15
-rw-r--r--chromium/chrome/common/apps/platform_apps/media_galleries_permission.cc157
-rw-r--r--chromium/chrome/common/apps/platform_apps/media_galleries_permission.h63
-rw-r--r--chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.cc60
-rw-r--r--chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.h50
-rw-r--r--chromium/chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc304
-rw-r--r--chromium/chrome/common/attrition_experiments.h26
-rw-r--r--chromium/chrome/common/auto_start_linux.cc95
-rw-r--r--chromium/chrome/common/auto_start_linux.h33
-rw-r--r--chromium/chrome/common/cast_messages.cc55
-rw-r--r--chromium/chrome/common/cast_messages.h254
-rw-r--r--chromium/chrome/common/channel_info.cc16
-rw-r--r--chromium/chrome/common/channel_info.h66
-rw-r--r--chromium/chrome/common/channel_info_android.cc31
-rw-r--r--chromium/chrome/common/channel_info_chromeos.cc75
-rw-r--r--chromium/chrome/common/channel_info_mac.mm63
-rw-r--r--chromium/chrome/common/channel_info_posix.cc100
-rw-r--r--chromium/chrome/common/channel_info_win.cc33
-rw-r--r--chromium/chrome/common/child_process_logging.h19
-rw-r--r--chromium/chrome/common/child_process_logging_win.cc32
-rw-r--r--chromium/chrome/common/chrome_constants_win_unittest.cc32
-rw-r--r--chromium/chrome/common/chrome_content_client.cc853
-rw-r--r--chromium/chrome/common/chrome_content_client.h115
-rw-r--r--chromium/chrome/common/chrome_content_client_constants.cc28
-rw-r--r--chromium/chrome/common/chrome_content_client_unittest.cc180
-rw-r--r--chromium/chrome/common/chrome_descriptors.h23
-rw-r--r--chromium/chrome/common/chrome_features.cc894
-rw-r--r--chromium/chrome/common/chrome_features.h552
-rw-r--r--chromium/chrome/common/chrome_icon_resources_win.h50
-rw-r--r--chromium/chrome/common/chrome_isolated_world_ids.h29
-rw-r--r--chromium/chrome/common/chrome_result_codes.h121
-rw-r--r--chromium/chrome/common/chrome_utility_printing_messages.h99
-rw-r--r--chromium/chrome/common/chrome_version.h.in20
-rw-r--r--chromium/chrome/common/client_hints/OWNERS8
-rw-r--r--chromium/chrome/common/client_hints/client_hints.cc66
-rw-r--r--chromium/chrome/common/client_hints/client_hints.h28
-rw-r--r--chromium/chrome/common/cloud_print/OWNERS5
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_class_mac.h17
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_class_mac.mm11
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_constants.cc79
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_constants.h135
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_helpers.cc240
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_helpers.h86
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_helpers_unittest.cc131
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_proxy_info.cc14
-rw-r--r--chromium/chrome/common/cloud_print/cloud_print_proxy_info.h24
-rw-r--r--chromium/chrome/common/common.vsprops8
-rw-r--r--chromium/chrome/common/common_message_generator.cc33
-rw-r--r--chromium/chrome/common/common_message_generator.h51
-rw-r--r--chromium/chrome/common/common_param_traits.cc27
-rw-r--r--chromium/chrome/common/common_param_traits.h13
-rw-r--r--chromium/chrome/common/common_param_traits_macros.h16
-rw-r--r--chromium/chrome/common/component_flash_hint_file_linux.cc229
-rw-r--r--chromium/chrome/common/component_flash_hint_file_linux.h49
-rw-r--r--chromium/chrome/common/component_flash_hint_file_linux_unittest.cc168
-rw-r--r--chromium/chrome/common/conflicts/OWNERS8
-rw-r--r--chromium/chrome/common/conflicts/module_watcher_win.cc266
-rw-r--r--chromium/chrome/common/conflicts/module_watcher_win.h149
-rw-r--r--chromium/chrome/common/conflicts/module_watcher_win_unittest.cc119
-rw-r--r--chromium/chrome/common/conflicts/remote_module_watcher_win.cc84
-rw-r--r--chromium/chrome/common/conflicts/remote_module_watcher_win.h87
-rw-r--r--chromium/chrome/common/conflicts/remote_module_watcher_win_unittest.cc122
-rw-r--r--chromium/chrome/common/crash_keys.cc118
-rw-r--r--chromium/chrome/common/crash_keys.h34
-rw-r--r--chromium/chrome/common/crash_keys_unittest.cc111
-rw-r--r--chromium/chrome/common/env_vars.cc36
-rw-r--r--chromium/chrome/common/env_vars.h23
-rw-r--r--chromium/chrome/common/extensions/DEPS18
-rw-r--r--chromium/chrome/common/extensions/OWNERS13
-rw-r--r--chromium/chrome/common/extensions/PRESUBMIT.py169
-rwxr-xr-xchromium/chrome/common/extensions/PRESUBMIT_test.py75
-rw-r--r--chromium/chrome/common/extensions/chrome_extensions_api_provider.cc67
-rw-r--r--chromium/chrome/common/extensions/chrome_extensions_api_provider.h35
-rw-r--r--chromium/chrome/common/extensions/chrome_extensions_client.cc257
-rw-r--r--chromium/chrome/common/extensions/chrome_extensions_client.h70
-rw-r--r--chromium/chrome/common/extensions/chrome_extensions_client_unittest.cc93
-rw-r--r--chromium/chrome/common/extensions/chrome_manifest_handlers.cc79
-rw-r--r--chromium/chrome/common/extensions/chrome_manifest_handlers.h16
-rw-r--r--chromium/chrome/common/extensions/chrome_manifest_url_handlers.cc187
-rw-r--r--chromium/chrome/common/extensions/chrome_manifest_url_handlers.h70
-rw-r--r--chromium/chrome/common/extensions/chrome_manifest_url_handlers_unittest.cc39
-rw-r--r--chromium/chrome/common/extensions/command.cc542
-rw-r--r--chromium/chrome/common/extensions/command.h87
-rw-r--r--chromium/chrome/common/extensions/command_unittest.cc348
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json16
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/commands/manifest.json28
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json10
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/contextMenus/global_context_search/manifest.json20
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/cookies/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json16
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/default_command_override/manifest.json36
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/desktopCapture/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/deviceInfo/basic/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json7
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/displaySource/tabCast/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/document_scan/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/download_filename_controller/manifest.json8
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json9
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json290
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json9
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json8
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/downloads/downloads_overwrite/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json24
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json16
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/archive/manifest.json28
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/basic/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/history/historyOverride/manifest.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/detectLanguage/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json27
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json27
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json24
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json8
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json10
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/app/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo-win.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/notifications/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/omnibox/newtab_search/manifest.json23
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json10
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json9
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json20
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/power/_locales/en/messages.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/power/manifest.json30
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json20
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/tabCapture/manifest.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json14
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json16
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/tabs/zoom/manifest.json24
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/topsites/basic/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/water_alarm_notification/manifest.json21
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json52
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/webview/capturevisibleregion/manifest.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_app/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_ext/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json31
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json31
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json12
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json13
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/constant_context/manifest.json37
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/download_images/manifest.json32
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json20
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/fx/manifest.json22
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json25
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json1
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json23
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json11
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/_locales/en/messages.json10
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/schema.json36
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json28
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json102
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news/manifest.json20
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json63
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json63
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json59
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/no_cookies/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json26
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/optional_permissions/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json23
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json16
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json54
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json27
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json49
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json23
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json18
-rw-r--r--chromium/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/howto/tab_shortcuts/manifest.json26
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json17
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/broken_background_color/manifest.json27
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/get_started/manifest.json6
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/get_started_complete/manifest.json27
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json16
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/hello_extensions/manifest.json19
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/oauth_starter/manifest.json15
-rw-r--r--chromium/chrome/common/extensions/docs/examples/tutorials/oauth_tutorial_complete/manifest.json23
-rw-r--r--chromium/chrome/common/extensions/docs/server2/known_broken_links.json2019
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json310
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/second.json1
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json86
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json41
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/rietveld_patcher/expected/extensions_sidenav.json6
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json10
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json24
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json7
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/test_json/add_rules_def_test.json19
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/test_json/expected_tester.json218
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json23
-rw-r--r--chromium/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json13
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/api_availabilities.json51
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/apps_sidenav.json278
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/chrome_sidenav.json834
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/content_providers.json105
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/extensions_sidenav.json222
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/intro_tables.json333
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/manifest.json252
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/permissions.json69
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/strings.json42
-rw-r--r--chromium/chrome/common/extensions/docs/templates/json/whats_new.json367
-rw-r--r--chromium/chrome/common/extensions/docs/templates/public/apps/redirects.json33
-rw-r--r--chromium/chrome/common/extensions/docs/templates/public/extensions/redirects.json20
-rw-r--r--chromium/chrome/common/extensions/docs/templates/public/redirects.json4
-rw-r--r--chromium/chrome/common/extensions/extension_constants.cc132
-rw-r--r--chromium/chrome/common/extensions/extension_constants.h272
-rw-r--r--chromium/chrome/common/extensions/extension_metrics.cc50
-rw-r--r--chromium/chrome/common/extensions/extension_metrics.h30
-rw-r--r--chromium/chrome/common/extensions/extension_test_util.cc115
-rw-r--r--chromium/chrome/common/extensions/extension_test_util.h70
-rw-r--r--chromium/chrome/common/extensions/extension_unittest.cc447
-rw-r--r--chromium/chrome/common/extensions/feature_switch_unittest.cc132
-rw-r--r--chromium/chrome/common/extensions/image_writer/OWNERS1
-rw-r--r--chromium/chrome/common/extensions/image_writer/image_writer_util_mac.cc112
-rw-r--r--chromium/chrome/common/extensions/image_writer/image_writer_util_mac.h24
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.cc86
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.h46
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_launch_info.cc330
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_launch_info.h85
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.cc68
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.h43
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/app_theme_color_manifest_unittest.cc37
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/automation_unittest.cc292
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc105
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc32
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.cc135
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.h35
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc307
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.cc110
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.h51
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.cc63
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.h31
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc72
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.h48
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc57
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc183
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h55
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc202
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/theme_handler.cc224
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/theme_handler.h64
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.cc181
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.h64
-rw-r--r--chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler_unittest.cc107
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.cc23
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.h35
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_about_unittest.cc30
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc261
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc58
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc35
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_default_extent_path_unittest.cc17
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc25
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_dummy_unittest.cc27
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc27
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc49
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_icons_unittest.cc53
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc177
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc34
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_kiosk_unittest.cc108
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc131
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc54
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc44
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc19
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc141
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc34
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc149
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc12
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc70
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc19
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc42
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc36
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_accessible_resources_unittest.cc55
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc52
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/extension_manifests_webview_accessible_resources_unittest.cc85
-rw-r--r--chromium/chrome/common/extensions/manifest_tests/permissions_parser_unittest.cc138
-rw-r--r--chromium/chrome/common/extensions/manifest_unittest.cc222
-rw-r--r--chromium/chrome/common/extensions/sync_helper.cc79
-rw-r--r--chromium/chrome/common/extensions/sync_helper.h29
-rw-r--r--chromium/chrome/common/extensions/sync_type_unittest.cc233
-rw-r--r--chromium/chrome/common/extensions/webstore_install_result.cc20
-rw-r--r--chromium/chrome/common/extensions/webstore_install_result.h87
-rw-r--r--chromium/chrome/common/extra_defines.vsprops7
-rw-r--r--chromium/chrome/common/google_url_loader_throttle.cc114
-rw-r--r--chromium/chrome/common/google_url_loader_throttle.h43
-rw-r--r--chromium/chrome/common/heap_profiler_controller.cc114
-rw-r--r--chromium/chrome/common/heap_profiler_controller.h34
-rw-r--r--chromium/chrome/common/heap_profiler_controller_unittest.cc91
-rw-r--r--chromium/chrome/common/importer/DEPS3
-rw-r--r--chromium/chrome/common/importer/OWNERS13
-rw-r--r--chromium/chrome/common/importer/edge_importer_utils_win.cc91
-rw-r--r--chromium/chrome/common/importer/edge_importer_utils_win.h28
-rw-r--r--chromium/chrome/common/importer/firefox_importer_utils.cc330
-rw-r--r--chromium/chrome/common/importer/firefox_importer_utils.h92
-rw-r--r--chromium/chrome/common/importer/firefox_importer_utils_linux.cc25
-rw-r--r--chromium/chrome/common/importer/firefox_importer_utils_mac.mm45
-rw-r--r--chromium/chrome/common/importer/firefox_importer_utils_unittest.cc163
-rw-r--r--chromium/chrome/common/importer/firefox_importer_utils_win.cc82
-rw-r--r--chromium/chrome/common/importer/ie_importer_utils_win.cc48
-rw-r--r--chromium/chrome/common/importer/ie_importer_utils_win.h26
-rw-r--r--chromium/chrome/common/importer/imported_bookmark_entry.cc24
-rw-r--r--chromium/chrome/common/importer/imported_bookmark_entry.h29
-rw-r--r--chromium/chrome/common/importer/importer_autofill_form_data_entry.cc11
-rw-r--r--chromium/chrome/common/importer/importer_autofill_form_data_entry.h33
-rw-r--r--chromium/chrome/common/importer/importer_bridge.cc9
-rw-r--r--chromium/chrome/common/importer/importer_bridge.h89
-rw-r--r--chromium/chrome/common/importer/importer_data_types.cc32
-rw-r--r--chromium/chrome/common/importer/importer_data_types.h90
-rw-r--r--chromium/chrome/common/importer/importer_test_registry_overrider_win.cc70
-rw-r--r--chromium/chrome/common/importer/importer_test_registry_overrider_win.h34
-rw-r--r--chromium/chrome/common/importer/importer_type.h36
-rw-r--r--chromium/chrome/common/importer/importer_url_row.cc21
-rw-r--r--chromium/chrome/common/importer/importer_url_row.h39
-rw-r--r--chromium/chrome/common/importer/mock_importer_bridge.cc9
-rw-r--r--chromium/chrome/common/importer/mock_importer_bridge.h45
-rw-r--r--chromium/chrome/common/importer/profile_import_process_param_traits.cc33
-rw-r--r--chromium/chrome/common/importer/profile_import_process_param_traits.h10
-rw-r--r--chromium/chrome/common/importer/profile_import_process_param_traits_macros.h94
-rw-r--r--chromium/chrome/common/importer/pstore_declarations.h188
-rw-r--r--chromium/chrome/common/importer/safari_importer_utils.h22
-rw-r--r--chromium/chrome/common/importer/safari_importer_utils.mm29
-rw-r--r--chromium/chrome/common/ini_parser.cc64
-rw-r--r--chromium/chrome/common/ini_parser.h64
-rw-r--r--chromium/chrome/common/ini_parser_unittest.cc130
-rw-r--r--chromium/chrome/common/initialize_extensions_client.cc33
-rw-r--r--chromium/chrome/common/initialize_extensions_client.h18
-rw-r--r--chromium/chrome/common/instant_mojom_traits.h90
-rw-r--r--chromium/chrome/common/logging_chrome.cc434
-rw-r--r--chromium/chrome/common/logging_chrome.h72
-rw-r--r--chromium/chrome/common/mac/DEPS3
-rw-r--r--chromium/chrome/common/mac/OWNERS15
-rw-r--r--chromium/chrome/common/mac/app_mode_chrome_locator.h40
-rw-r--r--chromium/chrome/common/mac/app_mode_chrome_locator.mm153
-rw-r--r--chromium/chrome/common/mac/app_mode_chrome_locator_browsertest.mm133
-rw-r--r--chromium/chrome/common/mac/app_mode_common.h187
-rw-r--r--chromium/chrome/common/mac/app_mode_common.mm73
-rw-r--r--chromium/chrome/common/mac/app_shim_launch.h61
-rw-r--r--chromium/chrome/common/mac/app_shim_param_traits.h26
-rw-r--r--chromium/chrome/common/mac/launchd.h95
-rw-r--r--chromium/chrome/common/mac/launchd.mm165
-rw-r--r--chromium/chrome/common/mac/mock_launchd.h77
-rw-r--r--chromium/chrome/common/mac/mock_launchd.mm168
-rw-r--r--chromium/chrome/common/mac/service_management.h72
-rw-r--r--chromium/chrome/common/mac/service_management.mm217
-rw-r--r--chromium/chrome/common/mac/staging_watcher.h72
-rw-r--r--chromium/chrome/common/mac/staging_watcher.mm191
-rw-r--r--chromium/chrome/common/mac/staging_watcher_unittest.mm181
-rw-r--r--chromium/chrome/common/media/OWNERS9
-rw-r--r--chromium/chrome/common/media/cdm_host_file_path.cc123
-rw-r--r--chromium/chrome/common/media/cdm_host_file_path.h16
-rw-r--r--chromium/chrome/common/media/cdm_manifest.cc372
-rw-r--r--chromium/chrome/common/media/cdm_manifest.h42
-rw-r--r--chromium/chrome/common/media/cdm_manifest_unittest.cc542
-rw-r--r--chromium/chrome/common/media/chrome_media_drm_bridge_client.cc17
-rw-r--r--chromium/chrome/common/media/chrome_media_drm_bridge_client.h29
-rw-r--r--chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.cc89
-rw-r--r--chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.h57
-rw-r--r--chromium/chrome/common/media/component_widevine_cdm_hint_file_linux_unittest.cc119
-rw-r--r--chromium/chrome/common/media/media_resource_provider.cc31
-rw-r--r--chromium/chrome/common/media/media_resource_provider.h15
-rw-r--r--chromium/chrome/common/media_galleries/OWNERS2
-rw-r--r--chromium/chrome/common/media_galleries/metadata_types.h19
-rw-r--r--chromium/chrome/common/media_router/OWNERS2
-rw-r--r--chromium/chrome/common/media_router/discovery/DEPS3
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_internal.cc211
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_internal.h142
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_internal_unittest.cc141
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_service_base.cc125
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_service_base.h135
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_service_base_unittest.cc147
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_service_util.cc29
-rw-r--r--chromium/chrome/common/media_router/discovery/media_sink_service_util.h44
-rw-r--r--chromium/chrome/common/media_router/issue.cc50
-rw-r--r--chromium/chrome/common/media_router/issue.h102
-rw-r--r--chromium/chrome/common/media_router/issue_unittest.cc117
-rw-r--r--chromium/chrome/common/media_router/media_route.cc64
-rw-r--r--chromium/chrome/common/media_router/media_route.h144
-rw-r--r--chromium/chrome/common/media_router/media_route_provider_helper.cc29
-rw-r--r--chromium/chrome/common/media_router/media_route_provider_helper.h27
-rw-r--r--chromium/chrome/common/media_router/media_route_unittest.cc57
-rw-r--r--chromium/chrome/common/media_router/media_sink.cc73
-rw-r--r--chromium/chrome/common/media_router/media_sink.h117
-rw-r--r--chromium/chrome/common/media_router/media_sink_unittest.cc52
-rw-r--r--chromium/chrome/common/media_router/media_source.cc131
-rw-r--r--chromium/chrome/common/media_router/media_source.h122
-rw-r--r--chromium/chrome/common/media_router/media_source_unittest.cc155
-rw-r--r--chromium/chrome/common/media_router/mojom/OWNERS6
-rw-r--r--chromium/chrome/common/media_router/mojom/media_router_mojom_traits.cc226
-rw-r--r--chromium/chrome/common/media_router/mojom/media_router_mojom_traits.h525
-rw-r--r--chromium/chrome/common/media_router/mojom/media_router_mojom_traits_unittest.cc112
-rw-r--r--chromium/chrome/common/media_router/providers/cast/DEPS3
-rw-r--r--chromium/chrome/common/media_router/providers/cast/cast_media_source.cc388
-rw-r--r--chromium/chrome/common/media_router/providers/cast/cast_media_source.h178
-rw-r--r--chromium/chrome/common/media_router/providers/cast/cast_media_source_unittest.cc153
-rw-r--r--chromium/chrome/common/media_router/route_request_result.cc42
-rw-r--r--chromium/chrome/common/media_router/route_request_result.h87
-rw-r--r--chromium/chrome/common/metrics_constants_util_win.cc15
-rw-r--r--chromium/chrome/common/metrics_constants_util_win.h20
-rw-r--r--chromium/chrome/common/multi_process_lock.h33
-rw-r--r--chromium/chrome/common/multi_process_lock_linux.cc111
-rw-r--r--chromium/chrome/common/multi_process_lock_mac.cc59
-rw-r--r--chromium/chrome/common/multi_process_lock_unittest.cc168
-rw-r--r--chromium/chrome/common/multi_process_lock_win.cc63
-rw-r--r--chromium/chrome/common/net/DEPS5
-rw-r--r--chromium/chrome/common/net/OWNERS4
-rw-r--r--chromium/chrome/common/net/net_resource_provider.cc64
-rw-r--r--chromium/chrome/common/net/net_resource_provider.h13
-rw-r--r--chromium/chrome/common/net/safe_search_util.cc113
-rw-r--r--chromium/chrome/common/net/safe_search_util.h55
-rw-r--r--chromium/chrome/common/net/safe_search_util_unittest.cc155
-rw-r--r--chromium/chrome/common/net/x509_certificate_model_nss.cc365
-rw-r--r--chromium/chrome/common/net/x509_certificate_model_nss.h117
-rw-r--r--chromium/chrome/common/net/x509_certificate_model_nss_unittest.cc403
-rw-r--r--chromium/chrome/common/origin_trials/OWNERS3
-rw-r--r--chromium/chrome/common/origin_trials/chrome_origin_trial_policy.cc117
-rw-r--r--chromium/chrome/common/origin_trials/chrome_origin_trial_policy.h41
-rw-r--r--chromium/chrome/common/origin_trials/chrome_origin_trial_policy_unittest.cc262
-rw-r--r--chromium/chrome/common/pdf_util.cc33
-rw-r--r--chromium/chrome/common/pdf_util.h31
-rw-r--r--chromium/chrome/common/pepper_flash.cc155
-rw-r--r--chromium/chrome/common/pepper_flash.h25
-rw-r--r--chromium/chrome/common/pepper_permission_util.cc101
-rw-r--r--chromium/chrome/common/pepper_permission_util.h37
-rw-r--r--chromium/chrome/common/pepper_permission_util_unittest.cc144
-rw-r--r--chromium/chrome/common/performance_manager/OWNERS1
-rw-r--r--chromium/chrome/common/performance_manager/mojom/OWNERS2
-rw-r--r--chromium/chrome/common/plugin_utils.cc23
-rw-r--r--chromium/chrome/common/plugin_utils.h15
-rw-r--r--chromium/chrome/common/ppapi_utils.cc130
-rw-r--r--chromium/chrome/common/ppapi_utils.h12
-rw-r--r--chromium/chrome/common/pref_font_script_names-inl.h163
-rw-r--r--chromium/chrome/common/pref_font_webkit_names.h19
-rw-r--r--chromium/chrome/common/pref_names.cc2815
-rw-r--r--chromium/chrome/common/pref_names.h999
-rw-r--r--chromium/chrome/common/pref_names_util.cc88
-rw-r--r--chromium/chrome/common/pref_names_util.h30
-rw-r--r--chromium/chrome/common/pref_names_util_unittest.cc52
-rw-r--r--chromium/chrome/common/prerender_messages.h67
-rw-r--r--chromium/chrome/common/prerender_types.h25
-rw-r--r--chromium/chrome/common/prerender_url_loader_throttle.cc217
-rw-r--r--chromium/chrome/common/prerender_url_loader_throttle.h83
-rw-r--r--chromium/chrome/common/prerender_util.cc112
-rw-r--r--chromium/chrome/common/prerender_util.h45
-rw-r--r--chromium/chrome/common/process_singleton_lock_posix.cc43
-rw-r--r--chromium/chrome/common/process_singleton_lock_posix.h20
-rw-r--r--chromium/chrome/common/profiler/OWNERS4
-rw-r--r--chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.cc44
-rw-r--r--chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.h35
-rw-r--r--chromium/chrome/common/ref_counted_util.h32
-rw-r--r--chromium/chrome/common/render_messages.h142
-rw-r--r--chromium/chrome/common/safe_browsing/DEPS6
-rw-r--r--chromium/chrome/common/safe_browsing/OWNERS23
-rw-r--r--chromium/chrome/common/safe_browsing/archive_analyzer_results.cc182
-rw-r--r--chromium/chrome/common/safe_browsing/archive_analyzer_results.h55
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor.cc71
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor.h87
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_fuzzer.cc24
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_mac.cc68
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc75
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_posix.cc30
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc106
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_win.cc173
-rw-r--r--chromium/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc210
-rw-r--r--chromium/chrome/common/safe_browsing/client_model.proto97
-rw-r--r--chromium/chrome/common/safe_browsing/crx_info.proto38
-rw-r--r--chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.cc64
-rw-r--r--chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.h43
-rw-r--r--chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac_unittest.cc115
-rw-r--r--chromium/chrome/common/safe_browsing/download_file_types.proto80
-rw-r--r--chromium/chrome/common/safe_browsing/download_type_util.cc101
-rw-r--r--chromium/chrome/common/safe_browsing/download_type_util.h21
-rw-r--r--chromium/chrome/common/safe_browsing/download_type_util_unittest.cc31
-rw-r--r--chromium/chrome/common/safe_browsing/file_type_policies.cc273
-rw-r--r--chromium/chrome/common/safe_browsing/file_type_policies.h156
-rw-r--r--chromium/chrome/common/safe_browsing/file_type_policies_test_util.cc36
-rw-r--r--chromium/chrome/common/safe_browsing/file_type_policies_test_util.h41
-rw-r--r--chromium/chrome/common/safe_browsing/file_type_policies_unittest.cc219
-rw-r--r--chromium/chrome/common/safe_browsing/ipc_protobuf_message_macros.h59
-rw-r--r--chromium/chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h19
-rw-r--r--chromium/chrome/common/safe_browsing/ipc_protobuf_message_test.proto19
-rw-r--r--chromium/chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h24
-rw-r--r--chromium/chrome/common/safe_browsing/ipc_protobuf_message_unittest.cc162
-rw-r--r--chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.cc257
-rw-r--r--chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.h107
-rw-r--r--chromium/chrome/common/safe_browsing/mach_o_image_reader_mac_unittest.cc515
-rw-r--r--chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.cc13
-rw-r--r--chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.h34
-rw-r--r--chromium/chrome/common/safe_browsing/pe_image_reader_win.cc390
-rw-r--r--chromium/chrome/common/safe_browsing/pe_image_reader_win.h164
-rw-r--r--chromium/chrome/common/safe_browsing/pe_image_reader_win_unittest.cc294
-rw-r--r--chromium/chrome/common/safe_browsing/protobuf_message_log_macros.h39
-rw-r--r--chromium/chrome/common/safe_browsing/protobuf_message_read_macros.h61
-rw-r--r--chromium/chrome/common/safe_browsing/protobuf_message_write_macros.h33
-rw-r--r--chromium/chrome/common/safe_browsing/rar_analyzer.cc74
-rw-r--r--chromium/chrome/common/safe_browsing/rar_analyzer.h47
-rw-r--r--chromium/chrome/common/safe_browsing/zip_analyzer.cc111
-rw-r--r--chromium/chrome/common/safe_browsing/zip_analyzer.h26
-rw-r--r--chromium/chrome/common/search/OWNERS2
-rw-r--r--chromium/chrome/common/search/chrome_colors_icon_template.h23
-rw-r--r--chromium/chrome/common/search/generate_colors_info.cc120
-rw-r--r--chromium/chrome/common/search/instant_types.cc54
-rw-r--r--chromium/chrome/common/search/instant_types.h188
-rw-r--r--chromium/chrome/common/search/mock_embedded_search_client.cc8
-rw-r--r--chromium/chrome/common/search/mock_embedded_search_client.h24
-rw-r--r--chromium/chrome/common/search/ntp_logging_events.h190
-rw-r--r--chromium/chrome/common/search/selected_colors_info.h65
-rw-r--r--chromium/chrome/common/secure_origin_whitelist.cc27
-rw-r--r--chromium/chrome/common/secure_origin_whitelist.h24
-rw-r--r--chromium/chrome/common/service_process_util.cc276
-rw-r--r--chromium/chrome/common/service_process_util.h197
-rw-r--r--chromium/chrome/common/service_process_util_linux.cc89
-rw-r--r--chromium/chrome/common/service_process_util_mac.mm405
-rw-r--r--chromium/chrome/common/service_process_util_mac_unittest.mm191
-rw-r--r--chromium/chrome/common/service_process_util_posix.cc360
-rw-r--r--chromium/chrome/common/service_process_util_posix.h93
-rw-r--r--chromium/chrome/common/service_process_util_unittest.cc253
-rw-r--r--chromium/chrome/common/service_process_util_win.cc268
-rw-r--r--chromium/chrome/common/ssl_insecure_content.cc45
-rw-r--r--chromium/chrome/common/ssl_insecure_content.h61
-rw-r--r--chromium/chrome/common/stack_sampling_configuration.cc219
-rw-r--r--chromium/chrome/common/stack_sampling_configuration.h88
-rw-r--r--chromium/chrome/common/themes/OWNERS3
-rw-r--r--chromium/chrome/common/themes/autogenerated_theme_util.cc136
-rw-r--r--chromium/chrome/common/themes/autogenerated_theme_util.h30
-rw-r--r--chromium/chrome/common/thread_profiler.cc312
-rw-r--r--chromium/chrome/common/thread_profiler.h155
-rw-r--r--chromium/chrome/common/thread_profiler_unittest.cc98
-rw-r--r--chromium/chrome/common/time_format_browsertest.cc42
-rw-r--r--chromium/chrome/common/web_application_info.cc19
-rw-r--r--chromium/chrome/common/web_application_info.h75
-rw-r--r--chromium/chrome/common/web_application_info_provider_param_traits.h27
-rw-r--r--chromium/chrome/common/win/OWNERS1
-rw-r--r--chromium/chrome/common/win/eventlog_messages.mc32
-rw-r--r--chromium/chrome/common/win/eventlog_provider.cc9
-rw-r--r--chromium/chrome/common/win/eventlog_provider.ver2
-rwxr-xr-xchromium/chrome/installer/linux/debian/deb_version.py116
-rw-r--r--chromium/chrome/installer/linux/debian/dist_package_versions.json212
-rw-r--r--chromium/chrome/installer/linux/rpm/dist_package_provides.json1695
-rw-r--r--chromium/chrome/renderer/DEPS80
-rw-r--r--chromium/chrome/renderer/OWNERS19
-rw-r--r--chromium/chrome/renderer/app_categorizer_unittest.cc79
-rw-r--r--chromium/chrome/renderer/autofill/OWNERS6
-rw-r--r--chromium/chrome/renderer/autofill/autofill_renderer_browsertest.cc402
-rw-r--r--chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.cc86
-rw-r--r--chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.h205
-rw-r--r--chromium/chrome/renderer/autofill/fake_password_generation_driver.cc22
-rw-r--r--chromium/chrome/renderer/autofill/fake_password_generation_driver.h58
-rw-r--r--chromium/chrome/renderer/autofill/form_autocomplete_browsertest.cc872
-rw-r--r--chromium/chrome/renderer/autofill/form_autofill_browsertest.cc5838
-rw-r--r--chromium/chrome/renderer/autofill/form_control_click_detection_browsertest.cc247
-rw-r--r--chromium/chrome/renderer/autofill/page_passwords_analyser_browsertest.cc279
-rw-r--r--chromium/chrome/renderer/autofill/password_autofill_agent_browsertest.cc3811
-rw-r--r--chromium/chrome/renderer/autofill/password_generation_agent_browsertest.cc1193
-rw-r--r--chromium/chrome/renderer/autofill/password_generation_test_utils.cc84
-rw-r--r--chromium/chrome/renderer/autofill/password_generation_test_utils.h35
-rw-r--r--chromium/chrome/renderer/benchmarking_extension.cc86
-rw-r--r--chromium/chrome/renderer/benchmarking_extension.h27
-rw-r--r--chromium/chrome/renderer/chrome_content_renderer_client.cc1614
-rw-r--r--chromium/chrome/renderer/chrome_content_renderer_client.h308
-rw-r--r--chromium/chrome/renderer/chrome_content_renderer_client_browsertest.cc201
-rw-r--r--chromium/chrome/renderer/chrome_content_renderer_client_receiver_bindings.cc63
-rw-r--r--chromium/chrome/renderer/chrome_content_renderer_client_unittest.cc251
-rw-r--r--chromium/chrome/renderer/chrome_mock_render_thread.cc24
-rw-r--r--chromium/chrome/renderer/chrome_mock_render_thread.h35
-rw-r--r--chromium/chrome/renderer/chrome_render_frame_observer.cc515
-rw-r--r--chromium/chrome/renderer/chrome_render_frame_observer.h127
-rw-r--r--chromium/chrome/renderer/chrome_render_frame_observer_browsertest.cc94
-rw-r--r--chromium/chrome/renderer/chrome_render_thread_observer.cc286
-rw-r--r--chromium/chrome/renderer/chrome_render_thread_observer.h144
-rw-r--r--chromium/chrome/renderer/chromeos_delayed_callback_group.cc115
-rw-r--r--chromium/chrome/renderer/chromeos_delayed_callback_group.h97
-rw-r--r--chromium/chrome/renderer/chromeos_delayed_callback_group_unittest.cc150
-rw-r--r--chromium/chrome/renderer/chromeos_merge_session_loader_throttle.cc79
-rw-r--r--chromium/chrome/renderer/chromeos_merge_session_loader_throttle.h55
-rw-r--r--chromium/chrome/renderer/content_settings_observer.cc669
-rw-r--r--chromium/chrome/renderer/content_settings_observer.h202
-rw-r--r--chromium/chrome/renderer/content_settings_observer_browsertest.cc586
-rw-r--r--chromium/chrome/renderer/content_settings_observer_unittest.cc53
-rw-r--r--chromium/chrome/renderer/custom_menu_commands.h14
-rw-r--r--chromium/chrome/renderer/extensions/DEPS5
-rw-r--r--chromium/chrome/renderer/extensions/OWNERS7
-rw-r--r--chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.cc121
-rw-r--r--chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.h43
-rw-r--r--chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate_unittest.cc79
-rw-r--r--chromium/chrome/renderer/extensions/app_hooks_delegate.cc238
-rw-r--r--chromium/chrome/renderer/extensions/app_hooks_delegate.h108
-rw-r--r--chromium/chrome/renderer/extensions/cast_streaming_native_handler.cc933
-rw-r--r--chromium/chrome/renderer/extensions/cast_streaming_native_handler.h142
-rw-r--r--chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc278
-rw-r--r--chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h36
-rw-r--r--chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.cc413
-rw-r--r--chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.h129
-rw-r--r--chromium/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc46
-rw-r--r--chromium/chrome/renderer/extensions/chrome_v8_extension_handler.cc36
-rw-r--r--chromium/chrome/renderer/extensions/chrome_v8_extension_handler.h38
-rw-r--r--chromium/chrome/renderer/extensions/custom_types_unittest.cc169
-rw-r--r--chromium/chrome/renderer/extensions/extension_hooks_delegate.cc332
-rw-r--r--chromium/chrome/renderer/extensions/extension_hooks_delegate.h65
-rw-r--r--chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc266
-rw-r--r--chromium/chrome/renderer/extensions/extension_localization_peer.cc286
-rw-r--r--chromium/chrome/renderer/extensions/extension_localization_peer.h144
-rw-r--r--chromium/chrome/renderer/extensions/extension_localization_peer_unittest.cc300
-rw-r--r--chromium/chrome/renderer/extensions/extension_process_policy.cc50
-rw-r--r--chromium/chrome/renderer/extensions/extension_process_policy.h29
-rw-r--r--chromium/chrome/renderer/extensions/extension_process_policy_unittest.cc63
-rw-r--r--chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.cc91
-rw-r--r--chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.h37
-rw-r--r--chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.cc74
-rw-r--r--chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.h32
-rw-r--r--chromium/chrome/renderer/extensions/i18n_hooks_delegate_unittest.cc110
-rw-r--r--chromium/chrome/renderer/extensions/media_galleries_custom_bindings.cc61
-rw-r--r--chromium/chrome/renderer/extensions/media_galleries_custom_bindings.h30
-rw-r--r--chromium/chrome/renderer/extensions/notifications_native_handler.cc63
-rw-r--r--chromium/chrome/renderer/extensions/notifications_native_handler.h46
-rw-r--r--chromium/chrome/renderer/extensions/page_capture_custom_bindings.cc57
-rw-r--r--chromium/chrome/renderer/extensions/page_capture_custom_bindings.h28
-rw-r--r--chromium/chrome/renderer/extensions/platform_keys_natives.cc138
-rw-r--r--chromium/chrome/renderer/extensions/platform_keys_natives.h39
-rw-r--r--chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.cc47
-rw-r--r--chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.h33
-rw-r--r--chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc86
-rw-r--r--chromium/chrome/renderer/extensions/resource_request_policy.cc174
-rw-r--r--chromium/chrome/renderer/extensions/resource_request_policy.h53
-rw-r--r--chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.cc66
-rw-r--r--chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.h32
-rw-r--r--chromium/chrome/renderer/extensions/tabs_hooks_delegate.cc166
-rw-r--r--chromium/chrome/renderer/extensions/tabs_hooks_delegate.h53
-rw-r--r--chromium/chrome/renderer/extensions/tabs_hooks_delegate_unittest.cc183
-rw-r--r--chromium/chrome/renderer/instant_restricted_id_cache.h168
-rw-r--r--chromium/chrome/renderer/instant_restricted_id_cache_unittest.cc433
-rw-r--r--chromium/chrome/renderer/loadtimes_extension_bindings.cc406
-rw-r--r--chromium/chrome/renderer/loadtimes_extension_bindings.h26
-rw-r--r--chromium/chrome/renderer/media/DEPS9
-rw-r--r--chromium/chrome/renderer/media/OWNERS11
-rw-r--r--chromium/chrome/renderer/media/cast_ipc_dispatcher.cc147
-rw-r--r--chromium/chrome/renderer/media/cast_ipc_dispatcher.h75
-rw-r--r--chromium/chrome/renderer/media/cast_ipc_dispatcher_unittest.cc64
-rw-r--r--chromium/chrome/renderer/media/cast_receiver_audio_valve.cc50
-rw-r--r--chromium/chrome/renderer/media/cast_receiver_audio_valve.h58
-rw-r--r--chromium/chrome/renderer/media/cast_receiver_session.cc184
-rw-r--r--chromium/chrome/renderer/media/cast_receiver_session.h77
-rw-r--r--chromium/chrome/renderer/media/cast_receiver_session_delegate.cc91
-rw-r--r--chromium/chrome/renderer/media/cast_receiver_session_delegate.h59
-rw-r--r--chromium/chrome/renderer/media/cast_rtp_stream.cc577
-rw-r--r--chromium/chrome/renderer/media/cast_rtp_stream.h95
-rw-r--r--chromium/chrome/renderer/media/cast_session.cc136
-rw-r--r--chromium/chrome/renderer/media/cast_session.h113
-rw-r--r--chromium/chrome/renderer/media/cast_session_browsertest.cc30
-rw-r--r--chromium/chrome/renderer/media/cast_session_delegate.cc332
-rw-r--r--chromium/chrome/renderer/media/cast_session_delegate.h155
-rw-r--r--chromium/chrome/renderer/media/cast_threads.cc25
-rw-r--r--chromium/chrome/renderer/media/cast_threads.h37
-rw-r--r--chromium/chrome/renderer/media/cast_transport_ipc.cc186
-rw-r--r--chromium/chrome/renderer/media/cast_transport_ipc.h86
-rw-r--r--chromium/chrome/renderer/media/cast_udp_transport.cc33
-rw-r--r--chromium/chrome/renderer/media/cast_udp_transport.h46
-rw-r--r--chromium/chrome/renderer/media/chrome_key_systems.cc335
-rw-r--r--chromium/chrome/renderer/media/chrome_key_systems.h20
-rw-r--r--chromium/chrome/renderer/media/chrome_key_systems_provider.cc82
-rw-r--r--chromium/chrome/renderer/media/chrome_key_systems_provider.h64
-rw-r--r--chromium/chrome/renderer/media/chrome_key_systems_provider_unittest.cc161
-rw-r--r--chromium/chrome/renderer/media/flash_embed_rewrite.cc77
-rw-r--r--chromium/chrome/renderer/media/flash_embed_rewrite.h23
-rw-r--r--chromium/chrome/renderer/media/flash_embed_rewrite_unittest.cc161
-rw-r--r--chromium/chrome/renderer/media/webrtc_logging_agent_impl.cc134
-rw-r--r--chromium/chrome/renderer/media/webrtc_logging_agent_impl.h43
-rw-r--r--chromium/chrome/renderer/media/webrtc_logging_agent_impl_unittest.cc99
-rw-r--r--chromium/chrome/renderer/net/DEPS8
-rw-r--r--chromium/chrome/renderer/net/OWNERS11
-rw-r--r--chromium/chrome/renderer/net/available_offline_content_helper.cc196
-rw-r--r--chromium/chrome/renderer/net/available_offline_content_helper.h70
-rw-r--r--chromium/chrome/renderer/net/net_error_helper.cc621
-rw-r--r--chromium/chrome/renderer/net/net_error_helper.h206
-rw-r--r--chromium/chrome/renderer/net/net_error_helper_core.cc1067
-rw-r--r--chromium/chrome/renderer/net/net_error_helper_core.h346
-rw-r--r--chromium/chrome/renderer/net/net_error_helper_core_unittest.cc2784
-rw-r--r--chromium/chrome/renderer/net/net_error_page_controller.cc162
-rw-r--r--chromium/chrome/renderer/net/net_error_page_controller.h126
-rw-r--r--chromium/chrome/renderer/net/page_auto_fetcher_helper_android.cc59
-rw-r--r--chromium/chrome/renderer/net/page_auto_fetcher_helper_android.h50
-rw-r--r--chromium/chrome/renderer/net_benchmarking_extension.cc115
-rw-r--r--chromium/chrome/renderer/net_benchmarking_extension.h23
-rw-r--r--chromium/chrome/renderer/performance_manager/OWNERS1
-rw-r--r--chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.cc36
-rw-r--r--chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.h34
-rw-r--r--chromium/chrome/renderer/plugins/DEPS4
-rw-r--r--chromium/chrome/renderer/plugins/OWNERS2
-rw-r--r--chromium/chrome/renderer/plugins/chrome_plugin_placeholder.cc399
-rw-r--r--chromium/chrome/renderer/plugins/chrome_plugin_placeholder.h105
-rw-r--r--chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc64
-rw-r--r--chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.h41
-rw-r--r--chromium/chrome/renderer/plugins/pdf_plugin_placeholder.cc56
-rw-r--r--chromium/chrome/renderer/plugins/pdf_plugin_placeholder.h40
-rw-r--r--chromium/chrome/renderer/plugins/plugin_preroller.cc92
-rw-r--r--chromium/chrome/renderer/plugins/plugin_preroller.h57
-rw-r--r--chromium/chrome/renderer/plugins/plugin_uma.cc182
-rw-r--r--chromium/chrome/renderer/plugins/plugin_uma.h91
-rw-r--r--chromium/chrome/renderer/plugins/plugin_uma_unittest.cc148
-rw-r--r--chromium/chrome/renderer/plugins/power_saver_info.cc75
-rw-r--r--chromium/chrome/renderer/plugins/power_saver_info.h51
-rw-r--r--chromium/chrome/renderer/prerender/OWNERS3
-rw-r--r--chromium/chrome/renderer/prerender/prerender_dispatcher.cc209
-rw-r--r--chromium/chrome/renderer/prerender/prerender_dispatcher.h87
-rw-r--r--chromium/chrome/renderer/prerender/prerender_dispatcher_unittest.cc156
-rw-r--r--chromium/chrome/renderer/prerender/prerender_extra_data.cc31
-rw-r--r--chromium/chrome/renderer/prerender/prerender_extra_data.h40
-rw-r--r--chromium/chrome/renderer/prerender/prerender_helper.cc86
-rw-r--r--chromium/chrome/renderer/prerender/prerender_helper.h62
-rw-r--r--chromium/chrome/renderer/prerender/prerenderer_client.cc46
-rw-r--r--chromium/chrome/renderer/prerender/prerenderer_client.h33
-rw-r--r--chromium/chrome/renderer/previews/OWNERS3
-rw-r--r--chromium/chrome/renderer/previews/resource_loading_hints_agent.cc103
-rw-r--r--chromium/chrome/renderer/previews/resource_loading_hints_agent.h67
-rw-r--r--chromium/chrome/renderer/printing/OWNERS3
-rw-r--r--chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.cc91
-rw-r--r--chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.h27
-rw-r--r--chromium/chrome/renderer/safe_browsing/DEPS28
-rw-r--r--chromium/chrome/renderer/safe_browsing/OWNERS5
-rw-r--r--chromium/chrome/renderer/safe_browsing/feature_extractor_clock.cc15
-rw-r--r--chromium/chrome/renderer/safe_browsing/feature_extractor_clock.h30
-rw-r--r--chromium/chrome/renderer/safe_browsing/features.cc83
-rw-r--r--chromium/chrome/renderer/safe_browsing/features.h181
-rw-r--r--chromium/chrome/renderer/safe_browsing/features_unittest.cc47
-rw-r--r--chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.cc14
-rw-r--r--chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.h29
-rw-r--r--chromium/chrome/renderer/safe_browsing/murmurhash3_util.cc16
-rw-r--r--chromium/chrome/renderer/safe_browsing/murmurhash3_util.h20
-rw-r--r--chromium/chrome/renderer/safe_browsing/murmurhash3_util_unittest.cc19
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_classifier.cc220
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_classifier.h155
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc268
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc307
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.h165
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc477
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc504
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h154
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc578
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc295
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h172
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc468
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc116
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.h50
-rw-r--r--chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc129
-rw-r--r--chromium/chrome/renderer/safe_browsing/scorer.cc134
-rw-r--r--chromium/chrome/renderer/safe_browsing/scorer.h93
-rw-r--r--chromium/chrome/renderer/safe_browsing/scorer_unittest.cc148
-rw-r--r--chromium/chrome/renderer/safe_browsing/test_utils.cc24
-rw-r--r--chromium/chrome/renderer/safe_browsing/test_utils.h19
-rw-r--r--chromium/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc461
-rw-r--r--chromium/chrome/renderer/sandbox_status_extension_android.cc165
-rw-r--r--chromium/chrome/renderer/sandbox_status_extension_android.h78
-rw-r--r--chromium/chrome/renderer/searchbox/DEPS4
-rw-r--r--chromium/chrome/renderer/searchbox/OWNERS4
-rw-r--r--chromium/chrome/renderer/searchbox/search_bouncer.cc56
-rw-r--r--chromium/chrome/renderer/searchbox/search_bouncer.h48
-rw-r--r--chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc29
-rw-r--r--chromium/chrome/renderer/searchbox/searchbox.cc596
-rw-r--r--chromium/chrome/renderer/searchbox/searchbox.h274
-rw-r--r--chromium/chrome/renderer/searchbox/searchbox_extension.cc1469
-rw-r--r--chromium/chrome/renderer/searchbox/searchbox_extension.h57
-rw-r--r--chromium/chrome/renderer/searchbox/searchbox_unittest.cc271
-rw-r--r--chromium/chrome/renderer/subresource_redirect/DEPS3
-rw-r--r--chromium/chrome/renderer/subresource_redirect/OWNERS4
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.cc40
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.h17
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments_unittest.cc117
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.cc26
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.h21
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc143
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h43
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle_unittest.cc62
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.cc63
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.h21
-rw-r--r--chromium/chrome/renderer/subresource_redirect/subresource_redirect_util_unittest.cc72
-rw-r--r--chromium/chrome/renderer/supervised_user/OWNERS6
-rw-r--r--chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.cc85
-rw-r--r--chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.h62
-rw-r--r--chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h21
-rw-r--r--chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.cc66
-rw-r--r--chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h61
-rw-r--r--chromium/chrome/renderer/sync_encryption_keys_extension.cc142
-rw-r--r--chromium/chrome/renderer/sync_encryption_keys_extension.h44
-rw-r--r--chromium/chrome/renderer/translate/OWNERS2
-rw-r--r--chromium/chrome/renderer/translate/translate_helper_browsertest.cc523
-rw-r--r--chromium/chrome/renderer/translate/translate_script_browsertest.cc237
-rw-r--r--chromium/chrome/renderer/url_loader_throttle_provider_impl.cc277
-rw-r--r--chromium/chrome/renderer/url_loader_throttle_provider_impl.h78
-rw-r--r--chromium/chrome/renderer/v8_unwinder.cc112
-rw-r--r--chromium/chrome/renderer/v8_unwinder.h39
-rw-r--r--chromium/chrome/renderer/v8_unwinder_unittest.cc223
-rw-r--r--chromium/chrome/renderer/web_apps.cc120
-rw-r--r--chromium/chrome/renderer/web_apps.h38
-rw-r--r--chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.cc53
-rw-r--r--chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.h48
-rw-r--r--chromium/chrome/renderer/worker_content_settings_client.cc123
-rw-r--r--chromium/chrome/renderer/worker_content_settings_client.h63
-rw-r--r--chromium/gpu/config/gpu_lists_version.h2
1821 files changed, 159191 insertions, 101777 deletions
diff --git a/chromium/DEPS b/chromium/DEPS
index 59ac669520a..a0ebad6ff20 100644
--- a/chromium/DEPS
+++ b/chromium/DEPS
@@ -1533,7 +1533,7 @@ deps = {
Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'),
'src-internal': {
- 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3f5c111149ef29832066640751a8a5450dd53bc4',
+ 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a118aff1f09e9b02d88c7ab0a3a8ebcdffce44cf',
'condition': 'checkout_src_internal',
},
diff --git a/chromium/build/util/LASTCHANGE b/chromium/build/util/LASTCHANGE
index 6b8bbfa8331..40e6fc9d764 100644
--- a/chromium/build/util/LASTCHANGE
+++ b/chromium/build/util/LASTCHANGE
@@ -1 +1 @@
-LASTCHANGE=cd3a886c28f57603dea2a9050d2aadef3e8c30a4-refs/branch-heads/3945@{#1070}
+LASTCHANGE=a92c93df20fcf33ca6a37962134389f0b85ec9ab-refs/branch-heads/3945@{#1085}
diff --git a/chromium/build/util/LASTCHANGE.committime b/chromium/build/util/LASTCHANGE.committime
index c7b8003e8df..614336b78b3 100644
--- a/chromium/build/util/LASTCHANGE.committime
+++ b/chromium/build/util/LASTCHANGE.committime
@@ -1 +1 @@
-1579744552 \ No newline at end of file
+1580433036 \ No newline at end of file
diff --git a/chromium/chrome/VERSION b/chromium/chrome/VERSION
index be1d2e2bda5..76fd95cfee3 100644
--- a/chromium/chrome/VERSION
+++ b/chromium/chrome/VERSION
@@ -1,4 +1,4 @@
MAJOR=79
MINOR=0
BUILD=3945
-PATCH=139
+PATCH=147
diff --git a/chromium/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java b/chromium/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java
deleted file mode 100644
index 6edfbc23ffe..00000000000
--- a/chromium/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.accessibility;
-
-import android.annotation.SuppressLint;
-import android.content.SharedPreferences;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.ObserverList;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.util.MathUtils;
-
-/**
- * Singleton class for accessing these font size-related preferences:
- * - User Font Scale Factor: the font scale value that the user sees and can set. This is a value
- * between 50% and 200% (i.e. 0.5 and 2).
- * - Font Scale Factor: the font scale factor applied to webpage text during font boosting. This
- * equals the user font scale factor times the Android system font scale factor, which
- * reflects the font size indicated in Android settings > Display > Font size.
- * - Force Enable Zoom: whether force enable zoom is on or off
- * - User Set Force Enable Zoom: whether the user has manually set the force enable zoom button
- */
-public class FontSizePrefs {
- /**
- * The font scale threshold beyond which force enable zoom is automatically turned on. It
- * is chosen such that force enable zoom will be activated when the accessibility large text
- * setting is on (i.e. this value should be the same as or lesser than the font size scale used
- * by accessiblity large text).
- */
- public static final float FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER = 1.3f;
-
- private static final float EPSILON = 0.001f;
-
- static final String PREF_USER_SET_FORCE_ENABLE_ZOOM = "user_set_force_enable_zoom";
- static final String PREF_USER_FONT_SCALE_FACTOR = "user_font_scale_factor";
-
- @SuppressLint("StaticFieldLeak")
- private static FontSizePrefs sFontSizePrefs;
-
- private final long mFontSizePrefsAndroidPtr;
- private final ObserverList<FontSizePrefsObserver> mObserverList;
-
- private Float mSystemFontScaleForTests;
-
- /**
- * Interface for observing changes in font size-related preferences.
- */
- public interface FontSizePrefsObserver {
- void onFontScaleFactorChanged(float fontScaleFactor, float userFontScaleFactor);
- void onForceEnableZoomChanged(boolean enabled);
- }
-
- private FontSizePrefs() {
- mFontSizePrefsAndroidPtr = FontSizePrefsJni.get().init(FontSizePrefs.this);
- mObserverList = new ObserverList<FontSizePrefsObserver>();
- }
-
- /**
- * Returns the singleton FontSizePrefs, constructing it if it doesn't already exist.
- */
- public static FontSizePrefs getInstance() {
- ThreadUtils.assertOnUiThread();
- if (sFontSizePrefs == null) {
- sFontSizePrefs = new FontSizePrefs();
- }
- return sFontSizePrefs;
- }
-
- /**
- * Adds an observer to listen for changes to font scale-related preferences.
- */
- public void addObserver(FontSizePrefsObserver observer) {
- mObserverList.addObserver(observer);
- }
-
- /**
- * Removes an observer so it will no longer receive updates for changes to font scale-related
- * preferences.
- */
- public void removeObserver(FontSizePrefsObserver observer) {
- mObserverList.removeObserver(observer);
- }
-
- /**
- * Updates the fontScaleFactor based on the userFontScaleFactor and the system-wide font scale.
- *
- * This should be called during application start-up and whenever the system font size changes.
- */
- public void onSystemFontScaleChanged() {
- float userFontScaleFactor = getUserFontScaleFactor();
- if (userFontScaleFactor != 0f) {
- setFontScaleFactor(userFontScaleFactor * getSystemFontScale());
- }
- }
-
- /**
- * Sets the userFontScaleFactor. This should be a value between .5 and 2.
- */
- public void setUserFontScaleFactor(float userFontScaleFactor) {
- SharedPreferences.Editor sharedPreferencesEditor =
- ContextUtils.getAppSharedPreferences().edit();
- sharedPreferencesEditor.putFloat(PREF_USER_FONT_SCALE_FACTOR, userFontScaleFactor);
- sharedPreferencesEditor.apply();
- setFontScaleFactor(userFontScaleFactor * getSystemFontScale());
- }
-
- /**
- * Returns the userFontScaleFactor. This is the value that should be displayed to the user.
- */
- public float getUserFontScaleFactor() {
- SharedPreferences sharedPreferences = ContextUtils.getAppSharedPreferences();
- float userFontScaleFactor = sharedPreferences.getFloat(PREF_USER_FONT_SCALE_FACTOR, 0f);
- if (userFontScaleFactor == 0f) {
- float fontScaleFactor = getFontScaleFactor();
-
- if (Math.abs(fontScaleFactor - 1f) <= EPSILON) {
- // If the font scale factor is 1, assume that the user hasn't customized their font
- // scale and/or wants the default value
- userFontScaleFactor = 1f;
- } else {
- // Initialize userFontScaleFactor based on fontScaleFactor, since
- // userFontScaleFactor was added long after fontScaleFactor.
- userFontScaleFactor =
- MathUtils.clamp(fontScaleFactor / getSystemFontScale(), 0.5f, 2f);
- }
- SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit();
- sharedPreferencesEditor.putFloat(PREF_USER_FONT_SCALE_FACTOR, userFontScaleFactor);
- sharedPreferencesEditor.apply();
- }
- return userFontScaleFactor;
- }
-
- /**
- * Returns the fontScaleFactor. This is the product of the userFontScaleFactor and the system
- * font scale, and is the amount by which webpage text will be scaled during font boosting.
- */
- public float getFontScaleFactor() {
- return FontSizePrefsJni.get().getFontScaleFactor(
- mFontSizePrefsAndroidPtr, FontSizePrefs.this);
- }
-
- /**
- * Sets forceEnableZoom due to a user request (e.g. checking a checkbox). This implicitly sets
- * userSetForceEnableZoom.
- */
- public void setForceEnableZoomFromUser(boolean enabled) {
- setForceEnableZoom(enabled, true);
- }
-
- /**
- * Returns whether forceEnableZoom is enabled.
- */
- public boolean getForceEnableZoom() {
- return FontSizePrefsJni.get().getForceEnableZoom(
- mFontSizePrefsAndroidPtr, FontSizePrefs.this);
- }
-
- /**
- * Sets a mock value for the system-wide font scale. Use only in tests.
- */
- @VisibleForTesting
- void setSystemFontScaleForTest(float fontScale) {
- mSystemFontScaleForTests = fontScale;
- }
-
- private float getSystemFontScale() {
- if (mSystemFontScaleForTests != null) return mSystemFontScaleForTests;
- return ContextUtils.getApplicationContext().getResources().getConfiguration().fontScale;
- }
-
- private void setForceEnableZoom(boolean enabled, boolean fromUser) {
- SharedPreferences.Editor sharedPreferencesEditor =
- ContextUtils.getAppSharedPreferences().edit();
- sharedPreferencesEditor.putBoolean(PREF_USER_SET_FORCE_ENABLE_ZOOM, fromUser);
- sharedPreferencesEditor.apply();
- FontSizePrefsJni.get().setForceEnableZoom(
- mFontSizePrefsAndroidPtr, FontSizePrefs.this, enabled);
- }
-
- private boolean getUserSetForceEnableZoom() {
- return ContextUtils.getAppSharedPreferences().getBoolean(
- PREF_USER_SET_FORCE_ENABLE_ZOOM, false);
- }
-
- private void setFontScaleFactor(float fontScaleFactor) {
- float previousFontScaleFactor = getFontScaleFactor();
- FontSizePrefsJni.get().setFontScaleFactor(
- mFontSizePrefsAndroidPtr, FontSizePrefs.this, fontScaleFactor);
-
- if (previousFontScaleFactor < FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER
- && fontScaleFactor >= FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER
- && !getForceEnableZoom()) {
- // If the font scale factor just crossed above the threshold, set force enable zoom even
- // if the user has previously unset it.
- setForceEnableZoom(true, false);
- } else if (previousFontScaleFactor >= FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER
- && fontScaleFactor < FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER
- && !getUserSetForceEnableZoom()) {
- // If the font scale factor just crossed below the threshold and the user didn't set
- // force enable zoom manually, then unset force enable zoom.
- setForceEnableZoom(false, false);
- }
- }
-
- @CalledByNative
- private void onFontScaleFactorChanged(float fontScaleFactor) {
- float userFontScaleFactor = getUserFontScaleFactor();
- for (FontSizePrefsObserver observer : mObserverList) {
- observer.onFontScaleFactorChanged(fontScaleFactor, userFontScaleFactor);
- }
- }
-
- @CalledByNative
- private void onForceEnableZoomChanged(boolean enabled) {
- for (FontSizePrefsObserver observer : mObserverList) {
- observer.onForceEnableZoomChanged(enabled);
- }
- }
-
- @NativeMethods
- interface Natives {
- long init(FontSizePrefs caller);
- void setFontScaleFactor(
- long nativeFontSizePrefsAndroid, FontSizePrefs caller, float fontScaleFactor);
- float getFontScaleFactor(long nativeFontSizePrefsAndroid, FontSizePrefs caller);
- boolean getForceEnableZoom(long nativeFontSizePrefsAndroid, FontSizePrefs caller);
- void setForceEnableZoom(
- long nativeFontSizePrefsAndroid, FontSizePrefs caller, boolean enabled);
- }
-}
diff --git a/chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintShareActivity.java b/chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintShareActivity.java
deleted file mode 100644
index 75a972b9b41..00000000000
--- a/chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintShareActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.printing;
-
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.preferences.PrefServiceBridge;
-import org.chromium.chrome.browser.share.ShareActivity;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.printing.PrintingController;
-import org.chromium.printing.PrintingControllerImpl;
-
-/**
- * A simple activity that allows Chrome to expose print as an option in the share menu.
- */
-public class PrintShareActivity extends ShareActivity {
- @Override
- protected void handleShareAction(ChromeActivity triggeringActivity) {
- triggeringActivity.onMenuOrKeyboardAction(R.id.print_id, true);
- }
-
- public static boolean featureIsAvailable(Tab currentTab) {
- PrintingController printingController = PrintingControllerImpl.getInstance();
- return (printingController != null && !currentTab.isNativePage()
- && !currentTab.isShowingInterstitialPage() && !printingController.isBusy()
- && PrefServiceBridge.getInstance().isPrintingEnabled());
- }
-}
diff --git a/chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java b/chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java
deleted file mode 100644
index ffa94ee1a6f..00000000000
--- a/chromium/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.printing;
-
-import android.text.TextUtils;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.printing.Printable;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Wraps printing related functionality of a {@link Tab} object.
- *
- * This class doesn't have any lifetime expectations with regards to Tab, since we keep a weak
- * reference.
- */
-@JNINamespace("printing")
-public class TabPrinter implements Printable {
- private static final String TAG = "printing";
-
- private final WeakReference<Tab> mTab;
- private final String mDefaultTitle;
-
- @CalledByNative
- private static TabPrinter getPrintable(Tab tab) {
- return new TabPrinter(tab);
- }
-
- public TabPrinter(Tab tab) {
- mTab = new WeakReference<Tab>(tab);
- mDefaultTitle = ContextUtils.getApplicationContext().getResources().getString(
- R.string.menu_print);
- }
-
- @Override
- public boolean print(int renderProcessId, int renderFrameId) {
- if (!canPrint()) return false;
- Tab tab = mTab.get();
- assert tab != null && tab.isInitialized();
- return TabPrinterJni.get().print(tab.getWebContents(), renderProcessId, renderFrameId);
- }
-
- @Override
- public String getTitle() {
- Tab tab = mTab.get();
- if (tab == null) return mDefaultTitle;
-
- String title = tab.getTitle();
- if (!TextUtils.isEmpty(title)) return title;
-
- String url = tab.getUrl();
- if (!TextUtils.isEmpty(url)) return url;
-
- return mDefaultTitle;
- }
-
- @Override
- public boolean canPrint() {
- Tab tab = mTab.get();
- if (tab == null || !tab.isInitialized()) {
- // tab.isInitialized() will be false if tab is in destroy process.
- Log.d(TAG, "Tab is not avaliable for printing.");
- return false;
- }
- return true;
- }
-
- @NativeMethods
- interface Natives {
- boolean print(WebContents webContents, int renderProcessId, int renderFrameId);
- }
-}
diff --git a/chromium/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java b/chromium/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java
deleted file mode 100644
index 1c7944fc03b..00000000000
--- a/chromium/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.accessibility;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.accessibility.FontSizePrefs.FontSizePrefsObserver;
-import org.chromium.chrome.test.ChromeBrowserTestRule;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-
-/**
- * Tests for {@link FontSizePrefs}.
- */
-@RunWith(BaseJUnit4ClassRunner.class)
-public class FontSizePrefsTest {
- @Rule
- public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
-
- private static final float EPSILON = 0.001f;
- private FontSizePrefs mFontSizePrefs;
-
- @Before
- public void setUp() {
- resetSharedPrefs();
- Context context = InstrumentationRegistry.getTargetContext();
- mFontSizePrefs = getFontSizePrefs(context);
- setSystemFontScaleForTest(1.0f);
- }
-
- private void resetSharedPrefs() {
- SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
- editor.remove(FontSizePrefs.PREF_USER_SET_FORCE_ENABLE_ZOOM);
- editor.remove(FontSizePrefs.PREF_USER_FONT_SCALE_FACTOR);
- editor.apply();
- }
-
- @Test
- @SmallTest
- @Feature({"Accessibility"})
- public void testForceEnableZoom() {
- Assert.assertEquals(false, getForceEnableZoom());
-
- TestingObserver observer = createAndAddFontSizePrefsObserver();
-
- setUserFontScaleFactor(1.5f);
- Assert.assertEquals(true, getForceEnableZoom());
- observer.assertConsistent();
- setUserFontScaleFactor(0.7f);
- Assert.assertEquals(false, getForceEnableZoom());
- observer.assertConsistent();
-
- setForceEnableZoomFromUser(true);
- Assert.assertEquals(true, getForceEnableZoom());
- observer.assertConsistent();
- setUserFontScaleFactor(1.5f);
- Assert.assertEquals(true, getForceEnableZoom());
- observer.assertConsistent();
- setUserFontScaleFactor(0.7f);
- Assert.assertEquals(true, getForceEnableZoom());
- observer.assertConsistent();
-
- setForceEnableZoomFromUser(false);
- Assert.assertEquals(false, getForceEnableZoom());
- observer.assertConsistent();
- setUserFontScaleFactor(1.5f);
- Assert.assertEquals(true, getForceEnableZoom());
- observer.assertConsistent();
- setUserFontScaleFactor(0.7f);
- Assert.assertEquals(false, getForceEnableZoom());
- observer.assertConsistent();
-
- // Force enable zoom should depend on fontScaleFactor, not on userFontScaleFactor.
- setSystemFontScaleForTest(2.0f);
- Assert.assertEquals(true, getForceEnableZoom());
- observer.assertConsistent();
- setSystemFontScaleForTest(1.0f);
- Assert.assertEquals(false, getForceEnableZoom());
- observer.assertConsistent();
- }
-
- @Test
- @SmallTest
- @Feature({"Accessibility"})
- public void testFontScaleFactor() {
- Assert.assertEquals(1f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(1f, getFontScaleFactor(), EPSILON);
-
- TestingObserver observer = createAndAddFontSizePrefsObserver();
-
- setUserFontScaleFactor(1.5f);
- Assert.assertEquals(1.5f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(1.5f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
-
- setUserFontScaleFactor(0.7f);
- Assert.assertEquals(0.7f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(0.7f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
-
- // Force enable zoom shouldn't affect font scale factor.
- setForceEnableZoomFromUser(true);
- Assert.assertEquals(0.7f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(0.7f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
-
- setForceEnableZoomFromUser(false);
- Assert.assertEquals(0.7f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(0.7f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
-
- // System font scale should affect fontScaleFactor, but not userFontScaleFactor.
- setSystemFontScaleForTest(1.3f);
- Assert.assertEquals(0.7f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(0.7f * 1.3f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
-
- setUserFontScaleFactor(1.5f);
- Assert.assertEquals(1.5f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(1.5f * 1.3f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
-
- setSystemFontScaleForTest(0.8f);
- Assert.assertEquals(1.5f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(1.5f * 0.8f, getFontScaleFactor(), EPSILON);
- observer.assertConsistent();
- }
-
- @Test
- @SmallTest
- @Feature({"Accessibility"})
- public void testUpgradeToUserFontScaleFactor() {
- setSystemFontScaleForTest(1.3f);
- setUserFontScaleFactor(1.5f);
-
- // Delete PREF_USER_FONT_SCALE_FACTOR. This simulates the condition just after upgrading to
- // M51, when userFontScaleFactor was added.
- SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
- editor.remove(FontSizePrefs.PREF_USER_FONT_SCALE_FACTOR).apply();
-
- // Intial userFontScaleFactor should be set to fontScaleFactor / systemFontScale.
- Assert.assertEquals(1.5f, getUserFontScaleFactor(), EPSILON);
- Assert.assertEquals(1.5f * 1.3f, getFontScaleFactor(), EPSILON);
- }
-
- private class TestingObserver implements FontSizePrefsObserver {
- private float mUserFontScaleFactor = getUserFontScaleFactor();
- private float mFontScaleFactor = getFontScaleFactor();
- private boolean mForceEnableZoom = getForceEnableZoom();
-
- @Override
- public void onFontScaleFactorChanged(float fontScaleFactor, float userFontScaleFactor) {
- mFontScaleFactor = fontScaleFactor;
- mUserFontScaleFactor = userFontScaleFactor;
- }
-
- @Override
- public void onForceEnableZoomChanged(boolean enabled) {
- mForceEnableZoom = enabled;
- }
-
- private void assertConsistent() {
- TestThreadUtils.runOnUiThreadBlocking(() -> {
- Assert.assertEquals(getUserFontScaleFactor(), mUserFontScaleFactor, EPSILON);
- Assert.assertEquals(getFontScaleFactor(), mFontScaleFactor, EPSILON);
- Assert.assertEquals(getForceEnableZoom(), mForceEnableZoom);
- });
- }
- }
-
- private FontSizePrefs getFontSizePrefs(final Context context) {
- return TestThreadUtils.runOnUiThreadBlockingNoException(() -> FontSizePrefs.getInstance());
- }
-
- private TestingObserver createAndAddFontSizePrefsObserver() {
- return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
- TestingObserver observer = new TestingObserver();
- mFontSizePrefs.addObserver(observer);
- return observer;
- });
- }
-
- private void setUserFontScaleFactor(final float fontsize) {
- TestThreadUtils.runOnUiThreadBlocking(
- () -> mFontSizePrefs.setUserFontScaleFactor(fontsize));
- }
-
- private float getUserFontScaleFactor() {
- return TestThreadUtils.runOnUiThreadBlockingNoException(
- () -> mFontSizePrefs.getUserFontScaleFactor());
- }
-
- private float getFontScaleFactor() {
- return TestThreadUtils.runOnUiThreadBlockingNoException(
- () -> mFontSizePrefs.getFontScaleFactor());
- }
-
- private void setForceEnableZoomFromUser(final boolean enabled) {
- TestThreadUtils.runOnUiThreadBlocking(
- () -> mFontSizePrefs.setForceEnableZoomFromUser(enabled));
- }
-
- private boolean getForceEnableZoom() {
- return TestThreadUtils.runOnUiThreadBlockingNoException(
- () -> mFontSizePrefs.getForceEnableZoom());
- }
-
- private void setSystemFontScaleForTest(final float systemFontScale) {
- TestThreadUtils.runOnUiThreadBlocking(() -> {
- mFontSizePrefs.setSystemFontScaleForTest(systemFontScale);
- mFontSizePrefs.onSystemFontScaleChanged();
- });
- }
-}
diff --git a/chromium/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java b/chromium/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java
deleted file mode 100644
index d3c8a7a8e24..00000000000
--- a/chromium/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java
+++ /dev/null
@@ -1,449 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.printing;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintDocumentInfo;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
-import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.base.test.util.TestFileUtil;
-import org.chromium.base.test.util.UrlUtils;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModelUtils;
-import org.chromium.chrome.test.ChromeActivityTestRule;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-import org.chromium.printing.PrintDocumentAdapterWrapper;
-import org.chromium.printing.PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper;
-import org.chromium.printing.PrintDocumentAdapterWrapper.WriteResultCallbackWrapper;
-import org.chromium.printing.PrintManagerDelegate;
-import org.chromium.printing.PrintingControllerImpl;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Tests Android printing.
- * TODO(cimamoglu): Add a test with cancellation.
- * TODO(cimamoglu): Add a test with multiple, stacked onLayout/onWrite calls.
- * TODO(cimamoglu): Add a test which emulates Chromium failing to generate a PDF.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@RetryOnFailure
-@SuppressLint("NewApi")
-@MinAndroidSdkLevel(Build.VERSION_CODES.KITKAT)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-public class PrintingControllerTest {
- @Rule
- public final ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
- new ChromeActivityTestRule<>(ChromeActivity.class);
-
- private static final String TEMP_FILE_NAME = "temp_print";
- private static final String TEMP_FILE_EXTENSION = ".pdf";
- private static final String PRINT_JOB_NAME = "foo";
- private static final String URL = UrlUtils.encodeHtmlDataUri(
- "<html><head></head><body>foo</body></html>");
- private static final String PDF_PREAMBLE = "%PDF-1";
- private static final long TEST_TIMEOUT = 20000L;
-
- @Before
- public void setUp() {
- // Do nothing.
- }
-
- private static class LayoutResultCallbackWrapperMock implements LayoutResultCallbackWrapper {
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {}
-
- @Override
- public void onLayoutFailed(CharSequence error) {}
-
- @Override
- public void onLayoutCancelled() {}
- }
-
- private static class WriteResultCallbackWrapperMock implements WriteResultCallbackWrapper {
- @Override
- public void onWriteFinished(PageRange[] pages) {}
-
- @Override
- public void onWriteFailed(CharSequence error) {}
-
- @Override
- public void onWriteCancelled() {}
- }
-
- private static class WaitForOnWriteHelper extends CallbackHelper {
- public void waitForCallback(String msg) throws TimeoutException {
- waitForFirst(msg, TEST_TIMEOUT, TimeUnit.MILLISECONDS);
- }
- }
-
- private static class TemporaryFileHandler implements AutoCloseable {
- private File mTempFile;
- private ParcelFileDescriptor mFileDescriptor;
-
- public TemporaryFileHandler() throws IOException {
- mTempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTENSION);
- try {
- mFileDescriptor =
- ParcelFileDescriptor.open(mTempFile, ParcelFileDescriptor.MODE_READ_WRITE);
- } catch (FileNotFoundException e) {
- // Exception happened, can't continue, cleanup the file.
- TestFileUtil.deleteFile(mTempFile.getAbsolutePath());
- throw new FileNotFoundException();
- }
- }
-
- ParcelFileDescriptor getFileDescriptor() {
- return mFileDescriptor;
- }
-
- @Override
- public void close() throws IOException {
- try {
- mFileDescriptor.close();
- } finally {
- TestFileUtil.deleteFile(mTempFile.getAbsolutePath());
- }
- }
- }
-
- private static class PrintingControllerImplPdfWritingDone extends PrintingControllerImpl {
- private WaitForOnWriteHelper mWaitForOnWrite;
-
- public PrintingControllerImplPdfWritingDone(
- PrintDocumentAdapterWrapper printDocumentAdapterWrapper, String errorText,
- WaitForOnWriteHelper waitForOnWrite) {
- super(printDocumentAdapterWrapper, errorText);
- mWaitForOnWrite = waitForOnWrite;
- sInstance = this;
- }
-
- @Override
- public void pdfWritingDone(int pageCount) {
- mWaitForOnWrite.notifyCalled();
- }
- }
-
- /**
- * Test a basic printing flow by emulating the corresponding system calls to the printing
- * controller: onStart, onLayout, onWrite, onFinish. Each one is called once, and in this
- * order, in the UI thread.
- */
- @Test
- @TargetApi(Build.VERSION_CODES.KITKAT)
- @LargeTest
- @Feature({"Printing"})
- public void testNormalPrintingFlow() throws Throwable {
- if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) return;
-
- mActivityTestRule.startMainActivityWithURL(URL);
- final Tab currentTab = mActivityTestRule.getActivity().getActivityTab();
-
- final PrintingControllerImpl printingController = createControllerOnUiThread();
-
- startControllerOnUiThread(printingController, currentTab);
- // {@link PrintDocumentAdapter#onStart} is always called first.
- callStartOnUiThread(printingController);
-
- // Create a temporary file to save the PDF.
- final File tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTENSION);
- final ParcelFileDescriptor fileDescriptor =
- ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
-
- // Use this to wait for PDF generation to complete, as it will happen asynchronously.
- final WaitForOnWriteHelper onWriteFinishedCompleted = new WaitForOnWriteHelper();
-
- final WriteResultCallbackWrapper writeResultCallback =
- new WriteResultCallbackWrapperMock() {
- @Override
- public void onWriteFinished(PageRange[] pages) {
- onWriteFinishedCompleted.notifyCalled();
- }
- };
-
- final LayoutResultCallbackWrapper layoutResultCallback =
- new LayoutResultCallbackWrapperMock() {
- // Called on UI thread.
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- printingController.onWrite(new PageRange[] {PageRange.ALL_PAGES},
- fileDescriptor, new CancellationSignal(), writeResultCallback);
- }
- };
-
- callLayoutOnUiThread(
- printingController, null, createDummyPrintAttributes(), layoutResultCallback);
-
- FileInputStream in = null;
- try {
- onWriteFinishedCompleted.waitForCallback("onWriteFinished callback never completed.");
- Assert.assertTrue(tempFile.length() > 0);
- in = new FileInputStream(tempFile);
- byte[] b = new byte[PDF_PREAMBLE.length()];
- in.read(b);
- String preamble = new String(b);
- Assert.assertEquals(PDF_PREAMBLE, preamble);
- } finally {
- if (in != null) in.close();
- callFinishOnUiThread(printingController);
- // Close the descriptor, if not closed already.
- fileDescriptor.close();
- TestFileUtil.deleteFile(tempFile.getAbsolutePath());
- }
- }
-
- /**
- * Test for http://crbug.com/528909
- * Simulating while a printing job is triggered and about to call Android framework to show UI,
- * the corresponding tab is closed, this behaviour is mostly from JavaScript code. Make sure we
- * don't crash and won't call into framework.
- */
- @Test
- @TargetApi(Build.VERSION_CODES.KITKAT)
- @MediumTest
- @Feature({"Printing"})
- public void testPrintCloseWindowBeforeStart() {
- if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) return;
-
- mActivityTestRule.startMainActivityWithURL(URL);
- final Tab currentTab = mActivityTestRule.getActivity().getActivityTab();
- final PrintingControllerImpl printingController = createControllerOnUiThread();
- final PrintManagerDelegate mockPrintManagerDelegate =
- mockPrintManagerDelegate(() -> Assert.fail("Shouldn't start a printing job."));
-
- TestThreadUtils.runOnUiThreadBlocking(() -> {
- printingController.setPendingPrint(
- new TabPrinter(currentTab), mockPrintManagerDelegate, -1, -1);
- TabModelUtils.closeCurrentTab(mActivityTestRule.getActivity().getCurrentTabModel());
- Assert.assertFalse("currentTab should be closed already.", currentTab.isInitialized());
- printingController.startPendingPrint();
- });
- }
-
- /**
- * Test for http://crbug.com/528909
- * Simulating while a printing job is triggered and printing UI is showing, the corresponding
- * tab is closed, this behaviour is mostly from JavaScript code. Make sure we don't crash and
- * let framework notify user that we can't perform printing job.
- */
- @Test
- @TargetApi(Build.VERSION_CODES.KITKAT)
- @LargeTest
- @Feature({"Printing"})
- public void testPrintCloseWindowBeforeOnWrite() throws Throwable {
- if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) return;
-
- mActivityTestRule.startMainActivityWithURL(URL);
- final Tab currentTab = mActivityTestRule.getActivity().getActivityTab();
- final PrintingControllerImpl printingController = createControllerOnUiThread();
-
- startControllerOnUiThread(printingController, currentTab);
- callStartOnUiThread(printingController);
-
- final WaitForOnWriteHelper onWriteFinishedCompleted = new WaitForOnWriteHelper();
- final LayoutResultCallbackWrapper layoutResultCallback =
- new LayoutResultCallbackWrapperMock() {
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- onWriteFinishedCompleted.notifyCalled();
- }
- };
- callLayoutOnUiThread(
- printingController, null, createDummyPrintAttributes(), layoutResultCallback);
-
- onWriteFinishedCompleted.waitForCallback("onWriteFinished callback never completed.");
-
- final WaitForOnWriteHelper onWriteFailedCompleted = new WaitForOnWriteHelper();
- // Create a temporary file to save the PDF.
- final File tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTENSION);
- final ParcelFileDescriptor fileDescriptor =
- ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
- try {
- TestThreadUtils.runOnUiThreadBlocking(() -> {
- // Close tab.
- TabModelUtils.closeCurrentTab(mActivityTestRule.getActivity().getCurrentTabModel());
- Assert.assertFalse(
- "currentTab should be closed already.", currentTab.isInitialized());
-
- final WriteResultCallbackWrapper writeResultCallback =
- new WriteResultCallbackWrapperMock() {
- @Override
- public void onWriteFailed(CharSequence error) {
- onWriteFailedCompleted.notifyCalled();
- }
- };
- // Call onWrite.
- printingController.onWrite(new PageRange[] {PageRange.ALL_PAGES}, fileDescriptor,
- new CancellationSignal(), writeResultCallback);
- });
-
- onWriteFailedCompleted.waitForCallback("onWriteFailed callback never completed.");
- } finally {
- // Proper cleanup.
- callFinishOnUiThread(printingController);
- // Close the descriptor, if not closed already.
- fileDescriptor.close();
- TestFileUtil.deleteFile(tempFile.getAbsolutePath());
- }
- }
-
- /**
- * Test for http://crbug.com/863297
- * This bug shows Android printing framework could call |PrintDocumentAdapter.onFinish()|
- * before one of |WriteResultCallback.onWrite{Cancelled, Failed, Finished}()| get called.
- * Crash test, pass if there is no crash.
- */
- @Test
- @TargetApi(Build.VERSION_CODES.KITKAT)
- @MediumTest
- @Feature({"Printing"})
- public void testCancelPrintBeforeWriteResultCallbacks() throws Throwable {
- if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) return;
-
- mActivityTestRule.startMainActivityWithURL(URL);
-
- final WaitForOnWriteHelper onWriteHelper = new WaitForOnWriteHelper();
- final Tab currentTab = mActivityTestRule.getActivity().getActivityTab();
- final PrintingControllerImpl printingController =
- TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
- return new PrintingControllerImplPdfWritingDone(
- new PrintDocumentAdapterWrapper(), PRINT_JOB_NAME, onWriteHelper);
- });
-
- startControllerOnUiThread(printingController, currentTab);
- callStartOnUiThread(printingController);
-
- final WriteResultCallbackWrapper writeResultCallback =
- new WriteResultCallbackWrapperMock() {
- @Override
- public void onWriteFinished(PageRange[] pages) {
- Assert.fail("onWriteFinished shouldn't be called");
- }
-
- @Override
- public void onWriteFailed(CharSequence error) {
- Assert.fail("onWriteFailed shouldn't be called");
- }
-
- @Override
- public void onWriteCancelled() {
- Assert.fail("onWriteCancelled shouldn't be called");
- }
- };
-
- try (TemporaryFileHandler handler = new TemporaryFileHandler()) {
- final LayoutResultCallbackWrapper layoutResultCallback =
- new LayoutResultCallbackWrapperMock() {
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- printingController.onWrite(new PageRange[] {PageRange.ALL_PAGES},
- handler.getFileDescriptor(), new CancellationSignal(),
- writeResultCallback);
- }
- };
- callLayoutOnUiThread(
- printingController, null, createDummyPrintAttributes(), layoutResultCallback);
- onWriteHelper.waitForCallback("pdfWritingDone never called");
- callFinishOnUiThread(printingController);
- }
- }
-
- /**
- * Regresstion test for crbug.com/974581. In some cases, native printing code will fail without
- * starting a printing task in Java side. pdfWritingDone() will be called with |pageCount| = 0
- * in this case. We don't need to do anything for this in Java side for now.
- */
- @Test
- @SmallTest
- @Feature({"Printing"})
- public void testPdfWritingDoneCalledWithoutInitailizePrintingTask() {
- if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) return;
-
- mActivityTestRule.startMainActivityWithURL(URL);
- final PrintingControllerImpl controller = createControllerOnUiThread();
-
- // Calling pdfWritingDone() with |pageCount| = 0 before onWrite() was called. It shouldn't
- // crash.
- TestThreadUtils.runOnUiThreadBlocking(() -> controller.pdfWritingDone(0));
- }
-
- private PrintingControllerImpl createControllerOnUiThread() {
- return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
- return (PrintingControllerImpl) PrintingControllerImpl.create(
- new PrintDocumentAdapterWrapper(), PRINT_JOB_NAME);
- });
- }
-
- @TargetApi(Build.VERSION_CODES.KITKAT)
- private PrintAttributes createDummyPrintAttributes() {
- return new PrintAttributes.Builder()
- .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
- .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
- .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
- .build();
- }
-
- private PrintManagerDelegate mockPrintManagerDelegate(final Runnable r) {
- return new PrintManagerDelegate() {
- @Override
- public void print(String printJobName, PrintDocumentAdapter documentAdapter,
- PrintAttributes attributes) {
- if (r != null) r.run();
- }
- };
- }
-
- private void startControllerOnUiThread(final PrintingControllerImpl controller, final Tab tab) {
- TestThreadUtils.runOnUiThreadBlocking(() -> {
- controller.startPrint(new TabPrinter(tab),
- /* non-op PrintManagerDelegate */ mockPrintManagerDelegate(null));
- });
- }
-
- private void callStartOnUiThread(final PrintingControllerImpl controller) {
- TestThreadUtils.runOnUiThreadBlocking(() -> controller.onStart());
- }
-
- private void callLayoutOnUiThread(final PrintingControllerImpl controller,
- final PrintAttributes oldAttributes, final PrintAttributes newAttributes,
- final LayoutResultCallbackWrapper layoutResultCallback) {
- TestThreadUtils.runOnUiThreadBlocking(() -> {
- controller.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
- layoutResultCallback, null);
- });
- }
-
- private void callFinishOnUiThread(final PrintingControllerImpl controller) {
- TestThreadUtils.runOnUiThreadBlocking(() -> controller.onFinish());
- }
-}
diff --git a/chromium/chrome/android/webapk/shell_apk/manifest/bound_manifest_config.json b/chromium/chrome/android/webapk/shell_apk/manifest/bound_manifest_config.json
deleted file mode 100644
index c665f630ff2..00000000000
--- a/chromium/chrome/android/webapk/shell_apk/manifest/bound_manifest_config.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "scope_url": "https://pwa.rocks/",
- "intent_filters": {
- "scope_url_scheme": "https",
- "scope_url_host": "pwa.rocks",
- "scope_url_path_type": "android:pathPrefix",
- "scope_url_path": "/"
- },
- "start_url": "https://pwa.rocks/",
- "display_mode": "standalone",
- "orientation": "portrait",
- "theme_color": "2147483648L",
- "background_color": "2147483648L",
- "background_color_xml": "#F8F9FA",
- "icon_urls_and_icon_murmur2_hashes": "http://www.pwa.rocks/icon1.png 0 http://www.pwa.rocks/icon2.png 0",
- "web_manifest_url": "https://pwa.rocks/pwa.webmanifest",
- "distributor": "browser",
- "version_code": "1",
- "version_name": "1.0",
- "bound_webapk": {
- "runtime_host": "com.google.android.apps.chrome",
- "runtime_host_application_name": "Chromium"
- },
- "share_template": [{
- "index": "0",
- "title": "Share Text and Images",
- "action": "https://pwa.rocks/share.html",
- "method": "POST",
- "enctype": "multipart/form-data",
- "param_title": "title",
- "param_text": "text",
- "param_url": "url",
- "is_file_upload": true,
- "param_names": "[&quot;param_name&quot;]",
- "param_accepts": "[[&quot;image/*&quot;]]",
- "mime_types": [{
- "mime_type": "image/*"
- }]
- }]
-}
diff --git a/chromium/chrome/android/webapk/shell_apk/manifest/javatest_manifest_config_delta.json b/chromium/chrome/android/webapk/shell_apk/manifest/javatest_manifest_config_delta.json
deleted file mode 100644
index a85c4eb07c7..00000000000
--- a/chromium/chrome/android/webapk/shell_apk/manifest/javatest_manifest_config_delta.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "bound_webapk": {
- "runtime_host": "org.chromium.chrome.tests"
- }
-}
diff --git a/chromium/chrome/android/webapk/shell_apk/manifest/maps_go_manifest_config.json b/chromium/chrome/android/webapk/shell_apk/manifest/maps_go_manifest_config.json
deleted file mode 100644
index 9a5b4a82100..00000000000
--- a/chromium/chrome/android/webapk/shell_apk/manifest/maps_go_manifest_config.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "scope_url": "https://www.google.com/maps",
- "intent_filters": {
- "scope_url_scheme": "https",
- "scope_url_host": "www.google.com",
- "scope_url_path_type": "android:pathPrefix",
- "scope_url_path": "/maps"
- },
- "start_url": "https://www.google.com/maps/@37.7890183,-122.3915063,15z?force=pwa",
- "display_mode": "standalone",
- "orientation": "portrait",
- "theme_color": "2147483648L",
- "background_color": "2147483648L",
- "background_color_xml": "#F8F9FA",
- "icon_urls_and_icon_murmur2_hashes": "https://maps.gstatic.com/mapfiles/maps_lite/pwa/icons/maps_pwa_icon_v0920_48x48.png 0 https://maps.gstatic.com/mapfiles/maps_lite/pwa/icons/maps_pwa_icon_v0920_72x72 0",
- "web_manifest_url": "https://maps.gstatic.com/tactile/worker/ml.json",
- "distributor": "other",
- "version_code": "1",
- "version_name": "1.0",
- "bound_webapk": {
- "runtime_host": "com.google.android.apps.chrome",
- "runtime_host_application_name": "Chromium"
- }
-}
diff --git a/chromium/chrome/android/webapk/shell_apk/manifest/unbound_manifest_config.json b/chromium/chrome/android/webapk/shell_apk/manifest/unbound_manifest_config.json
deleted file mode 100644
index 887c0213e02..00000000000
--- a/chromium/chrome/android/webapk/shell_apk/manifest/unbound_manifest_config.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "scope_url": "https://pwa.rocks/",
- "intent_filters": {
- "scope_url_scheme": "https",
- "scope_url_host": "pwa.rocks",
- "scope_url_path_type": "android:pathPrefix",
- "scope_url_path": "/"
- },
- "start_url": "https://pwa.rocks/",
- "logged_intent_url_param": "originalUrl",
- "display_mode": "standalone",
- "orientation": "portrait",
- "theme_color": "2147483648L",
- "background_color": "2147483648L",
- "background_color_xml": "#F8F9FA",
- "icon_urls_and_icon_murmur2_hashes": "http://www.pwa.rocks/icon1.png 0 http://www.pwa.rocks/icon2.png 0",
- "web_manifest_url": "https://pwa.rocks/pwa.webmanifest",
- "distributor": "other",
- "version_code": "1",
- "version_name": "1.0",
- "bound_webapk": false
-}
diff --git a/chromium/chrome/browser/extensions/default_extensions/external_extensions.json b/chromium/chrome/browser/extensions/default_extensions/external_extensions.json
deleted file mode 100644
index 1ef50f3be29..00000000000
--- a/chromium/chrome/browser/extensions/default_extensions/external_extensions.json
+++ /dev/null
@@ -1,5 +0,0 @@
-// This json file will contain a list of extensions that will be included
-// in the installer.
-
-{
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/flag-metadata.json b/chromium/chrome/browser/flag-metadata.json
deleted file mode 100644
index f7f2ce3924b..00000000000
--- a/chromium/chrome/browser/flag-metadata.json
+++ /dev/null
@@ -1,3537 +0,0 @@
-// This file lists metadata for chrome://flags entries. This metadata is not
-// ever used in the built browser or in any compiled code, but is used as part
-// of the review process and to clean up flags that have become obsolete or
-// unused. For more details, see:
-//
-// https://chromium.googlesource.com/chromium/src/+/master/docs/flag_ownership.md
-//
-// This file is a list of json objects; each object contains these keys:
-//
-// name: the internal name of the flag, as present in chrome://flags. This is
-// used as a primary key. The value is a string.
-//
-// owners: the person(s) or team(s) responsible for this flag. The value is a
-// nonempty list of strings, in order of specificity (i.e., the first entry
-// on the list is the best contact). Each entry is either:
-//
-// - A string containing '@', which is treated as an email address;
-// - A string beginning with '//', which is treated as a path to a file
-// containing a list of owners for this flag (commonly an OWNERS file);
-// - Any other string, which is treated as the username part of an
-// @chromium.org email address;
-//
-// expiry_milestone: the milestone after which this flag is obsolete.
-// Specifically, after the milestone with the given number branches, this flag
-// may freely be deleted and defaulted to either enabled or disabled where
-// used. The special value -1 means "never expires", which should only be used
-// in consultation with top-level OWNERS. The use of the -1 value must be
-// accompanied by a comment explaining why the flag must not expire.
-
-[
- {
- "name": "accessibility-internals-page-improvements",
- "owners": [ "abigailbklein" ],
- "expiry_milestone": 82
- },
- {
- "name": "account-consistency",
- "owners": [ "droger", "msarda" ],
- "expiry_milestone": 80
- },
- {
- "name": "aggregated-ml-app-ranking",
- "owners": [ "pdyson", "jiameng" ],
- "expiry_milestone": 81
- },
- {
- "name": "aggregated-ml-search-ranking",
- "owners": [ "thanhdng", "jiameng" ],
- "expiry_milestone": 82
- },
- {
- "name": "allow-disable-mouse-acceleration",
- "owners": [ "zentaro" ],
- "expiry_milestone": 82
- },
- {
- "name": "allow-insecure-localhost",
- "owners": [ "security-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "allow-popups-during-page-unload",
- "owners": [ "avi", "pastarmovj" ],
- // Bound to the timelene of the AllowPopupsDuringPageUnload policy.
- "expiry_milestone": 82
- },
- {
- "name": "allow-previews",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "allow-sxg-certs-without-extension",
- "owners": [ "//content/browser/web_package/OWNERS" ],
- // Used by developers for testing signed exchange loading using normal
- // certs. See https://crbug.com/862003
- "expiry_milestone": 83
- },
- {
- "name": "allow-sync-xhr-in-page-dismissal",
- "owners": [ "kdillon@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "android-files-in-files-app",
- "owners": [ "fukino" ],
- "expiry_milestone": 76
- },
- {
- "name": "android-picture-in-picture-api",
- "owners": [ "beccahughes", "jazzhsu" ],
- "expiry_milestone": 82
- },
- {
- "name": "android-setup-search-engine",
- "owners": [ "pavely", "wylieb" ],
- "expiry_milestone": 83
- },
- {
- "name": "android-site-settings-ui-refresh",
- "owners": [ "hkamila" ],
- "expiry_milestone": 76
- },
- {
- "name": "animated-avatar-button",
- "owners": ["jkrcal"],
- "expiry_milestone": 79
- },
- {
- "name": "app-management",
- "owners": [ "//chrome/browser/resources/app_management/OWNERS" ],
- "expiry_milestone": 81
- },
- {
- "name": "app-service-ash",
- "owners": [ "//chrome/services/app_service/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "app-service-intent-handling",
- "owners": [ "chromeos-apps-foundation-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "app-service-shelf",
- "owners": [ "//chrome/services/app_service/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "arc-available-for-child",
- "owners": [ "//chrome/browser/chromeos/arc/enterprise/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "arc-boot-completed-broadcast",
- "owners": [ "//components/arc/mojom/ARC_SECURITY_OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "arc-custom-tabs-experiment",
- "owners": [ "hashimoto" ],
- "expiry_milestone": 76
- },
- {
- "name": "arc-documents-provider",
- "owners": [ "fukino" ],
- "expiry_milestone": 76
- },
- {
- "name": "arc-file-picker-experiment",
- "owners": [ "niwa" ],
- "expiry_milestone": 80
- },
- {
- "name": "arc-graphics-buffer-visualization-tool",
- "owners": [ "khmel" ],
- "expiry_milestone": 76
- },
- {
- "name": "arc-native-bridge-toggle",
- "owners": [ "levarum@google.com" ],
- // Used on ChromeOS to compare and debug different ARC native-bridge
- // implementations which are normally used on different platforms.
- "expiry_milestone": -1
- },
- {
- "name": "arc-print-spooler-experiment",
- "owners": [ "jschettler@google.com", "bmgordon@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "arc-usb-host",
- "owners": [ "lgcheng" ],
- "expiry_milestone": 76
- },
- {
- "name": "arc-usb-storage-ui",
- "owners": [ "fukino" ],
- "expiry_milestone": 78
- },
- {
- "name": "arc-vpn",
- "owners": [ "cros-networking@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "ash-debug-shortcuts",
- "owners": [ "//ash/OWNERS" ],
- // Used by developers for debugging and to dump extra information to logs
- // in official builds.
- "expiry_milestone": -1
- },
- {
- "name": "ash-enable-overview-rounded-corners",
- "owners": [ "sammiequon" ],
- "expiry_milestone": 78
- },
- {
- "name": "ash-enable-pip-rounded-corners",
- "owners": [ "edcourtney" ],
- "expiry_milestone": 74
- },
- {
- "name": "ash-enable-unified-desktop",
- "owners": [ "//ash/OWNERS" ],
- // Unified desktop can be enabled by enterprise policy. The switch exists in
- // about:flags so QA can more easily test the feature.
- "expiry_milestone": -1
- },
- {
- "name": "ash-swap-side-volume-buttons-for-orientation",
- "owners": [ "minch" ],
- "expiry_milestone": 78
- },
- {
- "name": "audio-worklet-realtime-thread",
- "owners": [ "//third_party/blink/renderer/modules/webaudio/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "auto-fetch-on-net-error-page",
- "owners": [ "harringtond", "offline-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "auto-screen-brightness",
- "owners": [ "jiameng", "napper" ],
- "expiry_milestone": 79
- },
- {
- "name": "autofill-always-return-cloud-tokenized-card",
- "owners": [ "aneeshali@google.com", "annelim@google.com", "jsaul@google.com" ],
- "expiry_milestone": 87
- },
- {
- "name": "autofill-assistant-chrome-entry",
- "owners": [ "gogerald,wuandy" ],
- "expiry_milestone": 79
- },
- {
- "name": "autofill-assistant-direct-actions",
- "owners": [ "//components/autofill_assistant/OWNERS" ],
- "expiry_milestone": 81
- },
- {
- "name": "autofill-cache-query-responses",
- "owners": [ "rogerm" ],
- "expiry_milestone": 72
- },
- {
- "name": "autofill-enable-company-name",
- "owners": [ "sebsg" ],
- "expiry_milestone": 73
- },
- {
- "name": "autofill-enable-local-card-migration-for-non-sync-user",
- "owners": [ "siyua" ],
- "expiry_milestone": 83
- },
- {
- "name": "autofill-enable-toolbar-status-chip",
- "owners": [ "siyua" ],
- "expiry_milestone": 83
- },
- {
- "name": "autofill-enforce-min-required-fields-for-heuristics",
- "owners": [ "rogerm" ],
- "expiry_milestone": 73
- },
- {
- "name": "autofill-enforce-min-required-fields-for-query",
- "owners": [ "rogerm" ],
- "expiry_milestone": 73
- },
- {
- "name": "autofill-enforce-min-required-fields-for-upload",
- "owners": [ "rogerm" ],
- "expiry_milestone": 73
- },
- {
- "name": "autofill-keyboard-accessory-view",
- "owners": [ "fhorschig@chromium.org" ],
- "expiry_milestone": 79
- },
- {
- "name": "autofill-no-local-save-on-unmask-success",
- "owners": [ "jsaul@google.com" ],
- // Must wait for the Autofill Auth Project to be launched.
- "expiry_milestone": 84
- },
- {
- "name": "autofill-no-local-save-on-upload-success",
- "owners": [ "jsaul@google.com", "annelim@google.com" ],
- // Must wait for the Autofill Auth Project to be launched.
- "expiry_milestone": 84
- },
- {
- "name": "autofill-off-no-server-data",
- "owners": [ "seblalancette" ],
- "expiry_milestone": 79
- },
- {
- "name": "autofill-profile-client-validation",
- "owners": [ "parastoog" ],
- "expiry_milestone": 77
- },
- {
- "name": "autofill-profile-server-validation",
- "owners": [ "parastoog" ],
- "expiry_milestone": 77
- },
- {
- "name": "autofill-prune-suggestions",
- "owners": ["ftirelo", "tmartino"],
- "expiry_milestone": 78
- },
- {
- "name": "autofill-reject-company-birthyear",
- "owners": [ "parastoog" ],
- "expiry_milestone": 79
- },
- {
- "name": "autofill-restrict-formless-form-extraction",
- "owners": [ "rogerm" ],
- "expiry_milestone": 76
- },
- {
- "name": "autofill-rich-metadata-queries",
- "owners": [ "rogerm" ],
- "expiry_milestone": 76
- },
- {
- "name": "autofill-use-improved-label-disambiguation",
- "owners": [ "ftirelo", "tmartino" ],
- "expiry_milestone": 77
- },
- {
- "name": "autofill-use-mobile-label-disambiguation",
- "owners": [ "ftirelo", "tmartino" ],
- "expiry_milestone": 79
- },
- {
- "name": "back-forward-cache",
- "owners": [ "bfcache-dev" ],
- "expiry_milestone": 83
- },
- {
- "name": "background-task-component-update",
- "owners": [ "sorin", "waffles", "tiborg" ],
- "expiry_milestone": 76
- },
- {
- "name": "bluetooth-aggressive-appearance-filter",
- "owners": [ "hansberry" ],
- "expiry_milestone": 80
- },
- {
- "name": "BundledConnectionHelp",
- "owners": [ "carlosil" ],
- "expiry_milestone": 76
- },
- {
- "name": "bypass-app-banner-engagement-checks",
- "owners": [ "dominickn" ],
- "expiry_milestone": 78
- },
- {
- "name": "calculate-native-win-occlusion",
- "owners": [ "davidbienvenu", "fdoray" ],
- "expiry_milestone": 81
- },
- {
- "name": "camera-system-web-app",
- "owners": [ "calamity", "ortuno" ],
- "expiry_milestone": 82
- },
- {
- "name": "cast-media-route-provider",
- "owners": [ "mfoltz", "takumif" ],
- "expiry_milestone": 83
- },
- {
- "name": "cct-module",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-module-cache",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-module-custom-header",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-module-custom-request-header",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-module-dex-loading",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-module-post-message",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-module-use-intent-extras",
- "owners": [ "mvanouwerkerk", "//chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cct-target-translate-language",
- "owners": [ "amalova" ],
- "expiry_milestone": 76
- },
- {
- "name": "chrome-colors",
- "owners": ["gayane"],
- "expiry_milestone": 79
- },
- {
- "name": "chrome-colors-custom-color-picker",
- "owners": ["gayane"],
- "expiry_milestone": 79
- },
- {
- "name": "chrome-sharing-hub",
- "owners": ["kmilka"],
- "expiry_milestone": 83
- },
- {
- "name": "clear-old-browsing-data",
- "owners": [ "dullweber" ],
- "expiry_milestone": 78
- },
- {
- "name": "click-to-call-context-menu-selected-text",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "click-to-call-open-dialer-directly",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "click-to-call-receiver",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "click-to-call-ui",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "click-to-open-pdf",
- "owners": [ "tommycli" ],
- "expiry_milestone": 76
- },
- {
- "name": "composited-layer-borders",
- "owners": [ "ccameron" ],
- "expiry_milestone": 76
- },
- {
- "name": "compositor-threaded-scrollbar-scrolling",
- "owners": [ "arakeri@microsoft.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "context-menu-search-with-google-lens",
- "owners": [ "benwgold", "lens-chrome" ],
- "expiry_milestone": 88
- },
- {
- "name": "contextual-search-definitions",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "contextual-search-longpress-resolve",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "contextual-search-ml-tap-suppression",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "contextual-search-ranker-query",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "contextual-search-second-tap",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "contextual-search-simplified-server",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 81
- },
- {
- "name": "contextual-search-translation-model",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "contextual-search-unity-integration",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "cookie-deprecation-messages",
- "owners": [ "chlily", "aarontag" ],
- "expiry_milestone": 85
- },
- {
- "name": "cookies-without-same-site-must-be-secure",
- "owners": [ "chlily", "morlovich" ],
- "expiry_milestone": 83
- },
- {
- "name": "cros-regions-mode",
- "owners": [ "alemate" ],
- "expiry_milestone": 80
- },
- {
- "name": "cross-origin-embedder-policy",
- "owners": [ "yhirano" ],
- "expiry_milestone": 83
- },
- {
- "name": "crostini-backup",
- "owners": [ "joelhockey", "nverne", "benwells" ],
- "expiry_milestone": 77
- },
- {
- "name": "crostini-gpu-support",
- "owners": [ "nverne", "benwells" ],
- "expiry_milestone": 85
- },
- {
- "name": "crostini-usb-allow-unsupported",
- "owners": [ "nverne", "benwells" ],
- "expiry_milestone": 85
- },
- {
- "name": "crostini-usb-support",
- "owners": [ "jopra", "nverne", "benwells" ],
- "expiry_milestone": 76
- },
- {
- "name": "crostini-webui-installer",
- "owners": [ "lxj", "timloh", "benwells" ],
- "expiry_milestone": 82
- },
- {
- "name": "cryptauth-v2-devicesync",
- "owners": [ "khorimoto", "nohle" ],
- "expiry_milestone": 85
- },
- {
- "name": "cryptauth-v2-enrollment",
- "owners": [ "khorimoto", "nohle" ],
- "expiry_milestone": 82
- },
- {
- "name": "d3d11-video-decoder",
- "owners": [ "liberato", "tmathmeyer", "videostack-eng" ],
- "expiry_milestone": 78
- },
- {
- "name": "darken-websites-checkbox-in-themes-setting",
- "owners": [ "lazzzis@google.com", "twellington" ],
- "expiry_milestone": 83
- },
- {
- "name": "data-saver-server-previews",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "dcheck-is-fatal",
- "owners": [ "wez" ],
- // Used to debug failed assertions in environments where debug builds cannot
- // be used.
- "expiry_milestone": -1
- },
- {
- "name": "debug-packed-apps",
- "owners": [ "benwells", "raymes" ],
- "expiry_milestone": 88
- },
- {
- "name": "decode-jpeg-images-to-yuv",
- "owners": [ "sashamcintosh", "chromeos-gfx@google.com" ],
- "expiry_milestone": 88
- },
- {
- "name": "decode-webp-images-to-yuv",
- "owners": [ "andrescj", "chromeos-gfx@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "delegate-overscroll-swipes",
- "owners": [ "chrome-android-app@chromium.org" ],
- "expiry_milestone": 88
- },
- {
- "name": "device-discovery-notifications",
- "owners": [ "thestig" ],
- "expiry_milestone": 78
- },
- {
- "name": "dial-media-route-provider",
- "owners": [ "mfoltz", "takumif" ],
- "expiry_milestone": 83
- },
- {
- "name": "direct-actions",
- "owners": [ "szermatt", "tedchoc", "twellington" ],
- "expiry_milestone": 81
- },
- {
- "name": "direct-manipulation-stylus",
- "owners": [ "lanwei", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "disable-accelerated-2d-canvas",
- "owners": [ "fserb" ],
- "expiry_milestone": -1
- },
- {
- "name": "disable-accelerated-mjpeg-decode",
- "owners": [ "chromeos-camera-eng@google.com" ],
- // This flag does not expire because it allows users to disable HW mjpeg
- // decoding for debugging purpose and temporary workaround for some issues.
- "expiry_milestone": -1
- },
- {
- "name": "disable-accelerated-video-decode",
- "owners": [ "chromeos-video-eng@google.com" ],
- // This flag does not expire because it allows users to disable HW video
- // decoding for debugging purpose and temporary workaround for some issues.
- "expiry_milestone": -1
- },
- {
- "name": "disable-accelerated-video-encode",
- "owners": [ "chromeos-video-eng@google.com" ],
- // This flag does not expire because it allows users to disable HW video
- // encoding for debugging purpose and temporary workaround for some issues.
- "expiry_milestone": -1
- },
- {
- "name": "disable-best-effort-tasks",
- "owners": [ "catan-team@chromium.org" ],
- "expiry_milestone": 75
- },
- { "name": "disable-cancel-all-touches",
- "owners": [ "chrome-knowledge-eng@google.com" ],
- // This flag is use to disable CancelAllTouches() function, which enable the
- // testing for implementation of canceling single touches.
- "expiry_milestone": 86
- },
- {
- "name": "disable-explicit-dma-fences",
- "owners": [ "chromeos-gfx@google.com" ],
- // This flag is used for QA & debugging on ChromeOS, which has no way to
- // customize switches.
- "expiry_milestone": -1
- },
- {
- "name": "disable-hosted-app-shim-creation",
- "owners": [ "ccameron" ],
- "expiry_milestone": 77
- },
- {
- // See https://crbug.com/882238.
- "name": "disable-ipc-flooding-protection",
- "owners": [ "arthursonzogni@chromium.org", "palmer@chromium.org" ],
- "expiry_milestone": 76
- },
- {
- "name": "disable-javascript-harmony-shipping",
- "owners": [ "adamk", "hablich" ],
- // This flag is used for field debugging along with
- // enable-javascript-harmony.
- "expiry_milestone": -1
- },
- {
- "name": "disable-keepalive-fetch",
- "owners": [ "yhirano" ],
- "expiry_milestone": 83
- },
- {
- "name": "disable-office-editing-component-app",
- "owners": [
- "chrome-apps-platform-rationalization@google.com",
- "quickoffice-chrome-eng@google.com" ],
- "expiry_milestone": 88
- },
- {
- // See https://crbug.com/882238.
- "name": "disable-pushstate-throttle",
- "owners": [ "arthursonzogni@chromium.org", "palmer@chromium.org" ],
- "expiry_milestone": 76
- },
- {
- "name": "disable-threaded-scrolling",
- "owners": [ "bokan", "input-dev" ],
- // This flag is a useful debugging control when investigating scrolling
- // issues.
- "expiry_milestone": -1
- },
- {
- "name": "disable-touch-adjustment",
- "owners": [ "eirage", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "disable-webrtc-hw-decoding",
- "owners": [ "hiroh", "chromeos-video-eng@google.com" ],
- // This flag does not expire because it allows users to disable HW video
- // decoding only in webrtc usecase for debugging purpose and temporary
- // workaround for some issues.
- "expiry_milestone": -1
- },
- {
- "name": "disable-webrtc-hw-encoding",
- "owners": [ "hiroh", "chromeos-video-eng@google.com" ],
- // This flag does not expire because it allows users to disable HW video
- // encoding only in webrtc usecase for debugging purpose and temporary
- // workaround for some issues.
- "expiry_milestone": -1
- },
- {
- "name": "disallow-doc-written-script-loads",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This is a web-developer facing flag and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "disallow-unsafe-http-downloads",
- "owners": [ "cthomp" ],
- "expiry_milestone": 76
- },
- {
- "name": "discover-app",
- "owners": [ "alemate" ],
- "expiry_milestone": 80
- },
- {
- "name": "dns-over-https",
- "owners": [ "dalyk", "doh-core@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "document-passive-event-listeners",
- "owners": [ "nzolghadr, input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "document-passive-wheel-event-listeners",
- "owners": [ "sahel", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "double-tap-to-zoom-in-tablet-mode",
- "owners": [ "afakhry" ],
- "expiry_milestone": 80
- },
- {
- "name": "download-auto-resumption-native",
- "owners": [ "shaktisahu", "qinmin" ],
- "expiry_milestone": 80
- },
- {
- "name": "download-rename",
- "owners": [ "hesen", "dtrainor" ],
- "expiry_milestone": 79
- },
- {
- "name": "download-resumption-without-strong-validators",
- "owners": [ "qinmin", "dtrainor", "xingliu" ],
- "expiry_milestone": 80
- },
- {
- "name": "draw-vertically-edge-to-edge",
- "owners": [ "chrome-android-app@chromium.org" ],
- "expiry_milestone": 88
- },
- {
- // See: https://crbug.com/993569.
- "name": "dynamic-tcmalloc-tuning",
- "owners": [ "bgeffon", "sonnyrao" ],
- "expiry_milestone": 89
- },
- {
- "name": "enable-accessibility-expose-aria-annotations",
- "owners": [ "aleventhal@chromium.org", "//third_party/blink/renderer/modules/accessibility/OWNERS", "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-accessibility-expose-display-none",
- "owners": [ "adettenb@microsoft.com", "//third_party/blink/renderer/modules/accessibility/OWNERS", "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-accessibility-image-descriptions",
- "owners": [ "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-accessibility-object-model",
- "owners": [ "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-advanced-ppd-attributes",
- "owners": [ "vkuzkokov", "skau" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-ambient-authentication-in-guest-session",
- "owners": ["rhalavati", "chrome-privacy-core@google.com"],
- "expiry_milestone": 87
- },
- {
- "name": "enable-ambient-authentication-in-incognito",
- "owners": ["rhalavati", "chrome-privacy-core@google.com"],
- "expiry_milestone": 87
- },
- {
- "name": "enable-android-autofill-accessibility",
- "owners": [ "ftirelo" ],
- "expiry_milestone": 73
- },
- {
- "name": "enable-android-night-mode",
- "owners": [ "twellington", "tedchoc" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-android-spellchecker",
- "owners": [ "timvolodine" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-android-web-contents-dark-mode",
- "owners": [ "tedchoc" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-app-data-search",
- "owners": [ "jennyz", "kaznacheev" ],
- "expiry_milestone": 86
- },
- {
- "name": "enable-app-grid-ghost",
- "owners": [ "mmourgos" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-app-list-search-autocomplete",
- "owners": [ "newcomer" ],
- "expiry_milestone": 75
- },
- {
- "name": "enable-app-notification-status-messaging",
- "owners": [ "knollr", "peter" ],
- "expiry_milestone": 74
- },
- {
- "name": "enable-arc-cups-api",
- "owners": [ "luum", "skau" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-arc-unified-audio-focus",
- "owners": [ "beccahughes", "media-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-assistant-aec",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 88
- },
- {
- "name": "enable-assistant-app-support",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-assistant-dsp",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-assistant-key-remapping",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-assistant-launcher-integration",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-assistant-media-session-integration",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-assistant-routines",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-assistant-stereo-input",
- "owners": [ "croissant-eng" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-async-dns",
- "owners": [ "ericorth", "net-dev" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-audio-focus-enforcement",
- "owners": [ "beccahughes", "media-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-aura-tooltips-on-windows",
- "owners": [ "cliffsmo" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-autofill-account-wallet-storage",
- "owners": [ "treib", "jsaul@google.com", "butter-team@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-autofill-credit-card-ablation-experiment",
- "owners": [ "dlkumar@google.com" ],
- "expiry_milestone": 88
- },
- {
- "name": "enable-autofill-credit-card-authentication",
- "owners": [ "jsaul@google.com", "manasverma@google.com" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-autofill-credit-card-upload",
- "owners": [ "jsaul@google.com" ],
- // This flag is heavily used by the testing team that can't easily use the
- // commandline, and can't be enabled by default.
- // http://g/chrome-flags/s2RTQCvcRRs
- "expiry_milestone": -1
- },
- {
- "name": "enable-autofill-credit-card-upload-editable-cardholder-name",
- "owners": [ "jsaul@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-autofill-credit-card-upload-editable-expiration-date",
- "owners": [ "hozhng@google.com", "jsaul@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-autofill-credit-card-upload-feedback",
- "owners": [ "siyua@chromium.org, payments-autofill-team@google.com" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-autofill-do-not-migrate-unsupported-local-cards",
- "owners": [ "sujiezhu@google.com", "jsaul@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-autofill-local-card-migration-uses-strike-system-v2",
- "owners": [ "annelim@google.com", "jsaul@google.com" , "jiahuiguo@google.com"],
- "expiry_milestone": 84
- },
- {
- "name": "enable-autofill-manual-fallback",
- "owners": [ "fhorschig@chromium.org" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-autofill-refresh-style",
- "owners": [ "tmartino@chromium.org" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-autofill-save-card-show-no-thanks",
- "owners": ["siashah", "jsaul@google.com"],
- "expiry_milestone": 82
- },
- {
- "name": "enable-autofill-save-credit-card-uses-improved-messaging",
- "owners": [ "siyua@chromium.org", "payments-autofill-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-autofill-updated-card-unmask-prompt-ui",
- "owners": ["siyua", "payments-autofill-team@google.com"],
- "expiry_milestone": 87
- },
- {
- "name": "enable-autofill-upi-vpa",
- "owners": ["cfroussios"],
- "expiry_milestone": 82
- },
- {
- "name": "enable-backdrop-filter",
- "owners": [ "masonfreed", "paint-dev@chromium.org" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-background-blur",
- "owners": [ "newcomer" ],
- "expiry_milestone": 86
- },
- {
- "name": "enable-bookmark-reorder",
- "owners": ["jhimawan@google.com", "twellington" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-built-in-module-all",
- "owners": [ "hiroshige", "domenic" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-built-in-module-infra",
- "owners": [ "hiroshige", "domenic" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-built-in-module-kv-storage",
- "owners": [ "hiroshige", "domenic" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-bulk-printers",
- "owners": [ "skau" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-caption-settings",
- "owners": [ "evliu" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-chrome-duet",
- "owners": [ "mdjones" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-chrome-duet-labels",
- "owners": [ "mdjones" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-chromeos-account-manager",
- "owners": [ "sinhak@chromium.org" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-clipboard-provider-text-suggestions",
- "owners": [ "gangwu", "chromium-omnibox-team"],
- "expiry_milestone": 84
- },
- {
- "name": "enable-close-tab-suggestions",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-cloud-print-xps",
- "owners": [ "//printing/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-cloud-printer-handler",
- "owners": [ "//printing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-command-line-on-non-rooted-devices",
- "owners": [ "chrome-android-app" ],
- // This flag is used for debugging on Android; it causes Android Chromium
- // builds to read command-line arguments from a writable location on disk.
- "expiry_milestone": -1
- },
- {
- "name": "enable-cooperative-scheduling",
- "owners": [ "keishi" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-credit-card-assist",
- "owners": [ "ftirelo", "gogerald" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-cros-action-recorder",
- "owners": [ "charleszhao", "tby" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-cros-ime-input-logic-fst",
- "owners": [ "essential-inputs-team@google.com" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-cssom-view-scroll-coordinates",
- "owners": [ "cathiechen@igalia.com", "fwang@igalia.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-cups-printers-ui-overhaul",
- "owners": [ "jimmyxgong" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-custom-mac-paper-sizes",
- "owners": [ "thestig" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-data-reduction-proxy-server-experiment",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-data-reduction-proxy-with-network-service",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-dbus-and-x11-status-icons",
- "owners": [ "thestig", "thomasanderson" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-de-jelly",
- "owners": [ "ericrk", "//components/viz/OWNERS" ],
- "expiry_milestone": 94
- },
- {
- "name": "enable-defer-all-script",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-defer-all-script-without-optimization-hints",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-desktop-pwas-local-updating",
- "owners": [ "desktop-pwas-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-desktop-pwas-omnibox-install",
- "owners": [ "desktop-pwas-team@google.com" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-devtools-experiments",
- "owners": [ "//third_party/blink/renderer/devtools/OWNERS" ],
- // This is a catch-all for ongoing devtools experiments.
- "expiry_milestone": -1
- },
- {
- "name": "enable-display-locking",
- "owners": [ "//third_party/blink/renderer/core/display_lock/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-duet-tabstrip-integration",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-encryption-migration",
- "owners": [ "fukino" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-ephemeral-tab",
- "owners": [ "donnd", "jinsukkim" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-ephemeral-tab-bottom-sheet",
- "owners": [ "donnd", "jinsukkim" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-experimental-accessibility-autoclick",
- "owners": [ "katie", "dmazzoni", "dtseng" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-experimental-accessibility-chromevox-language-switching",
- "owners": [ "akihiroota", "dmazzoni", "dtseng" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-experimental-accessibility-chromevox-sub-node-language-switching",
- "owners": [ "akihiroota", "dmazzoni", "dtseng", "//ui/accessibility/OWNERS"],
- "expiry_milestone": 82
- },
- {
- "name": "enable-experimental-accessibility-features",
- "owners": [ "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-experimental-accessibility-language-detection",
- "owners": [ "chrishall", "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-experimental-accessibility-switch-access",
- "owners": [ "anastasi@google.com", "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-experimental-accessibility-switch-access-text",
- "owners": [ "anastasi@google.com", "//ui/accessibility/OWNERS" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-experimental-fling-animation",
- "owners": [ "sarsha@microsoft.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-experimental-kernel-vm-support",
- "owners": [ "jflat", "zwisler" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-experimental-productivity-features",
- "owners": [ "feature-control@chromium.org" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-experimental-web-platform-features",
- "owners": [ "//third_party/blink/renderer/core/OWNERS" ],
- // Used by developers to activate experimental features in blink.
- // See //third_party/blink/renderer/platform/runtime_enabled_features.json5.
- "expiry_milestone": -1
- },
- {
- "name": "enable-experimental-webassembly-features",
- "owners": [ "adamk", "hablich" ],
- // This flag is used by web developers to test upcoming WebAssembly
- // features.
- "expiry_milestone": -1
- },
- {
- "name": "enable-filtering-scroll-events",
- "owners": [ "axantoine", "eirage", "nzolghadr", "input-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-force-dark",
- "owners": [ "gilmanmh@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-fs-nosymfollow",
- "owners": [ "mortonm@chromium.org" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-future-v8-vm-features",
- "owners": [ "hablich" ],
- // This flag enables the rolling set of upcoming V8 features, for early
- // testing by JavaScript developers.
- "expiry_milestone": -1
- },
- {
- "name": "enable-generic-sensor-extra-classes",
- "owners": [ "reillyg@chromium.org", "raphael.kubo.da.costa@intel.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-google-branded-context-menu",
- "owners": [ "edwardjung@chromium.org" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-gpu-appcontainer",
- "owners": [ "forshaw", "wfh" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-gpu-rasterization",
- "owners": [ "enne", "vmiura", "ericrk" ],
- // A debugging flag intended for end-users where there may not be any other
- // way to turn off graphics features.
- "expiry_milestone": -1
- },
- {
- "name": "enable-gpu-service-logging",
- "owners": [ "vikassoni", "kbr" ],
- // A debugging flag intended for end-users where there may not be any other
- // way to turn off graphics features.
- "expiry_milestone": -1
- },
- {
- "name": "enable-heuristic-stylus-palm-rejection",
- "owners": ["robsc", "napper"],
- // We add a heuristic that changes the way fingers are handled. We should
- // always let users switch it off if needed. However, for now, during
- // experiments, we set milestone deep into future.
- "expiry_milestone": 90},
- {
- "name": "enable-hide-arc-media-notifications",
- "owners": [ "beccahughes", "media-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-history-favicons-google-server-query",
- "owners": ["victorvianna", "jkrcal"],
- "expiry_milestone": 79
- },
- {
- "name": "enable-history-manipulation-intervention",
- "owners": [ "shivanisha" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-horizontal-tab-switcher",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-hosted-app-quit-notification",
- "owners": [ "ccameron" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-immersive-fullscreen-toolbar",
- "owners": [ "sdy" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-implicit-root-scroller",
- "owners": [ "bokan", "input-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-incognito-window-counter",
- "owners": [ "rhalavati" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-inline-update-flow",
- "owners": [ "nyquist", "dtrainor" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-intent-picker",
- "owners": [ "chromeos-apps-foundation-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-javascript-harmony",
- "owners": [ "adamk", "hablich" ],
- // This flag is used by web developers to test upcoming javascript features.
- "expiry_milestone": -1
- },
- {
- "name": "enable-layout-ng",
- "owners": [ "layout-dev@chromium.org" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-lazy-frame-loading",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-lazy-image-loading",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-lite-page-server-previews",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-lock-screen-notification",
- "owners": [ "tengs" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-logging-js-console-messages",
- "owners": [ "hazems" ],
- // Never expires because it is used by developers to enable logging JS
- // console messages in system logs for debugging purposes. It's disabled by
- // default because logs may contain PII.
- "expiry_milestone": -1
- },
- {
- "name": "enable-lookalike-url-navigation-suggestions",
- "owners": [ "jdeblasio", "meacer" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-mark-http-as",
- "owners": [ "cthomp", "estark" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-md-rounded-corners-on-dialogs",
- "owners": ["bsep", "sajadm"],
- "expiry_milestone": 78
- },
- {
- "name": "enable-media-session-notifications",
- "owners": [ "beccahughes", "media-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-media-session-service",
- "owners": [ "beccahughes", "media-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-nacl",
- "owners": [ "dschuff", "native-client-dev@googlegroups.com" ],
- "expiry_milestone": 87
- },
- {
- "name": "enable-native-controls",
- "owners": [ "chrome-media-ux@google.com" ],
- "expiry_milestone": 75
- },
- {
- "name": "enable-native-notifications",
- "owners": [ "peter", "finnur" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-navigation-tracing",
- "owners": [ "tracing" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-network-logging-to-file",
- "owners": [ "eroman", "net-dev" ],
- // This flag is used to capture early-browser network logging on platforms
- // without easy access to startup time configuration.
- "expiry_milestone": -1
- },
- {
- "name": "enable-new-download-backend",
- "owners": [ "shaktisahu", "dtrainor" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-new-print-preview-layout",
- "owners": [ "//printing/OWNERS" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-noscript-previews",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-notification-indicator",
- "owners": [ "newcomer" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-ntp-remote-suggestions",
- "owners": [ "fgorski" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-offer-store-unmasked-wallet-cards",
- "owners": [ "jsaul@google.com" ],
- "expiry_milestone": 86
- },
- {
- "name": "enable-offline-previews",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-oop-rasterization",
- "owners": [ "enne", "khushalsagar" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-paint-holding",
- "owners": [ "schenney", "paint-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-parallel-downloading",
- "owners": [ "qinmin", "xingliu", "dtrainor" ],
- // This flag is used by dev teams on Android to disable this feature, since
- // it can badly break pages under test.
- "expiry_milestone": -1
- },
- {
- "name" : "enable-parental-controls-settings",
- "owners" : [ "danan", "michaelpg" ],
- // This flag is used to enable parental controls options in ChromeOS
- // settings.
- "expiry_milestone" : 79
- },
- {
- "name": "enable-pixel-canvas-recording",
- "owners": [ "malaykeshav", "oshima" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-play-store-search",
- "owners": [ "jennyz", "kaznacheev" ],
- "expiry_milestone": 83
- },
- {
- "name" : "enable-pointer-lock-options",
- "owners" : [ "eirage", "nzolghadr", "input-dev" ],
- "expiry_milestone" : 85
- },
- {
- "name": "enable-portals",
- "owners": [ "adithyas", "jbroman", "lfg" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-previews-coin-flip",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 90
- },
- {
- "name": "enable-process-sharing-with-default-site-instances",
- "owners": [ "acolwell" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-process-sharing-with-strict-site-instances",
- "owners": [ "japhet" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-query-in-omnibox",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-quic",
- "owners": [ "//net/quic/OWNERS" ],
- "expiry_milestone": 88
- },
- {
- "name": "enable-reader-mode",
- "owners": [ "gilmanmh@google.com" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-reader-mode-in-cct",
- "owners": [ "mdjones" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-removing-all-third-party-cookies",
- "owners": [ "dullweber" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-reopen-tab-in-product-help",
- "owners": [ "collinbaker" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-request-tablet-site",
- "owners": [ "xdai" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-resampling-input-events",
- "owners": [ "eirage", "nzolghadr", "input-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-resampling-scroll-events",
- "owners": [ "eirage", "nzolghadr", "input-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-resource-loading-hints",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-revamped-context-menu",
- "owners": [ "twellington" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-safe-browsing-ap-download-verdicts",
- "owners": [ "drubery" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-save-data",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // This flag is used for frequent manual testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-search-box-selection",
- "owners": [ "ginko", "newcomer" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-send-tab-to-self",
- "owners": [ "//components/send_tab_to_self/OWNERS" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-send-tab-to-self-broadcast",
- "owners": [ "//components/send_tab_to_self/OWNERS" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-send-tab-to-self-show-sending-ui",
- "owners": [ "//components/send_tab_to_self/OWNERS" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-send-tab-to-self-when-signed-in",
- "owners": [ "//components/send_tab_to_self/OWNERS" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-service-worker-imported-script-update-check",
- "owners": [ "worker-dev@chromium.org" ],
- // It's still in development. Tentatively set to M76 as the expiry_milestone.
- "expiry_milestone": 76
- },
- {
- "name": "enable-service-worker-long-running-message",
- "owners": [ "azeemarshad", "jlklein", "falken" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-service-worker-on-ui",
- "owners": [ "falken" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-sharing-device-registration",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-shill-sandboxing",
- "owners": [ "mortonm" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-show-autofill-signatures",
- "owners": [ "kolos" ],
- // Used for debugging and QA by the autofill team for the indefinite future.
- "expiry_milestone": -1
- },
- {
- "name": "enable-site-isolation-for-password-sites",
- "owners": [ "site-isolation-dev", "alexmos", "lukasza" ],
- // Note: consider keeping this flag even after password-triggered site
- // isolation launches to allow users of low-memory devices to opt in.
- "expiry_milestone": 80
- },
- {
- "name": "enable-site-per-process",
- "owners": [ "site-isolation-dev", "creis", "lukasza" ],
- // Even after shipping some form of Site Isolation on Android, we want to
- // give users the ability to opt into a stricter, more secure Site
- // Isolation.
- "expiry_milestone": -1
- },
- {
- "name": "enable-skia-renderer",
- "owners": [ "backer"],
- "expiry_milestone": 80
- },
- {
- "name": "enable-speculative-service-worker-start-on-query-input",
- "owners": [ "jdonnelly" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-start-surface",
- "owners": [ "gogerald"],
- "expiry_milestone": 83
- },
- {
- "name": "enable-storage-pressure-ui",
- "owners": [ "jarrydg"],
- "expiry_milestone": 82
- },
- {
- "name": "enable-streamlined-usb-printer-setup",
- "owners": [ "baileyberro" ],
- "expiry_milestone": 77
- },
- {
- "name" : "enable-subresource-redirect",
- "owners" : [ "robertogden", "tbansal" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-suggestions-with-substring-match",
- "owners": [ "tmartino" ],
- "expiry_milestone": 77
- },
- {
- "name": "enable-surfacecontrol",
- "owners": [ "khushalsagar" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-surfaces-for-videos",
- "owners": [ "ericrk", "khushalsagar", "mlamouri" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-sxg-prefetch-cache-for-navigations",
- "owners": [ "horo", "//content/browser/web_package/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-sxg-subresource-prefetching",
- "owners": [ "horo", "//content/browser/web_package/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-sync-device-info-in-transport-mode",
- "owners": ["//components/sync/OWNERS", "//chrome/browser/sharing/OWNERS"],
- "expiry_milestone": 81
- },
- {
- "name": "enable-sync-uss-bookmarks",
- "owners": [ "mamir", "//components/sync/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-sync-uss-nigori",
- "owners": [ "mmoskvitin@google.com", "//components/sync/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-sync-uss-passwords",
- "owners": [ "mamir", "//components/sync/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-system-webapps",
- "owners": [ "calamity" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-tab-engagement-reporting",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-tab-grid-layout",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-tab-groups",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-tab-groups-continuation",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-tab-groups-ui-improvements",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-tab-switcher-on-return",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-tab-to-gts-animation",
- "owners": [ "memex-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-text-fragment-anchor",
- "owners": [ "bokan", "input-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-tls13-early-data",
- "owners": [ "svaldez" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-touch-drag-drop",
- "owners": [ "eirage", "input-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-touchscreen-calibration",
- "owners": [ "malaykeshav" ],
- // This flag is used for testing new touchscreen ChromeOS hardware, both by
- // dev and QA.
- "expiry_milestone": -1
- },
- {
- "name": "enable-ui-devtools",
- "owners": [ "//components/ui_devtools/OWNERS" ],
- // Convenience debugging flag for non-stable ChromeOS Builds.
- // Devtools spins up a server which is not appropriate in the default case.
- "expiry_milestone": -1
- },
- {
- "name": "enable-unsafe-webgpu",
- "owners": [ "//third_party/blink/renderer/modules/webgpu/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-usbguard",
- "owners": [ "allenwebb", "mnissler", "jorgelo" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-use-hdr-transfer-function",
- "owners": [ "mcasas" ],
- "expiry_milestone": 89
- },
- {
- "name": "enable-use-zoom-for-dsf",
- "owners": [ "ccameron" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-vaapi-jpeg-image-decode-acceleration",
- "owners": [ "chromeos-gfx@google.com" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-vaapi-webp-image-decode-acceleration",
- "owners": [ "chromeos-gfx@google.com" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-virtual-desks",
- "owners": [ "afakhry" ],
- // This is a flag to enable or disable the Virtual Desks feature.
- "expiry_milestone": 81
- },
- {
- "name": "enable-virtual-desks-gestures",
- "owners": [ "afakhry" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-virtual-keyboard",
- "owners": [ "//ash/keyboard/OWNERS" ],
- // Useful for debugging the virtual keyboard on non-tablet devices.
- "expiry_milestone": -1
- },
- {
- "name": "enable-viz-display-compositor",
- "owners": [ "kylechar", "rjkroege" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-viz-hit-test-surface-layer",
- "owners": [ "yigu", "rjkroege", "kylechar" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-vulkan",
- "owners": [ "penghuang" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-web-authentication-ble-support",
- "owners": [ "webauthn-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "enable-web-authentication-cable-v2-support",
- "owners": [ "webauthn-team@google.com" ],
- "expiry_milestone": 84
- },
- {
- "name": "enable-web-authentication-testing-api",
- "owners": [ "webauthn-team@google.com" ],
- // This is required for testing.
- "expiry_milestone": -1
- },
- {
- "name": "enable-web-payments-experimental-features",
- "owners": [ "danyao", "rouslan", "paymentrequest@google.com" ],
- // This flag is used by early adoption partners to test new Web Payments
- // features in each release.
- "expiry_milestone": -1
- },
- {
- "name": "enable-webassembly-baseline",
- "owners": [ "clemensb", "wasm-team@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-webassembly-code-cache",
- "owners": [ "bbudge", "hablich", "wasm-team@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-webassembly-code-gc",
- "owners": [ "clemensb", "wasm-team@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-webassembly-simd",
- "owners": [ "gdeepti", "wasm-team@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-webassembly-threads",
- "owners": [ "binji", "hablich", "wasm-team@google.com" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-webgl-draft-extensions",
- "owners": [ "webgl-team@google.com" ],
- // This flag is the only way for end users to test upcoming WebGL
- // extensions on Android. It must not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "enable-webgl2-compute-context",
- "owners": [ "webgl-team@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-webnfc",
- "owners": [ "rijubrata.bhaumik@intel.com","reillyg" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-webrtc-hide-local-ips-with-mdns",
- "owners": [ "qingsi" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-webrtc-hw-h264-encoding",
- "owners": [ "hiroh", "chromeos-video-eng@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-webrtc-hw-vp8-encoding",
- "owners": [ "hiroh", "chromeos-video-eng@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-webrtc-hw-vp9-encoding",
- "owners": [ "hiroh", "chromeos-video-eng@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-webrtc-hybrid-agc",
- "owners": [ "aleloi" ],
- "expiry_milestone": 83
- },
- {
- "name": "enable-webrtc-new-encode-cpu-load-estimator",
- "owners": [ "nisse" ],
- "expiry_milestone": 85
- },
- {
- "name": "enable-webrtc-pipewire-capturer",
- "owners": [ "tomas.popela@gmail.com" ],
- "expiry_milestone": 79
- },
- {
- "name": "enable-webrtc-remote-event-log",
- "owners": [ "eladalon" ],
- "expiry_milestone": 76
- },
- {
- "name": "enable-webrtc-srtp-aes-gcm",
- "owners": [ "emadomara" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-webrtc-stun-origin",
- "owners": [ "qingsi", "jeroendb" ],
- "expiry_milestone": 78
- },
- {
- "name": "enable-webvr",
- "owners": [ "//third_party/blink/renderer/modules/vr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-winrt-geolocation-implementation",
- "owners": [ "pelavall@microsoft.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "enable-winrt-sensor-implementation",
- "owners": [ "wensh@microsoft.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "enable-zero-copy",
- "owners": [ "ccameron", "chrome-gpu@google.com" ],
- // This flag is commonly used when asking users to help gather debug info.
- "expiry_milestone": -1
- },
- {
- "name": "enable-zero-state-app-reinstall-suggestions",
- "owners": ["robsc", "napper", "jennyz"],
- "expiry_milestone": 80
- },
- {
- "name": "enable-zero-state-suggestions",
- "owners": [ "jennyz", "xiyuan" ],
- "expiry_milestone": 75
- },
- {
- "name": "enterprise-reporting-in-browser",
- "owners": [ "pastarmovj", "zmin" ],
- "expiry_milestone": 79
- },
- {
- "name": "ev-details-in-page-info",
- "owners": [ "cthomp" ],
- "expiry_milestone": 83
- },
- {
- "name": "exo-pointer-lock",
- "owners": [ "hollingum" ],
- "expiry_milestone": 82
- },
- {
- "name": "expensive-background-timer-throttling",
- "owners": [ "altimin" ],
- "expiry_milestone": 76
- },
- {
- "name": "explore-sites",
- "owners": [ "chili", "dewittj" ],
- "expiry_milestone": 83
- },
- {
- "name": "extension-apis",
- "owners": [ "rdevlin.cronin" ],
- "expiry_milestone": 76
- },
- {
- "name": "extension-content-verification",
- "owners": [ "//extensions/OWNERS" ],
- "expiry_milestone": 86
- },
- {
- "name": "extensions-on-chrome-urls",
- "owners": [ "//extensions/OWNERS" ],
- // This enables the use of extensions on chrome:// URLs. This is useful for
- // a11y extensions as well as for debugging.
- "expiry_milestone": -1
- },
- {
- "name": "extensions-toolbar-menu",
- "owners": [ "//extensions/OWNERS", "pbos" ],
- "expiry_milestone": 82
- },
- {
- "name": "file-handling-api",
- "owners": ["harrisjay@chromium.org", "raymes@chromium.org"],
- "expiry_milestone": 79
- },
- {
- "name": "file-manager-feedback-panel",
- "owners": [ "adanilo" ],
- "expiry_milestone": 81
- },
- {
- // See https://crbug.com/904630, offical builds only.
- "name": "file-manager-piex-wasm",
- "owners": [ "noel" ],
- "expiry_milestone": 79
- },
- {
- "name": "files-ng",
- "owners": [ "adanilo", "noel" ],
- "expiry_milestone": 82
- },
- {
- "name": "fill-on-account-select",
- "owners": [ "jdoerrie" ],
- "expiry_milestone": 80
- },
- {
- "name": "fill-on-account-select-http",
- "owners": [ "jdoerrie" ],
- "expiry_milestone": 80
- },
- {
- "name": "focus-mode",
- "owners": [ "dfried", "pbos", "yiningwang@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "font-src-local-matching",
- "owners": [ "drott", "layout-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "force-color-profile",
- "owners": [ "ccameron" ],
- "expiry_milestone": 81
- },
- {
- "name": "force-dice-migration",
- "owners": [ "msalama" ],
- "expiry_milestone": 85
- },
- {
- "name": "force-effective-connection-type",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // ECT is a baked-in feature of Chrome. This flag is used for frequent
- // testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "force-enable-system-aec",
- "owners": [ "grunell" ],
- "expiry_milestone": 80
- },
- {
- "name": "force-show-update-menu-badge",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/omaha/OWNERS" ],
- // This is required by test teams to verify functionality on devices which
- // have no access to commandline flags.
- "expiry_milestone": -1
- },
- {
- "name": "force-text-direction",
- "owners": [ "krb", "pbos", "ellyjones" ],
- // This flag is used for developers who don't speak an RTL language to QA
- // RTL text regardless of UI layout direction. This for instance impacts the
- // Omnibox where arbitrary text directions may be entered.
- "expiry_milestone": -1
- },
- {
- "name": "force-ui-direction",
- "owners": [ "pbos", "ellyjones" ],
- // This flag is used for developers who don't speak an RTL language to QA
- // RTL UI layout.
- "expiry_milestone": -1
- },
- {
- "name": "force-update-menu-type",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/omaha/OWNERS" ],
- // This is required by test teams to verify functionality on devices which
- // have no access to commandline flags.
- "expiry_milestone": -1
- },
- {
- "name": "forced-colors",
- "owners": [ "weblayout@microsoft.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "form-controls-refresh",
- "owners": [ "iopopesc@microsoft.com", "//third_party/blink/renderer/core/OWNERS" ],
- "expiry_milestone": 81
- },
- {
- "name": "fractional-scroll-offsets",
- "owners": [ "bokan" ],
- "expiry_milestone": 81
- },
- {
- "name": "fuzzy-app-search",
- "owners": [ "thanhdng", "jiameng" ],
- "expiry_milestone": 82
- },
- {
- "name": "gaia-action-buttons",
- "owners": [ "jeroendh", "rsorokin" ],
- "expiry_milestone": 79
- },
- {
- "name": "gdi-text-printing",
- "owners": [ "//printing/OWNERS" ],
- "expiry_milestone": 84
- },
- {
- "name": "gesture-properties-dbus-service",
- "owners": [ "hcutts", "chromeos-tango@google.com" ],
- // Used by developers for debugging and input device tuning.
- "expiry_milestone": -1
- },
- {
- "name": "global-media-controls",
- "owners": [ "steimel", "media-dev" ],
- "expiry_milestone": 84
- },
- {
- "name": "google-password-manager",
- "owners": [ "ioanap", "jdoerrie" ],
- "expiry_milestone": 82
- },
- {
- "name": "handwriting-gesture",
- "owners": [ "essential-inputs-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "happiness-tracking-surveys-for-desktop",
- "owners": [ "//chrome/browser/ui/hats/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "happiness-tracking-surveys-for-desktop-demo",
- "owners": [ "//chrome/browser/ui/hats/OWNERS" ],
- // A debugging and demo flag to allow UI/dev/testing team to always show the UI
- // components for the survey without being limited by the triggering mechanism.
- // This flag should expire along with the above feature flag.
- "expiry_milestone": 80
- },
- {
- "name": "hardware-media-key-handling",
- "owners": [ "steimel", "media-dev" ],
- "expiry_milestone": 84
- },
- {
- "name": "harfbuzz-pdf-subsetter",
- "owners": ["dhoss", "thestig"],
- "expiry_milestone": 80
- },
- {
- "name": "http-auth-committed-interstitials",
- "owners": [ "jdeblasio", "estark" ],
- "expiry_milestone": 77
- },
- {
- "name": "identity-disc",
- "owners": ["//chrome/android/java/src/org/chromium/chrome/browser/toolbar/OWNERS"],
- "expiry_milestone": 78
- },
- {
- "name": "ignore-gpu-blacklist",
- "owners": [ "kbr", "zmo" ],
- // A debugging flag intended for end-users where there may not be any other
- // way to turn off graphics features.
- "expiry_milestone": -1
- },
- {
- "name": "ignore-litepage-redirect-optimization-blacklist",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "ignore-previews-blocklist",
- "owners": [ "//components/data_reduction_proxy/OWNERS" ],
- // Previews is a baked-in feature of Chrome. This flag is used for frequent
- // testing and should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "improved-cookie-controls",
- "owners": [ "dullweber", "huanzhong" ],
- "expiry_milestone": 84
- },
- {
- "name": "improved-cookie-controls-for-third-party-cookie-blocking",
- "owners": [ "dullweber", "huanzhong" ],
- "expiry_milestone": 84
- },
- {
- "name": "in-product-help-demo-mode-choice",
- "owners": [ "dtrainor", "nyquist" ],
- // This flag is used by teams as they develop in-product help integrations,
- // as well as by QA; this feature is difficult to test without this.
- "expiry_milestone": -1
- },
- {
- "name": "installable-ink-drop",
- "owners": [ "collinbaker", "pbos" ],
- "expiry_milestone": 82
- },
- {
- "name": "instant-tethering",
- "owners": [ "khorimoto" ],
- "expiry_milestone": 80
- },
- {
- "name": "interest-feed-content-suggestions",
- "owners": [ "//chrome/android/feed/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "interest-feed-notifications",
- "owners": [ "iwells", "//chrome/android/feed/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "isolate-origins",
- "owners": [ "site-isolation-dev", "alexmos", "creis", "lukasza" ],
- // This is useful for isolating additional origins beyond the normal site
- // isolation policy, as well as for diagnosing problems when site isolation
- // only applies to some sites.
- "expiry_milestone": 90
- },
- {
- "name": "kids-management-url-classification",
- "owners": [ "alanwink", "vtrmc", "unichromeos-eng" ],
- "expiry_milestone": 80
- },
- {
- "name": "list-all-display-modes",
- "owners": [ "//ui/display/OWNERS" ],
- // This flag is used for debugging and development purposes to list all
- // external displays' modes without any exclusions.'
- "expiry_milestone": -1
- },
- {
- "name": "load-media-router-component-extension",
- "owners": [ "mfoltz", "media-dev" ],
- // This flag has two purposes: in-team development/Q&A, and allowing
- // Chromium users to load this extension, which isn't normally distributed
- // with Chromium. It can be removed once the extension is removed, which has
- // external dependencies.
- "expiry_milestone": -1
- },
- {
- "name": "lock-screen-media-controls",
- "owners": [ "beccahughes", "media-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "mac-syscall-sandbox",
- "owners": [ "kerrnel@google.com" ],
- "expiry_milestone": 88
- },
- {
- "name": "mac-system-media-permissions-info-ui",
- "owners": [ "grunell", "engedy", "hkamila" ],
- "expiry_milestone": 78
- },
- {
- "name": "mac-v2-gpu-sandbox",
- "owners": [ "kerrnel@google.com" ],
- "expiry_milestone": 88
- },
- {
- "name": "mac-views-task-manager",
- "owners": [ "ellyjones" ],
- "expiry_milestone": 77
- },
- {
- "name": "manual-password-generation-android",
- "owners": [ "ioanap" ],
- "expiry_milestone": 80
- },
- {
- "name": "media-router-cast-allow-all-ips",
- "owners": [ "mfoltz" ],
- // This flag is used by users with unusual network configurations to allow
- // cast to work, but enabling this behavior has security implications that
- // aren't easily understood. It is primarily used by support to help
- // individual users get cast working.
- "expiry_milestone": -1
- },
- {
- "name": "memlog",
- "owners": [ "erikchen", "ssid", "etienneb", "alph" ],
- // Memlog is Chrome's heap profiler. It is used for both automated and
- // manual performance analysis. This flag allows a user or developer to
- // capture a memlog without disturbing the situation under test by
- // restarting to apply a switch. It should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "memlog-sampling-rate",
- "owners": [ "erikchen", "ssid", "etienneb", "alph" ],
- // Memlog is Chrome's heap profiler. It is used for both automated and
- // manual performance analysis. This flag allows a user or developer to
- // capture a memlog without disturbing the situation under test by
- // restarting to apply a switch. It should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "memlog-stack-mode",
- "owners": [ "erikchen", "ssid", "etienneb", "alph" ],
- // Memlog is Chrome's heap profiler. It is used for both automated and
- // manual performance analysis. This flag allows a user or developer to
- // capture a memlog without disturbing the situation under test by
- // restarting to apply a switch. It should not be removed.
- "expiry_milestone": -1
- },
- {
- "name": "message-center-redesign",
- "owners": ["amehfooz", "tengs"],
- "expiry_milestone": 81
- },
- {
- "name": "metal",
- "owners": ["ccameron", "jvanverth"],
- "expiry_milestone": 83
- },
- {
- "name": "mime-handler-view-in-cross-process-frame",
- "owners": [ "ekaramad" ],
- "expiry_milestone": 80
- },
- {
- "name": "mirroring-service",
- "owners": [ "mfoltz", "takumif" ],
- "expiry_milestone": 83
- },
- {
- "name": "mobile-identity-consistency",
- "owners": [ "bsazonov", "droger", "msarda" ],
- "expiry_milestone": 80
- },
- {
- "name": "mouse-subframe-no-implicit-capture",
- "owners": [ "eirage", "input-dev" ],
- "expiry_milestone": 78
- },
- {
- "name": "native-file-system-api",
- "owners": [ "mek", "pwnall" ],
- "expiry_milestone": 80
- },
- {
- "name": "network-service-in-process",
- "owners": [ "network-service-dev" ],
- "expiry_milestone": 84
- },
- {
- "name": "new-net-error-page-ui",
- "owners": [ "harringtond" ],
- "expiry_milestone": 75
- },
- {
- "name": "new-overview-tablet-layout",
- "owners": [ "sammiequon" ],
- "expiry_milestone": 85
- },
- {
- "name": "new-tabstrip-animation",
- "owners": [ "tbergquist" ],
- "expiry_milestone": 81
- },
- {
- "name": "new-usb-backend",
- "owners": [ "reillyg@chromium.org" ],
- "expiry_milestone": 83
- },
- {
- "name": "newblue",
- "owners": [ "sonnysasaka" ],
- "expiry_milestone": 81
- },
- {
- "name": "no-credit-card-abort",
- "owners": [ "//components/payments/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "notification-scheduler-debug-options",
- "owners": [ "//chrome/browser/notifications/scheduler/OWNERS" ],
- "expiry_milestone": 81
- },
- {
- "name": "ntp-customization-menu-v2",
- "owners": ["kmilka", "ramyan"],
- "expiry_milestone": 79
- },
- {
- "name": "ntp-disable-initial-most-visited-fade-in",
- "owners": ["dbeam"],
- "expiry_milestone": 79
- },
- {
- "name": "ntp-dismiss-promos",
- "owners": ["dbeam"],
- "expiry_milestone": 82
- },
- {
- "name": "ntp-realbox",
- "owners": ["dbeam", "mahmadi"],
- "expiry_milestone": 85
- },
- {
- "name": "oculus-vr",
- "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "offline-home",
- "owners": [ "shaktisahu", "dtrainor"],
- "expiry_milestone": 85
- },
- {
- "name": "offline-indicator-always-http-probe",
- "owners": [ "jianli", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-indicator-choice",
- "owners": [ "jianli", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-indicator-v2",
- "owners": [ "sinansahin@google.com", "twellington", "offline-dev" ],
- "expiry_milestone": 85
- },
- {
- "name": "offline-pages-alternate-dino-page",
- "owners": [ "carlosk", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-ct",
- "owners": [ "dewittj", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-ct-suppress-completed-notification",
- "owners": [ "dewittj", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-ct-v2",
- "owners": [ "dewittj", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-failed-download",
- "owners": [ "chili", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-in-downloads-home-open-in-cct",
- "owners": [ "carlosk", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-live-page-sharing",
- "owners": [ "petewil", "jianli", "offline-dev" ],
- "expiry_milestone": 86
- },
- {
- "name": "offline-pages-load-signal-collecting",
- "owners": [ "petewil", "offline-dev" ],
- "expiry_milestone": 86
- },
- {
- "name": "offline-pages-pending-download",
- "owners": [ "chili", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-prefetching",
- "owners": [ "carlosk", "dewittj", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "offline-pages-renovations",
- "owners": [ "petewil", "offline-dev" ],
- "expiry_milestone": 86
- },
- {
- "name": "offline-pages-resource-based-snapshot",
- "owners": [ "petewil", "offline-dev" ],
- "expiry_milestone": 86
- },
- {
- "name": "offlining-recent-pages",
- "owners": [ "carlosk", "offline-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-autocomplete-titles",
- "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 85
- },
- {
- "name": "omnibox-disable-instant-extended-limit",
- "owners": [ "pnoland", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-display-title-for-current-url",
- "owners": [ "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-drive-suggestions",
- "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-experimental-keyword-mode",
- "owners": [ "krb", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-group-suggestions-by-search-vs-url",
- "owners": [ "krb", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 78
- },
- {
- "name": "omnibox-local-entity-suggestions",
- "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-material-design-weather-icons",
- "owners": [ "manukh", "ender", "chromium-omnibox-team" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-max-url-matches",
- "owners": [ "krb", "chromium-omnibox-team" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-on-device-head-suggestions",
- "owners": [ "cechen", "suggest-2g@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-on-focus-suggestions",
- "owners": [ "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 85
- },
- {
- "name": "omnibox-pedal-suggestions",
- "owners": [ "orinj", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-preserve-default-match-against-async-update",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 85
- },
- {
- "name": "omnibox-remove-suggestions-from-clipboard",
- "owners": [ "gangwu" ],
- "expiry_milestone": 85
- },
- {
- "name": "omnibox-reverse-answers",
- "owners": [ "jdonnelly", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-rich-entity-suggestions",
- "owners": [ "jdonnelly", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-search-engine-logo",
- "owners": [ "wylieb", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-short-bookmark-suggestions",
- "owners": [ "krb", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 78
- },
- {
- "name": "omnibox-spare-renderer",
- "owners": [ "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-suggestion-transparency-options",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-tab-switch-suggestions",
- "owners": [ "krb", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "omnibox-tab-switch-suggestions-dedicated-row",
- "owners": [ "krb", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "omnibox-ui-cues-for-search-history-matches",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 78
- },
- {
- "name": "omnibox-ui-hide-steady-state-url-path-query-and-ref",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-ui-hide-steady-state-url-scheme",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-ui-hide-steady-state-url-trivial-subdomains",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-ui-max-autocomplete-matches",
- "owners": [ "jdonnelly", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "omnibox-ui-one-click-unelide",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-ui-show-suggestion-favicons",
- "owners": [ "ender", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 79
- },
- {
- "name": "omnibox-ui-swap-title-and-url",
- "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "omnibox-zero-suggestions-on-ntp",
- "owners": [ "chrome-omnibox-team@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "omnibox-zero-suggestions-on-ntp-realbox",
- "owners": [ "dbeam", "mahmadi" ],
- "expiry_milestone": 85
- },
- {
- "name": "omnibox-zero-suggestions-on-serp",
- "owners": [ "tommycli", "pnoland" ],
- "expiry_milestone": 85
- },
- {
- "name": "on-the-fly-mhtml-hash-computation",
- "owners": [ "mtlieuu", "offline-dev@chromium.org" ],
- "expiry_milestone": 76
- },
- {
- "name": "openvr",
- "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "openxr",
- "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "out-of-blink-cors",
- "owners": [ "toyoshim" ],
- "expiry_milestone": 80
- },
- {
- "name": "overlay-new-layout",
- "owners": [ "donnd", "jinsukkim", "contextual-search-eng" ],
- "expiry_milestone": 79
- },
- {
- "name": "overlay-scrollbars",
- "owners": [ "chaopeng", "bokan", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "overlay-scrollbars-flash-after-scroll-update",
- "owners": [ "chaopeng", "bokan", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "overlay-scrollbars-flash-when-mouse-enter",
- "owners": [ "chaopeng", "bokan", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "overlay-strategies",
- "owners": [ "chromeos-gfx@google.com" ],
- // This flag is used for QA & debugging on ChromeOS, which has no way to
- // customize switches.
- "expiry_milestone": -1
- },
- {
- "name": "overscroll-history-navigation",
- "owners": [ "mohsen", "jinsukkim" ],
- "expiry_milestone": 80
- },
- {
- "name": "passive-event-listeners-due-to-fling",
- "owners": [ "sahel", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "passive-listener-default",
- "owners": [ "nzolghadr", "input-dev" ],
- "expiry_milestone": 78
- },
- {
- "name": "password-editing-android",
- "owners": [ "izuzic", "fhorschig" ],
- "expiry_milestone": 80
- },
- {
- "name": "password-leak-detection",
- "owners": [ "jdoerrie", "vasilii" ],
- "expiry_milestone": 80
- },
- {
- "name": "password-manager-onboarding-android",
- "owners": ["achulkov", "ioanap"],
- "expiry_milestone": 80
- },
- {
- "name": "PasswordImport",
- "owners": [ "vasilii" ],
- "expiry_milestone": 76
- },
- {
- "name": "passwords-account-storage",
- "owners": ["mamir", "treib"],
- "expiry_milestone": 85
- },
- {
- "name": "pdf-annotations",
- "owners": [ "dstockwell@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "pdf-form-save",
- "owners": [ "thestig" ],
- "expiry_milestone": 82
- },
- {
- "name": "per-method-can-make-payment-quota",
- "owners": [ "rouslan" ],
- "expiry_milestone": 78
- },
- {
- "name": "percent-based-scrolling",
- "owners": [ "maamert@microsoft.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "periodic-background-sync",
- "owners": [ "nator" ],
- "expiry_milestone": 80
- },
- {
- "name": "policy-atomic-group-enabled",
- "owners": [ "ydago" ],
- "expiry_milestone": 80
- },
- {
- "name": "prefetch-main-resource-network-isolation-key",
- "owners": [ "dom", "yhirano" ],
- "expiry_milestone": 85
- },
- {
- "name": "prefetch-privacy-changes",
- "owners": [ "dom", "yhirano" ],
- "expiry_milestone": 85
- },
- {
- "name": "privacy-settings-redesign",
- "owners": ["harrisonsean", "msramek",
- "chrome-privacy-core@google.com"],
- "expiry_milestone": 82
- },
- {
- "name": "proactive-tab-freeze",
- "owners": [ "fdoray" ],
- "expiry_milestone": 80
- },
- {
- "name": "profile-menu-revamp",
- "owners": [ "tangltom" ],
- "expiry_milestone": 82
- },
- {
- "name": "prominent-dark-mode-active-tab-title",
- "owners": [ "dfried" ],
- "expiry_milestone": 82
- },
- {
- "name": "pull-to-refresh",
- "owners": [ "afakhry" ],
- "expiry_milestone": 83
- },
- {
- "name": "quiet-notification-prompts",
- "owners": [ "engedy", "mkwst", "andypaicu", "hkamila" ],
- "expiry_milestone": 80
- },
- {
- "name": "reader-mode-heuristics",
- "owners": [ "mdjones" ],
- "expiry_milestone": 78
- },
- { "name": "reduce-display-notifications",
- "owners": [ "baileyberro", "zentaro" ],
- "expiry_milestone": 82
- },
- {
- "name": "reduced-referrer-granularity",
- "owners": [ "davidvc", "mkwst", "jochen" ],
- "expiry_milestone": 81
- },
- {
- "name": "release-notes",
- "owners": [ "yulunwu" ],
- "expiry_milestone": 79
- },
- {
- "name": "release-notes-notification",
- "owners": [ "yulunwu" ],
- "expiry_milestone": 79
- },
- {
- "name": "request-unbuffered-dispatch",
- "owners": [ "bokan" ],
- "expiry_milestone": 80
- },
- {
- "name": "rewrite-leveldb-on-deletion",
- "owners": [ "dullweber" ],
- "expiry_milestone": 75
- },
- {
- "name": "safe-browsing-use-local-blacklists-v2",
- "owners": [ "vakh" ],
- "expiry_milestone": 76
- },
- {
- "name": "safety-tips",
- "owners": [ "jdeblasio", "estark", "meacer" ],
- "expiry_milestone": 82
- },
- {
- "name": "same-site-by-default-cookies",
- "owners": [ "chlily", "morlovich" ],
- "expiry_milestone": 83
- },
- {
- "name": "scalable-app-list",
- "owners": [ "tbarzic" ],
- "expiry_milestone": 82
- },
- {
- "name": "scheduler-configuration",
- "owners": [ "kerrnel", "mnissler" ],
- "expiry_milestone": 88
- },
- {
- "name": "scrollable-tabstrip",
- "owners": ["chrome-desktop-ui-seattle@google.com", "tbergquist"],
- "expiry_milestone": 82
- },
- {
- "name": "security-interstitials-dark-mode",
- "owners": ["estark"],
- "expiry_milestone": 79
- },
- {
- "name": "set-market-url-for-testing",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/omaha/OWNERS" ],
- // This is required by test teams to verify functionality on devices which
- // have no access to commandline flags.
- "expiry_milestone": -1
- },
- {
- "name": "shared-array-buffer",
- "owners": [ "binji" ],
- "expiry_milestone": 78
- },
- {
- "name": "shared-clipboard-receiver",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "shared-clipboard-ui",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "sharing-derive-vapid-key",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "sharing-use-device-info",
- "owners": [ "//chrome/browser/sharing/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "shelf-hotseat",
- "owners": [ "manucornet", "mmourgos", "newcomer" ],
- "expiry_milestone": 82
- },
- {
- "name": "shelf-hover-previews",
- "owners": [ "manucornet" ],
- "expiry_milestone": 84
- },
- {
- "name": "shelf-scrollable",
- "owners": [ "andrewxu", "manucornet" ],
- "expiry_milestone": 82
- },
- {
- "name": "shopping-assist",
- "owners": [ "yusufo", "donnd" ],
- // This is a shopping assistance experiment for Android.
- "expiry_milestone": 77
- },
- {
- "name": "show-autofill-type-predictions",
- "owners": [ "ftirelo", "mathp" ],
- // This is used by autofill devs to debug on Android.
- "expiry_milestone": -1
- },
- {
- "name": "show-bluetooth-debug-log-toggle",
- "owners": [ "hansberry", "khorimoto", "yshavit" ],
- // This is a debug tool used to help in the field with Bluetooth issues
- // caused by specific hardware, noisy environments, etc. Thus, it should not
- // expire.
- "expiry_milestone": -1
- },
- {
- "name": "show-bluetooth-device-battery",
- "owners": [ "hansberry" ],
- "expiry_milestone": 80
- },
- {
- "name": "show-legacy-tls-warnings",
- "owners": [ "cthomp" ],
- "expiry_milestone": 85
- },
- {
- "name": "show-sync-paused-reason-cookies-cleared-on-exit",
- "owners": [ "msalama" ],
- "expiry_milestone": 85
- },
- {
- "name": "show-taps",
- "owners": [ "//ash/OWNERS" ],
- // This is a debug flag, so that video bug reports can show input taps to
- // aid in getting clear bug reports.
- "expiry_milestone": -1
- },
- {
- "name": "show-touch-hud",
- "owners": [ "//ash/OWNERS" ],
- // This is a debug tool to help in the field with hardware issues that
- // generate spurious touch events.
- "expiry_milestone": -1
- },
- {
- "name": "silent-debugger-extension-api",
- "owners": [ "//extensions/OWNERS" ],
- "expiry_milestone": 77
- },
- {
- "name": "simplify-https-indicator",
- "owners": [ "cthomp", "estark" ],
- "expiry_milestone": 76
- },
- {
- "_comment1": "Shipping some form of Site Isolation to Android is tracked",
- "_comment2": "in https://crbug.com/849815.",
- "name": "site-isolation-trial-opt-out",
- "owners": [ "site-isolation-dev", "alexmos", "creis", "lukasza" ],
- "expiry_milestone": 85
- },
- {
- "name": "site-settings",
- "owners": [ "mxcai" ],
- "expiry_milestone": 78
- },
- {
- "name": "smart-dim-model-v3",
- "owners": [ "amoylan", "jiameng" ],
- "expiry_milestone": 80
- },
- {
- "name": "smart-text-selection",
- "owners": [ "djacobo", "linben" ],
- "expiry_milestone": 76
- },
- {
- "name": "smooth-scrolling",
- "owners": [ "bokan", "input-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "split-settings",
- "owners": [ "maybelle" ],
- "expiry_milestone": 80
- },
- {
- "name": "stop-in-background",
- "owners": [ "chrome-catan@google.com" ],
- "expiry_milestone": 76
- },
- {
- "name": "stop-non-timers-in-background",
- "owners": [ "fdoray" ],
- "expiry_milestone": 75
- },
- {
- "name": "storage-access-api",
- "owners": [ "brandm@microsoft.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "strict-origin-isolation",
- "owners": ["wjmaclean", "alexmos", "creis"],
- // This can be used to opt in to origin isolation which isolates full
- // origins rather than sites. Note that this breaks document.domain and is
- // therefore unlikely to ship anytime soon, but this allows experimenting
- // with a stronger isolation policy for users who may not care about
- // breaking compatibility with document.domain.
- "expiry_milestone": 90
- },
- {
- "name": "SupervisedUserCommittedInterstitials",
- "owners": [ "carlosil" ],
- "expiry_milestone": 77
- },
- {
- "name": "sync-clipboard-service",
- "owners": [ "joaodasilva" ],
- "expiry_milestone": 80
- },
- {
- "name": "sync-support-secondary-account",
- "owners": [ "treib", "//components/sync/OWNERS" ],
- "expiry_milestone": 80
- },
- {
- "name": "sync-wifi-configurations",
- "owners": ["jonmann", "cros-system-services@google.com"],
- "expiry_milestone": 80
- },
- {
- "name": "system-keyboard-lock",
- "owners": [ "joedow", "garykac", "jamiewalch" ],
- "expiry_milestone": 79
- },
- {
- "name": "tab-groups",
- "owners": [ "chrome-desktop-ui-seattle@google.com", "bsep" ],
- "expiry_milestone": 82
- },
- {
- "name": "tab-hover-card-images",
- "owners": [ "dfried", "corising", "//chrome/browser/ui/views/tabs/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "tab-hover-cards",
- "owners": [ "corising", "//chrome/browser/ui/views/tabs/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "tab-outlines-in-low-contrast-themes",
- "owners": [ "dfried" ],
- "expiry_milestone": 82
- },
- {
- "name": "tab-switcher-longpress-menu",
- "owners": [ "lazzzis@google.com", "twellington" ],
- "expiry_milestone": 81
- },
- {
- "name": "temporary-unexpire-flags-m76",
- "owners": [ "ellyjones", "flags-dev" ],
- "expiry_milestone": 80
- },
- {
- "name": "temporary-unexpire-flags-m78",
- "owners": [ "ellyjones", "flags-dev" ],
- "expiry_milestone": 81
- },
- {
- "name": "terminal-system-app",
- "owners": [ "joelhockey", "benwells", "//chrome/browser/chromeos/guest_os/OWNERS" ],
- "expiry_milestone": 82
- },
- {
- "name": "tint-gl-composited-content",
- "owners": [ "chromeos-gfx@google.com" ],
- // This flag is used for QA & development on ChromeOS, which has no way to
- // customize switches.
- "expiry_milestone": -1
- },
- {
- "name": "tls13-hardening-for-local-anchors",
- "owners": [ "davidben", "svaldez" ],
- "expiry_milestone": 85
- },
- {
- "name": "top-chrome-touch-ui",
- "owners": [ "chrome-desktop-ui-sea@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "touch-events",
- "owners": [ "eirage", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "touch-selection-strategy",
- "owners": [ "omrilio" ],
- "expiry_milestone": 80
- },
- {
- "name": "touch-to-fill",
- "owners": [ "jdoerrie" ],
- "expiry_milestone": 80
- },
- {
- "name": "touchpad-overscroll-history-navigation",
- "owners": [ "chaopeng", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "trace-upload-url",
- "owners": [ "tracing" ],
- "expiry_milestone": 80
- },
- {
- "name": "translate-android-manual-trigger",
- "owners": [ "anthonyvd", "frechette", "chrome-language@google.com" ],
- // This flag is used to force manual Translate IPH to trigger on Android,
- // which has no way to set command-line flags.
- "expiry_milestone": -1
- },
- {
- "name": "translate-force-trigger-on-english",
- "owners": [ "anthonyvd", "frechette", "chrome-language@google.com" ],
- "expiry_milestone": 78
- },
- {
- "name": "translate-ui-bubble-options",
- "owners": [ "hhw", "anthonyvd", "chrome-language@google.com" ],
- "expiry_milestone": 78
- },
- {
- "name": "treat-unsafe-downloads-as-active-content",
- "owners": [ "jdeblasio", "cthomp" ],
- "expiry_milestone": 79
- },
- {
- "name": "trim-on-all-frames-frozen",
- "owners": [ "bgeffon", "sonnyrao" ],
- "expiry_milestone": 81
- },
- {
- "name": "trim-on-memory-pressure",
- "owners": [ "bgeffon", "sonnyrao" ],
- "expiry_milestone": 81
- },
- {
- "name": "try-supported-channel-layouts",
- "owners": [ "dalecurtis" ],
- "expiry_milestone": 76
- },
- {
- "name": "turn-off-streaming-media-caching",
- "owners": [ "shawnpi@microsoft.com" ],
- "expiry_milestone": 82
- },
- {
- "name": "ui-disable-partial-swap",
- "owners": [ "//components/viz/OWNERS" ],
- // This flag is used for in-the-field debugging of rendering issues,
- // especially on Chrome OS.
- "expiry_milestone": -1
- },
- {
- "name": "ui-show-composited-layer-borders",
- "owners": [ "ccameron" ],
- "expiry_milestone": 76
- },
- {
- "name": "ui-slow-animations",
- "owners": [ "oshima", "sammiequon", "afakhry" ],
- // This flag is used for in-the-field debugging of animation issues on
- // Chrome OS.
- "expiry_milestone": -1
- },
- {
- "name": "unified-consent",
- "owners": [ "msarda", "tangltom" ],
- "expiry_milestone": 79
- },
- {
- "name": "unsafely-treat-insecure-origin-as-secure",
- "owners": [ "security-dev" ],
- "expiry_milestone": 82
- },
- {
- "name": "update-hover-at-begin-frame",
- "owners": [ "lanwei", "input-dev" ],
- "expiry_milestone": 81
- },
- {
- "name": "update-menu-item-custom-summary",
- "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/omaha/OWNERS" ],
- // This is required by test teams to verify functionality on devices which
- // have no access to commandline flags.
- "expiry_milestone": -1
- },
- {
- "name": "updated_cellular_activation_ui",
- "owners": [ "azeemarshad", "khorimoto" ],
- "expiry_milestone": 80
- },
- {
- "name": "usage-stats",
- "owners": [ "pnoland", "chromeshine@google.com" ],
- "expiry_milestone": 80
- },
- {
- "name": "use-angle",
- "owners": [ "angle-team@google.com" ],
- // This flag is used by certain customers to set ANGLE to use its OpenGL
- // backend on Windows. Its usage is small enough that it isn't worth
- // promoting this to a chrome://settings entry, but it needs to be readily
- // available.
- "expiry_milestone": -1
- },
- {
- "name": "use-fake-device-for-media-stream",
- "owners": [ "mcasas", "chromeos-gfx@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "use_messages_google_com_domain",
- "owners": [ "azeemarshad", "khorimoto" ],
- "expiry_milestone": 76
- },
- {
- "name": "use_messages_staging_url",
- "owners": [ "azeemarshad", "khorimoto", "jonmann" ],
- // This flag is required for QA and dogfood testing.
- "expiry_milestone": -1
- },
- {
- "name": "use-multilogin-endpoint",
- "owners": [ "droger", "msarda" ],
- "expiry_milestone": 78
- },
- {
- "name": "use-new-accept-language-header",
- "owners": [ "claudiomagni" ],
- "expiry_milestone": 76
- },
- {
- "name": "use-pdf-compositor-service-for-print",
- "owners": [ "//printing/OWNERS" ],
- "expiry_milestone": 79
- },
- {
- "name": "use-search-click-for-right-click",
- "owners": [ "zentaro" ],
- "expiry_milestone": 80
- },
- {
- "name": "use-sync-sandbox",
- "owners": [ "//components/sync/OWNERS" ],
- // This flag is required for testing with the related
- // wallet-service-use-sandbox on Android.
- "expiry_milestone": -1
- },
- {
- "name": "use-winrt-midi-api",
- "owners": [ "toyoshim" ],
- "expiry_milestone": 83
- },
- {
- "name": "user-activation-v2",
- "owners": [ "mustaq", "input-dev" ],
- "expiry_milestone": 76
- },
- {
- "name": "username-first-flow",
- "owners": [ "dvadym" ],
- "expiry_milestone": 80
- },
- {
- "name": "wake-on-wifi-packet",
- "owners": [ "abhishekbh", "chirantan" ],
- "expiry_milestone": 76
- },
- {
- "name": "wallet-service-use-sandbox",
- "owners": [ "jsaul@google.com", "jiahuiguo", "payments-autofill-team@google.com" ],
- // This flag is used by testing teams to run Google Payments calls against
- // the development server environment.
- "expiry_milestone": -1
- },
- {
- "name": "web-bundles",
- "owners": [ "toyoshim", "ksakamoto", "horo", "kinuko" ],
- "expiry_milestone": 83
- },
- {
- "name": "web-contents-occlusion",
- "owners": [ "davidbienvenu" ],
- "expiry_milestone": 81
- },
- {
- "name": "webui-a11y-enhancements",
- "owners": [ "aee" ],
- "expiry_milestone": 82
- },
- {
- "name": "webui-tab-strip",
- "owners": [ "johntlee", "pbos" ],
- "expiry_milestone": 82
- },
- {
- "name": "webxr",
- "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "webxr-anchors",
- "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "webxr-ar-dom-overlay",
- "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 81
- },
- {
- "name": "webxr-ar-module",
- "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "webxr-hit-test",
- "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "webxr-orientation-sensor-device",
- "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "webxr-plane-detection",
- "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "win-use-native-spellchecker",
- "owners": [ "gujen@google.com", "yyushkina@google.com" ],
- "expiry_milestone": 81
- },
- {
- "name": "windows-mixed-reality",
- "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "xr-sandbox",
- "owners": [ "//chrome/services/isolated_xr_device/OWNERS", "xr-dev@chromium.org" ],
- "expiry_milestone": 82
- },
- {
- "name": "zero-state-files",
- "owners": [ "tby", "jiameng" ],
- "expiry_milestone": 82
- }
-]
diff --git a/chromium/chrome/browser/flag-never-expire-list.json b/chromium/chrome/browser/flag-never-expire-list.json
deleted file mode 100644
index dcd980b4bac..00000000000
--- a/chromium/chrome/browser/flag-never-expire-list.json
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// This file contains a list of flags that are permitted to have the
-// never-expire flag set (i.e., expiration milestone set to -1). This is a
-// separate file from flag-metadata.json so that that file can have per-file
-// owners set to * while still having flags-team policy about unexpiring flags
-// applied via ownership of this file. Note that these flags are not *required*
-// to have never-expire set; they are simply permitted to (i.e., there is no
-// enforcement the other direction).
-//
-// The format of this file is a list of flag names; it is validated by a unit
-// test (AboutFlagsTest.OnlyPermittedFlagsNeverExpire).
-[
- "allow-previews",
- "arc-native-bridge-toggle",
- "ash-debug-shortcuts",
- "ash-enable-unified-desktop",
- "data-saver-server-previews",
- "dcheck-is-fatal",
- "disable-accelerated-2d-canvas",
- "disable-accelerated-mjpeg-decode",
- "disable-accelerated-video-decode",
- "disable-accelerated-video-encode",
- "disable-explicit-dma-fences",
- "disable-javascript-harmony-shipping",
- "disable-threaded-scrolling",
- "disable-webrtc-hw-decoding",
- "disable-webrtc-hw-encoding",
- "disallow-doc-written-script-loads",
- "enable-autofill-credit-card-upload",
- "enable-command-line-on-non-rooted-devices",
- "enable-data-reduction-proxy-server-experiment",
- "enable-devtools-experiments",
- "enable-experimental-web-platform-features",
- "enable-experimental-webassembly-features",
- "enable-future-v8-vm-features",
- "enable-gpu-rasterization",
- "enable-gpu-service-logging",
- "enable-javascript-harmony",
- "enable-lite-page-server-previews",
- "enable-logging-js-console-messages",
- "enable-network-logging-to-file",
- "enable-noscript-previews",
- "enable-offline-previews",
- "enable-parallel-downloading",
- "enable-resource-loading-hints",
- "enable-save-data",
- "enable-show-autofill-signatures",
- "enable-site-per-process",
- "enable-touchscreen-calibration",
- "enable-ui-devtools",
- "enable-virtual-keyboard",
- "enable-web-authentication-testing-api",
- "enable-web-payments-experimental-features",
- "enable-webgl-draft-extensions",
- "enable-zero-copy",
- "extensions-on-chrome-urls",
- "force-effective-connection-type",
- "force-show-update-menu-badge",
- "force-text-direction",
- "force-ui-direction",
- "force-update-menu-type",
- "gesture-properties-dbus-service",
- "ignore-gpu-blacklist",
- "ignore-previews-blocklist",
- "in-product-help-demo-mode-choice",
- "list-all-display-modes",
- "load-media-router-component-extension",
- "media-router-cast-allow-all-ips",
- "memlog",
- "memlog-sampling-rate",
- "memlog-stack-mode",
- "overlay-strategies",
- "set-market-url-for-testing",
- "show-autofill-type-predictions",
- "show-bluetooth-debug-log-toggle",
- "show-taps",
- "show-touch-hud",
- "tint-gl-composited-content",
- "translate-android-manual-trigger",
- "ui-disable-partial-swap",
- "ui-slow-animations",
- "update-menu-item-custom-summary",
- "use-angle",
- "use_messages_staging_url",
- "use-sync-sandbox",
- "wallet-service-use-sandbox"
-]
diff --git a/chromium/chrome/browser/media/webrtc/DEPS b/chromium/chrome/browser/media/webrtc/DEPS
new file mode 100644
index 00000000000..e1fc475bafa
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+ "+media/webrtc",
+ "+services/audio/public/cpp",
+ "+third_party/libyuv",
+ "+third_party/webrtc",
+]
+
+specific_include_rules = {
+ # TODO(mash): Fix these. https://crbug.com/723880
+ "desktop_capture_access_handler\.cc": [
+ "+ash/shell.h",
+ ],
+ "desktop_media_list_ash\.cc": [
+ "+ash/shell.h",
+ "+ash/wm/desks/desks_util.h",
+ ],
+ # TODO(mash): Fix. https://crbug.com/855147
+ "media_capture_devices_dispatcher\.cc": [
+ "+ash/shell.h",
+ ],
+ # TODO(mash): Remove. https://crbug.com/723880
+ ".*_unittest\.cc": [
+ "+ash/test/ash_test_base.h",
+ ]
+}
diff --git a/chromium/chrome/browser/media/webrtc/OWNERS b/chromium/chrome/browser/media/webrtc/OWNERS
new file mode 100644
index 00000000000..1d63dd49b92
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/OWNERS
@@ -0,0 +1,24 @@
+sergeyu@chromium.org
+tommi@chromium.org
+guidou@chromium.org
+
+# For WebRTC desktop capturer related changes only
+braveyao@chromium.org
+
+# For WebRTC browser tests.
+per-file *webrtc*browsertest*=hbos@chromium.org
+per-file *webrtc*browsertest*=phoglund@chromium.org
+
+# For changes related to the tab media indicators.
+per-file media_stream_capture_indicator*=miu@chromium.org
+
+# For permissions related code.
+per-file media_stream_device*=raymes@chromium.org
+per-file media_permission*=raymes@chromium.org
+
+# For WebRTC event logging code.
+per-file webrtc_event_log_*=eladalon@chromium.org
+
+per-file *permission_context*=file://chrome/browser/permissions/PERMISSIONS_OWNERS
+
+# COMPONENT: Blink>WebRTC
diff --git a/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc
new file mode 100644
index 00000000000..44ee73b6ae7
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc
@@ -0,0 +1,168 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/audio_debug_recordings_handler.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/system_connector.h"
+#include "media/audio/audio_debug_recording_session.h"
+#include "services/audio/public/cpp/debug_recording_session_factory.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+using content::BrowserThread;
+
+// Keys used to attach handler to the RenderProcessHost
+const char AudioDebugRecordingsHandler::kAudioDebugRecordingsHandlerKey[] =
+ "kAudioDebugRecordingsHandlerKey";
+
+namespace {
+
+// Returns a path name to be used as prefix for audio debug recordings files.
+base::FilePath GetAudioDebugRecordingsPrefixPath(
+ const base::FilePath& directory,
+ uint64_t audio_debug_recordings_id) {
+ static const char kAudioDebugRecordingsFilePrefix[] = "AudioDebugRecordings.";
+ return directory.AppendASCII(kAudioDebugRecordingsFilePrefix +
+ base::NumberToString(audio_debug_recordings_id));
+}
+
+base::FilePath GetLogDirectoryAndEnsureExists(
+ content::BrowserContext* browser_context) {
+ base::FilePath log_dir_path =
+ webrtc_logging::TextLogList::GetWebRtcLogDirectoryForBrowserContextPath(
+ browser_context->GetPath());
+ base::File::Error error;
+ if (!base::CreateDirectoryAndGetError(log_dir_path, &error)) {
+ DLOG(ERROR) << "Could not create WebRTC log directory, error: " << error;
+ return base::FilePath();
+ }
+ return log_dir_path;
+}
+
+} // namespace
+
+AudioDebugRecordingsHandler::AudioDebugRecordingsHandler(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), current_audio_debug_recordings_id_(0) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context_);
+}
+
+AudioDebugRecordingsHandler::~AudioDebugRecordingsHandler() {}
+
+void AudioDebugRecordingsHandler::StartAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ base::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(&GetLogDirectoryAndEnsureExists, browser_context_),
+ base::BindOnce(&AudioDebugRecordingsHandler::DoStartAudioDebugRecordings,
+ this, host, delay, callback, error_callback));
+}
+
+void AudioDebugRecordingsHandler::StopAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const bool is_manual_stop = true;
+ base::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(&GetLogDirectoryAndEnsureExists, browser_context_),
+ base::BindOnce(&AudioDebugRecordingsHandler::DoStopAudioDebugRecordings,
+ this, host, is_manual_stop,
+ current_audio_debug_recordings_id_, callback,
+ error_callback));
+}
+
+void AudioDebugRecordingsHandler::DoStartAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (audio_debug_recording_session_) {
+ error_callback.Run("Audio debug recordings already in progress");
+ return;
+ }
+
+ base::FilePath prefix_path = GetAudioDebugRecordingsPrefixPath(
+ log_directory, ++current_audio_debug_recordings_id_);
+ host->EnableAudioDebugRecordings(prefix_path);
+
+ audio_debug_recording_session_ = audio::CreateAudioDebugRecordingSession(
+ prefix_path, content::GetSystemConnector()->Clone());
+
+ if (delay.is_zero()) {
+ const bool is_stopped = false, is_manual_stop = false;
+ callback.Run(prefix_path.AsUTF8Unsafe(), is_stopped, is_manual_stop);
+ return;
+ }
+
+ const bool is_manual_stop = false;
+ base::PostDelayedTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&AudioDebugRecordingsHandler::DoStopAudioDebugRecordings,
+ this, host, is_manual_stop,
+ current_audio_debug_recordings_id_, callback,
+ error_callback, log_directory),
+ delay);
+}
+
+void AudioDebugRecordingsHandler::DoStopAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ bool is_manual_stop,
+ uint64_t audio_debug_recordings_id,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_LE(audio_debug_recordings_id, current_audio_debug_recordings_id_);
+
+ base::FilePath prefix_path = GetAudioDebugRecordingsPrefixPath(
+ log_directory, audio_debug_recordings_id);
+ // Prevent an old posted StopAudioDebugRecordings() call to stop a newer dump.
+ // This could happen in a sequence like:
+ // Start(10); // Start dump 1. Post Stop() to run after 10 seconds.
+ // Stop(); // Manually stop dump 1 before 10 seconds;
+ // Start(20); // Start dump 2. Posted Stop() for 1 should not stop dump 2.
+ if (audio_debug_recordings_id < current_audio_debug_recordings_id_) {
+ const bool is_stopped = false;
+ callback.Run(prefix_path.AsUTF8Unsafe(), is_stopped, is_manual_stop);
+ return;
+ }
+
+ if (!audio_debug_recording_session_) {
+ error_callback.Run("No audio debug recording in progress");
+ return;
+ }
+
+ audio_debug_recording_session_.reset();
+
+ host->DisableAudioDebugRecordings();
+
+ const bool is_stopped = true;
+ callback.Run(prefix_path.AsUTF8Unsafe(), is_stopped, is_manual_stop);
+}
diff --git a/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h
new file mode 100644
index 00000000000..4725e565594
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h
@@ -0,0 +1,96 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_AUDIO_DEBUG_RECORDINGS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_AUDIO_DEBUG_RECORDINGS_HANDLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}
+
+namespace media {
+class AudioDebugRecordingSession;
+}
+
+// AudioDebugRecordingsHandler provides an interface to start and stop
+// AudioDebugRecordings, including WebRTC AEC dumps. Lives on the UI thread.
+class AudioDebugRecordingsHandler
+ : public base::RefCountedThreadSafe<AudioDebugRecordingsHandler> {
+ public:
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+ typedef base::Callback<void(const std::string&)> RecordingErrorCallback;
+ typedef base::Callback<void(const std::string&, bool, bool)>
+ RecordingDoneCallback;
+
+ // Key used to attach the handler to the RenderProcessHost
+ static const char kAudioDebugRecordingsHandlerKey[];
+
+ explicit AudioDebugRecordingsHandler(
+ content::BrowserContext* browser_context);
+
+ // Starts an audio debug recording. The recording lasts the given |delay|,
+ // unless |delay| is zero, in which case recording will continue until
+ // StopAudioDebugRecordings() is explicitly invoked.
+ // |callback| is invoked once recording stops. If |delay| is zero
+ // |callback| is invoked once recording starts.
+ // If a recording was already in progress, |error_callback| is invoked instead
+ // of |callback|.
+ void StartAudioDebugRecordings(content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback);
+
+ // Stops an audio debug recording. |callback| is invoked once recording
+ // stops. If no recording was in progress, |error_callback| is invoked instead
+ // of |callback|.
+ void StopAudioDebugRecordings(content::RenderProcessHost* host,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<AudioDebugRecordingsHandler>;
+
+ virtual ~AudioDebugRecordingsHandler();
+
+ // Helper for starting audio debug recordings.
+ void DoStartAudioDebugRecordings(content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory);
+
+ // Helper for stopping audio debug recordings.
+ void DoStopAudioDebugRecordings(content::RenderProcessHost* host,
+ bool is_manual_stop,
+ uint64_t audio_debug_recordings_id,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory);
+
+ // The browser context associated with our renderer process.
+ content::BrowserContext* const browser_context_;
+
+ // This counter allows saving each debug recording in separate files.
+ uint64_t current_audio_debug_recordings_id_;
+
+ // Used for controlling debug recordings.
+ std::unique_ptr<media::AudioDebugRecordingSession>
+ audio_debug_recording_session_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDebugRecordingsHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_AUDIO_DEBUG_RECORDINGS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
new file mode 100644
index 00000000000..7e52d84fa42
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -0,0 +1,598 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/screen_capture_notification_ui.h"
+#include "chrome/browser/ui/simple_message_box.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/desktop_streams_registry.h"
+#include "content/public/browser/media_stream_request.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/switches.h"
+#include "net/base/url_util.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/origin.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/shell.h"
+#include "ui/base/ui_base_features.h"
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#endif // defined(OS_MACOSX)
+
+using content::BrowserThread;
+
+namespace {
+
+// Helper to get title of the calling application shown in the screen capture
+// notification.
+base::string16 GetApplicationTitle(content::WebContents* web_contents,
+ const extensions::Extension* extension) {
+ // Use extension name as title for extensions and host/origin for drive-by
+ // web.
+ std::string title;
+ if (extension) {
+ title = extension->name();
+ return base::UTF8ToUTF16(title);
+ }
+ GURL url = web_contents->GetURL();
+ title = content::IsOriginSecure(url) ? net::GetHostAndOptionalPort(url)
+ : url.GetOrigin().spec();
+ return base::UTF8ToUTF16(title);
+}
+
+// Returns whether an on-screen notification should appear after desktop capture
+// is approved for |extension|. Component extensions do not display a
+// notification.
+bool ShouldDisplayNotification(const extensions::Extension* extension) {
+ return !(extension &&
+ (extension->location() == extensions::Manifest::COMPONENT ||
+ extension->location() == extensions::Manifest::EXTERNAL_COMPONENT));
+}
+
+#if !defined(OS_ANDROID)
+// Find browser or app window from a given |web_contents|.
+gfx::NativeWindow FindParentWindowForWebContents(
+ content::WebContents* web_contents) {
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ if (browser && browser->window())
+ return browser->window()->GetNativeWindow();
+
+ const extensions::AppWindowRegistry::AppWindowList& window_list =
+ extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext())
+ ->app_windows();
+ for (auto iter = window_list.begin(); iter != window_list.end(); ++iter) {
+ if ((*iter)->web_contents() == web_contents)
+ return (*iter)->GetNativeWindow();
+ }
+
+ return NULL;
+}
+#endif
+
+} // namespace
+
+// Holds pending request information so that we display one picker UI at a time
+// for each content::WebContents.
+struct DesktopCaptureAccessHandler::PendingAccessRequest {
+ PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension)
+ : picker(std::move(picker)),
+ request(request),
+ callback(std::move(callback)),
+ extension(extension) {}
+ ~PendingAccessRequest() = default;
+
+ std::unique_ptr<DesktopMediaPicker> picker;
+ content::MediaStreamRequest request;
+ content::MediaResponseCallback callback;
+ const extensions::Extension* extension;
+};
+
+DesktopCaptureAccessHandler::DesktopCaptureAccessHandler()
+ : picker_factory_(new DesktopMediaPickerFactoryImpl()),
+ display_notification_(true) {
+ AddNotificationObserver();
+}
+
+DesktopCaptureAccessHandler::DesktopCaptureAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory)
+ : picker_factory_(std::move(picker_factory)), display_notification_(false) {
+ AddNotificationObserver();
+}
+
+DesktopCaptureAccessHandler::~DesktopCaptureAccessHandler() = default;
+
+void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+
+ DCHECK_EQ(request.video_type,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
+
+ UpdateExtensionTrusted(request, extension);
+
+ bool loopback_audio_supported = false;
+#if defined(USE_CRAS) || defined(OS_WIN)
+ // Currently loopback audio capture is supported only on Windows and ChromeOS.
+ loopback_audio_supported = true;
+#endif
+
+ bool screen_capture_enabled =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableUserMediaScreenCapturing) ||
+ MediaCaptureDevicesDispatcher::IsOriginForCasting(
+ request.security_origin) ||
+ IsExtensionWhitelistedForScreenCapture(extension) ||
+ IsBuiltInExtension(request.security_origin);
+
+ const bool origin_is_secure =
+ content::IsOriginSecure(request.security_origin) ||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAllowHttpScreenCapture);
+
+ // If basic conditions (screen capturing is enabled and origin is secure)
+ // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
+ // it after checking permission.
+ // TODO(grunell): It would be good to change this result for something else,
+ // probably a new one.
+ blink::mojom::MediaStreamRequestResult result =
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE;
+
+ // Approve request only when the following conditions are met:
+ // 1. Screen capturing is enabled via command line switch or white-listed for
+ // the given origin.
+ // 2. Request comes from a page with a secure origin or from an extension.
+ if (screen_capture_enabled && origin_is_secure) {
+ // Get title of the calling application prior to showing the message box.
+ // chrome::ShowQuestionMessageBox() starts a nested run loop which may
+ // allow |web_contents| to be destroyed on the UI thread before the messag
+ // box is closed. See http://crbug.com/326690.
+ base::string16 application_title =
+ GetApplicationTitle(web_contents, extension);
+#if !defined(OS_ANDROID)
+ gfx::NativeWindow parent_window =
+ FindParentWindowForWebContents(web_contents);
+#else
+ gfx::NativeWindow parent_window = NULL;
+#endif
+ web_contents = NULL;
+
+ // Some extensions do not require user approval, because they provide their
+ // own user approval UI.
+ bool is_approved = IsDefaultApproved(extension);
+ if (!is_approved) {
+ base::string16 application_name =
+ base::UTF8ToUTF16(request.security_origin.spec());
+ if (extension)
+ application_name = base::UTF8ToUTF16(extension->name());
+ base::string16 confirmation_text = l10n_util::GetStringFUTF16(
+ request.audio_type == blink::mojom::MediaStreamType::NO_SERVICE
+ ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
+ : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
+ application_name);
+ chrome::MessageBoxResult result = chrome::ShowQuestionMessageBox(
+ parent_window,
+ l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
+ confirmation_text);
+ is_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
+ }
+
+ if (is_approved) {
+ content::DesktopMediaID screen_id;
+#if defined(OS_CHROMEOS)
+ screen_id = content::DesktopMediaID::RegisterNativeWindow(
+ content::DesktopMediaID::TYPE_SCREEN,
+ ash::Shell::Get()->GetPrimaryRootWindow());
+#else // defined(OS_CHROMEOS)
+ screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ webrtc::kFullDesktopScreenId);
+#endif // !defined(OS_CHROMEOS)
+
+ bool capture_audio =
+ (request.audio_type ==
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
+ loopback_audio_supported);
+
+ // Determine if the extension is required to display a notification.
+ const bool display_notification =
+ display_notification_ && ShouldDisplayNotification(extension);
+
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, screen_id,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
+ capture_audio, request.disable_local_echo, display_notification,
+ application_title, application_title);
+ DCHECK(!devices.empty());
+ }
+
+ // The only case when devices can be empty is if the user has denied
+ // permission.
+ result = devices.empty()
+ ? blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED
+ : blink::mojom::MediaStreamRequestResult::OK;
+ }
+
+ std::move(callback).Run(devices, result, std::move(ui));
+}
+
+bool DesktopCaptureAccessHandler::IsDefaultApproved(
+ const extensions::Extension* extension) {
+ return extension &&
+ (extension->location() == extensions::Manifest::COMPONENT ||
+ extension->location() == extensions::Manifest::EXTERNAL_COMPONENT ||
+ IsExtensionWhitelistedForScreenCapture(extension));
+}
+
+bool DesktopCaptureAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
+}
+
+bool DesktopCaptureAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return false;
+}
+
+void DesktopCaptureAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+
+ if (request.video_type !=
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
+ std::move(callback).Run(
+ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
+ std::move(ui));
+ return;
+ }
+
+ if (request.request_type == blink::MEDIA_DEVICE_UPDATE) {
+ ProcessChangeSourceRequest(web_contents, request, std::move(callback),
+ extension);
+ return;
+ }
+
+ // If the device id wasn't specified then this is a screen capture request
+ // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
+ if (request.requested_video_device_id.empty()) {
+#if defined(OS_MACOSX)
+ if (system_media_permissions::CheckSystemScreenCapturePermission() !=
+ system_media_permissions::SystemPermission::kAllowed) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
+ nullptr);
+ return;
+ }
+#endif
+ ProcessScreenCaptureAccessRequest(web_contents, request,
+ std::move(callback), extension);
+ return;
+ }
+
+ // Resolve DesktopMediaID for the specified device id.
+ content::DesktopMediaID media_id;
+ // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
+ // RenderFrame IDs once the desktop capture extension API implementation is
+ // fixed. http://crbug.com/304341
+ content::WebContents* const web_contents_for_stream =
+ content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(request.render_process_id,
+ request.render_frame_id));
+ content::RenderFrameHost* const main_frame =
+ web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL;
+ if (main_frame) {
+ media_id =
+ content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
+ request.requested_video_device_id,
+ main_frame->GetProcess()->GetID(), main_frame->GetRoutingID(),
+ url::Origin::Create(request.security_origin), nullptr,
+ content::kRegistryStreamTypeDesktop);
+ }
+
+ // Received invalid device id.
+ if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
+ std::move(callback).Run(
+ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
+ std::move(ui));
+ return;
+ }
+#if defined(OS_MACOSX)
+ if (media_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS &&
+ system_media_permissions::CheckSystemScreenCapturePermission() !=
+ system_media_permissions::SystemPermission::kAllowed) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
+ nullptr);
+ return;
+ }
+#endif
+
+ bool loopback_audio_supported = false;
+#if defined(USE_CRAS) || defined(OS_WIN)
+ // Currently loopback audio capture is supported only on Windows and ChromeOS.
+ loopback_audio_supported = true;
+#endif
+
+ // This value essentially from the checkbox on picker window, so it
+ // corresponds to user permission.
+ const bool audio_permitted = media_id.audio_share;
+
+ // This value essentially from whether getUserMedia requests audio stream.
+ const bool audio_requested =
+ request.audio_type ==
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
+
+ // This value shows for a given capture type, whether the system or our code
+ // can support audio sharing. Currently audio is only supported for screen and
+ // tab/webcontents capture streams.
+ const bool audio_supported =
+ (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
+ loopback_audio_supported) ||
+ media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS;
+
+ const bool check_audio_permission =
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kDisableDesktopCaptureAudio);
+ const bool capture_audio =
+ (check_audio_permission ? audio_permitted : true) && audio_requested &&
+ audio_supported;
+
+ // Determine if the extension is required to display a notification.
+ const bool display_notification =
+ display_notification_ && ShouldDisplayNotification(extension);
+
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, media_id,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, capture_audio,
+ request.disable_local_echo, display_notification,
+ GetApplicationTitle(web_contents, extension),
+ GetApplicationTitle(web_contents, extension));
+ UpdateExtensionTrusted(request, extension);
+ std::move(callback).Run(devices, blink::mojom::MediaStreamRequestResult::OK,
+ std::move(ui));
+}
+
+void DesktopCaptureAccessHandler::ProcessChangeSourceRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::unique_ptr<DesktopMediaPicker> picker;
+
+ if (!base::FeatureList::IsEnabled(
+ features::kDesktopCaptureTabSharingInfobar) ||
+ request.requested_video_device_id.empty()) {
+ picker = picker_factory_->CreatePicker();
+ if (!picker) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+ }
+
+ RequestsQueue& queue = pending_requests_[web_contents];
+ queue.push_back(std::make_unique<PendingAccessRequest>(
+ std::move(picker), request, std::move(callback), extension));
+ // If this is the only request then pop picker UI.
+ if (queue.size() == 1)
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DesktopCaptureAccessHandler::UpdateMediaRequestState(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (state != content::MEDIA_REQUEST_STATE_DONE &&
+ state != content::MEDIA_REQUEST_STATE_CLOSING) {
+ return;
+ }
+
+ if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ DeletePendingAccessRequest(render_process_id, render_frame_id,
+ page_request_id);
+ }
+ CaptureAccessHandlerBase::UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, stream_type, state);
+
+ // This method only gets called with the above checked states when all
+ // requests are to be canceled. Therefore, we don't need to process the
+ // next queued request.
+}
+
+void DesktopCaptureAccessHandler::ProcessQueuedAccessRequest(
+ const RequestsQueue& queue,
+ content::WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ const PendingAccessRequest& pending_request = *queue.front();
+
+ if (!pending_request.picker) {
+ DCHECK(!pending_request.request.requested_video_device_id.empty());
+ content::WebContentsMediaCaptureId web_contents_id;
+ if (content::WebContentsMediaCaptureId::Parse(
+ pending_request.request.requested_video_device_id,
+ &web_contents_id)) {
+ content::DesktopMediaID media_id(
+ content::DesktopMediaID::TYPE_WEB_CONTENTS,
+ content::DesktopMediaID::kNullId, web_contents_id);
+ media_id.audio_share = pending_request.request.audio_type !=
+ blink::mojom::MediaStreamType::NO_SERVICE;
+ OnPickerDialogResults(web_contents, media_id);
+ return;
+ }
+ }
+
+ std::vector<content::DesktopMediaID::Type> media_types = {
+ content::DesktopMediaID::TYPE_WEB_CONTENTS};
+ auto source_lists = picker_factory_->CreateMediaList(media_types);
+
+ DesktopMediaPicker::DoneCallback done_callback =
+ base::BindOnce(&DesktopCaptureAccessHandler::OnPickerDialogResults,
+ base::Unretained(this), web_contents);
+ DesktopMediaPicker::Params picker_params;
+ picker_params.web_contents = web_contents;
+ gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
+ picker_params.context = parent_window;
+ picker_params.parent = parent_window;
+ picker_params.app_name =
+ GetApplicationTitle(web_contents, pending_request.extension);
+ picker_params.target_name = picker_params.app_name;
+ picker_params.request_audio = (pending_request.request.audio_type ==
+ blink::mojom::MediaStreamType::NO_SERVICE)
+ ? false
+ : true;
+ pending_request.picker->Show(picker_params, std::move(source_lists),
+ std::move(done_callback));
+
+ // Focus on the tab with the picker for easy access.
+ if (auto* delegate = web_contents->GetDelegate())
+ delegate->ActivateContents(web_contents);
+}
+
+void DesktopCaptureAccessHandler::OnPickerDialogResults(
+ content::WebContents* web_contents,
+ content::DesktopMediaID media_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(web_contents);
+
+ auto it = pending_requests_.find(web_contents);
+ if (it == pending_requests_.end())
+ return;
+ RequestsQueue& queue = it->second;
+ if (queue.empty()) {
+ // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
+ // need to do anything.
+ return;
+ }
+
+ PendingAccessRequest& pending_request = *queue.front();
+ blink::MediaStreamDevices devices;
+ blink::mojom::MediaStreamRequestResult request_result =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ const extensions::Extension* extension = pending_request.extension;
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (media_id.is_null()) {
+ request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ } else {
+ request_result = blink::mojom::MediaStreamRequestResult::OK;
+ // Determine if the extension is required to display a notification.
+ const bool display_notification =
+ display_notification_ && ShouldDisplayNotification(extension);
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, media_id, pending_request.request.video_type,
+ pending_request.request.audio_type, media_id.audio_share,
+ pending_request.request.disable_local_echo, display_notification,
+ GetApplicationTitle(web_contents, extension),
+ GetApplicationTitle(web_contents, extension));
+ }
+
+ std::move(pending_request.callback)
+ .Run(devices, request_result, std::move(ui));
+ queue.pop_front();
+
+ if (!queue.empty())
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DesktopCaptureAccessHandler::AddNotificationObserver() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ notifications_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::NotificationService::AllSources());
+}
+
+void DesktopCaptureAccessHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+ pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
+
+void DesktopCaptureAccessHandler::DeletePendingAccessRequest(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id) {
+ for (auto& queue_it : pending_requests_) {
+ RequestsQueue& queue = queue_it.second;
+ for (auto it = queue.begin(); it != queue.end(); ++it) {
+ const PendingAccessRequest& pending_request = **it;
+ if (pending_request.request.render_process_id == render_process_id &&
+ pending_request.request.render_frame_id == render_frame_id &&
+ pending_request.request.page_request_id == page_request_id) {
+ queue.erase(it);
+ return;
+ }
+ }
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h
new file mode 100644
index 00000000000..e97d6318179
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h
@@ -0,0 +1,105 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
+
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "chrome/browser/media/capture_access_handler_base.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace extensions {
+class Extension;
+}
+
+// MediaAccessHandler for DesktopCapture API requests that originate from
+// getUserMedia() calls. Note that getDisplayMedia() calls are handled in
+// DisplayMediaAccessHandler.
+class DesktopCaptureAccessHandler : public CaptureAccessHandlerBase,
+ public content::NotificationObserver {
+ public:
+ DesktopCaptureAccessHandler();
+ explicit DesktopCaptureAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory);
+ ~DesktopCaptureAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+ void UpdateMediaRequestState(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+
+ private:
+ friend class DesktopCaptureAccessHandlerTest;
+
+ struct PendingAccessRequest;
+ using RequestsQueue =
+ base::circular_deque<std::unique_ptr<PendingAccessRequest>>;
+ using RequestsQueues = base::flat_map<content::WebContents*, RequestsQueue>;
+
+ void ProcessScreenCaptureAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension);
+
+ // Returns whether desktop capture is always approved for |extension|.
+ // Currently component extensions and some whitelisted extensions are default
+ // approved.
+ static bool IsDefaultApproved(const extensions::Extension* extension);
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+ void AddNotificationObserver();
+
+ // Methods for handling source change request, e.g. bringing up the picker to
+ // select a new source within the current desktop sharing session.
+ void ProcessChangeSourceRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension);
+ void ProcessQueuedAccessRequest(const RequestsQueue& queue,
+ content::WebContents* web_contents);
+ void OnPickerDialogResults(content::WebContents* web_contents,
+ content::DesktopMediaID source);
+ void DeletePendingAccessRequest(int render_process_id,
+ int render_frame_id,
+ int page_request_id);
+
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory_;
+ bool display_notification_;
+ RequestsQueues pending_requests_;
+ content::NotificationRegistrar notifications_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopCaptureAccessHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
new file mode 100644
index 00000000000..70ed3a0c4c8
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
@@ -0,0 +1,254 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+class DesktopCaptureAccessHandlerTest : public ChromeRenderViewHostTestHarness {
+ public:
+ DesktopCaptureAccessHandlerTest() {}
+ ~DesktopCaptureAccessHandlerTest() override {}
+
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+ auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
+ picker_factory_ = picker_factory.get();
+ access_handler_ = std::make_unique<DesktopCaptureAccessHandler>(
+ std::move(picker_factory));
+ }
+
+ void ProcessRequest(
+ const content::DesktopMediaID& fake_desktop_media_id_response,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ blink::MediaStreamRequestType request_type,
+ bool request_audio) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, request_audio /* expect_audio */,
+ fake_desktop_media_id_response /* selected_source */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ blink::mojom::MediaStreamType audio_type =
+ request_audio ? blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE
+ : blink::mojom::MediaStreamType::NO_SERVICE;
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, request_type, std::string(),
+ std::string(), audio_type,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, false);
+
+ base::RunLoop wait_loop;
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop, request_result, devices_result);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ wait_loop.Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+
+ access_handler_.reset();
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ }
+
+ void NotifyWebContentsDestroyed() {
+ access_handler_->Observe(
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<content::WebContents>(web_contents()),
+ content::NotificationDetails());
+ }
+
+ const DesktopCaptureAccessHandler::RequestsQueues& GetRequestQueues() {
+ return access_handler_->pending_requests_;
+ }
+
+ protected:
+ FakeDesktopMediaPickerFactory* picker_factory_;
+ std::unique_ptr<DesktopCaptureAccessHandler> access_handler_;
+};
+
+TEST_F(DesktopCaptureAccessHandlerTest,
+ ChangeSourceWithoutAudioRequestPermissionGiven) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId),
+ &result, &devices, blink::MEDIA_DEVICE_UPDATE,
+ false /*request_audio*/);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest,
+ ChangeSourceWithAudioRequestPermissionGiven) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId,
+ true /* audio_share */),
+ &result, &devices, blink::MEDIA_DEVICE_UPDATE,
+ true /* request_audio */);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(2u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
+ devices[1].type);
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourcePermissionDenied) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(), &result, &devices,
+ blink::MEDIA_DEVICE_UPDATE, false /*request audio*/);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
+ EXPECT_EQ(0u, devices.size());
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest,
+ ChangeSourceUpdateMediaRequestStateWithClosing) {
+ const int render_process_id = 0;
+ const int render_frame_id = 0;
+ const int page_request_id = 0;
+ const blink::mojom::MediaStreamType stream_type =
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ render_process_id, render_frame_id, page_request_id,
+ GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE, std::string(),
+ std::string(), blink::mojom::MediaStreamType::NO_SERVICE, stream_type,
+ false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ access_handler_->UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, stream_type,
+ content::MEDIA_REQUEST_STATE_CLOSING);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(0u, queue_it->second.size());
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ access_handler_.reset();
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceWebContentsDestroyed) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ NotifyWebContentsDestroyed();
+ EXPECT_EQ(0u, GetRequestQueues().size());
+ access_handler_.reset();
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceMultipleRequests) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId) /* selected_source */},
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::DesktopMediaID::kNullId) /* selected_source */}};
+ const size_t kTestFlagCount = 2;
+ picker_factory_->SetTestFlags(test_flags, kTestFlagCount);
+
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ base::RunLoop wait_loop[kTestFlagCount];
+ for (size_t i = 0; i < kTestFlagCount; ++i) {
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop[i], &result, &devices);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ }
+ wait_loop[0].Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+
+ blink::MediaStreamDevice first_device = devices[0];
+ EXPECT_TRUE(test_flags[1].picker_created);
+ EXPECT_FALSE(test_flags[1].picker_deleted);
+ wait_loop[1].Run();
+ EXPECT_TRUE(test_flags[1].picker_deleted);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_FALSE(devices[0].IsSameDevice(first_device));
+
+ access_handler_.reset();
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
new file mode 100644
index 00000000000..76fede3233e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
@@ -0,0 +1,198 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+
+#include <string>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/ui/screen_capture_notification_ui.h"
+#include "chrome/browser/ui/tab_sharing/tab_sharing_ui.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/audio/audio_device_description.h"
+#include "media/mojo/mojom/display_media_information.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+media::mojom::DisplayMediaInformationPtr
+DesktopMediaIDToDisplayMediaInformation(
+ const content::DesktopMediaID& media_id) {
+ media::mojom::DisplayCaptureSurfaceType display_surface =
+ media::mojom::DisplayCaptureSurfaceType::MONITOR;
+ bool logical_surface = true;
+ media::mojom::CursorCaptureType cursor =
+ media::mojom::CursorCaptureType::NEVER;
+#if defined(USE_AURA)
+ const bool uses_aura =
+ media_id.window_id != content::DesktopMediaID::kNullId ? true : false;
+#else
+ const bool uses_aura = false;
+#endif // defined(USE_AURA)
+ switch (media_id.type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR;
+ cursor = uses_aura ? media::mojom::CursorCaptureType::MOTION
+ : media::mojom::CursorCaptureType::ALWAYS;
+ break;
+ case content::DesktopMediaID::TYPE_WINDOW:
+ display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW;
+ cursor = uses_aura ? media::mojom::CursorCaptureType::MOTION
+ : media::mojom::CursorCaptureType::ALWAYS;
+ break;
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER;
+ cursor = media::mojom::CursorCaptureType::MOTION;
+ break;
+ case content::DesktopMediaID::TYPE_NONE:
+ break;
+ }
+
+ return media::mojom::DisplayMediaInformation::New(display_surface,
+ logical_surface, cursor);
+}
+
+base::string16 GetStopSharingUIString(
+ const base::string16& application_title,
+ const base::string16& registered_extension_name,
+ bool capture_audio,
+ content::DesktopMediaID::Type capture_type) {
+ if (!capture_audio) {
+ if (application_title == registered_extension_name) {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title);
+ case content::DesktopMediaID::TYPE_WINDOW:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ NOTREACHED();
+ }
+ } else {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_WINDOW:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ NOTREACHED();
+ }
+ }
+ } else { // The case with audio
+ if (application_title == registered_extension_name) {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
+ application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
+ application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ case content::DesktopMediaID::TYPE_WINDOW:
+ NOTREACHED();
+ }
+ } else {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ case content::DesktopMediaID::TYPE_WINDOW:
+ NOTREACHED();
+ }
+ }
+ }
+ return base::string16();
+}
+
+} // namespace
+
+std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
+ content::WebContents* web_contents,
+ blink::MediaStreamDevices* devices,
+ const content::DesktopMediaID& media_id,
+ blink::mojom::MediaStreamType devices_video_type,
+ blink::mojom::MediaStreamType devices_audio_type,
+ bool capture_audio,
+ bool disable_local_echo,
+ bool display_notification,
+ const base::string16& application_title,
+ const base::string16& registered_extension_name) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ DVLOG(2) << __func__ << ": media_id " << media_id.ToString()
+ << ", capture_audio " << capture_audio << ", disable_local_echo "
+ << disable_local_echo << ", display_notification "
+ << display_notification << ", application_title "
+ << application_title << ", extension_name "
+ << registered_extension_name;
+
+ // Add selected desktop source to the list.
+ auto device = blink::MediaStreamDevice(
+ devices_video_type, media_id.ToString(), media_id.ToString());
+ device.display_media_info = DesktopMediaIDToDisplayMediaInformation(media_id);
+ devices->push_back(device);
+ if (capture_audio) {
+ if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) {
+ content::WebContentsMediaCaptureId web_id = media_id.web_contents_id;
+ web_id.disable_local_echo = disable_local_echo;
+ devices->push_back(blink::MediaStreamDevice(
+ devices_audio_type, web_id.ToString(), "Tab audio"));
+ } else if (disable_local_echo) {
+ // Use the special loopback device ID for system audio capture.
+ devices->push_back(blink::MediaStreamDevice(
+ devices_audio_type,
+ media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
+ "System Audio"));
+ } else {
+ // Use the special loopback device ID for system audio capture.
+ devices->push_back(blink::MediaStreamDevice(
+ devices_audio_type,
+ media::AudioDeviceDescription::kLoopbackInputDeviceId,
+ "System Audio"));
+ }
+ }
+
+ // If required, register to display the notification for stream capture.
+ std::unique_ptr<MediaStreamUI> notification_ui;
+ if (display_notification) {
+ if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS &&
+ base::FeatureList::IsEnabled(
+ features::kDesktopCaptureTabSharingInfobar)) {
+ notification_ui = TabSharingUI::Create(media_id, application_title);
+ } else {
+ notification_ui = ScreenCaptureNotificationUI::Create(
+ GetStopSharingUIString(application_title, registered_extension_name,
+ capture_audio, media_id.type));
+ }
+ }
+
+ return MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents, *devices, std::move(notification_ui));
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h
new file mode 100644
index 00000000000..2bdfe18cd21
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
+
+#include <memory>
+
+#include "base/strings/string_util.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/media_stream_request.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+
+// Helper to get list of media stream devices for desktop capture in |devices|.
+// Registers to display notification if |display_notification| is true.
+// Returns an instance of MediaStreamUI to be passed to content layer.
+std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
+ content::WebContents* web_contents,
+ blink::MediaStreamDevices* devices,
+ const content::DesktopMediaID& media_id,
+ blink::mojom::MediaStreamType devices_video_type,
+ blink::mojom::MediaStreamType devices_audio_type,
+ bool capture_audio,
+ bool disable_local_echo,
+ bool display_notification,
+ const base::string16& application_title,
+ const base::string16& registered_extension_name);
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc
new file mode 100644
index 00000000000..8f69f01efaa
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc
@@ -0,0 +1,166 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+
+#include <utility>
+
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/shell.h"
+#include "ash/wm/desks/desks_util.h"
+#include "base/bind.h"
+#include "chrome/grit/generated_resources.h"
+#include "media/base/video_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image.h"
+#include "ui/snapshot/snapshot.h"
+
+using content::DesktopMediaID;
+
+namespace {
+
+// Update the list twice per second.
+const int kDefaultDesktopMediaListUpdatePeriod = 500;
+
+} // namespace
+
+DesktopMediaListAsh::DesktopMediaListAsh(content::DesktopMediaID::Type type)
+ : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
+ kDefaultDesktopMediaListUpdatePeriod)) {
+ DCHECK(type == content::DesktopMediaID::TYPE_SCREEN ||
+ type == content::DesktopMediaID::TYPE_WINDOW);
+ type_ = type;
+}
+
+DesktopMediaListAsh::~DesktopMediaListAsh() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void DesktopMediaListAsh::Refresh(bool update_thumnails) {
+ DCHECK(can_refresh());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(pending_window_capture_requests_, 0);
+
+ std::vector<SourceDescription> new_sources;
+ EnumerateSources(&new_sources, update_thumnails);
+ UpdateSourcesList(new_sources);
+ OnRefreshMaybeComplete();
+}
+
+void DesktopMediaListAsh::EnumerateWindowsForRoot(
+ std::vector<DesktopMediaListAsh::SourceDescription>* sources,
+ bool update_thumnails,
+ aura::Window* root_window,
+ int container_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ aura::Window* container = ash::Shell::GetContainer(root_window, container_id);
+ if (!container)
+ return;
+ // The |container| has all the top-level windows in reverse order, e.g. the
+ // most top-level window is at the end. So iterate children reversely to make
+ // sure |sources| is in the expected order.
+ for (aura::Window::Windows::const_reverse_iterator it =
+ container->children().rbegin();
+ it != container->children().rend(); ++it) {
+ if (!(*it)->IsVisible() || !(*it)->CanFocus())
+ continue;
+ content::DesktopMediaID id = content::DesktopMediaID::RegisterNativeWindow(
+ content::DesktopMediaID::TYPE_WINDOW, *it);
+ if (id.window_id == view_dialog_id_.window_id)
+ continue;
+ SourceDescription window_source(id, (*it)->GetTitle());
+ sources->push_back(window_source);
+
+ if (update_thumnails)
+ CaptureThumbnail(window_source.id, *it);
+ }
+}
+
+void DesktopMediaListAsh::EnumerateSources(
+ std::vector<DesktopMediaListAsh::SourceDescription>* sources,
+ bool update_thumnails) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
+
+ for (size_t i = 0; i < root_windows.size(); ++i) {
+ if (type_ == content::DesktopMediaID::TYPE_SCREEN) {
+ SourceDescription screen_source(
+ content::DesktopMediaID::RegisterNativeWindow(
+ content::DesktopMediaID::TYPE_SCREEN, root_windows[i]),
+ root_windows[i]->GetTitle());
+
+ if (root_windows[i] == ash::Shell::GetPrimaryRootWindow())
+ sources->insert(sources->begin(), screen_source);
+ else
+ sources->push_back(screen_source);
+
+ if (screen_source.name.empty()) {
+ if (root_windows.size() > 1) {
+ // 'Screen' in 'Screen 1, Screen 2, etc ' might be inflected in some
+ // languages depending on the number although rather unlikely. To be
+ // safe, use the plural format.
+ // TODO(jshin): Revert to GetStringFUTF16Int (with native digits)
+ // if none of UI languages inflects 'Screen' in this context.
+ screen_source.name = l10n_util::GetPluralStringFUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
+ static_cast<int>(i + 1));
+ } else {
+ screen_source.name = l10n_util::GetStringUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
+ }
+ }
+
+ if (update_thumnails)
+ CaptureThumbnail(screen_source.id, root_windows[i]);
+ } else {
+ // The list of desks containers depends on whether the Virtual Desks
+ // feature is enabled or not.
+ for (int desk_id : ash::desks_util::GetDesksContainersIds())
+ EnumerateWindowsForRoot(sources, update_thumnails, root_windows[i],
+ desk_id);
+
+ EnumerateWindowsForRoot(sources, update_thumnails, root_windows[i],
+ ash::kShellWindowId_AlwaysOnTopContainer);
+ EnumerateWindowsForRoot(sources, update_thumnails, root_windows[i],
+ ash::kShellWindowId_PipContainer);
+ }
+ }
+}
+
+void DesktopMediaListAsh::CaptureThumbnail(content::DesktopMediaID id,
+ aura::Window* window) {
+ gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
+ gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
+ gfx::Rect(thumbnail_size_), window_rect.size());
+
+ ++pending_window_capture_requests_;
+ ui::GrabWindowSnapshotAndScaleAsync(
+ window, window_rect, scaled_rect.size(),
+ base::Bind(&DesktopMediaListAsh::OnThumbnailCaptured,
+ weak_factory_.GetWeakPtr(), id));
+}
+
+void DesktopMediaListAsh::OnThumbnailCaptured(content::DesktopMediaID id,
+ gfx::Image image) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ UpdateSourceThumbnail(id, image.AsImageSkia());
+
+ --pending_window_capture_requests_;
+ DCHECK_GE(pending_window_capture_requests_, 0);
+
+ OnRefreshMaybeComplete();
+}
+
+void DesktopMediaListAsh::OnRefreshMaybeComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (pending_window_capture_requests_ == 0) {
+ // Once we've finished capturing all windows, notify the caller, which will
+ // post a task for the next list update if necessary.
+ OnRefreshComplete();
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h
new file mode 100644
index 00000000000..e8450edc102
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h
@@ -0,0 +1,54 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_ASH_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_ASH_H_
+
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+#include "content/public/browser/desktop_media_id.h"
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Image;
+}
+
+// Implementation of DesktopMediaList that shows native screens and
+// native windows.
+class DesktopMediaListAsh : public DesktopMediaListBase {
+ public:
+ explicit DesktopMediaListAsh(content::DesktopMediaID::Type type);
+ ~DesktopMediaListAsh() override;
+
+ private:
+ // Override from DesktopMediaListBase.
+ void Refresh(bool update_thumnails) override;
+ void EnumerateWindowsForRoot(
+ std::vector<DesktopMediaListAsh::SourceDescription>* windows,
+ bool update_thumnails,
+ aura::Window* root_window,
+ int container_id);
+ void EnumerateSources(
+ std::vector<DesktopMediaListAsh::SourceDescription>* windows,
+ bool update_thumnails);
+ void CaptureThumbnail(content::DesktopMediaID id, aura::Window* window);
+ void OnThumbnailCaptured(content::DesktopMediaID id, gfx::Image image);
+ void OnRefreshMaybeComplete();
+
+ int pending_window_capture_requests_ = 0;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ base::WeakPtrFactory<DesktopMediaListAsh> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaListAsh);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_ASH_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc
new file mode 100644
index 00000000000..dfc973b1989
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
+#include "chrome/test/base/chrome_ash_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
+
+int kThumbnailSize = 100;
+
+using testing::AtLeast;
+using testing::DoDefault;
+
+class MockDesktopMediaListObserver : public DesktopMediaListObserver {
+ public:
+ MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
+ MOCK_METHOD3(OnSourceMoved,
+ void(DesktopMediaList* list, int old_index, int new_index));
+ MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceThumbnailChanged,
+ void(DesktopMediaList* list, int index));
+};
+
+class DesktopMediaListAshTest : public ChromeAshTestBase {
+ public:
+ DesktopMediaListAshTest() {}
+ ~DesktopMediaListAshTest() override {}
+
+ void TearDown() override {
+ // Reset the unique_ptr so the list stops refreshing.
+ list_.reset();
+ ChromeAshTestBase::TearDown();
+ }
+
+ void CreateList(content::DesktopMediaID::Type type) {
+ list_.reset(new DesktopMediaListAsh(type));
+ list_->SetThumbnailSize(gfx::Size(kThumbnailSize, kThumbnailSize));
+
+ // Set update period to reduce the time it takes to run tests.
+ list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(1));
+ }
+
+ protected:
+ MockDesktopMediaListObserver observer_;
+ std::unique_ptr<DesktopMediaListAsh> list_;
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaListAshTest);
+};
+
+ACTION(QuitMessageLoop) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
+}
+
+TEST_F(DesktopMediaListAshTest, ScreenOnly) {
+ CreateList(content::DesktopMediaID::TYPE_SCREEN);
+
+ std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
+
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop())
+ .WillRepeatedly(DoDefault());
+
+ list_->StartUpdating(&observer_);
+ base::RunLoop().Run();
+}
+
+TEST_F(DesktopMediaListAshTest, WindowOnly) {
+ CreateList(content::DesktopMediaID::TYPE_WINDOW);
+
+ std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
+
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop())
+ .WillRepeatedly(DoDefault());
+ EXPECT_CALL(observer_, OnSourceRemoved(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ list_->StartUpdating(&observer_);
+ base::RunLoop().Run();
+ window.reset();
+ base::RunLoop().Run();
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc
new file mode 100644
index 00000000000..9d9b7435044
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc
@@ -0,0 +1,186 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/gfx/image/image.h"
+
+using content::BrowserThread;
+using content::DesktopMediaID;
+
+DesktopMediaListBase::DesktopMediaListBase(base::TimeDelta update_period)
+ : update_period_(update_period) {}
+
+DesktopMediaListBase::~DesktopMediaListBase() {}
+
+void DesktopMediaListBase::SetUpdatePeriod(base::TimeDelta period) {
+ DCHECK(!observer_);
+ update_period_ = period;
+}
+
+void DesktopMediaListBase::SetThumbnailSize(const gfx::Size& thumbnail_size) {
+ thumbnail_size_ = thumbnail_size;
+}
+
+void DesktopMediaListBase::SetViewDialogWindowId(DesktopMediaID dialog_id) {
+ view_dialog_id_ = dialog_id;
+}
+
+void DesktopMediaListBase::StartUpdating(DesktopMediaListObserver* observer) {
+ DCHECK(!observer_);
+ observer_ = observer;
+
+ // Process sources previously discovered by a call to Update().
+ if (observer_) {
+ for (size_t i = 0; i < sources_.size(); i++) {
+ observer_->OnSourceAdded(this, i);
+ }
+ }
+
+ DCHECK(!refresh_callback_);
+ refresh_callback_ = base::BindOnce(&DesktopMediaListBase::ScheduleNextRefresh,
+ weak_factory_.GetWeakPtr());
+ Refresh(true);
+}
+
+void DesktopMediaListBase::Update(UpdateCallback callback) {
+ DCHECK(sources_.empty());
+ DCHECK(!refresh_callback_);
+ refresh_callback_ = std::move(callback);
+ Refresh(false);
+}
+
+int DesktopMediaListBase::GetSourceCount() const {
+ return sources_.size();
+}
+
+const DesktopMediaList::Source& DesktopMediaListBase::GetSource(
+ int index) const {
+ DCHECK_GE(index, 0);
+ DCHECK_LT(index, static_cast<int>(sources_.size()));
+ return sources_[index];
+}
+
+DesktopMediaID::Type DesktopMediaListBase::GetMediaListType() const {
+ return type_;
+}
+
+DesktopMediaListBase::SourceDescription::SourceDescription(
+ DesktopMediaID id,
+ const base::string16& name)
+ : id(id), name(name) {}
+
+void DesktopMediaListBase::UpdateSourcesList(
+ const std::vector<SourceDescription>& new_sources) {
+ typedef std::set<DesktopMediaID> SourceSet;
+ SourceSet new_source_set;
+ for (size_t i = 0; i < new_sources.size(); ++i) {
+ new_source_set.insert(new_sources[i].id);
+ }
+ // Iterate through the old sources to find the removed sources.
+ for (size_t i = 0; i < sources_.size(); ++i) {
+ if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
+ sources_.erase(sources_.begin() + i);
+ if (observer_)
+ observer_->OnSourceRemoved(this, i);
+ --i;
+ }
+ }
+ // Iterate through the new sources to find the added sources.
+ if (new_sources.size() > sources_.size()) {
+ SourceSet old_source_set;
+ for (size_t i = 0; i < sources_.size(); ++i) {
+ old_source_set.insert(sources_[i].id);
+ }
+
+ for (size_t i = 0; i < new_sources.size(); ++i) {
+ if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
+ sources_.insert(sources_.begin() + i, Source());
+ sources_[i].id = new_sources[i].id;
+ sources_[i].name = new_sources[i].name;
+ if (observer_)
+ observer_->OnSourceAdded(this, i);
+ }
+ }
+ }
+ DCHECK_EQ(new_sources.size(), sources_.size());
+
+ // Find the moved/changed sources.
+ size_t pos = 0;
+ while (pos < sources_.size()) {
+ if (!(sources_[pos].id == new_sources[pos].id)) {
+ // Find the source that should be moved to |pos|, starting from |pos + 1|
+ // of |sources_|, because entries before |pos| should have been sorted.
+ size_t old_pos = pos + 1;
+ for (; old_pos < sources_.size(); ++old_pos) {
+ if (sources_[old_pos].id == new_sources[pos].id)
+ break;
+ }
+ DCHECK(sources_[old_pos].id == new_sources[pos].id);
+
+ // Move the source from |old_pos| to |pos|.
+ Source temp = sources_[old_pos];
+ sources_.erase(sources_.begin() + old_pos);
+ sources_.insert(sources_.begin() + pos, temp);
+
+ if (observer_)
+ observer_->OnSourceMoved(this, old_pos, pos);
+ }
+
+ if (sources_[pos].name != new_sources[pos].name) {
+ sources_[pos].name = new_sources[pos].name;
+ if (observer_)
+ observer_->OnSourceNameChanged(this, pos);
+ }
+ ++pos;
+ }
+}
+
+void DesktopMediaListBase::UpdateSourceThumbnail(DesktopMediaID id,
+ const gfx::ImageSkia& image) {
+ // Unlike other methods that check can_refresh(), this one won't cause
+ // OnRefreshComplete() to be called, but the caller is expected to schedule a
+ // call to OnRefreshComplete() after this method has been called as many times
+ // as needed, so the check is still valid.
+ DCHECK(can_refresh());
+
+ for (size_t i = 0; i < sources_.size(); ++i) {
+ if (sources_[i].id == id) {
+ sources_[i].thumbnail = image;
+ if (observer_)
+ observer_->OnSourceThumbnailChanged(this, i);
+ break;
+ }
+ }
+}
+
+// static
+uint32_t DesktopMediaListBase::GetImageHash(const gfx::Image& image) {
+ SkBitmap bitmap = image.AsBitmap();
+ uint32_t value = base::Hash(bitmap.getPixels(), bitmap.computeByteSize());
+ return value;
+}
+
+void DesktopMediaListBase::OnRefreshComplete() {
+ DCHECK(refresh_callback_);
+ std::move(refresh_callback_).Run();
+}
+
+void DesktopMediaListBase::ScheduleNextRefresh() {
+ DCHECK(!refresh_callback_);
+ refresh_callback_ = base::BindOnce(&DesktopMediaListBase::ScheduleNextRefresh,
+ weak_factory_.GetWeakPtr());
+ base::PostDelayedTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&DesktopMediaListBase::Refresh,
+ weak_factory_.GetWeakPtr(), true),
+ update_period_);
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_base.h b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.h
new file mode 100644
index 00000000000..3c09ec3111d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.h
@@ -0,0 +1,102 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_BASE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_BASE_H_
+
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
+#include "content/public/browser/desktop_media_id.h"
+
+namespace gfx {
+class Image;
+}
+
+// Thumbnail size is 100*100 pixels
+static const int kDefaultThumbnailSize = 100;
+
+// Base class for DesktopMediaList implementations. Implements logic shared
+// between implementations. Specifically it's responsible for keeping current
+// list of sources and calling the observer when the list changes.
+//
+// TODO(crbug.com/987001): Consider renaming this class.
+class DesktopMediaListBase : public DesktopMediaList {
+ public:
+ explicit DesktopMediaListBase(base::TimeDelta update_period);
+ ~DesktopMediaListBase() override;
+
+ // DesktopMediaList interface.
+ void SetUpdatePeriod(base::TimeDelta period) override;
+ void SetThumbnailSize(const gfx::Size& thumbnail_size) override;
+ void SetViewDialogWindowId(content::DesktopMediaID dialog_id) override;
+ void StartUpdating(DesktopMediaListObserver* observer) override;
+ void Update(UpdateCallback callback) override;
+ int GetSourceCount() const override;
+ const Source& GetSource(int index) const override;
+ content::DesktopMediaID::Type GetMediaListType() const override;
+
+ static uint32_t GetImageHash(const gfx::Image& image);
+
+ protected:
+ using RefreshCallback = UpdateCallback;
+
+ struct SourceDescription {
+ SourceDescription(content::DesktopMediaID id, const base::string16& name);
+
+ content::DesktopMediaID id;
+ base::string16 name;
+ };
+
+ // Before this method is called, |refresh_callback_| must be non-null, and
+ // after it completes (usually asychnonrously), |refresh_callback_| must be
+ // null. Since |refresh_callback_| is private, subclasses can check this
+ // condition by calling can_refresh().
+ virtual void Refresh(bool update_thumnails) = 0;
+
+ // Update source media list to observer.
+ void UpdateSourcesList(const std::vector<SourceDescription>& new_sources);
+
+ // Update a thumbnail to observer.
+ void UpdateSourceThumbnail(content::DesktopMediaID id,
+ const gfx::ImageSkia& image);
+
+ // Called when a refresh is complete. Invokes |refresh_callback_| unless it
+ // is null. Postcondition: |refresh_callback_| is null.
+ void OnRefreshComplete();
+
+ bool can_refresh() const { return !refresh_callback_.is_null(); }
+
+ // Size of thumbnails generated by the model.
+ gfx::Size thumbnail_size_ =
+ gfx::Size(kDefaultThumbnailSize, kDefaultThumbnailSize);
+
+ // ID of the hosting dialog.
+ content::DesktopMediaID view_dialog_id_ =
+ content::DesktopMediaID(content::DesktopMediaID::TYPE_NONE, -1);
+
+ // Desktop media type of the list.
+ content::DesktopMediaID::Type type_ = content::DesktopMediaID::TYPE_NONE;
+
+ private:
+ // Post a task for next list update.
+ void ScheduleNextRefresh();
+
+ // Time interval between mode updates.
+ base::TimeDelta update_period_;
+
+ // Current list of sources.
+ std::vector<Source> sources_;
+
+ // The observer passed to StartUpdating().
+ DesktopMediaListObserver* observer_ = nullptr;
+
+ // Called when a refresh operation completes.
+ RefreshCallback refresh_callback_;
+
+ base::WeakPtrFactory<DesktopMediaListBase> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaListBase);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_BASE_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h b/chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h
new file mode 100644
index 00000000000..ad7f766a36b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_OBSERVER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_OBSERVER_H_
+
+class DesktopMediaList;
+
+// Interface implemented by the desktop media picker dialog to receive
+// notifications about changes in DesktopMediaList.
+class DesktopMediaListObserver {
+ public:
+ // TODO(jrw): None of the |list| parameters below seem to be used. Consider
+ // removing them.
+ virtual void OnSourceAdded(DesktopMediaList* list, int index) = 0;
+ virtual void OnSourceRemoved(DesktopMediaList* list, int index) = 0;
+ virtual void OnSourceMoved(DesktopMediaList* list,
+ int old_index,
+ int new_index) = 0;
+ virtual void OnSourceNameChanged(DesktopMediaList* list, int index) = 0;
+ virtual void OnSourceThumbnailChanged(DesktopMediaList* list, int index) = 0;
+
+ protected:
+ virtual ~DesktopMediaListObserver() {}
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_OBSERVER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker.cc
new file mode 100644
index 00000000000..32b31d5f579
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker.cc
@@ -0,0 +1,10 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "desktop_media_picker.h"
+
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+
+DesktopMediaPicker::Params::Params() = default;
+DesktopMediaPicker::Params::~Params() = default;
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker.h
new file mode 100644
index 00000000000..afcb60351dd
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker.h
@@ -0,0 +1,88 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/native_widget_types.h"
+
+class DesktopMediaList;
+
+namespace content {
+class WebContents;
+}
+
+// Abstract interface for desktop media picker UI. It's used by Desktop Media
+// API and by ARC to let user choose a desktop media source.
+//
+// TODO(crbug.com/987001): Rename this class.
+class DesktopMediaPicker {
+ public:
+ using DoneCallback = base::OnceCallback<void(content::DesktopMediaID id)>;
+
+ struct Params {
+ Params();
+ ~Params();
+
+ // WebContents this picker is relative to, can be null.
+ content::WebContents* web_contents = nullptr;
+ // The context whose root window is used for dialog placement, cannot be
+ // null for Aura.
+ gfx::NativeWindow context = nullptr;
+ // Parent window the dialog is relative to, only used on Mac.
+ gfx::NativeWindow parent = nullptr;
+ // The modality used for showing the dialog.
+ ui::ModalType modality = ui::ModalType::MODAL_TYPE_CHILD;
+ // The name used in the dialog for what is requesting the picker to be
+ // shown.
+ base::string16 app_name;
+ // Can be the same as target_name. If it is not then this is used in the
+ // dialog for what is specific target within the app_name is requesting the
+ // picker.
+ base::string16 target_name;
+ // Whether audio capture should be shown as an option in the picker.
+ bool request_audio = false;
+ // Whether audio capture option should be approved by default if shown.
+ bool approve_audio_by_default = true;
+ // This flag controls the behvior in the case where the picker is invoked to
+ // select a screen and there is only one screen available. If true, the
+ // dialog is bypassed entirely and the screen is automatically selected.
+ // This behavior is disabled by default because in addition to letting the
+ // user select a desktop, the desktop picker also serves to prevent the
+ // screen screen from being shared without the user's explicit consent.
+ bool select_only_screen = false;
+ };
+
+ // Creates default implementation of DesktopMediaPicker for the current
+ // platform.
+ static std::unique_ptr<DesktopMediaPicker> Create();
+
+ DesktopMediaPicker() {}
+ virtual ~DesktopMediaPicker() {}
+
+ // Shows dialog with list of desktop media sources (screens, windows, tabs)
+ // provided by |sources_lists|.
+ // Dialog window will call |done_callback| when user chooses one of the
+ // sources or closes the dialog.
+ virtual void Show(const Params& params,
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists,
+ DoneCallback done_callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPicker);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc
new file mode 100644
index 00000000000..e026c204534
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc
@@ -0,0 +1,109 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_picker_controller.h"
+
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/grit/chromium_strings.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/desktop_streams_registry.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "desktop_media_picker.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/switches.h"
+#include "ui/base/l10n/l10n_util.h"
+
+DesktopMediaPickerController::DesktopMediaPickerController(
+ DesktopMediaPickerFactory* picker_factory)
+ : picker_factory_(picker_factory
+ ? picker_factory
+ : DesktopMediaPickerFactoryImpl::GetInstance()) {}
+
+DesktopMediaPickerController::~DesktopMediaPickerController() = default;
+
+void DesktopMediaPickerController::Show(
+ const Params& params,
+ const std::vector<content::DesktopMediaID::Type>& sources,
+ DoneCallback done_callback) {
+ DCHECK(!base::Contains(sources, content::DesktopMediaID::TYPE_NONE));
+ DCHECK(!done_callback_);
+
+ done_callback_ = std::move(done_callback);
+ params_ = params;
+
+ Observe(params.web_contents);
+
+ // Keep same order as the input |sources| and avoid duplicates.
+ source_lists_ = picker_factory_->CreateMediaList(sources);
+ if (source_lists_.empty()) {
+ OnPickerDialogResults("At least one source type must be specified.", {});
+ return;
+ }
+
+ if (params.select_only_screen && sources.size() == 1 &&
+ sources[0] == content::DesktopMediaID::TYPE_SCREEN) {
+ // Try to bypass the picker dialog if possible.
+ DCHECK(source_lists_.size() == 1);
+ auto* source_list = source_lists_[0].get();
+ source_list->Update(
+ base::BindOnce(&DesktopMediaPickerController::OnInitialMediaListFound,
+ base::Unretained(this)));
+ } else {
+ ShowPickerDialog();
+ }
+}
+
+void DesktopMediaPickerController::WebContentsDestroyed() {
+ OnPickerDialogResults(std::string(), content::DesktopMediaID());
+}
+
+void DesktopMediaPickerController::OnInitialMediaListFound() {
+ DCHECK(params_.select_only_screen);
+ DCHECK(source_lists_.size() == 1);
+ auto* source_list = source_lists_[0].get();
+ if (source_list->GetSourceCount() == 1) {
+ OnPickerDialogResults({}, source_list->GetSource(0).id);
+ return;
+ }
+
+ ShowPickerDialog();
+}
+
+void DesktopMediaPickerController::ShowPickerDialog() {
+ picker_ = picker_factory_->CreatePicker();
+ if (!picker_) {
+ OnPickerDialogResults(
+ "Desktop Capture API is not yet implemented for this platform.", {});
+ return;
+ }
+
+ picker_->Show(params_, std::move(source_lists_),
+ base::Bind(&DesktopMediaPickerController::OnPickerDialogResults,
+ base::Unretained(this), std::string()));
+}
+
+void DesktopMediaPickerController::OnPickerDialogResults(
+ const std::string& err,
+ content::DesktopMediaID source) {
+ DCHECK(done_callback_);
+ std::move(done_callback_).Run(err, source);
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h
new file mode 100644
index 00000000000..5bdd0ecdf82
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h
@@ -0,0 +1,91 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_CONTROLLER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/base/ui_base_types.h"
+
+class DesktopMediaList;
+class DesktopMediaPickerFactory;
+
+// The main entry point for the desktop picker dialog box, which prompts the
+// user to select a desktop or an application window whose content will be made
+// available as a video stream.
+//
+// TODO(crbug.com/987001): Rename this class. Consider merging with
+// DesktopMediaPickerViews and naming the merged class just DesktopMediaPicker.
+class DesktopMediaPickerController : private content::WebContentsObserver {
+ public:
+ using Params = DesktopMediaPicker::Params;
+
+ // Callback for desktop selection results. There are three possible cases:
+ //
+ // - If |err| is non-empty, it contains an error message regarding why the
+ // dialog could not be displayed, and the value of |id| should not be used.
+ //
+ // - If |err| is empty and id.is_null() is true, the user canceled the dialog.
+ //
+ // - Otherwise, |id| represents the user's selection.
+ using DoneCallback = base::OnceCallback<void(const std::string& err,
+ content::DesktopMediaID id)>;
+
+ explicit DesktopMediaPickerController(
+ DesktopMediaPickerFactory* picker_factory = nullptr);
+ DesktopMediaPickerController(const DesktopMediaPickerController&) = delete;
+ DesktopMediaPickerController& operator=(const DesktopMediaPickerController&) =
+ delete;
+ ~DesktopMediaPickerController() override;
+
+ // Show the desktop picker dialog using the parameters specified by |params|,
+ // with the possible selections restricted to those included in |sources|. If
+ // an error is detected synchronously, it is reported by returning an error
+ // string. Otherwise, the return value is nullopt, and the closure passed as
+ // |done_callback| is called when the dialog is closed. If the dialog is
+ // canceled, the argument to |done_callback| will be an instance of
+ // DesktopMediaID whose is_null() method returns true.
+ //
+ // As a special case, if |params.select_only_screen| is true, and the only
+ // selection type is TYPE_SCREEN, and there is only one screen,
+ // |done_callback| is called immediately with the screen's ID, and the dialog
+ // is not shown. This option must be used with care, because even when the
+ // dialog has only one option to select, the dialog itself helps prevent the
+ // user for accidentally sharing their screen and gives them the option to
+ // prevent their screen from being shared.
+ //
+ // Note that |done_callback| is called only if the dialog completes normally.
+ // If an instance of this class is destroyed while the dialog is visible, the
+ // dialog will be cleaned up, but |done_callback| will not be invoked.
+ void Show(const Params& params,
+ const std::vector<content::DesktopMediaID::Type>& sources,
+ DoneCallback done_callback);
+
+ // content::WebContentsObserver overrides.
+ void WebContentsDestroyed() override;
+
+ private:
+ void OnInitialMediaListFound();
+ void ShowPickerDialog();
+ // This function is responsible to call |done_callback_| and after running the
+ // callback |this| might be destroyed. Do **not** access fields after calling
+ // this function.
+ void OnPickerDialogResults(const std::string& err,
+ content::DesktopMediaID source);
+
+ Params params_;
+ DoneCallback done_callback_;
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists_;
+ std::unique_ptr<DesktopMediaPicker> picker_;
+ DesktopMediaPickerFactory* picker_factory_;
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_CONTROLLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc
new file mode 100644
index 00000000000..545d149d069
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/mock_callback.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_controller.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::ByMove;
+using testing::InvokeArgument;
+using testing::Ne;
+using testing::Return;
+using testing::ReturnRef;
+using testing::WithArg;
+
+class MockDesktopMediaPicker : public DesktopMediaPicker {
+ public:
+ MOCK_METHOD3(Show,
+ void(const Params& params,
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists,
+ DoneCallback done_callback));
+};
+
+class MockDesktopMediaList : public DesktopMediaList {
+ public:
+ MOCK_METHOD1(SetUpdatePeriod, void(base::TimeDelta period));
+ MOCK_METHOD1(SetThumbnailSize, void(const gfx::Size& thumbnail_size));
+ MOCK_METHOD1(SetViewDialogWindowId, void(content::DesktopMediaID dialog_id));
+ MOCK_METHOD1(StartUpdating, void(DesktopMediaListObserver* observer));
+ MOCK_METHOD1(Update, void(UpdateCallback callback));
+ MOCK_CONST_METHOD0(GetSourceCount, int());
+ MOCK_CONST_METHOD1(GetSource, Source&(int index));
+ MOCK_CONST_METHOD0(GetMediaListType, content::DesktopMediaID::Type());
+};
+
+class MockDesktopMediaPickerFactory : public DesktopMediaPickerFactory {
+ public:
+ MOCK_METHOD0(CreatePicker, std::unique_ptr<DesktopMediaPicker>());
+ MOCK_METHOD1(CreateMediaList,
+ std::vector<std::unique_ptr<DesktopMediaList>>(
+ const std::vector<content::DesktopMediaID::Type>& types));
+};
+
+class DesktopMediaPickerControllerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ON_CALL(factory_, CreatePicker).WillByDefault([this]() {
+ return std::unique_ptr<DesktopMediaPicker>(std::move(picker_));
+ });
+ ON_CALL(factory_, CreateMediaList).WillByDefault([this](const auto& types) {
+ std::vector<std::unique_ptr<DesktopMediaList>> lists;
+ lists.push_back(std::move(media_list_));
+ return lists;
+ });
+ }
+
+ protected:
+ DesktopMediaPickerController::Params picker_params_;
+ base::MockCallback<DesktopMediaPickerController::DoneCallback> done_;
+ std::vector<content::DesktopMediaID::Type> source_types_{
+ content::DesktopMediaID::TYPE_SCREEN};
+ content::DesktopMediaID media_id_{content::DesktopMediaID::TYPE_SCREEN, 42};
+ std::unique_ptr<MockDesktopMediaPicker> picker_ =
+ std::make_unique<MockDesktopMediaPicker>();
+ std::unique_ptr<MockDesktopMediaList> media_list_ =
+ std::make_unique<MockDesktopMediaList>();
+ MockDesktopMediaPickerFactory factory_;
+};
+
+// Test that the picker dialog is shown and the selected media ID is returned.
+TEST_F(DesktopMediaPickerControllerTest, ShowPicker) {
+ EXPECT_CALL(factory_, CreatePicker());
+ EXPECT_CALL(factory_, CreateMediaList(source_types_));
+ EXPECT_CALL(done_, Run("", media_id_));
+ EXPECT_CALL(*picker_, Show)
+ .WillOnce(WithArg<2>([&](DesktopMediaPicker::DoneCallback cb) {
+ std::move(cb).Run(media_id_);
+ }));
+ EXPECT_CALL(*media_list_, Update).Times(0);
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
+
+// Test that a null result is returned in response to WebContentsDestroyed().
+TEST_F(DesktopMediaPickerControllerTest, WebContentsDestroyed) {
+ EXPECT_CALL(factory_, CreatePicker());
+ EXPECT_CALL(factory_, CreateMediaList(source_types_));
+ EXPECT_CALL(done_, Run("", content::DesktopMediaID()));
+ EXPECT_CALL(*picker_, Show);
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+ controller.WebContentsDestroyed();
+}
+
+// Test that the picker dialog can be bypassed.
+TEST_F(DesktopMediaPickerControllerTest, ShowSingleScreen) {
+ picker_params_.select_only_screen = true;
+
+ DesktopMediaList::Source source;
+ source.id = media_id_;
+ source.name = base::ASCIIToUTF16("fake name");
+
+ EXPECT_CALL(factory_, CreatePicker()).Times(0);
+ EXPECT_CALL(factory_, CreateMediaList(source_types_));
+ EXPECT_CALL(done_, Run("", source.id));
+ EXPECT_CALL(*picker_, Show).Times(0);
+ EXPECT_CALL(*media_list_, Update)
+ .WillOnce(
+ [](DesktopMediaList::UpdateCallback cb) { std::move(cb).Run(); });
+ EXPECT_CALL(*media_list_, GetSourceCount)
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(1));
+ EXPECT_CALL(*media_list_, GetSource(0))
+ .Times(AnyNumber())
+ .WillRepeatedly(ReturnRef(source));
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
+
+// Test that an error is reported when no sources are found.
+TEST_F(DesktopMediaPickerControllerTest, EmptySourceList) {
+ EXPECT_CALL(factory_, CreateMediaList)
+ .WillOnce(
+ Return(ByMove(std::vector<std::unique_ptr<DesktopMediaList>>())));
+ EXPECT_CALL(done_, Run(Ne(""), content::DesktopMediaID()));
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
+
+// Test that an error is reported when no picker can be created.
+TEST_F(DesktopMediaPickerControllerTest, NoPicker) {
+ EXPECT_CALL(factory_, CreatePicker)
+ .WillOnce(Return(ByMove(std::unique_ptr<DesktopMediaPicker>())));
+ EXPECT_CALL(done_, Run(Ne(""), content::DesktopMediaID()));
+ EXPECT_CALL(factory_, CreateMediaList).Times(AnyNumber());
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc
new file mode 100644
index 00000000000..cbb3e722374
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc
@@ -0,0 +1,9 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+
+DesktopMediaPickerFactory::DesktopMediaPickerFactory() = default;
+
+DesktopMediaPickerFactory::~DesktopMediaPickerFactory() = default;
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h
new file mode 100644
index 00000000000..0e393e91a4d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "content/public/browser/desktop_media_id.h"
+
+// Interface for factory creating DesktopMediaList and DesktopMediaPicker
+// instances.
+class DesktopMediaPickerFactory {
+ public:
+ virtual ~DesktopMediaPickerFactory();
+
+ virtual std::unique_ptr<DesktopMediaPicker> CreatePicker() = 0;
+ virtual std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) = 0;
+
+ protected:
+ DesktopMediaPickerFactory();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerFactory);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc
new file mode 100644
index 00000000000..fa7d872810a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc
@@ -0,0 +1,91 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+
+#include "base/no_destructor.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "content/public/browser/desktop_capture.h"
+
+DesktopMediaPickerFactoryImpl::DesktopMediaPickerFactoryImpl() = default;
+
+DesktopMediaPickerFactoryImpl::~DesktopMediaPickerFactoryImpl() = default;
+
+// static
+DesktopMediaPickerFactoryImpl* DesktopMediaPickerFactoryImpl::GetInstance() {
+ static base::NoDestructor<DesktopMediaPickerFactoryImpl> impl;
+ return impl.get();
+}
+
+std::unique_ptr<DesktopMediaPicker>
+DesktopMediaPickerFactoryImpl::CreatePicker() {
+// DesktopMediaPicker is implemented only for Windows, OSX and Aura Linux
+// builds.
+#if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX)
+ return DesktopMediaPicker::Create();
+#else
+ return nullptr;
+#endif
+}
+
+std::vector<std::unique_ptr<DesktopMediaList>>
+DesktopMediaPickerFactoryImpl::CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) {
+ // Keep same order as the input |sources| and avoid duplicates.
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists;
+ bool have_screen_list = false;
+ bool have_window_list = false;
+ bool have_tab_list = false;
+ for (auto source_type : types) {
+ switch (source_type) {
+ case content::DesktopMediaID::TYPE_NONE:
+ break;
+ case content::DesktopMediaID::TYPE_SCREEN: {
+ if (have_screen_list)
+ continue;
+ std::unique_ptr<DesktopMediaList> screen_list;
+#if defined(OS_CHROMEOS)
+ screen_list = std::make_unique<DesktopMediaListAsh>(
+ content::DesktopMediaID::TYPE_SCREEN);
+#else // !defined(OS_CHROMEOS)
+ screen_list = std::make_unique<NativeDesktopMediaList>(
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::desktop_capture::CreateScreenCapturer());
+#endif // !defined(OS_CHROMEOS)
+ have_screen_list = true;
+ source_lists.push_back(std::move(screen_list));
+ break;
+ }
+ case content::DesktopMediaID::TYPE_WINDOW: {
+ if (have_window_list)
+ continue;
+ std::unique_ptr<DesktopMediaList> window_list;
+#if defined(OS_CHROMEOS)
+ window_list = std::make_unique<DesktopMediaListAsh>(
+ content::DesktopMediaID::TYPE_WINDOW);
+#else // !defined(OS_CHROMEOS)
+ window_list = std::make_unique<NativeDesktopMediaList>(
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::desktop_capture::CreateWindowCapturer());
+#endif // !defined(OS_CHROMEOS)
+ have_window_list = true;
+ source_lists.push_back(std::move(window_list));
+ break;
+ }
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS: {
+ if (have_tab_list)
+ continue;
+ std::unique_ptr<DesktopMediaList> tab_list =
+ std::make_unique<TabDesktopMediaList>();
+ have_tab_list = true;
+ source_lists.push_back(std::move(tab_list));
+ break;
+ }
+ }
+ }
+ return source_lists;
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h
new file mode 100644
index 00000000000..4888b591445
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_IMPL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_IMPL_H_
+
+#include <memory>
+#include <vector>
+
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+
+// Factory creating DesktopMediaList and DesktopMediaPicker instances.
+class DesktopMediaPickerFactoryImpl : public DesktopMediaPickerFactory {
+ public:
+ DesktopMediaPickerFactoryImpl();
+ ~DesktopMediaPickerFactoryImpl() override;
+
+ // Get the lazy initialized instance of the factory.
+ static DesktopMediaPickerFactoryImpl* GetInstance();
+
+ // DesktopMediaPickerFactory implementation
+ // Can return |nullptr| if platform doesn't support DesktopMediaPicker.
+ std::unique_ptr<DesktopMediaPicker> CreatePicker() override;
+ std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerFactoryImpl);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_IMPL_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc
new file mode 100644
index 00000000000..1f3a18f8268
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_media_picker_manager.h"
+
+#include "base/no_destructor.h"
+
+// static
+DesktopMediaPickerManager* DesktopMediaPickerManager::Get() {
+ static base::NoDestructor<DesktopMediaPickerManager> instance;
+ return instance.get();
+}
+
+DesktopMediaPickerManager::DesktopMediaPickerManager() = default;
+DesktopMediaPickerManager::~DesktopMediaPickerManager() = default;
+
+void DesktopMediaPickerManager::AddObserver(DialogObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DesktopMediaPickerManager::RemoveObserver(DialogObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void DesktopMediaPickerManager::OnShowDialog() {
+ for (auto& observer : observers_)
+ observer.OnDialogOpened();
+}
+
+void DesktopMediaPickerManager::OnHideDialog() {
+ for (auto& observer : observers_)
+ observer.OnDialogClosed();
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h
new file mode 100644
index 00000000000..391994156e6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_MANAGER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_MANAGER_H_
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+
+namespace base {
+template <typename T>
+class NoDestructor;
+}
+
+// A singleton that acts as a rendezvous for dialog observers to register and
+// the dialog managers/delegates to post their activities.
+// TODO(crbug/953495): Merge this into DesktopMediaPickerFactoryImpl.
+class DesktopMediaPickerManager {
+ public:
+ class DialogObserver : public base::CheckedObserver {
+ public:
+ // Called when a media dialog is opened/shown.
+ virtual void OnDialogOpened() = 0;
+
+ // Called when a media dialog is closed/hidden.
+ virtual void OnDialogClosed() = 0;
+ };
+
+ static DesktopMediaPickerManager* Get();
+
+ // For the observers
+ void AddObserver(DialogObserver* observer);
+ void RemoveObserver(DialogObserver* observer);
+
+ // For the notifiers
+ void OnShowDialog();
+ void OnHideDialog();
+
+ private:
+ friend base::NoDestructor<DesktopMediaPickerManager>;
+
+ DesktopMediaPickerManager();
+ ~DesktopMediaPickerManager(); // Never called.
+
+ base::ObserverList<DesktopMediaPickerManager::DialogObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerManager);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_MANAGER_H_
diff --git a/chromium/chrome/browser/media/webrtc/display_media_access_handler.cc b/chromium/chrome/browser/media/webrtc/display_media_access_handler.cc
new file mode 100644
index 00000000000..7e63e08ccf9
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -0,0 +1,270 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#endif
+
+// Holds pending request information so that we display one picker UI at a time
+// for each content::WebContents.
+struct DisplayMediaAccessHandler::PendingAccessRequest {
+ PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback)
+ : picker(std::move(picker)),
+ request(request),
+ callback(std::move(callback)) {}
+ ~PendingAccessRequest() = default;
+
+ std::unique_ptr<DesktopMediaPicker> picker;
+ content::MediaStreamRequest request;
+ content::MediaResponseCallback callback;
+};
+
+DisplayMediaAccessHandler::DisplayMediaAccessHandler()
+ : picker_factory_(new DesktopMediaPickerFactoryImpl()) {
+ AddNotificationObserver();
+}
+
+DisplayMediaAccessHandler::DisplayMediaAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
+ bool display_notification)
+ : display_notification_(display_notification),
+ picker_factory_(std::move(picker_factory)) {
+ AddNotificationObserver();
+}
+
+DisplayMediaAccessHandler::~DisplayMediaAccessHandler() = default;
+
+bool DisplayMediaAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType stream_type,
+ const extensions::Extension* extension) {
+ return stream_type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+ // This class handles MEDIA_DISPLAY_AUDIO_CAPTURE as well, but only if it is
+ // accompanied by MEDIA_DISPLAY_VIDEO_CAPTURE request as per spec.
+ // https://w3c.github.io/mediacapture-screen-share/#mediadevices-additions
+ // 5.1 MediaDevices Additions
+ // "The user agent MUST reject audio-only requests."
+}
+
+bool DisplayMediaAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return false;
+}
+
+void DisplayMediaAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+#if defined(OS_MACOSX)
+ // Do not allow picker UI to be shown on a page that isn't in the foreground
+ // in Mac, because the UI implementation in Mac pops a window over any content
+ // which might be confusing for the users. See https://crbug.com/1407733 for
+ // details.
+ // TODO(emircan): Remove this once Mac UI doesn't use a window.
+ if (web_contents->GetVisibility() != content::Visibility::VISIBLE) {
+ LOG(ERROR) << "Do not allow getDisplayMedia() on a backgrounded page.";
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+#endif // defined(OS_MACOSX)
+
+ std::unique_ptr<DesktopMediaPicker> picker = picker_factory_->CreatePicker();
+ if (!picker) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+
+ RequestsQueue& queue = pending_requests_[web_contents];
+ queue.push_back(std::make_unique<PendingAccessRequest>(
+ std::move(picker), request, std::move(callback)));
+ // If this is the only request then pop picker UI.
+ if (queue.size() == 1)
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DisplayMediaAccessHandler::UpdateMediaRequestState(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (state != content::MEDIA_REQUEST_STATE_DONE &&
+ state != content::MEDIA_REQUEST_STATE_CLOSING) {
+ return;
+ }
+
+ if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ DeletePendingAccessRequest(render_process_id, render_frame_id,
+ page_request_id);
+ }
+ CaptureAccessHandlerBase::UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, stream_type, state);
+
+ // This method only gets called with the above checked states when all
+ // requests are to be canceled. Therefore, we don't need to process the
+ // next queued request.
+}
+
+void DisplayMediaAccessHandler::ProcessQueuedAccessRequest(
+ const RequestsQueue& queue,
+ content::WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ const PendingAccessRequest& pending_request = *queue.front();
+ UpdateTrusted(pending_request.request, false /* is_trusted */);
+
+ std::vector<content::DesktopMediaID::Type> media_types = {
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::DesktopMediaID::TYPE_WEB_CONTENTS};
+ auto source_lists = picker_factory_->CreateMediaList(media_types);
+
+ DesktopMediaPicker::DoneCallback done_callback =
+ base::BindOnce(&DisplayMediaAccessHandler::OnPickerDialogResults,
+ base::Unretained(this), web_contents);
+ DesktopMediaPicker::Params picker_params;
+ picker_params.web_contents = web_contents;
+ gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
+ picker_params.context = parent_window;
+ picker_params.parent = parent_window;
+ picker_params.app_name = url_formatter::FormatUrlForSecurityDisplay(
+ web_contents->GetLastCommittedURL(),
+ url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
+ picker_params.target_name = picker_params.app_name;
+ picker_params.request_audio =
+ pending_request.request.audio_type ==
+ blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
+ picker_params.approve_audio_by_default = false;
+ pending_request.picker->Show(picker_params, std::move(source_lists),
+ std::move(done_callback));
+}
+
+void DisplayMediaAccessHandler::OnPickerDialogResults(
+ content::WebContents* web_contents,
+ content::DesktopMediaID media_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(web_contents);
+
+ auto it = pending_requests_.find(web_contents);
+ if (it == pending_requests_.end())
+ return;
+ RequestsQueue& queue = it->second;
+ if (queue.empty()) {
+ // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
+ // need to do anything.
+ return;
+ }
+
+ PendingAccessRequest& pending_request = *queue.front();
+ blink::MediaStreamDevices devices;
+ blink::mojom::MediaStreamRequestResult request_result =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (media_id.is_null()) {
+ request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ } else {
+ request_result = blink::mojom::MediaStreamRequestResult::OK;
+#if defined(OS_MACOSX)
+ // Check screen capture permissions on Mac if necessary.
+ if ((media_id.type == content::DesktopMediaID::TYPE_SCREEN ||
+ media_id.type == content::DesktopMediaID::TYPE_WINDOW) &&
+ system_media_permissions::CheckSystemScreenCapturePermission() !=
+ system_media_permissions::SystemPermission::kAllowed) {
+ request_result =
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
+ }
+#endif
+ if (request_result == blink::mojom::MediaStreamRequestResult::OK) {
+ const auto& visible_url = url_formatter::FormatUrlForSecurityDisplay(
+ web_contents->GetLastCommittedURL(),
+ url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, media_id,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
+ media_id.audio_share, false /* disable_local_echo */,
+ display_notification_, visible_url, visible_url);
+ }
+ }
+
+ std::move(pending_request.callback)
+ .Run(devices, request_result, std::move(ui));
+ queue.pop_front();
+
+ if (!queue.empty())
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DisplayMediaAccessHandler::AddNotificationObserver() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ notifications_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::NotificationService::AllSources());
+}
+
+void DisplayMediaAccessHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+ pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
+
+void DisplayMediaAccessHandler::DeletePendingAccessRequest(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id) {
+ for (auto& queue_it : pending_requests_) {
+ RequestsQueue& queue = queue_it.second;
+ for (auto it = queue.begin(); it != queue.end(); ++it) {
+ const PendingAccessRequest& pending_request = **it;
+ if (pending_request.request.render_process_id == render_process_id &&
+ pending_request.request.render_frame_id == render_frame_id &&
+ pending_request.request.page_request_id == page_request_id) {
+ queue.erase(it);
+ return;
+ }
+ }
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/display_media_access_handler.h b/chromium/chrome/browser/media/webrtc/display_media_access_handler.h
new file mode 100644
index 00000000000..8ae226f2324
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/display_media_access_handler.h
@@ -0,0 +1,89 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "chrome/browser/media/capture_access_handler_base.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace extensions {
+class Extension;
+}
+
+// MediaAccessHandler for getDisplayMedia API, see
+// https://w3c.github.io/mediacapture-screen-share.
+class DisplayMediaAccessHandler : public CaptureAccessHandlerBase,
+ public content::NotificationObserver {
+ public:
+ DisplayMediaAccessHandler();
+ DisplayMediaAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
+ bool display_notification);
+ ~DisplayMediaAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType stream_type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+ void UpdateMediaRequestState(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+
+ private:
+ friend class DisplayMediaAccessHandlerTest;
+
+ struct PendingAccessRequest;
+ using RequestsQueue =
+ base::circular_deque<std::unique_ptr<PendingAccessRequest>>;
+ using RequestsQueues = base::flat_map<content::WebContents*, RequestsQueue>;
+
+ void ProcessQueuedAccessRequest(const RequestsQueue& queue,
+ content::WebContents* web_contents);
+
+ void OnPickerDialogResults(content::WebContents* web_contents,
+ content::DesktopMediaID source);
+
+ void AddNotificationObserver();
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ void DeletePendingAccessRequest(int render_process_id,
+ int render_frame_id,
+ int page_request_id);
+
+ bool display_notification_ = true;
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory_;
+ RequestsQueues pending_requests_;
+ content::NotificationRegistrar notifications_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayMediaAccessHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc b/chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
new file mode 100644
index 00000000000..0fa5aa3bfbe
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
@@ -0,0 +1,286 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+class DisplayMediaAccessHandlerTest : public ChromeRenderViewHostTestHarness {
+ public:
+ DisplayMediaAccessHandlerTest() {}
+ ~DisplayMediaAccessHandlerTest() override {}
+
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+ auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
+ picker_factory_ = picker_factory.get();
+ access_handler_ = std::make_unique<DisplayMediaAccessHandler>(
+ std::move(picker_factory), false /* display_notification */);
+ }
+
+ void ProcessRequest(
+ const content::DesktopMediaID& fake_desktop_media_id_response,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ bool request_audio) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, request_audio,
+ fake_desktop_media_id_response /* selected_source */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(),
+ request_audio ? blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE
+ : blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, false);
+
+ base::RunLoop wait_loop;
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop, request_result, devices_result);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ wait_loop.Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+
+ access_handler_.reset();
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ }
+
+ void NotifyWebContentsDestroyed() {
+ access_handler_->Observe(
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<content::WebContents>(web_contents()),
+ content::NotificationDetails());
+ }
+
+ const DisplayMediaAccessHandler::RequestsQueues& GetRequestQueues() {
+ return access_handler_->pending_requests_;
+ }
+
+ protected:
+ FakeDesktopMediaPickerFactory* picker_factory_;
+ std::unique_ptr<DisplayMediaAccessHandler> access_handler_;
+};
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionGiven) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId),
+ &result, &devices, false /* request_audio */);
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default.
+ if (base::mac::IsAtLeastOS10_15()) {
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ result);
+ return;
+ }
+#endif
+
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_TRUE(devices[0].display_media_info.has_value());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionGivenToRequestWithAudio) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ content::DesktopMediaID fake_media_id(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId,
+ true /* audio_share */);
+ ProcessRequest(fake_media_id, &result, &devices, true /* request_audio */);
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default.
+ if (base::mac::IsAtLeastOS10_15()) {
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ result);
+ return;
+ }
+#endif
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(2u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_TRUE(devices[0].display_media_info.has_value());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
+ devices[1].type);
+ EXPECT_TRUE(devices[1].input.IsValid());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionDenied) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(), &result, &devices,
+ true /* request_audio */);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
+ EXPECT_EQ(0u, devices.size());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, UpdateMediaRequestStateWithClosing) {
+ const int render_process_id = 0;
+ const int render_frame_id = 0;
+ const int page_request_id = 0;
+ const blink::mojom::MediaStreamType video_stream_type =
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+ const blink::mojom::MediaStreamType audio_stream_type =
+ blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, true /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ render_process_id, render_frame_id, page_request_id,
+ GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(), audio_stream_type, video_stream_type,
+ false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ access_handler_->UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, video_stream_type,
+ content::MEDIA_REQUEST_STATE_CLOSING);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(0u, queue_it->second.size());
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ access_handler_.reset();
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, WebContentsDestroyed) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ NotifyWebContentsDestroyed();
+ EXPECT_EQ(0u, GetRequestQueues().size());
+ access_handler_.reset();
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, MultipleRequests) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId) /* selected_source */},
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::DesktopMediaID::kNullId) /* selected_source */}};
+ const size_t kTestFlagCount = 2;
+ picker_factory_->SetTestFlags(test_flags, kTestFlagCount);
+
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ base::RunLoop wait_loop[kTestFlagCount];
+ for (size_t i = 0; i < kTestFlagCount; ++i) {
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop[i], &result, &devices);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ }
+ wait_loop[0].Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default.
+ if (base::mac::IsAtLeastOS10_15()) {
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ result);
+ access_handler_.reset();
+ return;
+ }
+#endif
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+
+ blink::MediaStreamDevice first_device = devices[0];
+ EXPECT_TRUE(test_flags[1].picker_created);
+ EXPECT_FALSE(test_flags[1].picker_deleted);
+ wait_loop[1].Run();
+ EXPECT_TRUE(test_flags[1].picker_deleted);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_FALSE(devices[0].IsSameDevice(first_device));
+
+ access_handler_.reset();
+}
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc
new file mode 100644
index 00000000000..ee71126dc3e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc
@@ -0,0 +1,88 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/fake_desktop_media_list.h"
+
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
+#include "ui/gfx/skia_util.h"
+
+using content::DesktopMediaID;
+
+FakeDesktopMediaList::FakeDesktopMediaList(DesktopMediaID::Type type)
+ : observer_(NULL), type_(type) {}
+FakeDesktopMediaList::~FakeDesktopMediaList() {}
+
+void FakeDesktopMediaList::AddSource(int id) {
+ AddSourceByFullMediaID(
+ content::DesktopMediaID(DesktopMediaID::TYPE_WINDOW, id));
+}
+
+void FakeDesktopMediaList::AddSourceByFullMediaID(
+ content::DesktopMediaID media_id) {
+ Source source;
+ source.id = media_id;
+ source.name = base::NumberToString16(media_id.id);
+
+ sources_.push_back(source);
+ observer_->OnSourceAdded(this, sources_.size() - 1);
+}
+
+void FakeDesktopMediaList::RemoveSource(int index) {
+ sources_.erase(sources_.begin() + index);
+ observer_->OnSourceRemoved(this, index);
+}
+
+void FakeDesktopMediaList::MoveSource(int old_index, int new_index) {
+ Source source = sources_[old_index];
+ sources_.erase(sources_.begin() + old_index);
+ sources_.insert(sources_.begin() + new_index, source);
+ observer_->OnSourceMoved(this, old_index, new_index);
+}
+
+void FakeDesktopMediaList::SetSourceThumbnail(int index) {
+ sources_[index].thumbnail = thumbnail_;
+ observer_->OnSourceThumbnailChanged(this, index);
+}
+
+void FakeDesktopMediaList::SetSourceName(int index, base::string16 name) {
+ sources_[index].name = name;
+ observer_->OnSourceNameChanged(this, index);
+}
+
+void FakeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {}
+
+void FakeDesktopMediaList::SetThumbnailSize(const gfx::Size& thumbnail_size) {}
+
+void FakeDesktopMediaList::SetViewDialogWindowId(
+ content::DesktopMediaID dialog_id) {}
+
+void FakeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) {
+ observer_ = observer;
+
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(150, 150);
+ bitmap.eraseARGB(255, 0, 255, 0);
+ thumbnail_ = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+}
+
+void FakeDesktopMediaList::Update(UpdateCallback callback) {
+ std::move(callback).Run();
+}
+
+int FakeDesktopMediaList::GetSourceCount() const {
+ return sources_.size();
+}
+
+const DesktopMediaList::Source& FakeDesktopMediaList::GetSource(
+ int index) const {
+ return sources_[index];
+}
+
+DesktopMediaID::Type FakeDesktopMediaList::GetMediaListType() const {
+ return type_;
+}
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h
new file mode 100644
index 00000000000..d9ed43891a3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_LIST_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_LIST_H_
+
+#include <vector>
+
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+
+class FakeDesktopMediaList : public DesktopMediaList {
+ public:
+ explicit FakeDesktopMediaList(content::DesktopMediaID::Type type);
+ ~FakeDesktopMediaList() override;
+
+ void AddSource(int id);
+ void AddSourceByFullMediaID(content::DesktopMediaID media_id);
+ void RemoveSource(int index);
+ void MoveSource(int old_index, int new_index);
+ void SetSourceThumbnail(int index);
+ void SetSourceName(int index, base::string16 name);
+
+ // DesktopMediaList implementation:
+ void SetUpdatePeriod(base::TimeDelta period) override;
+ void SetThumbnailSize(const gfx::Size& thumbnail_size) override;
+ void SetViewDialogWindowId(content::DesktopMediaID dialog_id) override;
+ void StartUpdating(DesktopMediaListObserver* observer) override;
+ void Update(UpdateCallback callback) override;
+ int GetSourceCount() const override;
+ const Source& GetSource(int index) const override;
+ content::DesktopMediaID::Type GetMediaListType() const override;
+
+ private:
+ std::vector<Source> sources_;
+ DesktopMediaListObserver* observer_;
+ gfx::ImageSkia thumbnail_;
+ const content::DesktopMediaID::Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDesktopMediaList);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_LIST_H_
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
new file mode 100644
index 00000000000..99a474ce324
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
@@ -0,0 +1,108 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class FakeDesktopMediaPicker : public DesktopMediaPicker {
+ public:
+ explicit FakeDesktopMediaPicker(
+ FakeDesktopMediaPickerFactory::TestFlags* expectation)
+ : expectation_(expectation) {
+ expectation_->picker_created = true;
+ }
+ ~FakeDesktopMediaPicker() override { expectation_->picker_deleted = true; }
+
+ // DesktopMediaPicker interface.
+ void Show(const DesktopMediaPicker::Params& params,
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists,
+ DoneCallback done_callback) override {
+ bool show_screens = false;
+ bool show_windows = false;
+ bool show_tabs = false;
+
+ for (auto& source_list : source_lists) {
+ switch (source_list->GetMediaListType()) {
+ case content::DesktopMediaID::TYPE_NONE:
+ break;
+ case content::DesktopMediaID::TYPE_SCREEN:
+ show_screens = true;
+ break;
+ case content::DesktopMediaID::TYPE_WINDOW:
+ show_windows = true;
+ break;
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ show_tabs = true;
+ break;
+ }
+ }
+ EXPECT_EQ(expectation_->expect_screens, show_screens);
+ EXPECT_EQ(expectation_->expect_windows, show_windows);
+ EXPECT_EQ(expectation_->expect_tabs, show_tabs);
+ EXPECT_EQ(expectation_->expect_audio, params.request_audio);
+ EXPECT_EQ(params.modality, ui::ModalType::MODAL_TYPE_CHILD);
+
+ if (!expectation_->cancelled) {
+ // Post a task to call the callback asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&FakeDesktopMediaPicker::CallCallback,
+ weak_factory_.GetWeakPtr(), std::move(done_callback)));
+ } else {
+ // If we expect the dialog to be cancelled then store the callback to
+ // retain reference to the callback handler.
+ done_callback_ = std::move(done_callback);
+ }
+ }
+
+ private:
+ void CallCallback(DoneCallback done_callback) {
+ std::move(done_callback).Run(expectation_->selected_source);
+ }
+
+ FakeDesktopMediaPickerFactory::TestFlags* expectation_;
+ DoneCallback done_callback_;
+
+ base::WeakPtrFactory<FakeDesktopMediaPicker> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDesktopMediaPicker);
+};
+
+FakeDesktopMediaPickerFactory::FakeDesktopMediaPickerFactory() = default;
+
+FakeDesktopMediaPickerFactory::~FakeDesktopMediaPickerFactory() = default;
+
+void FakeDesktopMediaPickerFactory::SetTestFlags(TestFlags* test_flags,
+ int tests_count) {
+ test_flags_ = test_flags;
+ tests_count_ = tests_count;
+ current_test_ = 0;
+}
+
+std::unique_ptr<DesktopMediaPicker>
+FakeDesktopMediaPickerFactory::CreatePicker() {
+ EXPECT_LE(current_test_, tests_count_);
+ if (current_test_ >= tests_count_)
+ return std::unique_ptr<DesktopMediaPicker>();
+ ++current_test_;
+ return std::unique_ptr<DesktopMediaPicker>(
+ new FakeDesktopMediaPicker(test_flags_ + current_test_ - 1));
+}
+
+std::vector<std::unique_ptr<DesktopMediaList>>
+FakeDesktopMediaPickerFactory::CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) {
+ EXPECT_LE(current_test_, tests_count_);
+ std::vector<std::unique_ptr<DesktopMediaList>> media_lists;
+ for (auto source_type : types)
+ media_lists.emplace_back(new FakeDesktopMediaList(source_type));
+ return media_lists;
+}
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
new file mode 100644
index 00000000000..57ace04ddcc
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
@@ -0,0 +1,53 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_PICKER_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_PICKER_FACTORY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+
+// Used in tests to supply fake picker.
+class FakeDesktopMediaPickerFactory : public DesktopMediaPickerFactory {
+ public:
+ struct TestFlags {
+ bool expect_screens = false;
+ bool expect_windows = false;
+ bool expect_tabs = false;
+ bool expect_audio = false;
+ content::DesktopMediaID selected_source;
+ bool cancelled = false;
+
+ // Following flags are set by FakeDesktopMediaPicker when it's created and
+ // deleted.
+ bool picker_created = false;
+ bool picker_deleted = false;
+ };
+
+ FakeDesktopMediaPickerFactory();
+ ~FakeDesktopMediaPickerFactory() override;
+
+ // |test_flags| are expected to outlive the factory.
+ void SetTestFlags(TestFlags* test_flags, int tests_count);
+
+ // DesktopMediaPickerFactory implementation
+ std::unique_ptr<DesktopMediaPicker> CreatePicker() override;
+ std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) override;
+
+ private:
+ TestFlags* test_flags_;
+ int tests_count_;
+ int current_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDesktopMediaPickerFactory);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_PICKER_FACTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h b/chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h
new file mode 100644
index 00000000000..cd5c578d604
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_AUTHORIZATION_WRAPPER_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_AUTHORIZATION_WRAPPER_MAC_H_
+
+#import <Foundation/NSString.h>
+
+#include "base/callback_forward.h"
+
+namespace system_media_permissions {
+
+class MediaAuthorizationWrapper {
+ public:
+ virtual ~MediaAuthorizationWrapper() {}
+
+ virtual NSInteger AuthorizationStatusForMediaType(NSString* media_type) = 0;
+ virtual void RequestAccessForMediaType(NSString* media_type,
+ base::RepeatingClosure callback,
+ const base::TaskTraits& traits) = 0;
+};
+
+} // namespace system_media_permissions
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_AUTHORIZATION_WRAPPER_MAC_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
new file mode 100644
index 00000000000..954ce697768
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -0,0 +1,493 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/media_capture_devices.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/buildflags/buildflags.h"
+#include "extensions/common/constants.h"
+#include "media/base/media_switches.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+#include "ash/shell.h"
+#include "chrome/browser/media/chromeos_login_media_access_handler.h"
+#include "chrome/browser/media/public_session_media_access_handler.h"
+#include "chrome/browser/media/public_session_tab_capture_access_handler.h"
+#endif // defined(OS_CHROMEOS)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/media/extension_media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
+#include "chrome/browser/media/webrtc/tab_capture_access_handler.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+using blink::MediaStreamDevices;
+using content::BrowserThread;
+using content::MediaCaptureDevices;
+
+namespace {
+
+// Finds a device in |devices| that has |device_id|, or NULL if not found.
+const blink::MediaStreamDevice* FindDeviceWithId(
+ const blink::MediaStreamDevices& devices,
+ const std::string& device_id) {
+ auto iter = devices.begin();
+ for (; iter != devices.end(); ++iter) {
+ if (iter->id == device_id) {
+ return &(*iter);
+ }
+ }
+ return NULL;
+}
+
+content::WebContents* WebContentsFromIds(int render_process_id,
+ int render_frame_id) {
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id));
+ return web_contents;
+}
+
+} // namespace
+
+MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
+ return base::Singleton<MediaCaptureDevicesDispatcher>::get();
+}
+
+MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
+ : is_device_enumeration_disabled_(false),
+ media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+#if !defined(OS_ANDROID)
+ media_access_handlers_.push_back(
+ std::make_unique<DisplayMediaAccessHandler>());
+#endif // defined(OS_ANDROID)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#if defined(OS_CHROMEOS)
+ media_access_handlers_.push_back(
+ std::make_unique<ChromeOSLoginMediaAccessHandler>());
+ // Wrapper around ExtensionMediaAccessHandler used in Public Sessions.
+ media_access_handlers_.push_back(
+ std::make_unique<PublicSessionMediaAccessHandler>());
+#else
+ media_access_handlers_.push_back(
+ std::make_unique<ExtensionMediaAccessHandler>());
+#endif
+ media_access_handlers_.push_back(
+ std::make_unique<DesktopCaptureAccessHandler>());
+#if defined(OS_CHROMEOS)
+ // Wrapper around TabCaptureAccessHandler used in Public Sessions.
+ media_access_handlers_.push_back(
+ std::make_unique<PublicSessionTabCaptureAccessHandler>());
+#else
+ media_access_handlers_.push_back(std::make_unique<TabCaptureAccessHandler>());
+#endif
+#endif
+ media_access_handlers_.push_back(
+ std::make_unique<PermissionBubbleMediaAccessHandler>());
+}
+
+MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
+
+void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterStringPref(prefs::kDefaultAudioCaptureDevice,
+ std::string());
+ registry->RegisterStringPref(prefs::kDefaultVideoCaptureDevice,
+ std::string());
+}
+
+bool MediaCaptureDevicesDispatcher::IsOriginForCasting(const GURL& origin) {
+ // Whitelisted tab casting extensions.
+ return
+ // Media Router Dev
+ origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
+ // Media Router Stable
+ origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/";
+}
+
+void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!observers_.HasObserver(observer))
+ observers_.AddObserver(observer);
+}
+
+void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ observers_.RemoveObserver(observer);
+}
+
+const MediaStreamDevices&
+MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
+ return test_audio_devices_;
+
+ return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
+}
+
+const MediaStreamDevices&
+MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
+ return test_video_devices_;
+
+ return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
+}
+
+void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // Kill switch for getDisplayMedia() on browser side to prevent renderer from
+ // bypassing blink side checks.
+ if (request.video_type ==
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE &&
+ !base::FeatureList::IsEnabled(blink::features::kRTCGetDisplayMedia)) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
+ return;
+ }
+
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->SupportsStreamType(web_contents, request.video_type,
+ extension) ||
+ handler->SupportsStreamType(web_contents, request.audio_type,
+ extension)) {
+ handler->HandleRequest(web_contents, request, std::move(callback),
+ extension);
+ return;
+ }
+ }
+ std::move(callback).Run(blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
+ nullptr);
+}
+
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return CheckMediaAccessPermission(render_frame_host, security_origin, type,
+ nullptr);
+}
+
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->SupportsStreamType(
+ content::WebContents::FromRenderFrameHost(render_frame_host), type,
+ extension)) {
+ return handler->CheckMediaAccessPermission(
+ render_frame_host, security_origin, type, extension);
+ }
+ }
+ return false;
+}
+
+void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
+ Profile* profile,
+ bool audio,
+ bool video,
+ blink::MediaStreamDevices* devices) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(audio || video);
+
+ PrefService* prefs = profile->GetPrefs();
+ std::string default_device;
+ if (audio) {
+ default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
+ const blink::MediaStreamDevice* device =
+ GetRequestedAudioDevice(default_device);
+ if (!device)
+ device = GetFirstAvailableAudioDevice();
+ if (device)
+ devices->push_back(*device);
+ }
+
+ if (video) {
+ default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
+ const blink::MediaStreamDevice* device =
+ GetRequestedVideoDevice(default_device);
+ if (!device)
+ device = GetFirstAvailableVideoDevice();
+ if (device)
+ devices->push_back(*device);
+ }
+}
+
+std::string MediaCaptureDevicesDispatcher::GetDefaultDeviceIDForProfile(
+ Profile* profile,
+ blink::mojom::MediaStreamType type) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ PrefService* prefs = profile->GetPrefs();
+ if (type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE)
+ return prefs->GetString(prefs::kDefaultAudioCaptureDevice);
+ else if (type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE)
+ return prefs->GetString(prefs::kDefaultVideoCaptureDevice);
+ else
+ return std::string();
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
+ const std::string& requested_audio_device_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
+ const blink::MediaStreamDevice* const device =
+ FindDeviceWithId(audio_devices, requested_audio_device_id);
+ return device;
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
+ if (audio_devices.empty())
+ return NULL;
+ return &(*audio_devices.begin());
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
+ const std::string& requested_video_device_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
+ const blink::MediaStreamDevice* const device =
+ FindDeviceWithId(video_devices, requested_video_device_id);
+ return device;
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
+ if (video_devices.empty())
+ return NULL;
+ return &(*video_devices.begin());
+}
+
+void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
+ is_device_enumeration_disabled_ = true;
+}
+
+scoped_refptr<MediaStreamCaptureIndicator>
+MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
+ return media_stream_capture_indicator_;
+}
+
+void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
+ base::Unretained(this)));
+}
+
+void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
+ base::Unretained(this)));
+}
+
+void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
+ base::Unretained(this), render_process_id, render_frame_id,
+ page_request_id, security_origin, stream_type, state));
+}
+
+void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(int render_process_id,
+ int render_frame_id) {
+ // TODO(https://crbug.com/837606): Figure out how to simplify threading here.
+ // Currently, this will either always be called on the UI thread, or always
+ // on the IO thread, depending on how far along the work to migrate to the
+ // audio service has progressed. The rest of the methods of the
+ // content::MediaObserver are always called on the IO thread.
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ OnCreatingAudioStreamOnUIThread(render_process_id, render_frame_id);
+ return;
+ }
+
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
+ base::Unretained(this), render_process_id, render_frame_id));
+}
+
+void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
+ MediaStreamDevices devices = GetAudioCaptureDevices();
+ for (auto& observer : observers_)
+ observer.OnUpdateAudioDevices(devices);
+}
+
+void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
+ MediaStreamDevices devices = GetVideoCaptureDevices();
+ for (auto& observer : observers_)
+ observer.OnUpdateVideoDevices(devices);
+}
+
+void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->SupportsStreamType(
+ WebContentsFromIds(render_process_id, render_frame_id), stream_type,
+ nullptr)) {
+ handler->UpdateMediaRequestState(render_process_id, render_frame_id,
+ page_request_id, stream_type, state);
+ break;
+ }
+ }
+
+#if defined(OS_CHROMEOS)
+ if (IsOriginForCasting(security_origin) &&
+ blink::IsVideoInputMediaType(stream_type)) {
+ // Notify ash that casting state has changed.
+ if (state == content::MEDIA_REQUEST_STATE_DONE) {
+ ash::Shell::Get()->OnCastingSessionStartedOrStopped(true);
+ } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ ash::Shell::Get()->OnCastingSessionStartedOrStopped(false);
+ }
+ }
+#endif
+
+ for (auto& observer : observers_) {
+ observer.OnRequestUpdate(render_process_id, render_frame_id, stream_type,
+ state);
+ }
+}
+
+void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
+ int render_process_id,
+ int render_frame_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (auto& observer : observers_)
+ observer.OnCreatingAudioStream(render_process_id, render_frame_id);
+}
+
+bool MediaCaptureDevicesDispatcher::IsInsecureCapturingInProgress(
+ int render_process_id,
+ int render_frame_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->IsInsecureCapturingInProgress(render_process_id,
+ render_frame_id))
+ return true;
+ }
+ return false;
+}
+
+void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
+ const MediaStreamDevices& devices) {
+ test_audio_devices_ = devices;
+}
+
+void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
+ const MediaStreamDevices& devices) {
+ test_video_devices_ = devices;
+}
+
+void MediaCaptureDevicesDispatcher::OnSetCapturingLinkSecured(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ if (!blink::IsVideoScreenCaptureMediaType(stream_type))
+ return;
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::UpdateVideoScreenCaptureStatus,
+ base::Unretained(this), render_process_id, render_frame_id,
+ page_request_id, stream_type, is_secure));
+}
+
+void MediaCaptureDevicesDispatcher::UpdateVideoScreenCaptureStatus(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(blink::IsVideoScreenCaptureMediaType(stream_type));
+
+ for (const auto& handler : media_access_handlers_) {
+ handler->UpdateVideoScreenCaptureStatus(render_process_id, render_frame_id,
+ page_request_id, is_secure);
+ break;
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
new file mode 100644
index 00000000000..51f9d9db2b3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
@@ -0,0 +1,209 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "content/public/browser/media_observer.h"
+#include "content/public/browser/media_stream_request.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+
+class MediaAccessHandler;
+class MediaStreamCaptureIndicator;
+class Profile;
+
+namespace extensions {
+class Extension;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// This singleton is used to receive updates about media events from the content
+// layer.
+class MediaCaptureDevicesDispatcher : public content::MediaObserver {
+ public:
+ class Observer {
+ public:
+ // Handle an information update consisting of a up-to-date audio capture
+ // device lists. This happens when a microphone is plugged in or unplugged.
+ virtual void OnUpdateAudioDevices(
+ const blink::MediaStreamDevices& devices) {}
+
+ // Handle an information update consisting of a up-to-date video capture
+ // device lists. This happens when a camera is plugged in or unplugged.
+ virtual void OnUpdateVideoDevices(
+ const blink::MediaStreamDevices& devices) {}
+
+ // Handle an information update related to a media stream request.
+ virtual void OnRequestUpdate(int render_process_id,
+ int render_frame_id,
+ blink::mojom::MediaStreamType stream_type,
+ const content::MediaRequestState state) {}
+
+ // Handle an information update that a new stream is being created.
+ virtual void OnCreatingAudioStream(int render_process_id,
+ int render_frame_id) {}
+
+ virtual ~Observer() {}
+ };
+
+ static MediaCaptureDevicesDispatcher* GetInstance();
+
+ // Registers the preferences related to Media Stream default devices.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Returns true if the security origin is associated with casting.
+ static bool IsOriginForCasting(const GURL& origin);
+
+ // Methods for observers. Called on UI thread.
+ // Observers should add themselves on construction and remove themselves
+ // on destruction.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+ const blink::MediaStreamDevices& GetAudioCaptureDevices();
+ const blink::MediaStreamDevices& GetVideoCaptureDevices();
+
+ // Method called from WebCapturerDelegate implementations to process access
+ // requests. |extension| is set to NULL if request was made from a drive-by
+ // page.
+ void ProcessMediaAccessRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension);
+
+ // Method called from WebCapturerDelegate implementations to check media
+ // access permission. Note that this does not query the user.
+ bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type);
+
+ // Same as above but for an |extension|, which may not be NULL.
+ bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension);
+
+ // Helper to get the default devices which can be used by the media request.
+ // Uses the first available devices if the default devices are not available.
+ // If the return list is empty, it means there is no available device on the
+ // OS.
+ // Called on the UI thread.
+ void GetDefaultDevicesForProfile(Profile* profile,
+ bool audio,
+ bool video,
+ blink::MediaStreamDevices* devices);
+
+ // Helper to get default device IDs. If the returned value is an empty string,
+ // it means that there is no default device for the given device |type|. The
+ // only supported |type| values are
+ // blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE and
+ // blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE.
+ // Must be called on the UI thread.
+ std::string GetDefaultDeviceIDForProfile(Profile* profile,
+ blink::mojom::MediaStreamType type);
+
+ // Helpers for picking particular requested devices, identified by raw id.
+ // If the device requested is not available it will return NULL.
+ const blink::MediaStreamDevice* GetRequestedAudioDevice(
+ const std::string& requested_audio_device_id);
+ const blink::MediaStreamDevice* GetRequestedVideoDevice(
+ const std::string& requested_video_device_id);
+
+ // Returns the first available audio or video device, or NULL if no devices
+ // are available.
+ const blink::MediaStreamDevice* GetFirstAvailableAudioDevice();
+ const blink::MediaStreamDevice* GetFirstAvailableVideoDevice();
+
+ // Unittests that do not require actual device enumeration should call this
+ // API on the singleton. It is safe to call this multiple times on the
+ // signleton.
+ void DisableDeviceEnumerationForTesting();
+
+ // Overridden from content::MediaObserver:
+ void OnAudioCaptureDevicesChanged() override;
+ void OnVideoCaptureDevicesChanged() override;
+ void OnMediaRequestStateChanged(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+ void OnCreatingAudioStream(int render_process_id,
+ int render_frame_id) override;
+ void OnSetCapturingLinkSecured(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure) override;
+
+ scoped_refptr<MediaStreamCaptureIndicator> GetMediaStreamCaptureIndicator();
+
+ // Return true if there is any ongoing insecured capturing. The capturing is
+ // deemed secure if all connected video sinks are reported secure and the
+ // extension is trusted.
+ bool IsInsecureCapturingInProgress(int render_process_id,
+ int render_frame_id);
+
+ // Only for testing.
+ void SetTestAudioCaptureDevices(const blink::MediaStreamDevices& devices);
+ void SetTestVideoCaptureDevices(const blink::MediaStreamDevices& devices);
+
+ private:
+ friend struct base::DefaultSingletonTraits<MediaCaptureDevicesDispatcher>;
+
+ MediaCaptureDevicesDispatcher();
+ ~MediaCaptureDevicesDispatcher() override;
+
+ // Called by the MediaObserver() functions, executed on UI thread.
+ void NotifyAudioDevicesChangedOnUIThread();
+ void NotifyVideoDevicesChangedOnUIThread();
+ void UpdateMediaRequestStateOnUIThread(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state);
+ void OnCreatingAudioStreamOnUIThread(int render_process_id,
+ int render_frame_id);
+ void UpdateVideoScreenCaptureStatus(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure);
+
+ // Only for testing, a list of cached audio capture devices.
+ blink::MediaStreamDevices test_audio_devices_;
+
+ // Only for testing, a list of cached video capture devices.
+ blink::MediaStreamDevices test_video_devices_;
+
+ // A list of observers for the device update notifications.
+ base::ObserverList<Observer>::Unchecked observers_;
+
+ // Flag used by unittests to disable device enumeration.
+ bool is_device_enumeration_disabled_;
+
+ scoped_refptr<MediaStreamCaptureIndicator> media_stream_capture_indicator_;
+
+ // Handlers for processing media access requests.
+ std::vector<std::unique_ptr<MediaAccessHandler>> media_access_handlers_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaCaptureDevicesDispatcher);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
new file mode 100644
index 00000000000..8cda295cb72
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
@@ -0,0 +1,470 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
+#include "chrome/browser/status_icons/status_icon.h"
+#include "chrome/browser/status_icons/status_tray.h"
+#include "chrome/browser/tab_contents/tab_util.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ui/gfx/image/image_skia.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/grit/chromium_strings.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/paint_vector_icon.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#endif
+
+using content::BrowserThread;
+using content::WebContents;
+
+namespace {
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+const extensions::Extension* GetExtension(WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!web_contents)
+ return NULL;
+
+ extensions::ExtensionRegistry* registry =
+ extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext());
+ return registry->enabled_extensions().GetExtensionOrAppByURL(
+ web_contents->GetURL());
+}
+
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+base::string16 GetTitle(WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!web_contents)
+ return base::string16();
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ const extensions::Extension* const extension = GetExtension(web_contents);
+ if (extension)
+ return base::UTF8ToUTF16(extension->name());
+#endif
+
+ return url_formatter::FormatUrlForSecurityDisplay(web_contents->GetURL());
+}
+
+} // namespace
+
+// Stores usage counts for all the capture devices associated with a single
+// WebContents instance. Instances of this class are owned by
+// MediaStreamCaptureIndicator. They also observe for the destruction of their
+// corresponding WebContents and trigger their own deletion from their
+// MediaStreamCaptureIndicator.
+class MediaStreamCaptureIndicator::WebContentsDeviceUsage
+ : public content::WebContentsObserver {
+ public:
+ WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,
+ WebContents* web_contents)
+ : WebContentsObserver(web_contents), indicator_(std::move(indicator)) {}
+
+ bool IsCapturingAudio() const { return audio_stream_count_ > 0; }
+ bool IsCapturingVideo() const { return video_stream_count_ > 0; }
+ bool IsMirroring() const { return mirroring_stream_count_ > 0; }
+ bool IsCapturingDesktop() const { return desktop_stream_count_ > 0; }
+
+ std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui);
+
+ // Increment ref-counts up based on the type of each device provided.
+ void AddDevices(const blink::MediaStreamDevices& devices,
+ base::OnceClosure stop_callback);
+
+ // Decrement ref-counts up based on the type of each device provided.
+ void RemoveDevices(const blink::MediaStreamDevices& devices);
+
+ // Helper to call |stop_callback_|.
+ void NotifyStopped();
+
+ private:
+ int& GetStreamCount(blink::mojom::MediaStreamType type);
+
+ // content::WebContentsObserver overrides.
+ void WebContentsDestroyed() override {
+ indicator_->UnregisterWebContents(web_contents());
+ }
+
+ scoped_refptr<MediaStreamCaptureIndicator> indicator_;
+ int audio_stream_count_ = 0;
+ int video_stream_count_ = 0;
+ int mirroring_stream_count_ = 0;
+ int desktop_stream_count_ = 0;
+
+ base::OnceClosure stop_callback_;
+ base::WeakPtrFactory<WebContentsDeviceUsage> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
+};
+
+// Implements MediaStreamUI interface. Instances of this class are created for
+// each MediaStream and their ownership is passed to MediaStream implementation
+// in the content layer. Each UIDelegate keeps a weak pointer to the
+// corresponding WebContentsDeviceUsage object to deliver updates about state of
+// the stream.
+class MediaStreamCaptureIndicator::UIDelegate : public content::MediaStreamUI {
+ public:
+ UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<::MediaStreamUI> ui)
+ : device_usage_(device_usage), devices_(devices), ui_(std::move(ui)) {
+ DCHECK(!devices_.empty());
+ }
+
+ ~UIDelegate() override {
+ if (started_ && device_usage_)
+ device_usage_->RemoveDevices(devices_);
+ }
+
+ private:
+ // content::MediaStreamUI interface.
+ gfx::NativeViewId OnStarted(
+ base::OnceClosure stop_callback,
+ content::MediaStreamUI::SourceCallback source_callback) override {
+ DCHECK(!started_);
+ started_ = true;
+
+ if (device_usage_) {
+ // |device_usage_| handles |stop_callback| when |ui_| is unspecified.
+ device_usage_->AddDevices(
+ devices_, ui_ ? base::OnceClosure() : std::move(stop_callback));
+ }
+
+ // If a custom |ui_| is specified, notify it that the stream started and let
+ // it handle the |stop_callback| and |source_callback|.
+ if (ui_)
+ return ui_->OnStarted(std::move(stop_callback),
+ std::move(source_callback));
+
+ return 0;
+ }
+
+ base::WeakPtr<WebContentsDeviceUsage> device_usage_;
+ const blink::MediaStreamDevices devices_;
+ const std::unique_ptr<::MediaStreamUI> ui_;
+ bool started_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(UIDelegate);
+};
+
+std::unique_ptr<content::MediaStreamUI>
+MediaStreamCaptureIndicator::WebContentsDeviceUsage::RegisterMediaStream(
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui) {
+ return std::make_unique<UIDelegate>(weak_factory_.GetWeakPtr(), devices,
+ std::move(ui));
+}
+
+void MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices(
+ const blink::MediaStreamDevices& devices,
+ base::OnceClosure stop_callback) {
+ for (const auto& device : devices)
+ ++GetStreamCount(device.type);
+
+ if (web_contents()) {
+ stop_callback_ = std::move(stop_callback);
+ web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
+ }
+
+ indicator_->UpdateNotificationUserInterface();
+}
+
+void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices(
+ const blink::MediaStreamDevices& devices) {
+ for (const auto& device : devices) {
+ int& stream_count = GetStreamCount(device.type);
+ --stream_count;
+ DCHECK_GE(stream_count, 0);
+ }
+
+ if (web_contents()) {
+ web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
+ content_settings::UpdateLocationBarUiForWebContents(web_contents());
+ }
+
+ indicator_->UpdateNotificationUserInterface();
+}
+
+void MediaStreamCaptureIndicator::WebContentsDeviceUsage::NotifyStopped() {
+ if (stop_callback_)
+ std::move(stop_callback_).Run();
+}
+
+int& MediaStreamCaptureIndicator::WebContentsDeviceUsage::GetStreamCount(
+ blink::mojom::MediaStreamType type) {
+ switch (type) {
+ case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
+ return audio_stream_count_;
+
+ case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
+ return video_stream_count_;
+
+ case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
+ case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
+ return mirroring_stream_count_;
+
+ case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
+ case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
+ case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
+ case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
+ return desktop_stream_count_;
+
+ case blink::mojom::MediaStreamType::NO_SERVICE:
+ case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
+ NOTREACHED();
+ return video_stream_count_;
+ }
+}
+
+MediaStreamCaptureIndicator::MediaStreamCaptureIndicator() {}
+
+MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
+ // The user is responsible for cleaning up by reporting the closure of any
+ // opened devices. However, there exists a race condition at shutdown: The UI
+ // thread may be stopped before CaptureDevicesClosed() posts the task to
+ // invoke DoDevicesClosedOnUIThread(). In this case, usage_map_ won't be
+ // empty like it should.
+ DCHECK(usage_map_.empty() ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+}
+
+std::unique_ptr<content::MediaStreamUI>
+MediaStreamCaptureIndicator::RegisterMediaStream(
+ content::WebContents* web_contents,
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui) {
+ auto& usage = usage_map_[web_contents];
+ if (!usage)
+ usage = std::make_unique<WebContentsDeviceUsage>(this, web_contents);
+
+ return usage->RegisterMediaStream(devices, std::move(ui));
+}
+
+void MediaStreamCaptureIndicator::ExecuteCommand(int command_id,
+ int event_flags) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const int index =
+ command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
+ DCHECK_LE(0, index);
+ DCHECK_GT(static_cast<int>(command_targets_.size()), index);
+ WebContents* web_contents = command_targets_[index];
+ if (base::Contains(usage_map_, web_contents))
+ web_contents->GetDelegate()->ActivateContents(web_contents);
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() &&
+ (it->second->IsCapturingAudio() || it->second->IsCapturingVideo());
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingVideo(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsCapturingVideo();
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingAudio(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsCapturingAudio();
+}
+
+bool MediaStreamCaptureIndicator::IsBeingMirrored(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsMirroring();
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingDesktop(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsCapturingDesktop();
+}
+
+void MediaStreamCaptureIndicator::NotifyStopped(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ DCHECK(it != usage_map_.end());
+ it->second->NotifyStopped();
+}
+
+void MediaStreamCaptureIndicator::UnregisterWebContents(
+ WebContents* web_contents) {
+ usage_map_.erase(web_contents);
+ UpdateNotificationUserInterface();
+}
+
+void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon(bool audio,
+ bool video) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (status_icon_)
+ return;
+
+ // If there is no browser process, we should not create the status tray.
+ if (!g_browser_process)
+ return;
+
+ StatusTray* status_tray = g_browser_process->status_tray();
+ if (!status_tray)
+ return;
+
+ gfx::ImageSkia image;
+ base::string16 tool_tip;
+ GetStatusTrayIconInfo(audio, video, &image, &tool_tip);
+ DCHECK(!image.isNull());
+ DCHECK(!tool_tip.empty());
+
+ status_icon_ = status_tray->CreateStatusIcon(
+ StatusTray::MEDIA_STREAM_CAPTURE_ICON, image, tool_tip);
+}
+
+void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!status_icon_)
+ return;
+
+ // If there is no browser process, we should not do anything.
+ if (!g_browser_process)
+ return;
+
+ StatusTray* status_tray = g_browser_process->status_tray();
+ if (status_tray != NULL) {
+ status_tray->RemoveStatusIcon(status_icon_);
+ status_icon_ = NULL;
+ }
+}
+
+void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ std::unique_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
+ bool audio = false;
+ bool video = false;
+ int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
+ command_targets_.clear();
+
+ for (const auto& it : usage_map_) {
+ // Check if any audio and video devices have been used.
+ const WebContentsDeviceUsage& usage = *it.second;
+ if (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())
+ continue;
+
+ WebContents* const web_contents = it.first;
+
+ // The audio/video icon is shown only for non-whitelisted extensions or on
+ // Android. For regular tabs on desktop, we show an indicator in the tab
+ // icon.
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ const extensions::Extension* extension = GetExtension(web_contents);
+ if (!extension)
+ continue;
+#endif
+
+ audio = audio || usage.IsCapturingAudio();
+ video = video || usage.IsCapturingVideo();
+
+ command_targets_.push_back(web_contents);
+ menu->AddItem(command_id, GetTitle(web_contents));
+
+ // If the menu item is not a label, enable it.
+ menu->SetCommandIdEnabled(command_id, command_id != IDC_MinimumLabelValue);
+
+ // If reaching the maximum number, no more item will be added to the menu.
+ if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
+ break;
+ ++command_id;
+ }
+
+ if (command_targets_.empty()) {
+ MaybeDestroyStatusTrayIcon();
+ return;
+ }
+
+ // The icon will take the ownership of the passed context menu.
+ MaybeCreateStatusTrayIcon(audio, video);
+ if (status_icon_) {
+ status_icon_->SetContextMenu(std::move(menu));
+ }
+}
+
+void MediaStreamCaptureIndicator::GetStatusTrayIconInfo(
+ bool audio,
+ bool video,
+ gfx::ImageSkia* image,
+ base::string16* tool_tip) {
+#if defined(OS_ANDROID)
+ NOTREACHED();
+#else // !defined(OS_ANDROID)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(audio || video);
+ DCHECK(image);
+ DCHECK(tool_tip);
+
+ int message_id = 0;
+ const gfx::VectorIcon* icon = nullptr;
+ if (audio && video) {
+ message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
+ icon = &vector_icons::kVideocamIcon;
+ } else if (audio && !video) {
+ message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
+ icon = &vector_icons::kMicIcon;
+ } else if (!audio && video) {
+ message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
+ icon = &vector_icons::kVideocamIcon;
+ }
+
+ *tool_tip = l10n_util::GetStringUTF16(message_id);
+ *image = gfx::CreateVectorIcon(*icon, 16, gfx::kChromeIconGrey);
+#endif // !defined(OS_ANDROID)
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h
new file mode 100644
index 00000000000..29591788429
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_CAPTURE_INDICATOR_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_CAPTURE_INDICATOR_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/browser/status_icons/status_icon_menu_model.h"
+#include "content/public/browser/media_stream_request.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace gfx {
+class ImageSkia;
+} // namespace gfx
+
+class StatusIcon;
+
+// Interface to display custom UI during stream capture.
+class MediaStreamUI {
+ public:
+ // Called when stream capture is stopped.
+ virtual ~MediaStreamUI() = default;
+
+ // Called when stream capture starts.
+ // |stop_callback| is a callback to stop the stream.
+ // |source_callback| is a callback to change the desktop capture source.
+ // Returns the platform-dependent window ID for the UI, or 0 if not
+ // applicable.
+ virtual gfx::NativeViewId OnStarted(
+ base::OnceClosure stop_callback,
+ content::MediaStreamUI::SourceCallback source_callback) = 0;
+};
+
+// Keeps track of which WebContents are capturing media streams. Used to display
+// indicators (e.g. in the tab strip, via notifications) and to make resource
+// allocation decisions (e.g. WebContents capturing streams are not discarded).
+//
+// Owned by MediaCaptureDevicesDispatcher, which is a singleton.
+class MediaStreamCaptureIndicator
+ : public base::RefCountedThreadSafe<MediaStreamCaptureIndicator>,
+ public StatusIconMenuModel::Delegate {
+ public:
+ MediaStreamCaptureIndicator();
+
+ // Registers a new media stream for |web_contents| and returns an object used
+ // by the content layer to notify about the state of the stream. Optionally,
+ // |ui| is used to display custom UI while the stream is captured.
+ std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
+ content::WebContents* web_contents,
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui = nullptr);
+
+ // Overrides from StatusIconMenuModel::Delegate implementation.
+ void ExecuteCommand(int command_id, int event_flags) override;
+
+ // Returns true if |web_contents| is capturing user media (e.g., webcam or
+ // microphone input).
+ bool IsCapturingUserMedia(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| is capturing video (e.g., webcam).
+ bool IsCapturingVideo(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| is capturing audio (e.g., microphone).
+ bool IsCapturingAudio(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| itself is being mirrored (e.g., a source of
+ // media for remote broadcast).
+ bool IsBeingMirrored(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| is capturing the desktop (screen, window,
+ // audio).
+ bool IsCapturingDesktop(content::WebContents* web_contents) const;
+
+ // Called when STOP button in media capture notification is clicked.
+ void NotifyStopped(content::WebContents* web_contents) const;
+
+ private:
+ class UIDelegate;
+ class WebContentsDeviceUsage;
+ friend class WebContentsDeviceUsage;
+
+ friend class base::RefCountedThreadSafe<MediaStreamCaptureIndicator>;
+ ~MediaStreamCaptureIndicator() override;
+
+ // Following functions/variables are executed/accessed only on UI thread.
+
+ // Called by WebContentsDeviceUsage when it's about to destroy itself, i.e.
+ // when WebContents is being destroyed.
+ void UnregisterWebContents(content::WebContents* web_contents);
+
+ // Updates the status tray menu. Called by WebContentsDeviceUsage.
+ void UpdateNotificationUserInterface();
+
+ // Helpers to create and destroy status tray icon. Called from
+ // UpdateNotificationUserInterface().
+ void EnsureStatusTrayIconResources();
+ void MaybeCreateStatusTrayIcon(bool audio, bool video);
+ void MaybeDestroyStatusTrayIcon();
+
+ // Gets the status icon image and the string to use as the tooltip.
+ void GetStatusTrayIconInfo(bool audio,
+ bool video,
+ gfx::ImageSkia* image,
+ base::string16* tool_tip);
+
+ // Reference to our status icon - owned by the StatusTray. If null,
+ // the platform doesn't support status icons.
+ StatusIcon* status_icon_ = nullptr;
+
+ // A map that contains the usage counts of the opened capture devices for each
+ // WebContents instance.
+ std::unordered_map<content::WebContents*,
+ std::unique_ptr<WebContentsDeviceUsage>>
+ usage_map_;
+
+ // A vector which maps command IDs to their associated WebContents
+ // instance. This is rebuilt each time the status tray icon context menu is
+ // updated.
+ typedef std::vector<content::WebContents*> CommandTargets;
+ CommandTargets command_targets_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamCaptureIndicator);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_CAPTURE_INDICATOR_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
new file mode 100644
index 00000000000..b64eea23ca6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
@@ -0,0 +1,101 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/media_stream_device_permission_context.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
+
+namespace {
+
+blink::mojom::FeaturePolicyFeature GetFeaturePolicyFeature(
+ ContentSettingsType type) {
+ if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
+ return blink::mojom::FeaturePolicyFeature::kMicrophone;
+
+ DCHECK_EQ(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, type);
+ return blink::mojom::FeaturePolicyFeature::kCamera;
+}
+
+} // namespace
+
+MediaStreamDevicePermissionContext::MediaStreamDevicePermissionContext(
+ Profile* profile,
+ const ContentSettingsType content_settings_type)
+ : PermissionContextBase(profile,
+ content_settings_type,
+ GetFeaturePolicyFeature(content_settings_type)),
+ content_settings_type_(content_settings_type) {
+ DCHECK(content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
+ content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+}
+
+MediaStreamDevicePermissionContext::~MediaStreamDevicePermissionContext() {}
+
+void MediaStreamDevicePermissionContext::DecidePermission(
+ content::WebContents* web_contents,
+ const PermissionRequestID& id,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin,
+ bool user_gesture,
+ BrowserPermissionCallback callback) {
+ PermissionContextBase::DecidePermission(web_contents, id, requesting_origin,
+ embedding_origin, user_gesture,
+ std::move(callback));
+}
+
+ContentSetting MediaStreamDevicePermissionContext::GetPermissionStatusInternal(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin) const {
+ // TODO(raymes): Merge this policy check into content settings
+ // crbug.com/244389.
+ const char* policy_name = nullptr;
+ const char* urls_policy_name = nullptr;
+ if (content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
+ policy_name = prefs::kAudioCaptureAllowed;
+ urls_policy_name = prefs::kAudioCaptureAllowedUrls;
+ } else {
+ DCHECK(content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ policy_name = prefs::kVideoCaptureAllowed;
+ urls_policy_name = prefs::kVideoCaptureAllowedUrls;
+ }
+
+ MediaStreamDevicePolicy policy = GetDevicePolicy(
+ profile(), requesting_origin, policy_name, urls_policy_name);
+
+ switch (policy) {
+ case ALWAYS_DENY:
+ return CONTENT_SETTING_BLOCK;
+ case ALWAYS_ALLOW:
+ return CONTENT_SETTING_ALLOW;
+ default:
+ DCHECK_EQ(POLICY_NOT_SET, policy);
+ }
+
+ // Check the content setting. TODO(raymes): currently mic/camera permission
+ // doesn't consider the embedder.
+ ContentSetting setting = PermissionContextBase::GetPermissionStatusInternal(
+ render_frame_host, requesting_origin, requesting_origin);
+
+ if (setting == CONTENT_SETTING_DEFAULT)
+ setting = CONTENT_SETTING_ASK;
+
+ return setting;
+}
+
+void MediaStreamDevicePermissionContext::ResetPermission(
+ const GURL& requesting_origin,
+ const GURL& embedding_origin) {
+ NOTREACHED() << "ResetPermission is not implemented";
+}
+
+bool MediaStreamDevicePermissionContext::IsRestrictedToSecureOrigins() const {
+ return true;
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h
new file mode 100644
index 00000000000..9a1983462d9
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSION_CONTEXT_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSION_CONTEXT_H_
+
+#include "base/macros.h"
+#include "chrome/browser/permissions/permission_context_base.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+
+// Common class which handles the mic and camera permissions.
+class MediaStreamDevicePermissionContext : public PermissionContextBase {
+ public:
+ MediaStreamDevicePermissionContext(Profile* profile,
+ ContentSettingsType content_settings_type);
+ ~MediaStreamDevicePermissionContext() override;
+
+ // PermissionContextBase:
+ void DecidePermission(content::WebContents* web_contents,
+ const PermissionRequestID& id,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin,
+ bool user_gesture,
+ BrowserPermissionCallback callback) override;
+
+ // TODO(xhwang): GURL.GetOrigin() shouldn't be used as the origin. Need to
+ // refactor to use url::Origin. crbug.com/527149 is filed for this.
+ ContentSetting GetPermissionStatusInternal(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin) const override;
+
+ void ResetPermission(const GURL& requesting_origin,
+ const GURL& embedding_origin) override;
+
+ private:
+ // PermissionContextBase:
+ bool IsRestrictedToSecureOrigins() const override;
+
+ ContentSettingsType content_settings_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicePermissionContext);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSION_CONTEXT_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
new file mode 100644
index 00000000000..7dbaaf09cbb
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/media_stream_device_permission_context.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/permissions/permission_request_id.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/permissions/permission_request_manager.h"
+#endif
+
+namespace {
+class TestPermissionContext : public MediaStreamDevicePermissionContext {
+ public:
+ TestPermissionContext(Profile* profile,
+ const ContentSettingsType content_settings_type)
+ : MediaStreamDevicePermissionContext(profile, content_settings_type) {}
+
+ ~TestPermissionContext() override {}
+};
+
+} // anonymous namespace
+
+// TODO(raymes): many tests in MediaStreamDevicesControllerTest should be
+// converted to tests in this file.
+class MediaStreamDevicePermissionContextTests
+ : public ChromeRenderViewHostTestHarness {
+ protected:
+ MediaStreamDevicePermissionContextTests() = default;
+
+ void TestInsecureQueryingUrl(ContentSettingsType content_settings_type) {
+ TestPermissionContext permission_context(profile(), content_settings_type);
+ GURL insecure_url("http://www.example.com");
+ GURL secure_url("https://www.example.com");
+
+ // Check that there is no saved content settings.
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(insecure_url.GetOrigin(),
+ insecure_url.GetOrigin(),
+ content_settings_type, std::string()));
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(secure_url.GetOrigin(),
+ insecure_url.GetOrigin(),
+ content_settings_type, std::string()));
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(insecure_url.GetOrigin(),
+ secure_url.GetOrigin(),
+ content_settings_type, std::string()));
+
+ EXPECT_EQ(CONTENT_SETTING_BLOCK,
+ permission_context
+ .GetPermissionStatus(nullptr /* render_frame_host */,
+ insecure_url, insecure_url)
+ .content_setting);
+
+ EXPECT_EQ(CONTENT_SETTING_BLOCK,
+ permission_context
+ .GetPermissionStatus(nullptr /* render_frame_host */,
+ insecure_url, secure_url)
+ .content_setting);
+ }
+
+ void TestSecureQueryingUrl(ContentSettingsType content_settings_type) {
+ TestPermissionContext permission_context(profile(), content_settings_type);
+ GURL secure_url("https://www.example.com");
+
+ // Check that there is no saved content settings.
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(secure_url.GetOrigin(),
+ secure_url.GetOrigin(),
+ content_settings_type,
+ std::string()));
+
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ permission_context
+ .GetPermissionStatus(nullptr /* render_frame_host */,
+ secure_url, secure_url)
+ .content_setting);
+ }
+
+ private:
+ // ChromeRenderViewHostTestHarness:
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+#if defined(OS_ANDROID)
+ InfoBarService::CreateForWebContents(web_contents());
+#else
+ PermissionRequestManager::CreateForWebContents(web_contents());
+#endif
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicePermissionContextTests);
+};
+
+// MEDIASTREAM_MIC permission status should be ask for insecure origin to
+// accommodate the usage case of Flash.
+TEST_F(MediaStreamDevicePermissionContextTests, TestMicInsecureQueryingUrl) {
+ TestInsecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+}
+
+// MEDIASTREAM_CAMERA permission status should be ask for insecure origin to
+// accommodate the usage case of Flash.
+TEST_F(MediaStreamDevicePermissionContextTests, TestCameraInsecureQueryingUrl) {
+ TestInsecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+}
+
+// MEDIASTREAM_MIC permission status should be ask for Secure origin.
+TEST_F(MediaStreamDevicePermissionContextTests, TestMicSecureQueryingUrl) {
+ TestSecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+}
+
+// MEDIASTREAM_CAMERA permission status should be ask for Secure origin.
+TEST_F(MediaStreamDevicePermissionContextTests, TestCameraSecureQueryingUrl) {
+ TestSecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc
new file mode 100644
index 00000000000..a1aaed06c81
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+
+#include <stddef.h>
+
+#include "base/values.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/origin_util.h"
+#include "extensions/common/constants.h"
+#include "url/gurl.h"
+
+MediaStreamDevicePolicy GetDevicePolicy(const Profile* profile,
+ const GURL& security_origin,
+ const char* policy_name,
+ const char* whitelist_policy_name) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // If the security origin policy matches a value in the whitelist, allow it.
+ // Otherwise, check the |policy_name| master switch for the default behavior.
+
+ const PrefService* prefs = profile->GetPrefs();
+
+ const base::ListValue* list = prefs->GetList(whitelist_policy_name);
+ std::string value;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (list->GetString(i, &value)) {
+ ContentSettingsPattern pattern =
+ ContentSettingsPattern::FromString(value);
+ if (pattern == ContentSettingsPattern::Wildcard()) {
+ DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value;
+ continue;
+ }
+ DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value;
+ if (pattern.IsValid() && pattern.Matches(security_origin))
+ return ALWAYS_ALLOW;
+ }
+ }
+
+ // If a match was not found, check if audio capture is otherwise disallowed
+ // or if the user should be prompted. Setting the policy value to "true"
+ // is equal to not setting it at all, so from hereon out, we will return
+ // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access).
+ if (!prefs->GetBoolean(policy_name))
+ return ALWAYS_DENY;
+
+ return POLICY_NOT_SET;
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h
new file mode 100644
index 00000000000..5bfbb9e26a5
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSIONS_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSIONS_H_
+
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+
+class GURL;
+class Profile;
+
+enum MediaStreamDevicePolicy {
+ POLICY_NOT_SET,
+ ALWAYS_DENY,
+ ALWAYS_ALLOW,
+};
+
+// Get the device policy for |security_origin| and |profile|.
+MediaStreamDevicePolicy GetDevicePolicy(const Profile* profile,
+ const GURL& security_origin,
+ const char* policy_name,
+ const char* whitelist_policy_name);
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSIONS_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc
new file mode 100644
index 00000000000..cddfd5b1b4d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc
@@ -0,0 +1,605 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/permissions/permission_manager.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/permissions/permission_result.h"
+#include "chrome/browser/permissions/permission_uma_util.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/origin_util.h"
+#include "extensions/common/constants.h"
+#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/browser/android/preferences/pref_service_bridge.h"
+#include "chrome/browser/permissions/permission_dialog_delegate.h"
+#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
+#include "ui/android/window_android.h"
+#else // !defined(OS_ANDROID)
+#include "components/vector_icons/vector_icons.h"
+#endif
+
+using content::BrowserThread;
+
+namespace {
+
+// Returns true if the given ContentSettingsType is being requested in
+// |request|.
+bool ContentTypeIsRequested(ContentSettingsType type,
+ const content::MediaStreamRequest& request) {
+ if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
+ return request.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
+
+ if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA)
+ return request.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
+
+ return false;
+}
+
+bool HasAvailableDevices(ContentSettingsType content_type,
+ const std::string& device_id) {
+ const blink::MediaStreamDevices* devices = nullptr;
+ if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
+ devices =
+ &MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
+ } else if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
+ devices =
+ &MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
+ } else {
+ NOTREACHED();
+ }
+
+ // TODO(tommi): It's kind of strange to have this here since if we fail this
+ // test, there'll be a UI shown that indicates to the user that access to
+ // non-existing audio/video devices has been denied. The user won't have
+ // any way to change that but there will be a UI shown which indicates that
+ // access is blocked.
+ if (devices->empty())
+ return false;
+
+ // Note: we check device_id before dereferencing devices. If the requested
+ // device id is non-empty, then the corresponding device list must not be
+ // NULL.
+ if (!device_id.empty()) {
+ auto it = std::find_if(devices->begin(), devices->end(),
+ [device_id](const blink::MediaStreamDevice& device) {
+ return device.id == device_id;
+ });
+ if (it == devices->end())
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+// static
+void MediaStreamDevicesController::RequestPermissions(
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback) {
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
+ request.render_process_id, request.render_frame_id);
+ // The RFH may have been destroyed by the time the request is processed.
+ if (!rfh) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ std::unique_ptr<content::MediaStreamUI>());
+ return;
+ }
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(rfh);
+ std::unique_ptr<MediaStreamDevicesController> controller(
+ new MediaStreamDevicesController(web_contents, request,
+ std::move(callback)));
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ std::vector<ContentSettingsType> content_settings_types;
+
+ PermissionManager* permission_manager = PermissionManager::Get(profile);
+ bool will_prompt_for_audio = false;
+ bool will_prompt_for_video = false;
+
+ if (controller->ShouldRequestAudio()) {
+ PermissionResult permission_status =
+ permission_manager->GetPermissionStatusForFrame(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, rfh,
+ request.security_origin);
+ if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+ controller->denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ controller->RunCallback(permission_status.source ==
+ PermissionStatusSource::FEATURE_POLICY);
+ return;
+ }
+
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+ will_prompt_for_audio =
+ permission_status.content_setting == CONTENT_SETTING_ASK;
+ }
+ if (controller->ShouldRequestVideo()) {
+ PermissionResult permission_status =
+ permission_manager->GetPermissionStatusForFrame(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, rfh,
+ request.security_origin);
+ if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+ controller->denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ controller->RunCallback(permission_status.source ==
+ PermissionStatusSource::FEATURE_POLICY);
+ return;
+ }
+
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ will_prompt_for_video =
+ permission_status.content_setting == CONTENT_SETTING_ASK;
+ }
+
+ permission_manager->RequestPermissions(
+ content_settings_types, rfh, request.security_origin,
+ request.user_gesture,
+ base::Bind(
+ &MediaStreamDevicesController::RequestAndroidPermissionsIfNeeded,
+ web_contents, base::Passed(&controller), will_prompt_for_audio,
+ will_prompt_for_video));
+}
+
+void MediaStreamDevicesController::RequestAndroidPermissionsIfNeeded(
+ content::WebContents* web_contents,
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ bool did_prompt_for_audio,
+ bool did_prompt_for_video,
+ const std::vector<ContentSetting>& responses) {
+#if defined(OS_ANDROID)
+ // If either audio or video was previously allowed and Chrome no longer has
+ // the necessary permissions, show a infobar to attempt to address this
+ // mismatch.
+ std::vector<ContentSettingsType> content_settings_types;
+ // The audio setting will always be the first one in the vector, if it was
+ // requested.
+ // If the user was already prompted for mic (|did_prompt_for_audio| flag), we
+ // would have requested Android permission at that point.
+ if (!did_prompt_for_audio &&
+ controller->ShouldRequestAudio() &&
+ responses.front() == CONTENT_SETTING_ALLOW) {
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+ }
+
+ // If the user was already prompted for camera (|did_prompt_for_video| flag),
+ // we would have requested Android permission at that point.
+ if (!did_prompt_for_video &&
+ controller->ShouldRequestVideo() &&
+ responses.back() == CONTENT_SETTING_ALLOW) {
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ }
+ if (content_settings_types.empty()) {
+ controller->PromptAnsweredGroupedRequest(responses);
+ return;
+ }
+
+ ShowPermissionInfoBarState show_permission_infobar_state =
+ PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfoBar(
+ web_contents, content_settings_types);
+ switch (show_permission_infobar_state) {
+ case ShowPermissionInfoBarState::NO_NEED_TO_SHOW_PERMISSION_INFOBAR:
+ controller->PromptAnsweredGroupedRequest(responses);
+ return;
+ case ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR:
+ PermissionUpdateInfoBarDelegate::Create(
+ web_contents, content_settings_types,
+ base::BindOnce(&MediaStreamDevicesController::AndroidOSPromptAnswered,
+ base::Passed(&controller), responses));
+ return;
+ case ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR: {
+ std::vector<ContentSetting> blocked_responses(responses.size(),
+ CONTENT_SETTING_BLOCK);
+ controller->PromptAnsweredGroupedRequest(blocked_responses);
+ return;
+ }
+ }
+
+ NOTREACHED() << "Unknown show permission infobar state.";
+#else
+ controller->PromptAnsweredGroupedRequest(responses);
+#endif
+}
+
+#if defined(OS_ANDROID)
+// static
+void MediaStreamDevicesController::AndroidOSPromptAnswered(
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ std::vector<ContentSetting> responses,
+ bool android_prompt_granted) {
+ if (!android_prompt_granted) {
+ // Only permissions that were previously ALLOW for a site will have had
+ // their android permissions requested. It's only in that case that we need
+ // to change the setting to BLOCK to reflect that it wasn't allowed.
+ for (size_t i = 0; i < responses.size(); ++i) {
+ if (responses[i] == CONTENT_SETTING_ALLOW)
+ responses[i] = CONTENT_SETTING_BLOCK;
+ }
+ }
+
+ controller->PromptAnsweredGroupedRequest(responses);
+}
+#endif // defined(OS_ANDROID)
+
+// static
+void MediaStreamDevicesController::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* prefs) {
+ prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true);
+ prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true);
+ prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls);
+ prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls);
+}
+
+MediaStreamDevicesController::~MediaStreamDevicesController() {
+ if (!callback_.is_null()) {
+ std::move(callback_).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ std::unique_ptr<content::MediaStreamUI>());
+ }
+}
+
+void MediaStreamDevicesController::PromptAnsweredGroupedRequest(
+ const std::vector<ContentSetting>& responses) {
+ // The audio setting will always be the first one in the vector, if it was
+ // requested.
+ bool blocked_by_feature_policy = ShouldRequestAudio() || ShouldRequestVideo();
+ if (ShouldRequestAudio()) {
+ audio_setting_ = responses.front();
+ blocked_by_feature_policy &=
+ audio_setting_ == CONTENT_SETTING_BLOCK &&
+ PermissionIsBlockedForReason(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+ PermissionStatusSource::FEATURE_POLICY);
+ }
+
+ if (ShouldRequestVideo()) {
+ video_setting_ = responses.back();
+ blocked_by_feature_policy &=
+ video_setting_ == CONTENT_SETTING_BLOCK &&
+ PermissionIsBlockedForReason(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ PermissionStatusSource::FEATURE_POLICY);
+ }
+
+ for (ContentSetting response : responses) {
+ if (response == CONTENT_SETTING_BLOCK)
+ denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ else if (response == CONTENT_SETTING_ASK)
+ denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED;
+ }
+
+ RunCallback(blocked_by_feature_policy);
+}
+
+MediaStreamDevicesController::MediaStreamDevicesController(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback)
+ : web_contents_(web_contents),
+ request_(request),
+ callback_(std::move(callback)) {
+ DCHECK(content::IsOriginSecure(request_.security_origin) ||
+ request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
+
+ profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
+
+ denial_reason_ = blink::mojom::MediaStreamRequestResult::OK;
+ audio_setting_ = GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+ request, &denial_reason_);
+ video_setting_ = GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ request, &denial_reason_);
+}
+
+bool MediaStreamDevicesController::ShouldRequestAudio() const {
+ return audio_setting_ == CONTENT_SETTING_ASK;
+}
+
+bool MediaStreamDevicesController::ShouldRequestVideo() const {
+ return video_setting_ == CONTENT_SETTING_ASK;
+}
+
+blink::MediaStreamDevices MediaStreamDevicesController::GetDevices(
+ ContentSetting audio_setting,
+ ContentSetting video_setting) {
+ bool audio_allowed = audio_setting == CONTENT_SETTING_ALLOW;
+ bool video_allowed = video_setting == CONTENT_SETTING_ALLOW;
+
+ if (!audio_allowed && !video_allowed)
+ return blink::MediaStreamDevices();
+
+ blink::MediaStreamDevices devices;
+ switch (request_.request_type) {
+ case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: {
+ const blink::MediaStreamDevice* device = NULL;
+ // For open device request, when requested device_id is empty, pick
+ // the first available of the given type. If requested device_id is
+ // not empty, return the desired device if it's available. Otherwise,
+ // return no device.
+ if (audio_allowed &&
+ request_.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
+ DCHECK_EQ(blink::mojom::MediaStreamType::NO_SERVICE,
+ request_.video_type);
+ if (!request_.requested_audio_device_id.empty()) {
+ device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedAudioDevice(request_.requested_audio_device_id);
+ } else {
+ device = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetFirstAvailableAudioDevice();
+ }
+ } else if (video_allowed &&
+ request_.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
+ DCHECK_EQ(blink::mojom::MediaStreamType::NO_SERVICE,
+ request_.audio_type);
+ // Pepper API opens only one device at a time.
+ if (!request_.requested_video_device_id.empty()) {
+ device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedVideoDevice(request_.requested_video_device_id);
+ } else {
+ device = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetFirstAvailableVideoDevice();
+ }
+ }
+ if (device)
+ devices.push_back(*device);
+ break;
+ }
+ case blink::MEDIA_GENERATE_STREAM: {
+ bool get_default_audio_device = audio_allowed;
+ bool get_default_video_device = video_allowed;
+
+ // Get the exact audio or video device if an id is specified.
+ if (audio_allowed && !request_.requested_audio_device_id.empty()) {
+ const blink::MediaStreamDevice* audio_device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedAudioDevice(request_.requested_audio_device_id);
+ if (audio_device) {
+ devices.push_back(*audio_device);
+ get_default_audio_device = false;
+ }
+ }
+ if (video_allowed && !request_.requested_video_device_id.empty()) {
+ const blink::MediaStreamDevice* video_device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedVideoDevice(request_.requested_video_device_id);
+ if (video_device) {
+ devices.push_back(*video_device);
+ get_default_video_device = false;
+ }
+ }
+
+ // If either or both audio and video devices were requested but not
+ // specified by id, get the default devices.
+ if (get_default_audio_device || get_default_video_device) {
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetDefaultDevicesForProfile(profile_, get_default_audio_device,
+ get_default_video_device, &devices);
+ }
+ break;
+ }
+ case blink::MEDIA_DEVICE_ACCESS: {
+ // Get the default devices for the request.
+ MediaCaptureDevicesDispatcher::GetInstance()->GetDefaultDevicesForProfile(
+ profile_, audio_allowed, video_allowed, &devices);
+ break;
+ }
+ case blink::MEDIA_DEVICE_UPDATE: {
+ NOTREACHED();
+ break;
+ }
+ } // switch
+
+ return devices;
+}
+
+void MediaStreamDevicesController::RunCallback(bool blocked_by_feature_policy) {
+ CHECK(!callback_.is_null());
+
+ // If the kill switch is, or the request was blocked because of feature
+ // policy we don't update the tab context.
+ if (denial_reason_ !=
+ blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
+ !blocked_by_feature_policy) {
+ UpdateTabSpecificContentSettings(audio_setting_, video_setting_);
+ }
+
+ blink::MediaStreamDevices devices;
+
+ // If all requested permissions are allowed then the callback should report
+ // success, otherwise we report |denial_reason_|.
+ blink::mojom::MediaStreamRequestResult request_result =
+ blink::mojom::MediaStreamRequestResult::OK;
+ if ((audio_setting_ == CONTENT_SETTING_ALLOW ||
+ audio_setting_ == CONTENT_SETTING_DEFAULT) &&
+ (video_setting_ == CONTENT_SETTING_ALLOW ||
+ video_setting_ == CONTENT_SETTING_DEFAULT)) {
+ devices = GetDevices(audio_setting_, video_setting_);
+ if (devices.empty()) {
+ // Even if all requested permissions are allowed, if there are no devices
+ // at this point we still report a failure.
+ request_result = blink::mojom::MediaStreamRequestResult::NO_HARDWARE;
+ }
+ } else {
+ DCHECK_NE(blink::mojom::MediaStreamRequestResult::OK, denial_reason_);
+ request_result = denial_reason_;
+ }
+
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (!devices.empty()) {
+ ui = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents_, devices);
+ }
+ std::move(callback_).Run(devices, request_result, std::move(ui));
+}
+
+void MediaStreamDevicesController::UpdateTabSpecificContentSettings(
+ ContentSetting audio_setting,
+ ContentSetting video_setting) const {
+ if (!content_settings_)
+ return;
+
+ TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+ TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
+ std::string selected_audio_device;
+ std::string selected_video_device;
+ std::string requested_audio_device = request_.requested_audio_device_id;
+ std::string requested_video_device = request_.requested_video_device_id;
+
+ // TODO(raymes): Why do we use the defaults here for the selected devices?
+ // Shouldn't we just use the devices that were actually selected?
+ PrefService* prefs = Profile::FromBrowserContext(
+ web_contents_->GetBrowserContext())->GetPrefs();
+ if (audio_setting != CONTENT_SETTING_DEFAULT) {
+ selected_audio_device =
+ requested_audio_device.empty()
+ ? prefs->GetString(prefs::kDefaultAudioCaptureDevice)
+ : requested_audio_device;
+ microphone_camera_state |=
+ TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ (audio_setting == CONTENT_SETTING_ALLOW
+ ? 0
+ : TabSpecificContentSettings::MICROPHONE_BLOCKED);
+ }
+
+ if (video_setting != CONTENT_SETTING_DEFAULT) {
+ selected_video_device =
+ requested_video_device.empty()
+ ? prefs->GetString(prefs::kDefaultVideoCaptureDevice)
+ : requested_video_device;
+ microphone_camera_state |=
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ (video_setting == CONTENT_SETTING_ALLOW
+ ? 0
+ : TabSpecificContentSettings::CAMERA_BLOCKED);
+ }
+
+ content_settings_->OnMediaStreamPermissionSet(
+ PermissionManager::Get(profile_)->GetCanonicalOrigin(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, request_.security_origin,
+ web_contents_->GetLastCommittedURL()),
+ microphone_camera_state, selected_audio_device, selected_video_device,
+ requested_audio_device, requested_video_device);
+}
+
+ContentSetting MediaStreamDevicesController::GetContentSetting(
+ ContentSettingsType content_type,
+ const content::MediaStreamRequest& request,
+ blink::mojom::MediaStreamRequestResult* denial_reason) const {
+ DCHECK(content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
+ content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ DCHECK(!request_.security_origin.is_empty());
+ DCHECK(content::IsOriginSecure(request_.security_origin) ||
+ request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
+ if (!ContentTypeIsRequested(content_type, request)) {
+ // No denial reason set as it will have been previously set.
+ return CONTENT_SETTING_DEFAULT;
+ }
+
+ std::string device_id;
+ if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
+ device_id = request.requested_audio_device_id;
+ else
+ device_id = request.requested_video_device_id;
+ if (!HasAvailableDevices(content_type, device_id)) {
+ *denial_reason = blink::mojom::MediaStreamRequestResult::NO_HARDWARE;
+ return CONTENT_SETTING_BLOCK;
+ }
+
+ if (!IsUserAcceptAllowed(content_type)) {
+ *denial_reason = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ return CONTENT_SETTING_BLOCK;
+ }
+
+ // Don't request if the kill switch is on.
+ if (PermissionIsBlockedForReason(content_type,
+ PermissionStatusSource::KILL_SWITCH)) {
+ *denial_reason = blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON;
+ return CONTENT_SETTING_BLOCK;
+ }
+
+ return CONTENT_SETTING_ASK;
+}
+
+bool MediaStreamDevicesController::IsUserAcceptAllowed(
+ ContentSettingsType content_type) const {
+#if defined(OS_ANDROID)
+ ui::WindowAndroid* window_android =
+ web_contents_->GetNativeView()->GetWindowAndroid();
+ if (!window_android)
+ return false;
+
+ std::vector<std::string> android_permissions;
+ PrefServiceBridge::GetAndroidPermissionsForContentSetting(
+ content_type, &android_permissions);
+ for (const auto& android_permission : android_permissions) {
+ if (!window_android->HasPermission(android_permission) &&
+ !window_android->CanRequestPermission(android_permission)) {
+ return false;
+ }
+ }
+
+ // Don't approve device requests if the tab was hidden.
+ // TODO(qinmin): Add a test for this. http://crbug.com/396869.
+ // TODO(raymes): Shouldn't this apply to all permissions not just audio/video?
+ return web_contents_->GetRenderWidgetHostView()->IsShowing();
+#endif
+ return true;
+}
+
+bool MediaStreamDevicesController::PermissionIsBlockedForReason(
+ ContentSettingsType content_type,
+ PermissionStatusSource reason) const {
+ // TODO(raymes): This function wouldn't be needed if
+ // PermissionManager::RequestPermissions returned a denial reason.
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
+ request_.render_process_id, request_.render_frame_id);
+ PermissionResult result =
+ PermissionManager::Get(profile_)->GetPermissionStatusForFrame(
+ content_type, rfh, request_.security_origin);
+ if (result.source == reason) {
+ DCHECK_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
+ return true;
+ }
+ return false;
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h
new file mode 100644
index 00000000000..4b5920a2c9b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "content/public/browser/media_stream_request.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+class MediaStreamDevicesController;
+class Profile;
+class TabSpecificContentSettings;
+enum class PermissionStatusSource;
+
+namespace content {
+class WebContents;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace policy {
+class MediaStreamDevicesControllerBrowserTest;
+}
+
+namespace test {
+class MediaStreamDevicesControllerTestApi;
+}
+
+class MediaStreamDevicesController {
+ public:
+ static void RequestPermissions(const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+
+ static void RequestAndroidPermissionsIfNeeded(
+ content::WebContents* web_contents,
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ bool did_prompt_for_audio,
+ bool did_prompt_for_video,
+ const std::vector<ContentSetting>& responses);
+
+#if defined(OS_ANDROID)
+ // Called when the Android OS-level prompt is answered.
+ static void AndroidOSPromptAnswered(
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ std::vector<ContentSetting> responses,
+ bool android_prompt_granted);
+#endif // defined(OS_ANDROID)
+
+ // Registers the prefs backing the audio and video policies.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ ~MediaStreamDevicesController();
+
+ // Called when a permission prompt is answered through the PermissionManager.
+ void PromptAnsweredGroupedRequest(
+ const std::vector<ContentSetting>& responses);
+
+ private:
+ friend class MediaStreamDevicesControllerTest;
+ friend class test::MediaStreamDevicesControllerTestApi;
+ friend class policy::MediaStreamDevicesControllerBrowserTest;
+
+ MediaStreamDevicesController(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+
+ // Returns true if audio/video should be requested through the
+ // PermissionManager. We won't try to request permission if the request is
+ // already blocked for some other reason, e.g. there are no devices available.
+ bool ShouldRequestAudio() const;
+ bool ShouldRequestVideo() const;
+
+ // Returns a list of devices available for the request for the given
+ // audio/video permission settings.
+ blink::MediaStreamDevices GetDevices(ContentSetting audio_setting,
+ ContentSetting video_setting);
+
+ // Runs |callback_| with the current audio/video permission settings.
+ void RunCallback(bool blocked_by_feature_policy);
+
+ // Called when the permission has been set to update the
+ // TabSpecificContentSettings.
+ void UpdateTabSpecificContentSettings(ContentSetting audio_setting,
+ ContentSetting video_setting) const;
+
+ // Returns the content settings for the given content type and request.
+ ContentSetting GetContentSetting(
+ ContentSettingsType content_type,
+ const content::MediaStreamRequest& request,
+ blink::mojom::MediaStreamRequestResult* denial_reason) const;
+
+ // Returns true if clicking allow on the dialog should give access to the
+ // requested devices.
+ bool IsUserAcceptAllowed(ContentSettingsType content_type) const;
+
+ bool PermissionIsBlockedForReason(ContentSettingsType content_type,
+ PermissionStatusSource reason) const;
+
+ // The current state of the audio/video content settings which may be updated
+ // through the lifetime of the request.
+ ContentSetting audio_setting_;
+ ContentSetting video_setting_;
+ blink::mojom::MediaStreamRequestResult denial_reason_;
+
+ content::WebContents* web_contents_;
+
+ // The owner of this class needs to make sure it does not outlive the profile.
+ Profile* profile_;
+
+ // Weak pointer to the tab specific content settings of the tab for which the
+ // MediaStreamDevicesController was created. The tab specific content
+ // settings are associated with a the web contents of the tab. The
+ // MediaStreamDeviceController must not outlive the web contents for which it
+ // was created.
+ TabSpecificContentSettings* content_settings_;
+
+ // The original request for access to devices.
+ const content::MediaStreamRequest request_;
+
+ // The callback that needs to be Run to notify WebRTC of whether access to
+ // audio/video devices was granted or not.
+ content::MediaResponseCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicesController);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
new file mode 100644
index 00000000000..ed67e419527
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
@@ -0,0 +1,963 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/field_trial.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/permissions/permission_context_base.h"
+#include "chrome/browser/permissions/permission_request.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "extensions/common/constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+
+class MediaStreamDevicesControllerTest : public WebRtcTestBase {
+ public:
+ MediaStreamDevicesControllerTest()
+ : example_audio_id_("fake_audio_dev"),
+ example_video_id_("fake_video_dev"),
+ media_stream_result_(
+ blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS) {
+ }
+
+ // Dummy callback for when we deny the current request directly.
+ void OnMediaStreamResponse(const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ media_stream_devices_ = devices;
+ media_stream_result_ = result;
+ quit_closure_.Run();
+ quit_closure_ = base::Closure();
+ }
+
+ protected:
+ enum DeviceType { DEVICE_TYPE_AUDIO, DEVICE_TYPE_VIDEO };
+ enum Access { ACCESS_ALLOWED, ACCESS_DENIED };
+
+ const GURL& example_url() const { return example_url_; }
+
+ TabSpecificContentSettings* GetContentSettings() {
+ return TabSpecificContentSettings::FromWebContents(GetWebContents());
+ }
+
+ const std::string& example_audio_id() const { return example_audio_id_; }
+ const std::string& example_video_id() const { return example_video_id_; }
+
+ blink::mojom::MediaStreamRequestResult media_stream_result() const {
+ return media_stream_result_;
+ }
+
+ void RequestPermissions(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback) {
+ base::RunLoop run_loop;
+ ASSERT_TRUE(quit_closure_.is_null());
+ quit_closure_ = run_loop.QuitClosure();
+ MediaStreamDevicesController::RequestPermissions(request,
+ std::move(callback));
+ run_loop.Run();
+ }
+
+ // Sets the device policy-controlled |access| for |example_url_| to be for the
+ // selected |device_type|.
+ void SetDevicePolicy(DeviceType device_type, Access access) {
+ PrefService* prefs = Profile::FromBrowserContext(
+ GetWebContents()->GetBrowserContext())->GetPrefs();
+ const char* policy_name = NULL;
+ switch (device_type) {
+ case DEVICE_TYPE_AUDIO:
+ policy_name = prefs::kAudioCaptureAllowed;
+ break;
+ case DEVICE_TYPE_VIDEO:
+ policy_name = prefs::kVideoCaptureAllowed;
+ break;
+ }
+ prefs->SetBoolean(policy_name, access == ACCESS_ALLOWED);
+ }
+
+ // Set the content settings for mic/cam.
+ void SetContentSettings(ContentSetting mic_setting,
+ ContentSetting cam_setting) {
+ HostContentSettingsMap* content_settings =
+ HostContentSettingsMapFactory::GetForProfile(
+ Profile::FromBrowserContext(GetWebContents()->GetBrowserContext()));
+ content_settings->SetContentSettingDefaultScope(
+ example_url_, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+ std::string(), mic_setting);
+ content_settings->SetContentSettingDefaultScope(
+ example_url_, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ std::string(), cam_setting);
+ }
+
+ // Checks whether the devices returned in OnMediaStreamResponse contains a
+ // microphone and/or camera device.
+ bool CheckDevicesListContains(blink::mojom::MediaStreamType type) {
+ for (const auto& device : media_stream_devices_) {
+ if (device.type == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ content::WebContents* GetWebContents() {
+ return browser()->tab_strip_model()->GetActiveWebContents();
+ }
+
+ // Creates a MediaStreamRequest, asking for those media types, which have a
+ // non-empty id string.
+ content::MediaStreamRequest CreateRequestWithType(
+ const std::string& audio_id,
+ const std::string& video_id,
+ blink::MediaStreamRequestType request_type) {
+ blink::mojom::MediaStreamType audio_type =
+ audio_id.empty() ? blink::mojom::MediaStreamType::NO_SERVICE
+ : blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
+ blink::mojom::MediaStreamType video_type =
+ video_id.empty() ? blink::mojom::MediaStreamType::NO_SERVICE
+ : blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
+ EXPECT_EQ(example_url(),
+ GetWebContents()->GetMainFrame()->GetLastCommittedURL());
+ int render_process_id =
+ GetWebContents()->GetMainFrame()->GetProcess()->GetID();
+ int render_frame_id = GetWebContents()->GetMainFrame()->GetRoutingID();
+ return content::MediaStreamRequest(
+ render_process_id, render_frame_id, 0, example_url(), false,
+ request_type, audio_id, video_id, audio_type, video_type, false);
+ }
+
+ content::MediaStreamRequest CreateRequest(const std::string& audio_id,
+ const std::string& video_id) {
+ return CreateRequestWithType(audio_id, video_id,
+ blink::MEDIA_DEVICE_ACCESS);
+ }
+
+ void InitWithUrl(const GURL& url) {
+ DCHECK(example_url_.is_empty());
+ example_url_ = url;
+ ui_test_utils::NavigateToURL(browser(), example_url_);
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ }
+
+ MockPermissionPromptFactory* prompt_factory() {
+ return prompt_factory_.get();
+ }
+
+ private:
+ void SetUpOnMainThread() override {
+ WebRtcTestBase::SetUpOnMainThread();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ PermissionRequestManager* manager =
+ PermissionRequestManager::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents());
+ prompt_factory_.reset(new MockPermissionPromptFactory(manager));
+
+ // Cleanup.
+ media_stream_devices_.clear();
+ media_stream_result_ =
+ blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS;
+
+ blink::MediaStreamDevices audio_devices;
+ blink::MediaStreamDevice fake_audio_device(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE, example_audio_id_,
+ "Fake Audio Device");
+ audio_devices.push_back(fake_audio_device);
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestAudioCaptureDevices(
+ audio_devices);
+
+ blink::MediaStreamDevices video_devices;
+ blink::MediaStreamDevice fake_video_device(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, example_video_id_,
+ "Fake Video Device");
+ video_devices.push_back(fake_video_device);
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestVideoCaptureDevices(
+ video_devices);
+ }
+
+ void TearDownOnMainThread() override {
+ prompt_factory_.reset();
+
+ WebRtcTestBase::TearDownOnMainThread();
+ }
+
+ GURL example_url_;
+ const std::string example_audio_id_;
+ const std::string example_video_id_;
+
+ blink::MediaStreamDevices media_stream_devices_;
+ blink::mojom::MediaStreamRequestResult media_stream_result_;
+
+ base::Closure quit_closure_;
+
+ std::unique_ptr<MockPermissionPromptFactory> prompt_factory_;
+};
+
+// Request and allow microphone access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndAllowMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and allow camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndAllowCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and block microphone access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndBlockMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and block camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndBlockCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and allow microphone and camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestAndAllowMicCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and block microphone and camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestAndBlockMicCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request microphone and camera access. Camera is denied, thus everything
+// must be denied.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestMicCamBlockCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request microphone and camera access. Microphone is denied, thus everything
+// must be denied.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestMicCamBlockMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request microphone access. Requesting camera should not change microphone
+// state.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestCamDoesNotChangeMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ // Request mic and deny.
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+
+ // Request cam and allow
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+
+ // Mic state should not have changed.
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+}
+
+// Denying mic access after camera access should still show the camera as state.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ DenyMicDoesNotChangeCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ // Request cam and allow
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+ EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+
+ // Simulate that an a video stream is now being captured.
+ blink::MediaStreamDevices video_devices(1);
+ video_devices[0] = blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, example_video_id(),
+ example_video_id());
+ MediaCaptureDevicesDispatcher* dispatcher =
+ MediaCaptureDevicesDispatcher::GetInstance();
+ dispatcher->SetTestVideoCaptureDevices(video_devices);
+ std::unique_ptr<content::MediaStreamUI> video_stream_ui =
+ dispatcher->GetMediaStreamCaptureIndicator()->RegisterMediaStream(
+ GetWebContents(), video_devices);
+ video_stream_ui->OnStarted(base::OnceClosure(),
+ content::MediaStreamUI::SourceCallback());
+
+ // Request mic and deny.
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+
+ // Cam should still be included in the state.
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+
+ // After ending the camera capture, the camera permission is no longer
+ // relevant, so it should no be included in the mic/cam state.
+ video_stream_ui.reset();
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+}
+
+// Stores the ContentSettings inputs for a particular test and has functions
+// which return the expected outputs for that test.
+struct ContentSettingsTestData {
+ // The initial value of the mic/cam content settings.
+ ContentSetting mic;
+ ContentSetting cam;
+ // Whether the infobar should be accepted if it's shown.
+ bool accept_infobar;
+
+ // Whether the infobar should be displayed to request mic/cam for the given
+ // content settings inputs.
+ bool ExpectMicInfobar() const {
+ return mic == CONTENT_SETTING_ASK && cam != CONTENT_SETTING_BLOCK;
+ }
+ bool ExpectCamInfobar() const {
+ return cam == CONTENT_SETTING_ASK && mic != CONTENT_SETTING_BLOCK;
+ }
+
+ // Whether or not the mic/cam should be allowed after clicking accept/deny for
+ // the given inputs.
+ bool ExpectMicAllowed() const {
+ return mic == CONTENT_SETTING_ALLOW ||
+ (mic == CONTENT_SETTING_ASK && accept_infobar);
+ }
+ bool ExpectCamAllowed() const {
+ return cam == CONTENT_SETTING_ALLOW ||
+ (cam == CONTENT_SETTING_ASK && accept_infobar);
+ }
+
+ // The expected media stream result after clicking accept/deny for the given
+ // inputs.
+ blink::mojom::MediaStreamRequestResult ExpectedMediaStreamResult() const {
+ if (ExpectMicAllowed() && ExpectCamAllowed())
+ return blink::mojom::MediaStreamRequestResult::OK;
+ return blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ }
+};
+
+// Test all combinations of cam/mic content settings. Then tests the result of
+// clicking both accept/deny on the infobar. Both cam/mic are requested.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, ContentSettings) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ static const ContentSettingsTestData tests[] = {
+ // Settings that won't result in an infobar.
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, false},
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, false},
+ {CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW, false},
+ {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, false},
+ {CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK, false},
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK, false},
+
+ // Settings that will result in an infobar. Test both accept and deny.
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, false},
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, true},
+
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ASK, false},
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ASK, true},
+
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, false},
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, true},
+ };
+
+ for (auto& test : tests) {
+ SetContentSettings(test.mic, test.cam);
+
+ prompt_factory()->ResetCounts();
+
+ // Accept or deny the infobar if it's showing.
+ if (test.ExpectMicInfobar() || test.ExpectCamInfobar()) {
+ if (test.accept_infobar) {
+ prompt_factory()->set_response_type(
+ PermissionRequestManager::ACCEPT_ALL);
+ } else {
+ prompt_factory()->set_response_type(PermissionRequestManager::DENY_ALL);
+ }
+ } else {
+ prompt_factory()->set_response_type(PermissionRequestManager::NONE);
+ }
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_LE(prompt_factory()->TotalRequestCount(), 2);
+ ASSERT_EQ(test.ExpectMicInfobar(),
+ prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_MIC));
+ ASSERT_EQ(test.ExpectCamInfobar(),
+ prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_CAMERA));
+
+ // Check the media stream result is expected and the devices returned are
+ // expected;
+ ASSERT_EQ(test.ExpectedMediaStreamResult(), media_stream_result());
+ ASSERT_EQ(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE),
+ test.ExpectMicAllowed() && test.ExpectCamAllowed());
+ ASSERT_EQ(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE),
+ test.ExpectMicAllowed() && test.ExpectCamAllowed());
+ }
+}
+
+// Request and allow camera access on WebUI pages without prompting.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ WebUIRequestAndAllowCam) {
+ InitWithUrl(GURL(chrome::kChromeUIVersionURL));
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ ExtensionRequestMicCam) {
+ std::string pdf_extension_page = std::string(extensions::kExtensionScheme) +
+ "://" + extension_misc::kPdfExtensionId +
+ "/index.html";
+ InitWithUrl(GURL(pdf_extension_page));
+ // Test that a prompt is required.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(2, prompt_factory()->TotalRequestCount());
+ ASSERT_TRUE(prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_CAMERA));
+ ASSERT_TRUE(prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_MIC));
+
+ // Accept the prompt.
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+
+ // Check that re-requesting allows without prompting.
+ prompt_factory()->ResetCounts();
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ PepperRequestInsecure) {
+ InitWithUrl(GURL("http://www.example.com"));
+
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+
+ RequestPermissions(
+ GetWebContents(),
+ CreateRequestWithType(example_audio_id(), example_video_id(),
+ blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, WebContentsDestroyed) {
+ InitWithUrl(GURL("http://www.example.com"));
+
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+
+ content::MediaStreamRequest request =
+ CreateRequest(example_audio_id(), example_video_id());
+ // Simulate a destroyed RenderFrameHost.
+ request.render_frame_id = 0;
+ request.render_process_id = 0;
+
+ RequestPermissions(
+ nullptr, request,
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+// Request and block microphone and camera access with kill switch.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestAndKillSwitchMicCam) {
+ std::map<std::string, std::string> params;
+ params[PermissionUtil::GetPermissionString(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)] =
+ PermissionContextBase::kPermissionsKillSwitchBlockedValue;
+ params[PermissionUtil::GetPermissionString(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA)] =
+ PermissionContextBase::kPermissionsKillSwitchBlockedValue;
+ variations::AssociateVariationParams(
+ PermissionContextBase::kPermissionsKillSwitchFieldStudy,
+ "TestGroup", params);
+ base::FieldTrialList::CreateFieldTrial(
+ PermissionContextBase::kPermissionsKillSwitchFieldStudy,
+ "TestGroup");
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestCamAndMicBlockedByFeaturePolicy) {
+ InitWithUrl(embedded_test_server()->GetURL("/iframe_blank.html"));
+
+ // Create a cross-origin request by using localhost as the iframe origin.
+ GURL::Replacements replace_host;
+ replace_host.SetHostStr("localhost");
+ GURL cross_origin_url = embedded_test_server()
+ ->GetURL("/simple.html")
+ .ReplaceComponents(replace_host);
+ content::NavigateIframeToURL(GetWebContents(), "test",
+ GURL(cross_origin_url));
+ content::RenderFrameHost* child_frame =
+ ChildFrameAt(GetWebContents()->GetMainFrame(), 0);
+
+ content::MediaStreamRequest request =
+ CreateRequest(example_audio_id(), example_video_id());
+ // Make the child frame the source of the request.
+ request.render_process_id = child_frame->GetProcess()->GetID();
+ request.render_frame_id = child_frame->GetRoutingID();
+ RequestPermissions(
+ GetWebContents(), request,
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestCamBlockedByFeaturePolicy) {
+ InitWithUrl(embedded_test_server()->GetURL("/iframe_blank.html"));
+
+ // Create a cross-origin request by using localhost as the iframe origin.
+ GURL::Replacements replace_host;
+ replace_host.SetHostStr("localhost");
+ GURL cross_origin_url = embedded_test_server()
+ ->GetURL("/simple.html")
+ .ReplaceComponents(replace_host);
+ content::NavigateIframeToURL(GetWebContents(), "test",
+ GURL(cross_origin_url));
+ content::RenderFrameHost* child_frame =
+ ChildFrameAt(GetWebContents()->GetMainFrame(), 0);
+
+ content::MediaStreamRequest request =
+ CreateRequest(std::string(), example_video_id());
+ // Make the child frame the source of the request.
+ request.render_process_id = child_frame->GetProcess()->GetID();
+ request.render_frame_id = child_frame->GetRoutingID();
+ RequestPermissions(
+ GetWebContents(), request,
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ PepperAudioRequestNoCamera) {
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestVideoCaptureDevices({});
+ InitWithUrl(GURL(chrome::kChromeUIVersionURL));
+ RequestPermissions(
+ GetWebContents(),
+ CreateRequestWithType(example_audio_id(), std::string(),
+ blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
+ base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ EXPECT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ EXPECT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ PepperVideoRequestNoMic) {
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestAudioCaptureDevices({});
+ InitWithUrl(GURL(chrome::kChromeUIVersionURL));
+ RequestPermissions(
+ GetWebContents(),
+ CreateRequestWithType(std::string(), example_video_id(),
+ blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
+ base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ EXPECT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ EXPECT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc b/chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
new file mode 100644
index 00000000000..651c8025892
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
@@ -0,0 +1,168 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/test_switches.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+// MediaStreamPermissionTest ---------------------------------------------------
+
+class MediaStreamPermissionTest : public WebRtcTestBase {
+ public:
+ MediaStreamPermissionTest() {}
+ ~MediaStreamPermissionTest() override {}
+
+ // InProcessBrowserTest:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // This test expects to run with fake devices but real UI.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream))
+ << "Since this test tests the UI we want the real UI!";
+ }
+
+ protected:
+ content::WebContents* LoadTestPageInTab() {
+ return LoadTestPageInBrowser(browser());
+ }
+
+ content::WebContents* LoadTestPageInIncognitoTab() {
+ return LoadTestPageInBrowser(CreateIncognitoBrowser());
+ }
+
+ // Returns the URL of the main test page.
+ GURL test_page_url() const {
+ const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+ return embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage);
+ }
+
+ private:
+ content::WebContents* LoadTestPageInBrowser(Browser* browser) {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ // Uses the default server.
+ GURL url = test_page_url();
+
+ EXPECT_TRUE(content::IsOriginSecure(url));
+
+ ui_test_utils::NavigateToURL(browser, url);
+ return browser->tab_strip_model()->GetActiveWebContents();
+ }
+
+ // Dummy callback for when we deny the current request directly.
+ static void OnMediaStreamResponse(
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {}
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamPermissionTest);
+};
+
+// Actual tests ---------------------------------------------------------------
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestAllowingUserMedia) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ EXPECT_TRUE(GetUserMediaAndAccept(tab_contents));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestDenyingUserMedia) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ GetUserMediaAndDeny(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestDismissingRequest) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ GetUserMediaAndDismiss(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestDenyingUserMediaIncognito) {
+ content::WebContents* tab_contents = LoadTestPageInIncognitoTab();
+ GetUserMediaAndDeny(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestSecureOriginDenyIsSticky) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ EXPECT_TRUE(content::IsOriginSecure(tab_contents->GetLastCommittedURL()));
+
+ GetUserMediaAndDeny(tab_contents);
+ GetUserMediaAndExpectAutoDenyWithoutPrompt(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestSecureOriginAcceptIsSticky) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ EXPECT_TRUE(content::IsOriginSecure(tab_contents->GetLastCommittedURL()));
+
+ EXPECT_TRUE(GetUserMediaAndAccept(tab_contents));
+ GetUserMediaAndExpectAutoAcceptWithoutPrompt(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestDismissIsNotSticky) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaAndDismiss(tab_contents);
+ GetUserMediaAndDismiss(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestDenyingThenClearingStickyException) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaAndDeny(tab_contents);
+ GetUserMediaAndExpectAutoDenyWithoutPrompt(tab_contents);
+
+ HostContentSettingsMap* settings_map =
+ HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+
+ settings_map->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+ settings_map->ClearSettingsForOneType(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+
+ GetUserMediaAndDeny(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ DenyingMicDoesNotCauseStickyDenyForCameras) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaWithSpecificConstraintsAndDeny(tab_contents,
+ kAudioOnlyCallConstraints);
+ EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAccept(
+ tab_contents, kVideoOnlyCallConstraints));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ DenyingCameraDoesNotCauseStickyDenyForMics) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaWithSpecificConstraintsAndDeny(tab_contents,
+ kVideoOnlyCallConstraints);
+ EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAccept(
+ tab_contents, kAudioOnlyCallConstraints));
+}
diff --git a/chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc
new file mode 100644
index 00000000000..2b109bd1dea
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc
@@ -0,0 +1,407 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/hash/hash.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/base/video_util.h"
+#include "third_party/libyuv/include/libyuv/scale_argb.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/snapshot/snapshot.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/snapshot/snapshot_aura.h"
+#endif
+
+using content::BrowserThread;
+using content::DesktopMediaID;
+
+namespace {
+
+// Update the list every second.
+const int kDefaultNativeDesktopMediaListUpdatePeriod = 1000;
+
+// Returns a hash of a DesktopFrame content to detect when image for a desktop
+// media source has changed.
+uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
+ int data_size = frame->stride() * frame->size().height();
+ return base::Hash(frame->data(), data_size);
+}
+
+gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr<webrtc::DesktopFrame> frame,
+ gfx::Size size) {
+ gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
+ gfx::Rect(0, 0, size.width(), size.height()),
+ gfx::Size(frame->size().width(), frame->size().height()));
+
+ SkBitmap result;
+ result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);
+
+ uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
+ libyuv::ARGBScale(frame->data(), frame->stride(), frame->size().width(),
+ frame->size().height(), pixels_data, result.rowBytes(),
+ scaled_rect.width(), scaled_rect.height(),
+ libyuv::kFilterBilinear);
+
+ // Set alpha channel values to 255 for all pixels.
+ // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
+ // remove this code. Currently screen/window capturers (at least some
+ // implementations) only capture R, G and B channels and set Alpha to 0.
+ // crbug.com/264424
+ for (int y = 0; y < result.height(); ++y) {
+ for (int x = 0; x < result.width(); ++x) {
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
+ 0xff;
+ }
+ }
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
+
+} // namespace
+
+class NativeDesktopMediaList::Worker
+ : public webrtc::DesktopCapturer::Callback {
+ public:
+ Worker(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::WeakPtr<NativeDesktopMediaList> media_list,
+ DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer);
+ ~Worker() override;
+
+ void Start();
+ void Refresh(const DesktopMediaID::Id& view_dialog_id, bool update_thumnails);
+
+ void RefreshThumbnails(const std::vector<DesktopMediaID>& native_ids,
+ const gfx::Size& thumbnail_size);
+
+ private:
+ typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap;
+
+ // webrtc::DesktopCapturer::Callback interface.
+ void OnCaptureResult(webrtc::DesktopCapturer::Result result,
+ std::unique_ptr<webrtc::DesktopFrame> frame) override;
+
+ // Task runner used for capturing operations.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ base::WeakPtr<NativeDesktopMediaList> media_list_;
+
+ DesktopMediaID::Type type_;
+ std::unique_ptr<webrtc::DesktopCapturer> capturer_;
+
+ std::unique_ptr<webrtc::DesktopFrame> current_frame_;
+
+ ImageHashesMap image_hashes_;
+
+ DISALLOW_COPY_AND_ASSIGN(Worker);
+};
+
+NativeDesktopMediaList::Worker::Worker(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::WeakPtr<NativeDesktopMediaList> media_list,
+ DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer)
+ : task_runner_(task_runner),
+ media_list_(media_list),
+ type_(type),
+ capturer_(std::move(capturer)) {}
+
+NativeDesktopMediaList::Worker::~Worker() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+}
+
+void NativeDesktopMediaList::Worker::Start() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ capturer_->Start(this);
+}
+
+void NativeDesktopMediaList::Worker::Refresh(
+ const DesktopMediaID::Id& view_dialog_id,
+ bool update_thumnails) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ std::vector<SourceDescription> result;
+
+ webrtc::DesktopCapturer::SourceList sources;
+ if (!capturer_->GetSourceList(&sources)) {
+ // Will pass empty results list to RefreshForAuraWindows().
+ sources.clear();
+ }
+
+ bool mutiple_sources = sources.size() > 1;
+ base::string16 title;
+ for (size_t i = 0; i < sources.size(); ++i) {
+ switch (type_) {
+ case DesktopMediaID::TYPE_SCREEN:
+ // Just in case 'Screen' is inflected depending on the screen number,
+ // use plural formatter.
+ title = mutiple_sources
+ ? l10n_util::GetPluralStringFUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
+ static_cast<int>(i + 1))
+ : l10n_util::GetStringUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
+ break;
+
+ case DesktopMediaID::TYPE_WINDOW:
+ // Skip the picker dialog window.
+ if (sources[i].id == view_dialog_id)
+ continue;
+ title = base::UTF8ToUTF16(sources[i].title);
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ result.push_back(
+ SourceDescription(DesktopMediaID(type_, sources[i].id), title));
+ }
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::RefreshForAuraWindows,
+ media_list_, result, update_thumnails));
+}
+
+void NativeDesktopMediaList::Worker::RefreshThumbnails(
+ const std::vector<DesktopMediaID>& native_ids,
+ const gfx::Size& thumbnail_size) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ ImageHashesMap new_image_hashes;
+
+ // Get a thumbnail for each native source.
+ for (const auto& id : native_ids) {
+ if (!capturer_->SelectSource(id.id))
+ continue;
+ capturer_->CaptureFrame();
+
+ // Expect that DesktopCapturer to always captures frames synchronously.
+ // |current_frame_| may be NULL if capture failed (e.g. because window has
+ // been closed).
+ if (current_frame_) {
+ uint32_t frame_hash = GetFrameHash(current_frame_.get());
+ new_image_hashes[id] = frame_hash;
+
+ // Scale the image only if it has changed.
+ auto it = image_hashes_.find(id);
+ if (it == image_hashes_.end() || it->second != frame_hash) {
+ gfx::ImageSkia thumbnail =
+ ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail,
+ media_list_, id, thumbnail));
+ }
+ }
+ }
+
+ image_hashes_.swap(new_image_hashes);
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished,
+ media_list_));
+}
+
+void NativeDesktopMediaList::Worker::OnCaptureResult(
+ webrtc::DesktopCapturer::Result result,
+ std::unique_ptr<webrtc::DesktopFrame> frame) {
+ current_frame_ = std::move(frame);
+}
+
+NativeDesktopMediaList::NativeDesktopMediaList(
+ DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer)
+ : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
+ kDefaultNativeDesktopMediaListUpdatePeriod)),
+ thread_("DesktopMediaListCaptureThread") {
+ type_ = type;
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ // On Windows/OSX the thread must be a UI thread.
+ base::MessagePumpType thread_type = base::MessagePumpType::UI;
+#else
+ base::MessagePumpType thread_type = base::MessagePumpType::DEFAULT;
+#endif
+ thread_.StartWithOptions(base::Thread::Options(thread_type, 0));
+
+ worker_.reset(new Worker(thread_.task_runner(), weak_factory_.GetWeakPtr(),
+ type, std::move(capturer)));
+
+ thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Worker::Start, base::Unretained(worker_.get())));
+}
+
+NativeDesktopMediaList::~NativeDesktopMediaList() {
+ // This thread should mostly be an idle observer. Stopping it should be fast.
+ base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
+ thread_.task_runner()->DeleteSoon(FROM_HERE, worker_.release());
+ thread_.Stop();
+}
+
+void NativeDesktopMediaList::Refresh(bool update_thumnails) {
+ DCHECK(can_refresh());
+
+#if defined(USE_AURA)
+ DCHECK_EQ(pending_aura_capture_requests_, 0);
+ DCHECK(!pending_native_thumbnail_capture_);
+ new_aura_thumbnail_hashes_.clear();
+#endif
+
+ thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Worker::Refresh, base::Unretained(worker_.get()),
+ view_dialog_id_.id, update_thumnails));
+}
+
+void NativeDesktopMediaList::RefreshForAuraWindows(
+ std::vector<SourceDescription> sources,
+ bool update_thumnails) {
+ DCHECK(can_refresh());
+
+#if defined(USE_AURA)
+ // Associate aura id with native id.
+ for (auto& source : sources) {
+ if (source.id.type != DesktopMediaID::TYPE_WINDOW)
+ continue;
+
+ aura::WindowTreeHost* const host =
+ aura::WindowTreeHost::GetForAcceleratedWidget(
+ *reinterpret_cast<gfx::AcceleratedWidget*>(&source.id.id));
+ aura::Window* const aura_window = host ? host->window() : nullptr;
+ if (aura_window) {
+ DesktopMediaID aura_id = DesktopMediaID::RegisterNativeWindow(
+ DesktopMediaID::TYPE_WINDOW, aura_window);
+ source.id.window_id = aura_id.window_id;
+ }
+ }
+#endif // defined(USE_AURA)
+
+ UpdateSourcesList(sources);
+
+ if (!update_thumnails) {
+ OnRefreshComplete();
+ return;
+ }
+
+ if (thumbnail_size_.IsEmpty()) {
+#if defined(USE_AURA)
+ pending_native_thumbnail_capture_ = true;
+#endif
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ // OnAuraThumbnailCaptured() and UpdateNativeThumbnailsFinished() are
+ // guaranteed to be executed after RefreshForAuraWindows() and
+ // CaptureAuraWindowThumbnail() in the browser UI thread.
+ // Therefore pending_aura_capture_requests_ will be set the number of aura
+ // windows to be captured and pending_native_thumbnail_capture_ will be set
+ // true if native thumbnail capture is needed before OnAuraThumbnailCaptured()
+ // or UpdateNativeThumbnailsFinished() are called.
+ std::vector<DesktopMediaID> native_ids;
+ for (const auto& source : sources) {
+#if defined(USE_AURA)
+ if (source.id.window_id > DesktopMediaID::kNullId) {
+ CaptureAuraWindowThumbnail(source.id);
+ continue;
+ }
+#endif // defined(USE_AURA)
+ native_ids.push_back(source.id);
+ }
+
+ if (!native_ids.empty()) {
+#if defined(USE_AURA)
+ pending_native_thumbnail_capture_ = true;
+#endif
+ thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails,
+ base::Unretained(worker_.get()), native_ids,
+ thumbnail_size_));
+ }
+}
+
+void NativeDesktopMediaList::UpdateNativeThumbnailsFinished() {
+#if defined(USE_AURA)
+ DCHECK(pending_native_thumbnail_capture_);
+ pending_native_thumbnail_capture_ = false;
+ // If native thumbnail captures finished after aura thumbnail captures,
+ // execute |done_callback| to let the caller know the update process is
+ // finished. If necessary, this will schedule the next refresh.
+ if (pending_aura_capture_requests_ == 0)
+ OnRefreshComplete();
+#else
+ OnRefreshComplete();
+#endif // defined(USE_AURA)
+}
+
+#if defined(USE_AURA)
+
+void NativeDesktopMediaList::CaptureAuraWindowThumbnail(
+ const DesktopMediaID& id) {
+ DCHECK(can_refresh());
+
+ gfx::NativeWindow window = DesktopMediaID::GetNativeWindowById(id);
+ if (!window)
+ return;
+
+ gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
+ gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
+ gfx::Rect(thumbnail_size_), window_rect.size());
+
+ pending_aura_capture_requests_++;
+ ui::GrabWindowSnapshotAndScaleAsyncAura(
+ window, window_rect, scaled_rect.size(),
+ base::Bind(&NativeDesktopMediaList::OnAuraThumbnailCaptured,
+ weak_factory_.GetWeakPtr(), id));
+}
+
+void NativeDesktopMediaList::OnAuraThumbnailCaptured(const DesktopMediaID& id,
+ gfx::Image image) {
+ DCHECK(can_refresh());
+
+ if (!image.IsEmpty()) {
+ // Only new or changed thumbnail need update.
+ new_aura_thumbnail_hashes_[id] = GetImageHash(image);
+ if (!previous_aura_thumbnail_hashes_.count(id) ||
+ previous_aura_thumbnail_hashes_[id] != new_aura_thumbnail_hashes_[id]) {
+ UpdateSourceThumbnail(id, image.AsImageSkia());
+ }
+ }
+
+ // After all aura windows are processed, schedule next refresh;
+ pending_aura_capture_requests_--;
+ DCHECK_GE(pending_aura_capture_requests_, 0);
+ if (pending_aura_capture_requests_ == 0) {
+ previous_aura_thumbnail_hashes_ = std::move(new_aura_thumbnail_hashes_);
+ // Schedule next refresh if aura thumbnail captures finished after native
+ // thumbnail captures.
+ if (!pending_native_thumbnail_capture_)
+ OnRefreshComplete();
+ }
+}
+
+#endif // defined(USE_AURA)
diff --git a/chromium/chrome/browser/media/webrtc/native_desktop_media_list.h b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.h
new file mode 100644
index 00000000000..cfd8378fe93
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.h
@@ -0,0 +1,67 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_NATIVE_DESKTOP_MEDIA_LIST_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_NATIVE_DESKTOP_MEDIA_LIST_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "ui/gfx/image/image.h"
+
+namespace webrtc {
+class DesktopCapturer;
+}
+
+// Implementation of DesktopMediaList that shows native screens and
+// native windows.
+class NativeDesktopMediaList : public DesktopMediaListBase {
+ public:
+ NativeDesktopMediaList(content::DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer);
+ ~NativeDesktopMediaList() override;
+
+ private:
+ typedef std::map<content::DesktopMediaID, uint32_t> ImageHashesMap;
+
+ class Worker;
+ friend class Worker;
+
+ // Refresh() posts a task for the |worker_| to update list of windows, get
+ // thumbnails and schedules next refresh.
+ void Refresh(bool update_thumnails) override;
+
+ void RefreshForAuraWindows(std::vector<SourceDescription> sources,
+ bool update_thumnails);
+ void UpdateNativeThumbnailsFinished();
+
+#if defined(USE_AURA)
+ void CaptureAuraWindowThumbnail(const content::DesktopMediaID& id);
+ void OnAuraThumbnailCaptured(const content::DesktopMediaID& id,
+ gfx::Image image);
+#endif
+
+ base::Thread thread_;
+ std::unique_ptr<Worker> worker_;
+
+#if defined(USE_AURA)
+ // previous_aura_thumbnail_hashes_ holds thumbanil hash values of aura windows
+ // in the previous refresh. While new_aura_thumbnail_hashes_ has hash values
+ // of the ongoing refresh. Those two maps are used to detect new thumbnails
+ // and changed thumbnails from the previous refresh.
+ ImageHashesMap previous_aura_thumbnail_hashes_;
+ ImageHashesMap new_aura_thumbnail_hashes_;
+
+ int pending_aura_capture_requests_ = 0;
+ bool pending_native_thumbnail_capture_ = false;
+#endif
+ base::WeakPtrFactory<NativeDesktopMediaList> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaList);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_NATIVE_DESKTOP_MEDIA_LIST_H_
diff --git a/chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc b/chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
new file mode 100644
index 00000000000..fe430e586e3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
@@ -0,0 +1,554 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/test/views/chrome_views_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
+#endif
+
+using content::DesktopMediaID;
+using testing::_;
+using testing::DoAll;
+
+namespace {
+
+// Aura window capture unit tests are not stable. crbug.com/602494 and
+// crbug.com/603823.
+// #define ENABLE_AURA_WINDOW_TESTS
+
+static const int kDefaultWindowCount = 2;
+#if defined(ENABLE_AURA_WINDOW_TESTS)
+static const int kDefaultAuraCount = 1;
+#else
+static const int kDefaultAuraCount = 0;
+#endif
+
+class MockObserver : public DesktopMediaListObserver {
+ public:
+ MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
+ MOCK_METHOD3(OnSourceMoved,
+ void(DesktopMediaList* list, int old_index, int new_index));
+ MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceThumbnailChanged,
+ void(DesktopMediaList* list, int index));
+ MOCK_METHOD1(OnAllSourcesFound, void(DesktopMediaList* list));
+};
+
+class FakeScreenCapturer : public webrtc::DesktopCapturer {
+ public:
+ FakeScreenCapturer() {}
+ ~FakeScreenCapturer() override {}
+
+ // webrtc::ScreenCapturer implementation.
+ void Start(Callback* callback) override { callback_ = callback; }
+
+ void CaptureFrame() override {
+ DCHECK(callback_);
+ std::unique_ptr<webrtc::DesktopFrame> frame(
+ new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
+ callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
+ std::move(frame));
+ }
+
+ bool GetSourceList(SourceList* screens) override {
+ screens->push_back({0});
+ return true;
+ }
+
+ bool SelectSource(SourceId id) override {
+ EXPECT_EQ(0, id);
+ return true;
+ }
+
+ protected:
+ Callback* callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeScreenCapturer);
+};
+
+class FakeWindowCapturer : public webrtc::DesktopCapturer {
+ public:
+ FakeWindowCapturer() : callback_(nullptr) {}
+ ~FakeWindowCapturer() override {}
+
+ void SetWindowList(const SourceList& list) {
+ base::AutoLock lock(window_list_lock_);
+ window_list_ = list;
+ }
+
+ // Sets |value| thats going to be used to memset() content of the frames
+ // generated for |window_id|. By default generated frames are set to zeros.
+ void SetNextFrameValue(SourceId window_id, int8_t value) {
+ base::AutoLock lock(frame_values_lock_);
+ frame_values_[window_id] = value;
+ }
+
+ // webrtc::WindowCapturer implementation.
+ void Start(Callback* callback) override { callback_ = callback; }
+
+ void CaptureFrame() override {
+ DCHECK(callback_);
+
+ base::AutoLock lock(frame_values_lock_);
+
+ auto it = frame_values_.find(selected_window_id_);
+ int8_t value = (it != frame_values_.end()) ? it->second : 0;
+ std::unique_ptr<webrtc::DesktopFrame> frame(
+ new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
+ memset(frame->data(), value, frame->stride() * frame->size().height());
+ callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
+ std::move(frame));
+ }
+
+ bool GetSourceList(SourceList* windows) override {
+ base::AutoLock lock(window_list_lock_);
+ *windows = window_list_;
+ return true;
+ }
+
+ bool SelectSource(SourceId id) override {
+ selected_window_id_ = id;
+ return true;
+ }
+
+ bool FocusOnSelectedSource() override { return true; }
+
+ private:
+ Callback* callback_;
+ SourceList window_list_;
+ base::Lock window_list_lock_;
+
+ SourceId selected_window_id_;
+
+ // Frames to be captured per window.
+ std::map<SourceId, int8_t> frame_values_;
+ base::Lock frame_values_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeWindowCapturer);
+};
+
+} // namespace
+
+ACTION_P2(CheckListSize, model, expected_list_size) {
+ EXPECT_EQ(expected_list_size, model->GetSourceCount());
+}
+
+ACTION_P2(QuitRunLoop, task_runner, run_loop) {
+ task_runner->PostTask(FROM_HERE, run_loop->QuitWhenIdleClosure());
+}
+
+class NativeDesktopMediaListTest : public ChromeViewsTestBase {
+ public:
+ NativeDesktopMediaListTest() = default;
+
+ void TearDown() override {
+ for (size_t i = 0; i < desktop_widgets_.size(); i++)
+ desktop_widgets_[i].reset();
+
+ ChromeViewsTestBase::TearDown();
+ }
+
+ void AddNativeWindow(int id) {
+ webrtc::DesktopCapturer::Source window;
+ window.id = id;
+ window.title = "Test window";
+ window_list_.push_back(window);
+ }
+
+#if defined(USE_AURA)
+ std::unique_ptr<views::Widget> CreateDesktopWidget() {
+ std::unique_ptr<views::Widget> widget(new views::Widget);
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ params.accept_events = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.native_widget = new views::DesktopNativeWidgetAura(widget.get());
+ params.bounds = gfx::Rect(0, 0, 20, 20);
+ widget->Init(std::move(params));
+ widget->Show();
+ return widget;
+ }
+
+ void AddAuraWindow() {
+ webrtc::DesktopCapturer::Source window;
+ window.title = "Test window";
+
+ // Create a aura native widow through a widget.
+ desktop_widgets_.push_back(CreateDesktopWidget());
+ aura::WindowTreeHost* const host =
+ desktop_widgets_.back()->GetNativeWindow()->GetHost();
+ aura::Window* const aura_window = host->window();
+
+ // Get the native window's id.
+ gfx::AcceleratedWidget widget = host->GetAcceleratedWidget();
+#if defined(OS_WIN)
+ window.id = reinterpret_cast<DesktopMediaID::Id>(widget);
+#else
+ window.id = widget;
+#endif
+
+ // Get the aura window's id.
+ DesktopMediaID aura_id = DesktopMediaID::RegisterNativeWindow(
+ DesktopMediaID::TYPE_WINDOW, aura_window);
+ native_aura_id_map_[window.id] = aura_id.window_id;
+
+ window_list_.push_back(window);
+ }
+
+ void RemoveAuraWindow(int index) {
+ DCHECK_LT(index, static_cast<int>(desktop_widgets_.size()));
+
+ // Get the native window's id.
+ aura::Window* aura_window = desktop_widgets_[index]->GetNativeWindow();
+ gfx::AcceleratedWidget widget =
+ aura_window->GetHost()->GetAcceleratedWidget();
+#if defined(OS_WIN)
+ int native_id = reinterpret_cast<DesktopMediaID::Id>(widget);
+#else
+ int native_id = widget;
+#endif
+ // Remove the widget and associated aura window.
+ desktop_widgets_.erase(desktop_widgets_.begin() + index);
+ // Remove the aura window from the window list.
+ size_t i;
+ for (i = 0; i < window_list_.size(); i++) {
+ if (window_list_[i].id == native_id)
+ break;
+ }
+ DCHECK_LT(i, window_list_.size());
+ window_list_.erase(window_list_.begin() + i);
+ native_aura_id_map_.erase(native_id);
+ }
+
+#endif // defined(USE_AURA)
+
+ void AddWindowsAndVerify(bool has_view_dialog) {
+ window_capturer_ = new FakeWindowCapturer();
+ model_ = std::make_unique<NativeDesktopMediaList>(
+ DesktopMediaID::TYPE_WINDOW, base::WrapUnique(window_capturer_));
+
+ // Set update period to reduce the time it takes to run tests.
+ model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
+
+ // Set up widows.
+ size_t aura_window_first_index = kDefaultWindowCount - kDefaultAuraCount;
+ for (size_t i = 0; i < kDefaultWindowCount; ++i) {
+ if (i < aura_window_first_index) {
+ AddNativeWindow(i);
+ } else {
+#if defined(USE_AURA)
+ AddAuraWindow();
+#endif
+ }
+ }
+
+ if (window_capturer_)
+ window_capturer_->SetWindowList(window_list_);
+
+ size_t window_count = kDefaultWindowCount;
+
+ // Set view dialog window ID as the first window id.
+ if (has_view_dialog) {
+ DesktopMediaID dialog_window_id(DesktopMediaID::TYPE_WINDOW,
+ window_list_[0].id);
+ model_->SetViewDialogWindowId(dialog_window_id);
+ window_count--;
+ aura_window_first_index--;
+ }
+
+ base::RunLoop run_loop;
+
+ {
+ testing::InSequence dummy;
+ for (size_t i = 0; i < window_count; ++i) {
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), i))
+ .WillOnce(CheckListSize(model_.get(), static_cast<int>(i + 1)));
+ }
+ for (size_t i = 0; i < window_count - 1; ++i) {
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), i));
+ }
+ EXPECT_CALL(observer_,
+ OnSourceThumbnailChanged(model_.get(), window_count - 1))
+ .WillOnce(
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+ }
+ model_->StartUpdating(&observer_);
+ run_loop.Run();
+
+ for (size_t i = 0; i < window_count; ++i) {
+ EXPECT_EQ(model_->GetSource(i).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(i).name, base::UTF8ToUTF16("Test window"));
+ int index = has_view_dialog ? i + 1 : i;
+ int native_id = window_list_[index].id;
+ EXPECT_EQ(model_->GetSource(i).id.id, native_id);
+#if defined(USE_AURA)
+ if (i >= aura_window_first_index)
+ EXPECT_EQ(model_->GetSource(i).id.window_id,
+ native_aura_id_map_[native_id]);
+#endif
+ }
+ testing::Mock::VerifyAndClearExpectations(&observer_);
+ }
+
+ protected:
+ // Must be listed before |model_|, so it's destroyed last.
+ MockObserver observer_;
+
+ // Owned by |model_|;
+ FakeWindowCapturer* window_capturer_;
+
+ webrtc::DesktopCapturer::SourceList window_list_;
+ std::vector<std::unique_ptr<views::Widget>> desktop_widgets_;
+ std::map<DesktopMediaID::Id, DesktopMediaID::Id> native_aura_id_map_;
+ std::unique_ptr<NativeDesktopMediaList> model_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaListTest);
+};
+
+TEST_F(NativeDesktopMediaListTest, Windows) {
+ AddWindowsAndVerify(false);
+}
+
+TEST_F(NativeDesktopMediaListTest, ScreenOnly) {
+ model_ = std::make_unique<NativeDesktopMediaList>(
+ DesktopMediaID::TYPE_SCREEN, std::make_unique<FakeScreenCapturer>());
+
+ // Set update period to reduce the time it takes to run tests.
+ model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
+
+ base::RunLoop run_loop;
+
+ {
+ testing::InSequence dummy;
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), 0))
+ .WillOnce(CheckListSize(model_.get(), 1));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 0))
+ .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+ }
+ model_->StartUpdating(&observer_);
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_SCREEN);
+ EXPECT_EQ(model_->GetSource(0).id.id, 0);
+}
+
+// Verifies that the window specified with SetViewDialogWindowId() is filtered
+// from the results.
+TEST_F(NativeDesktopMediaListTest, WindowFiltering) {
+ AddWindowsAndVerify(true);
+}
+
+TEST_F(NativeDesktopMediaListTest, AddNativeWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ const int index = kDefaultWindowCount;
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ AddNativeWindow(index);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(index).id.id, index);
+}
+
+#if defined(ENABLE_AURA_WINDOW_TESTS)
+TEST_F(NativeDesktopMediaListTest, AddAuraWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ const int index = kDefaultWindowCount;
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ AddAuraWindow();
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ int native_id = window_list_.back().id;
+ EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(index).id.id, native_id);
+ EXPECT_EQ(model_->GetSource(index).id.window_id,
+ native_aura_id_map_[native_id]);
+}
+#endif // defined(ENABLE_AURA_WINDOW_TESTS)
+
+TEST_F(NativeDesktopMediaListTest, RemoveNativeWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ window_list_.erase(window_list_.begin());
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+
+#if defined(ENABLE_AURA_WINDOW_TESTS)
+TEST_F(NativeDesktopMediaListTest, RemoveAuraWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ int aura_window_start_index = kDefaultWindowCount - kDefaultAuraCount;
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), aura_window_start_index))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ RemoveAuraWindow(0);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+#endif // defined(ENABLE_AURA_WINDOW_TESTS)
+
+TEST_F(NativeDesktopMediaListTest, RemoveAllWindows) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ testing::InSequence seq;
+ for (int i = 0; i < kDefaultWindowCount - 1; i++) {
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
+ .WillOnce(CheckListSize(model_.get(), kDefaultWindowCount - i - 1));
+ }
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), 0),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ window_list_.clear();
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+
+TEST_F(NativeDesktopMediaListTest, UpdateTitle) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceNameChanged(model_.get(), 0))
+ .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+
+ const std::string kTestTitle = "New Title";
+ window_list_[0].title = kTestTitle;
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(0).name, base::UTF8ToUTF16(kTestTitle));
+}
+
+TEST_F(NativeDesktopMediaListTest, UpdateThumbnail) {
+ AddWindowsAndVerify(false);
+
+ // Aura windows' thumbnails may unpredictably change over time.
+ for (size_t i = kDefaultWindowCount - kDefaultAuraCount;
+ i < kDefaultWindowCount; ++i) {
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), i))
+ .Times(testing::AnyNumber());
+ }
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 0))
+ .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+
+ // Update frame for the window and verify that we get notification about it.
+ window_capturer_->SetNextFrameValue(0, 10);
+
+ run_loop.Run();
+}
+
+TEST_F(NativeDesktopMediaListTest, MoveWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceMoved(model_.get(), 1, 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ std::swap(window_list_[0], window_list_[1]);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+
+// This test verifies that webrtc::DesktopCapturer::CaptureFrame() is not
+// called when the thumbnail size is empty.
+TEST_F(NativeDesktopMediaListTest, EmptyThumbnail) {
+ window_capturer_ = new FakeWindowCapturer();
+ model_ = std::make_unique<NativeDesktopMediaList>(
+ DesktopMediaID::TYPE_WINDOW, base::WrapUnique(window_capturer_));
+ model_->SetThumbnailSize(gfx::Size());
+
+ // Set update period to reduce the time it takes to run tests.
+ model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+ // Called upon webrtc::DesktopCapturer::CaptureFrame() call.
+ ON_CALL(observer_, OnSourceThumbnailChanged(_, _))
+ .WillByDefault(testing::InvokeWithoutArgs([]() { NOTREACHED(); }));
+
+ model_->StartUpdating(&observer_);
+
+ AddNativeWindow(0);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(0).id.id, 0);
+ EXPECT_EQ(model_->GetSource(0).thumbnail.size(), gfx::Size());
+}
diff --git a/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
new file mode 100644
index 00000000000..c6303424cf2
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -0,0 +1,324 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/metrics/field_trial.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/permissions/permission_manager.h"
+#include "chrome/browser/permissions/permission_result.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+
+#if defined(OS_ANDROID)
+#include <vector>
+
+#include "chrome/browser/android/chrome_feature_list.h"
+#include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
+#include "chrome/browser/permissions/permission_uma_util.h"
+#include "chrome/browser/permissions/permission_util.h"
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_MACOSX)
+#include "base/metrics/histogram_macros.h"
+#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
+#endif
+
+using content::BrowserThread;
+
+using RepeatingMediaResponseCallback =
+ base::RepeatingCallback<void(const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui)>;
+
+#if defined(OS_MACOSX)
+using system_media_permissions::SystemPermission;
+#endif
+
+struct PermissionBubbleMediaAccessHandler::PendingAccessRequest {
+ PendingAccessRequest(const content::MediaStreamRequest& request,
+ RepeatingMediaResponseCallback callback)
+ : request(request), callback(callback) {}
+ ~PendingAccessRequest() {}
+
+ // TODO(gbillock): make the MediaStreamDevicesController owned by
+ // this object when we're using bubbles.
+ content::MediaStreamRequest request;
+ RepeatingMediaResponseCallback callback;
+};
+
+PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() {
+ // PermissionBubbleMediaAccessHandler should be created on UI thread.
+ // Otherwise, it will not receive
+ // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
+ // possible use after free.
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ notifications_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::NotificationService::AllSources());
+}
+
+PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() {}
+
+bool PermissionBubbleMediaAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+#if defined(OS_ANDROID)
+ return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+#else
+ return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
+#endif
+}
+
+bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(render_frame_host);
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ ContentSettingsType content_settings_type =
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
+ ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
+ : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA;
+
+ DCHECK(!security_origin.is_empty());
+ GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
+ PermissionManager* permission_manager = PermissionManager::Get(profile);
+ return permission_manager
+ ->GetPermissionStatusForFrame(content_settings_type,
+ render_frame_host, security_origin)
+ .content_setting == CONTENT_SETTING_ALLOW;
+}
+
+void PermissionBubbleMediaAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+#if defined(OS_ANDROID)
+ if (blink::IsScreenCaptureMediaType(request.video_type) &&
+ !base::FeatureList::IsEnabled(
+ chrome::android::kUserMediaScreenCapturing)) {
+ // If screen capturing isn't enabled on Android, we'll use "invalid state"
+ // as result, same as on desktop.
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+#endif // defined(OS_ANDROID)
+
+ RequestsMap& requests_map = pending_requests_[web_contents];
+ requests_map.emplace(
+ next_request_id_++,
+ PendingAccessRequest(
+ request, base::AdaptCallbackForRepeating(std::move(callback))));
+
+ // If this is the only request then show the infobar.
+ if (requests_map.size() == 1)
+ ProcessQueuedAccessRequest(web_contents);
+}
+
+void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest(
+ content::WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = pending_requests_.find(web_contents);
+
+ if (it == pending_requests_.end() || it->second.empty()) {
+ // Don't do anything if the tab was closed.
+ return;
+ }
+
+ DCHECK(!it->second.empty());
+
+ const int request_id = it->second.begin()->first;
+ const content::MediaStreamRequest& request =
+ it->second.begin()->second.request;
+#if defined(OS_ANDROID)
+ if (blink::IsScreenCaptureMediaType(request.video_type)) {
+ ScreenCaptureInfoBarDelegateAndroid::Create(
+ web_contents, request,
+ base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ base::Unretained(this), web_contents, request_id));
+ return;
+ }
+#endif
+
+ MediaStreamDevicesController::RequestPermissions(
+ request,
+ base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ base::Unretained(this), web_contents, request_id));
+}
+
+void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (state != content::MEDIA_REQUEST_STATE_CLOSING)
+ return;
+
+ bool found = false;
+ for (auto requests_it = pending_requests_.begin();
+ requests_it != pending_requests_.end(); ++requests_it) {
+ RequestsMap& requests_map = requests_it->second;
+ for (RequestsMap::iterator it = requests_map.begin();
+ it != requests_map.end(); ++it) {
+ if (it->second.request.render_process_id == render_process_id &&
+ it->second.request.render_frame_id == render_frame_id &&
+ it->second.request.page_request_id == page_request_id) {
+ requests_map.erase(it);
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+}
+
+void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse(
+ content::WebContents* web_contents,
+ int request_id,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto request_maps_it = pending_requests_.find(web_contents);
+ if (request_maps_it == pending_requests_.end()) {
+ // WebContents has been destroyed. Don't need to do anything.
+ return;
+ }
+
+ RequestsMap& requests_map(request_maps_it->second);
+ if (requests_map.empty())
+ return;
+
+ auto request_it = requests_map.find(request_id);
+ DCHECK(request_it != requests_map.end());
+ if (request_it == requests_map.end())
+ return;
+
+ blink::mojom::MediaStreamRequestResult final_result = result;
+
+#if defined(OS_MACOSX)
+ // If the request was approved, ask for system permissions if needed, and run
+ // this function again when done.
+ if (result == blink::mojom::MediaStreamRequestResult::OK) {
+ const content::MediaStreamRequest& request = request_it->second.request;
+ if (request.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
+ const SystemPermission system_audio_permission =
+ system_media_permissions::CheckSystemAudioCapturePermission();
+ UMA_HISTOGRAM_ENUMERATION(
+ "Media.Audio.Capture.Mac.MicSystemPermission.UserMedia",
+ system_audio_permission);
+ if (system_audio_permission == SystemPermission::kNotDetermined) {
+ // Using WeakPtr since callback can come at any time and we might be
+ // destroyed.
+ system_media_permissions::RequestSystemAudioCapturePermisson(
+ base::BindOnce(
+ &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
+ result, std::move(ui)),
+ {content::BrowserThread::UI});
+ return;
+ } else if (system_audio_permission == SystemPermission::kRestricted ||
+ system_audio_permission == SystemPermission::kDenied) {
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ final_result =
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
+ system_media_permissions::SystemAudioCapturePermissionBlocked();
+ } else {
+ DCHECK_EQ(system_audio_permission, SystemPermission::kAllowed);
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ }
+ }
+ if (request.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
+ const SystemPermission system_video_permission =
+ system_media_permissions::CheckSystemVideoCapturePermission();
+ UMA_HISTOGRAM_ENUMERATION(
+ "Media.Video.Capture.Mac.CameraSystemPermission.UserMedia",
+ system_video_permission);
+ if (system_video_permission == SystemPermission::kNotDetermined) {
+ // Using WeakPtr since callback can come at any time and we might be
+ // destroyed.
+ system_media_permissions::RequestSystemVideoCapturePermisson(
+ base::BindOnce(
+ &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
+ result, std::move(ui)),
+ {content::BrowserThread::UI});
+ return;
+ } else if (system_video_permission == SystemPermission::kRestricted ||
+ system_video_permission == SystemPermission::kDenied) {
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ final_result =
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
+ system_media_permissions::SystemVideoCapturePermissionBlocked();
+ } else {
+ DCHECK_EQ(system_video_permission, SystemPermission::kAllowed);
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ }
+ }
+ }
+#endif // defined(OS_MACOSX)
+
+ RepeatingMediaResponseCallback callback =
+ std::move(request_it->second.callback);
+ requests_map.erase(request_it);
+
+ if (!requests_map.empty()) {
+ // Post a task to process next queued request. It has to be done
+ // asynchronously to make sure that calling infobar is not destroyed until
+ // after this function returns.
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest,
+ base::Unretained(this), web_contents));
+ }
+
+ std::move(callback).Run(devices, final_result, std::move(ui));
+}
+
+void PermissionBubbleMediaAccessHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+ pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
diff --git a/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
new file mode 100644
index 00000000000..53da4bb7a94
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
@@ -0,0 +1,67 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_PERMISSION_BUBBLE_MEDIA_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_PERMISSION_BUBBLE_MEDIA_ACCESS_HANDLER_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+// MediaAccessHandler for permission bubble requests.
+class PermissionBubbleMediaAccessHandler
+ : public MediaAccessHandler,
+ public content::NotificationObserver {
+ public:
+ PermissionBubbleMediaAccessHandler();
+ ~PermissionBubbleMediaAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+ void UpdateMediaRequestState(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+
+ private:
+ struct PendingAccessRequest;
+ using RequestsMap = std::map<int, PendingAccessRequest>;
+ using RequestsMaps = std::map<content::WebContents*, RequestsMap>;
+
+ void ProcessQueuedAccessRequest(content::WebContents* web_contents);
+ void OnAccessRequestResponse(content::WebContents* web_contents,
+ int request_id,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui);
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ int next_request_id_ = 0;
+ RequestsMaps pending_requests_;
+ content::NotificationRegistrar notifications_registrar_;
+
+ base::WeakPtrFactory<PermissionBubbleMediaAccessHandler> weak_factory_{this};
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_PERMISSION_BUBBLE_MEDIA_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/rtp_dump_type.h b/chromium/chrome/browser/media/webrtc/rtp_dump_type.h
new file mode 100644
index 00000000000..e4fc33dc8fd
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/rtp_dump_type.h
@@ -0,0 +1,12 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_RTP_DUMP_TYPE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_RTP_DUMP_TYPE_H_
+
+// The types of RTP header dumps: incoming packets only, outgoing packets only,
+// and both incoming and outgoing packets.
+enum RtpDumpType { RTP_DUMP_INCOMING, RTP_DUMP_OUTGOING, RTP_DUMP_BOTH };
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_RTP_DUMP_TYPE_H_
diff --git a/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
new file mode 100644
index 00000000000..d891933c061
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
@@ -0,0 +1,109 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
+
+#include "base/callback_helpers.h"
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/infobars/core/infobar.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "ui/base/l10n/l10n_util.h"
+
+// static
+void ScreenCaptureInfoBarDelegateAndroid::Create(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback) {
+ InfoBarService* infobar_service =
+ InfoBarService::FromWebContents(web_contents);
+
+ infobar_service->AddInfoBar(infobar_service->CreateConfirmInfoBar(
+ std::unique_ptr<ConfirmInfoBarDelegate>(
+ new ScreenCaptureInfoBarDelegateAndroid(web_contents, request,
+ std::move(callback)))));
+}
+
+ScreenCaptureInfoBarDelegateAndroid::ScreenCaptureInfoBarDelegateAndroid(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback)
+ : web_contents_(web_contents),
+ request_(request),
+ callback_(std::move(callback)) {
+ DCHECK_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ request.video_type);
+}
+
+ScreenCaptureInfoBarDelegateAndroid::~ScreenCaptureInfoBarDelegateAndroid() {
+ if (!callback_.is_null()) {
+ std::move(callback_).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ nullptr);
+ }
+}
+
+infobars::InfoBarDelegate::InfoBarIdentifier
+ScreenCaptureInfoBarDelegateAndroid::GetIdentifier() const {
+ return SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID;
+}
+
+base::string16 ScreenCaptureInfoBarDelegateAndroid::GetMessageText() const {
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_CAPTURE_SCREEN_INFOBAR_TEXT,
+ url_formatter::FormatUrlForSecurityDisplay(request_.security_origin));
+}
+
+int ScreenCaptureInfoBarDelegateAndroid::GetIconId() const {
+ return IDR_ANDROID_INFOBAR_MEDIA_STREAM_SCREEN;
+}
+
+base::string16 ScreenCaptureInfoBarDelegateAndroid::GetButtonLabel(
+ InfoBarButton button) const {
+ return l10n_util::GetStringUTF16((button == BUTTON_OK) ? IDS_PERMISSION_ALLOW
+ : IDS_PERMISSION_DENY);
+}
+
+bool ScreenCaptureInfoBarDelegateAndroid::Accept() {
+ RunCallback(blink::mojom::MediaStreamRequestResult::OK);
+ return true;
+}
+
+bool ScreenCaptureInfoBarDelegateAndroid::Cancel() {
+ RunCallback(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED);
+ return true;
+}
+
+void ScreenCaptureInfoBarDelegateAndroid::InfoBarDismissed() {
+ RunCallback(blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED);
+}
+
+void ScreenCaptureInfoBarDelegateAndroid::RunCallback(
+ blink::mojom::MediaStreamRequestResult result) {
+ DCHECK(!callback_.is_null());
+
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (result == blink::mojom::MediaStreamRequestResult::OK) {
+ content::DesktopMediaID screen_id = content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId);
+ devices.push_back(blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ screen_id.ToString(), "Screen"));
+
+ ui = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents_, devices);
+ }
+
+ std::move(callback_).Run(devices, result, std::move(ui));
+}
diff --git a/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h
new file mode 100644
index 00000000000..829212ce022
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h
@@ -0,0 +1,52 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID_H_
+
+#include "chrome/browser/media/media_access_handler.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+namespace content {
+class WebContents;
+}
+
+// An infobar that allows the user to share their screen with the current page.
+class ScreenCaptureInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
+ public:
+ // Creates a screen capture infobar and delegate and adds the infobar to the
+ // InfoBarService associated with |web_contents|.
+ static void Create(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+
+ private:
+ ScreenCaptureInfoBarDelegateAndroid(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+ ~ScreenCaptureInfoBarDelegateAndroid() override;
+
+ // ConfirmInfoBarDelegate:
+ infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
+ base::string16 GetMessageText() const override;
+ int GetIconId() const override;
+ base::string16 GetButtonLabel(InfoBarButton button) const override;
+ bool Accept() override;
+ bool Cancel() override;
+ void InfoBarDismissed() override;
+
+ // Runs |callback_|, passing it the |result|, and (if permission was granted)
+ // the appropriate stream device and UI object for video capture.
+ void RunCallback(blink::mojom::MediaStreamRequestResult result);
+
+ content::WebContents* web_contents_;
+ const content::MediaStreamRequest request_;
+ content::MediaResponseCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureInfoBarDelegateAndroid);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID_H_
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
new file mode 100644
index 00000000000..73b04535322
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
+
+#include "base/callback_forward.h"
+
+namespace base {
+class TaskTraits;
+}
+
+namespace system_media_permissions {
+
+class MediaAuthorizationWrapper;
+
+// System permission state. These are also used in stats - do not remove or
+// re-arrange the values.
+enum class SystemPermission {
+ kNotDetermined = 0,
+ kRestricted = 1,
+ kDenied = 2,
+ kAllowed = 3,
+ kMaxValue = kAllowed
+};
+
+// On 10.14 and above: returns the system permission.
+// On 10.13 and below: returns |SystemPermission::kAllowed|, since there are no
+// system media capture permissions.
+SystemPermission CheckSystemAudioCapturePermission();
+SystemPermission CheckSystemVideoCapturePermission();
+
+// On 10.15 and above: returns the system permission.
+// On 10.14 and below: returns |SystemPermission::kAllowed|, since there are no
+// system screen capture permissions.
+SystemPermission CheckSystemScreenCapturePermission();
+
+// On 10.14 and above: requests system permission and returns. When requesting
+// permission, the OS will show a user dialog and respond asynchronously. At the
+// response, |callback| is posted with |traits|.
+// On 10.13 and below: posts |callback| with |traits|, since there are no system
+// media capture permissions.
+// Note: these functions should really never be called for pre-10.14 since one
+// would normally check the permission first, and only call this if it's not
+// determined.
+void RequestSystemAudioCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits);
+void RequestSystemVideoCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits);
+
+// Sets the wrapper object for OS calls. For test mocking purposes.
+void SetMediaAuthorizationWrapperForTesting(MediaAuthorizationWrapper* wrapper);
+
+} // namespace system_media_permissions
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
new file mode 100644
index 00000000000..66db2533c01
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
@@ -0,0 +1,246 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Authorization functions and types are available on 10.14+.
+// To avoid availability compile errors, use performSelector invocation of
+// functions, NSInteger instead of AVAuthorizationStatus, and NSString* instead
+// of AVMediaType.
+// The AVAuthorizationStatus enum is defined as follows (10.14 SDK):
+// AVAuthorizationStatusNotDetermined = 0,
+// AVAuthorizationStatusRestricted = 1,
+// AVAuthorizationStatusDenied = 2,
+// AVAuthorizationStatusAuthorized = 3,
+// TODO(grunell): Call functions directly and use AVAuthorizationStatus once
+// we use the 10.14 SDK.
+
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/browser/media/webrtc/media_authorization_wrapper_mac.h"
+#include "chrome/common/chrome_features.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/base/media_switches.h"
+
+namespace system_media_permissions {
+
+namespace {
+
+bool UsingFakeMediaDevices() {
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream);
+}
+
+// Pointer to OS call wrapper that tests can set.
+MediaAuthorizationWrapper* g_media_authorization_wrapper_for_tests = nullptr;
+
+// Implementation of OS call wrapper that does the actual OS calls.
+class MediaAuthorizationWrapperImpl : public MediaAuthorizationWrapper {
+ public:
+ MediaAuthorizationWrapperImpl() = default;
+ ~MediaAuthorizationWrapperImpl() final = default;
+
+ NSInteger AuthorizationStatusForMediaType(NSString* media_type) final {
+ if (@available(macOS 10.14, *)) {
+ AVCaptureDevice* target = [AVCaptureDevice class];
+ SEL selector = @selector(authorizationStatusForMediaType:);
+ NSInteger auth_status = 0;
+ if ([target respondsToSelector:selector]) {
+ auth_status =
+ (NSInteger)[target performSelector:selector withObject:media_type];
+ } else {
+ DLOG(WARNING)
+ << "authorizationStatusForMediaType could not be executed";
+ }
+ return auth_status;
+ }
+
+ NOTREACHED();
+ return 0;
+ }
+
+ void RequestAccessForMediaType(NSString* media_type,
+ base::RepeatingClosure callback,
+ const base::TaskTraits& traits) final {
+ if (@available(macOS 10.14, *)) {
+ AVCaptureDevice* target = [AVCaptureDevice class];
+ SEL selector = @selector(requestAccessForMediaType:completionHandler:);
+ if ([target respondsToSelector:selector]) {
+ [target performSelector:selector
+ withObject:media_type
+ withObject:^(BOOL granted) {
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }];
+ } else {
+ DLOG(WARNING) << "requestAccessForMediaType could not be executed";
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }
+ } else {
+ NOTREACHED();
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaAuthorizationWrapperImpl);
+};
+
+MediaAuthorizationWrapper& GetMediaAuthorizationWrapper() {
+ if (g_media_authorization_wrapper_for_tests)
+ return *g_media_authorization_wrapper_for_tests;
+
+ static base::NoDestructor<MediaAuthorizationWrapperImpl>
+ media_authorization_wrapper;
+ return *media_authorization_wrapper;
+}
+
+NSInteger MediaAuthorizationStatus(NSString* media_type) {
+ if (@available(macOS 10.14, *)) {
+ return GetMediaAuthorizationWrapper().AuthorizationStatusForMediaType(
+ media_type);
+ }
+
+ NOTREACHED();
+ return 0;
+}
+
+SystemPermission CheckSystemMediaCapturePermission(NSString* media_type) {
+ if (UsingFakeMediaDevices())
+ return SystemPermission::kAllowed;
+
+ if (@available(macOS 10.14, *)) {
+ NSInteger auth_status = MediaAuthorizationStatus(media_type);
+ switch (auth_status) {
+ case 0:
+ return SystemPermission::kNotDetermined;
+ case 1:
+ return SystemPermission::kRestricted;
+ case 2:
+ return SystemPermission::kDenied;
+ case 3:
+ return SystemPermission::kAllowed;
+ default:
+ NOTREACHED();
+ return SystemPermission::kAllowed;
+ }
+ }
+
+ // On pre-10.14, there are no system permissions, so we return allowed.
+ return SystemPermission::kAllowed;
+}
+
+// Use RepeatingCallback since it must be copyable for use in the block. It's
+// only called once though.
+void RequestSystemMediaCapturePermission(NSString* media_type,
+ base::RepeatingClosure callback,
+ const base::TaskTraits& traits) {
+ if (UsingFakeMediaDevices()) {
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ return;
+ }
+
+ if (@available(macOS 10.14, *)) {
+ GetMediaAuthorizationWrapper().RequestAccessForMediaType(
+ media_type, std::move(callback), traits);
+ } else {
+ NOTREACHED();
+ // Should never happen since for pre-10.14 system permissions don't exist
+ // and checking them in CheckSystemAudioCapturePermission() will always
+ // return allowed, and this function should not be called.
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }
+}
+
+// Heuristic to check screen capture permission on macOS 10.15.
+// Screen Capture is considered allowed if the name of at least one normal
+// or dock window running on another process is visible.
+// See https://crbug.com/993692.
+bool IsScreenCaptureAllowed() {
+ if (@available(macOS 10.15, *)) {
+ if (!base::FeatureList::IsEnabled(
+ features::kMacSystemScreenCapturePermissionCheck)) {
+ return true;
+ }
+
+ base::ScopedCFTypeRef<CFArrayRef> window_list(
+ CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID));
+ int current_pid = [[NSProcessInfo processInfo] processIdentifier];
+ for (NSDictionary* window in base::mac::CFToNSCast(window_list.get())) {
+ NSNumber* window_pid =
+ [window objectForKey:base::mac::CFToNSCast(kCGWindowOwnerPID)];
+ if (!window_pid || [window_pid integerValue] == current_pid)
+ continue;
+
+ NSString* window_name =
+ [window objectForKey:base::mac::CFToNSCast(kCGWindowName)];
+ if (!window_name)
+ continue;
+
+ NSNumber* layer =
+ [window objectForKey:base::mac::CFToNSCast(kCGWindowLayer)];
+ if (!layer)
+ continue;
+
+ NSInteger layer_integer = [layer integerValue];
+ if (layer_integer == CGWindowLevelForKey(kCGNormalWindowLevelKey) ||
+ layer_integer == CGWindowLevelForKey(kCGDockWindowLevelKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Screen capture is always allowed in older macOS versions.
+ return true;
+}
+
+} // namespace
+
+SystemPermission CheckSystemAudioCapturePermission() {
+ return CheckSystemMediaCapturePermission(AVMediaTypeAudio);
+}
+
+SystemPermission CheckSystemVideoCapturePermission() {
+ return CheckSystemMediaCapturePermission(AVMediaTypeVideo);
+}
+
+SystemPermission CheckSystemScreenCapturePermission() {
+ return IsScreenCaptureAllowed() ? SystemPermission::kAllowed
+ : SystemPermission::kDenied;
+}
+
+void RequestSystemAudioCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits) {
+ RequestSystemMediaCapturePermission(
+ AVMediaTypeAudio, base::AdaptCallbackForRepeating(std::move(callback)),
+ traits);
+}
+
+void RequestSystemVideoCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits) {
+ RequestSystemMediaCapturePermission(
+ AVMediaTypeVideo, base::AdaptCallbackForRepeating(std::move(callback)),
+ traits);
+}
+
+void SetMediaAuthorizationWrapperForTesting(
+ MediaAuthorizationWrapper* wrapper) {
+ CHECK(!g_media_authorization_wrapper_for_tests);
+ g_media_authorization_wrapper_for_tests = wrapper;
+}
+
+} // namespace system_media_permissions
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h
new file mode 100644
index 00000000000..9e1269beb47
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Functions for handling stats for system media permissions (camera,
+// microphone).
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_STATS_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_STATS_MAC_H_
+
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+
+class PrefRegistrySimple;
+
+namespace system_media_permissions {
+
+// Registers preferences used for system media permissions stats.
+void RegisterSystemMediaPermissionStatesPrefs(PrefRegistrySimple* registry);
+
+// Logs stats for system media permissions. Called once per browser session, at
+// browser start.
+void LogSystemMediaPermissionsStartupStats();
+
+// Called when a system permission goes from "not determined" to another state.
+// The new permission is logged as startup state.
+void SystemAudioCapturePermissionDetermined(SystemPermission permission);
+void SystemVideoCapturePermissionDetermined(SystemPermission permission);
+
+// Called when a system permission was requested but was blocked. Information
+// stored is later used when logging stats at startup.
+void SystemAudioCapturePermissionBlocked();
+void SystemVideoCapturePermissionBlocked();
+
+} // namespace system_media_permissions
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_STATS_MAC_H_
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm
new file mode 100644
index 00000000000..8c85e68cb36
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm
@@ -0,0 +1,196 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace system_media_permissions {
+
+namespace {
+
+const char kSystemPermissionMicFirstBlockedTimePref[] =
+ "system_permission.mic.first_blocked_time";
+const char kSystemPermissionMicLastBlockedTimePref[] =
+ "system_permission.mic.last_blocked_time";
+const char kSystemPermissionCameraFirstBlockedTimePref[] =
+ "system_permission.camera.first_blocked_time";
+const char kSystemPermissionCameraLastBlockedTimePref[] =
+ "system_permission.camera.last_blocked_time";
+
+void LogStartupMicSystemPermission(SystemPermission permission) {
+ base::UmaHistogramEnumeration(
+ "Media.Audio.Capture.Mac.MicSystemPermission.Startup", permission);
+}
+
+void LogStartupCameraSystemPermission(SystemPermission permission) {
+ base::UmaHistogramEnumeration(
+ "Media.Video.Capture.Mac.CameraSystemPermission.Startup", permission);
+}
+
+void MaybeLogAdditionalMicSystemPermissionStats(SystemPermission permission) {
+ PrefService* prefs = g_browser_process->local_state();
+
+ if (!prefs->HasPrefPath(kSystemPermissionMicFirstBlockedTimePref)) {
+ DCHECK(!prefs->HasPrefPath(kSystemPermissionMicLastBlockedTimePref));
+ return;
+ }
+
+ // A pref exists, so there was a failure accessing the mic due to blocked
+ // system permission before the last restart. Log additional stats.
+
+ DCHECK(prefs->HasPrefPath(kSystemPermissionMicLastBlockedTimePref));
+ base::UmaHistogramEnumeration("Media.Audio.Capture.Mac.MicSystemPermission."
+ "StartupAfterFailure",
+ permission);
+
+ // If the state has changed to allowed, log the time it took since first
+ // and last failure before restart. Check for positive time delta, since
+ // the system clock may change at any time.
+ if (permission == SystemPermission::kAllowed) {
+ base::Time stored_time =
+ prefs->GetTime(kSystemPermissionMicFirstBlockedTimePref);
+ base::TimeDelta time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Audio.Capture.Mac.MicSystemPermission."
+ "FixedTime.SinceFirstFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+
+ stored_time = prefs->GetTime(kSystemPermissionMicLastBlockedTimePref);
+ time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Audio.Capture.Mac.MicSystemPermission."
+ "FixedTime.SinceLastFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+ }
+
+ prefs->ClearPref(kSystemPermissionMicFirstBlockedTimePref);
+ prefs->ClearPref(kSystemPermissionMicLastBlockedTimePref);
+}
+
+void MaybeLogAdditionalCameraSystemPermissionStats(
+ SystemPermission permission) {
+ PrefService* prefs = g_browser_process->local_state();
+
+ if (!prefs->HasPrefPath(kSystemPermissionCameraFirstBlockedTimePref)) {
+ DCHECK(!prefs->HasPrefPath(kSystemPermissionCameraLastBlockedTimePref));
+ return;
+ }
+
+ // A pref exists, so there was a failure accessing the camera due to blocked
+ // system permission before the last restart. Log additional stats.
+
+ DCHECK(prefs->HasPrefPath(kSystemPermissionCameraLastBlockedTimePref));
+ base::UmaHistogramEnumeration(
+ "Media.Video.Capture.Mac.CameraSystemPermission."
+ "StartupAfterFailure",
+ permission);
+
+ // If the state has changed to allowed, log the time it took since first
+ // and last failure before restart. Check for positive time delta, since
+ // the system clock may change at any time.
+ if (permission == SystemPermission::kAllowed) {
+ base::Time stored_time =
+ prefs->GetTime(kSystemPermissionCameraFirstBlockedTimePref);
+ base::TimeDelta time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Video.Capture.Mac.CameraSystemPermission.FixedTime."
+ "SinceFirstFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+
+ stored_time = prefs->GetTime(kSystemPermissionCameraLastBlockedTimePref);
+ time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Video.Capture.Mac.CameraSystemPermission.FixedTime."
+ "SinceLastFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+ }
+
+ prefs->ClearPref(kSystemPermissionCameraFirstBlockedTimePref);
+ prefs->ClearPref(kSystemPermissionCameraLastBlockedTimePref);
+}
+
+} // namespace
+
+void RegisterSystemMediaPermissionStatesPrefs(PrefRegistrySimple* registry) {
+ if (@available(macOS 10.14, *)) {
+ registry->RegisterTimePref(kSystemPermissionMicFirstBlockedTimePref,
+ base::Time());
+ registry->RegisterTimePref(kSystemPermissionMicLastBlockedTimePref,
+ base::Time());
+ registry->RegisterTimePref(kSystemPermissionCameraFirstBlockedTimePref,
+ base::Time());
+ registry->RegisterTimePref(kSystemPermissionCameraLastBlockedTimePref,
+ base::Time());
+ }
+}
+
+void LogSystemMediaPermissionsStartupStats() {
+ if (@available(macOS 10.14, *)) {
+ const SystemPermission audio_permission =
+ CheckSystemAudioCapturePermission();
+ LogStartupMicSystemPermission(audio_permission);
+ MaybeLogAdditionalMicSystemPermissionStats(audio_permission);
+
+ const SystemPermission video_permission =
+ CheckSystemVideoCapturePermission();
+ LogStartupCameraSystemPermission(video_permission);
+ MaybeLogAdditionalCameraSystemPermissionStats(video_permission);
+ } // (@available(macOS 10.14, *))
+}
+
+void SystemAudioCapturePermissionDetermined(SystemPermission permission) {
+ if (@available(macOS 10.14, *)) {
+ DCHECK_NE(permission, SystemPermission::kNotDetermined);
+ LogStartupMicSystemPermission(permission);
+ }
+}
+
+void SystemVideoCapturePermissionDetermined(SystemPermission permission) {
+ if (@available(macOS 10.14, *)) {
+ DCHECK_NE(permission, SystemPermission::kNotDetermined);
+ LogStartupCameraSystemPermission(permission);
+ }
+}
+
+void SystemAudioCapturePermissionBlocked() {
+ if (@available(macOS 10.14, *)) {
+ PrefService* prefs = g_browser_process->local_state();
+ if (!prefs->HasPrefPath(kSystemPermissionMicFirstBlockedTimePref)) {
+ prefs->SetTime(kSystemPermissionMicFirstBlockedTimePref,
+ base::Time::Now());
+ }
+ prefs->SetTime(kSystemPermissionMicLastBlockedTimePref, base::Time::Now());
+ }
+}
+
+void SystemVideoCapturePermissionBlocked() {
+ if (@available(macOS 10.14, *)) {
+ PrefService* prefs = g_browser_process->local_state();
+ if (!prefs->HasPrefPath(kSystemPermissionCameraFirstBlockedTimePref)) {
+ prefs->SetTime(kSystemPermissionCameraFirstBlockedTimePref,
+ base::Time::Now());
+ }
+ prefs->SetTime(kSystemPermissionCameraLastBlockedTimePref,
+ base::Time::Now());
+ }
+}
+
+} // namespace system_media_permissions
diff --git a/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc
new file mode 100644
index 00000000000..8a3fe7e1918
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc
@@ -0,0 +1,92 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/tab_capture_access_handler.h"
+
+#include <utility>
+
+#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+TabCaptureAccessHandler::TabCaptureAccessHandler() {
+}
+
+TabCaptureAccessHandler::~TabCaptureAccessHandler() {
+}
+
+bool TabCaptureAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return type == blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE;
+}
+
+bool TabCaptureAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return false;
+}
+
+void TabCaptureAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ extensions::TabCaptureRegistry* tab_capture_registry =
+ extensions::TabCaptureRegistry::Get(profile);
+ if (!tab_capture_registry) {
+ NOTREACHED();
+ std::move(callback).Run(
+ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
+ std::move(ui));
+ return;
+ }
+ // |extension| may be null if the tabCapture starts with
+ // tabCapture.getMediaStreamId().
+ // TODO(crbug.com/831722): Deprecate tabCaptureRegistry soon.
+ const std::string extension_id = extension ? extension->id() : "";
+ const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
+ request.render_process_id, request.render_frame_id, extension_id);
+
+ if (request.audio_type ==
+ blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE &&
+ tab_capture_allowed) {
+ devices.push_back(blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE, std::string(),
+ std::string()));
+ }
+
+ if (request.video_type ==
+ blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE &&
+ tab_capture_allowed) {
+ devices.push_back(blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE, std::string(),
+ std::string()));
+ }
+
+ if (!devices.empty()) {
+ ui = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents, devices);
+ }
+ UpdateExtensionTrusted(request, extension);
+ std::move(callback).Run(
+ devices,
+ devices.empty() ? blink::mojom::MediaStreamRequestResult::INVALID_STATE
+ : blink::mojom::MediaStreamRequestResult::OK,
+ std::move(ui));
+}
diff --git a/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h
new file mode 100644
index 00000000000..26fd89ad892
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
+
+#include "chrome/browser/media/capture_access_handler_base.h"
+
+// MediaAccessHandler for TabCapture API.
+class TabCaptureAccessHandler : public CaptureAccessHandlerBase {
+ public:
+ TabCaptureAccessHandler();
+ ~TabCaptureAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc
new file mode 100644
index 00000000000..00df7fa7d40
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc
@@ -0,0 +1,163 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/hash/hash.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "components/favicon/content/content_favicon_driver.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "media/base/video_util.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "ui/gfx/favicon_size.h"
+#include "ui/gfx/image/image.h"
+
+using content::BrowserThread;
+using content::DesktopMediaID;
+
+namespace {
+
+gfx::ImageSkia CreateEnclosedFaviconImage(gfx::Size size,
+ const gfx::ImageSkia& favicon) {
+ DCHECK_GE(size.width(), gfx::kFaviconSize);
+ DCHECK_GE(size.height(), gfx::kFaviconSize);
+
+ // Create a bitmap.
+ SkBitmap result;
+ result.allocN32Pixels(size.width(), size.height(), false);
+ SkCanvas canvas(result);
+ canvas.clear(SK_ColorTRANSPARENT);
+
+ // Draw the favicon image into the center of result image. If the favicon is
+ // too big, scale it down.
+ gfx::Size fill_size = favicon.size();
+ if (result.width() < favicon.width() || result.height() < favicon.height())
+ fill_size = media::ScaleSizeToFitWithinTarget(favicon.size(), size);
+
+ gfx::Rect center_rect(result.width(), result.height());
+ center_rect.ClampToCenteredSize(fill_size);
+ SkRect dest_rect =
+ SkRect::MakeLTRB(center_rect.x(), center_rect.y(), center_rect.right(),
+ center_rect.bottom());
+ canvas.drawBitmapRect(*favicon.bitmap(), dest_rect, nullptr);
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
+
+// Update the list once per second.
+const int kDefaultTabDesktopMediaListUpdatePeriod = 1000;
+
+} // namespace
+
+TabDesktopMediaList::TabDesktopMediaList()
+ : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
+ kDefaultTabDesktopMediaListUpdatePeriod)) {
+ type_ = DesktopMediaID::TYPE_WEB_CONTENTS;
+ thumbnail_task_runner_ = base::CreateSequencedTaskRunner(
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE});
+}
+
+TabDesktopMediaList::~TabDesktopMediaList() {}
+
+void TabDesktopMediaList::Refresh(bool update_thumnails) {
+ DCHECK(can_refresh());
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ Profile* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();
+ if (!profile) {
+ OnRefreshComplete();
+ return;
+ }
+
+ std::vector<Browser*> browsers;
+ for (auto* browser : *BrowserList::GetInstance()) {
+ if (browser->profile()->GetOriginalProfile() ==
+ profile->GetOriginalProfile()) {
+ browsers.push_back(browser);
+ }
+ }
+
+ ImageHashesMap new_favicon_hashes;
+ std::vector<SourceDescription> sources;
+ std::map<base::TimeTicks, SourceDescription> tab_map;
+ std::vector<std::pair<DesktopMediaID, gfx::ImageSkia>> favicon_pairs;
+
+ // Enumerate all tabs with their titles and favicons for a user profile.
+ for (auto* browser : browsers) {
+ const TabStripModel* tab_strip_model = browser->tab_strip_model();
+ DCHECK(tab_strip_model);
+
+ for (int i = 0; i < tab_strip_model->count(); i++) {
+ // Create id for tab.
+ content::WebContents* contents = tab_strip_model->GetWebContentsAt(i);
+ DCHECK(contents);
+ content::RenderFrameHost* main_frame = contents->GetMainFrame();
+ DCHECK(main_frame);
+ DesktopMediaID media_id(
+ DesktopMediaID::TYPE_WEB_CONTENTS, DesktopMediaID::kNullId,
+ content::WebContentsMediaCaptureId(main_frame->GetProcess()->GetID(),
+ main_frame->GetRoutingID()));
+
+ // Get tab's last active time stamp.
+ const base::TimeTicks t = contents->GetLastActiveTime();
+ tab_map.insert(
+ std::make_pair(t, SourceDescription(media_id, contents->GetTitle())));
+
+ // Get favicon for tab.
+ favicon::FaviconDriver* favicon_driver =
+ favicon::ContentFaviconDriver::FromWebContents(contents);
+ if (!favicon_driver)
+ continue;
+
+ gfx::Image favicon = favicon_driver->GetFavicon();
+ if (favicon.IsEmpty())
+ continue;
+
+ // Only new or changed favicon need update.
+ new_favicon_hashes[media_id] = GetImageHash(favicon);
+ if (!favicon_hashes_.count(media_id) ||
+ (favicon_hashes_[media_id] != new_favicon_hashes[media_id])) {
+ gfx::ImageSkia image = favicon.AsImageSkia();
+ image.MakeThreadSafe();
+ favicon_pairs.push_back(std::make_pair(media_id, image));
+ }
+ }
+ }
+ favicon_hashes_ = new_favicon_hashes;
+
+ // Sort tab sources by time. Most recent one first. Then update sources list.
+ for (auto it = tab_map.rbegin(); it != tab_map.rend(); ++it)
+ sources.push_back(it->second);
+
+ UpdateSourcesList(sources);
+
+ for (const auto& it : favicon_pairs) {
+ // Create a thumbail in a different thread and update the thumbnail in
+ // current thread.
+ base::PostTaskAndReplyWithResult(
+ thumbnail_task_runner_.get(), FROM_HERE,
+ base::Bind(&CreateEnclosedFaviconImage, thumbnail_size_, it.second),
+ base::Bind(&TabDesktopMediaList::UpdateSourceThumbnail,
+ weak_factory_.GetWeakPtr(), it.first));
+ }
+
+ // OnRefreshComplete() needs to be called after all calls for
+ // UpdateSourceThumbnail() have done. Therefore, a DoNothing task is posted to
+ // the same sequenced task runner that CreateEnlargedFaviconImag() is posted.
+ thumbnail_task_runner_.get()->PostTaskAndReply(
+ FROM_HERE, base::DoNothing(),
+ base::BindOnce(&TabDesktopMediaList::OnRefreshComplete,
+ weak_factory_.GetWeakPtr()));
+}
diff --git a/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h
new file mode 100644
index 00000000000..7454d5c364a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h
@@ -0,0 +1,31 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_TAB_DESKTOP_MEDIA_LIST_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_TAB_DESKTOP_MEDIA_LIST_H_
+
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+
+// Implementation of DesktopMediaList that shows tab/WebContents.
+class TabDesktopMediaList : public DesktopMediaListBase {
+ public:
+ TabDesktopMediaList();
+ ~TabDesktopMediaList() override;
+
+ private:
+ typedef std::map<content::DesktopMediaID, uint32_t> ImageHashesMap;
+
+ void Refresh(bool update_thumnails) override;
+
+ ImageHashesMap favicon_hashes_;
+
+ // Task runner used for the |worker_|.
+ scoped_refptr<base::SequencedTaskRunner> thumbnail_task_runner_;
+
+ base::WeakPtrFactory<TabDesktopMediaList> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(TabDesktopMediaList);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_TAB_DESKTOP_MEDIA_LIST_H_
diff --git a/chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc
new file mode 100644
index 00000000000..b2d850b6c42
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc
@@ -0,0 +1,372 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/test_browser_window.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
+#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
+#endif // defined(OS_CHROMEOS)
+
+using content::WebContents;
+using content::WebContentsTester;
+
+namespace {
+
+constexpr int kDefaultSourceCount = 2;
+constexpr int kThumbnailSize = 50;
+
+class UnittestProfileManager : public ::ProfileManagerWithoutInit {
+ public:
+ explicit UnittestProfileManager(const base::FilePath& user_data_dir)
+ : ::ProfileManagerWithoutInit(user_data_dir) {}
+
+ protected:
+ std::unique_ptr<Profile> CreateProfileHelper(
+ const base::FilePath& path) override {
+ if (!base::PathExists(path) && !base::CreateDirectory(path))
+ return nullptr;
+ return std::make_unique<TestingProfile>(path);
+ }
+};
+
+// Create a greyscale image with certain size and grayscale value.
+gfx::Image CreateGrayscaleImage(gfx::Size size, uint8_t greyscale_value) {
+ SkBitmap result;
+ result.allocN32Pixels(size.width(), size.height(), true);
+
+ uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
+
+ // Set greyscale value for all pixels.
+ for (int y = 0; y < result.height(); ++y) {
+ for (int x = 0; x < result.width(); ++x) {
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel()] =
+ greyscale_value;
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 1] =
+ greyscale_value;
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 2] =
+ greyscale_value;
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
+ 0xff;
+ }
+ }
+
+ return gfx::Image::CreateFrom1xBitmap(result);
+}
+
+} // namespace
+
+class MockObserver : public DesktopMediaListObserver {
+ public:
+ MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
+ MOCK_METHOD3(OnSourceMoved,
+ void(DesktopMediaList* list, int old_index, int new_index));
+ MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceThumbnailChanged,
+ void(DesktopMediaList* list, int index));
+ MOCK_METHOD1(OnAllSourcesFound, void(DesktopMediaList* list));
+
+ void VerifyAndClearExpectations() {
+ testing::Mock::VerifyAndClearExpectations(this);
+ }
+};
+
+ACTION_P2(CheckListSize, list, expected_list_size) {
+ EXPECT_EQ(expected_list_size, list->GetSourceCount());
+}
+
+ACTION(QuitMessageLoop) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
+}
+
+class TabDesktopMediaListTest : public testing::Test {
+ protected:
+ TabDesktopMediaListTest()
+ : local_state_(TestingBrowserProcess::GetGlobal()) {}
+
+ void AddWebcontents(int favicon_greyscale) {
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ std::unique_ptr<WebContents> contents(
+ content::WebContentsTester::CreateTestWebContents(
+ profile_, content::SiteInstance::Create(profile_)));
+ ASSERT_TRUE(contents);
+
+ WebContentsTester::For(contents.get())
+ ->SetLastActiveTime(base::TimeTicks::Now());
+
+ // Get or create the transient NavigationEntry and add a title and a
+ // favicon to it.
+ content::NavigationEntry* entry =
+ contents->GetController().GetTransientEntry();
+ if (!entry) {
+ std::unique_ptr<content::NavigationEntry> entry_new =
+ content::NavigationController::CreateNavigationEntry(
+ GURL("chrome://blank"), content::Referrer(), base::nullopt,
+ ui::PAGE_TRANSITION_LINK, false, std::string(), profile_,
+ nullptr /* blob_url_loader_factory */);
+
+ contents->GetController().SetTransientEntry(std::move(entry_new));
+ entry = contents->GetController().GetTransientEntry();
+ }
+
+ contents->UpdateTitleForEntry(entry, base::ASCIIToUTF16("Test tab"));
+
+ content::FaviconStatus favicon_info;
+ favicon_info.image =
+ CreateGrayscaleImage(gfx::Size(10, 10), favicon_greyscale);
+ entry->GetFavicon() = favicon_info;
+
+ manually_added_web_contents_.push_back(contents.get());
+ tab_strip_model->AppendWebContents(std::move(contents), true);
+ }
+
+ void SetUp() override {
+ manually_added_web_contents_.clear();
+ rvh_test_enabler_.reset(new content::RenderViewHostTestEnabler());
+ // Create a new temporary directory, and store the path.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ TestingBrowserProcess::GetGlobal()->SetProfileManager(
+ new UnittestProfileManager(temp_dir_.GetPath()));
+
+#if defined(OS_CHROMEOS)
+ base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
+ cl->AppendSwitch(switches::kTestType);
+#endif
+
+ // Create profile.
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ ASSERT_TRUE(profile_manager);
+
+ profile_ = profile_manager->GetLastUsedProfileAllowedByPolicy();
+ ASSERT_TRUE(profile_);
+
+ // Create browser.
+ Browser::CreateParams profile_params(profile_, true);
+ browser_ = CreateBrowserWithTestWindowForParams(&profile_params);
+ ASSERT_TRUE(browser_);
+ for (int i = 0; i < kDefaultSourceCount; i++) {
+ AddWebcontents(i + 1);
+ }
+ }
+
+ void TearDown() override {
+ // TODO(erikchen): Tearing down the TabStripModel should just delete all its
+ // owned WebContents. Then |manually_added_web_contents_| won't be
+ // necessary. https://crbug.com/832879.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ for (WebContents* contents : manually_added_web_contents_) {
+ tab_strip_model->DetachWebContentsAt(
+ tab_strip_model->GetIndexOfWebContents(contents));
+ }
+ manually_added_web_contents_.clear();
+
+ browser_.reset();
+ TestingBrowserProcess::GetGlobal()->SetProfileManager(NULL);
+ base::RunLoop().RunUntilIdle();
+ rvh_test_enabler_.reset();
+ }
+
+ void CreateDefaultList() {
+ list_.reset(new TabDesktopMediaList());
+ list_->SetThumbnailSize(gfx::Size(kThumbnailSize, kThumbnailSize));
+
+ // Set update period to reduce the time it takes to run tests.
+ // >0 to avoid unit test failure.
+ list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(1));
+ }
+
+ void InitializeAndVerify() {
+ CreateDefaultList();
+
+ // The tabs in media source list are sorted in decreasing time order. The
+ // latest one is listed first. However, tabs are added to TabStripModel in
+ // increasing time order, the oldest one is added first.
+ {
+ testing::InSequence dummy;
+
+ for (int i = 0; i < kDefaultSourceCount; i++) {
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), i))
+ .WillOnce(CheckListSize(list_.get(), i + 1));
+ }
+
+ for (int i = 0; i < kDefaultSourceCount - 1; i++) {
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(
+ list_.get(), kDefaultSourceCount - 1 - i));
+ }
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+ }
+
+ list_->StartUpdating(&observer_);
+ base::RunLoop().Run();
+
+ for (int i = 0; i < kDefaultSourceCount; ++i) {
+ EXPECT_EQ(list_->GetSource(i).id.type,
+ content::DesktopMediaID::TYPE_WEB_CONTENTS);
+ }
+
+ observer_.VerifyAndClearExpectations();
+ }
+
+ // The path to temporary directory used to contain the test operations.
+ base::ScopedTempDir temp_dir_;
+ ScopedTestingLocalState local_state_;
+
+ std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
+ Profile* profile_;
+ std::unique_ptr<Browser> browser_;
+
+ // Must be listed before |list_|, so it's destroyed last.
+ MockObserver observer_;
+ std::unique_ptr<TabDesktopMediaList> list_;
+ std::vector<WebContents*> manually_added_web_contents_;
+
+ content::BrowserTaskEnvironment task_environment_;
+
+#if defined(OS_CHROMEOS)
+ chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
+ chromeos::ScopedTestUserManager test_user_manager_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(TabDesktopMediaListTest);
+};
+
+TEST_F(TabDesktopMediaListTest, AddTab) {
+ InitializeAndVerify();
+
+ AddWebcontents(10);
+
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0))
+ .WillOnce(CheckListSize(list_.get(), kDefaultSourceCount + 1));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, RemoveTab) {
+ InitializeAndVerify();
+
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ std::unique_ptr<WebContents> released_web_contents =
+ tab_strip_model->DetachWebContentsAt(kDefaultSourceCount - 1);
+ for (auto it = manually_added_web_contents_.begin();
+ it != manually_added_web_contents_.end(); ++it) {
+ if (*it == released_web_contents.get()) {
+ manually_added_web_contents_.erase(it);
+ break;
+ }
+ }
+
+ EXPECT_CALL(observer_, OnSourceRemoved(list_.get(), 0))
+ .WillOnce(
+ testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount - 1),
+ QuitMessageLoop()));
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, MoveTab) {
+ InitializeAndVerify();
+
+ // Swap the two media sources by swap their time stamps.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+
+ WebContents* contents0 = tab_strip_model->GetWebContentsAt(0);
+ ASSERT_TRUE(contents0);
+ base::TimeTicks t0 = contents0->GetLastActiveTime();
+ WebContents* contents1 = tab_strip_model->GetWebContentsAt(1);
+ ASSERT_TRUE(contents1);
+ base::TimeTicks t1 = contents1->GetLastActiveTime();
+
+ WebContentsTester::For(contents0)->SetLastActiveTime(t1);
+ WebContentsTester::For(contents1)->SetLastActiveTime(t0);
+
+ EXPECT_CALL(observer_, OnSourceMoved(list_.get(), 1, 0))
+ .WillOnce(testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount),
+ QuitMessageLoop()));
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, UpdateTitle) {
+ InitializeAndVerify();
+
+ // Change tab's title.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ WebContents* contents =
+ tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
+ ASSERT_TRUE(contents);
+ content::NavigationController& controller = contents->GetController();
+ contents->UpdateTitleForEntry(controller.GetTransientEntry(),
+ base::ASCIIToUTF16("New test tab"));
+
+ EXPECT_CALL(observer_, OnSourceNameChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ base::RunLoop().Run();
+
+ EXPECT_EQ(list_->GetSource(0).name, base::UTF8ToUTF16("New test tab"));
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, UpdateThumbnail) {
+ InitializeAndVerify();
+
+ // Change tab's favicon.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ WebContents* contents =
+ tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
+ ASSERT_TRUE(contents);
+
+ content::FaviconStatus favicon_info;
+ favicon_info.image = CreateGrayscaleImage(gfx::Size(10, 10), 100);
+ contents->GetController().GetTransientEntry()->GetFavicon() = favicon_info;
+
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
diff --git a/chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc
new file mode 100644
index 00000000000..a02c385a068
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc
@@ -0,0 +1,216 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+
+namespace content {
+
+TestStatsReportDictionary::TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue> report)
+ : report_(std::move(report)) {
+ CHECK(report_);
+}
+
+TestStatsReportDictionary::~TestStatsReportDictionary() {
+}
+
+void TestStatsReportDictionary::ForEach(
+ std::function<void(const TestStatsDictionary&)> iteration) {
+ for (base::DictionaryValue::Iterator it(*report_); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* it_value;
+ CHECK(it.value().GetAsDictionary(&it_value));
+ iteration(TestStatsDictionary(this, it_value));
+ }
+}
+
+std::vector<TestStatsDictionary> TestStatsReportDictionary::Filter(
+ std::function<bool(const TestStatsDictionary&)> filter) {
+ std::vector<TestStatsDictionary> result;
+ ForEach([&result, &filter](const TestStatsDictionary& stats) {
+ if (filter(stats))
+ result.push_back(stats);
+ });
+ return result;
+}
+
+std::unique_ptr<TestStatsDictionary> TestStatsReportDictionary::Get(
+ const std::string& id) {
+ const base::DictionaryValue* dictionary;
+ if (!report_->GetDictionary(id, &dictionary))
+ return nullptr;
+ return std::unique_ptr<TestStatsDictionary>(
+ new TestStatsDictionary(this, dictionary));
+}
+
+std::vector<TestStatsDictionary> TestStatsReportDictionary::GetAll() {
+ return Filter([](const TestStatsDictionary&) { return true; });
+}
+
+std::vector<TestStatsDictionary> TestStatsReportDictionary::GetByType(
+ const std::string& type) {
+ return Filter([&type](const TestStatsDictionary& stats) {
+ return stats.GetString("type") == type;
+ });
+}
+
+TestStatsDictionary::TestStatsDictionary(
+ TestStatsReportDictionary* report, const base::DictionaryValue* stats)
+ : report_(report), stats_(stats) {
+ CHECK(report_);
+ CHECK(stats_);
+}
+
+TestStatsDictionary::TestStatsDictionary(
+ const TestStatsDictionary& other) = default;
+
+TestStatsDictionary::~TestStatsDictionary() {
+}
+
+bool TestStatsDictionary::IsBoolean(const std::string& key) const {
+ bool value;
+ return GetBoolean(key, &value);
+}
+
+bool TestStatsDictionary::GetBoolean(const std::string& key) const {
+ bool value;
+ CHECK(GetBoolean(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsNumber(const std::string& key) const {
+ double value;
+ return GetNumber(key, &value);
+}
+
+double TestStatsDictionary::GetNumber(const std::string& key) const {
+ double value;
+ CHECK(GetNumber(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsString(const std::string& key) const {
+ std::string value;
+ return GetString(key, &value);
+}
+
+std::string TestStatsDictionary::GetString(const std::string& key) const {
+ std::string value;
+ CHECK(GetString(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsSequenceBoolean(const std::string& key) const {
+ std::vector<bool> value;
+ return GetSequenceBoolean(key, &value);
+}
+
+std::vector<bool> TestStatsDictionary::GetSequenceBoolean(
+ const std::string& key) const {
+ std::vector<bool> value;
+ CHECK(GetSequenceBoolean(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsSequenceNumber(const std::string& key) const {
+ std::vector<double> value;
+ return GetSequenceNumber(key, &value);
+}
+
+std::vector<double> TestStatsDictionary::GetSequenceNumber(
+ const std::string& key) const {
+ std::vector<double> value;
+ CHECK(GetSequenceNumber(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsSequenceString(const std::string& key) const {
+ std::vector<std::string> value;
+ return GetSequenceString(key, &value);
+}
+
+std::vector<std::string> TestStatsDictionary::GetSequenceString(
+ const std::string& key) const {
+ std::vector<std::string> value;
+ CHECK(GetSequenceString(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::GetBoolean(
+ const std::string& key, bool* out) const {
+ return stats_->GetBoolean(key, out);
+}
+
+bool TestStatsDictionary::GetNumber(
+ const std::string& key, double* out) const {
+ return stats_->GetDouble(key, out);
+}
+
+bool TestStatsDictionary::GetString(
+ const std::string& key, std::string* out) const {
+ return stats_->GetString(key, out);
+}
+
+bool TestStatsDictionary::GetSequenceBoolean(
+ const std::string& key,
+ std::vector<bool>* out) const {
+ const base::ListValue* list;
+ if (!stats_->GetList(key, &list))
+ return false;
+ std::vector<bool> sequence;
+ bool element;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (!list->GetBoolean(i, &element))
+ return false;
+ sequence.push_back(element);
+ }
+ *out = std::move(sequence);
+ return true;
+}
+
+bool TestStatsDictionary::GetSequenceNumber(
+ const std::string& key,
+ std::vector<double>* out) const {
+ const base::ListValue* list;
+ if (!stats_->GetList(key, &list))
+ return false;
+ std::vector<double> sequence;
+ double element;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (!list->GetDouble(i, &element))
+ return false;
+ sequence.push_back(element);
+ }
+ *out = std::move(sequence);
+ return true;
+}
+
+bool TestStatsDictionary::GetSequenceString(
+ const std::string& key,
+ std::vector<std::string>* out) const {
+ const base::ListValue* list;
+ if (!stats_->GetList(key, &list))
+ return false;
+ std::vector<std::string> sequence;
+ std::string element;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (!list->GetString(i, &element))
+ return false;
+ sequence.push_back(element);
+ }
+ *out = std::move(sequence);
+ return true;
+}
+
+std::string TestStatsDictionary::ToString() const {
+ std::string str;
+ CHECK(base::JSONWriter::WriteWithOptions(
+ *stats_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &str));
+ return str;
+}
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/test_stats_dictionary.h b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.h
new file mode 100644
index 00000000000..13fd7f78c6a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.h
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+
+namespace content {
+
+class TestStatsDictionary;
+
+class TestStatsReportDictionary
+ : public base::RefCounted<TestStatsReportDictionary> {
+ public:
+ explicit TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue> report);
+
+ void ForEach(std::function<void(const TestStatsDictionary&)> iteration);
+ std::vector<TestStatsDictionary> Filter(
+ std::function<bool(const TestStatsDictionary&)> filter);
+
+ std::unique_ptr<TestStatsDictionary> Get(const std::string& id);
+ std::vector<TestStatsDictionary> GetAll();
+ std::vector<TestStatsDictionary> GetByType(const std::string& type);
+
+ private:
+ friend class base::RefCounted<TestStatsReportDictionary>;
+ ~TestStatsReportDictionary();
+
+ std::unique_ptr<base::DictionaryValue> report_;
+};
+
+class TestStatsDictionary {
+ public:
+ TestStatsDictionary(TestStatsReportDictionary* report,
+ const base::DictionaryValue* stats);
+ TestStatsDictionary(const TestStatsDictionary& other);
+ ~TestStatsDictionary();
+
+ bool IsBoolean(const std::string& key) const;
+ bool GetBoolean(const std::string& key) const;
+
+ bool IsNumber(const std::string& key) const;
+ double GetNumber(const std::string& key) const;
+
+ bool IsString(const std::string& key) const;
+ std::string GetString(const std::string& key) const;
+
+ bool IsSequenceBoolean(const std::string& key) const;
+ std::vector<bool> GetSequenceBoolean(const std::string& key) const;
+
+ bool IsSequenceNumber(const std::string& key) const;
+ std::vector<double> GetSequenceNumber(const std::string& key) const;
+
+ bool IsSequenceString(const std::string& key) const;
+ std::vector<std::string> GetSequenceString(const std::string& key) const;
+
+ std::string ToString() const;
+
+ private:
+ bool GetBoolean(const std::string& key, bool* out) const;
+ bool GetNumber(const std::string& key, double* out) const;
+ bool GetString(const std::string& key, std::string* out) const;
+ bool GetSequenceBoolean(
+ const std::string& key, std::vector<bool>* out) const;
+ bool GetSequenceNumber(
+ const std::string& key, std::vector<double>* out) const;
+ bool GetSequenceString(
+ const std::string& key, std::vector<std::string>* out) const;
+
+ // The reference keeps the report alive which indirectly owns |stats_|.
+ scoped_refptr<TestStatsReportDictionary> report_;
+ const base::DictionaryValue* stats_;
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
diff --git a/chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc b/chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc
new file mode 100644
index 00000000000..51bfa1630bc
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc
@@ -0,0 +1,167 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+const char kTestStatsReportJson[] =
+ R"({
+ "GarbageA": {
+ "id": "GarbageA",
+ "timestamp": 0.0,
+ "type": "garbage"
+ },
+ "RTCTestStatsID": {
+ "id": "RTCTestStatsID",
+ "timestamp": 13.37,
+ "type": "test",
+ "boolean": true,
+ "number": 42,
+ "string": "text",
+ "sequenceBoolean": [ true ],
+ "sequenceNumber": [ 42 ],
+ "sequenceString": [ "text" ]
+ },
+ "GarbageB": {
+ "id": "GarbageB",
+ "timestamp": 0.0,
+ "type": "garbage"
+ }
+})";
+
+class TestStatsDictionaryTest : public testing::Test {
+ public:
+ TestStatsDictionaryTest() {
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::ReadDeprecated(kTestStatsReportJson);
+ CHECK(value);
+ base::DictionaryValue* dictionary;
+ CHECK(value->GetAsDictionary(&dictionary));
+ ignore_result(value.release());
+ report_ = new TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue>(dictionary));
+ }
+
+ protected:
+ scoped_refptr<TestStatsReportDictionary> report_;
+};
+
+TEST_F(TestStatsDictionaryTest, ReportGetStats) {
+ EXPECT_FALSE(report_->Get("InvalidID"));
+ EXPECT_TRUE(report_->Get("GarbageA"));
+ EXPECT_TRUE(report_->Get("RTCTestStatsID"));
+ EXPECT_TRUE(report_->Get("GarbageB"));
+}
+
+TEST_F(TestStatsDictionaryTest, ReportForEach) {
+ std::set<std::string> remaining;
+ remaining.insert("GarbageA");
+ remaining.insert("RTCTestStatsID");
+ remaining.insert("GarbageB");
+ report_->ForEach([&remaining](const TestStatsDictionary& stats) {
+ remaining.erase(stats.GetString("id"));
+ });
+ EXPECT_TRUE(remaining.empty());
+}
+
+TEST_F(TestStatsDictionaryTest, ReportFilterStats) {
+ std::vector<TestStatsDictionary> filtered_stats = report_->Filter(
+ [](const TestStatsDictionary& stats) -> bool {
+ return false;
+ });
+ EXPECT_EQ(filtered_stats.size(), 0u);
+
+ filtered_stats = report_->Filter(
+ [](const TestStatsDictionary& stats) -> bool {
+ return true;
+ });
+ EXPECT_EQ(filtered_stats.size(), 3u);
+
+ filtered_stats = report_->Filter(
+ [](const TestStatsDictionary& stats) -> bool {
+ return stats.GetString("id") == "RTCTestStatsID";
+ });
+ EXPECT_EQ(filtered_stats.size(), 1u);
+}
+
+TEST_F(TestStatsDictionaryTest, ReportGetAll) {
+ std::set<std::string> remaining;
+ remaining.insert("GarbageA");
+ remaining.insert("RTCTestStatsID");
+ remaining.insert("GarbageB");
+ for (const TestStatsDictionary& stats : report_->GetAll()) {
+ remaining.erase(stats.GetString("id"));
+ }
+ EXPECT_TRUE(remaining.empty());
+}
+
+TEST_F(TestStatsDictionaryTest, ReportGetByType) {
+ std::vector<TestStatsDictionary> stats = report_->GetByType("garbage");
+ EXPECT_EQ(stats.size(), 2u);
+ std::set<std::string> remaining;
+ remaining.insert("GarbageA");
+ remaining.insert("GarbageB");
+ report_->ForEach([&remaining](const TestStatsDictionary& stats) {
+ remaining.erase(stats.GetString("id"));
+ });
+ EXPECT_TRUE(remaining.empty());
+}
+
+TEST_F(TestStatsDictionaryTest, StatsVerifyMembers) {
+ std::unique_ptr<TestStatsDictionary> stats = report_->Get("RTCTestStatsID");
+ EXPECT_TRUE(stats);
+
+ EXPECT_FALSE(stats->IsBoolean("nonexistentMember"));
+ EXPECT_FALSE(stats->IsNumber("nonexistentMember"));
+ EXPECT_FALSE(stats->IsString("nonexistentMember"));
+ EXPECT_FALSE(stats->IsSequenceBoolean("nonexistentMember"));
+ EXPECT_FALSE(stats->IsSequenceNumber("nonexistentMember"));
+ EXPECT_FALSE(stats->IsSequenceString("nonexistentMember"));
+
+ ASSERT_TRUE(stats->IsBoolean("boolean"));
+ EXPECT_EQ(stats->GetBoolean("boolean"), true);
+
+ ASSERT_TRUE(stats->IsNumber("number"));
+ EXPECT_EQ(stats->GetNumber("number"), 42.0);
+
+ ASSERT_TRUE(stats->IsString("string"));
+ EXPECT_EQ(stats->GetString("string"), "text");
+
+ ASSERT_TRUE(stats->IsSequenceBoolean("sequenceBoolean"));
+ EXPECT_EQ(stats->GetSequenceBoolean("sequenceBoolean"),
+ std::vector<bool> { true });
+
+ ASSERT_TRUE(stats->IsSequenceNumber("sequenceNumber"));
+ EXPECT_EQ(stats->GetSequenceNumber("sequenceNumber"),
+ std::vector<double> { 42.0 });
+
+ ASSERT_TRUE(stats->IsSequenceString("sequenceString"));
+ EXPECT_EQ(stats->GetSequenceString("sequenceString"),
+ std::vector<std::string> { "text" });
+}
+
+TEST_F(TestStatsDictionaryTest, TestStatsDictionaryShouldKeepReportAlive) {
+ std::unique_ptr<TestStatsDictionary> stats = report_->Get("RTCTestStatsID");
+ EXPECT_TRUE(stats);
+ report_ = nullptr;
+ EXPECT_EQ(stats->GetString("string"), "text");
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc
new file mode 100644
index 00000000000..e46f6188f28
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc
@@ -0,0 +1,259 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/rand_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/infobars/infobar_responder.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/python_utils.h"
+#include "ui/gl/gl_switches.h"
+
+const char kTitlePageOfAppEngineAdminPage[] = "Instances";
+
+const char kIsApprtcCallUpJavascript[] =
+ "var remoteVideo = document.querySelector('#remote-video');"
+ "var remoteVideoActive ="
+ " remoteVideo != null &&"
+ " remoteVideo.classList.contains('active');"
+ "window.domAutomationController.send(remoteVideoActive.toString());";
+
+// WebRTC-AppRTC integration test. Requires a real webcam and microphone
+// on the running system. This test is not meant to run in the main browser
+// test suite since normal tester machines do not have webcams.
+//
+// This test will bring up a AppRTC instance on localhost and verify that the
+// call gets up when connecting to the same room from two tabs in a browser.
+class WebRtcApprtcBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcApprtcBrowserTest() {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // The video playback will not work without a GPU, so force its use here.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ // This test fails on some Mac bots if no default devices are specified on
+ // the command line.
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kUseFakeDeviceForMediaStream,
+ "audio-input-default-id=default,video-input-default-id=default");
+ }
+
+ void TearDown() override {
+ // Kill any processes we may have brought up. Note: this isn't perfect,
+ // especially if the test hangs or if we're on Windows.
+ LOG(INFO) << "Entering TearDown";
+ if (dev_appserver_.IsValid())
+ dev_appserver_.Terminate(0, false);
+ if (collider_server_.IsValid())
+ collider_server_.Terminate(0, false);
+ LOG(INFO) << "Exiting TearDown";
+ }
+
+ protected:
+ bool LaunchApprtcInstanceOnLocalhost(const std::string& port) {
+ base::FilePath appengine_dev_appserver = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/browsertest/"
+ "apprtc/temp/google-cloud-sdk/bin/dev_appserver.py"));
+ if (!base::PathExists(appengine_dev_appserver)) {
+ LOG(ERROR) << "Missing appengine sdk at " <<
+ appengine_dev_appserver.value() << ".\n" <<
+ test::kAdviseOnGclientSolution;
+ return false;
+ }
+
+ base::FilePath apprtc_dir = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/"
+ "browsertest/apprtc/out/app_engine"));
+ if (!base::PathExists(apprtc_dir)) {
+ LOG(ERROR) << "Missing AppRTC AppEngine app at " <<
+ apprtc_dir.value() << ".\n" << test::kAdviseOnGclientSolution;
+ return false;
+ }
+ if (!base::PathExists(apprtc_dir.Append(FILE_PATH_LITERAL("app.yaml")))) {
+ LOG(ERROR) << "The AppRTC AppEngine app at " << apprtc_dir.value()
+ << " appears to have not been built."
+ << "This should have been done by webrtc.DEPS scripts.";
+ return false;
+ }
+
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+ EXPECT_TRUE(GetPythonCommand(&command_line));
+
+ command_line.AppendArgPath(appengine_dev_appserver);
+ command_line.AppendArgPath(apprtc_dir);
+ command_line.AppendArg("--port=" + port);
+ command_line.AppendArg("--admin_port=9998");
+ command_line.AppendArg("--skip_sdk_update_check");
+ command_line.AppendArg("--clear_datastore=yes");
+
+ DVLOG(1) << "Running " << command_line.GetCommandLineString();
+ dev_appserver_ = base::LaunchProcess(command_line, base::LaunchOptions());
+ return dev_appserver_.IsValid();
+ }
+
+ bool LaunchColliderOnLocalHost(const std::string& apprtc_url,
+ const std::string& collider_port) {
+ // The go workspace should be created, and collidermain built, at the
+ // runhooks stage when webrtc.DEPS/build_apprtc_collider.py runs.
+#if defined(OS_WIN)
+ base::FilePath collider_server = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/"
+ "browsertest/collider/collidermain.exe"));
+#else
+ base::FilePath collider_server = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/"
+ "browsertest/collider/collidermain"));
+#endif
+ if (!base::PathExists(collider_server)) {
+ LOG(ERROR) << "Missing Collider server binary at " <<
+ collider_server.value() << ".\n" << test::kAdviseOnGclientSolution;
+ return false;
+ }
+
+ base::CommandLine command_line(collider_server);
+
+ command_line.AppendArg("-tls=false");
+ command_line.AppendArg("-port=" + collider_port);
+ command_line.AppendArg("-room-server=" + apprtc_url);
+
+ DVLOG(1) << "Running " << command_line.GetCommandLineString();
+ collider_server_ = base::LaunchProcess(command_line, base::LaunchOptions());
+ return collider_server_.IsValid();
+ }
+
+ bool LocalApprtcInstanceIsUp() {
+ // Load the admin page and see if we manage to load it right.
+ ui_test_utils::NavigateToURL(browser(), GURL("localhost:9998"));
+ content::WebContents* tab_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::string javascript =
+ "window.domAutomationController.send(document.title)";
+ std::string result;
+ if (!content::ExecuteScriptAndExtractString(tab_contents, javascript,
+ &result))
+ return false;
+
+ return result == kTitlePageOfAppEngineAdminPage;
+ }
+
+ bool WaitForCallToComeUp(content::WebContents* tab_contents) {
+ return test::PollingWaitUntil(kIsApprtcCallUpJavascript, "true",
+ tab_contents);
+ }
+
+ bool EvalInJavascriptFile(content::WebContents* tab_contents,
+ const base::FilePath& path) {
+ std::string javascript;
+ if (!ReadFileToString(path, &javascript)) {
+ LOG(ERROR) << "Missing javascript code at " << path.value() << ".";
+ return false;
+ }
+
+ if (!content::ExecuteScript(tab_contents, javascript)) {
+ LOG(ERROR) << "Failed to execute the following javascript: " <<
+ javascript;
+ return false;
+ }
+ return true;
+ }
+
+ bool DetectLocalVideoPlaying(content::WebContents* tab_contents) {
+ // The remote video tag is called "local-video" in the AppRTC code.
+ return DetectVideoPlaying(tab_contents, "local-video");
+ }
+
+ bool DetectRemoteVideoPlaying(content::WebContents* tab_contents) {
+ // The remote video tag is called "remote-video" in the AppRTC code.
+ return DetectVideoPlaying(tab_contents, "remote-video");
+ }
+
+ bool DetectVideoPlaying(content::WebContents* tab_contents,
+ const std::string& video_tag) {
+ if (!EvalInJavascriptFile(tab_contents, GetSourceDir().Append(
+ FILE_PATH_LITERAL("chrome/test/data/webrtc/test_functions.js"))))
+ return false;
+ if (!EvalInJavascriptFile(tab_contents, GetSourceDir().Append(
+ FILE_PATH_LITERAL("chrome/test/data/webrtc/video_detector.js"))))
+ return false;
+
+ StartDetectingVideo(tab_contents, video_tag);
+ WaitForVideoToPlay(tab_contents);
+ return true;
+ }
+
+ base::FilePath GetSourceDir() {
+ base::FilePath source_dir;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir);
+ return source_dir;
+ }
+
+ private:
+ base::Process dev_appserver_;
+ base::Process collider_server_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebRtcApprtcBrowserTest, MANUAL_WorksOnApprtc) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ DetectErrorsInJavaScript();
+ ASSERT_TRUE(LaunchApprtcInstanceOnLocalhost("9999"));
+ ASSERT_TRUE(LaunchColliderOnLocalHost("http://localhost:9999", "8089"));
+ while (!LocalApprtcInstanceIsUp())
+ DVLOG(1) << "Waiting for AppRTC to come up...";
+
+ GURL room_url = GURL("http://localhost:9999/r/some_room"
+ "?wshpp=localhost:8089&wstls=false");
+
+ // Set up the left tab.
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ content::WebContents* left_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ PermissionRequestManager::FromWebContents(left_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ InfoBarResponder left_infobar_responder(
+ InfoBarService::FromWebContents(left_tab), InfoBarResponder::ACCEPT);
+ ui_test_utils::NavigateToURL(browser(), room_url);
+
+ // Wait for the local video to start playing. This is needed, because opening
+ // a new tab too quickly, by sending the current tab to the background, can
+ // lead to the request for starting the video capture in the current tab to
+ // not get sent before it comes back to the foreground (which in this test
+ // case is never).
+ ASSERT_TRUE(DetectLocalVideoPlaying(left_tab));
+
+ // Set up the right tab.
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ content::WebContents* right_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ PermissionRequestManager::FromWebContents(right_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ InfoBarResponder right_infobar_responder(
+ InfoBarService::FromWebContents(right_tab), InfoBarResponder::ACCEPT);
+ ui_test_utils::NavigateToURL(browser(), room_url);
+
+ ASSERT_TRUE(WaitForCallToComeUp(left_tab));
+ ASSERT_TRUE(WaitForCallToComeUp(right_tab));
+
+ ASSERT_TRUE(DetectRemoteVideoPlaying(left_tab));
+ ASSERT_TRUE(DetectRemoteVideoPlaying(right_tab));
+
+ chrome::CloseWebContents(browser(), left_tab, false);
+ chrome::CloseWebContents(browser(), right_tab, false);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc
new file mode 100644
index 00000000000..3afb1d4d9c8
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc
@@ -0,0 +1,344 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/deferred_sequenced_task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/nqe/network_quality_estimator.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/network_service.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_service_test.mojom.h"
+#include "third_party/blink/public/common/features.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+static const char kKeygenAlgorithmRsa[] =
+ "{ name: \"RSASSA-PKCS1-v1_5\", modulusLength: 2048, publicExponent: "
+ "new Uint8Array([1, 0, 1]), hash: \"SHA-256\" }";
+static const char kKeygenAlgorithmEcdsa[] =
+ "{ name: \"ECDSA\", namedCurve: \"P-256\" }";
+
+// Top-level integration test for WebRTC. It always uses fake devices; see
+// WebRtcWebcamBrowserTest for a test that acquires any real webcam on the
+// system.
+class WebRtcBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcBrowserTest() : left_tab_(nullptr), right_tab_(nullptr) {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Always use fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // Flag used by TestWebAudioMediaStream to force garbage collection.
+ command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+ }
+
+ void RunsAudioVideoWebRTCCallInTwoTabs(
+ const std::string& video_codec = WebRtcTestBase::kUseDefaultVideoCodec,
+ bool prefer_hw_video_codec = false,
+ const std::string& offer_cert_keygen_alg =
+ WebRtcTestBase::kUseDefaultCertKeygen,
+ const std::string& answer_cert_keygen_alg =
+ WebRtcTestBase::kUseDefaultCertKeygen) {
+ StartServerAndOpenTabs();
+
+ SetupPeerconnectionWithLocalStream(left_tab_, offer_cert_keygen_alg);
+ SetupPeerconnectionWithLocalStream(right_tab_, answer_cert_keygen_alg);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab_, video_codec, prefer_hw_video_codec);
+ SetDefaultVideoCodec(right_tab_, video_codec, prefer_hw_video_codec);
+ }
+ NegotiateCall(left_tab_, right_tab_);
+
+ DetectVideoAndHangUp();
+ }
+
+ void RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificate(
+ const std::string& cert_keygen_alg =
+ WebRtcTestBase::kUseDefaultCertKeygen) {
+ StartServerAndOpenTabs();
+
+ // Generate and clone a certificate, resulting in JavaScript variable
+ // |gCertificateClone| being set to the resulting clone.
+ DeleteDatabase(left_tab_);
+ OpenDatabase(left_tab_);
+ GenerateAndCloneCertificate(left_tab_, cert_keygen_alg);
+ CloseDatabase(left_tab_);
+ DeleteDatabase(left_tab_);
+
+ SetupPeerconnectionWithCertificateAndLocalStream(
+ left_tab_, "gCertificateClone");
+ SetupPeerconnectionWithLocalStream(right_tab_, cert_keygen_alg);
+
+ NegotiateCall(left_tab_, right_tab_);
+ VerifyLocalDescriptionContainsCertificate(left_tab_, "gCertificate");
+
+ DetectVideoAndHangUp();
+ }
+
+ uint32_t GetPeerToPeerConnectionsCountChangeFromNetworkService() {
+ uint32_t connection_count = 0u;
+ if (content::IsInProcessNetworkService()) {
+ base::RunLoop run_loop;
+ content::GetNetworkTaskRunner()->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&connection_count, &run_loop] {
+ connection_count =
+ network::NetworkService::GetNetworkServiceForTesting()
+ ->network_quality_estimator()
+ ->GetPeerToPeerConnectionsCountChange();
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ return connection_count;
+ }
+
+ mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
+ content::GetNetworkService()->BindTestInterface(
+ network_service_test.BindNewPipeAndPassReceiver());
+ // TODO(crbug.com/901026): Make sure the network process is started to avoid
+ // a deadlock on Android.
+ network_service_test.FlushForTesting();
+
+ mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+
+ bool available = network_service_test->GetPeerToPeerConnectionsCountChange(
+ &connection_count);
+ EXPECT_TRUE(available);
+
+ return connection_count;
+ }
+
+ protected:
+ void StartServerAndOpenTabs() {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ left_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ }
+
+ void DetectVideoAndHangUp() {
+ StartDetectingVideo(left_tab_, "remote-view");
+ StartDetectingVideo(right_tab_, "remote-view");
+#if !defined(OS_MACOSX)
+ // Video is choppy on Mac OS X. http://crbug.com/443542.
+ WaitForVideoToPlay(left_tab_);
+ WaitForVideoToPlay(right_tab_);
+#endif
+ HangUp(left_tab_);
+ HangUp(right_tab_);
+ }
+
+ content::WebContents* left_tab_;
+ content::WebContents* right_tab_;
+};
+
+// TODO(898546): many of these tests are failing on ASan builds.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_WebRtcBrowserTest DISABLED_WebRtcBrowserTest
+class DISABLED_WebRtcBrowserTest : public WebRtcBrowserTest {};
+#else
+#define MAYBE_WebRtcBrowserTest WebRtcBrowserTest
+#endif
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsVP8) {
+ RunsAudioVideoWebRTCCallInTwoTabs("VP8");
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsVP9) {
+ RunsAudioVideoWebRTCCallInTwoTabs("VP9");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsH264) {
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcBrowserTest.RunsAudioVideoWebRTCCallInTwoTabsH264 "
+ "(test \"OK\")";
+ return;
+ }
+
+#if defined(OS_MACOSX)
+ // TODO(jam): this test only on 10.12.
+ if (base::mac::IsOS10_12())
+ return;
+#endif
+
+ RunsAudioVideoWebRTCCallInTwoTabs("H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(WebRtcBrowserTest, TestWebAudioMediaStream) {
+ // This tests against crash regressions for the WebAudio-MediaStream
+ // integration.
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/webrtc/webaudio_crash.html"));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // A sleep is necessary to be able to detect the crash.
+ test::SleepInJavascript(tab, 1000);
+
+ ASSERT_FALSE(tab->IsCrashed());
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferRsaAnswerRsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(WebRtcTestBase::kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmRsa, kKeygenAlgorithmRsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferEcdsaAnswerEcdsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(
+ WebRtcTestBase::kUseDefaultVideoCodec, false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmEcdsa, kKeygenAlgorithmEcdsa);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificateRsa) {
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificate(kKeygenAlgorithmRsa);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificateEcdsa) {
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificate(kKeygenAlgorithmEcdsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferRsaAnswerEcdsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(WebRtcTestBase::kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmRsa, kKeygenAlgorithmEcdsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferEcdsaAnswerRsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(WebRtcTestBase::kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmEcdsa, kKeygenAlgorithmRsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsGetStatsCallback) {
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+
+ VerifyStatsGeneratedCallback(left_tab_);
+
+ DetectVideoAndHangUp();
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ GetPeerToPeerConnectionsCountChangeFromNetworkService) {
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+
+ VerifyStatsGeneratedCallback(left_tab_);
+ EXPECT_EQ(2u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+
+ DetectVideoAndHangUp();
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsGetStatsPromise) {
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ CreateDataChannel(left_tab_, "data");
+ CreateDataChannel(right_tab_, "data");
+ NegotiateCall(left_tab_, right_tab_);
+
+ std::set<std::string> missing_expected_stats;
+ for (const std::string& type : GetMandatoryStatsTypes(left_tab_)) {
+ missing_expected_stats.insert(type);
+ }
+ for (const std::string& type : VerifyStatsGeneratedPromise(left_tab_)) {
+ missing_expected_stats.erase(type);
+ }
+ for (const std::string& type : missing_expected_stats) {
+ EXPECT_TRUE(false) << "Expected stats dictionary is missing: " << type;
+ }
+
+ DetectVideoAndHangUp();
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsEmitsGatheringStateChange) {
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+
+ std::string ice_gatheringstate =
+ ExecuteJavascript("getLastGatheringState()", left_tab_);
+
+ EXPECT_EQ("complete", ice_gatheringstate);
+ DetectVideoAndHangUp();
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsEmitsGatheringStateChange_ConnectionCount) {
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+ EXPECT_EQ(2u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+
+ std::string ice_gatheringstate =
+ ExecuteJavascript("getLastGatheringState()", left_tab_);
+
+ EXPECT_EQ("complete", ice_gatheringstate);
+ DetectVideoAndHangUp();
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
new file mode 100644
index 00000000000..3affb26a72b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
@@ -0,0 +1,651 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+
+#include <stddef.h>
+
+#include <limits>
+
+#include "base/json/json_reader.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#if defined(OS_WIN)
+// For fine-grained suppression.
+#include "base/win/windows_version.h"
+#endif
+
+const char WebRtcTestBase::kAudioVideoCallConstraints[] =
+ "{audio: true, video: true}";
+const char WebRtcTestBase::kVideoCallConstraintsQVGA[] =
+ "{video: {mandatory: {minWidth: 320, maxWidth: 320, "
+ " minHeight: 240, maxHeight: 240}}}";
+const char WebRtcTestBase::kVideoCallConstraints360p[] =
+ "{video: {mandatory: {minWidth: 640, maxWidth: 640, "
+ " minHeight: 360, maxHeight: 360}}}";
+const char WebRtcTestBase::kVideoCallConstraintsVGA[] =
+ "{video: {mandatory: {minWidth: 640, maxWidth: 640, "
+ " minHeight: 480, maxHeight: 480}}}";
+const char WebRtcTestBase::kVideoCallConstraints720p[] =
+ "{video: {mandatory: {minWidth: 1280, maxWidth: 1280, "
+ " minHeight: 720, maxHeight: 720}}}";
+const char WebRtcTestBase::kVideoCallConstraints1080p[] =
+ "{video: {mandatory: {minWidth: 1920, maxWidth: 1920, "
+ " minHeight: 1080, maxHeight: 1080}}}";
+const char WebRtcTestBase::kAudioOnlyCallConstraints[] = "{audio: true}";
+const char WebRtcTestBase::kVideoOnlyCallConstraints[] = "{video: true}";
+const char WebRtcTestBase::kOkGotStream[] = "ok-got-stream";
+const char WebRtcTestBase::kFailedWithNotAllowedError[] =
+ "failed-with-error-NotAllowedError";
+const char WebRtcTestBase::kAudioVideoCallConstraints360p[] =
+ "{audio: true, video: {mandatory: {minWidth: 640, maxWidth: 640, "
+ " minHeight: 360, maxHeight: 360}}}";
+const char WebRtcTestBase::kAudioVideoCallConstraints720p[] =
+ "{audio: true, video: {mandatory: {minWidth: 1280, maxWidth: 1280, "
+ " minHeight: 720, maxHeight: 720}}}";
+const char WebRtcTestBase::kUseDefaultCertKeygen[] = "null";
+const char WebRtcTestBase::kUseDefaultAudioCodec[] = "";
+const char WebRtcTestBase::kUseDefaultVideoCodec[] = "";
+const char WebRtcTestBase::kVP9Profile0Specifier[] = "profile-id=0";
+const char WebRtcTestBase::kVP9Profile2Specifier[] = "profile-id=2";
+const char WebRtcTestBase::kUndefined[] = "undefined";
+
+namespace {
+
+base::LazyInstance<bool>::DestructorAtExit hit_javascript_errors_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Intercepts all log messages. We always attach this handler but only look at
+// the results if the test requests so. Note that this will only work if the
+// WebrtcTestBase-inheriting test cases do not run in parallel (if they did they
+// would race to look at the log, which is global to all tests).
+bool JavascriptErrorDetectingLogHandler(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str) {
+ if (file == NULL || std::string("CONSOLE") != file)
+ return false;
+
+ // TODO(crbug.com/918871): Fix AppRTC and stop ignoring this error.
+ if (str.find("Synchronous XHR in page dismissal") != std::string::npos)
+ return false;
+
+ bool contains_uncaught = str.find("\"Uncaught ") != std::string::npos;
+ if (severity == logging::LOG_ERROR ||
+ (severity == logging::LOG_INFO && contains_uncaught)) {
+ hit_javascript_errors_.Get() = true;
+ }
+
+ return false;
+}
+
+// PermissionRequestObserver ---------------------------------------------------
+
+// Used to observe the creation of permission prompt without responding.
+class PermissionRequestObserver : public PermissionRequestManager::Observer {
+ public:
+ explicit PermissionRequestObserver(content::WebContents* web_contents)
+ : request_manager_(
+ PermissionRequestManager::FromWebContents(web_contents)),
+ request_shown_(false),
+ message_loop_runner_(new content::MessageLoopRunner) {
+ request_manager_->AddObserver(this);
+ }
+ ~PermissionRequestObserver() override {
+ // Safe to remove twice if it happens.
+ request_manager_->RemoveObserver(this);
+ }
+
+ void Wait() { message_loop_runner_->Run(); }
+
+ bool request_shown() const { return request_shown_; }
+
+ private:
+ // PermissionRequestManager::Observer
+ void OnBubbleAdded() override {
+ request_shown_ = true;
+ request_manager_->RemoveObserver(this);
+ message_loop_runner_->Quit();
+ }
+
+ PermissionRequestManager* request_manager_;
+ bool request_shown_;
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermissionRequestObserver);
+};
+
+std::vector<std::string> JsonArrayToVectorOfStrings(
+ const std::string& json_array) {
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::ReadDeprecated(json_array);
+ EXPECT_TRUE(value);
+ EXPECT_TRUE(value->is_list());
+ std::unique_ptr<base::ListValue> list =
+ base::ListValue::From(std::move(value));
+ std::vector<std::string> vector;
+ vector.reserve(list->GetSize());
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ base::Value* item;
+ EXPECT_TRUE(list->Get(i, &item));
+ EXPECT_TRUE(item->is_string());
+ std::string item_str;
+ EXPECT_TRUE(item->GetAsString(&item_str));
+ vector.push_back(std::move(item_str));
+ }
+ return vector;
+}
+
+} // namespace
+
+WebRtcTestBase::WebRtcTestBase(): detect_errors_in_javascript_(false) {
+ // The handler gets set for each test method, but that's fine since this
+ // set operation is idempotent.
+ logging::SetLogMessageHandler(&JavascriptErrorDetectingLogHandler);
+ hit_javascript_errors_.Get() = false;
+
+ EnablePixelOutput();
+}
+
+WebRtcTestBase::~WebRtcTestBase() {
+ if (detect_errors_in_javascript_) {
+ EXPECT_FALSE(hit_javascript_errors_.Get())
+ << "Encountered javascript errors during test execution (Search "
+ << "for Uncaught or ERROR:CONSOLE in the test output).";
+ }
+}
+
+bool WebRtcTestBase::GetUserMediaAndAccept(
+ content::WebContents* tab_contents) const {
+ return GetUserMediaWithSpecificConstraintsAndAccept(
+ tab_contents, kAudioVideoCallConstraints);
+}
+
+bool WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndAccept(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, constraints);
+ EXPECT_TRUE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ return kOkGotStream == result;
+}
+
+bool WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ GetUserMedia(tab_contents, constraints);
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ return kOkGotStream == result;
+}
+
+void WebRtcTestBase::GetUserMediaAndDeny(content::WebContents* tab_contents) {
+ return GetUserMediaWithSpecificConstraintsAndDeny(tab_contents,
+ kAudioVideoCallConstraints);
+}
+
+void WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndDeny(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::DENY_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, constraints);
+ EXPECT_TRUE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kFailedWithNotAllowedError, result);
+}
+
+void WebRtcTestBase::GetUserMediaAndDismiss(
+ content::WebContents* tab_contents) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::DISMISS);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, kAudioVideoCallConstraints);
+ EXPECT_TRUE(permissionRequestObserver.request_shown());
+ // A dismiss should be treated like a deny.
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kFailedWithNotAllowedError, result);
+}
+
+void WebRtcTestBase::GetUserMediaAndExpectAutoAcceptWithoutPrompt(
+ content::WebContents* tab_contents) const {
+ std::string result;
+ // We issue a GetUserMedia() request. We expect that the origin already has a
+ // sticky "accept" permission (e.g. because the caller previously called
+ // GetUserMediaAndAccept()), and therefore the GetUserMedia() request
+ // automatically succeeds without a prompt.
+ // If the caller made a mistake, a prompt may show up instead. For this case,
+ // we set an auto-response to avoid leaving the prompt hanging. The choice of
+ // DENY_ALL makes sure that the response to the prompt doesn't accidentally
+ // result in a newly granted media stream permission.
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::DENY_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, kAudioVideoCallConstraints);
+ EXPECT_FALSE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kOkGotStream, result);
+}
+
+void WebRtcTestBase::GetUserMediaAndExpectAutoDenyWithoutPrompt(
+ content::WebContents* tab_contents) const {
+ std::string result;
+ // We issue a GetUserMedia() request. We expect that the origin already has a
+ // sticky "deny" permission (e.g. because the caller previously called
+ // GetUserMediaAndDeny()), and therefore the GetUserMedia() request
+ // automatically succeeds without a prompt.
+ // If the caller made a mistake, a prompt may show up instead. For this case,
+ // we set an auto-response to avoid leaving the prompt hanging. The choice of
+ // ACCEPT_ALL makes sure that the response to the prompt doesn't accidentally
+ // result in a newly granted media stream permission.
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, kAudioVideoCallConstraints);
+ EXPECT_FALSE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kFailedWithNotAllowedError, result);
+}
+
+void WebRtcTestBase::GetUserMedia(content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ // Request user media: this will launch the media stream info bar or bubble.
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents, "doGetUserMedia(" + constraints + ");", &result));
+ EXPECT_TRUE(result == "request-callback-denied" ||
+ result == "request-callback-granted");
+}
+
+content::WebContents* WebRtcTestBase::OpenPageAndGetUserMediaInNewTab(
+ const GURL& url) const {
+ return OpenPageAndGetUserMediaInNewTabWithConstraints(
+ url, kAudioVideoCallConstraints);
+}
+
+content::WebContents*
+WebRtcTestBase::OpenPageAndGetUserMediaInNewTabWithConstraints(
+ const GURL& url,
+ const std::string& constraints) const {
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* new_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ // Accept if necessary, but don't expect a prompt (because auto-accept is also
+ // okay).
+ PermissionRequestManager::FromWebContents(new_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ GetUserMedia(new_tab, constraints);
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ new_tab->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kOkGotStream, result);
+ return new_tab;
+}
+
+content::WebContents* WebRtcTestBase::OpenTestPageAndGetUserMediaInNewTab(
+ const std::string& test_page) const {
+ return OpenPageAndGetUserMediaInNewTab(
+ embedded_test_server()->GetURL(test_page));
+}
+
+content::WebContents* WebRtcTestBase::OpenTestPageInNewTab(
+ const std::string& test_page) const {
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ GURL url = embedded_test_server()->GetURL(test_page);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* new_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ // Accept if necessary, but don't expect a prompt (because auto-accept is also
+ // okay).
+ PermissionRequestManager::FromWebContents(new_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ return new_tab;
+}
+
+void WebRtcTestBase::CloseLastLocalStream(
+ content::WebContents* tab_contents) const {
+ EXPECT_EQ("ok-stopped",
+ ExecuteJavascript("stopLocalStream();", tab_contents));
+}
+
+// Convenience method which executes the provided javascript in the context
+// of the provided web contents and returns what it evaluated to.
+std::string WebRtcTestBase::ExecuteJavascript(
+ const std::string& javascript,
+ content::WebContents* tab_contents) const {
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents, javascript, &result));
+ return result;
+}
+
+void WebRtcTestBase::ChangeToLegacyGetStats(content::WebContents* tab) const {
+ content::ExecuteScriptAsync(tab, "changeToLegacyGetStats()");
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm) const {
+ SetupPeerconnectionWithoutLocalStream(tab, certificate_keygen_algorithm);
+ EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm) const {
+ std::string javascript = base::StringPrintf(
+ "preparePeerConnection(%s)", certificate_keygen_algorithm.c_str());
+ EXPECT_EQ("ok-peerconnection-created", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithCertificateAndLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const {
+ SetupPeerconnectionWithCertificateWithoutLocalStream(tab, certificate);
+ EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithCertificateWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const {
+ std::string javascript = base::StringPrintf(
+ "preparePeerConnectionWithCertificate(%s)", certificate.c_str());
+ EXPECT_EQ("ok-peerconnection-created", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithConstraintsAndLocalStream(
+ content::WebContents* tab,
+ const std::string& constraints,
+ const std::string& certificate_keygen_algorithm) const {
+ std::string javascript = base::StringPrintf(
+ "preparePeerConnection(%s, %s)", certificate_keygen_algorithm.c_str(),
+ constraints.c_str());
+ EXPECT_EQ("ok-peerconnection-created", ExecuteJavascript(javascript, tab));
+ EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", tab));
+}
+
+std::string WebRtcTestBase::CreateLocalOffer(
+ content::WebContents* from_tab) const {
+ std::string response = ExecuteJavascript("createLocalOffer({})", from_tab);
+ EXPECT_EQ("ok-", response.substr(0, 3)) << "Failed to create local offer: "
+ << response;
+
+ std::string local_offer = response.substr(3);
+ return local_offer;
+}
+
+std::string WebRtcTestBase::CreateAnswer(std::string local_offer,
+ content::WebContents* to_tab) const {
+ std::string javascript =
+ base::StringPrintf("receiveOfferFromPeer('%s', {})", local_offer.c_str());
+ std::string response = ExecuteJavascript(javascript, to_tab);
+ EXPECT_EQ("ok-", response.substr(0, 3))
+ << "Receiving peer failed to receive offer and create answer: "
+ << response;
+
+ std::string answer = response.substr(3);
+ response = ExecuteJavascript(
+ base::StringPrintf("verifyDefaultCodecs('%s')", answer.c_str()),
+ to_tab);
+ EXPECT_EQ("ok-", response.substr(0, 3))
+ << "Receiving peer failed to verify default codec: " << response;
+ return answer;
+}
+
+void WebRtcTestBase::ReceiveAnswer(const std::string& answer,
+ content::WebContents* from_tab) const {
+ ASSERT_EQ(
+ "ok-accepted-answer",
+ ExecuteJavascript(
+ base::StringPrintf("receiveAnswerFromPeer('%s')", answer.c_str()),
+ from_tab));
+}
+
+void WebRtcTestBase::GatherAndSendIceCandidates(
+ content::WebContents* from_tab,
+ content::WebContents* to_tab) const {
+ std::string ice_candidates =
+ ExecuteJavascript("getAllIceCandidates()", from_tab);
+
+ EXPECT_EQ("ok-received-candidates", ExecuteJavascript(
+ base::StringPrintf("receiveIceCandidates('%s')", ice_candidates.c_str()),
+ to_tab));
+}
+
+void WebRtcTestBase::CreateDataChannel(content::WebContents* tab,
+ const std::string& label) {
+ EXPECT_EQ("ok-created",
+ ExecuteJavascript("createDataChannel('" + label + "')", tab));
+}
+
+void WebRtcTestBase::NegotiateCall(content::WebContents* from_tab,
+ content::WebContents* to_tab) const {
+ std::string local_offer = CreateLocalOffer(from_tab);
+ std::string answer = CreateAnswer(local_offer, to_tab);
+ ReceiveAnswer(answer, from_tab);
+
+ // Send all ICE candidates (wait for gathering to finish if necessary).
+ GatherAndSendIceCandidates(to_tab, from_tab);
+ GatherAndSendIceCandidates(from_tab, to_tab);
+}
+
+void WebRtcTestBase::VerifyLocalDescriptionContainsCertificate(
+ content::WebContents* tab,
+ const std::string& certificate) const {
+ std::string javascript = base::StringPrintf(
+ "verifyLocalDescriptionContainsCertificate(%s)", certificate.c_str());
+ EXPECT_EQ("ok-verified", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::HangUp(content::WebContents* from_tab) const {
+ EXPECT_EQ("ok-call-hung-up", ExecuteJavascript("hangUp()", from_tab));
+}
+
+void WebRtcTestBase::DetectErrorsInJavaScript() {
+ detect_errors_in_javascript_ = true;
+}
+
+void WebRtcTestBase::StartDetectingVideo(
+ content::WebContents* tab_contents,
+ const std::string& video_element) const {
+ std::string javascript = base::StringPrintf(
+ "startDetection('%s', 320, 240)", video_element.c_str());
+ EXPECT_EQ("ok-started", ExecuteJavascript(javascript, tab_contents));
+}
+
+bool WebRtcTestBase::WaitForVideoToPlay(
+ content::WebContents* tab_contents) const {
+ bool is_video_playing = test::PollingWaitUntil(
+ "isVideoPlaying()", "video-playing", tab_contents);
+ EXPECT_TRUE(is_video_playing);
+ return is_video_playing;
+}
+
+std::string WebRtcTestBase::GetStreamSize(
+ content::WebContents* tab_contents,
+ const std::string& video_element) const {
+ std::string javascript =
+ base::StringPrintf("getStreamSize('%s')", video_element.c_str());
+ std::string result = ExecuteJavascript(javascript, tab_contents);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ return result.substr(3);
+}
+
+bool WebRtcTestBase::OnWin8OrHigher() const {
+#if defined(OS_WIN)
+ return base::win::GetVersion() >= base::win::Version::WIN8;
+#else
+ return false;
+#endif
+}
+
+void WebRtcTestBase::OpenDatabase(content::WebContents* tab) const {
+ EXPECT_EQ("ok-database-opened", ExecuteJavascript("openDatabase()", tab));
+}
+
+void WebRtcTestBase::CloseDatabase(content::WebContents* tab) const {
+ EXPECT_EQ("ok-database-closed", ExecuteJavascript("closeDatabase()", tab));
+}
+
+void WebRtcTestBase::DeleteDatabase(content::WebContents* tab) const {
+ EXPECT_EQ("ok-database-deleted", ExecuteJavascript("deleteDatabase()", tab));
+}
+
+void WebRtcTestBase::GenerateAndCloneCertificate(
+ content::WebContents* tab, const std::string& keygen_algorithm) const {
+ std::string javascript = base::StringPrintf(
+ "generateAndCloneCertificate(%s)", keygen_algorithm.c_str());
+ EXPECT_EQ("ok-generated-and-cloned", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::VerifyStatsGeneratedCallback(
+ content::WebContents* tab) const {
+ EXPECT_EQ("ok-got-stats",
+ ExecuteJavascript("verifyLegacyStatsGenerated()", tab));
+}
+
+std::vector<std::string> WebRtcTestBase::VerifyStatsGeneratedPromise(
+ content::WebContents* tab) const {
+ std::string result = ExecuteJavascript("verifyStatsGeneratedPromise()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ return JsonArrayToVectorOfStrings(result.substr(3));
+}
+
+double WebRtcTestBase::MeasureGetStatsCallbackPerformance(
+ content::WebContents* tab) const {
+ std::string result = ExecuteJavascript(
+ "measureGetStatsCallbackPerformance()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ double ms;
+ if (!base::StringToDouble(result.substr(3), &ms))
+ return std::numeric_limits<double>::infinity();
+ return ms;
+}
+
+scoped_refptr<content::TestStatsReportDictionary>
+WebRtcTestBase::GetStatsReportDictionary(content::WebContents* tab) const {
+ std::string result = ExecuteJavascript("getStatsReportDictionary()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ std::unique_ptr<base::Value> parsed_json =
+ base::JSONReader::ReadDeprecated(result.substr(3));
+ base::DictionaryValue* dictionary;
+ CHECK(parsed_json);
+ CHECK(parsed_json->GetAsDictionary(&dictionary));
+ ignore_result(parsed_json.release());
+ return scoped_refptr<content::TestStatsReportDictionary>(
+ new content::TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue>(dictionary)));
+}
+
+double WebRtcTestBase::MeasureGetStatsPerformance(
+ content::WebContents* tab) const {
+ std::string result = ExecuteJavascript("measureGetStatsPerformance()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ double ms;
+ if (!base::StringToDouble(result.substr(3), &ms))
+ return std::numeric_limits<double>::infinity();
+ return ms;
+}
+
+std::vector<std::string> WebRtcTestBase::GetMandatoryStatsTypes(
+ content::WebContents* tab) const {
+ return JsonArrayToVectorOfStrings(
+ ExecuteJavascript("getMandatoryStatsTypes()", tab));
+}
+
+void WebRtcTestBase::SetDefaultAudioCodec(
+ content::WebContents* tab,
+ const std::string& audio_codec) const {
+ EXPECT_EQ("ok", ExecuteJavascript(
+ "setDefaultAudioCodec('" + audio_codec + "')", tab));
+}
+
+void WebRtcTestBase::SetDefaultVideoCodec(content::WebContents* tab,
+ const std::string& video_codec,
+ bool prefer_hw_codec,
+ const std::string& profile) const {
+ std::string codec_profile = profile;
+ // When no |profile| is given, we default VP9 to Profile 0.
+ if (video_codec.compare("VP9") == 0 && codec_profile.empty())
+ codec_profile = kVP9Profile0Specifier;
+
+ EXPECT_EQ("ok", ExecuteJavascript(
+ "setDefaultVideoCodec('" + video_codec + "'," +
+ (prefer_hw_codec ? "true" : "false") + "," +
+ (codec_profile.empty() ? "null"
+ : "'" + codec_profile + "'") +
+ ")",
+ tab));
+}
+
+void WebRtcTestBase::EnableOpusDtx(content::WebContents* tab) const {
+ EXPECT_EQ("ok-forced", ExecuteJavascript("forceOpusDtx()", tab));
+}
+
+std::string WebRtcTestBase::GetDesktopMediaStream(content::WebContents* tab) {
+ DCHECK(static_cast<bool>(LoadDesktopCaptureExtension()));
+
+ // Post a task to the extension, opening a desktop media stream.
+ return ExecuteJavascript("openDesktopMediaStream()", tab);
+}
+
+base::Optional<std::string> WebRtcTestBase::LoadDesktopCaptureExtension() {
+ base::Optional<std::string> extension_id;
+ if (!desktop_capture_extension_.get()) {
+ extensions::ChromeTestExtensionLoader loader(browser()->profile());
+ base::FilePath extension_path;
+ EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &extension_path));
+ extension_path = extension_path.AppendASCII("extensions/desktop_capture");
+ desktop_capture_extension_ = loader.LoadExtension(extension_path);
+ LOG(INFO) << "Loaded desktop capture extension, id = "
+ << desktop_capture_extension_->id();
+
+ extensions::ExtensionRegistry* registry =
+ extensions::ExtensionRegistry::Get(browser()->profile());
+
+ EXPECT_TRUE(registry->enabled_extensions().GetByID(
+ desktop_capture_extension_->id()));
+ }
+ if (desktop_capture_extension_)
+ extension_id.emplace(desktop_capture_extension_->id());
+ return extension_id;
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h
new file mode 100644
index 00000000000..e85f0026928
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h
@@ -0,0 +1,257 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+#include "chrome/test/base/in_process_browser_test.h"
+
+namespace infobars {
+class InfoBar;
+}
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+class Extension;
+}
+
+// Base class for WebRTC browser tests with useful primitives for interacting
+// getUserMedia. We use inheritance here because it makes the test code look
+// as clean as it can be.
+class WebRtcTestBase : public InProcessBrowserTest {
+ public:
+ // Typical constraints.
+ static const char kAudioVideoCallConstraints[];
+ static const char kAudioOnlyCallConstraints[];
+ static const char kVideoOnlyCallConstraints[];
+ static const char kVideoCallConstraintsQVGA[];
+ static const char kVideoCallConstraints360p[];
+ static const char kVideoCallConstraintsVGA[];
+ static const char kVideoCallConstraints720p[];
+ static const char kVideoCallConstraints1080p[];
+ static const char kAudioVideoCallConstraints360p[];
+ static const char kAudioVideoCallConstraints720p[];
+
+ static const char kOkGotStream[];
+ static const char kFailedWithNotAllowedError[];
+
+ static const char kUseDefaultCertKeygen[];
+ static const char kUseDefaultAudioCodec[];
+ static const char kUseDefaultVideoCodec[];
+
+ static const char kVP9Profile0Specifier[];
+ static const char kVP9Profile2Specifier[];
+
+ static const char kUndefined[];
+
+ enum class StreamArgumentType {
+ NO_STREAM,
+ SHARED_STREAM,
+ INDIVIDUAL_STREAMS
+ };
+
+ protected:
+ WebRtcTestBase();
+ ~WebRtcTestBase() override;
+
+ // These all require that the loaded page fulfills the public interface in
+ // chrome/test/data/webrtc/getusermedia.js.
+ // If an error is reported back from the getUserMedia call, these functions
+ // will return false.
+ // The ...AndAccept()/...AndDeny()/...AndDismiss() functions expect that a
+ // prompt will be shown (i.e. the current origin in the tab_contents doesn't
+ // have a saved permission).
+ bool GetUserMediaAndAccept(content::WebContents* tab_contents) const;
+ bool GetUserMediaWithSpecificConstraintsAndAccept(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+ bool GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+ void GetUserMediaAndDeny(content::WebContents* tab_contents);
+ void GetUserMediaWithSpecificConstraintsAndDeny(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+ void GetUserMediaAndDismiss(content::WebContents* tab_contents) const;
+ void GetUserMediaAndExpectAutoAcceptWithoutPrompt(
+ content::WebContents* tab_contents) const;
+ void GetUserMediaAndExpectAutoDenyWithoutPrompt(
+ content::WebContents* tab_contents) const;
+ void GetUserMedia(content::WebContents* tab_contents,
+ const std::string& constraints) const;
+
+ // Convenience method which opens the page at url, calls GetUserMediaAndAccept
+ // and returns the new tab.
+ content::WebContents* OpenPageAndGetUserMediaInNewTab(const GURL& url) const;
+
+ // Convenience method which opens the page at url, calls
+ // GetUserMediaAndAcceptWithSpecificConstraints and returns the new tab.
+ content::WebContents* OpenPageAndGetUserMediaInNewTabWithConstraints(
+ const GURL& url, const std::string& constraints) const;
+
+ // Convenience method which gets the URL for |test_page| and calls
+ // OpenPageAndGetUserMediaInNewTab().
+ content::WebContents* OpenTestPageAndGetUserMediaInNewTab(
+ const std::string& test_page) const;
+
+ // Convenience method which gets the URL for |test_page|, but without calling
+ // GetUserMedia.
+ content::WebContents* OpenTestPageInNewTab(
+ const std::string& test_page) const;
+
+ // Closes the last local stream acquired by the GetUserMedia* methods.
+ void CloseLastLocalStream(content::WebContents* tab_contents) const;
+
+ std::string ExecuteJavascript(const std::string& javascript,
+ content::WebContents* tab_contents) const;
+
+ // TODO(https://crbug.com/1004239): Remove this function as soon as browser
+ // tests stop relying on the legacy getStats() API.
+ void ChangeToLegacyGetStats(content::WebContents* tab) const;
+
+ // Sets up a peer connection in the tab and adds the current local stream
+ // (which you can prepare by calling one of the GetUserMedia* methods above).
+ // Optionally, |certificate_keygen_algorithm| is JavaScript for an
+ // |AlgorithmIdentifier| to be used as parameter to
+ // |RTCPeerConnection.generateCertificate|. The resulting certificate will be
+ // used by the peer connection. Or use |kUseDefaultCertKeygen| to use a
+ // certificate.
+ void SetupPeerconnectionWithLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm =
+ kUseDefaultCertKeygen) const;
+ // Same as above but does not add the local stream.
+ void SetupPeerconnectionWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm =
+ kUseDefaultCertKeygen) const;
+ // Same as |SetupPeerconnectionWithLocalStream| except a certificate is
+ // specified, which is a reference to an |RTCCertificate| object.
+ void SetupPeerconnectionWithCertificateAndLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const;
+ // Same as above but does not add the local stream.
+ void SetupPeerconnectionWithCertificateWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const;
+ // Same as |SetupPeerconnectionWithLocalStream| except RTCPeerConnection
+ // constraints are specified.
+ void SetupPeerconnectionWithConstraintsAndLocalStream(
+ content::WebContents* tab,
+ const std::string& constraints,
+ const std::string& certificate_keygen_algorithm =
+ kUseDefaultCertKeygen) const;
+
+ void CreateDataChannel(content::WebContents* tab, const std::string& label);
+
+ // Exchanges offers and answers between the peer connections in the
+ // respective tabs. Before calling this, you must have prepared peer
+ // connections in both tabs and configured them as you like (for instance by
+ // calling SetupPeerconnectionWithLocalStream).
+ // If |video_codec| is not |kUseDefaultVideoCodec|, the SDP offer is modified
+ // (and SDP answer verified) so that the specified video codec (case-sensitive
+ // name) is used during the call instead of the default one.
+ void NegotiateCall(content::WebContents* from_tab,
+ content::WebContents* to_tab) const;
+
+ void VerifyLocalDescriptionContainsCertificate(
+ content::WebContents* tab,
+ const std::string& certificate) const;
+
+ // Hangs up a negotiated call.
+ void HangUp(content::WebContents* from_tab) const;
+
+ // Call this to enable monitoring of javascript errors for this test method.
+ // This will only work if the tests are run sequentially by the test runner
+ // (i.e. with --test-launcher-developer-mode or --test-launcher-jobs=1).
+ void DetectErrorsInJavaScript();
+
+ // Methods for detecting if video is playing (the loaded page must have
+ // chrome/test/data/webrtc/video_detector.js and its dependencies loaded to
+ // make that work). Looks at a 320x240 area of the target video tag.
+ void StartDetectingVideo(content::WebContents* tab_contents,
+ const std::string& video_element) const;
+ bool WaitForVideoToPlay(content::WebContents* tab_contents) const;
+
+ // Returns the stream size as a string on the format <width>x<height>.
+ std::string GetStreamSize(content::WebContents* tab_contents,
+ const std::string& video_element) const;
+
+ // Returns true if we're on Windows 8 or higher.
+ bool OnWin8OrHigher() const;
+
+ void OpenDatabase(content::WebContents* tab) const;
+ void CloseDatabase(content::WebContents* tab) const;
+ void DeleteDatabase(content::WebContents* tab) const;
+
+ void GenerateAndCloneCertificate(content::WebContents* tab,
+ const std::string& keygen_algorithm) const;
+
+ void VerifyStatsGeneratedCallback(content::WebContents* tab) const;
+ double MeasureGetStatsCallbackPerformance(content::WebContents* tab) const;
+ std::vector<std::string> VerifyStatsGeneratedPromise(
+ content::WebContents* tab) const;
+ scoped_refptr<content::TestStatsReportDictionary> GetStatsReportDictionary(
+ content::WebContents* tab) const;
+ double MeasureGetStatsPerformance(content::WebContents* tab) const;
+ std::vector<std::string> GetMandatoryStatsTypes(
+ content::WebContents* tab) const;
+
+ // Change the default audio/video codec in the offer SDP.
+ void SetDefaultAudioCodec(content::WebContents* tab,
+ const std::string& audio_codec) const;
+ // |prefer_hw_codec| controls what codec with name |video_codec| (and with
+ // profile |profile| if given)should be selected. This parameter only matters
+ // if there are multiple codecs with the same name, which can be the case for
+ // H264.
+ void SetDefaultVideoCodec(content::WebContents* tab,
+ const std::string& video_codec,
+ bool prefer_hw_codec = false,
+ const std::string& profile = std::string()) const;
+
+ // Add 'usedtx=1' to the offer SDP.
+ void EnableOpusDtx(content::WebContents* tab) const;
+
+ // Try to open a dekstop media stream, and return the stream id.
+ // On failure, will return empty string.
+ std::string GetDesktopMediaStream(content::WebContents* tab);
+ base::Optional<std::string> LoadDesktopCaptureExtension();
+
+ private:
+ void CloseInfoBarInTab(content::WebContents* tab_contents,
+ infobars::InfoBar* infobar) const;
+
+ std::string CreateLocalOffer(content::WebContents* from_tab) const;
+ std::string CreateAnswer(std::string local_offer,
+ content::WebContents* to_tab) const;
+ void ReceiveAnswer(const std::string& answer,
+ content::WebContents* from_tab) const;
+ void GatherAndSendIceCandidates(content::WebContents* from_tab,
+ content::WebContents* to_tab) const;
+ bool HasStreamWithTrack(content::WebContents* tab,
+ const char* function_name,
+ std::string stream_id,
+ std::string track_id) const;
+
+ infobars::InfoBar* GetUserMediaAndWaitForInfoBar(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+
+ bool detect_errors_in_javascript_;
+ scoped_refptr<const extensions::Extension> desktop_capture_extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcTestBase);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc
new file mode 100644
index 00000000000..3bb61917d8c
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc
@@ -0,0 +1,180 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/common/chrome_paths.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace test {
+
+// Relative to the chrome/test/data directory.
+const base::FilePath::CharType kReferenceFilesDirName[] =
+ FILE_PATH_LITERAL("webrtc/resources");
+const base::FilePath::CharType kReferenceFileName360p[] =
+ FILE_PATH_LITERAL("reference_video_640x360_30fps");
+const base::FilePath::CharType kReferenceFileName720p[] =
+ FILE_PATH_LITERAL("reference_video_1280x720_30fps");
+const base::FilePath::CharType kYuvFileExtension[] = FILE_PATH_LITERAL("yuv");
+const base::FilePath::CharType kY4mFileExtension[] = FILE_PATH_LITERAL("y4m");
+
+// This message describes how to modify your .gclient to get the reference
+// video files downloaded for you.
+const char kAdviseOnGclientSolution[] =
+ "To run this test, you must run download_from_google_storage --config\n"
+ "and follow the instructions (use 'browser' for project id)\n"
+ "You also need to add this solution to your .gclient:\n"
+ "{\n"
+ " \"name\" : \"webrtc.DEPS\",\n"
+ " \"url\" : \"https://chromium.googlesource.com/chromium/deps/"
+ "webrtc/webrtc.DEPS\",\n"
+ "}\n"
+ "and run gclient sync. This will download the required ref files.";
+
+#if defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER) || \
+ defined(ADDRESS_SANITIZER)
+#if defined(OS_CHROMEOS)
+const int kDefaultPollIntervalMsec = 2000;
+#else
+const int kDefaultPollIntervalMsec = 1000;
+#endif // OS_CHROMEOS
+#else
+#if defined(OS_CHROMEOS)
+const int kDefaultPollIntervalMsec = 500;
+#else
+const int kDefaultPollIntervalMsec = 250;
+#endif // OS_CHROMEOS
+#endif
+
+bool IsErrorResult(const std::string& result) {
+ return base::StartsWith(result, "failed-",
+ base::CompareCase::INSENSITIVE_ASCII);
+}
+
+base::FilePath GetReferenceFilesDir() {
+ base::FilePath test_data_dir;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
+
+ return test_data_dir.Append(kReferenceFilesDirName);
+}
+
+base::FilePath GetToolForPlatform(const std::string& tool_name) {
+ base::FilePath tools_dir =
+ GetReferenceFilesDir().Append(FILE_PATH_LITERAL("tools"));
+#if defined(OS_WIN)
+ return tools_dir
+ .Append(FILE_PATH_LITERAL("win"))
+ .AppendASCII(tool_name)
+ .AddExtension(FILE_PATH_LITERAL("exe"));
+#elif defined(OS_MACOSX)
+ return tools_dir.Append(FILE_PATH_LITERAL("mac")).AppendASCII(tool_name);
+#elif defined(OS_LINUX)
+ return tools_dir.Append(FILE_PATH_LITERAL("linux")).AppendASCII(tool_name);
+#else
+ CHECK(false) << "Can't retrieve tool " << tool_name << " on this platform.";
+ return base::FilePath();
+#endif
+}
+
+bool HasReferenceFilesInCheckout() {
+ if (!base::PathExists(GetReferenceFilesDir())) {
+ LOG(ERROR)
+ << "Cannot find the working directory for the reference video "
+ << "files, expected at " << GetReferenceFilesDir().value() << ". " <<
+ kAdviseOnGclientSolution;
+ return false;
+ }
+ return HasYuvAndY4mFile(test::kReferenceFileName360p) &&
+ HasYuvAndY4mFile(test::kReferenceFileName720p);
+}
+
+bool HasYuvAndY4mFile(const base::FilePath::CharType* reference_file) {
+ base::FilePath webrtc_reference_video_yuv = GetReferenceFilesDir()
+ .Append(reference_file).AddExtension(kYuvFileExtension);
+ if (!base::PathExists(webrtc_reference_video_yuv)) {
+ LOG(ERROR)
+ << "Missing YUV reference video to be used for quality"
+ << " comparison, expected at " << webrtc_reference_video_yuv.value()
+ << ". " << kAdviseOnGclientSolution;
+ return false;
+ }
+
+ base::FilePath webrtc_reference_video_y4m = GetReferenceFilesDir()
+ .Append(reference_file).AddExtension(kY4mFileExtension);
+ if (!base::PathExists(webrtc_reference_video_y4m)) {
+ LOG(ERROR)
+ << "Missing Y4M reference video to be used for quality"
+ << " comparison, expected at "<< webrtc_reference_video_y4m.value()
+ << ". " << kAdviseOnGclientSolution;
+ return false;
+ }
+ return true;
+}
+
+bool SleepInJavascript(content::WebContents* tab_contents, int timeout_msec) {
+ const std::string javascript = base::StringPrintf(
+ "setTimeout(function() {"
+ " window.domAutomationController.send('sleep-ok');"
+ "}, %d)", timeout_msec);
+
+ std::string result;
+ bool ok = content::ExecuteScriptAndExtractString(
+ tab_contents, javascript, &result);
+ return ok && result == "sleep-ok";
+}
+
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents) {
+ return PollingWaitUntil(javascript, evaluates_to, tab_contents,
+ kDefaultPollIntervalMsec);
+}
+
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents,
+ int poll_interval_msec) {
+ base::Time start_time = base::Time::Now();
+ base::TimeDelta timeout = TestTimeouts::action_max_timeout();
+ std::string result;
+
+ while (base::Time::Now() - start_time < timeout) {
+ std::string result;
+ if (!content::ExecuteScriptAndExtractString(tab_contents, javascript,
+ &result)) {
+ LOG(ERROR) << "Failed to execute javascript " << javascript;
+ return false;
+ }
+
+ if (evaluates_to == result) {
+ return true;
+ } else if (IsErrorResult(result)) {
+ LOG(ERROR) << "|" << javascript << "| returned an error: " << result;
+ return false;
+ }
+
+ // Sleep a bit here to keep this loop from spinlocking too badly.
+ if (!SleepInJavascript(tab_contents, poll_interval_msec)) {
+ // TODO(phoglund): Figure out why this fails every now and then.
+ // It's not a huge deal if it does though.
+ LOG(ERROR) << "Failed to sleep.";
+ }
+ }
+ LOG(ERROR)
+ << "Timed out while waiting for " << javascript
+ << " to evaluate to " << evaluates_to << "; last result was '" << result
+ << "'";
+ return false;
+}
+
+} // namespace test
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h
new file mode 100644
index 00000000000..cd3ecef1f4c
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h
@@ -0,0 +1,67 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_COMMON_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_COMMON_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/process/process_handle.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace test {
+
+// Reference file locations.
+
+// Checks if the user has the reference files directory, returns true if so.
+// If the user's checkout don't have these dirs, they need to configure their
+// .gclient as described in chrome/test/data/webrtc/resources/README. The reason
+// for this is that we don't want to burden regular chrome devs with downloading
+// these sizable reference files by default.
+bool HasReferenceFilesInCheckout();
+
+// Verifies both the YUV and Y4M version of the reference file exists.
+bool HasYuvAndY4mFile(const base::FilePath::CharType* reference_file);
+
+// Retrieves the reference files dir, to which file names can be appended.
+base::FilePath GetReferenceFilesDir();
+
+// Retrieves a tool binary path from chrome/test/data/webrtc/resources/tools,
+// according to platform. If we're running on Linux, requesting pesq will yield
+// chrome/test/data/webrtc/resources/tools/linux/pesq, whereas the same call on
+// Windows will yield chrome/test/data/webrtc/resources/tools/win/pesq.exe.
+// This function does not check the binary actually exists.
+base::FilePath GetToolForPlatform(const std::string& tool_name);
+
+extern const base::FilePath::CharType kReferenceFileName360p[];
+extern const base::FilePath::CharType kReferenceFileName720p[];
+extern const base::FilePath::CharType kYuvFileExtension[];
+extern const base::FilePath::CharType kY4mFileExtension[];
+extern const char kAdviseOnGclientSolution[];
+
+// Executes javascript code which will sleep for |timeout_msec| milliseconds.
+// Returns true on success.
+bool SleepInJavascript(content::WebContents* tab_contents, int timeout_msec);
+
+// This function will execute the provided |javascript| until it causes a call
+// to window.domAutomationController.send() with |evaluates_to| as the message.
+// That is, we are NOT checking what the javascript evaluates to. Returns false
+// if we exceed the TestTimeouts::action_max_timeout().
+// TODO(phoglund): Consider a better interaction method with the javascript
+// than polling javascript methods.
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents);
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents,
+ int poll_interval_msec);
+
+} // namespace test
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_COMMON_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc
new file mode 100644
index 00000000000..9a61637d902
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc
@@ -0,0 +1,338 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_browsertest_perf.h"
+
+#include <stddef.h>
+
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "testing/perf/perf_result_reporter.h"
+
+namespace {
+
+constexpr char kMetricPrefixAudioReceive[] = "WebRtcAudioReceive.";
+constexpr char kMetricPrefixAudioSend[] = "WebRtcAudioSend.";
+constexpr char kMetricPrefixVideoSend[] = "WebRtcVideoSend.";
+constexpr char kMetricPrefixVideoRecieve[] = "WebRtcVideoReceive.";
+constexpr char kMetricPrefixBwe[] = "WebRtcBwe.";
+constexpr char kMetricPacketsLostFrames[] = "packets_lost";
+constexpr char kMetricGoogJitterRecvMs[] = "goog_jitter_recv";
+constexpr char kMetricGoogExpandRatePercent[] = "goog_expand_rate";
+constexpr char kMetricGoogSpeechExpandRatePercent[] = "goog_speech_expand_rate";
+constexpr char kMetricGoogSecondaryDecodeRatePercent[] =
+ "goog_secondary_decode_rate";
+constexpr char kMetricGoogRttMs[] = "goog_rtt";
+constexpr char kMetricPacketsPerSecondPackets[] = "packets_per_second";
+constexpr char kMetricGoogFpsSentFps[] = "goog_frame_rate_sent";
+constexpr char kMetricGoogFpsInputFps[] = "goog_frame_rate_input";
+constexpr char kMetricGoogFirsReceivedUnitless[] = "goog_firs_recv";
+constexpr char kMetricGoogNacksReceivedUnitless[] = "goog_nacks_recv";
+constexpr char kMetricGoogFrameWidthCount[] = "goog_frame_width";
+constexpr char kMetricGoogFrameHeightCount[] = "goog_frame_height";
+constexpr char kMetricGoogAvgEncodeMs[] = "goog_avg_encode";
+constexpr char kMetricGoogEncodeCpuUsagePercent[] = "goog_encode_cpu_usage";
+constexpr char kMetricGoogFpsRecvFps[] = "goog_frame_rate_recv";
+constexpr char kMetricGoogFpsOutputFps[] = "goog_frame_rate_output";
+constexpr char kMetricGoogActualDelayMs[] = "goog_actual_delay";
+constexpr char kMetricGoogTargetDelayMs[] = "goog_target_delay";
+constexpr char kMetricGoogDecodeTimeMs[] = "goog_decode_time";
+constexpr char kMetricGoogMaxDecodeTimeMs[] = "goog_max_decode_time";
+constexpr char kMetricGoogJitterBufferMs[] = "goog_jitter_buffer";
+constexpr char kMetricGoogRenderDelayMs[] = "goog_render_delay";
+constexpr char kMetricAvailableSendBandwidthBitsPerS[] = "available_send_bw";
+constexpr char kMetricAvailableRecvBandwidthBitsPerS[] = "available_recv_bw";
+constexpr char kMetricTargetEncodeBitrateBitsPerS[] = "target_encode_bitrate";
+constexpr char kMetricActualEncodeBitrateBitsPerS[] = "actual_encode_bitrate";
+constexpr char kMetricTransmitBitrateBitsPerS[] = "transmit_bitrate";
+
+perf_test::PerfResultReporter SetUpAudioReceiveReporter(
+ const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixAudioReceive, story);
+ reporter.RegisterFyiMetric(kMetricPacketsLostFrames, "frames");
+ reporter.RegisterFyiMetric(kMetricGoogJitterRecvMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogExpandRatePercent, "%");
+ reporter.RegisterFyiMetric(kMetricGoogSpeechExpandRatePercent, "%");
+ reporter.RegisterFyiMetric(kMetricGoogSecondaryDecodeRatePercent, "%");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpAudioSendReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixAudioSend, story);
+ reporter.RegisterFyiMetric(kMetricGoogJitterRecvMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogRttMs, "ms");
+ reporter.RegisterFyiMetric(kMetricPacketsPerSecondPackets, "packets");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpVideoSendReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoSend, story);
+ reporter.RegisterFyiMetric(kMetricGoogFpsSentFps, "fps");
+ reporter.RegisterFyiMetric(kMetricGoogFpsInputFps, "fps");
+ reporter.RegisterFyiMetric(kMetricGoogFirsReceivedUnitless, "unitless");
+ reporter.RegisterFyiMetric(kMetricGoogNacksReceivedUnitless, "unitless");
+ reporter.RegisterFyiMetric(kMetricGoogFrameWidthCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogFrameHeightCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogAvgEncodeMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogRttMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogEncodeCpuUsagePercent, "%");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpVideoReceiveReporter(
+ const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoRecieve, story);
+ reporter.RegisterFyiMetric(kMetricGoogFpsRecvFps, "fps");
+ reporter.RegisterFyiMetric(kMetricGoogFpsOutputFps, "fps");
+ reporter.RegisterFyiMetric(kMetricPacketsLostFrames, "frames");
+ reporter.RegisterFyiMetric(kMetricGoogFrameWidthCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogFrameHeightCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogActualDelayMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogTargetDelayMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogDecodeTimeMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogMaxDecodeTimeMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogJitterBufferMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogRenderDelayMs, "ms");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpBweReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixBwe, story);
+ reporter.RegisterFyiMetric(kMetricAvailableSendBandwidthBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricAvailableRecvBandwidthBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricTargetEncodeBitrateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricActualEncodeBitrateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricTransmitBitrateBitsPerS, "bits/s");
+ return reporter;
+}
+
+} // namespace
+
+static std::string Statistic(const std::string& statistic,
+ const std::string& bucket) {
+ // A ssrc stats key will be on the form stats.<bucket>-<key>.values.
+ // This will give a json "path" which will dig into the time series for the
+ // specified statistic. Buckets can be for instance ssrc_1212344, bweforvideo,
+ // and will each contain a bunch of statistics relevant to their nature.
+ // Each peer connection has a number of such buckets.
+ return base::StringPrintf("stats.%s-%s.values", bucket.c_str(),
+ statistic.c_str());
+}
+
+static void MaybePrintResultsForAudioReceive(
+ const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("audioOutputLevel", ssrc), &value)) {
+ // Not an audio receive stream.
+ return;
+ }
+
+ auto reporter = SetUpAudioReceiveReporter(story);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("packetsLost", ssrc), &value));
+ reporter.AddResult(kMetricPacketsLostFrames, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogJitterRecvMs, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googExpandRate", ssrc), &value));
+ reporter.AddResult(kMetricGoogExpandRatePercent, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googSpeechExpandRate", ssrc), &value));
+ reporter.AddResult(kMetricGoogSpeechExpandRatePercent, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googSecondaryDecodedRate", ssrc), &value));
+ reporter.AddResult(kMetricGoogSecondaryDecodeRatePercent, value);
+}
+
+static void MaybePrintResultsForAudioSend(const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("audioInputLevel", ssrc), &value)) {
+ // Not an audio send stream.
+ return;
+ }
+
+ auto reporter = SetUpAudioSendReporter(story);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogJitterRecvMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googRtt", ssrc), &value));
+ reporter.AddResult(kMetricGoogRttMs, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("packetsSentPerSecond", ssrc), &value));
+ reporter.AddResult(kMetricPacketsPerSecondPackets, value);
+}
+
+static void MaybePrintResultsForVideoSend(const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("googFrameRateSent", ssrc), &value)) {
+ // Not a video send stream.
+ return;
+ }
+
+ // Graph these by unit: the dashboard expects all stats in one graph to have
+ // the same unit (e.g. ms, fps, etc). Most graphs, like video_fps, will also
+ // be populated by the counterparts on the video receiving side.
+ auto reporter = SetUpVideoSendReporter(story);
+ reporter.AddResult(kMetricGoogFpsSentFps, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googFrameRateInput", ssrc), &value));
+ reporter.AddResult(kMetricGoogFpsInputFps, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googFirsReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogFirsReceivedUnitless, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googNacksReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogNacksReceivedUnitless, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googFrameWidthSent", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameWidthCount, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameHeightSent", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameHeightCount, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googAvgEncodeMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogAvgEncodeMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googRtt", ssrc), &value));
+ reporter.AddResult(kMetricGoogRttMs, value);
+
+ EXPECT_TRUE(pc_dict.GetString(
+ Statistic("googEncodeUsagePercent", ssrc), &value));
+ reporter.AddResult(kMetricGoogEncodeCpuUsagePercent, value);
+}
+
+static void MaybePrintResultsForVideoReceive(
+ const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("googFrameRateReceived", ssrc), &value)) {
+ // Not a video receive stream.
+ return;
+ }
+
+ auto reporter = SetUpVideoReceiveReporter(story);
+ reporter.AddResult(kMetricGoogFpsRecvFps, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameRateOutput", ssrc), &value));
+ reporter.AddResult(kMetricGoogFpsOutputFps, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("packetsLost", ssrc), &value));
+ reporter.AddResult(kMetricPacketsLostFrames, value);
+
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameWidthReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameWidthCount, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameHeightReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameHeightCount, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googCurrentDelayMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogActualDelayMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googTargetDelayMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogTargetDelayMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googDecodeMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogDecodeTimeMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googMaxDecodeMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogMaxDecodeTimeMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterBufferMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogJitterBufferMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googRenderDelayMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogRenderDelayMs, value);
+}
+
+static std::string ExtractSsrcIdentifier(const std::string& key) {
+ // Example key: ssrc_1234-someStatName. Grab the part before the dash.
+ size_t key_start_pos = 0;
+ size_t key_end_pos = key.find("-");
+ CHECK(key_end_pos != std::string::npos) << "Could not parse key " << key;
+ return key.substr(key_start_pos, key_end_pos - key_start_pos);
+}
+
+// Returns the set of unique ssrc identifiers in the call (e.g. ssrc_1234,
+// ssrc_12356, etc). |stats_dict| is the .stats dict from one peer connection.
+static std::set<std::string> FindAllSsrcIdentifiers(
+ const base::DictionaryValue& stats_dict) {
+ std::set<std::string> result;
+ base::DictionaryValue::Iterator stats_iterator(stats_dict);
+
+ while (!stats_iterator.IsAtEnd()) {
+ if (stats_iterator.key().find("ssrc_") != std::string::npos)
+ result.insert(ExtractSsrcIdentifier(stats_iterator.key()));
+ stats_iterator.Advance();
+ }
+ return result;
+}
+
+namespace test {
+
+void PrintBweForVideoMetrics(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ std::string story = video_codec.empty() ? "baseline_story" : video_codec;
+ story += modifier;
+ const std::string kBweStatsKey = "bweforvideo";
+ std::string value;
+ auto reporter = SetUpBweReporter(story);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googAvailableSendBandwidth", kBweStatsKey), &value));
+ reporter.AddResult(kMetricAvailableSendBandwidthBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googAvailableReceiveBandwidth", kBweStatsKey), &value));
+ reporter.AddResult(kMetricAvailableRecvBandwidthBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googTargetEncBitrate", kBweStatsKey), &value));
+ reporter.AddResult(kMetricTargetEncodeBitrateBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googActualEncBitrate", kBweStatsKey), &value));
+ reporter.AddResult(kMetricActualEncodeBitrateBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googTransmitBitrate", kBweStatsKey), &value));
+ reporter.AddResult(kMetricTransmitBitrateBitsPerS, value);
+}
+
+void PrintMetricsForAllStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ PrintMetricsForSendStreams(pc_dict, modifier, video_codec);
+ PrintMetricsForRecvStreams(pc_dict, modifier, video_codec);
+}
+
+void PrintMetricsForSendStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ std::string story = video_codec.empty() ? "baseline_story" : video_codec;
+ story += modifier;
+ const base::DictionaryValue* stats_dict;
+ ASSERT_TRUE(pc_dict.GetDictionary("stats", &stats_dict));
+ std::set<std::string> ssrc_identifiers = FindAllSsrcIdentifiers(*stats_dict);
+
+ auto ssrc_iterator = ssrc_identifiers.begin();
+ for (; ssrc_iterator != ssrc_identifiers.end(); ++ssrc_iterator) {
+ const std::string& ssrc = *ssrc_iterator;
+ MaybePrintResultsForAudioSend(ssrc, pc_dict, story);
+ MaybePrintResultsForVideoSend(ssrc, pc_dict, story);
+ }
+}
+
+void PrintMetricsForRecvStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ std::string story = video_codec.empty() ? "baseline_story" : video_codec;
+ story += modifier;
+ const base::DictionaryValue* stats_dict;
+ ASSERT_TRUE(pc_dict.GetDictionary("stats", &stats_dict));
+ std::set<std::string> ssrc_identifiers = FindAllSsrcIdentifiers(*stats_dict);
+
+ auto ssrc_iterator = ssrc_identifiers.begin();
+ for (; ssrc_iterator != ssrc_identifiers.end(); ++ssrc_iterator) {
+ const std::string& ssrc = *ssrc_iterator;
+ MaybePrintResultsForAudioReceive(ssrc, pc_dict, story);
+ MaybePrintResultsForVideoReceive(ssrc, pc_dict, story);
+ }
+}
+
+} // namespace test
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h
new file mode 100644
index 00000000000..9394f235b64
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h
@@ -0,0 +1,44 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_PERF_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_PERF_H_
+
+#include <string>
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace test {
+
+// These functions takes parsed data (on one peer connection) from the
+// peerConnectionDataStore object that is backing webrtc-internals and prints
+// metrics they consider interesting using testing/perf/perf_test.h primitives.
+// The idea is to put as many webrtc-related metrics as possible into the
+// dashboard and thereby track it for regressions.
+//
+// These functions expect to run under googletest and will use EXPECT_ and
+// ASSERT_ macros to signal failure. They take as argument one peer connection's
+// stats data and a |modifier| to append to each result bucket. For instance, if
+// the modifier is '_oneway', the rtt stat will be logged as goog_rtt in
+// the video_tx_oneway bucket.
+// If |video_codec| is a non-empty string, the codec name is appended last for
+// video metrics, e.g. 'video_tx_oneway_VP9'.
+void PrintBweForVideoMetrics(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+void PrintMetricsForAllStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+void PrintMetricsForSendStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+void PrintMetricsForRecvStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+
+} // namespace test
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_PERF_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
new file mode 100644
index 00000000000..1b757d9f528
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
@@ -0,0 +1,96 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+static const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+} // namespace
+
+// Top-level integration test for WebRTC. Uses an actual desktop capture
+// extension to capture whole screen.
+class WebRtcDesktopCaptureBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcDesktopCaptureBrowserTest() : left_tab_(nullptr), right_tab_(nullptr) {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Always use fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // Flags use to automatically select the right dekstop source and get
+ // around security restrictions.
+ command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
+ "Entire screen");
+ command_line->AppendSwitch(switches::kEnableUserMediaScreenCapturing);
+ }
+
+ protected:
+ void DetectVideoAndHangUp() {
+ StartDetectingVideo(left_tab_, "remote-view");
+ StartDetectingVideo(right_tab_, "remote-view");
+#if !defined(OS_MACOSX)
+ // Video is choppy on Mac OS X. http://crbug.com/443542.
+ WaitForVideoToPlay(left_tab_);
+ WaitForVideoToPlay(right_tab_);
+#endif
+ HangUp(left_tab_);
+ HangUp(right_tab_);
+ }
+
+ content::WebContents* left_tab_;
+ content::WebContents* right_tab_;
+};
+
+// TODO(crbug.com/796889): Enable on Mac when thread check crash is fixed.
+// TODO(sprang): Figure out why test times out on Win 10 and ChromeOS.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#define MAYBE_RunsScreenshareFromOneTabToAnother \
+ RunsScreenshareFromOneTabToAnother
+#else
+#define MAYBE_RunsScreenshareFromOneTabToAnother \
+ DISABLED_RunsScreenshareFromOneTabToAnother
+#endif
+IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest,
+ MAYBE_RunsScreenshareFromOneTabToAnother) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ LoadDesktopCaptureExtension();
+ left_tab_ = OpenTestPageInNewTab(kMainWebrtcTestHtmlPage);
+ std::string stream_id = GetDesktopMediaStream(left_tab_);
+ EXPECT_NE(stream_id, "");
+
+ LOG(INFO) << "Opened desktop media stream, got id " << stream_id;
+
+ const std::string constraints =
+ "{audio: false, video: {mandatory: {chromeMediaSource: 'desktop',"
+ "chromeMediaSourceId: '" +
+ stream_id + "'}}}";
+ EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+ left_tab_, constraints));
+ right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+ VerifyStatsGeneratedCallback(right_tab_);
+ DetectVideoAndHangUp();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
new file mode 100644
index 00000000000..c8897585016
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/common/channel_info.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+// This tests the --disable-webrtc-encryption command line flag. Disabling
+// encryption should only be possible on certain channels.
+
+// NOTE: The test case for each channel will only be exercised when the browser
+// is actually built for that channel. This is not ideal. One can test manually
+// by e.g. faking the channel returned in chrome::GetChannel(). It's likely good
+// to have the test anyway, even though a failure might not be detected until a
+// branch has been promoted to another channel. The unit test for
+// ChromeContentBrowserClient::MaybeCopyDisableWebRtcEncryptionSwitch tests for
+// all channels however.
+// TODO(grunell): Test the different channel cases for any build.
+class WebRtcDisableEncryptionFlagBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcDisableEncryptionFlagBrowserTest() {}
+ ~WebRtcDisableEncryptionFlagBrowserTest() override {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // This test should run with fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // Disable encryption with the command line flag.
+ command_line->AppendSwitch(switches::kDisableWebRtcEncryption);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRtcDisableEncryptionFlagBrowserTest);
+};
+
+// Makes a call and checks that there's encryption or not in the SDP offer.
+// TODO(crbug.com/910216): De-flake this for ChromeOs.
+// TODO(crbug.com/984879): De-flake this for MSAN Linux.
+#if defined(OS_CHROMEOS) || (defined(OS_LINUX) && defined(MEMORY_SANITIZER))
+#define MAYBE_VerifyEncryption DISABLED_VerifyEncryption
+#else
+#define MAYBE_VerifyEncryption VerifyEncryption
+#endif
+IN_PROC_BROWSER_TEST_F(WebRtcDisableEncryptionFlagBrowserTest,
+ MAYBE_VerifyEncryption) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* left_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ content::WebContents* right_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+
+ NegotiateCall(left_tab, right_tab);
+
+ StartDetectingVideo(left_tab, "remote-view");
+ StartDetectingVideo(right_tab, "remote-view");
+
+ WaitForVideoToPlay(left_tab);
+ WaitForVideoToPlay(right_tab);
+
+ bool should_detect_encryption = true;
+ version_info::Channel channel = chrome::GetChannel();
+ if (channel == version_info::Channel::UNKNOWN ||
+ channel == version_info::Channel::CANARY ||
+ channel == version_info::Channel::DEV) {
+ should_detect_encryption = false;
+ }
+#if defined(OS_ANDROID)
+ if (channel == version_info::Channel::BETA)
+ should_detect_encryption = false;
+#endif
+
+ std::string expected_string = should_detect_encryption ?
+ "crypto-seen" : "no-crypto-seen";
+
+ ASSERT_EQ(expected_string,
+ ExecuteJavascript("hasSeenCryptoInSdp()", left_tab));
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc
new file mode 100644
index 00000000000..59f14e44920
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc
@@ -0,0 +1,423 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
+
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+namespace webrtc_event_logging {
+
+const size_t kWebRtcEventLogMaxUploadIdBytes = 100;
+
+namespace {
+// Compactness is not important for these few and small files; we therefore
+// go with a human-readable format.
+const char kCaptureTimeLinePrefix[] =
+ "Capture time (seconds since UNIX epoch): ";
+const char kUploadTimeLinePrefix[] = "Upload time (seconds since UNIX epoch): ";
+const char kUploadIdLinePrefix[] = "Upload ID: ";
+
+// No need to use \r\n for Windows; better have a consistent file format
+// between platforms.
+const char kEOL[] = "\n";
+static_assert(base::size(kEOL) == 1 + 1 /* +1 for the implicit \0. */,
+ "SplitString relies on this being a single character.");
+
+// |time| must *not* be earlier than UNIX epoch start. If it is, the empty
+// string is returned.
+std::string DeltaFromEpochSeconds(base::Time time) {
+ if (time.is_null() || time.is_min() || time.is_max()) {
+ LOG(ERROR) << "Not a valid time (" << time << ").";
+ return std::string();
+ }
+
+ const base::Time epoch = base::Time::UnixEpoch();
+ if (time < epoch) {
+ LOG(WARNING) << "Time to go back to the future.";
+ return std::string();
+ }
+
+ return base::NumberToString((time - epoch).InSeconds());
+}
+
+// Helper for ParseTime; see its documentation for details.
+base::Time StringToTime(const std::string& time) {
+ int64_t seconds_from_epoch;
+ if (!base::StringToInt64(time, &seconds_from_epoch) ||
+ seconds_from_epoch < 0) {
+ LOG(WARNING) << "Error encountered while reading time.";
+ return base::Time();
+ }
+
+ return base::Time::UnixEpoch() +
+ base::TimeDelta::FromSeconds(seconds_from_epoch);
+}
+
+// Convert a history file's timestamp, which is the number of seconds since
+// UNIX epoch, into a base::Time object.
+// This function errors on timestamps from UNIX epoch or before it.
+bool ParseTime(const std::string& line,
+ const std::string& prefix,
+ base::Time* out) {
+ DCHECK(line.find(prefix) == 0);
+ DCHECK(out);
+
+ if (!out->is_null()) {
+ LOG(WARNING) << "Repeated line.";
+ return false;
+ }
+
+ const base::Time time = StringToTime(line.substr(prefix.length()));
+ if (time.is_null()) {
+ LOG(WARNING) << "Null time.";
+ return false;
+ }
+
+ *out = time;
+
+ return true;
+}
+
+bool ParseString(const std::string& line,
+ const std::string& prefix,
+ std::string* out) {
+ DCHECK(line.find(prefix) == 0);
+ DCHECK(out);
+
+ if (!out->empty()) {
+ LOG(WARNING) << "Repeated line.";
+ return false;
+ }
+
+ *out = line.substr(prefix.length());
+
+ if (out->empty()) {
+ LOG(WARNING) << "Empty string.";
+ return false;
+ }
+
+ return true;
+}
+} // namespace
+
+std::unique_ptr<WebRtcEventLogHistoryFileWriter>
+WebRtcEventLogHistoryFileWriter::Create(const base::FilePath& path) {
+ auto history_file_writer =
+ base::WrapUnique(new WebRtcEventLogHistoryFileWriter(path));
+ if (!history_file_writer->Init()) {
+ LOG(WARNING) << "Initialization of history file writer failed.";
+ return nullptr;
+ }
+ return history_file_writer;
+}
+
+WebRtcEventLogHistoryFileWriter::WebRtcEventLogHistoryFileWriter(
+ const base::FilePath& path)
+ : path_(path), valid_(false) {}
+
+bool WebRtcEventLogHistoryFileWriter::Init() {
+ DCHECK(!valid_);
+
+ if (base::PathExists(path_)) {
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "History file already exists, and could not be deleted.";
+ return false;
+ } else {
+ LOG(WARNING) << "History file already existed; deleted.";
+ }
+ }
+
+ // Attempt to create the file.
+ constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE;
+ file_.Initialize(path_, file_flags);
+ if (!file_.IsValid() || !file_.created()) {
+ LOG(WARNING) << "Couldn't create history file.";
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+ return false;
+ }
+
+ valid_ = true;
+ return true;
+}
+
+bool WebRtcEventLogHistoryFileWriter::WriteCaptureTime(
+ base::Time capture_time) {
+ DCHECK(valid_);
+
+ if (capture_time.is_null()) {
+ valid_ = false;
+ return false;
+ }
+
+ const std::string delta_seconds = DeltaFromEpochSeconds(capture_time);
+ if (delta_seconds.empty()) {
+ valid_ = false;
+ return false;
+ }
+
+ const bool written = Write(kCaptureTimeLinePrefix + delta_seconds + kEOL);
+ if (!written) {
+ // Error logged by Write().
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcEventLogHistoryFileWriter::WriteUploadTime(base::Time upload_time) {
+ DCHECK(valid_);
+
+ if (upload_time.is_null()) {
+ valid_ = false;
+ return false;
+ }
+
+ const std::string delta_seconds = DeltaFromEpochSeconds(upload_time);
+ if (delta_seconds.empty()) {
+ valid_ = false;
+ return false;
+ }
+
+ const bool written = Write(kUploadTimeLinePrefix + delta_seconds + kEOL);
+ if (!written) {
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcEventLogHistoryFileWriter::WriteUploadId(
+ const std::string& upload_id) {
+ DCHECK(valid_);
+ DCHECK(!upload_id.empty());
+ DCHECK_LE(upload_id.length(), kWebRtcEventLogMaxUploadIdBytes);
+
+ const bool written = Write(kUploadIdLinePrefix + upload_id + kEOL);
+ if (!written) {
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+void WebRtcEventLogHistoryFileWriter::Delete() {
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "History file could not be deleted.";
+ }
+
+ valid_ = false; // Like was already false.
+}
+
+base::FilePath WebRtcEventLogHistoryFileWriter::path() const {
+ DCHECK(valid_); // Can be performed on invalid objects, but likely shouldn't.
+ return path_;
+}
+
+bool WebRtcEventLogHistoryFileWriter::Write(const std::string& str) {
+ DCHECK(valid_);
+ DCHECK(!str.empty());
+ DCHECK_LE(str.length(), static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ const int written = file_.WriteAtCurrentPos(str.c_str(), str.length());
+ if (written != static_cast<int>(str.length())) {
+ LOG(WARNING) << "Writing to history file failed.";
+ valid_ = false;
+ return false;
+ }
+
+ // Writes to the history file are infrequent, and happen on a |task_runner_|
+ // dedicated to event logs. We can therefore afford to Flush() after every
+ // write, giving us greater confidence that information would not get lost if,
+ // e.g., Chrome crashes.
+ file_.Flush();
+
+ return true;
+}
+
+std::unique_ptr<WebRtcEventLogHistoryFileReader>
+WebRtcEventLogHistoryFileReader::Create(const base::FilePath& path) {
+ auto history_file_reader =
+ base::WrapUnique(new WebRtcEventLogHistoryFileReader(path));
+ if (!history_file_reader->Init()) {
+ LOG(WARNING) << "Initialization of history file reader failed.";
+ return nullptr;
+ }
+ return history_file_reader;
+}
+
+WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
+ const base::FilePath& path)
+ : path_(path),
+ local_id_(ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(path_)),
+ valid_(false) {}
+
+WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
+ WebRtcEventLogHistoryFileReader&& other)
+ : path_(other.path_),
+ local_id_(other.local_id_),
+ capture_time_(other.capture_time_),
+ upload_time_(other.upload_time_),
+ upload_id_(other.upload_id_),
+ valid_(other.valid_) {
+ other.valid_ = false;
+}
+
+bool WebRtcEventLogHistoryFileReader::Init() {
+ DCHECK(!valid_);
+
+ if (local_id_.empty()) {
+ LOG(WARNING) << "Unknown local ID.";
+ return false;
+ }
+
+ if (local_id_.length() > kWebRtcEventLogMaxUploadIdBytes) {
+ LOG(WARNING) << "Excessively long local ID.";
+ return false;
+ }
+
+ if (!base::PathExists(path_)) {
+ LOG(WARNING) << "File does not exist.";
+ return false;
+ }
+
+ constexpr int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+ base::File file(path_, file_flags);
+ if (!file.IsValid()) {
+ LOG(WARNING) << "Couldn't read history file.";
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+ return false;
+ }
+
+ constexpr size_t kMaxHistoryFileSizeBytes = 1024;
+ static_assert(kWebRtcEventLogMaxUploadIdBytes < kMaxHistoryFileSizeBytes, "");
+
+ std::string file_contents;
+ file_contents.resize(kMaxHistoryFileSizeBytes);
+ const int read_bytes = file.Read(0, &file_contents[0], file_contents.size());
+ if (read_bytes < 0) {
+ LOG(WARNING) << "Couldn't read contents of history file.";
+ return false;
+ }
+ DCHECK_LE(static_cast<size_t>(read_bytes), file_contents.size());
+ file_contents.resize(static_cast<size_t>(read_bytes));
+ // Note: In excessively long files, the rest of the file will be ignored; the
+ // beginning of the file will encounter a parse error.
+
+ if (!Parse(file_contents)) {
+ LOG(WARNING) << "Parsing of history file failed.";
+ return false;
+ }
+
+ valid_ = true;
+ return true;
+}
+
+std::string WebRtcEventLogHistoryFileReader::LocalId() const {
+ DCHECK(valid_);
+ DCHECK(!local_id_.empty());
+ return local_id_;
+}
+
+base::Time WebRtcEventLogHistoryFileReader::CaptureTime() const {
+ DCHECK(valid_);
+ DCHECK(!capture_time_.is_null());
+ return capture_time_;
+}
+
+base::Time WebRtcEventLogHistoryFileReader::UploadTime() const {
+ DCHECK(valid_);
+ return upload_time_; // May be null (which indicates "unset").
+}
+
+std::string WebRtcEventLogHistoryFileReader::UploadId() const {
+ DCHECK(valid_);
+ return upload_id_;
+}
+
+base::FilePath WebRtcEventLogHistoryFileReader::path() const {
+ DCHECK(valid_);
+ return path_;
+}
+
+bool WebRtcEventLogHistoryFileReader::operator<(
+ const WebRtcEventLogHistoryFileReader& other) const {
+ DCHECK(valid_);
+ DCHECK(!capture_time_.is_null());
+ DCHECK(other.valid_);
+ DCHECK(!other.capture_time_.is_null());
+ if (capture_time_ == other.capture_time_) {
+ // Resolve ties arbitrarily, but consistently (Local IDs are unique).
+ return LocalId() < other.LocalId();
+ }
+ return (capture_time_ < other.capture_time_);
+}
+
+bool WebRtcEventLogHistoryFileReader::Parse(const std::string& file_contents) {
+ DCHECK(!valid_);
+ DCHECK(capture_time_.is_null());
+ DCHECK(upload_time_.is_null());
+ DCHECK(upload_id_.empty());
+
+ const std::vector<std::string> lines =
+ base::SplitString(file_contents, kEOL, base::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+
+ for (const std::string& line : lines) {
+ if (line.find(kCaptureTimeLinePrefix) == 0) {
+ if (!ParseTime(line, kCaptureTimeLinePrefix, &capture_time_)) {
+ return false;
+ }
+ } else if (line.find(kUploadTimeLinePrefix) == 0) {
+ if (!ParseTime(line, kUploadTimeLinePrefix, &upload_time_)) {
+ return false;
+ }
+ } else if (line.find(kUploadIdLinePrefix) == 0) {
+ if (!ParseString(line, kUploadIdLinePrefix, &upload_id_)) {
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Unrecognized line in history file.";
+ return false;
+ }
+ }
+
+ if (capture_time_.is_null()) {
+ LOG(WARNING) << "Incomplete history file; capture time unknown.";
+ return false;
+ }
+
+ if (!upload_id_.empty() && upload_time_.is_null()) {
+ LOG(WARNING) << "Incomplete history file; upload time known, "
+ << "but ID unknown.";
+ return false;
+ }
+
+ if (!upload_time_.is_null() && upload_time_ < capture_time_) {
+ LOG(WARNING) << "Defective history file; claims to have been uploaded "
+ << "before being captured.";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h
new file mode 100644
index 00000000000..96180ec1ede
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h
@@ -0,0 +1,121 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_HISTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_HISTORY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace webrtc_event_logging {
+
+// Writes a small history file to disk, which allows us to remember what logs
+// were captured and uploaded, after they are uploaded (whether successfully or
+// not), or after they ware pruned (if they expire before an upload opportunity
+// presents itself).
+class WebRtcEventLogHistoryFileWriter final {
+ public:
+ // Creates and initializes a WebRtcEventLogHistoryFileWriter object.
+ // Overwrites existing files on disk, if any.
+ // If initialization fails (e.g. couldn't create the file), an empty
+ // unique_ptr is returned.
+ static std::unique_ptr<WebRtcEventLogHistoryFileWriter> Create(
+ const base::FilePath& path);
+
+ // The capture time must be later than UNIX epoch start.
+ bool WriteCaptureTime(base::Time capture_time);
+
+ // The upload time must be later than UNIX epoch start.
+ // Writing an upload time earlier than the capture time is not prevented,
+ // but an invalid history file will be produced.
+ bool WriteUploadTime(base::Time upload_time);
+
+ // If |upload_id| is empty, it means the upload was not successful. In that
+ // case, the |upload_time| still denotes the time when the upload started.
+ // |upload_id|'s length must not exceed kWebRtcEventLogMaxUploadIdBytes.
+ bool WriteUploadId(const std::string& upload_id);
+
+ // Deletes the file being written to, and invalidates this object.
+ void Delete();
+
+ // May only be called on a valid object.
+ base::FilePath path() const;
+
+ private:
+ explicit WebRtcEventLogHistoryFileWriter(const base::FilePath& path);
+
+ // Returns true if initialization was successful; false otherwise.
+ // Overwrites existing files on disk, if any.
+ bool Init();
+
+ // Returns true if and only if the entire string was written to the file.
+ bool Write(const std::string& str);
+
+ const base::FilePath path_;
+ base::File file_;
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogHistoryFileWriter);
+};
+
+// Reads from disk a small history file and recovers the data from it.
+class WebRtcEventLogHistoryFileReader final {
+ public:
+ // Creates and initializes a WebRtcEventLogHistoryFileReader object.
+ // If initialization fails (e.g. couldn't parse the file), an empty
+ // unique_ptr is returned.
+ static std::unique_ptr<WebRtcEventLogHistoryFileReader> Create(
+ const base::FilePath& path);
+
+ WebRtcEventLogHistoryFileReader(WebRtcEventLogHistoryFileReader&& other);
+
+ // Mandatory fields.
+ std::string LocalId() const; // Must return a non-empty ID.
+ base::Time CaptureTime() const; // Must return a non-null base::Time.
+
+ // Optional fields.
+ base::Time UploadTime() const; // Non-null only if upload was attempted.
+ std::string UploadId() const; // Non-null only if upload was successful.
+
+ // May only be performed on a valid object.
+ base::FilePath path() const;
+
+ // Compares by capture time.
+ bool operator<(const WebRtcEventLogHistoryFileReader& other) const;
+
+ private:
+ explicit WebRtcEventLogHistoryFileReader(const base::FilePath& path);
+
+ // Returns true if initialization was successful; false otherwise.
+ // If true is returned, |this| is now valid, and will remain so until
+ // the object is destroyed or std::move()-ed away from.
+ bool Init();
+
+ // Returns true if parsing succeeded; false otherwise.
+ bool Parse(const std::string& file_contents);
+
+ const base::FilePath path_;
+
+ const std::string local_id_;
+
+ // Mandatory field; must be non-null (and therefore also non-zero).
+ base::Time capture_time_;
+
+ // Optional fields; may appear 0 or 1 times in the file.
+ base::Time upload_time_; // Nullness/zero-ness indicates "unset".
+ std::string upload_id_; // Empty string indicates "unset".
+
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogHistoryFileReader);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_HISTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
new file mode 100644
index 00000000000..577b56b007a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
@@ -0,0 +1,1086 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/render_process_host.h"
+
+#if defined OS_CHROMEOS
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#endif
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#endif
+
+namespace webrtc_event_logging {
+
+namespace {
+
+using BrowserContext = content::BrowserContext;
+using BrowserThread = content::BrowserThread;
+using RenderProcessHost = content::RenderProcessHost;
+
+using BrowserContextId = WebRtcEventLogManager::BrowserContextId;
+
+class PeerConnectionTrackerProxyImpl
+ : public WebRtcEventLogManager::PeerConnectionTrackerProxy {
+ public:
+ ~PeerConnectionTrackerProxyImpl() override = default;
+
+ void EnableWebRtcEventLogging(const WebRtcEventLogPeerConnectionKey& key,
+ int output_period_ms) override {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &PeerConnectionTrackerProxyImpl::EnableWebRtcEventLoggingInternal,
+ key, output_period_ms));
+ }
+
+ void DisableWebRtcEventLogging(
+ const WebRtcEventLogPeerConnectionKey& key) override {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &PeerConnectionTrackerProxyImpl::DisableWebRtcEventLoggingInternal,
+ key));
+ }
+
+ private:
+ static void EnableWebRtcEventLoggingInternal(
+ WebRtcEventLogPeerConnectionKey key,
+ int output_period_ms) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHost* host = RenderProcessHost::FromID(key.render_process_id);
+ if (!host) {
+ return; // The host has been asynchronously removed; not a problem.
+ }
+ host->EnableWebRtcEventLogOutput(key.lid, output_period_ms);
+ }
+
+ static void DisableWebRtcEventLoggingInternal(
+ WebRtcEventLogPeerConnectionKey key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHost* host = RenderProcessHost::FromID(key.render_process_id);
+ if (!host) {
+ return; // The host has been asynchronously removed; not a problem.
+ }
+ host->DisableWebRtcEventLogOutput(key.lid);
+ }
+};
+
+// Check whether remote-bound logging is generally allowed, although not
+// necessarily for any given user profile.
+// 1. Certain platforms (mobile) are blocked from remote-bound logging.
+// 2. There is a Finch-controlled kill-switch for the feature.
+bool IsRemoteLoggingFeatureEnabled() {
+#if defined(OS_ANDROID)
+ bool enabled = false;
+#else
+ bool enabled = base::FeatureList::IsEnabled(features::kWebRtcRemoteEventLog);
+#endif
+
+ VLOG(1) << "WebRTC remote-bound event logging "
+ << (enabled ? "enabled" : "disabled") << ".";
+
+ return enabled;
+}
+
+// Checks whether the Profile is considered managed. Used to
+// determine the default value for the policy controlling event logging.
+bool IsBrowserManagedForProfile(const Profile* profile) {
+// For Chrome OS, exclude the signin profile and ephemeral profiles.
+#if defined(OS_CHROMEOS)
+ if (chromeos::ProfileHelper::IsSigninProfile(profile) ||
+ chromeos::ProfileHelper::IsEphemeralUserProfile(profile)) {
+ return false;
+ }
+#endif
+
+ // Child accounts should not have a logging default of true so
+ // we do not consider them as being managed here.
+ if (profile->IsChild()) {
+ return false;
+ }
+
+ if (profile->GetProfilePolicyConnector()
+ ->policy_service()
+ ->IsInitializationComplete(policy::POLICY_DOMAIN_CHROME) &&
+ profile->GetProfilePolicyConnector()->IsManaged()) {
+ return true;
+ }
+
+ // For desktop, machine level policies (Windows, Linux, Mac OS) can affect
+ // user profiles, so we consider these profiles managed.
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ return g_browser_process->browser_policy_connector()
+ ->HasMachineLevelPolicies();
+#else
+ return false;
+#endif
+}
+
+BrowserContext* GetBrowserContext(int render_process_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHost* const host = RenderProcessHost::FromID(render_process_id);
+ return host ? host->GetBrowserContext() : nullptr;
+}
+
+// Post reply back if non-empty.
+template <typename... Args>
+inline void MaybeReply(const base::Location& location,
+ base::OnceCallback<void(Args...)> reply,
+ Args... args) {
+ if (reply) {
+ base::PostTask(location, {BrowserThread::UI},
+ base::BindOnce(std::move(reply), args...));
+ }
+}
+
+} // namespace
+
+WebRtcEventLogManager* WebRtcEventLogManager::g_webrtc_event_log_manager =
+ nullptr;
+
+std::unique_ptr<WebRtcEventLogManager>
+WebRtcEventLogManager::CreateSingletonInstance() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!g_webrtc_event_log_manager);
+ g_webrtc_event_log_manager = new WebRtcEventLogManager;
+ return base::WrapUnique<WebRtcEventLogManager>(g_webrtc_event_log_manager);
+}
+
+WebRtcEventLogManager* WebRtcEventLogManager::GetInstance() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return g_webrtc_event_log_manager;
+}
+
+base::FilePath WebRtcEventLogManager::GetRemoteBoundWebRtcEventLogsDir(
+ content::BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+ // Incognito BrowserContext will return their parent profile's directory.
+ return webrtc_event_logging::GetRemoteBoundWebRtcEventLogsDir(
+ browser_context->GetPath());
+}
+
+WebRtcEventLogManager::WebRtcEventLogManager()
+ : task_runner_(base::CreateSequencedTaskRunner(
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+ remote_logging_feature_enabled_(IsRemoteLoggingFeatureEnabled()),
+ local_logs_observer_(nullptr),
+ remote_logs_observer_(nullptr),
+ local_logs_manager_(this),
+ remote_logs_manager_(this, task_runner_),
+ pc_tracker_proxy_(new PeerConnectionTrackerProxyImpl),
+ first_browser_context_initializations_done_(false) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!g_webrtc_event_log_manager);
+ g_webrtc_event_log_manager = this;
+}
+
+WebRtcEventLogManager::~WebRtcEventLogManager() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ for (RenderProcessHost* host : observed_render_process_hosts_) {
+ host->RemoveObserver(this);
+ }
+
+ DCHECK(g_webrtc_event_log_manager);
+ g_webrtc_event_log_manager = nullptr;
+}
+
+void WebRtcEventLogManager::EnableForBrowserContext(
+ BrowserContext* browser_context,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+ CHECK(!browser_context->IsOffTheRecord());
+
+ if (!first_browser_context_initializations_done_) {
+ OnFirstBrowserContextLoaded();
+ first_browser_context_initializations_done_ = true;
+ }
+
+ StartListeningForPrefChangeForBrowserContext(browser_context);
+
+ if (!IsRemoteLoggingAllowedForBrowserContext(browser_context)) {
+ // If remote-bound logging was enabled during a previous Chrome session,
+ // it might have produced some pending log files, which we will now
+ // wish to remove.
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::
+ RemovePendingRemoteBoundLogsForNotEnabledBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ browser_context->GetPath(), std::move(reply)));
+ return;
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::EnableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ browser_context->GetPath(), std::move(reply)));
+}
+
+void WebRtcEventLogManager::DisableForBrowserContext(
+ content::BrowserContext* browser_context,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+
+ StopListeningForPrefChangeForBrowserContext(browser_context);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::DisableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::PeerConnectionAdded(
+ int render_process_id,
+ int lid,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ RenderProcessHost* rph = RenderProcessHost::FromID(render_process_id);
+ if (!rph) {
+ // RPH died before processing of this notification.
+ MaybeReply(FROM_HERE, std::move(reply), false);
+ return;
+ }
+
+ auto it = observed_render_process_hosts_.find(rph);
+ if (it == observed_render_process_hosts_.end()) {
+ // This is the first PeerConnection which we see that's associated
+ // with this RPH.
+ rph->AddObserver(this);
+ observed_render_process_hosts_.insert(rph);
+ }
+
+ const auto browser_context_id = GetBrowserContextId(rph->GetBrowserContext());
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::PeerConnectionAddedInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::PeerConnectionRemoved(
+ int render_process_id,
+ int lid,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(render_process_id);
+ if (browser_context_id == kNullBrowserContextId) {
+ // RPH died before processing of this notification. This is handled by
+ // RenderProcessExited() / RenderProcessHostDestroyed.
+ MaybeReply(FROM_HERE, std::move(reply), false);
+ return;
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::PeerConnectionRemovedInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::PeerConnectionStopped(
+ int render_process_id,
+ int lid,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return PeerConnectionRemoved(render_process_id, lid, std::move(reply));
+}
+
+void WebRtcEventLogManager::PeerConnectionSessionIdSet(
+ int render_process_id,
+ int lid,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(render_process_id);
+ if (browser_context_id == kNullBrowserContextId) {
+ // RPH died before processing of this notification. This is handled by
+ // RenderProcessExited() / RenderProcessHostDestroyed.
+ MaybeReply(FROM_HERE, std::move(reply), false);
+ return;
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::PeerConnectionSessionIdSetInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ session_id, std::move(reply)));
+}
+
+void WebRtcEventLogManager::EnableLocalLogging(
+ const base::FilePath& base_path,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ EnableLocalLogging(base_path, kDefaultMaxLocalLogFileSizeBytes,
+ std::move(reply));
+}
+
+void WebRtcEventLogManager::EnableLocalLogging(
+ const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!base_path.empty());
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::EnableLocalLoggingInternal,
+ base::Unretained(this), base_path, max_file_size_bytes,
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::DisableLocalLogging(
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::DisableLocalLoggingInternal,
+ base::Unretained(this), std::move(reply)));
+}
+
+void WebRtcEventLogManager::OnWebRtcEventLogWrite(
+ int render_process_id,
+ int lid,
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const BrowserContext* browser_context = GetBrowserContext(render_process_id);
+ if (!browser_context) {
+ // RPH died before processing of this notification.
+ MaybeReply(FROM_HERE, std::move(reply), std::make_pair(false, false));
+ return;
+ }
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::OnWebRtcEventLogWriteInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ message, std::move(reply)));
+}
+
+void WebRtcEventLogManager::StartRemoteLogging(
+ int render_process_id,
+ const std::string& session_id,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ BrowserContext* browser_context = GetBrowserContext(render_process_id);
+ const char* error = nullptr;
+
+ if (!browser_context) {
+ // RPH died before processing of this notification.
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kDeadRph);
+ error = kStartRemoteLoggingFailureDeadRenderProcessHost;
+ } else if (!IsRemoteLoggingAllowedForBrowserContext(browser_context)) {
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kFeatureDisabled);
+ error = kStartRemoteLoggingFailureFeatureDisabled;
+ } else if (browser_context->IsOffTheRecord()) {
+ // Feature disable in incognito. Since the feature can be disabled for
+ // non-incognito sessions, this should not expose incognito mode.
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIncognito);
+ error = kStartRemoteLoggingFailureFeatureDisabled;
+ }
+
+ if (error) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(std::move(reply), false, std::string(),
+ std::string(error)));
+ return;
+ }
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::StartRemoteLoggingInternal,
+ base::Unretained(this), render_process_id,
+ browser_context_id, session_id, browser_context->GetPath(),
+ max_file_size_bytes, output_period_ms, web_app_id,
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::ClearCacheForBrowserContext(
+ const BrowserContext* browser_context,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::ClearCacheForBrowserContextInternal,
+ base::Unretained(this), browser_context_id, delete_begin, delete_end),
+ std::move(reply));
+}
+
+void WebRtcEventLogManager::GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcEventLogManager::GetHistoryInternal,
+ base::Unretained(this), browser_context_id,
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetLocalLogsObserver(
+ WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::SetLocalLogsObserverInternal,
+ base::Unretained(this), observer, std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetRemoteLogsObserver(
+ WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::SetRemoteLogsObserverInternal,
+ base::Unretained(this), observer, std::move(reply)));
+}
+
+bool WebRtcEventLogManager::IsRemoteLoggingAllowedForBrowserContext(
+ BrowserContext* browser_context) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+
+ if (!remote_logging_feature_enabled_) {
+ return false;
+ }
+
+ const Profile* profile = Profile::FromBrowserContext(browser_context);
+ DCHECK(profile);
+
+ const PrefService::Preference* webrtc_event_log_collection_allowed_pref =
+ profile->GetPrefs()->FindPreference(
+ prefs::kWebRtcEventLogCollectionAllowed);
+ DCHECK(webrtc_event_log_collection_allowed_pref);
+
+ if (webrtc_event_log_collection_allowed_pref->IsDefaultValue()) {
+ // The pref has not been set. GetBoolean would only return the default
+ // value. However, there is no single default value,
+ // because it depends on whether Chrome is managed,
+ // so we check whether Chrome is managed.
+ // TODO(https://crbug.com/980132): use generalized policy default
+ // mechanism when it is available.
+ const bool managed = IsBrowserManagedForProfile(profile);
+ constexpr bool kCollectionAllowedDefaultManaged = true;
+ constexpr bool kCollectionAllowedDefaultUnManaged = false;
+ return managed ? kCollectionAllowedDefaultManaged
+ : kCollectionAllowedDefaultUnManaged;
+ }
+
+ // There is a non-default value set, so this value is authoritative.
+ return profile->GetPrefs()->GetBoolean(
+ prefs::kWebRtcEventLogCollectionAllowed);
+}
+
+std::unique_ptr<LogFileWriter::Factory>
+WebRtcEventLogManager::CreateRemoteLogFileWriterFactory() {
+ if (remote_log_file_writer_factory_for_testing_) {
+ return std::move(remote_log_file_writer_factory_for_testing_);
+#if !defined(OS_ANDROID)
+ } else if (base::FeatureList::IsEnabled(
+ features::kWebRtcRemoteEventLogGzipped)) {
+ return std::make_unique<GzippedLogFileWriterFactory>(
+ std::make_unique<GzipLogCompressorFactory>(
+ std::make_unique<DefaultGzippedSizeEstimator::Factory>()));
+#endif
+ } else {
+ return std::make_unique<BaseLogFileWriterFactory>();
+ }
+}
+
+void WebRtcEventLogManager::RenderProcessExited(
+ RenderProcessHost* host,
+ const content::ChildProcessTerminationInfo& info) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHostExitedDestroyed(host);
+}
+
+void WebRtcEventLogManager::RenderProcessHostDestroyed(
+ RenderProcessHost* host) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHostExitedDestroyed(host);
+}
+
+void WebRtcEventLogManager::RenderProcessHostExitedDestroyed(
+ RenderProcessHost* host) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(host);
+
+ auto it = observed_render_process_hosts_.find(host);
+ if (it == observed_render_process_hosts_.end()) {
+ return; // We've never seen PeerConnections associated with this RPH.
+ }
+ host->RemoveObserver(this);
+ observed_render_process_hosts_.erase(host);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::RenderProcessExitedInternal,
+ base::Unretained(this), host->GetID()));
+}
+
+void WebRtcEventLogManager::OnLocalLogStarted(PeerConnectionKey peer_connection,
+ const base::FilePath& file_path) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ constexpr int kLogOutputPeriodMsForLocalLogging = 0; // No batching.
+ OnLoggingTargetStarted(LoggingTarget::kLocalLogging, peer_connection,
+ kLogOutputPeriodMsForLocalLogging);
+
+ if (local_logs_observer_) {
+ local_logs_observer_->OnLocalLogStarted(peer_connection, file_path);
+ }
+}
+
+void WebRtcEventLogManager::OnLocalLogStopped(
+ PeerConnectionKey peer_connection) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ OnLoggingTargetStopped(LoggingTarget::kLocalLogging, peer_connection);
+
+ if (local_logs_observer_) {
+ local_logs_observer_->OnLocalLogStopped(peer_connection);
+ }
+}
+
+void WebRtcEventLogManager::OnRemoteLogStarted(PeerConnectionKey key,
+ const base::FilePath& file_path,
+ int output_period_ms) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ OnLoggingTargetStarted(LoggingTarget::kRemoteLogging, key, output_period_ms);
+ if (remote_logs_observer_) {
+ remote_logs_observer_->OnRemoteLogStarted(key, file_path, output_period_ms);
+ }
+}
+
+void WebRtcEventLogManager::OnRemoteLogStopped(
+ WebRtcEventLogPeerConnectionKey key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ OnLoggingTargetStopped(LoggingTarget::kRemoteLogging, key);
+ if (remote_logs_observer_) {
+ remote_logs_observer_->OnRemoteLogStopped(key);
+ }
+}
+
+void WebRtcEventLogManager::OnLoggingTargetStarted(LoggingTarget target,
+ PeerConnectionKey key,
+ int output_period_ms) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ auto it = peer_connections_with_event_logging_enabled_in_webrtc_.find(key);
+ if (it != peer_connections_with_event_logging_enabled_in_webrtc_.end()) {
+ DCHECK_EQ((it->second & target), 0u);
+ it->second |= target;
+ } else {
+ // This is the first client for WebRTC event logging - let WebRTC know
+ // that it should start informing us of events.
+ peer_connections_with_event_logging_enabled_in_webrtc_.emplace(key, target);
+ pc_tracker_proxy_->EnableWebRtcEventLogging(key, output_period_ms);
+ }
+}
+
+void WebRtcEventLogManager::OnLoggingTargetStopped(LoggingTarget target,
+ PeerConnectionKey key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Record that we're no longer performing this type of logging for this PC.
+ auto it = peer_connections_with_event_logging_enabled_in_webrtc_.find(key);
+ CHECK(it != peer_connections_with_event_logging_enabled_in_webrtc_.end());
+ DCHECK_NE(it->second, 0u);
+ it->second &= ~target;
+
+ // If we're not doing any other type of logging for this peer connection,
+ // it's time to stop receiving notifications for it from WebRTC.
+ if (it->second == 0u) {
+ peer_connections_with_event_logging_enabled_in_webrtc_.erase(it);
+ pc_tracker_proxy_->DisableWebRtcEventLogging(key);
+ }
+}
+
+void WebRtcEventLogManager::StartListeningForPrefChangeForBrowserContext(
+ BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(first_browser_context_initializations_done_);
+ CHECK(!browser_context->IsOffTheRecord());
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ auto it = pref_change_registrars_.emplace(std::piecewise_construct,
+ std::make_tuple(browser_context_id),
+ std::make_tuple());
+ DCHECK(it.second) << "Already listening.";
+ PrefChangeRegistrar& registrar = it.first->second;
+
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+ DCHECK(profile);
+ registrar.Init(profile->GetPrefs());
+
+ // * |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ // * base::Unretained(browser_context) is safe, because |browser_context|
+ // stays alive until Chrome shut-down, at which point we'll stop listening
+ // as part of its (BrowserContext's) tear-down process.
+ registrar.Add(prefs::kWebRtcEventLogCollectionAllowed,
+ base::BindRepeating(&WebRtcEventLogManager::OnPrefChange,
+ base::Unretained(this),
+ base::Unretained(browser_context)));
+}
+
+void WebRtcEventLogManager::StopListeningForPrefChangeForBrowserContext(
+ BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+
+ size_t erased_count = pref_change_registrars_.erase(browser_context_id);
+ DCHECK_EQ(erased_count, 1u);
+}
+
+void WebRtcEventLogManager::OnPrefChange(BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(first_browser_context_initializations_done_);
+
+ const Profile* profile = Profile::FromBrowserContext(browser_context);
+ DCHECK(profile);
+
+ const bool enabled = IsRemoteLoggingAllowedForBrowserContext(browser_context);
+
+ if (!enabled) {
+ // Dynamic refresh of the policy to DISABLED; stop ongoing logs, remove
+ // pending log files and stop any active uploads.
+ ClearCacheForBrowserContext(browser_context, base::Time::Min(),
+ base::Time::Max(), base::DoNothing());
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ base::OnceClosure task;
+ if (enabled) {
+ task = base::BindOnce(
+ &WebRtcEventLogManager::EnableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ browser_context->GetPath(), base::OnceClosure());
+ } else {
+ task = base::BindOnce(
+ &WebRtcEventLogManager::DisableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ base::OnceClosure());
+ }
+
+ task_runner_->PostTask(FROM_HERE, std::move(task));
+}
+
+void WebRtcEventLogManager::OnFirstBrowserContextLoaded() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ network::NetworkConnectionTracker* network_connection_tracker =
+ content::GetNetworkConnectionTracker();
+ DCHECK(network_connection_tracker);
+
+ auto log_file_writer_factory = CreateRemoteLogFileWriterFactory();
+ DCHECK(log_file_writer_factory);
+
+ // |network_connection_tracker| is owned by BrowserProcessImpl, which owns
+ // the IOThread. The internal task runner on which |this| uses
+ // |network_connection_tracker|, stops before IOThread dies, so we can trust
+ // that |network_connection_tracker| will not be used after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::OnFirstBrowserContextLoadedInternal,
+ base::Unretained(this), base::Unretained(network_connection_tracker),
+ std::move(log_file_writer_factory)));
+}
+
+void WebRtcEventLogManager::OnFirstBrowserContextLoadedInternal(
+ network::NetworkConnectionTracker* network_connection_tracker,
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(network_connection_tracker);
+ DCHECK(log_file_writer_factory);
+ remote_logs_manager_.SetNetworkConnectionTracker(network_connection_tracker);
+ remote_logs_manager_.SetLogFileWriterFactory(
+ std::move(log_file_writer_factory));
+}
+
+void WebRtcEventLogManager::EnableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ remote_logs_manager_.EnableForBrowserContext(browser_context_id,
+ browser_context_dir);
+
+ MaybeReply(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::DisableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Note that the BrowserContext might never have been enabled in the
+ // remote-bound manager; that's not a problem.
+ remote_logs_manager_.DisableForBrowserContext(browser_context_id);
+
+ MaybeReply(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::
+ RemovePendingRemoteBoundLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ remote_logs_manager_.RemovePendingLogsForNotEnabledBrowserContext(
+ browser_context_id, browser_context_dir);
+
+ MaybeReply(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::PeerConnectionAddedInternal(
+ PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool local_result = local_logs_manager_.PeerConnectionAdded(key);
+ const bool remote_result = remote_logs_manager_.PeerConnectionAdded(key);
+ DCHECK_EQ(local_result, remote_result);
+
+ MaybeReply(FROM_HERE, std::move(reply), local_result);
+}
+
+void WebRtcEventLogManager::PeerConnectionRemovedInternal(
+ PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool local_result = local_logs_manager_.PeerConnectionRemoved(key);
+ const bool remote_result = remote_logs_manager_.PeerConnectionRemoved(key);
+ DCHECK_EQ(local_result, remote_result);
+
+ MaybeReply(FROM_HERE, std::move(reply), local_result);
+}
+
+void WebRtcEventLogManager::PeerConnectionSessionIdSetInternal(
+ PeerConnectionKey key,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ const bool result =
+ remote_logs_manager_.PeerConnectionSessionIdSet(key, session_id);
+ MaybeReply(FROM_HERE, std::move(reply), result);
+}
+
+void WebRtcEventLogManager::EnableLocalLoggingInternal(
+ const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool result =
+ local_logs_manager_.EnableLogging(base_path, max_file_size_bytes);
+
+ MaybeReply(FROM_HERE, std::move(reply), result);
+}
+
+void WebRtcEventLogManager::DisableLocalLoggingInternal(
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool result = local_logs_manager_.DisableLogging();
+
+ MaybeReply(FROM_HERE, std::move(reply), result);
+}
+
+void WebRtcEventLogManager::OnWebRtcEventLogWriteInternal(
+ PeerConnectionKey key,
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool local_result = local_logs_manager_.EventLogWrite(key, message);
+ const bool remote_result = remote_logs_manager_.EventLogWrite(key, message);
+
+ MaybeReply(FROM_HERE, std::move(reply),
+ std::make_pair(local_result, remote_result));
+}
+
+void WebRtcEventLogManager::StartRemoteLoggingInternal(
+ int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ std::string log_id;
+ std::string error_message;
+ const bool result = remote_logs_manager_.StartRemoteLogging(
+ render_process_id, browser_context_id, session_id, browser_context_dir,
+ max_file_size_bytes, output_period_ms, web_app_id, &log_id,
+ &error_message);
+
+ // |log_id| set only if successful; |error_message| set only if unsuccessful.
+ DCHECK_EQ(result, !log_id.empty());
+ DCHECK_EQ(!result, !error_message.empty());
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(std::move(reply), result, log_id, error_message));
+}
+
+void WebRtcEventLogManager::ClearCacheForBrowserContextInternal(
+ BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ remote_logs_manager_.ClearCacheForBrowserContext(browser_context_id,
+ delete_begin, delete_end);
+}
+
+void WebRtcEventLogManager::GetHistoryInternal(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(reply);
+ remote_logs_manager_.GetHistory(browser_context_id, std::move(reply));
+}
+
+void WebRtcEventLogManager::RenderProcessExitedInternal(int render_process_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ local_logs_manager_.RenderProcessHostExitedDestroyed(render_process_id);
+ remote_logs_manager_.RenderProcessHostExitedDestroyed(render_process_id);
+}
+
+void WebRtcEventLogManager::SetLocalLogsObserverInternal(
+ WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ local_logs_observer_ = observer;
+
+ if (reply) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ }
+}
+
+void WebRtcEventLogManager::SetRemoteLogsObserverInternal(
+ WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ remote_logs_observer_ = observer;
+
+ if (reply) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ }
+}
+
+void WebRtcEventLogManager::SetClockForTesting(base::Clock* clock,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ auto task = [](WebRtcEventLogManager* manager, base::Clock* clock,
+ base::OnceClosure reply) {
+ manager->local_logs_manager_.SetClockForTesting(clock);
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ };
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(FROM_HERE, base::BindOnce(task, base::Unretained(this),
+ clock, std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetPeerConnectionTrackerProxyForTesting(
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ auto task = [](WebRtcEventLogManager* manager,
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy,
+ base::OnceClosure reply) {
+ manager->pc_tracker_proxy_ = std::move(pc_tracker_proxy);
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ };
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(task, base::Unretained(this),
+ std::move(pc_tracker_proxy), std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ auto task =
+ [](WebRtcEventLogManager* manager,
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
+ base::OnceClosure reply) {
+ auto& remote_logs_manager = manager->remote_logs_manager_;
+ remote_logs_manager.SetWebRtcEventLogUploaderFactoryForTesting(
+ std::move(uploader_factory));
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ };
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(task, base::Unretained(this),
+ std::move(uploader_factory), std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetRemoteLogFileWriterFactoryForTesting(
+ std::unique_ptr<LogFileWriter::Factory> factory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!first_browser_context_initializations_done_) << "Too late.";
+ DCHECK(!remote_log_file_writer_factory_for_testing_) << "Already called.";
+ remote_log_file_writer_factory_for_testing_ = std::move(factory);
+}
+
+void WebRtcEventLogManager::UploadConditionsHoldForTesting(
+ base::OnceCallback<void(bool)> callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Unit tests block until |callback| is sent back, so the use
+ // of base::Unretained(&remote_logs_manager_) is safe.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcRemoteEventLogManager::UploadConditionsHoldForTesting,
+ base::Unretained(&remote_logs_manager_), std::move(callback)));
+}
+
+scoped_refptr<base::SequencedTaskRunner>&
+WebRtcEventLogManager::GetTaskRunnerForTesting() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return task_runner_;
+}
+
+void WebRtcEventLogManager::PostNullTaskForTesting(base::OnceClosure reply) {
+ task_runner_->PostTask(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::ShutDownForTesting(base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Unit tests block until |callback| is sent back, so the use
+ // of base::Unretained(&remote_logs_manager_) is safe.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::ShutDownForTesting,
+ base::Unretained(&remote_logs_manager_),
+ std::move(reply)));
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h
new file mode 100644
index 00000000000..6b4b815e6af
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h
@@ -0,0 +1,429 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_set.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_local.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/upload_list/upload_list.h"
+#include "content/public/browser/render_process_host_observer.h"
+#include "content/public/browser/webrtc_event_logger.h"
+
+class WebRTCInternalsIntegrationBrowserTest;
+
+namespace content {
+class BrowserContext;
+class NetworkConnectionTracker;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// This is a singleton class running in the browser UI thread (ownership of
+// the only instance lies in BrowserContext). It is in charge of writing WebRTC
+// event logs to temporary files, then uploading those files to remote servers,
+// as well as of writing the logs to files which were manually indicated by the
+// user from the WebRTCIntenals. (A log may simulatenously be written to both,
+// either, or none.)
+// The only instance of this class is owned by BrowserProcessImpl. It is
+// destroyed from ~BrowserProcessImpl(), at which point any tasks posted to the
+// internal SequencedTaskRunner, or coming from another thread, would no longer
+// execute.
+class WebRtcEventLogManager final : public content::RenderProcessHostObserver,
+ public content::WebRtcEventLogger,
+ public WebRtcLocalEventLogsObserver,
+ public WebRtcRemoteEventLogsObserver {
+ public:
+ using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+
+ // To turn WebRTC on and off, we go through PeerConnectionTrackerProxy. In
+ // order to make this toggling easily testable, PeerConnectionTrackerProxyImpl
+ // will send real messages to PeerConnectionTracker, whereas
+ // PeerConnectionTrackerProxyForTesting will be a mock that just makes sure
+ // the correct messages were attempted to be sent.
+ class PeerConnectionTrackerProxy {
+ public:
+ virtual ~PeerConnectionTrackerProxy() = default;
+
+ virtual void EnableWebRtcEventLogging(
+ const WebRtcEventLogPeerConnectionKey& key,
+ int output_period_ms) = 0;
+
+ virtual void DisableWebRtcEventLogging(
+ const WebRtcEventLogPeerConnectionKey& key) = 0;
+ };
+
+ // Ensures that no previous instantiation of the class was performed, then
+ // instantiates the class and returns the object (ownership is transfered to
+ // the caller). Subsequent calls to GetInstance() will return this object,
+ // until it is destructed, at which pointer nullptr will be returned by
+ // subsequent calls.
+ static std::unique_ptr<WebRtcEventLogManager> CreateSingletonInstance();
+
+ // Returns the object previously constructed using CreateSingletonInstance(),
+ // if it was constructed and was not yet destroyed; nullptr otherwise.
+ static WebRtcEventLogManager* GetInstance();
+
+ // Given a BrowserContext, return the path to the directory where its
+ // remote-bound event logs are kept.
+ // Since incognito sessions don't have such a directory, an empty
+ // base::FilePath will be returned for them.
+ static base::FilePath GetRemoteBoundWebRtcEventLogsDir(
+ content::BrowserContext* browser_context);
+
+ ~WebRtcEventLogManager() override;
+
+ void EnableForBrowserContext(content::BrowserContext* browser_context,
+ base::OnceClosure reply);
+
+ void DisableForBrowserContext(content::BrowserContext* browser_context,
+ base::OnceClosure reply);
+
+ void PeerConnectionAdded(int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ base::OnceCallback<void(bool)> reply) override;
+
+ void PeerConnectionRemoved(int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ base::OnceCallback<void(bool)> reply) override;
+
+ // From the logger's perspective, we treat stopping a peer connection the
+ // same as we do its removal. Should a stopped peer connection be later
+ // removed, the removal callback will assume the value |false|.
+ void PeerConnectionStopped(int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ base::OnceCallback<void(bool)> reply) override;
+
+ void PeerConnectionSessionIdSet(
+ int render_process_id,
+ int lid,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply) override;
+
+ // The file's actual path is derived from |base_path| by adding a timestamp,
+ // the render process ID and the PeerConnection's local ID.
+ void EnableLocalLogging(const base::FilePath& base_path,
+ base::OnceCallback<void(bool)> reply) override;
+ void EnableLocalLogging(const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply);
+
+ void DisableLocalLogging(base::OnceCallback<void(bool)> reply) override;
+
+ void OnWebRtcEventLogWrite(
+ int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply) override;
+
+ // Start logging a peer connection's WebRTC events to a file, which will
+ // later be uploaded to a remote server. If a reply is provided, it will be
+ // posted back to BrowserThread::UI with the log-identifier (if successful)
+ // of the created log or (if unsuccessful) the error message.
+ // See the comment in WebRtcRemoteEventLogManager::StartRemoteLogging for
+ // more details.
+ void StartRemoteLogging(
+ int render_process_id,
+ const std::string& session_id,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply);
+
+ // Clear WebRTC event logs associated with a given browser context, in a given
+ // time range (|delete_begin| inclusive, |delete_end| exclusive), then
+ // post |reply| back to the thread from which the method was originally
+ // invoked (which can be any thread).
+ void ClearCacheForBrowserContext(
+ const content::BrowserContext* browser_context,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::OnceClosure reply);
+
+ // Get the logging history (relevant only to remote-bound logs). This includes
+ // information such as when logs were captured, when they were uploaded,
+ // and what their ID in the remote server was.
+ // Must be called on the UI thread.
+ // The results to the query are posted using |reply| back to the UI thread.
+ // If |browser_context_id| is not the ID a profile for which remote-bound
+ // logging is enabled, an empty list is returned.
+ // The returned vector is sorted by capture time in ascending order.
+ void GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply);
+
+ // Set (or unset) an observer that will be informed whenever a local log file
+ // is started/stopped. The observer needs to be able to either run from
+ // anywhere. If you need the code to run on specific runners or queues, have
+ // the observer post them there.
+ // If a reply callback is given, it will be posted back to BrowserThread::UI
+ // after the observer has been set.
+ void SetLocalLogsObserver(WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ // Set (or unset) an observer that will be informed whenever a remote log file
+ // is started/stopped. Note that this refers to writing these files to disk,
+ // not for uploading them to the server.
+ // The observer needs to be able to either run from anywhere. If you need the
+ // code to run on specific runners or queues, have the observer post
+ // them there.
+ // If a reply callback is given, it will be posted back to BrowserThread::UI
+ // after the observer has been set.
+ void SetRemoteLogsObserver(WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ private:
+ friend class WebRtcEventLogManagerTestBase;
+ friend class ::WebRTCInternalsIntegrationBrowserTest;
+
+ using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+
+ // This bitmap allows us to track for which clients (local/remote logging)
+ // we have turned WebRTC event logging on for a given peer connection, so that
+ // we may turn it off only when the last client no longer needs it.
+ enum LoggingTarget : unsigned int {
+ kLocalLogging = 1 << 0,
+ kRemoteLogging = 1 << 1
+ };
+ using LoggingTargetBitmap = std::underlying_type<LoggingTarget>::type;
+
+ WebRtcEventLogManager();
+
+ bool IsRemoteLoggingAllowedForBrowserContext(
+ content::BrowserContext* browser_context) const;
+
+ // Determines the exact subclass of LogFileWriter::Factory to be used for
+ // producing remote-bound logs.
+ std::unique_ptr<LogFileWriter::Factory> CreateRemoteLogFileWriterFactory();
+
+ // RenderProcessHostObserver implementation.
+ void RenderProcessExited(
+ content::RenderProcessHost* host,
+ const content::ChildProcessTerminationInfo& info) override;
+ void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
+
+ // RenderProcessExited() and RenderProcessHostDestroyed() treated similarly
+ // by this function.
+ void RenderProcessHostExitedDestroyed(content::RenderProcessHost* host);
+
+ // WebRtcLocalEventLogsObserver implementation:
+ void OnLocalLogStarted(PeerConnectionKey peer_connection,
+ const base::FilePath& file_path) override;
+ void OnLocalLogStopped(PeerConnectionKey peer_connection) override;
+
+ // WebRtcRemoteEventLogsObserver implementation:
+ void OnRemoteLogStarted(PeerConnectionKey key,
+ const base::FilePath& file_path,
+ int output_period_ms) override;
+ void OnRemoteLogStopped(PeerConnectionKey key) override;
+
+ void OnLoggingTargetStarted(LoggingTarget target,
+ PeerConnectionKey key,
+ int output_period_ms);
+ void OnLoggingTargetStopped(LoggingTarget target, PeerConnectionKey key);
+
+ void StartListeningForPrefChangeForBrowserContext(
+ content::BrowserContext* browser_context);
+ void StopListeningForPrefChangeForBrowserContext(
+ content::BrowserContext* browser_context);
+
+ void OnPrefChange(content::BrowserContext* browser_context);
+
+ // network_connection_tracker() is not available during instantiation;
+ // we get it when the first profile is loaded, which is also the earliest
+ // time when it could be needed.
+ // The LogFileWriter::Factory is similarly deferred, but for a different
+ // reason - it makes it easier to allow unit tests to inject their own.
+ // OnFirstBrowserContextLoaded() is on the UI thread.
+ // OnFirstBrowserContextLoadedInternal() is the task sent to |task_runner_|.
+ void OnFirstBrowserContextLoaded();
+ void OnFirstBrowserContextLoadedInternal(
+ network::NetworkConnectionTracker* network_connection_tracker,
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory);
+
+ void EnableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply);
+
+ void DisableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ base::OnceClosure reply);
+
+ void RemovePendingRemoteBoundLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply);
+
+ void PeerConnectionAddedInternal(PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply);
+ void PeerConnectionRemovedInternal(PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply);
+
+ void PeerConnectionSessionIdSetInternal(PeerConnectionKey key,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply);
+
+ void EnableLocalLoggingInternal(const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply);
+ void DisableLocalLoggingInternal(base::OnceCallback<void(bool)> reply);
+
+ void OnWebRtcEventLogWriteInternal(
+ PeerConnectionKey key,
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply);
+
+ void StartRemoteLoggingInternal(
+ int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply);
+
+ void ClearCacheForBrowserContextInternal(BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end);
+
+ void GetHistoryInternal(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply);
+
+ void RenderProcessExitedInternal(int render_process_id);
+
+ void SetLocalLogsObserverInternal(WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ void SetRemoteLogsObserverInternal(WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ // Injects a fake clock, to be used by tests. For example, this could be
+ // used to inject a frozen clock, thereby allowing unit tests to know what a
+ // local log's filename would end up being.
+ void SetClockForTesting(base::Clock* clock, base::OnceClosure reply);
+
+ // Injects a PeerConnectionTrackerProxy for testing. The normal tracker proxy
+ // is used to communicate back to WebRTC whether event logging is desired for
+ // a given peer connection. Using this function, those indications can be
+ // intercepted by a unit test.
+ void SetPeerConnectionTrackerProxyForTesting(
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy,
+ base::OnceClosure reply);
+
+ // Injects a fake uploader, to be used by unit tests.
+ void SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
+ base::OnceClosure reply);
+
+ // Sets a LogFileWriter factory for remote-bound files.
+ // Only usable in tests.
+ // Must be called before the first browser context is enabled.
+ // Effective immediately.
+ void SetRemoteLogFileWriterFactoryForTesting(
+ std::unique_ptr<LogFileWriter::Factory> factory);
+
+ // It is not always feasible to check in unit tests that uploads do not occur
+ // at a certain time, because that's (sometimes) racy with the event that
+ // suppresses the upload. We therefore allow unit tests to glimpse into the
+ // black box and verify that the box is aware that it should not upload.
+ void UploadConditionsHoldForTesting(base::OnceCallback<void(bool)> callback);
+
+ // This allows unit tests that do not wish to change the task runner to still
+ // check when certain operations are finished.
+ // TODO(crbug.com/775415): Remove this and use PostNullTaskForTesting instead.
+ scoped_refptr<base::SequencedTaskRunner>& GetTaskRunnerForTesting();
+
+ void PostNullTaskForTesting(base::OnceClosure reply);
+
+ // Documented in WebRtcRemoteEventLogManager.
+ void ShutDownForTesting(base::OnceClosure reply);
+
+ static WebRtcEventLogManager* g_webrtc_event_log_manager;
+
+ // The main logic will run sequentially on this runner, on which blocking
+ // tasks are allowed.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Indicates whether remote-bound logging is generally allowed, although
+ // possibly not for all profiles. This makes it possible for remote-bound to
+ // be disabled through Finch.
+ // TODO(crbug.com/775415): Remove this kill-switch.
+ const bool remote_logging_feature_enabled_;
+
+ // Observer which will be informed whenever a local log file is started or
+ // stopped. Its callbacks are called synchronously from |task_runner_|,
+ // so the observer needs to be able to either run from any (sequenced) runner.
+ WebRtcLocalEventLogsObserver* local_logs_observer_;
+
+ // Observer which will be informed whenever a remote log file is started or
+ // stopped. Its callbacks are called synchronously from |task_runner_|,
+ // so the observer needs to be able to either run from any (sequenced) runner.
+ WebRtcRemoteEventLogsObserver* remote_logs_observer_;
+
+ // Manages local-bound logs - logs stored on the local filesystem when
+ // logging has been explicitly enabled by the user.
+ WebRtcLocalEventLogManager local_logs_manager_;
+
+ // Manages remote-bound logs - logs which will be sent to a remote server.
+ // This is only possible when the appropriate Chrome policy is configured.
+ WebRtcRemoteEventLogManager remote_logs_manager_;
+
+ // Each loaded BrowserContext is mapped to a PrefChangeRegistrar, which keeps
+ // us informed about preference changes, thereby allowing as to support
+ // dynamic refresh.
+ std::map<BrowserContextId, PrefChangeRegistrar> pref_change_registrars_;
+
+ // This keeps track of which peer connections have event logging turned on
+ // in WebRTC, and for which client(s).
+ std::map<PeerConnectionKey, LoggingTargetBitmap>
+ peer_connections_with_event_logging_enabled_in_webrtc_;
+
+ // The set of RenderProcessHosts with which the manager is registered for
+ // observation. Allows us to register for each RPH only once, and get notified
+ // when it exits (cleanly or due to a crash).
+ // This object is only to be accessed on the UI thread.
+ base::flat_set<content::RenderProcessHost*> observed_render_process_hosts_;
+
+ // In production, this holds a small object that just tells WebRTC (via
+ // PeerConnectionTracker) to start/stop producing event logs for a specific
+ // peer connection. In (relevant) unit tests, a mock will be injected.
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy_;
+
+ // The globals network_connection_tracker() and system_request_context() are
+ // sent down to |remote_logs_manager_| with the first enabled browser context.
+ // This member must only be accessed on the UI thread.
+ bool first_browser_context_initializations_done_;
+
+ // May only be set for tests, in which case, it will be passed to
+ // |remote_logs_manager_| when (and if) produced.
+ std::unique_ptr<LogFileWriter::Factory>
+ remote_log_file_writer_factory_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManager);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
new file mode 100644
index 00000000000..59ad7a2b45e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
@@ -0,0 +1,1013 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+#include <cctype>
+#include <limits>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/unguessable_token.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "third_party/zlib/zlib.h"
+
+namespace webrtc_event_logging {
+
+using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+
+const size_t kWebRtcEventLogManagerUnlimitedFileSize = 0;
+
+const size_t kWebRtcEventLogIdLength = 32;
+
+// Be careful not to change these without updating the number of characters
+// reserved in the filename. See kWebAppIdLength.
+const size_t kMinWebRtcEventLogWebAppId = 1;
+const size_t kMaxWebRtcEventLogWebAppId = 99;
+
+// Sentinel value for an invalid web-app ID.
+const size_t kInvalidWebRtcEventLogWebAppId = 0;
+static_assert(kInvalidWebRtcEventLogWebAppId < kMinWebRtcEventLogWebAppId ||
+ kInvalidWebRtcEventLogWebAppId > kMaxWebRtcEventLogWebAppId,
+ "Sentinel value must be distinct from legal values.");
+
+const char kRemoteBoundWebRtcEventLogFileNamePrefix[] = "webrtc_event_log";
+
+// Important! These values may be relied on by web-apps. Do not change.
+const char kStartRemoteLoggingFailureAlreadyLogging[] = "Already logging.";
+const char kStartRemoteLoggingFailureDeadRenderProcessHost[] =
+ "RPH already dead.";
+const char kStartRemoteLoggingFailureFeatureDisabled[] = "Feature disabled.";
+const char kStartRemoteLoggingFailureFileCreationError[] =
+ "Could not create file.";
+const char kStartRemoteLoggingFailureFilePathUsedHistory[] =
+ "Used history file path.";
+const char kStartRemoteLoggingFailureFilePathUsedLog[] = "Used log file path.";
+const char kStartRemoteLoggingFailureIllegalWebAppId[] = "Illegal web-app ID.";
+const char kStartRemoteLoggingFailureLoggingDisabledBrowserContext[] =
+ "Disabled for browser context.";
+const char kStartRemoteLoggingFailureMaxSizeTooLarge[] =
+ "Excessively large max log size.";
+const char kStartRemoteLoggingFailureMaxSizeTooSmall[] = "Max size too small.";
+const char kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed[] =
+ "No additional active logs allowed.";
+const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[] =
+ "Excessively large output period (ms).";
+const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[] =
+ "Unknown or inactive peer connection.";
+const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[] =
+ "Unlimited size disallowed.";
+
+const BrowserContextId kNullBrowserContextId =
+ reinterpret_cast<BrowserContextId>(nullptr);
+
+void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result) {
+ base::UmaHistogramEnumeration("WebRtcEventLogging.Api", result);
+}
+
+void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result) {
+ base::UmaHistogramEnumeration("WebRtcEventLogging.Upload", result);
+}
+
+void UmaRecordWebRtcEventLoggingNetErrorType(int net_error) {
+ base::UmaHistogramSparse("WebRtcEventLogging.NetError", net_error);
+}
+
+namespace {
+
+constexpr int kDefaultMemLevel = 8;
+
+constexpr size_t kGzipHeaderBytes = 15;
+constexpr size_t kGzipFooterBytes = 10;
+
+constexpr size_t kWebAppIdLength = 2;
+
+// Tracks budget over a resource (such as bytes allowed in a file, etc.).
+// Allows an unlimited budget.
+class Budget {
+ public:
+ // If !max.has_value(), the budget is unlimited.
+ explicit Budget(base::Optional<size_t> max) : max_(max), current_(0) {}
+
+ // Check whether the budget allows consuming an additional |consumed| of
+ // the resource.
+ bool ConsumeAllowed(size_t consumed) const {
+ if (!max_.has_value()) {
+ return true;
+ }
+
+ DCHECK_LE(current_, max_.value());
+
+ const size_t after_consumption = current_ + consumed;
+
+ if (after_consumption < current_) {
+ return false; // Wrap-around.
+ } else if (after_consumption > max_.value()) {
+ return false; // Budget exceeded.
+ } else {
+ return true;
+ }
+ }
+
+ // Checks whether the budget has been completely used up.
+ bool Exhausted() const { return !ConsumeAllowed(0); }
+
+ // Consume an additional |consumed| of the resource.
+ void Consume(size_t consumed) {
+ DCHECK(ConsumeAllowed(consumed));
+ current_ += consumed;
+ }
+
+ private:
+ const base::Optional<size_t> max_;
+ size_t current_;
+};
+
+// Writes a log to a file while observing a maximum size.
+class BaseLogFileWriter : public LogFileWriter {
+ public:
+ // If !max_file_size_bytes.has_value(), an unlimited writer is created.
+ // If it has a value, it must be at least MinFileSizeBytes().
+ BaseLogFileWriter(const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes);
+
+ ~BaseLogFileWriter() override;
+
+ bool Init() override;
+
+ const base::FilePath& path() const override;
+
+ bool MaxSizeReached() const override;
+
+ bool Write(const std::string& input) override;
+
+ bool Close() override;
+
+ void Delete() override;
+
+ protected:
+ // * Logs are created PRE_INIT.
+ // * If Init() is successful (potentially writing some header to the log),
+ // the log becomes ACTIVE.
+ // * Any error puts the log into an unrecoverable ERRORED state. When an
+ // errored file is Close()-ed, it is deleted.
+ // * If Write() is ever denied because of budget constraintss, the file
+ // becomes FULL. Only metadata is then allowed (subject to its own budget).
+ // * Closing an ACTIVE or FULL file puts it into CLOSED, at which point the
+ // file may be used. (Note that closing itself might also yield an error,
+ // which would put the file into ERRORED, then deleted.)
+ // * Closed files may be DELETED.
+ enum class State { PRE_INIT, ACTIVE, FULL, CLOSED, ERRORED, DELETED };
+
+ // Setter/getter for |state_|.
+ void SetState(State state);
+ State state() const { return state_; }
+
+ // Checks whether the budget allows writing an additional |bytes|.
+ bool WithinBudget(size_t bytes) const;
+
+ // Writes |input| to the file.
+ // May only be called on ACTIVE or FULL files (for FULL files, only metadata
+ // such as compression footers, etc., may be written; the budget must still
+ // be respected).
+ // It's up to the caller to respect the budget; this will DCHECK on it.
+ // Returns |true| if writing was successful. |false| indicates an
+ // unrecoverable error; the file must be discarded.
+ bool WriteInternal(const std::string& input, bool metadata);
+
+ // Finalizes the file (writes metadata such as compression footer, if any).
+ // Reports whether the file was successfully finalized. Those which weren't
+ // should be discarded.
+ virtual bool Finalize();
+
+ private:
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ const base::FilePath path_;
+ base::File file_; // Populated by Init().
+ State state_;
+ Budget budget_;
+};
+
+BaseLogFileWriter::BaseLogFileWriter(const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes)
+ : task_runner_(base::SequencedTaskRunnerHandle::Get()),
+ path_(path),
+ state_(State::PRE_INIT),
+ budget_(max_file_size_bytes) {}
+
+BaseLogFileWriter::~BaseLogFileWriter() {
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ // Chrome shut-down. The original task_runner_ is no longer running, so
+ // no risk of concurrent access or races.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ task_runner_ = base::SequencedTaskRunnerHandle::Get();
+ }
+
+ if (state() != State::CLOSED && state() != State::DELETED) {
+ Close();
+ }
+}
+
+bool BaseLogFileWriter::Init() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(state(), State::PRE_INIT);
+
+ // TODO(crbug.com/775415): Use a temporary filename which will indicate
+ // incompletion, and rename to something that is eligible for upload only
+ // on an orderly and successful Close().
+
+ // Attempt to create the file.
+ constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE;
+ file_.Initialize(path_, file_flags);
+ if (!file_.IsValid() || !file_.created()) {
+ LOG(WARNING) << "Couldn't create remote-bound WebRTC event log file.";
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+ SetState(State::ERRORED);
+ return false;
+ }
+
+ SetState(State::ACTIVE);
+
+ return true;
+}
+
+const base::FilePath& BaseLogFileWriter::path() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return path_;
+}
+
+bool BaseLogFileWriter::MaxSizeReached() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(state(), State::ACTIVE);
+ return !WithinBudget(1);
+}
+
+bool BaseLogFileWriter::Write(const std::string& input) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(state(), State::ACTIVE);
+ DCHECK(!MaxSizeReached());
+
+ if (input.empty()) {
+ return true;
+ }
+
+ if (!WithinBudget(input.length())) {
+ SetState(State::FULL);
+ return false;
+ }
+
+ const bool did_write = WriteInternal(input, /*metadata=*/false);
+ if (!did_write) {
+ SetState(State::ERRORED);
+ }
+ return did_write;
+}
+
+bool BaseLogFileWriter::Close() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(state(), State::CLOSED);
+ DCHECK_NE(state(), State::DELETED);
+
+ const bool result = ((state() != State::ERRORED) && Finalize());
+
+ if (result) {
+ file_.Flush();
+ file_.Close();
+ SetState(State::CLOSED);
+ } else {
+ Delete(); // Changes the state to DELETED.
+ }
+
+ return result;
+}
+
+void BaseLogFileWriter::Delete() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(state(), State::DELETED);
+
+ // The file should be closed before deletion. However, we do not want to go
+ // through Finalize() and any potential production of a compression footer,
+ // etc., since we'll be discarding the file anyway.
+ if (state() != State::CLOSED) {
+ file_.Close();
+ }
+
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+
+ SetState(State::DELETED);
+}
+
+void BaseLogFileWriter::SetState(State state) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ state_ = state;
+}
+
+bool BaseLogFileWriter::WithinBudget(size_t bytes) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return budget_.ConsumeAllowed(bytes);
+}
+
+bool BaseLogFileWriter::WriteInternal(const std::string& input, bool metadata) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(state() == State::ACTIVE || (state() == State::FULL && metadata));
+ DCHECK(WithinBudget(input.length()));
+
+ // base::File's interface does not allow writing more than
+ // numeric_limits<int>::max() bytes at a time.
+ DCHECK_LE(input.length(),
+ static_cast<size_t>(std::numeric_limits<int>::max()));
+ const int input_len = static_cast<int>(input.length());
+
+ int written = file_.WriteAtCurrentPos(input.c_str(), input_len);
+ if (written != input_len) {
+ LOG(WARNING) << "WebRTC event log couldn't be written to the "
+ "locally stored file in its entirety.";
+ return false;
+ }
+
+ budget_.Consume(static_cast<size_t>(written));
+
+ return true;
+}
+
+bool BaseLogFileWriter::Finalize() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(state(), State::CLOSED);
+ DCHECK_NE(state(), State::DELETED);
+ DCHECK_NE(state(), State::ERRORED);
+ return true;
+}
+
+// Writes a GZIP-compressed log to a file while observing a maximum size.
+class GzippedLogFileWriter : public BaseLogFileWriter {
+ public:
+ GzippedLogFileWriter(const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes,
+ std::unique_ptr<LogCompressor> compressor);
+
+ ~GzippedLogFileWriter() override = default;
+
+ bool Init() override;
+
+ bool MaxSizeReached() const override;
+
+ bool Write(const std::string& input) override;
+
+ protected:
+ bool Finalize() override;
+
+ private:
+ std::unique_ptr<LogCompressor> compressor_;
+};
+
+GzippedLogFileWriter::GzippedLogFileWriter(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes,
+ std::unique_ptr<LogCompressor> compressor)
+ : BaseLogFileWriter(path, max_file_size_bytes),
+ compressor_(std::move(compressor)) {
+ // Factory validates size before instantiation.
+ DCHECK(!max_file_size_bytes.has_value() ||
+ max_file_size_bytes.value() >= kGzipOverheadBytes);
+}
+
+bool GzippedLogFileWriter::Init() {
+ if (!BaseLogFileWriter::Init()) {
+ // Super-class should SetState on its own.
+ return false;
+ }
+
+ std::string header;
+ compressor_->CreateHeader(&header);
+
+ const bool result = WriteInternal(header, /*metadata=*/true);
+ if (!result) {
+ SetState(State::ERRORED);
+ }
+
+ return result;
+}
+
+bool GzippedLogFileWriter::MaxSizeReached() const {
+ DCHECK_EQ(state(), State::ACTIVE);
+
+ // Note that the overhead used (footer only) assumes state() is State::ACTIVE,
+ // as DCHECKed above.
+ return !WithinBudget(1 + kGzipFooterBytes);
+}
+
+bool GzippedLogFileWriter::Write(const std::string& input) {
+ DCHECK_EQ(state(), State::ACTIVE);
+ DCHECK(!MaxSizeReached());
+
+ if (input.empty()) {
+ return true;
+ }
+
+ std::string compressed_input;
+ const auto result = compressor_->Compress(input, &compressed_input);
+
+ switch (result) {
+ case LogCompressor::Result::OK: {
+ // |compressor_| guarantees |compressed_input| is within-budget.
+ bool did_write = WriteInternal(compressed_input, /*metadata=*/false);
+ if (!did_write) {
+ SetState(State::ERRORED);
+ }
+ return did_write;
+ }
+ case LogCompressor::Result::DISALLOWED: {
+ SetState(State::FULL);
+ return false;
+ }
+ case LogCompressor::Result::ERROR_ENCOUNTERED: {
+ SetState(State::ERRORED);
+ return false;
+ }
+ }
+
+ NOTREACHED();
+ return false; // Appease compiler.
+}
+
+bool GzippedLogFileWriter::Finalize() {
+ DCHECK_NE(state(), State::CLOSED);
+ DCHECK_NE(state(), State::DELETED);
+ DCHECK_NE(state(), State::ERRORED);
+
+ std::string footer;
+ if (!compressor_->CreateFooter(&footer)) {
+ LOG(WARNING) << "Compression footer could not be produced.";
+ SetState(State::ERRORED);
+ return false;
+ }
+
+ // |compressor_| guarantees |footer| is within-budget.
+ if (!WriteInternal(footer, /*metadata=*/true)) {
+ LOG(WARNING) << "Footer could not be written.";
+ SetState(State::ERRORED);
+ return false;
+ }
+
+ return true;
+}
+
+// Concrete implementation of LogCompressor using GZIP.
+class GzipLogCompressor : public LogCompressor {
+ public:
+ GzipLogCompressor(
+ base::Optional<size_t> max_size_bytes,
+ std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator);
+
+ ~GzipLogCompressor() override;
+
+ void CreateHeader(std::string* output) override;
+
+ Result Compress(const std::string& input, std::string* output) override;
+
+ bool CreateFooter(std::string* output) override;
+
+ private:
+ // * A compressed log starts out empty (PRE_HEADER).
+ // * Once the header is produced, the stream is ACTIVE.
+ // * If it is ever detected that compressing the next input would exceed the
+ // budget, that input is NOT compressed, and the state becomes FULL, from
+ // which only writing the footer or discarding the file are allowed.
+ // * Writing the footer is allowed on an ACTIVE or FULL stream. Then, the
+ // stream is effectively closed.
+ // * Any error puts the stream into ERRORED. An errored stream can only
+ // be discarded.
+ enum class State { PRE_HEADER, ACTIVE, FULL, POST_FOOTER, ERRORED };
+
+ // Returns the budget left after reserving the GZIP overhead.
+ // Optionals without a value, both in the parameters as well as in the
+ // return value of the function, signal an unlimited amount.
+ static base::Optional<size_t> SizeAfterOverheadReservation(
+ base::Optional<size_t> max_size_bytes);
+
+ // Compresses |input| into |output|, while observing the budget (unless
+ // !budgeted). If |last|, also closes the stream.
+ Result CompressInternal(const std::string& input,
+ std::string* output,
+ bool budgeted,
+ bool last);
+
+ // Compresses the input data already in |stream_| into |output|.
+ bool Deflate(int flush, std::string* output);
+
+ State state_;
+ Budget budget_;
+ std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator_;
+ z_stream stream_;
+};
+
+GzipLogCompressor::GzipLogCompressor(
+ base::Optional<size_t> max_size_bytes,
+ std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)
+ : state_(State::PRE_HEADER),
+ budget_(SizeAfterOverheadReservation(max_size_bytes)),
+ compressed_size_estimator_(std::move(compressed_size_estimator)) {
+ memset(&stream_, 0, sizeof(z_stream));
+ // Using (MAX_WBITS + 16) triggers the creation of a GZIP header.
+ const int result =
+ deflateInit2(&stream_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16,
+ kDefaultMemLevel, Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(result, Z_OK);
+}
+
+GzipLogCompressor::~GzipLogCompressor() {
+ const int result = deflateEnd(&stream_);
+ // Z_DATA_ERROR reports that the stream was not properly terminated,
+ // but nevertheless correctly released. That happens when we don't
+ // write the footer.
+ DCHECK(result == Z_OK ||
+ (result == Z_DATA_ERROR && state_ != State::POST_FOOTER));
+}
+
+void GzipLogCompressor::CreateHeader(std::string* output) {
+ DCHECK(output);
+ DCHECK(output->empty());
+ DCHECK_EQ(state_, State::PRE_HEADER);
+
+ const Result result = CompressInternal(std::string(), output,
+ /*budgeted=*/false, /*last=*/false);
+ DCHECK_EQ(result, Result::OK);
+ DCHECK_EQ(output->size(), kGzipHeaderBytes);
+
+ state_ = State::ACTIVE;
+}
+
+LogCompressor::Result GzipLogCompressor::Compress(const std::string& input,
+ std::string* output) {
+ DCHECK_EQ(state_, State::ACTIVE);
+
+ if (input.empty()) {
+ return Result::OK;
+ }
+
+ const auto result =
+ CompressInternal(input, output, /*budgeted=*/true, /*last=*/false);
+
+ switch (result) {
+ case Result::OK:
+ return result;
+ case Result::DISALLOWED:
+ state_ = State::FULL;
+ return result;
+ case Result::ERROR_ENCOUNTERED:
+ state_ = State::ERRORED;
+ return result;
+ }
+
+ NOTREACHED();
+ return Result::ERROR_ENCOUNTERED; // Appease compiler.
+}
+
+bool GzipLogCompressor::CreateFooter(std::string* output) {
+ DCHECK(output);
+ DCHECK(output->empty());
+ DCHECK(state_ == State::ACTIVE || state_ == State::FULL);
+
+ const Result result = CompressInternal(std::string(), output,
+ /*budgeted=*/false, /*last=*/true);
+ if (result != Result::OK) { // !budgeted -> Result::DISALLOWED impossible.
+ DCHECK_EQ(result, Result::ERROR_ENCOUNTERED);
+ // An error message was logged by CompressInternal().
+ state_ = State::ERRORED;
+ return false;
+ }
+
+ if (output->length() != kGzipFooterBytes) {
+ LOG(ERROR) << "Incorrect footer size (" << output->length() << ").";
+ state_ = State::ERRORED;
+ return false;
+ }
+
+ state_ = State::POST_FOOTER;
+
+ return true;
+}
+
+base::Optional<size_t> GzipLogCompressor::SizeAfterOverheadReservation(
+ base::Optional<size_t> max_size_bytes) {
+ if (!max_size_bytes.has_value()) {
+ return base::Optional<size_t>();
+ } else {
+ DCHECK_GE(max_size_bytes.value(), kGzipHeaderBytes + kGzipFooterBytes);
+ return max_size_bytes.value() - (kGzipHeaderBytes + kGzipFooterBytes);
+ }
+}
+
+LogCompressor::Result GzipLogCompressor::CompressInternal(
+ const std::string& input,
+ std::string* output,
+ bool budgeted,
+ bool last) {
+ DCHECK(output);
+ DCHECK(output->empty());
+ DCHECK(state_ == State::PRE_HEADER || state_ == State::ACTIVE ||
+ (!budgeted && state_ == State::FULL));
+
+ // Avoid writing to |output| unless the return value is OK.
+ std::string temp_output;
+
+ if (budgeted) {
+ const size_t estimated_compressed_size =
+ compressed_size_estimator_->EstimateCompressedSize(input);
+ if (!budget_.ConsumeAllowed(estimated_compressed_size)) {
+ return Result::DISALLOWED;
+ }
+ }
+
+ if (last) {
+ DCHECK(input.empty());
+ stream_.next_in = nullptr;
+ } else {
+ stream_.next_in = reinterpret_cast<z_const Bytef*>(input.c_str());
+ }
+
+ DCHECK_LE(input.length(),
+ static_cast<size_t>(std::numeric_limits<uInt>::max()));
+ stream_.avail_in = static_cast<uInt>(input.length());
+
+ const bool result = Deflate(last ? Z_FINISH : Z_SYNC_FLUSH, &temp_output);
+
+ stream_.next_in = nullptr; // Avoid dangling pointers.
+
+ if (!result) {
+ // An error message was logged by Deflate().
+ return Result::ERROR_ENCOUNTERED;
+ }
+
+ if (budgeted) {
+ if (!budget_.ConsumeAllowed(temp_output.length())) {
+ LOG(WARNING) << "Compressed size was above estimate and unexpectedly "
+ "exceeded the budget.";
+ return Result::ERROR_ENCOUNTERED;
+ }
+ budget_.Consume(temp_output.length());
+ }
+
+ std::swap(*output, temp_output);
+ return Result::OK;
+}
+
+bool GzipLogCompressor::Deflate(int flush, std::string* output) {
+ DCHECK((flush != Z_FINISH && stream_.next_in != nullptr) ||
+ (flush == Z_FINISH && stream_.next_in == nullptr));
+ DCHECK(output->empty());
+
+ bool success = true; // Result of this method.
+ int z_result; // Result of the zlib function.
+
+ size_t total_compressed_size = 0;
+
+ do {
+ // Allocate some additional buffer.
+ constexpr uInt kCompressionBuffer = 4 * 1024;
+ output->resize(total_compressed_size + kCompressionBuffer);
+
+ // This iteration should write directly beyond previous iterations' last
+ // written byte.
+ stream_.next_out =
+ reinterpret_cast<uint8_t*>(&((*output)[total_compressed_size]));
+ stream_.avail_out = kCompressionBuffer;
+
+ z_result = deflate(&stream_, flush);
+
+ DCHECK_GE(kCompressionBuffer, stream_.avail_out);
+ const size_t compressed_size = kCompressionBuffer - stream_.avail_out;
+
+ if (flush != Z_FINISH) {
+ if (z_result != Z_OK) {
+ LOG(ERROR) << "Compression failed (" << z_result << ").";
+ success = false;
+ break;
+ }
+ } else { // flush == Z_FINISH
+ // End of the stream; we expect the footer to be exactly the size which
+ // we've set aside for it.
+ if (z_result != Z_STREAM_END || compressed_size != kGzipFooterBytes) {
+ LOG(ERROR) << "Compression failed (" << z_result << ", "
+ << compressed_size << ").";
+ success = false;
+ break;
+ }
+ }
+
+ total_compressed_size += compressed_size;
+ } while (stream_.avail_out == 0 && z_result != Z_STREAM_END);
+
+ stream_.next_out = nullptr; // Avoid dangling pointers.
+
+ if (success) {
+ output->resize(total_compressed_size);
+ } else {
+ output->clear();
+ }
+
+ return success;
+}
+
+// Given a string with a textual representation of a web-app ID, return the
+// ID in integer form. If the textual representation does not name a valid
+// web-app ID, return kInvalidWebRtcEventLogWebAppId.
+size_t ExtractWebAppId(base::StringPiece str) {
+ DCHECK_EQ(str.length(), kWebAppIdLength);
+
+ // Avoid leading '+', etc.
+ for (size_t i = 0; i < str.length(); i++) {
+ if (!std::isdigit(str[i])) {
+ return kInvalidWebRtcEventLogWebAppId;
+ }
+ }
+
+ size_t result;
+ if (!base::StringToSizeT(str, &result) ||
+ result < kMinWebRtcEventLogWebAppId ||
+ result > kMaxWebRtcEventLogWebAppId) {
+ return kInvalidWebRtcEventLogWebAppId;
+ }
+ return result;
+}
+
+} // namespace
+
+const size_t kGzipOverheadBytes = kGzipHeaderBytes + kGzipFooterBytes;
+
+const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[] =
+ FILE_PATH_LITERAL("log");
+const base::FilePath::CharType kWebRtcEventLogGzippedExtension[] =
+ FILE_PATH_LITERAL("log.gz");
+const base::FilePath::CharType kWebRtcEventLogHistoryExtension[] =
+ FILE_PATH_LITERAL("hist");
+
+size_t BaseLogFileWriterFactory::MinFileSizeBytes() const {
+ // No overhead incurred; data written straight to the file without metadata.
+ return 0;
+}
+
+base::FilePath::StringPieceType BaseLogFileWriterFactory::Extension() const {
+ return kWebRtcEventLogUncompressedExtension;
+}
+
+std::unique_ptr<LogFileWriter> BaseLogFileWriterFactory::Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const {
+ if (max_file_size_bytes.has_value() &&
+ max_file_size_bytes.value() < MinFileSizeBytes()) {
+ LOG(WARNING) << "Max size (" << max_file_size_bytes.value()
+ << ") below minimum size (" << MinFileSizeBytes() << ").";
+ return nullptr;
+ }
+
+ auto result = std::make_unique<BaseLogFileWriter>(path, max_file_size_bytes);
+
+ if (!result->Init()) {
+ // Error logged by Init.
+ result.reset(); // Destructor deletes errored files.
+ }
+
+ return result;
+}
+
+std::unique_ptr<CompressedSizeEstimator>
+DefaultGzippedSizeEstimator::Factory::Create() const {
+ return std::make_unique<DefaultGzippedSizeEstimator>();
+}
+
+size_t DefaultGzippedSizeEstimator::EstimateCompressedSize(
+ const std::string& input) const {
+ // This estimation is not tight. Since we expect to produce logs of
+ // several MBs, overshooting the estimation by one KB should be
+ // very safe and still relatively efficient.
+ constexpr size_t kOverheadOverUncompressedSizeBytes = 1000;
+ return input.length() + kOverheadOverUncompressedSizeBytes;
+}
+
+GzipLogCompressorFactory::GzipLogCompressorFactory(
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)
+ : estimator_factory_(std::move(estimator_factory)) {}
+
+GzipLogCompressorFactory::~GzipLogCompressorFactory() = default;
+
+size_t GzipLogCompressorFactory::MinSizeBytes() const {
+ return kGzipOverheadBytes;
+}
+
+std::unique_ptr<LogCompressor> GzipLogCompressorFactory::Create(
+ base::Optional<size_t> max_size_bytes) const {
+ if (max_size_bytes.has_value() && max_size_bytes.value() < MinSizeBytes()) {
+ LOG(WARNING) << "Max size (" << max_size_bytes.value()
+ << ") below minimum size (" << MinSizeBytes() << ").";
+ return nullptr;
+ }
+ return std::make_unique<GzipLogCompressor>(max_size_bytes,
+ estimator_factory_->Create());
+}
+
+GzippedLogFileWriterFactory::GzippedLogFileWriterFactory(
+ std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)
+ : gzip_compressor_factory_(std::move(gzip_compressor_factory)) {}
+
+GzippedLogFileWriterFactory::~GzippedLogFileWriterFactory() = default;
+
+size_t GzippedLogFileWriterFactory::MinFileSizeBytes() const {
+ // Only the compression's own overhead is incurred.
+ return gzip_compressor_factory_->MinSizeBytes();
+}
+
+base::FilePath::StringPieceType GzippedLogFileWriterFactory::Extension() const {
+ return kWebRtcEventLogGzippedExtension;
+}
+
+std::unique_ptr<LogFileWriter> GzippedLogFileWriterFactory::Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const {
+ if (max_file_size_bytes.has_value() &&
+ max_file_size_bytes.value() < MinFileSizeBytes()) {
+ LOG(WARNING) << "Size below allowed minimum.";
+ return nullptr;
+ }
+
+ auto gzip_compressor = gzip_compressor_factory_->Create(max_file_size_bytes);
+ if (!gzip_compressor) {
+ // The factory itself will have logged an error.
+ return nullptr;
+ }
+
+ auto result = std::make_unique<GzippedLogFileWriter>(
+ path, max_file_size_bytes, std::move(gzip_compressor));
+
+ if (!result->Init()) {
+ // Error logged by Init.
+ result.reset(); // Destructor deletes errored files.
+ }
+
+ return result;
+}
+
+// Create a random identifier of 32 hexadecimal (uppercase) characters.
+std::string CreateWebRtcEventLogId() {
+ // UnguessableToken's interface makes no promisses over case. We therefore
+ // convert, even if the current implementation does not require it.
+ std::string log_id =
+ base::ToUpperASCII(base::UnguessableToken::Create().ToString());
+ DCHECK_EQ(log_id.size(), kWebRtcEventLogIdLength);
+ DCHECK_EQ(log_id.find_first_not_of("0123456789ABCDEF"), std::string::npos);
+ return log_id;
+}
+
+BrowserContextId GetBrowserContextId(
+ const content::BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ return reinterpret_cast<BrowserContextId>(browser_context);
+}
+
+BrowserContextId GetBrowserContextId(int render_process_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ content::RenderProcessHost* const host =
+ content::RenderProcessHost::FromID(render_process_id);
+
+ content::BrowserContext* const browser_context =
+ host ? host->GetBrowserContext() : nullptr;
+
+ return GetBrowserContextId(browser_context);
+}
+
+base::FilePath GetRemoteBoundWebRtcEventLogsDir(
+ const base::FilePath& browser_context_dir) {
+ const base::FilePath::CharType kRemoteBoundLogSubDirectory[] =
+ FILE_PATH_LITERAL("webrtc_event_logs");
+ return browser_context_dir.Append(kRemoteBoundLogSubDirectory);
+}
+
+base::FilePath WebRtcEventLogPath(
+ const base::FilePath& remote_logs_dir,
+ const std::string& log_id,
+ size_t web_app_id,
+ const base::FilePath::StringPieceType& extension) {
+ DCHECK_GE(web_app_id, kMinWebRtcEventLogWebAppId);
+ DCHECK_LE(web_app_id, kMaxWebRtcEventLogWebAppId);
+
+ static_assert(kWebAppIdLength == 2u, "Fix the code below.");
+ const std::string web_app_id_str = base::StringPrintf("%02zu", web_app_id);
+ DCHECK_EQ(web_app_id_str.length(), kWebAppIdLength);
+
+ const std::string filename =
+ std::string(kRemoteBoundWebRtcEventLogFileNamePrefix) + "_" +
+ web_app_id_str + "_" + log_id;
+
+ return remote_logs_dir.AppendASCII(filename).AddExtension(extension);
+}
+
+bool IsValidRemoteBoundLogFilename(const std::string& filename) {
+ // The -1 is because of the implict \0.
+ const size_t kPrefixLength =
+ base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
+
+ // [prefix]_[web_app_id]_[log_id]
+ const size_t expected_length =
+ kPrefixLength + 1 + kWebAppIdLength + 1 + kWebRtcEventLogIdLength;
+ if (filename.length() != expected_length) {
+ return false;
+ }
+
+ size_t index = 0;
+
+ // Expect prefix.
+ if (filename.find(kRemoteBoundWebRtcEventLogFileNamePrefix) != index) {
+ return false;
+ }
+ index += kPrefixLength;
+
+ // Expect underscore between prefix and web-app ID.
+ if (filename[index] != '_') {
+ return false;
+ }
+ index += 1;
+
+ // Expect web-app-ID.
+ const size_t web_app_id =
+ ExtractWebAppId(base::StringPiece(&filename[index], kWebAppIdLength));
+ if (web_app_id == kInvalidWebRtcEventLogWebAppId) {
+ return false;
+ }
+ index += kWebAppIdLength;
+
+ // Expect underscore between web-app ID and log ID.
+ if (filename[index] != '_') {
+ return false;
+ }
+ index += 1;
+
+ // Expect log ID.
+ const std::string log_id = filename.substr(index);
+ DCHECK_EQ(log_id.length(), kWebRtcEventLogIdLength);
+ const char* const log_id_chars = "0123456789ABCDEF";
+ if (filename.find_first_not_of(log_id_chars, index) != std::string::npos) {
+ return false;
+ }
+
+ return true;
+}
+
+bool IsValidRemoteBoundLogFilePath(const base::FilePath& path) {
+ const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
+ return IsValidRemoteBoundLogFilename(filename);
+}
+
+base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path) {
+ // TODO(crbug.com/775415): Check for validity (after fixing unit tests).
+ return path.RemoveExtension().AddExtension(kWebRtcEventLogHistoryExtension);
+}
+
+std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
+ const base::FilePath& path) {
+ const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
+ if (!IsValidRemoteBoundLogFilename(filename)) {
+ LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
+ return std::string();
+ }
+
+ DCHECK_GE(filename.length(), kWebRtcEventLogIdLength);
+ return filename.substr(filename.length() - kWebRtcEventLogIdLength);
+}
+
+size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
+ const base::FilePath& path) {
+ const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
+ if (!IsValidRemoteBoundLogFilename(filename)) {
+ LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
+ return kInvalidWebRtcEventLogWebAppId;
+ }
+
+ // The -1 is because of the implict \0.
+ const size_t kPrefixLength =
+ base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
+
+ // The +1 is for the underscore between the prefix and the web-app ID.
+ // Length verified by above call to IsValidRemoteBoundLogFilename().
+ DCHECK_GE(filename.length(), kPrefixLength + 1 + kWebAppIdLength);
+ base::StringPiece id_str(&filename[kPrefixLength + 1], kWebAppIdLength);
+
+ return ExtractWebAppId(id_str);
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
new file mode 100644
index 00000000000..c6479729a94
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
@@ -0,0 +1,543 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// This file is intended for:
+// 1. Code shared between WebRtcEventLogManager, WebRtcLocalEventLogManager
+// and WebRtcRemoteEventLogManager.
+// 2. Code specific to either of the above classes, but which also needs
+// to be seen by unit tests (such as constants).
+
+extern const size_t kWebRtcEventLogManagerUnlimitedFileSize;
+
+extern const size_t kDefaultMaxLocalLogFileSizeBytes;
+extern const size_t kMaxNumberLocalWebRtcEventLogFiles;
+
+extern const size_t kMaxRemoteLogFileSizeBytes;
+
+extern const int kMaxOutputPeriodMs;
+
+// Maximum size for a response from Crash, which is the upload ID.
+extern const size_t kWebRtcEventLogMaxUploadIdBytes;
+
+// The number of digits required to encode a remote-bound log ID.
+extern const size_t kWebRtcEventLogIdLength;
+
+// Min/max legal web-app IDs.
+extern const size_t kMinWebRtcEventLogWebAppId;
+extern const size_t kMaxWebRtcEventLogWebAppId;
+
+// Sentinel value, guaranteed not to fall inside the range of min-max valid IDs.
+extern const size_t kInvalidWebRtcEventLogWebAppId;
+
+// Limit over the number of concurrently active (currently being written to
+// disk) remote-bound log files. This limits IO operations, and so it is
+// applied globally (all browser contexts are limited together).
+extern const size_t kMaxActiveRemoteBoundWebRtcEventLogs;
+
+// Limit over the number of pending logs (logs stored on disk and awaiting to
+// be uploaded to a remote server). This limit avoids excessive storage. If a
+// user chooses to have multiple profiles (and hence browser contexts) on a
+// system, it is assumed that the user has enough storage to accommodate
+// the increased storage consumption that comes with it. Therefore, this
+// limit is applied per browser context.
+extern const size_t kMaxPendingRemoteBoundWebRtcEventLogs;
+
+// Max number of history files that may be kept; after this number is exceeded,
+// the oldest logs should be pruned.
+extern const size_t kMaxWebRtcEventLogHistoryFiles;
+
+// Overhead incurred by GZIP due to its header and footer.
+extern const size_t kGzipOverheadBytes;
+
+// Remote-bound log files' names will be of the format:
+// [prefix]_[web_app_id]_[log_id].[ext]
+// Where:
+// * |prefix| is equal to kRemoteBoundWebRtcEventLogFileNamePrefix.
+// * |web_app_id| is a number between kMinWebRtcEventLogWebAppId and
+// kMaxWebRtcEventLogWebAppId, with zero padding.
+// * |log_id| is composed of 32 random characters from '0'-'9' and 'A'-'F'.
+// * |ext| is the extension determined by the used LogCompressor::Factory,
+// which will be either kWebRtcEventLogUncompressedExtension or
+// kWebRtcEventLogGzippedExtension.
+extern const char kRemoteBoundWebRtcEventLogFileNamePrefix[];
+extern const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[];
+extern const base::FilePath::CharType kWebRtcEventLogGzippedExtension[];
+
+// Logs themselves are kept on disk for kRemoteBoundWebRtcEventLogsMaxRetention,
+// or until uploaded. Smaller history files are kept for a longer time, allowing
+// Chrome to display on chrome://webrtc-logs/ that these files were captured
+// and later uploaded.
+extern const base::FilePath::CharType kWebRtcEventLogHistoryExtension[];
+
+// Remote-bound event logs will not be uploaded if the time since their last
+// modification (meaning the time when they were completed) exceeds this value.
+// Such expired files will be purged from disk when examined.
+extern const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention;
+
+// These are made globally visible so that unit tests may check for them.
+extern const char kStartRemoteLoggingFailureAlreadyLogging[];
+extern const char kStartRemoteLoggingFailureDeadRenderProcessHost[];
+extern const char kStartRemoteLoggingFailureFeatureDisabled[];
+extern const char kStartRemoteLoggingFailureFileCreationError[];
+extern const char kStartRemoteLoggingFailureFilePathUsedHistory[];
+extern const char kStartRemoteLoggingFailureFilePathUsedLog[];
+extern const char kStartRemoteLoggingFailureIllegalWebAppId[];
+extern const char kStartRemoteLoggingFailureLoggingDisabledBrowserContext[];
+extern const char kStartRemoteLoggingFailureMaxSizeTooLarge[];
+extern const char kStartRemoteLoggingFailureMaxSizeTooSmall[];
+extern const char kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed[];
+extern const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[];
+extern const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[];
+extern const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[];
+
+// Values for the histogram for the result of the API call to collect
+// a WebRTC event log.
+// Must match the numbering of WebRtcEventLoggingApiEnum in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class WebRtcEventLoggingApiUma {
+ kSuccess = 0, // Log successfully collected.
+ kDeadRph = 1, // Log not collected.
+ kFeatureDisabled = 2, // Log not collected.
+ kIncognito = 3, // Log not collected.
+ kInvalidArguments = 4, // Log not collected.
+ kIllegalSessionId = 5, // Log not collected.
+ kDisabledBrowserContext = 6, // Log not collected.
+ kUnknownOrInvalidPeerConnection = 7, // Log not collected.
+ kAlreadyLogging = 8, // Log not collected.
+ kNoAdditionalLogsAllowed = 9, // Log not collected.
+ kLogPathNotAvailable = 10, // Log not collected.
+ kHistoryPathNotAvailable = 11, // Log not collected.
+ kFileCreationError = 12, // Log not collected.
+ kMaxValue = kFileCreationError
+};
+
+void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result);
+
+// Values for the histogram for the result of the upload of a WebRTC event log.
+// Must match the numbering of WebRtcEventLoggingUploadEnum in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class WebRtcEventLoggingUploadUma {
+ kSuccess = 0, // Uploaded successfully.
+ kLogFileWriteError = 1, // Will not be uploaded.
+ kActiveLogCancelledDueToCacheClear = 2, // Will not be uploaded.
+ kPendingLogDeletedDueToCacheClear = 3, // Will not be uploaded.
+ kHistoryFileCreationError = 4, // Will not be uploaded.
+ kHistoryFileWriteError = 5, // Will not be uploaded.
+ kLogFileReadError = 6, // Will not be uploaded.
+ kLogFileNameError = 7, // Will not be uploaded.
+ kUploadCancelled = 8, // Upload started then cancelled.
+ kUploadFailure = 9, // Upload attempted and failed.
+ kIncompletePastUpload = 10, // Upload attempted and failed.
+ kExpiredLogFileAtChromeStart = 11, // Expired before upload opportunity.
+ kExpiredLogFileDuringSession = 12, // Expired before upload opportunity.
+ kMaxValue = kExpiredLogFileDuringSession
+};
+
+void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result);
+
+// Success is signalled by 0.
+// All negative values signal errors.
+// Positive values are not used.
+void UmaRecordWebRtcEventLoggingNetErrorType(int net_error);
+
+// For a given Chrome session, this is a unique key for PeerConnections.
+// It's not, however, unique between sessions (after Chrome is restarted).
+struct WebRtcEventLogPeerConnectionKey {
+ using BrowserContextId = uintptr_t;
+
+ constexpr WebRtcEventLogPeerConnectionKey()
+ : WebRtcEventLogPeerConnectionKey(
+ /* render_process_id = */ 0,
+ /* lid = */ 0,
+ reinterpret_cast<BrowserContextId>(nullptr)) {}
+
+ constexpr WebRtcEventLogPeerConnectionKey(int render_process_id,
+ int lid,
+ BrowserContextId browser_context_id)
+ : render_process_id(render_process_id),
+ lid(lid),
+ browser_context_id(browser_context_id) {}
+
+ bool operator==(const WebRtcEventLogPeerConnectionKey& other) const {
+ // Each RPH is associated with exactly one BrowserContext.
+ DCHECK(render_process_id != other.render_process_id ||
+ browser_context_id == other.browser_context_id);
+
+ const bool equal = std::tie(render_process_id, lid) ==
+ std::tie(other.render_process_id, other.lid);
+ return equal;
+ }
+
+ bool operator<(const WebRtcEventLogPeerConnectionKey& other) const {
+ // Each RPH is associated with exactly one BrowserContext.
+ DCHECK(render_process_id != other.render_process_id ||
+ browser_context_id == other.browser_context_id);
+
+ return std::tie(render_process_id, lid) <
+ std::tie(other.render_process_id, other.lid);
+ }
+
+ // These two fields are the actual key; any peer connection is uniquely
+ // identifiable by the renderer process in which it lives, and its ID within
+ // that process.
+ int render_process_id;
+ int lid; // Renderer-local PeerConnection ID.
+
+ // The BrowserContext is not actually part of the key, but each PeerConnection
+ // is associated with a BrowserContext, and that BrowserContext is almost
+ // always necessary, so it makes sense to remember it along with the key.
+ BrowserContextId browser_context_id;
+};
+
+// Sentinel value for an unknown BrowserContext.
+extern const WebRtcEventLogPeerConnectionKey::BrowserContextId
+ kNullBrowserContextId;
+
+// Holds housekeeping information about log files.
+struct WebRtcLogFileInfo {
+ WebRtcLogFileInfo(
+ WebRtcEventLogPeerConnectionKey::BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ base::Time last_modified)
+ : browser_context_id(browser_context_id),
+ path(path),
+ last_modified(last_modified) {}
+
+ WebRtcLogFileInfo(const WebRtcLogFileInfo& other)
+ : browser_context_id(other.browser_context_id),
+ path(other.path),
+ last_modified(other.last_modified) {}
+
+ bool operator<(const WebRtcLogFileInfo& other) const {
+ if (last_modified != other.last_modified) {
+ return last_modified < other.last_modified;
+ }
+ return path < other.path; // Break ties arbitrarily, but consistently.
+ }
+
+ // The BrowserContext which produced this file.
+ const WebRtcEventLogPeerConnectionKey::BrowserContextId browser_context_id;
+
+ // The path to the log file itself.
+ const base::FilePath path;
+
+ // |last_modified| recorded at BrowserContext initialization. Chrome will
+ // not modify it afterwards, and neither should the user.
+ const base::Time last_modified;
+};
+
+// An observer for notifications of local log files being started/stopped, and
+// the paths which will be used for these logs.
+class WebRtcLocalEventLogsObserver {
+ public:
+ virtual void OnLocalLogStarted(WebRtcEventLogPeerConnectionKey key,
+ const base::FilePath& file_path) = 0;
+ virtual void OnLocalLogStopped(WebRtcEventLogPeerConnectionKey key) = 0;
+
+ protected:
+ virtual ~WebRtcLocalEventLogsObserver() = default;
+};
+
+// An observer for notifications of remote-bound log files being
+// started/stopped. The start event would likely only interest unit tests
+// (because it exposes the randomized filename to them). The stop event is of
+// general interest, because it would often mean that WebRTC can stop sending
+// us event logs for this peer connection.
+// Some cases where OnRemoteLogStopped would be called include:
+// 1. The PeerConnection has become inactive.
+// 2. The file's maximum size has been reached.
+// 3. Any type of error while writing to the file.
+class WebRtcRemoteEventLogsObserver {
+ public:
+ virtual void OnRemoteLogStarted(WebRtcEventLogPeerConnectionKey key,
+ const base::FilePath& file_path,
+ int output_period_ms) = 0;
+ virtual void OnRemoteLogStopped(WebRtcEventLogPeerConnectionKey key) = 0;
+
+ protected:
+ virtual ~WebRtcRemoteEventLogsObserver() = default;
+};
+
+// Writes a log to a file while observing a maximum size.
+class LogFileWriter {
+ public:
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ // The smallest size a log file of this type may assume.
+ virtual size_t MinFileSizeBytes() const = 0;
+
+ // The extension type associated with this type of log files.
+ virtual base::FilePath::StringPieceType Extension() const = 0;
+
+ // Instantiate and initialize a LogFileWriter.
+ // If creation or initialization fail, an empty unique_ptr will be returned,
+ // and it will be guaranteed that the file itself is not created. (If |path|
+ // had pointed to an existing file, that file will be deleted.)
+ // If !max_file_size_bytes.has_value(), the LogFileWriter is unlimited.
+ virtual std::unique_ptr<LogFileWriter> Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const = 0;
+ };
+
+ virtual ~LogFileWriter() = default;
+
+ // Init() must be called on each LogFileWriter exactly once, before it's used.
+ // If initialization fails, no further actions may be performed on the object
+ // other than Close() and Delete().
+ virtual bool Init() = 0;
+
+ // Getter for the path of the file |this| wraps.
+ virtual const base::FilePath& path() const = 0;
+
+ // Whether the maximum file size was reached.
+ virtual bool MaxSizeReached() const = 0;
+
+ // Writes to the log file while respecting the file's size limit.
+ // True is returned if and only if the message was written to the file in
+ // it entirety. That is, |false| is returned either if a genuine error
+ // occurs, or when the budget does not allow the next write.
+ // If |false| is ever returned, only Close() and Delete() may subsequently
+ // be called.
+ // The function does *not* close the file.
+ // The function may not be called if MaxSizeReached().
+ virtual bool Write(const std::string& input) = 0;
+
+ // If the file was successfully closed, true is returned, and the file may
+ // now be used. Otherwise, the file is deleted, and false is returned.
+ virtual bool Close() = 0;
+
+ // Delete the file from disk.
+ virtual void Delete() = 0;
+};
+
+// Produces LogFileWriter instances that perform no compression.
+class BaseLogFileWriterFactory : public LogFileWriter::Factory {
+ public:
+ ~BaseLogFileWriterFactory() override = default;
+
+ size_t MinFileSizeBytes() const override;
+
+ base::FilePath::StringPieceType Extension() const override;
+
+ std::unique_ptr<LogFileWriter> Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const override;
+};
+
+// Interface for a class that provides compression of a stream, while attempting
+// to observe a limit on the size.
+//
+// One should note that:
+// * For compressors that use a footer, to guarantee proper decompression,
+// the footer must be written to the file.
+// * In such a case, usually, nothing can be omitted from the file, or the
+// footer's CRC (if used) would be wrong.
+// * Determining a string's size pre-compression, without performing the actual
+// compression, is heuristic in nature.
+//
+// Therefore, compression might terminate (FULL) earlier than it
+// must, or even in theory (which we attempt to avoid in practice) exceed the
+// size allowed it, in which case the file will be discarded (ERROR).
+class LogCompressor {
+ public:
+ // By subclassing this factory, concrete implementations of LogCompressor can
+ // be produced by unit tests, while keeping their definition in the .cc file.
+ // (Only the factory needs to be declared in the header.)
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ // The smallest size a log file of this type may assume.
+ virtual size_t MinSizeBytes() const = 0;
+
+ // Returns a LogCompressor if the parameters are valid and all
+ // initializations are successful; en empty unique_ptr otherwise.
+ // If !max_size_bytes.has_value(), an unlimited compressor is created.
+ virtual std::unique_ptr<LogCompressor> Create(
+ base::Optional<size_t> max_size_bytes) const = 0;
+ };
+
+ // Result of a call to Compress().
+ // * OK and ERROR_ENCOUNTERED are self-explanatory.
+ // * DISALLOWED means that, due to budget constraints, the input could
+ // not be compressed. The stream is still in a legal state, but only
+ // a call to CreateFooter() is now allowed.
+ enum class Result { OK, DISALLOWED, ERROR_ENCOUNTERED };
+
+ virtual ~LogCompressor() = default;
+
+ // Produces a compression header and writes it to |output|.
+ // The size does not count towards the max size limit.
+ // Guaranteed not to fail (nothing can realistically go wrong).
+ virtual void CreateHeader(std::string* output) = 0;
+
+ // Compresses |input| into |output|.
+ // * If compression succeeded, and the budget was observed, OK is returned.
+ // * If the compressor thinks the string, once compressed, will exceed the
+ // maximum size (when combined with previously compressed strings),
+ // compression will not be done, and DISALLOWED will be returned.
+ // This allows producing a valid footer without exceeding the size limit.
+ // * Unexpected errors in the underlying compressor (e.g. zlib, etc.),
+ // or unexpectedly getting a compressed string which exceeds the budget,
+ // will return ERROR_ENCOUNTERED.
+ // This function may not be called again if DISALLOWED or ERROR_ENCOUNTERED
+ // were ever returned before, or after CreateFooter() was called.
+ virtual Result Compress(const std::string& input, std::string* output) = 0;
+
+ // Produces a compression footer and writes it to |output|.
+ // The footer does not count towards the max size limit.
+ // May not be called more than once, or if Compress() returned ERROR.
+ virtual bool CreateFooter(std::string* output) = 0;
+};
+
+// Estimates the compressed size, without performing compression (except in
+// unit tests, where performance is of lesser importance).
+// This interface allows unit tests to simulate specific cases, such as
+// over/under-estimation, and show that the code using the LogCompressor
+// deals with them correctly. (E.g., if the estimation expects the compression
+// to not go over-budget, but then it does.)
+// The estimator is expected to be stateful. That is, the order of calls to
+// EstimateCompressedSize() should correspond to the order of calls
+// to Compress().
+class CompressedSizeEstimator {
+ public:
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+ virtual std::unique_ptr<CompressedSizeEstimator> Create() const = 0;
+ };
+
+ virtual ~CompressedSizeEstimator() = default;
+
+ virtual size_t EstimateCompressedSize(const std::string& input) const = 0;
+};
+
+// Provides a conservative estimation of the number of bytes required to
+// compress a string using GZIP. This estimation is not expected to ever
+// be overly optimistic, but the code using it should nevertheless be prepared
+// to deal with that theoretical possibility.
+class DefaultGzippedSizeEstimator : public CompressedSizeEstimator {
+ public:
+ class Factory : public CompressedSizeEstimator::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<CompressedSizeEstimator> Create() const override;
+ };
+
+ ~DefaultGzippedSizeEstimator() override = default;
+
+ size_t EstimateCompressedSize(const std::string& input) const override;
+};
+
+// Interface for producing LogCompressorGzip objects.
+class GzipLogCompressorFactory : public LogCompressor::Factory {
+ public:
+ explicit GzipLogCompressorFactory(
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory);
+ ~GzipLogCompressorFactory() override;
+
+ size_t MinSizeBytes() const override;
+
+ std::unique_ptr<LogCompressor> Create(
+ base::Optional<size_t> max_size_bytes) const override;
+
+ private:
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory_;
+};
+
+// Produces LogFileWriter instances that perform compression using GZIP.
+class GzippedLogFileWriterFactory : public LogFileWriter::Factory {
+ public:
+ explicit GzippedLogFileWriterFactory(
+ std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory);
+
+ ~GzippedLogFileWriterFactory() override;
+
+ size_t MinFileSizeBytes() const override;
+
+ base::FilePath::StringPieceType Extension() const override;
+
+ std::unique_ptr<LogFileWriter> Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const override;
+
+ private:
+ std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory_;
+};
+
+// Create a random identifier of 32 hexadecimal (uppercase) characters.
+std::string CreateWebRtcEventLogId();
+
+// Translate a BrowserContext into an ID. This lets us associate PeerConnections
+// with BrowserContexts, while making sure that we never call the
+// BrowserContext's methods outside of the UI thread (because we can't call them
+// at all without a cast that would alert us to the danger).
+WebRtcEventLogPeerConnectionKey::BrowserContextId GetBrowserContextId(
+ const content::BrowserContext* browser_context);
+
+// Fetches the BrowserContext associated with the render process ID, then
+// returns its BrowserContextId. (If the render process has already died,
+// it would have no BrowserContext associated, so the ID associated with a
+// null BrowserContext will be returned.)
+WebRtcEventLogPeerConnectionKey::BrowserContextId GetBrowserContextId(
+ int render_process_id);
+
+// Given a BrowserContext's directory, return the path to the directory where
+// we store the pending remote-bound logs associated with this BrowserContext.
+// This function may be called on any task queue.
+base::FilePath GetRemoteBoundWebRtcEventLogsDir(
+ const base::FilePath& browser_context_dir);
+
+// Produce the path to a remote-bound WebRTC event log file with the given
+// log ID, web-app ID and extension, in the given directory.
+base::FilePath WebRtcEventLogPath(
+ const base::FilePath& remote_logs_dir,
+ const std::string& log_id,
+ size_t web_app_id,
+ const base::FilePath::StringPieceType& extension);
+
+// Checks whether the path/filename would be a valid reference to a remote-bound
+// even log. These functions do not examine the file's content or its extension.
+bool IsValidRemoteBoundLogFilename(const std::string& filename);
+bool IsValidRemoteBoundLogFilePath(const base::FilePath& path);
+
+// Given WebRTC event log's path, return the path to the history file that
+// is, or would be, associated with it.
+base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path);
+
+// Attempts to extract the local ID from the file's path. Returns the empty
+// string in case of an error.
+std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
+ const base::FilePath& path);
+
+// Attempts to extract the web-app ID from the file's path.
+// Returns kInvalidWebRtcEventLogWebAppId in case of an error.
+size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
+ const base::FilePath& path);
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc
new file mode 100644
index 00000000000..400d4391500
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc
@@ -0,0 +1,655 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+#include <memory>
+#include <numeric>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/optional.h"
+#include "base/rand_util.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace webrtc_event_logging {
+
+namespace {
+constexpr LogCompressor::Result OK = LogCompressor::Result::OK;
+constexpr LogCompressor::Result DISALLOWED = LogCompressor::Result::DISALLOWED;
+constexpr LogCompressor::Result ERROR_ENCOUNTERED =
+ LogCompressor::Result::ERROR_ENCOUNTERED;
+} // namespace
+
+// Tests for GzipLogCompressor.
+// Note that these tests may not use GzippedSize(), or they would be assuming
+// what they set out to prove. (Subsequent tests may use it, though.)
+class GzipLogCompressorTest : public ::testing::Test {
+ public:
+ ~GzipLogCompressorTest() override = default;
+
+ void Init(
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory) {
+ DCHECK(!compressor_factory_);
+ DCHECK(estimator_factory);
+ compressor_factory_ = std::make_unique<GzipLogCompressorFactory>(
+ std::move(estimator_factory));
+ }
+
+ std::string Decompress(const std::string& input) {
+ std::string output;
+ EXPECT_TRUE(compression::GzipUncompress(input, &output));
+ return output;
+ }
+
+ std::unique_ptr<GzipLogCompressorFactory> compressor_factory_;
+};
+
+TEST_F(GzipLogCompressorTest,
+ GzipLogCompressorFactoryCreatesCompressorIfMinimalSizeOrAbove) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+ const size_t min_size = compressor_factory_->MinSizeBytes();
+ auto compressor = compressor_factory_->Create(min_size);
+ EXPECT_TRUE(compressor);
+}
+
+TEST_F(GzipLogCompressorTest,
+ GzipLogCompressorFactoryDoesNotCreateCompressorIfBelowMinimalSize) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+ const size_t min_size = compressor_factory_->MinSizeBytes();
+ ASSERT_GE(min_size, 1u);
+ auto compressor = compressor_factory_->Create(min_size - 1);
+ EXPECT_FALSE(compressor);
+}
+
+TEST_F(GzipLogCompressorTest, EmptyStreamReasonableMaxSize) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + footer;
+ EXPECT_EQ(Decompress(simulated_file), std::string());
+}
+
+TEST_F(GzipLogCompressorTest, EmptyStreamMinimalSize) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ const size_t min_size = compressor_factory_->MinSizeBytes();
+ auto compressor = compressor_factory_->Create(min_size);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + footer;
+ EXPECT_EQ(Decompress(simulated_file), std::string());
+}
+
+TEST_F(GzipLogCompressorTest, SingleCallToCompress) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ const std::string input = "Some random text.";
+ std::string log;
+ ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, MultipleCallsToCompress) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ const std::vector<std::string> inputs = {
+ "Some random text.",
+ "This text is also random. I give you my word for it. 100% random.",
+ "nejnnc pqmnx0981 mnl<D@ikjed90~~,z."};
+
+ std::vector<std::string> logs(inputs.size());
+ for (size_t i = 0; i < inputs.size(); i++) {
+ ASSERT_EQ(compressor->Compress(inputs[i], &logs[i]), OK);
+ }
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const auto input = std::accumulate(begin(inputs), end(inputs), std::string());
+ const auto log = std::accumulate(begin(logs), end(logs), std::string());
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, UnlimitedBudgetSanity) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(base::Optional<size_t>());
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ const std::string input = "Some random text.";
+ std::string log;
+ ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+// Test once with a big input, to provide coverage over inputs that could
+// exceed the size of some local buffers in the UUT.
+TEST_F(GzipLogCompressorTest, CompressionBigInput) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ constexpr size_t kRealisticSizeBytes = 1000 * 1000;
+ const std::string input = base::RandBytesAsString(kRealisticSizeBytes);
+ std::string log;
+ ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, BudgetExceededByFirstCompressYieldsEmptyFile) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ const std::string input = "This won't fit.";
+
+ auto compressor = compressor_factory_->Create(GzippedSize(input) - 1);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ // Focal point #1 - Compress() returns DISALLOWED.
+ std::string log;
+ EXPECT_EQ(compressor->Compress(input, &log), DISALLOWED);
+
+ // Focal point #2 - CreateFooter() still succeeds;
+ std::string footer;
+ EXPECT_TRUE(compressor->CreateFooter(&footer));
+
+ // Focal point #3 - the resulting log is parsable, and contains only those
+ // logs for which Compress() was successful.
+ // Note that |log| is not supposed to be written to the file, because
+ // Compress() has disallowed it.
+ const std::string simulated_file = header + footer;
+ EXPECT_EQ(Decompress(simulated_file), std::string());
+}
+
+TEST_F(GzipLogCompressorTest,
+ BudgetExceededByNonFirstCompressYieldsPartialFile) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ const std::string short_input = "short";
+ const std::string long_input = "A somewhat longer input string. @$%^&*()!!2";
+
+ // Allocate enough budget that |short_input| would be produced, and not yet
+ // exhaust the budget, but |long_input| won't fit.
+ auto compressor = compressor_factory_->Create(GzippedSize(short_input) + 1);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ std::string short_log;
+ ASSERT_EQ(compressor->Compress(short_input, &short_log), OK);
+
+ // Focal point #1 - Compress() returns DISALLOWED.
+ std::string long_log;
+ EXPECT_EQ(compressor->Compress(long_input, &long_log), DISALLOWED);
+ EXPECT_TRUE(long_log.empty());
+
+ // Focal point #2 - CreateFooter() still succeeds;
+ std::string footer;
+ EXPECT_TRUE(compressor->CreateFooter(&footer));
+
+ // Focal point #3 - the resulting log is parsable, and contains only those
+ // logs for which Compress() was successful.
+ // Note that |long_log| is not supposed to be written to the file, because
+ // Compress() has disallowed it.
+ const std::string simulated_file = header + short_log + footer;
+ EXPECT_EQ(Decompress(simulated_file), short_input);
+}
+
+TEST_F(GzipLogCompressorTest,
+ ExceedingBudgetDueToOverlyOptimisticEstimationYieldsError) {
+ // Use an estimator that will always be overly optimistic.
+ Init(std::make_unique<NullEstimator::Factory>());
+
+ // Set a budget that will easily be exceeded.
+ auto compressor = compressor_factory_->Create(kGzipOverheadBytes + 5);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ // Prepare to compress an input that is guaranteed to exceed the budget.
+ const std::string input = "A string that would not fit in five bytes.";
+
+ // The estimation allowed the compression, but then the compressed output
+ // ended up being over-budget.
+ std::string compressed;
+ EXPECT_EQ(compressor->Compress(input, &compressed), ERROR_ENCOUNTERED);
+ EXPECT_TRUE(compressed.empty());
+}
+
+// Tests relevant to all LogFileWriter subclasses.
+class LogFileWriterTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<WebRtcEventLogCompression> {
+ public:
+ LogFileWriterTest() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+ ~LogFileWriterTest() override {}
+
+ void Init(WebRtcEventLogCompression compression) {
+ DCHECK(!compression_.has_value()) << "Must only be called once.";
+ compression_ = compression;
+ log_file_writer_factory_ = CreateLogFileWriterFactory(compression);
+ path_ = temp_dir_.GetPath()
+ .Append(FILE_PATH_LITERAL("arbitrary_filename"))
+ .AddExtension(log_file_writer_factory_->Extension());
+ }
+
+ std::unique_ptr<LogFileWriter> CreateWriter(base::Optional<size_t> max_size) {
+ return log_file_writer_factory_->Create(path_, max_size);
+ }
+
+ void ExpectFileContents(const base::FilePath& file_path,
+ const std::string& expected_contents) {
+ DCHECK(compression_.has_value()) << "Must call Init().";
+
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+
+ switch (compression_.value()) {
+ case WebRtcEventLogCompression::NONE: {
+ EXPECT_EQ(file_contents, expected_contents);
+ break;
+ }
+ case WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION:
+ case WebRtcEventLogCompression::GZIP_NULL_ESTIMATION: {
+ std::string uncompressed;
+ ASSERT_TRUE(compression::GzipUncompress(file_contents, &uncompressed));
+ EXPECT_EQ(uncompressed, expected_contents);
+ break;
+ }
+ default: { NOTREACHED(); }
+ }
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ base::Optional<WebRtcEventLogCompression> compression_; // Set in Init().
+ base::ScopedTempDir temp_dir_;
+ base::FilePath path_;
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory_;
+};
+
+TEST_P(LogFileWriterTest, FactoryCreatesLogFileWriter) {
+ Init(GetParam());
+ EXPECT_TRUE(CreateWriter(log_file_writer_factory_->MinFileSizeBytes()));
+}
+
+#if defined(OS_POSIX)
+TEST_P(LogFileWriterTest, FactoryReturnsEmptyUniquePtrIfCantCreateFile) {
+ Init(GetParam());
+ RemoveWritePermissions(temp_dir_.GetPath());
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ EXPECT_FALSE(writer);
+}
+#endif // defined(OS_POSIX)
+
+TEST_P(LogFileWriterTest, CloseSucceedsWhenNoErrorsOccurred) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Close());
+}
+
+// Other tests check check the case of compression where the estimation is
+// close to the file's capacity, reaches or exceeds it.
+TEST_P(LogFileWriterTest, CallToWriteSuccedsWhenCapacityFarOff) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, CallToWriteWithEmptyStringSucceeds) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "";
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, UnlimitedBudgetSanity) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(base::Optional<size_t>());
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, DeleteRemovesUnclosedFile) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ writer->Delete();
+ EXPECT_FALSE(base::PathExists(path_));
+}
+
+TEST_P(LogFileWriterTest, DeleteRemovesClosedFile) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Close());
+
+ writer->Delete();
+ EXPECT_FALSE(base::PathExists(path_));
+}
+
+#if !defined(OS_WIN) // Deleting the open file does not work on Windows.
+TEST_P(LogFileWriterTest, WriteDoesNotCrashIfFileRemovedExternally) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+ ASSERT_FALSE(base::PathExists(path_)); // Sanity on the test itself.
+
+ // It's up to the OS whether this will succeed or fail, but it must not crash.
+ writer->Write("log");
+}
+
+TEST_P(LogFileWriterTest, CloseDoesNotCrashIfFileRemovedExternally) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+ ASSERT_FALSE(base::PathExists(path_)); // Sanity on the test itself.
+
+ // It's up to the OS whether this will succeed or fail, but it must not crash.
+ writer->Close();
+}
+
+TEST_P(LogFileWriterTest, DeleteDoesNotCrashIfFileRemovedExternally) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+ ASSERT_FALSE(base::PathExists(path_)); // Sanity on the test itself.
+
+ // It's up to the OS whether this will succeed or fail, but it must not crash.
+ writer->Delete();
+}
+#endif // !defined(OS_WIN)
+
+TEST_P(LogFileWriterTest, PathReturnsTheCorrectPath) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_EQ(writer->path(), path_);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Compression,
+ LogFileWriterTest,
+ ::testing::Values(WebRtcEventLogCompression::NONE,
+ WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION));
+
+// Tests for UncompressedLogFileWriterTest only.
+class UncompressedLogFileWriterTest : public LogFileWriterTest {
+ public:
+ ~UncompressedLogFileWriterTest() override = default;
+};
+
+TEST_F(UncompressedLogFileWriterTest,
+ MaxSizeReachedReturnsFalseWhenMaxNotReached) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ ASSERT_TRUE(writer->Write(log));
+
+ EXPECT_FALSE(writer->MaxSizeReached());
+}
+
+TEST_F(UncompressedLogFileWriterTest, MaxSizeReachedReturnsTrueWhenMaxReached) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(log.size());
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(writer->Write(log)); // (CallToWriteSuccedsWhenCapacityReached)
+
+ EXPECT_TRUE(writer->MaxSizeReached());
+}
+
+TEST_F(UncompressedLogFileWriterTest, CallToWriteSuccedsWhenCapacityReached) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(log.size());
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_F(UncompressedLogFileWriterTest, CallToWriteFailsWhenCapacityExceeded) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(log.size() - 1);
+ ASSERT_TRUE(writer);
+
+ EXPECT_FALSE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, std::string());
+}
+
+TEST_F(UncompressedLogFileWriterTest, WriteCompleteMessagesOnly) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log1 = "01234";
+ const std::string log2 = "56789";
+
+ auto writer = CreateWriter(log1.size() + log2.size() - 1);
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Write(log1));
+
+ EXPECT_FALSE(writer->Write(log2));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log1);
+}
+
+// Tests for GzippedLogFileWriterTest only.
+class GzippedLogFileWriterTest : public LogFileWriterTest {
+ public:
+ ~GzippedLogFileWriterTest() override = default;
+};
+
+TEST_F(GzippedLogFileWriterTest, FactoryDeletesFileIfMaxSizeBelowMin) {
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ const size_t min_size = log_file_writer_factory_->MinFileSizeBytes();
+ ASSERT_GE(min_size, 1u);
+
+ auto writer = CreateWriter(min_size - 1);
+ ASSERT_FALSE(writer);
+
+ EXPECT_FALSE(base::PathExists(path_));
+}
+
+TEST_F(GzippedLogFileWriterTest, MaxSizeReachedReturnsFalseWhenMaxNotReached) {
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ ASSERT_TRUE(writer->Write(log));
+ EXPECT_FALSE(writer->MaxSizeReached());
+}
+
+TEST_F(GzippedLogFileWriterTest, MaxSizeReachedReturnsTrueWhenMaxReached) {
+ // By using a 0 estimation, we allow the compressor to keep going to
+ // the point of budget saturation.
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(GzippedSize(log));
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(writer->Write(log)); // (CallToWriteSuccedsWhenCapacityReached)
+ EXPECT_TRUE(writer->MaxSizeReached());
+}
+
+TEST_F(GzippedLogFileWriterTest, CallToWriteSuccedsWhenCapacityReached) {
+ Init(WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(GzippedSize(log));
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+// Also tests the scenario WriteCompleteMessagesOnly.
+TEST_F(GzippedLogFileWriterTest,
+ CallToWriteFailsWhenCapacityWouldBeExceededButEstimationPreventedWrite) {
+ Init(WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION);
+
+ const std::string log1 = "abcde";
+ const std::string log2 = "fghij";
+ const std::vector<std::string> logs = {log1, log2};
+
+ // Find out the size necessary for compressing log1 and log2 in two calls.
+ const size_t compressed_len = GzippedSize(logs); // Vector version.
+
+ auto writer = CreateWriter(compressed_len - 1);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(writer->Write(log1));
+
+ EXPECT_FALSE(writer->Write(log2));
+
+ // The second write was succesfully prevented; no error should have occurred,
+ // and it should be possible to produce a meaningful gzipped log file.
+ EXPECT_TRUE(writer->Close());
+
+ ExpectFileContents(path_, log1); // Only the in-budget part was written.
+}
+
+// This tests the case when the estimation fails to warn us of a pending
+// over-budget write, which leaves us unable to produce a valid compression
+// footer for the truncated file. This forces us to discard the file.
+TEST_F(GzippedLogFileWriterTest,
+ CallToWriteFailsWhenCapacityExceededDespiteEstimationAllowingIt) {
+ // By using a 0 estimation, we allow the compressor to keep going to
+ // the point of budget saturation.
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(GzippedSize(log) - 1);
+ ASSERT_TRUE(writer);
+
+ EXPECT_FALSE(writer->Write(log));
+
+ EXPECT_FALSE(writer->Close());
+ EXPECT_FALSE(base::PathExists(path_)); // Errored files deleted by Close().
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc
new file mode 100644
index 00000000000..ec5401e454a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc
@@ -0,0 +1,36 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h"
+
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace webrtc_event_logging {
+
+WebRtcEventLogManagerKeyedService::WebRtcEventLogManagerKeyedService(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {
+ DCHECK(!browser_context_->IsOffTheRecord());
+
+ WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
+ if (manager) {
+ manager->EnableForBrowserContext(browser_context_, base::OnceClosure());
+ reported_ = true;
+ } else {
+ reported_ = false;
+ }
+}
+
+void WebRtcEventLogManagerKeyedService::Shutdown() {
+ WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
+ if (manager) {
+ DCHECK(reported_) << "WebRtcEventLogManager constructed too late.";
+ manager->DisableForBrowserContext(browser_context_, base::OnceClosure());
+ }
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h
new file mode 100644
index 00000000000..c12cf66f999
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// KeyedService working on behalf of WebRtcEventLogManager, informing it when
+// new BrowserContext-s are loaded.
+class WebRtcEventLogManagerKeyedService : public KeyedService {
+ public:
+ explicit WebRtcEventLogManagerKeyedService(
+ content::BrowserContext* browser_context);
+
+ ~WebRtcEventLogManagerKeyedService() override = default;
+
+ void Shutdown() override;
+
+ private:
+ // The BrowserContext associated with this instance of the service.
+ content::BrowserContext* const browser_context_;
+
+ // Whether the singleton content::WebRtcEventLogger existed at the time this
+ // service was instantiated, and therefore got the report that this
+ // BrowserContext was loaded.
+ // See usage for rationale.
+ bool reported_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManagerKeyedService);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc
new file mode 100644
index 00000000000..5660683eebe
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc
@@ -0,0 +1,40 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h"
+
+#include "base/logging.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace webrtc_event_logging {
+
+// static
+WebRtcEventLogManagerKeyedServiceFactory*
+WebRtcEventLogManagerKeyedServiceFactory::GetInstance() {
+ return base::Singleton<WebRtcEventLogManagerKeyedServiceFactory>::get();
+}
+
+WebRtcEventLogManagerKeyedServiceFactory::
+ WebRtcEventLogManagerKeyedServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "WebRtcEventLogManagerKeyedService",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+WebRtcEventLogManagerKeyedServiceFactory::
+ ~WebRtcEventLogManagerKeyedServiceFactory() = default;
+
+bool WebRtcEventLogManagerKeyedServiceFactory::
+ ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+KeyedService* WebRtcEventLogManagerKeyedServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ DCHECK(!context->IsOffTheRecord());
+ return new WebRtcEventLogManagerKeyedService(context);
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h
new file mode 100644
index 00000000000..6e7195758f1
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h
@@ -0,0 +1,44 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class KeyedService;
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// Produces WebRtcEventLogManagerKeyedService-s for non-incognito profiles.
+class WebRtcEventLogManagerKeyedServiceFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static WebRtcEventLogManagerKeyedServiceFactory* GetInstance();
+
+ protected:
+ bool ServiceIsCreatedWithBrowserContext() const override;
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ WebRtcEventLogManagerKeyedServiceFactory>;
+
+ WebRtcEventLogManagerKeyedServiceFactory();
+ ~WebRtcEventLogManagerKeyedServiceFactory() override;
+
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManagerKeyedServiceFactory);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_FACTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
new file mode 100644
index 00000000000..d13b25244aa
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
@@ -0,0 +1,252 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_local.h"
+
+#include "base/files/file_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+
+#if defined(OS_WIN)
+#define NumberToStringType base::NumberToString16
+#else
+#define NumberToStringType base::NumberToString
+#endif
+
+namespace webrtc_event_logging {
+
+#if defined(OS_ANDROID)
+const size_t kDefaultMaxLocalLogFileSizeBytes = 10000000;
+const size_t kMaxNumberLocalWebRtcEventLogFiles = 3;
+#else
+const size_t kDefaultMaxLocalLogFileSizeBytes = 60000000;
+const size_t kMaxNumberLocalWebRtcEventLogFiles = 5;
+#endif
+
+WebRtcLocalEventLogManager::WebRtcLocalEventLogManager(
+ WebRtcLocalEventLogsObserver* observer)
+ : observer_(observer),
+ clock_for_testing_(nullptr),
+ max_log_file_size_bytes_(kDefaultMaxLocalLogFileSizeBytes) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DETACH_FROM_SEQUENCE(io_task_sequence_checker_);
+}
+
+WebRtcLocalEventLogManager::~WebRtcLocalEventLogManager() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+bool WebRtcLocalEventLogManager::PeerConnectionAdded(
+ const PeerConnectionKey& key) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ const auto insertion_result = active_peer_connections_.insert(key);
+ if (!insertion_result.second) {
+ return false; // Attempt to re-add the PeerConnection.
+ }
+
+ if (!base_path_.empty() &&
+ log_files_.size() < kMaxNumberLocalWebRtcEventLogFiles) {
+ // Note that success/failure of starting the local log file is unrelated
+ // to the success/failure of PeerConnectionAdded().
+ StartLogFile(key);
+ }
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::PeerConnectionRemoved(
+ const PeerConnectionKey& key) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ auto peer_connection = active_peer_connections_.find(key);
+
+ if (peer_connection == active_peer_connections_.end()) {
+ DCHECK(log_files_.find(key) == log_files_.end());
+ return false;
+ }
+
+ auto local_log = log_files_.find(key);
+ if (local_log != log_files_.end()) {
+ // Note that success/failure of stopping the local log file is unrelated
+ // to the success/failure of PeerConnectionRemoved().
+ CloseLogFile(local_log);
+ }
+
+ active_peer_connections_.erase(peer_connection);
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::EnableLogging(const base::FilePath& base_path,
+ size_t max_file_size_bytes) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ if (!base_path_.empty()) {
+ return false;
+ }
+
+ DCHECK_EQ(log_files_.size(), 0u);
+
+ base_path_ = base_path;
+
+ max_log_file_size_bytes_ =
+ (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize)
+ ? base::Optional<size_t>()
+ : base::Optional<size_t>(max_file_size_bytes);
+
+ for (const PeerConnectionKey& peer_connection : active_peer_connections_) {
+ if (log_files_.size() >= kMaxNumberLocalWebRtcEventLogFiles) {
+ break;
+ }
+ StartLogFile(peer_connection);
+ }
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::DisableLogging() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ if (base_path_.empty()) {
+ return false;
+ }
+
+ for (auto local_log = log_files_.begin(); local_log != log_files_.end();) {
+ local_log = CloseLogFile(local_log);
+ }
+
+ base_path_.clear(); // Marks local-logging as disabled.
+ max_log_file_size_bytes_ = kDefaultMaxLocalLogFileSizeBytes;
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::EventLogWrite(const PeerConnectionKey& key,
+ const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+ auto it = log_files_.find(key);
+ if (it == log_files_.end()) {
+ return false;
+ }
+
+ const bool write_successful = it->second->Write(message);
+
+ if (!write_successful || it->second->MaxSizeReached()) {
+ CloseLogFile(it);
+ }
+
+ return write_successful;
+}
+
+void WebRtcLocalEventLogManager::RenderProcessHostExitedDestroyed(
+ int render_process_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ // Remove all of the peer connections associated with this render process.
+ auto pc_it = active_peer_connections_.begin();
+ while (pc_it != active_peer_connections_.end()) {
+ if (pc_it->render_process_id == render_process_id) {
+ pc_it = active_peer_connections_.erase(pc_it);
+ } else {
+ ++pc_it;
+ }
+ }
+
+ // Close all of the files that were associated with peer connections which
+ // belonged to this render process.
+ auto log_it = log_files_.begin();
+ while (log_it != log_files_.end()) {
+ if (log_it->first.render_process_id == render_process_id) {
+ log_it = CloseLogFile(log_it);
+ } else {
+ ++log_it;
+ }
+ }
+}
+
+void WebRtcLocalEventLogManager::SetClockForTesting(base::Clock* clock) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+ clock_for_testing_ = clock;
+}
+
+void WebRtcLocalEventLogManager::StartLogFile(const PeerConnectionKey& key) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+ DCHECK(log_files_.find(key) == log_files_.end());
+
+ // Add some information to the name given by the caller.
+ base::FilePath file_path = GetFilePath(base_path_, key);
+ CHECK(!file_path.empty()) << "Couldn't set path for local WebRTC log file.";
+
+ // In the unlikely case that this filename is already taken, find a unique
+ // number to append to the filename, if possible.
+ file_path = base::GetUniquePath(file_path);
+ if (file_path.empty()) {
+ return; // No available file path was found.
+ }
+
+ auto log_file =
+ log_file_writer_factory_.Create(file_path, max_log_file_size_bytes_);
+ if (!log_file) {
+ LOG(WARNING) << "Couldn't create and/or open local WebRTC event log file.";
+ return;
+ }
+
+ const auto it = log_files_.emplace(key, std::move(log_file));
+ DCHECK(it.second);
+
+ // The observer needs to be able to run on any TaskQueue.
+ if (observer_) {
+ LogFilesMap::iterator map_iter = it.first;
+ // map_iter->second is a std::unique_ptr<LogFileWriter>.
+ observer_->OnLocalLogStarted(key, map_iter->second->path());
+ }
+}
+
+WebRtcLocalEventLogManager::LogFilesMap::iterator
+WebRtcLocalEventLogManager::CloseLogFile(LogFilesMap::iterator it) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ const PeerConnectionKey peer_connection = it->first;
+
+ it->second->Close();
+ it = log_files_.erase(it);
+
+ if (observer_) {
+ observer_->OnLocalLogStopped(peer_connection);
+ }
+
+ return it;
+}
+
+base::FilePath WebRtcLocalEventLogManager::GetFilePath(
+ const base::FilePath& base_path,
+ const PeerConnectionKey& key) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ base::Time::Exploded now;
+ if (clock_for_testing_) {
+ clock_for_testing_->Now().LocalExplode(&now);
+ } else {
+ base::Time::Now().LocalExplode(&now);
+ }
+
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ char stamp[100];
+ int written =
+ base::snprintf(stamp, base::size(stamp), "%04d%02d%02d_%02d%02d_%d_%d",
+ now.year, now.month, now.day_of_month, now.hour,
+ now.minute, key.render_process_id, key.lid);
+ CHECK_GT(written, 0);
+ CHECK_LT(static_cast<size_t>(written), base::size(stamp));
+
+ return base_path.InsertBeforeExtension(FILE_PATH_LITERAL("_"))
+ .AddExtension(log_file_writer_factory_.Extension())
+ .InsertBeforeExtensionASCII(base::StringPiece(stamp));
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
new file mode 100644
index 00000000000..6ebf3e49185
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
@@ -0,0 +1,98 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_LOCAL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_LOCAL_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/time/clock.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+namespace webrtc_event_logging {
+
+class WebRtcLocalEventLogManager final {
+ using LogFilesMap =
+ std::map<WebRtcEventLogPeerConnectionKey, std::unique_ptr<LogFileWriter>>;
+ using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+
+ public:
+ explicit WebRtcLocalEventLogManager(WebRtcLocalEventLogsObserver* observer);
+ ~WebRtcLocalEventLogManager();
+
+ bool PeerConnectionAdded(const PeerConnectionKey& key);
+ bool PeerConnectionRemoved(const PeerConnectionKey& key);
+
+ bool EnableLogging(const base::FilePath& base_path,
+ size_t max_file_size_bytes);
+ bool DisableLogging();
+
+ bool EventLogWrite(const PeerConnectionKey& key, const std::string& message);
+
+ void RenderProcessHostExitedDestroyed(int render_process_id);
+
+ // This function is public, but this entire class is a protected
+ // implementation detail of WebRtcEventLogManager, which hides this
+ // function from everybody except its own unit tests.
+ void SetClockForTesting(base::Clock* clock);
+
+ private:
+ // Create a local log file.
+ void StartLogFile(const PeerConnectionKey& key);
+
+ // Closes an active log file.
+ // Returns an iterator to the next active log file.
+ LogFilesMap::iterator CloseLogFile(LogFilesMap::iterator it);
+
+ // Derives the name of a local log file. The format is:
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ base::FilePath GetFilePath(const base::FilePath& base_path,
+ const PeerConnectionKey& key) const;
+
+ // This object is expected to be created and destroyed on the UI thread,
+ // but live on its owner's internal, IO-capable task queue.
+ SEQUENCE_CHECKER(io_task_sequence_checker_);
+
+ // Produces LogFileWriter instances, for writing the logs to files.
+ BaseLogFileWriterFactory log_file_writer_factory_;
+
+ // Observer which will be informed whenever a local log file is started or
+ // stopped. Through this, the owning WebRtcEventLogManager can be informed,
+ // and decide whether it wants to turn notifications from WebRTC on/off.
+ WebRtcLocalEventLogsObserver* const observer_;
+
+ // For unit tests only, and specifically for unit tests that verify the
+ // filename format (derived from the current time as well as the renderer PID
+ // and PeerConnection local ID), we want to make sure that the time and date
+ // cannot change between the time the clock is read by the unit under test
+ // (namely WebRtcEventLogManager) and the time it's read by the test.
+ base::Clock* clock_for_testing_;
+
+ // Currently active peer connections. PeerConnections which have been closed
+ // are not considered active, regardless of whether they have been torn down.
+ std::set<PeerConnectionKey> active_peer_connections_;
+
+ // Local log files, stored at the behest of the user (via WebRTCInternals).
+ LogFilesMap log_files_;
+
+ // If |base_path_| is empty, local logging is disabled.
+ // If nonempty, local logging is enabled, and all local logs will be saved
+ // to this directory.
+ base::FilePath base_path_;
+
+ // The maximum size for local logs, in bytes.
+ // If !has_value(), the value is unlimited.
+ base::Optional<size_t> max_log_file_size_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLocalEventLogManager);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_LOCAL_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
new file mode 100644
index 00000000000..a69b8d02b3a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
@@ -0,0 +1,1376 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h"
+
+#include <algorithm>
+#include <iterator>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace webrtc_event_logging {
+
+// TODO(crbug.com/775415): Change max back to (1u << 29) after resolving the
+// issue where we read the entire file into memory.
+const size_t kMaxRemoteLogFileSizeBytes = 50000000u;
+
+const int kDefaultOutputPeriodMs = 5000;
+const int kMaxOutputPeriodMs = 60000;
+
+namespace {
+const base::TimeDelta kDefaultProactivePruningDelta =
+ base::TimeDelta::FromMinutes(5);
+
+const base::TimeDelta kDefaultWebRtcRemoteEventLogUploadDelay =
+ base::TimeDelta::FromSeconds(30);
+
+// Because history files are rarely used, their existence is not kept in memory.
+// That means that pruning them involves inspecting data on disk. This is not
+// terribly cheap (up to kMaxWebRtcEventLogHistoryFiles files per profile), and
+// should therefore be done somewhat infrequently.
+const base::TimeDelta kProactiveHistoryFilesPruneDelta =
+ base::TimeDelta::FromMinutes(30);
+
+base::TimeDelta GetProactivePendingLogsPruneDelta() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kWebRtcRemoteEventLogProactivePruningDelta)) {
+ const std::string delta_seconds_str =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ ::switches::kWebRtcRemoteEventLogProactivePruningDelta);
+ int64_t seconds;
+ if (base::StringToInt64(delta_seconds_str, &seconds) && seconds >= 0) {
+ return base::TimeDelta::FromSeconds(seconds);
+ } else {
+ LOG(WARNING) << "Proactive pruning delta could not be parsed.";
+ }
+ }
+
+ return kDefaultProactivePruningDelta;
+}
+
+base::TimeDelta GetUploadDelay() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs)) {
+ const std::string delta_seconds_str =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs);
+ int64_t ms;
+ if (base::StringToInt64(delta_seconds_str, &ms) && ms >= 0) {
+ return base::TimeDelta::FromMilliseconds(ms);
+ } else {
+ LOG(WARNING) << "Upload delay could not be parsed; using default delay.";
+ }
+ }
+
+ return kDefaultWebRtcRemoteEventLogUploadDelay;
+}
+
+bool TimePointInRange(const base::Time& time_point,
+ const base::Time& range_begin,
+ const base::Time& range_end) {
+ DCHECK(!time_point.is_null());
+ DCHECK(range_begin.is_null() || range_end.is_null() ||
+ range_begin <= range_end);
+ return (range_begin.is_null() || range_begin <= time_point) &&
+ (range_end.is_null() || time_point < range_end);
+}
+
+// Do not attempt to upload when there is no active connection.
+// Do not attempt to upload if the connection is known to be a mobile one.
+// Note #1: A device may have multiple connections, so this is not bullet-proof.
+// Note #2: Does not attempt to recognize mobile hotspots.
+bool UploadSupportedUsingConnectionType(
+ network::mojom::ConnectionType connection) {
+ return connection != network::mojom::ConnectionType::CONNECTION_NONE &&
+ connection != network::mojom::ConnectionType::CONNECTION_2G &&
+ connection != network::mojom::ConnectionType::CONNECTION_3G &&
+ connection != network::mojom::ConnectionType::CONNECTION_4G;
+}
+
+// Produce a history file for a given file.
+void CreateHistoryFile(const base::FilePath& log_file_path,
+ const base::Time& capture_time) {
+ std::unique_ptr<WebRtcEventLogHistoryFileWriter> writer =
+ WebRtcEventLogHistoryFileWriter::Create(
+ GetWebRtcEventLogHistoryFilePath(log_file_path));
+ if (!writer) {
+ LOG(ERROR) << "Could not create history file.";
+ return;
+ }
+
+ if (!writer->WriteCaptureTime(capture_time)) {
+ LOG(ERROR) << "Could not write capture time to history file.";
+ writer->Delete();
+ return;
+ }
+}
+
+// The following is a list of entry types used to transmit information
+// from GetHistory() to the caller (normally - the UI).
+// Each entry is of type UploadList::UploadInfo. Depending on the entry
+// type, the fields in the UploadInfo have different values:
+// 1+2. Currently-being-captured or pending -> State::Pending && !upload_time.
+// 3. Currently-being-uploaded -> State::Pending && upload_time.
+// 4. Pruned before being uploaded -> State::NotUploaded && !upload_time.
+// 5. Unsuccessful upload attempt -> State::NotUploaded && upload_time.
+// 6. Successfully uploaded -> State::Uploaded.
+//
+// As for the meaning of the local_id field, its semantics change according to
+// the above entry type.
+// * For cases 1-3 above, it is the filename, since the log is still on disk.
+// * For cases 5-6 above, it is the local log ID that the now-deleted file used
+// * to have.
+namespace history {
+UploadList::UploadInfo CreateActivelyCapturedLogEntry(
+ const base::FilePath& path,
+ const base::Time& capture_time) {
+ using State = UploadList::UploadInfo::State;
+ const std::string filename = path.BaseName().MaybeAsASCII();
+ DCHECK(!filename.empty());
+ return UploadList::UploadInfo(std::string(), base::Time(), filename,
+ capture_time, State::Pending);
+}
+
+UploadList::UploadInfo CreatePendingLogEntry(
+ const WebRtcLogFileInfo& log_info) {
+ using State = UploadList::UploadInfo::State;
+ const std::string filename = log_info.path.BaseName().MaybeAsASCII();
+ DCHECK(!filename.empty());
+ return UploadList::UploadInfo(std::string(), base::Time(), filename,
+ log_info.last_modified, State::Pending);
+}
+
+UploadList::UploadInfo CreateActivelyUploadedLogEntry(
+ const WebRtcLogFileInfo& log_info,
+ const base::Time& upload_time) {
+ using State = UploadList::UploadInfo::State;
+ const std::string filename = log_info.path.BaseName().MaybeAsASCII();
+ DCHECK(!filename.empty());
+ return UploadList::UploadInfo(std::string(), upload_time, filename,
+ log_info.last_modified, State::Pending);
+}
+
+UploadList::UploadInfo CreateEntryFromHistoryFileReader(
+ const WebRtcEventLogHistoryFileReader& reader) {
+ using State = UploadList::UploadInfo::State;
+ const auto state =
+ reader.UploadId().empty() ? State::NotUploaded : State::Uploaded;
+ return UploadList::UploadInfo(reader.UploadId(), reader.UploadTime(),
+ reader.LocalId(), reader.CaptureTime(), state);
+}
+} // namespace history
+} // namespace
+
+const size_t kMaxActiveRemoteBoundWebRtcEventLogs = 3;
+const size_t kMaxPendingRemoteBoundWebRtcEventLogs = 5;
+static_assert(kMaxActiveRemoteBoundWebRtcEventLogs <=
+ kMaxPendingRemoteBoundWebRtcEventLogs,
+ "This assumption affects unit test coverage.");
+const size_t kMaxWebRtcEventLogHistoryFiles = 50;
+
+// Maximum time to keep remote-bound logs on disk.
+const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention =
+ base::TimeDelta::FromDays(7);
+
+// Maximum time to keep history files on disk. These serve to display an upload
+// on chrome://webrtc-logs/. It is persisted for longer than the log itself.
+const base::TimeDelta kHistoryFileRetention = base::TimeDelta::FromDays(30);
+
+WebRtcRemoteEventLogManager::WebRtcRemoteEventLogManager(
+ WebRtcRemoteEventLogsObserver* observer,
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : upload_suppression_disabled_(
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kWebRtcRemoteEventLogUploadNoSuppression)),
+ upload_delay_(GetUploadDelay()),
+ proactive_pending_logs_prune_delta_(GetProactivePendingLogsPruneDelta()),
+ proactive_prune_scheduling_started_(false),
+ observer_(observer),
+ network_connection_tracker_(nullptr),
+ uploading_supported_for_connection_type_(false),
+ scheduled_upload_tasks_(0),
+ uploader_factory_(
+ std::make_unique<WebRtcEventLogUploaderImpl::Factory>()),
+ task_runner_(task_runner),
+ weak_ptr_factory_(
+ std::make_unique<base::WeakPtrFactory<WebRtcRemoteEventLogManager>>(
+ this)) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // Proactive pruning would not do anything at the moment; it will be started
+ // with the first enabled browser context. This will all have the benefit
+ // of doing so on |task_runner_| rather than the UI thread.
+}
+
+WebRtcRemoteEventLogManager::~WebRtcRemoteEventLogManager() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // TODO(crbug.com/775415): Purge from disk files which were being uploaded
+ // while destruction took place, thereby avoiding endless attempts to upload
+ // the same file.
+
+ if (weak_ptr_factory_) {
+ // Not a unit test; that would have gone through ShutDownForTesting().
+ const bool will_delete =
+ task_runner_->DeleteSoon(FROM_HERE, weak_ptr_factory_.release());
+ DCHECK(!will_delete)
+ << "Task runners must have been stopped by this stage of shutdown.";
+ }
+
+ if (network_connection_tracker_) {
+ // * |network_connection_tracker_| might already have posted a task back
+ // to us, but it will not run, because |task_runner_| has already been
+ // stopped.
+ // * RemoveNetworkConnectionObserver() should generally be called on the
+ // same thread as AddNetworkConnectionObserver(), but in this case it's
+ // okay to remove on a separate thread, because this only happens during
+ // Chrome shutdown, when no others tasks are running; there can be no
+ // concurrently executing notification from the tracker.
+ network_connection_tracker_->RemoveNetworkConnectionObserver(this);
+ }
+}
+
+void WebRtcRemoteEventLogManager::SetNetworkConnectionTracker(
+ network::NetworkConnectionTracker* network_connection_tracker) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(network_connection_tracker);
+ DCHECK(!network_connection_tracker_);
+
+ // |this| is only destroyed (on the UI thread) after |task_runner_| stops,
+ // so AddNetworkConnectionObserver() is safe.
+
+ network_connection_tracker_ = network_connection_tracker;
+ network_connection_tracker_->AddNetworkConnectionObserver(this);
+
+ auto callback =
+ base::BindOnce(&WebRtcRemoteEventLogManager::OnConnectionChanged,
+ weak_ptr_factory_->GetWeakPtr());
+ network::mojom::ConnectionType connection_type;
+ const bool sync_answer = network_connection_tracker_->GetConnectionType(
+ &connection_type, std::move(callback));
+
+ if (sync_answer) {
+ OnConnectionChanged(connection_type);
+ }
+
+ // Because this happens while enabling the first browser context, there is no
+ // necessity to consider uploading yet.
+ DCHECK_EQ(enabled_browser_contexts_.size(), 0u);
+}
+
+void WebRtcRemoteEventLogManager::SetLogFileWriterFactory(
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(log_file_writer_factory);
+ DCHECK(!log_file_writer_factory_);
+ log_file_writer_factory_ = std::move(log_file_writer_factory);
+}
+
+void WebRtcRemoteEventLogManager::EnableForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(network_connection_tracker_)
+ << "SetNetworkConnectionTracker not called.";
+ DCHECK(log_file_writer_factory_) << "SetLogFileWriterFactory() not called.";
+ DCHECK(!BrowserContextEnabled(browser_context_id)) << "Already enabled.";
+
+ const base::FilePath remote_bound_logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
+ if (!MaybeCreateLogsDirectory(remote_bound_logs_dir)) {
+ LOG(WARNING)
+ << "WebRtcRemoteEventLogManager couldn't create logs directory.";
+ return;
+ }
+
+ enabled_browser_contexts_.emplace(browser_context_id, remote_bound_logs_dir);
+
+ LoadLogsDirectory(browser_context_id, remote_bound_logs_dir);
+
+ if (!proactive_prune_scheduling_started_) {
+ proactive_prune_scheduling_started_ = true;
+
+ if (!proactive_pending_logs_prune_delta_.is_zero()) {
+ RecurringlyPrunePendingLogs();
+ }
+
+ RecurringlyPruneHistoryFiles();
+ }
+}
+
+void WebRtcRemoteEventLogManager::DisableForBrowserContext(
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!BrowserContextEnabled(browser_context_id)) {
+ return; // Enabling may have failed due to lacking permissions.
+ }
+
+ enabled_browser_contexts_.erase(browser_context_id);
+
+#if DCHECK_IS_ON()
+ // DisableForBrowserContext() is called in one of two cases:
+ // 1. If Chrome is shutting down. In that case, all the RPHs associated with
+ // this BrowserContext must already have exited, which should have
+ // implicitly stopped all active logs.
+ // 2. Remote-bound logging is no longer allowed for this BrowserContext.
+ // In that case, some peer connections associated with this BrowserContext
+ // might still be active, or become active at a later time, but all
+ // logs must have already been stopped.
+ auto pred = [browser_context_id](decltype(active_logs_)::value_type& log) {
+ return log.first.browser_context_id == browser_context_id;
+ };
+ DCHECK(std::count_if(active_logs_.begin(), active_logs_.end(), pred) == 0u);
+#endif
+
+ // Pending logs for this BrowserContext are no longer eligible for upload.
+ for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
+ if (it->browser_context_id == browser_context_id) {
+ it = pending_logs_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // Active uploads of logs associated with this BrowserContext must be stopped.
+ MaybeCancelUpload(base::Time::Min(), base::Time::Max(), browser_context_id);
+
+ // Active logs may have been removed, which could remove upload suppression,
+ // or pending logs which were about to be uploaded may have been removed,
+ // so uploading may no longer be possible.
+ ManageUploadSchedule();
+}
+
+bool WebRtcRemoteEventLogManager::PeerConnectionAdded(
+ const PeerConnectionKey& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Infrequent event - good opportunity to prune.
+
+ const auto result = active_peer_connections_.emplace(key, std::string());
+
+ // An upload about to start might need to be suppressed.
+ ManageUploadSchedule();
+
+ return result.second;
+}
+
+bool WebRtcRemoteEventLogManager::PeerConnectionRemoved(
+ const PeerConnectionKey& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Infrequent event - good opportunity to prune.
+
+ const auto peer_connection = active_peer_connections_.find(key);
+ if (peer_connection == active_peer_connections_.end()) {
+ return false;
+ }
+
+ MaybeStopRemoteLogging(key);
+
+ active_peer_connections_.erase(peer_connection);
+
+ ManageUploadSchedule(); // Suppression might have been removed.
+
+ return true;
+}
+
+bool WebRtcRemoteEventLogManager::PeerConnectionSessionIdSet(
+ const PeerConnectionKey& key,
+ const std::string& session_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Infrequent event - good opportunity to prune.
+
+ if (session_id.empty()) {
+ LOG(ERROR) << "Empty session ID.";
+ return false;
+ }
+
+ auto peer_connection = active_peer_connections_.find(key);
+ if (peer_connection == active_peer_connections_.end()) {
+ return false; // Unknown peer connection; already closed?
+ }
+
+ if (!peer_connection->second.empty()) {
+ LOG(ERROR) << "Session ID already set.";
+ return false;
+ }
+
+ peer_connection->second = session_id;
+
+ return true;
+}
+
+bool WebRtcRemoteEventLogManager::StartRemoteLogging(
+ int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id,
+ std::string* error_message) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(log_id);
+ DCHECK(log_id->empty());
+ DCHECK(error_message);
+ DCHECK(error_message->empty());
+
+ if (output_period_ms < 0) {
+ output_period_ms = kDefaultOutputPeriodMs;
+ }
+
+ if (!AreLogParametersValid(max_file_size_bytes, output_period_ms, web_app_id,
+ error_message)) {
+ // |error_message| will have been set by AreLogParametersValid().
+ DCHECK(!error_message->empty()) << "AreLogParametersValid() reported an "
+ "error without an error message.";
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kInvalidArguments);
+ return false;
+ }
+
+ if (session_id.empty()) {
+ *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIllegalSessionId);
+ return false;
+ }
+
+ if (!BrowserContextEnabled(browser_context_id)) {
+ // Remote-bound event logging has either not yet been enabled for this
+ // BrowserContext, or has been recently disabled. This error should not
+ // really be reached, barring a timing issue.
+ *error_message = kStartRemoteLoggingFailureLoggingDisabledBrowserContext;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kDisabledBrowserContext);
+ return false;
+ }
+
+ PeerConnectionKey key;
+ if (!FindPeerConnection(render_process_id, session_id, &key)) {
+ *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kUnknownOrInvalidPeerConnection);
+ return false;
+ }
+
+ // May not restart active remote logs.
+ auto it = active_logs_.find(key);
+ if (it != active_logs_.end()) {
+ LOG(ERROR) << "Remote logging already underway for " << session_id << ".";
+ *error_message = kStartRemoteLoggingFailureAlreadyLogging;
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kAlreadyLogging);
+ return false;
+ }
+
+ // This is a good opportunity to prune the list of pending logs, potentially
+ // making room for this file.
+ PrunePendingLogs();
+
+ if (!AdditionalActiveLogAllowed(key.browser_context_id)) {
+ *error_message = kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kNoAdditionalLogsAllowed);
+ return false;
+ }
+
+ return StartWritingLog(key, browser_context_dir, max_file_size_bytes,
+ output_period_ms, web_app_id, log_id, error_message);
+}
+
+bool WebRtcRemoteEventLogManager::EventLogWrite(const PeerConnectionKey& key,
+ const std::string& message) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ auto it = active_logs_.find(key);
+ if (it == active_logs_.end()) {
+ return false;
+ }
+
+ const bool write_successful = it->second->Write(message);
+ if (!write_successful || it->second->MaxSizeReached()) {
+ // Note: If the file is invalid, CloseLogFile() will discard it.
+ CloseLogFile(it, /*make_pending=*/true);
+ ManageUploadSchedule();
+ }
+
+ return write_successful;
+}
+
+void WebRtcRemoteEventLogManager::ClearCacheForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ // Rationale for the order:
+ // 1. Active logs cancelled. This has no side effects, and can be safely
+ // done before anything else.
+ // 2. Pending logs removed, before they can be considered as the
+ // next log to be uploaded. This may cause history files to be created.
+ // 3. Remove history files, including those that #2 might have created.
+ // 4. Cancel any active upload precisely at a time when nothing being cleared
+ // by ClearCacheForBrowserContext() could accidentally replace it.
+ // 5. Explicitly consider uploading, now that things have changed.
+ MaybeCancelActiveLogs(delete_begin, delete_end, browser_context_id);
+ MaybeRemovePendingLogs(delete_begin, delete_end, browser_context_id,
+ /*is_cache_clear=*/true);
+ MaybeRemoveHistoryFiles(delete_begin, delete_end, browser_context_id);
+ MaybeCancelUpload(delete_begin, delete_end, browser_context_id);
+ ManageUploadSchedule();
+}
+
+void WebRtcRemoteEventLogManager::GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ std::vector<UploadList::UploadInfo> history;
+
+ if (!BrowserContextEnabled(browser_context_id)) {
+ LOG(ERROR) << "Unknown |browser_context_id|.";
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(reply), history));
+ return;
+ }
+
+ PrunePendingLogs(browser_context_id);
+
+ const base::Time now = base::Time::Now();
+
+ std::set<WebRtcEventLogHistoryFileReader> history_files =
+ PruneAndLoadHistoryFilesForBrowserContext(
+ base::Time::Min(), now - kHistoryFileRetention, browser_context_id);
+ for (const auto& history_file : history_files) {
+ history.push_back(history::CreateEntryFromHistoryFileReader(history_file));
+ }
+
+ for (const WebRtcLogFileInfo& log_info : pending_logs_) {
+ if (browser_context_id == log_info.browser_context_id) {
+ history.push_back(history::CreatePendingLogEntry(log_info));
+ }
+ }
+
+ for (const auto& it : active_logs_) {
+ if (browser_context_id == it.first.browser_context_id) {
+ history.push_back(
+ history::CreateActivelyCapturedLogEntry(it.second->path(), now));
+ }
+ }
+
+ if (uploader_) {
+ const WebRtcLogFileInfo log_info = uploader_->GetWebRtcLogFileInfo();
+ if (browser_context_id == log_info.browser_context_id) {
+ history.push_back(history::CreateActivelyUploadedLogEntry(log_info, now));
+ }
+ }
+
+ // Sort according to capture time, for consistent orders regardless of
+ // future operations on the log files.
+ auto cmp = [](const UploadList::UploadInfo& lhs,
+ const UploadList::UploadInfo& rhs) {
+ if (lhs.capture_time == rhs.capture_time) {
+ // Resolve ties arbitrarily, but consistently. (Local ID expected to be
+ // distinct for distinct items; if not, anything goes.)
+ return lhs.local_id < rhs.local_id;
+ }
+ return (lhs.capture_time < rhs.capture_time);
+ };
+ std::sort(history.begin(), history.end(), cmp);
+
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(reply), history));
+}
+
+void WebRtcRemoteEventLogManager::RemovePendingLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!BrowserContextEnabled(browser_context_id));
+ const base::FilePath remote_bound_logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
+ if (!base::DeleteFile(remote_bound_logs_dir, /*recursive=*/true)) {
+ LOG(ERROR) << "Failed to delete `" << remote_bound_logs_dir << ".";
+ }
+}
+
+void WebRtcRemoteEventLogManager::RenderProcessHostExitedDestroyed(
+ int render_process_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Remove all of the peer connections associated with this render process.
+ // It's important to do this before closing the actual files, because closing
+ // files can trigger a new upload if no active peer connections are present.
+ auto pc_it = active_peer_connections_.begin();
+ while (pc_it != active_peer_connections_.end()) {
+ if (pc_it->first.render_process_id == render_process_id) {
+ pc_it = active_peer_connections_.erase(pc_it);
+ } else {
+ ++pc_it;
+ }
+ }
+
+ // Close all of the files that were associated with peer connections which
+ // belonged to this render process.
+ auto log_it = active_logs_.begin();
+ while (log_it != active_logs_.end()) {
+ if (log_it->first.render_process_id == render_process_id) {
+ log_it = CloseLogFile(log_it, /*make_pending=*/true);
+ } else {
+ ++log_it;
+ }
+ }
+
+ ManageUploadSchedule();
+}
+
+void WebRtcRemoteEventLogManager::OnConnectionChanged(
+ network::mojom::ConnectionType type) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ // Even if switching from WiFi to Ethernet, or between to WiFi connections,
+ // reset the timer (if running) until an upload is permissible due to stable
+ // upload-supporting conditions.
+ time_when_upload_conditions_met_ = base::TimeTicks();
+
+ uploading_supported_for_connection_type_ =
+ UploadSupportedUsingConnectionType(type);
+
+ ManageUploadSchedule();
+
+ // TODO(crbug.com/775415): Support pausing uploads when connection goes down,
+ // or switches to an unsupported connection type.
+}
+
+void WebRtcRemoteEventLogManager::SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(uploader_factory);
+ uploader_factory_ = std::move(uploader_factory);
+}
+
+void WebRtcRemoteEventLogManager::UploadConditionsHoldForTesting(
+ base::OnceCallback<void(bool)> callback) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(callback), UploadConditionsHold()));
+}
+
+void WebRtcRemoteEventLogManager::ShutDownForTesting(base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ weak_ptr_factory_->InvalidateWeakPtrs();
+ weak_ptr_factory_.reset();
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(reply)));
+}
+
+bool WebRtcRemoteEventLogManager::AreLogParametersValid(
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* error_message) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize) {
+ LOG(WARNING) << "Unlimited file sizes not allowed for remote-bound logs.";
+ *error_message = kStartRemoteLoggingFailureUnlimitedSizeDisallowed;
+ return false;
+ }
+
+ if (max_file_size_bytes < log_file_writer_factory_->MinFileSizeBytes()) {
+ LOG(WARNING) << "File size below minimum allowed.";
+ *error_message = kStartRemoteLoggingFailureMaxSizeTooSmall;
+ return false;
+ }
+
+ if (max_file_size_bytes > kMaxRemoteLogFileSizeBytes) {
+ LOG(WARNING) << "File size exceeds maximum allowed.";
+ *error_message = kStartRemoteLoggingFailureMaxSizeTooLarge;
+ return false;
+ }
+
+ if (output_period_ms > kMaxOutputPeriodMs) {
+ LOG(WARNING) << "Output period (ms) exceeds maximum allowed.";
+ *error_message = kStartRemoteLoggingFailureOutputPeriodMsTooLarge;
+ return false;
+ }
+
+ if (web_app_id < kMinWebRtcEventLogWebAppId ||
+ web_app_id > kMaxWebRtcEventLogWebAppId) {
+ LOG(WARNING) << "Illegal web-app identifier.";
+ *error_message = kStartRemoteLoggingFailureIllegalWebAppId;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcRemoteEventLogManager::BrowserContextEnabled(
+ BrowserContextId browser_context_id) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ const auto it = enabled_browser_contexts_.find(browser_context_id);
+ return it != enabled_browser_contexts_.cend();
+}
+
+WebRtcRemoteEventLogManager::LogFilesMap::iterator
+WebRtcRemoteEventLogManager::CloseLogFile(LogFilesMap::iterator it,
+ bool make_pending) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const PeerConnectionKey peer_connection = it->first; // Copy, not reference.
+
+ const bool valid_file = it->second->Close();
+ if (valid_file) {
+ if (make_pending) {
+ // The current time is a good enough approximation of the file's last
+ // modification time.
+ const base::Time last_modified = base::Time::Now();
+
+ // The stopped log becomes a pending log.
+ const auto emplace_result =
+ pending_logs_.emplace(peer_connection.browser_context_id,
+ it->second->path(), last_modified);
+ DCHECK(emplace_result.second); // No pre-existing entry.
+ } else {
+ const base::FilePath log_file_path = it->second->path();
+ if (!base::DeleteFile(log_file_path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << log_file_path << ".";
+ }
+ }
+ } else { // !valid_file
+ // Close() deleted the file.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileWriteError);
+ }
+
+ it = active_logs_.erase(it);
+
+ if (observer_) {
+ observer_->OnRemoteLogStopped(peer_connection);
+ }
+
+ return it;
+}
+
+bool WebRtcRemoteEventLogManager::MaybeCreateLogsDirectory(
+ const base::FilePath& remote_bound_logs_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (base::PathExists(remote_bound_logs_dir)) {
+ if (!base::DirectoryExists(remote_bound_logs_dir)) {
+ LOG(ERROR) << "Path for remote-bound logs is taken by a non-directory.";
+ return false;
+ }
+ } else if (!base::CreateDirectory(remote_bound_logs_dir)) {
+ LOG(ERROR) << "Failed to create the local directory for remote-bound logs.";
+ return false;
+ }
+
+ // TODO(crbug.com/775415): Test for appropriate permissions.
+
+ return true;
+}
+
+void WebRtcRemoteEventLogManager::LoadLogsDirectory(
+ BrowserContextId browser_context_id,
+ const base::FilePath& remote_bound_logs_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const auto separator =
+ base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
+ const base::Time now = base::Time::Now();
+
+ std::set<std::pair<base::FilePath, base::Time>> log_files_to_delete;
+ std::set<base::FilePath> history_files_to_delete;
+
+ // Iterate over all of the files in the directory; find the ones that need
+ // to be deleted. Skip unknown files; they may belong to the OS.
+ base::FileEnumerator enumerator(remote_bound_logs_dir,
+ /*recursive=*/false,
+ base::FileEnumerator::FILES);
+ for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
+ const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+ const base::FilePath::StringType extension = info.GetName().Extension();
+ if (extension == separator + kWebRtcEventLogUncompressedExtension ||
+ extension == separator + kWebRtcEventLogGzippedExtension) {
+ const bool loaded = LoadPendingLogInfo(
+ browser_context_id, path, enumerator.GetInfo().GetLastModifiedTime());
+ if (!loaded) {
+ log_files_to_delete.insert(
+ std::make_pair(path, info.GetLastModifiedTime()));
+ }
+ } else if (extension == separator + kWebRtcEventLogHistoryExtension) {
+ auto reader = LoadHistoryFile(browser_context_id, path, base::Time::Min(),
+ now - kHistoryFileRetention);
+ if (!reader) {
+ history_files_to_delete.insert(path);
+ }
+ }
+ }
+
+ // Remove expired logs.
+ for (const auto& file_to_delete : log_files_to_delete) {
+ // Produce history file, unless we're discarding this log file precisely
+ // because we see it has a history file associated.
+ const base::FilePath& log_file_path = file_to_delete.first;
+ if (!base::PathExists(GetWebRtcEventLogHistoryFilePath(log_file_path))) {
+ const base::Time capture_time = file_to_delete.second;
+ CreateHistoryFile(log_file_path, capture_time);
+ }
+
+ // Remove the log file itself.
+ if (!base::DeleteFile(log_file_path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << file_to_delete.first << ".";
+ }
+ }
+
+ // Remove expired history files.
+ for (const base::FilePath& history_file_path : history_files_to_delete) {
+ if (!base::DeleteFile(history_file_path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << history_file_path << ".";
+ }
+ }
+
+ ManageUploadSchedule();
+}
+
+bool WebRtcRemoteEventLogManager::LoadPendingLogInfo(
+ BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ base::Time last_modified) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!IsValidRemoteBoundLogFilePath(path)) {
+ return false;
+ }
+
+ const base::FilePath history_path = GetWebRtcEventLogHistoryFilePath(path);
+ if (base::PathExists(history_path)) {
+ // Log file has associated history file, indicating an upload was started
+ // for it. We should delete the original log from disk.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kIncompletePastUpload);
+ return false;
+ }
+
+ const base::Time now = base::Time::Now();
+ if (last_modified + kRemoteBoundWebRtcEventLogsMaxRetention < now) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kExpiredLogFileAtChromeStart);
+ return false;
+ }
+
+ auto it = pending_logs_.emplace(browser_context_id, path, last_modified);
+ DCHECK(it.second); // No pre-existing entry.
+
+ return true;
+}
+
+std::unique_ptr<WebRtcEventLogHistoryFileReader>
+WebRtcRemoteEventLogManager::LoadHistoryFile(
+ BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ const base::Time& prune_begin,
+ const base::Time& prune_end) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!IsValidRemoteBoundLogFilePath(path)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<WebRtcEventLogHistoryFileReader> reader =
+ WebRtcEventLogHistoryFileReader::Create(path);
+ if (!reader) {
+ return nullptr;
+ }
+
+ const base::Time capture_time = reader->CaptureTime();
+ if (prune_begin <= capture_time && capture_time <= prune_end) {
+ return nullptr;
+ }
+
+ const base::Time upload_time = reader->UploadTime();
+ if (!upload_time.is_null()) {
+ if (prune_begin <= upload_time && upload_time <= prune_end) {
+ return nullptr;
+ }
+ }
+
+ return reader;
+}
+
+std::set<WebRtcEventLogHistoryFileReader>
+WebRtcRemoteEventLogManager::PruneAndLoadHistoryFilesForBrowserContext(
+ const base::Time& prune_begin,
+ const base::Time& prune_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ std::set<WebRtcEventLogHistoryFileReader> history_files;
+
+ auto browser_contexts_it = enabled_browser_contexts_.find(browser_context_id);
+ if (browser_contexts_it == enabled_browser_contexts_.end()) {
+ return history_files;
+ }
+
+ std::set<base::FilePath> files_to_delete;
+
+ base::FileEnumerator enumerator(browser_contexts_it->second,
+ /*recursive=*/false,
+ base::FileEnumerator::FILES);
+
+ for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
+ const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+ const base::FilePath::StringType extension = info.GetName().Extension();
+ const auto separator =
+ base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
+ if (extension != separator + kWebRtcEventLogHistoryExtension) {
+ continue;
+ }
+
+ if (uploader_) {
+ const base::FilePath log_path = uploader_->GetWebRtcLogFileInfo().path;
+ const base::FilePath history_path =
+ GetWebRtcEventLogHistoryFilePath(log_path);
+ if (path == history_path) {
+ continue;
+ }
+ }
+
+ auto reader =
+ LoadHistoryFile(browser_context_id, path, prune_begin, prune_end);
+ if (reader) {
+ history_files.insert(std::move(*reader));
+ reader.reset(); // |reader| in undetermined state after move().
+ } else { // Defective or expired.
+ files_to_delete.insert(path);
+ }
+ }
+
+ // |history_files| is sorted by log capture time in ascending order;
+ // remove the oldest entries until kMaxWebRtcEventLogHistoryFiles is obeyed.
+ size_t num_history_files = history_files.size();
+ for (auto it = history_files.begin();
+ num_history_files > kMaxWebRtcEventLogHistoryFiles;
+ --num_history_files) {
+ DCHECK(it != history_files.end());
+ files_to_delete.insert(it->path());
+ it = history_files.erase(it);
+ }
+
+ for (const base::FilePath& path : files_to_delete) {
+ if (!base::DeleteFile(path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path << ".";
+ }
+ }
+
+ return history_files;
+}
+
+bool WebRtcRemoteEventLogManager::StartWritingLog(
+ const PeerConnectionKey& key,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id_out,
+ std::string* error_message_out) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // The log is assigned a universally unique ID (with high probability).
+ const std::string log_id = CreateWebRtcEventLogId();
+
+ // Use the log ID as part of the filename. In the highly unlikely event that
+ // this filename is already taken, or that an earlier log with the same name
+ // existed and left a history file behind, it will be treated the same way as
+ // any other failure to start the log file.
+ // TODO(crbug.com/775415): Add a unit test for above comment.
+ const base::FilePath remote_logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
+ const base::FilePath log_path =
+ WebRtcEventLogPath(remote_logs_dir, log_id, web_app_id,
+ log_file_writer_factory_->Extension());
+
+ if (base::PathExists(log_path)) {
+ LOG(ERROR) << "Previously used ID selected.";
+ *error_message_out = kStartRemoteLoggingFailureFilePathUsedLog;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kLogPathNotAvailable);
+ return false;
+ }
+
+ const base::FilePath history_file_path =
+ GetWebRtcEventLogHistoryFilePath(log_path);
+ if (base::PathExists(history_file_path)) {
+ LOG(ERROR) << "Previously used ID selected.";
+ *error_message_out = kStartRemoteLoggingFailureFilePathUsedHistory;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kHistoryPathNotAvailable);
+ return false;
+ }
+
+ // The log is now ACTIVE.
+ DCHECK_NE(max_file_size_bytes, kWebRtcEventLogManagerUnlimitedFileSize);
+ auto log_file =
+ log_file_writer_factory_->Create(log_path, max_file_size_bytes);
+ if (!log_file) {
+ LOG(ERROR) << "Failed to initialize remote-bound WebRTC event log file.";
+ *error_message_out = kStartRemoteLoggingFailureFileCreationError;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kFileCreationError);
+ return false;
+ }
+ const auto it = active_logs_.emplace(key, std::move(log_file));
+ DCHECK(it.second);
+
+ observer_->OnRemoteLogStarted(key, it.first->second->path(),
+ output_period_ms);
+
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kSuccess);
+
+ *log_id_out = log_id;
+ return true;
+}
+
+void WebRtcRemoteEventLogManager::MaybeStopRemoteLogging(
+ const PeerConnectionKey& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const auto it = active_logs_.find(key);
+ if (it == active_logs_.end()) {
+ return;
+ }
+
+ CloseLogFile(it, /*make_pending=*/true);
+
+ ManageUploadSchedule();
+}
+
+void WebRtcRemoteEventLogManager::PrunePendingLogs(
+ base::Optional<BrowserContextId> browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ MaybeRemovePendingLogs(
+ base::Time::Min(),
+ base::Time::Now() - kRemoteBoundWebRtcEventLogsMaxRetention,
+ browser_context_id, /*is_cache_clear=*/false);
+}
+
+void WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!proactive_pending_logs_prune_delta_.is_zero());
+ DCHECK(proactive_prune_scheduling_started_);
+
+ PrunePendingLogs();
+
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs,
+ weak_ptr_factory_->GetWeakPtr()),
+ proactive_pending_logs_prune_delta_);
+}
+
+void WebRtcRemoteEventLogManager::PruneHistoryFiles() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ for (auto it = enabled_browser_contexts_.begin();
+ it != enabled_browser_contexts_.end(); ++it) {
+ const BrowserContextId browser_context_id = it->first;
+ MaybeRemoveHistoryFiles(base::Time::Min(),
+ base::Time::Now() - kHistoryFileRetention,
+ browser_context_id);
+ }
+}
+
+void WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(proactive_prune_scheduling_started_);
+
+ PruneHistoryFiles();
+
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles,
+ weak_ptr_factory_->GetWeakPtr()),
+ kProactiveHistoryFilesPruneDelta);
+}
+
+void WebRtcRemoteEventLogManager::MaybeCancelActiveLogs(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ for (auto it = active_logs_.begin(); it != active_logs_.end();) {
+ // Since the file is active, assume it's still being modified.
+ if (MatchesFilter(it->first.browser_context_id, base::Time::Now(),
+ browser_context_id, delete_begin, delete_end)) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kActiveLogCancelledDueToCacheClear);
+ it = CloseLogFile(it, /*make_pending=*/false);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void WebRtcRemoteEventLogManager::MaybeRemovePendingLogs(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::Optional<BrowserContextId> browser_context_id,
+ bool is_cache_clear) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
+ if (MatchesFilter(it->browser_context_id, it->last_modified,
+ browser_context_id, delete_begin, delete_end)) {
+ UmaRecordWebRtcEventLoggingUpload(
+ is_cache_clear
+ ? WebRtcEventLoggingUploadUma::kPendingLogDeletedDueToCacheClear
+ : WebRtcEventLoggingUploadUma::kExpiredLogFileDuringSession);
+
+ if (!base::DeleteFile(it->path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << it->path << ".";
+ }
+
+ // Produce a history file (they have longer retention) to replace the log.
+ if (is_cache_clear) { // Will be immediately deleted otherwise.
+ CreateHistoryFile(it->path, it->last_modified);
+ }
+
+ it = pending_logs_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // The last pending log might have been removed.
+ if (!UploadConditionsHold()) {
+ time_when_upload_conditions_met_ = base::TimeTicks();
+ }
+}
+
+void WebRtcRemoteEventLogManager::MaybeRemoveHistoryFiles(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ PruneAndLoadHistoryFilesForBrowserContext(delete_begin, delete_end,
+ browser_context_id);
+ return;
+}
+
+void WebRtcRemoteEventLogManager::MaybeCancelUpload(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!uploader_) {
+ return;
+ }
+
+ const WebRtcLogFileInfo& info = uploader_->GetWebRtcLogFileInfo();
+ if (!MatchesFilter(info.browser_context_id, info.last_modified,
+ browser_context_id, delete_begin, delete_end)) {
+ return;
+ }
+
+ // Cancel the upload.
+ // * If the upload has asynchronously completed by now, the uploader would
+ // have posted a task back to our queue to delete it and move on to the
+ // next file; cancellation is reported as unsuccessful in that case. In that
+ // case, we avoid resetting |uploader_| until that callback task executes.
+ // * If the upload was still underway when we cancelled it, then we can
+ // safely reset |uploader_| and move on to the next file the next time
+ // ManageUploadSchedule() is called.
+ const bool cancelled = uploader_->Cancel();
+ if (cancelled) {
+ uploader_.reset();
+ }
+}
+
+bool WebRtcRemoteEventLogManager::MatchesFilter(
+ BrowserContextId log_browser_context_id,
+ const base::Time& log_last_modification,
+ base::Optional<BrowserContextId> filter_browser_context_id,
+ const base::Time& filter_range_begin,
+ const base::Time& filter_range_end) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (filter_browser_context_id &&
+ *filter_browser_context_id != log_browser_context_id) {
+ return false;
+ }
+ return TimePointInRange(log_last_modification, filter_range_begin,
+ filter_range_end);
+}
+
+bool WebRtcRemoteEventLogManager::AdditionalActiveLogAllowed(
+ BrowserContextId browser_context_id) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Limit over concurrently active logs (across BrowserContext-s).
+ if (active_logs_.size() >= kMaxActiveRemoteBoundWebRtcEventLogs) {
+ return false;
+ }
+
+ // Limit over the number of pending logs (per BrowserContext). We count active
+ // logs too, since they become pending logs once completed.
+ const size_t active_count = std::count_if(
+ active_logs_.begin(), active_logs_.end(),
+ [browser_context_id](const decltype(active_logs_)::value_type& log) {
+ return log.first.browser_context_id == browser_context_id;
+ });
+ const size_t pending_count = std::count_if(
+ pending_logs_.begin(), pending_logs_.end(),
+ [browser_context_id](const decltype(pending_logs_)::value_type& log) {
+ return log.browser_context_id == browser_context_id;
+ });
+ return active_count + pending_count < kMaxPendingRemoteBoundWebRtcEventLogs;
+}
+
+bool WebRtcRemoteEventLogManager::UploadSuppressed() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return !upload_suppression_disabled_ && !active_peer_connections_.empty();
+}
+
+bool WebRtcRemoteEventLogManager::UploadConditionsHold() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return !uploader_ && !pending_logs_.empty() && !UploadSuppressed() &&
+ uploading_supported_for_connection_type_;
+}
+
+void WebRtcRemoteEventLogManager::ManageUploadSchedule() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Avoid uploading freshly expired files.
+
+ if (!UploadConditionsHold()) {
+ time_when_upload_conditions_met_ = base::TimeTicks();
+ return;
+ }
+
+ if (!time_when_upload_conditions_met_.is_null()) {
+ // Conditions have been holding for a while; MaybeStartUploading() has
+ // already been scheduled when |time_when_upload_conditions_met_| was set.
+ return;
+ }
+
+ ++scheduled_upload_tasks_;
+
+ time_when_upload_conditions_met_ = base::TimeTicks::Now();
+
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::MaybeStartUploading,
+ weak_ptr_factory_->GetWeakPtr()),
+ upload_delay_);
+}
+
+void WebRtcRemoteEventLogManager::MaybeStartUploading() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_GT(scheduled_upload_tasks_, 0u);
+
+ // Since MaybeStartUploading() was scheduled, conditions might have stopped
+ // holding at some point. They may have even stopped and started several times
+ // while the currently running task was scheduled, meaning several tasks could
+ // be pending now, only the last of which should really end up uploading.
+
+ if (time_when_upload_conditions_met_.is_null()) {
+ // Conditions no longer hold; no way to know how many (now irrelevant) other
+ // similar tasks are pending, if any.
+ } else if (base::TimeTicks::Now() - time_when_upload_conditions_met_ <
+ upload_delay_) {
+ // Conditions have stopped holding, then started holding again; there has
+ // to be a more recent task scheduled, that will take over later.
+ DCHECK_GT(scheduled_upload_tasks_, 1u);
+ } else {
+ // It's up to the rest of the code to turn |scheduled_upload_tasks_| off
+ // if the conditions have at some point stopped holding, or it wouldn't
+ // know to turn it on when they resume.
+ DCHECK(UploadConditionsHold());
+
+ // When the upload we're about to start finishes, there will be another
+ // delay of length |upload_delay_| before the next one starts.
+ time_when_upload_conditions_met_ = base::TimeTicks();
+
+ auto callback = base::BindOnce(
+ &WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete,
+ weak_ptr_factory_->GetWeakPtr());
+
+ // The uploader takes ownership of the file; it's no longer considered to be
+ // pending. (If the upload fails, the log will be deleted.)
+ // TODO(crbug.com/775415): Add more refined retry behavior, so that we would
+ // not delete the log permanently if the network is just down, on the one
+ // hand, but also would not be uploading unlimited data on endless retries
+ // on the other hand.
+ // TODO(crbug.com/775415): Rename the file before uploading, so that we
+ // would not retry the upload after restarting Chrome, if the upload is
+ // interrupted.
+ uploader_ =
+ uploader_factory_->Create(*pending_logs_.begin(), std::move(callback));
+ pending_logs_.erase(pending_logs_.begin());
+ }
+
+ --scheduled_upload_tasks_;
+}
+
+void WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete(
+ const base::FilePath& log_file,
+ bool upload_successful) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(uploader_);
+ uploader_.reset();
+ ManageUploadSchedule();
+}
+
+bool WebRtcRemoteEventLogManager::FindPeerConnection(
+ int render_process_id,
+ const std::string& session_id,
+ PeerConnectionKey* key) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!session_id.empty());
+
+ const auto it = FindNextPeerConnection(active_peer_connections_.cbegin(),
+ render_process_id, session_id);
+ if (it == active_peer_connections_.cend()) {
+ return false;
+ }
+
+ // Make sure that the session ID is unique for the renderer process,
+ // though not necessarily between renderer processes.
+ // (The helper exists solely to allow this DCHECK.)
+ DCHECK(FindNextPeerConnection(std::next(it), render_process_id, session_id) ==
+ active_peer_connections_.cend());
+
+ *key = it->first;
+ return true;
+}
+
+WebRtcRemoteEventLogManager::PeerConnectionMap::const_iterator
+WebRtcRemoteEventLogManager::FindNextPeerConnection(
+ PeerConnectionMap::const_iterator begin,
+ int render_process_id,
+ const std::string& session_id) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!session_id.empty());
+ const auto end = active_peer_connections_.cend();
+ for (auto it = begin; it != end; ++it) {
+ if (it->first.render_process_id == render_process_id &&
+ it->second == session_id) {
+ return it;
+ }
+ }
+ return end;
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
new file mode 100644
index 00000000000..ff1f93a93e6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
@@ -0,0 +1,487 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_REMOTE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_REMOTE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+#include "components/upload_list/upload_list.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+namespace webrtc_event_logging {
+
+class WebRtcRemoteEventLogManager final
+ : public network::NetworkConnectionTracker::NetworkConnectionObserver {
+ using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+ using LogFilesMap =
+ std::map<WebRtcEventLogPeerConnectionKey, std::unique_ptr<LogFileWriter>>;
+ using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+
+ public:
+ WebRtcRemoteEventLogManager(
+ WebRtcRemoteEventLogsObserver* observer,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+ ~WebRtcRemoteEventLogManager() override;
+
+ // Sets a network::NetworkConnectionTracker which will be used to track
+ // network connectivity.
+ // Must not be called more than once.
+ // Must be called before any call to EnableForBrowserContext().
+ void SetNetworkConnectionTracker(
+ network::NetworkConnectionTracker* network_connection_tracker);
+
+ // Sets a LogFileWriter factory.
+ // Must not be called more than once.
+ // Must be called before any call to EnableForBrowserContext().
+ void SetLogFileWriterFactory(
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory);
+
+ // Enables remote-bound logging for a given BrowserContext. Logs stored during
+ // previous sessions become eligible for upload, and recording of new logs for
+ // peer connections associated with this BrowserContext, in the
+ // BrowserContext's user-data directory, becomes possible.
+ // This method would typically be called when a BrowserContext is initialized.
+ // Enabling for the same BrowserContext twice in a row, without disabling
+ // in between, is an error.
+ void EnableForBrowserContext(BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir);
+
+ // Disables remote-bound logging for a given BrowserContext. Pending logs from
+ // earlier (while it was enabled) may no longer be uploaded, additional
+ // logs will not be created, and any active uploads associated with the
+ // BrowserContext will be cancelled.
+ // Disabling for a BrowserContext which was not enabled is not an error,
+ // because the caller is not required to know whether a previous call
+ // to EnableForBrowserContext() was successful.
+ void DisableForBrowserContext(BrowserContextId browser_context_id);
+
+ // Called to inform |this| of peer connections being added/removed.
+ // This information is used to:
+ // 1. Make decisions about when to upload previously finished logs.
+ // 2. When a peer connection is removed, if it was being logged, its log
+ // changes from ACTIVE to PENDING.
+ // The return value of both methods indicates only the consistency of the
+ // information with previously received information (e.g. can't remove a
+ // peer connection that was never added, etc.).
+ bool PeerConnectionAdded(const PeerConnectionKey& key);
+ bool PeerConnectionRemoved(const PeerConnectionKey& key);
+
+ // Called to inform |this| that a peer connection has been associated
+ // with |session_id|. After this, it is possible to refer to that peer
+ // connection using StartRemoteLogging() by providing |session_id|.
+ bool PeerConnectionSessionIdSet(const PeerConnectionKey& key,
+ const std::string& session_id);
+
+ // Attempt to start logging the WebRTC events of an active peer connection.
+ // Logging is subject to several restrictions:
+ // 1. May not log more than kMaxNumberActiveRemoteWebRtcEventLogFiles logs
+ // at the same time.
+ // 2. Each browser context may have only kMaxPendingLogFilesPerBrowserContext
+ // pending logs. Since active logs later become pending logs, it is also
+ // forbidden to start a remote-bound log that would, once completed, become
+ // a pending log that would exceed that limit.
+ // 3. The maximum file size must be sensible.
+ //
+ // If all of the restrictions were observed, and if a file was successfully
+ // created, true will be returned.
+ //
+ // If the call succeeds, the log's identifier will be written to |log_id|.
+ // The log identifier is exactly 32 uppercase ASCII characters from the
+ // ranges 0-9 and A-F.
+ //
+ // The log's filename will also incorporate |web_app_id|.
+ // |web_app_id| must be between 1 and 99 (inclusive); error otherwise.
+ //
+ // If the call fails, an error message is written to |error_message|.
+ // The error message will be specific to the failure (as opposed to a generic
+ // one) is produced only if that error message is useful for the caller:
+ // * Bad parameters.
+ // * Function called at a time when the caller could know it would fail,
+ // such as for a peer connection that was already logged.
+ // We intentionally avoid giving specific errors in some cases, so as
+ // to avoid leaking information such as having too many active and/or
+ // pending logs.
+ bool StartRemoteLogging(int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id,
+ std::string* error_message);
+
+ // If an active remote-bound log exists for the given peer connection, this
+ // will append |message| to that log.
+ // If writing |message| to the log would exceed the log's maximum allowed
+ // size, the write is disallowed and the file is closed instead (and changes
+ // from ACTIVE to PENDING).
+ // If the log file's capacity is exhausted as a result of this function call,
+ // or if a write error occurs, the file is closed, and the remote-bound log
+ // changes from ACTIVE to PENDING.
+ // True is returned if and only if |message| was written in its entirety to
+ // an active log.
+ bool EventLogWrite(const PeerConnectionKey& key, const std::string& message);
+
+ // Clear PENDING WebRTC event logs associated with a given browser context,
+ // in a given time range, then post |reply| back to the thread from which
+ // the method was originally invoked (which can be any thread).
+ // Log files currently being written are *not* interrupted.
+ // Active uploads *are* interrupted.
+ void ClearCacheForBrowserContext(BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end);
+
+ // See documentation of same method in WebRtcEventLogManager for details.
+ void GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply);
+
+ // Works on not-enabled BrowserContext-s, which means the logs are never made
+ // eligible for upload. Useful when a BrowserContext is loaded which in
+ // the past had remote-logging enabled, but no longer does.
+ void RemovePendingLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir);
+
+ // An implicit PeerConnectionRemoved() on all of the peer connections that
+ // were associated with the renderer process.
+ void RenderProcessHostExitedDestroyed(int render_process_id);
+
+ // network::NetworkConnectionTracker::NetworkConnectionObserver implementation
+ void OnConnectionChanged(network::mojom::ConnectionType type) override;
+
+ // Unit tests may use this to inject null uploaders, or ones which are
+ // directly controlled by the unit test (succeed or fail according to the
+ // test's needs).
+ // Note that for simplicity's sake, this may be called from outside the
+ // task queue on which this object lives (WebRtcEventLogManager::task_queue_).
+ // Therefore, if a test calls this, it should call it before it initializes
+ // any BrowserContext with pending log files in its directory.
+ void SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory);
+
+ // Exposes UploadConditionsHold() to unit tests. See WebRtcEventLogManager's
+ // documentation for the rationale.
+ void UploadConditionsHoldForTesting(base::OnceCallback<void(bool)> callback);
+
+ // In production code, |task_runner_| stops running tasks as part of Chrome's
+ // shut-down process, before |this| is torn down. In unit tests, this is
+ // not the case.
+ void ShutDownForTesting(base::OnceClosure reply);
+
+ private:
+ using PeerConnectionMap = std::map<PeerConnectionKey, std::string>;
+
+ // Validates log parameters.
+ // If valid, returns true. Otherwise, false, and |error_message| gets
+ // a relevant error.
+ bool AreLogParametersValid(size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* error_message) const;
+
+ // Checks whether a browser context has already been enabled via a call to
+ // EnableForBrowserContext(), and not yet disabled using a call to
+ // DisableForBrowserContext().
+ bool BrowserContextEnabled(BrowserContextId browser_context_id) const;
+
+ // Closes an active log file.
+ // If |make_pending| is true, closing the file changes its state from ACTIVE
+ // to PENDING. If |make_pending| is false, or if the file couldn't be closed
+ // correctly, the file will be deleted.
+ // Returns an iterator to the next ACTIVE file.
+ LogFilesMap::iterator CloseLogFile(LogFilesMap::iterator it,
+ bool make_pending);
+
+ // Attempts to create the directory where we'll write the logs, if it does
+ // not already exist. Returns true if the directory exists (either it already
+ // existed, or it was successfully created).
+ bool MaybeCreateLogsDirectory(const base::FilePath& remote_bound_logs_dir);
+
+ // Scans the user data directory associated with the BrowserContext
+ // associated with the given BrowserContextId remote-bound logs that were
+ // created during previous Chrome sessions and for history files,
+ // then process them (discard expired files, etc.)
+ void LoadLogsDirectory(BrowserContextId browser_context_id,
+ const base::FilePath& remote_bound_logs_dir);
+
+ // Loads the pending log file whose path is |path|, into the BrowserContext
+ // indicated by |browser_context_id|. Note that the contents of the file are
+ // note read by this method.
+ // Returns true if the file was loaded correctly, and should be kept on disk;
+ // false if the file was not loaded (e.g. incomplete or expired), and needs
+ // to be deleted.
+ bool LoadPendingLogInfo(BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ base::Time last_modified);
+
+ // Loads a history file. Returns a WebRtcEventLogHistoryFileReader if the
+ // file was loaded correctly, and should be kept on disk; nullptr otherwise,
+ // signaling that the file should be deleted.
+ // |prune_begin| and |prune_end| define a time range where, if the log falls
+ // within the range, it will not be loaded.
+ std::unique_ptr<WebRtcEventLogHistoryFileReader> LoadHistoryFile(
+ BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ const base::Time& prune_begin,
+ const base::Time& prune_end);
+
+ // Deletes any history logs associated with |browser_context_id| captured or
+ // uploaded between |prune_begin| and |prune_end|, inclusive, then returns a
+ // set of readers for the remaining (meaning not-pruned) history files.
+ std::set<WebRtcEventLogHistoryFileReader>
+ PruneAndLoadHistoryFilesForBrowserContext(
+ const base::Time& prune_begin,
+ const base::Time& prune_end,
+ BrowserContextId browser_context_id);
+
+ // Attempts the creation of a locally stored file into which a remote-bound
+ // log may be written. The log-identifier is returned if successful, the empty
+ // string otherwise.
+ bool StartWritingLog(const PeerConnectionKey& key,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id_out,
+ std::string* error_message_out);
+
+ // Checks if the referenced peer connection has an associated active
+ // remote-bound log. If it does, the log is changed from ACTIVE to PENDING.
+ void MaybeStopRemoteLogging(const PeerConnectionKey& key);
+
+ // Get rid of pending logs whose age exceeds our retention policy.
+ // On the one hand, we want to remove expired files as soon as possible, but
+ // on the other hand, we don't want to waste CPU by checking this too often.
+ // Therefore, we prune pending files:
+ // 1. When a new BrowserContext is initalized, thereby also pruning the
+ // pending logs contributed by that BrowserContext.
+ // 2. Before initiating a new upload, thereby avoiding uploading a file that
+ // has just now expired.
+ // 3. On infrequent events - peer connection addition/removal, but NOT
+ // on something that could potentially be frequent, such as EventLogWrite.
+ // Note that the last modification date of a file, which is the value measured
+ // against for retention, is only read from disk once per file, meaning
+ // this check is not too expensive.
+ // If a |browser_context_id| is provided, logs are only pruned for it.
+ void PrunePendingLogs(
+ base::Optional<BrowserContextId> browser_context_id = base::nullopt);
+
+ // PrunePendingLogs() and schedule the next proactive pending logs prune.
+ void RecurringlyPrunePendingLogs();
+
+ // Removes expired history files.
+ // Since these are small, and since looking for them is not as cheap as
+ // looking for pending logs, we do not make an effort to remove them as
+ // soon as possible.
+ void PruneHistoryFiles();
+
+ // PruneHistoryFiles() and schedule the next proactive history files prune.
+ void RecurringlyPruneHistoryFiles();
+
+ // Cancels and deletes active logs which match the given filter criteria, as
+ // described by MatchesFilter's documentation.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeCancelActiveLogs(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id);
+
+ // Removes pending logs files which match the given filter criteria, as
+ // described by MatchesFilter's documentation.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeRemovePendingLogs(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::Optional<BrowserContextId> browser_context_id,
+ bool is_cache_clear);
+
+ // Remove all history files associated with |browser_context_id| which were
+ // either captured or uploaded between |delete_begin| and |delete_end|.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeRemoveHistoryFiles(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id);
+
+ // If the currently uploaded file matches the given filter criteria, as
+ // described by MatchesFilter's documentation, the upload will be
+ // cancelled, and the log file deleted. If this happens, the next pending log
+ // file will be considered for upload.
+ // This method is used to ensure that clearing of browsing data by the user
+ // does not leave the currently-uploaded file on disk, even for the duration
+ // of the upload.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeCancelUpload(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id);
+
+ // Checks whether a log file matches a range and (potentially) BrowserContext:
+ // * A file matches if its last modification date was at or later than
+ // |filter_range_begin|, and earlier than |filter_range_end|.
+ // * If a null time-point is given as either |filter_range_begin| or
+ // |filter_range_end|, it is treated as "beginning-of-time" or
+ // "end-of-time", respectively.
+ // * If |filter_browser_context_id| is set, only log files associated with it
+ // can match the filter.
+ bool MatchesFilter(BrowserContextId log_browser_context_id,
+ const base::Time& log_last_modification,
+ base::Optional<BrowserContextId> filter_browser_context_id,
+ const base::Time& filter_range_begin,
+ const base::Time& filter_range_end) const;
+
+ // Return |true| if and only if we can start another active log (with respect
+ // to limitations on the numbers active and pending logs).
+ bool AdditionalActiveLogAllowed(BrowserContextId browser_context_id) const;
+
+ // Uploading suppressed while active peer connections exist (unless
+ // suppression) is turned off from the command line.
+ bool UploadSuppressed() const;
+
+ // Check whether all the conditions necessary for uploading log files are
+ // currently satisfied.
+ // 1. There may be no active peer connections which might be adversely
+ // affected by the bandwidth consumption of the upload.
+ // 2. Chrome has a network connection, and that conneciton is either a wired
+ // one, or WiFi. (That is, not 3G, etc.)
+ // 3. Naturally, a file pending upload must exist.
+ bool UploadConditionsHold() const;
+
+ // When the conditions necessary for uploading first hold, schedule a delayed
+ // task to upload (MaybeStartUploading). If they ever stop holding, void it.
+ void ManageUploadSchedule();
+
+ // Posted as a delayed task by ManageUploadSchedule. If not voided until
+ // executed, will initiate an upload of the next log file.
+ void MaybeStartUploading();
+
+ // Callback for the success/failure of an upload.
+ // When an upload is complete, it might be time to upload the next file.
+ // Note: |log_file| and |upload_successful| are ignored in production; they
+ // are used in unit tests, so we keep them here to make things simpler, so
+ // that this method would match WebRtcEventLogUploader::UploadResultCallback
+ // without adaptation.
+ void OnWebRtcEventLogUploadComplete(const base::FilePath& log_file,
+ bool upload_successful);
+
+ // Given a renderer process ID and peer connection's session ID, find the
+ // peer connection to which they refer.
+ bool FindPeerConnection(int render_process_id,
+ const std::string& session_id,
+ PeerConnectionKey* key) const;
+
+ // Find the next peer connection in a map to which the renderer process ID
+ // and session ID refer.
+ // This helper allows FindPeerConnection() to DCHECK on uniqueness of the ID
+ // without descending down a recursive rabbit hole.
+ PeerConnectionMap::const_iterator FindNextPeerConnection(
+ PeerConnectionMap::const_iterator begin,
+ int render_process_id,
+ const std::string& session_id) const;
+
+ // Normally, uploading is suppressed while there are active peer connections.
+ // This may be disabled from the command line.
+ const bool upload_suppression_disabled_;
+
+ // The conditions for upload must hold for this much time, uninterrupted,
+ // before an upload may be initiated.
+ const base::TimeDelta upload_delay_;
+
+ // If non-zero, every |proactive_pending_logs_prune_delta_|, pending logs
+ // will be pruned. This avoids them staying around on disk for longer than
+ // their expiration if no event occurs which triggers reactive pruning.
+ const base::TimeDelta proactive_pending_logs_prune_delta_;
+
+ // Proactive pruning, if enabled, starts with the first enabled browser
+ // context. To avoid unnecessary complexity, if that browser context is
+ // disabled, proactive pruning is not disabled.
+ bool proactive_prune_scheduling_started_;
+
+ // This is used to inform WebRtcEventLogManager when remote-bound logging
+ // of a peer connection starts/stops, which allows WebRtcEventLogManager to
+ // decide when to ask WebRTC to start/stop sending event logs.
+ WebRtcRemoteEventLogsObserver* const observer_;
+
+ // The IDs of the BrowserContexts for which logging is enabled, mapped to
+ // the directory where each BrowserContext's remote-bound logs are stored.
+ std::map<BrowserContextId, base::FilePath> enabled_browser_contexts_;
+
+ // Currently active peer connections, mapped to their session IDs (once the
+ // session ID is set).
+ // PeerConnections which have been closed are not considered active,
+ // regardless of whether they have been torn down.
+ PeerConnectionMap active_peer_connections_;
+
+ // Creates LogFileWriter instances (compressed/uncompressed, etc.).
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory_;
+
+ // Remote-bound logs which we're currently in the process of writing to disk.
+ LogFilesMap active_logs_;
+
+ // Remote-bound logs which have been written to disk before (either during
+ // this Chrome session or during an earlier one), and which are no waiting to
+ // be uploaded.
+ std::set<WebRtcLogFileInfo> pending_logs_;
+
+ // Null if no ongoing upload, or an uploader which owns a file, and is
+ // currently busy uploading it to a remote server.
+ std::unique_ptr<WebRtcEventLogUploader> uploader_;
+
+ // Provides notifications of network changes.
+ network::NetworkConnectionTracker* network_connection_tracker_;
+
+ // Whether the network we are currently connected to, if any, is one over
+ // which we may upload.
+ bool uploading_supported_for_connection_type_;
+
+ // If the conditions for initiating an upload do not hold, this will be
+ // set to an empty base::TimeTicks.
+ // If the conditions were found to hold, this will record the time when they
+ // started holding. (It will be set back to 0 if they ever cease holding.)
+ base::TimeTicks time_when_upload_conditions_met_;
+
+ // This is a vehicle for DCHECKs to ensure code sanity. It counts the number
+ // of scheduled tasks of MaybeStartUploading(), and proves that we never
+ // end up with a scheduled upload that never occurs.
+ size_t scheduled_upload_tasks_;
+
+ // Producer of uploader objects. (In unit tests, this would create
+ // null-implementation uploaders, or uploaders whose behavior is controlled
+ // by the unit test.)
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory_;
+
+ // |this| is created and destroyed on the UI thread, but operates on the
+ // following IO-capable sequenced task runner.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Weak pointer factory. Only expected to be useful for unit tests, because
+ // in production, |task_runner_| is stopped during shut-down, so tasks will
+ // either find the pointer to be valid, or not run because the runner has
+ // already been stopped.
+ // Note that the unique_ptr is used just to make it clearer that ownership is
+ // here. In reality, this is never auto-destroyed; see destructor for details.
+ std::unique_ptr<base::WeakPtrFactory<WebRtcRemoteEventLogManager>>
+ weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcRemoteEventLogManager);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_REMOTE_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
new file mode 100644
index 00000000000..e887b44737b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
@@ -0,0 +1,4934 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+
+#include <algorithm>
+#include <list>
+#include <map>
+#include <memory>
+#include <numeric>
+#include <queue>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/gtest_util.h"
+#include "base/test/scoped_command_line.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/simple_test_clock.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/testing_pref_store.h"
+#include "components/sync_preferences/pref_service_mock_factory.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#endif
+
+namespace webrtc_event_logging {
+
+#if defined(OS_WIN)
+#define NumberToStringType base::NumberToString16
+#else
+#define NumberToStringType base::NumberToString
+#endif
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+
+using BrowserContext = content::BrowserContext;
+using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+using MockRenderProcessHost = content::MockRenderProcessHost;
+using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+using RenderProcessHost = content::RenderProcessHost;
+
+using Compression = WebRtcEventLogCompression;
+
+namespace {
+
+#if !defined(OS_ANDROID)
+
+auto SaveFilePathTo(base::Optional<base::FilePath>* output) {
+ return [output](PeerConnectionKey ignored_key, base::FilePath file_path,
+ int output_period_ms = 0) { *output = file_path; };
+}
+
+auto SaveKeyAndFilePathTo(base::Optional<PeerConnectionKey>* key_output,
+ base::Optional<base::FilePath>* file_path_output) {
+ return [key_output, file_path_output](PeerConnectionKey key,
+ base::FilePath file_path) {
+ *key_output = key;
+ *file_path_output = file_path;
+ };
+}
+
+const int kMaxActiveRemoteLogFiles =
+ static_cast<int>(kMaxActiveRemoteBoundWebRtcEventLogs);
+const int kMaxPendingRemoteLogFiles =
+ static_cast<int>(kMaxPendingRemoteBoundWebRtcEventLogs);
+const char kSessionId[] = "session_id";
+
+base::Time GetLastModificationTime(const base::FilePath& file_path) {
+ base::File::Info file_info;
+ if (!base::GetFileInfo(file_path, &file_info)) {
+ EXPECT_TRUE(false);
+ return base::Time();
+ }
+ return file_info.last_modified;
+}
+
+#endif
+
+// Common default/arbitrary values.
+constexpr int kLid = 478;
+constexpr size_t kWebAppId = 42;
+
+PeerConnectionKey GetPeerConnectionKey(RenderProcessHost* rph, int lid) {
+ const BrowserContext* browser_context = rph->GetBrowserContext();
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ return PeerConnectionKey(rph->GetID(), lid, browser_context_id);
+}
+
+bool CreateRemoteBoundLogFile(const base::FilePath& dir,
+ size_t web_app_id,
+ const base::FilePath::StringPieceType& extension,
+ base::Time capture_time,
+ base::FilePath* file_path,
+ base::File* file) {
+ *file_path =
+ dir.AsEndingWithSeparator()
+ .InsertBeforeExtensionASCII(kRemoteBoundWebRtcEventLogFileNamePrefix)
+ .InsertBeforeExtensionASCII("_")
+ .InsertBeforeExtensionASCII(std::to_string(web_app_id))
+ .InsertBeforeExtensionASCII("_")
+ .InsertBeforeExtensionASCII(CreateWebRtcEventLogId())
+ .AddExtension(extension);
+
+ constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE;
+ file->Initialize(*file_path, file_flags);
+ if (!file->IsValid() || !file->created()) {
+ return false;
+ }
+
+ if (!base::TouchFile(*file_path, capture_time, capture_time)) {
+ return false;
+ }
+
+ return true;
+}
+
+// This implementation does not upload files, nor pretends to have finished an
+// upload. Most importantly, it does not get rid of the locally-stored log file
+// after finishing a simulated upload; this is useful because it keeps the file
+// on disk, where unit tests may inspect it.
+// This class enforces an expectation over the upload being cancelled or not.
+class NullWebRtcEventLogUploader : public WebRtcEventLogUploader {
+ public:
+ NullWebRtcEventLogUploader(const WebRtcLogFileInfo& log_file,
+ bool cancellation_expected)
+ : log_file_(log_file),
+ cancellation_expected_(cancellation_expected),
+ was_cancelled_(false) {}
+
+ ~NullWebRtcEventLogUploader() override {
+ EXPECT_EQ(was_cancelled_, cancellation_expected_);
+ }
+
+ const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const override {
+ return log_file_;
+ }
+
+ bool Cancel() override {
+ EXPECT_TRUE(cancellation_expected_);
+ if (was_cancelled_) { // Should not be called more than once.
+ EXPECT_TRUE(false);
+ return false;
+ }
+ was_cancelled_ = true;
+ return true;
+ }
+
+ class Factory : public WebRtcEventLogUploader::Factory {
+ public:
+ Factory(bool cancellation_expected,
+ base::Optional<size_t> expected_instance_count = base::nullopt)
+ : cancellation_expected_(cancellation_expected),
+ expected_instance_count_(expected_instance_count),
+ instance_count_(0) {}
+
+ ~Factory() override {
+ if (expected_instance_count_.has_value()) {
+ EXPECT_EQ(instance_count_, expected_instance_count_.value());
+ }
+ }
+
+ std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) override {
+ if (expected_instance_count_.has_value()) {
+ EXPECT_LE(++instance_count_, expected_instance_count_.value());
+ }
+ return std::make_unique<NullWebRtcEventLogUploader>(
+ log_file, cancellation_expected_);
+ }
+
+ private:
+ const bool cancellation_expected_;
+ const base::Optional<size_t> expected_instance_count_;
+ size_t instance_count_;
+ };
+
+ private:
+ const WebRtcLogFileInfo log_file_;
+ const bool cancellation_expected_;
+ bool was_cancelled_;
+};
+
+class MockWebRtcLocalEventLogsObserver : public WebRtcLocalEventLogsObserver {
+ public:
+ ~MockWebRtcLocalEventLogsObserver() override = default;
+ MOCK_METHOD2(OnLocalLogStarted,
+ void(PeerConnectionKey, const base::FilePath&));
+ MOCK_METHOD1(OnLocalLogStopped, void(PeerConnectionKey));
+};
+
+class MockWebRtcRemoteEventLogsObserver : public WebRtcRemoteEventLogsObserver {
+ public:
+ ~MockWebRtcRemoteEventLogsObserver() override = default;
+ MOCK_METHOD3(OnRemoteLogStarted,
+ void(PeerConnectionKey, const base::FilePath&, int));
+ MOCK_METHOD1(OnRemoteLogStopped, void(PeerConnectionKey));
+};
+
+} // namespace
+
+class WebRtcEventLogManagerTestBase : public ::testing::Test {
+ public:
+ WebRtcEventLogManagerTestBase()
+ : test_shared_url_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)),
+ run_loop_(std::make_unique<base::RunLoop>()),
+ uploader_run_loop_(std::make_unique<base::RunLoop>()),
+ browser_context_(nullptr),
+ browser_context_id_(GetBrowserContextId(browser_context_.get())) {
+ TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(
+ test_shared_url_loader_factory_);
+
+ // Avoid proactive pruning; it has the potential to mess up tests, as well
+ // as keep pendings tasks around with a dangling reference to the unit
+ // under test. (Zero is a sentinel value for disabling proactive pruning.)
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogProactivePruningDelta, "0");
+
+ EXPECT_TRUE(local_logs_base_dir_.CreateUniqueTempDir());
+ local_logs_base_path_ = local_logs_base_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("local_event_logs"));
+
+ EXPECT_TRUE(profiles_dir_.CreateUniqueTempDir());
+ }
+
+ ~WebRtcEventLogManagerTestBase() override {
+ WaitForPendingTasks();
+
+ base::RunLoop run_loop;
+ event_log_manager_->ShutDownForTesting(run_loop.QuitClosure());
+ run_loop.Run();
+
+ // We do not want to satisfy any unsatisfied expectations by destroying
+ // |rph_|, |browser_context_|, etc., at the end of the test, before we
+ // destroy |event_log_manager_|. However, we must also make sure that their
+ // destructors do not attempt to access |event_log_manager_|, which in
+ // normal code lives forever, but not in the unit tests.
+ event_log_manager_.reset();
+
+ // Guard against unexpected state changes.
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+ }
+
+ void SetUp() override {
+ SetUpNetworkConnection(true,
+ network::mojom::ConnectionType::CONNECTION_ETHERNET);
+ SetLocalLogsObserver(&local_observer_);
+ SetRemoteLogsObserver(&remote_observer_);
+ LoadMainTestProfile();
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ policy::BrowserPolicyConnectorBase::SetPolicyProviderForTesting(&provider_);
+#endif
+ }
+
+ void TearDown() override {
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ TestingBrowserProcess::GetGlobal()->ShutdownBrowserPolicyConnector();
+#endif
+ }
+
+ void SetUpNetworkConnection(bool respond_synchronously,
+ network::mojom::ConnectionType connection_type) {
+ auto* tracker = network::TestNetworkConnectionTracker::GetInstance();
+ tracker->SetRespondSynchronously(respond_synchronously);
+ tracker->SetConnectionType(connection_type);
+ }
+
+ void SetConnectionType(network::mojom::ConnectionType connection_type) {
+ network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
+ connection_type);
+ }
+
+ void CreateWebRtcEventLogManager(
+ base::Optional<Compression> remote = base::nullopt) {
+ DCHECK(!event_log_manager_);
+
+ event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+
+ local_log_extension_ = kWebRtcEventLogUncompressedExtension;
+
+ if (remote.has_value()) {
+ auto factory = CreateLogFileWriterFactory(remote.value());
+ remote_log_extension_ = factory->Extension();
+ event_log_manager_->SetRemoteLogFileWriterFactoryForTesting(
+ std::move(factory));
+ } else {
+ // kWebRtcRemoteEventLogGzipped is turned on by default.
+ remote_log_extension_ = kWebRtcEventLogGzippedExtension;
+ }
+ }
+
+ void LoadMainTestProfile() {
+ browser_context_ = CreateBrowserContext("browser_context_");
+ browser_context_id_ = GetBrowserContextId(browser_context_.get());
+ rph_ = std::make_unique<MockRenderProcessHost>(browser_context_.get());
+ }
+
+ void UnloadMainTestProfile() {
+ rph_.reset();
+ browser_context_.reset();
+ browser_context_id_ = GetBrowserContextId(browser_context_.get());
+ }
+
+ void WaitForReply() {
+ run_loop_->Run();
+ run_loop_.reset(new base::RunLoop); // Allow re-blocking.
+ }
+
+ void Reply() { run_loop_->QuitWhenIdle(); }
+
+ base::OnceClosure ReplyClosure() {
+ // Intermediary pointer used to help the compiler distinguish between
+ // the overloaded Reply() functions.
+ void (WebRtcEventLogManagerTestBase::*function)() =
+ &WebRtcEventLogManagerTestBase::Reply;
+ return base::BindOnce(function, base::Unretained(this));
+ }
+
+ template <typename T>
+ void Reply(T* output, T val) {
+ *output = val;
+ run_loop_->QuitWhenIdle();
+ }
+
+ template <typename T>
+ base::OnceCallback<void(T)> ReplyClosure(T* output) {
+ // Intermediary pointer used to help the compiler distinguish between
+ // the overloaded Reply() functions.
+ void (WebRtcEventLogManagerTestBase::*function)(T*, T) =
+ &WebRtcEventLogManagerTestBase::Reply;
+ return base::BindOnce(function, base::Unretained(this), output);
+ }
+
+ void Reply(bool* output_bool,
+ std::string* output_str1,
+ std::string* output_str2,
+ bool bool_val,
+ const std::string& str1_val,
+ const std::string& str2_val) {
+ *output_bool = bool_val;
+ *output_str1 = str1_val;
+ *output_str2 = str2_val;
+ run_loop_->QuitWhenIdle();
+ }
+
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ ReplyClosure(bool* output_bool,
+ std::string* output_str1,
+ std::string* output_str2) {
+ // Intermediary pointer used to help the compiler distinguish between
+ // the overloaded Reply() functions.
+ void (WebRtcEventLogManagerTestBase::*function)(
+ bool*, std::string*, std::string*, bool, const std::string&,
+ const std::string&) = &WebRtcEventLogManagerTestBase::Reply;
+ return base::BindOnce(function, base::Unretained(this), output_bool,
+ output_str1, output_str2);
+ }
+
+ bool PeerConnectionAdded(const PeerConnectionKey& key) {
+ bool result;
+ event_log_manager_->PeerConnectionAdded(key.render_process_id, key.lid,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool PeerConnectionRemoved(const PeerConnectionKey& key) {
+ bool result;
+ event_log_manager_->PeerConnectionRemoved(key.render_process_id, key.lid,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool PeerConnectionSessionIdSet(const PeerConnectionKey& key,
+ const std::string& session_id) {
+ bool result;
+ event_log_manager_->PeerConnectionSessionIdSet(
+ key.render_process_id, key.lid, session_id, ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool PeerConnectionSessionIdSet(const PeerConnectionKey& key) {
+ return PeerConnectionSessionIdSet(key, GetUniqueId(key));
+ }
+
+ bool PeerConnectionStopped(const PeerConnectionKey& key) {
+ bool result;
+ event_log_manager_->PeerConnectionStopped(key.render_process_id, key.lid,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool EnableLocalLogging(
+ size_t max_size_bytes = kWebRtcEventLogManagerUnlimitedFileSize) {
+ return EnableLocalLogging(local_logs_base_path_, max_size_bytes);
+ }
+
+ bool EnableLocalLogging(
+ base::FilePath local_logs_base_path,
+ size_t max_size_bytes = kWebRtcEventLogManagerUnlimitedFileSize) {
+ bool result;
+ event_log_manager_->EnableLocalLogging(local_logs_base_path, max_size_bytes,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool DisableLocalLogging() {
+ bool result;
+ event_log_manager_->DisableLocalLogging(ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool StartRemoteLogging(const PeerConnectionKey& key,
+ const std::string& session_id,
+ size_t max_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id_output = nullptr,
+ std::string* error_message_output = nullptr) {
+ bool result;
+ std::string log_id;
+ std::string error_message;
+
+ event_log_manager_->StartRemoteLogging(
+ key.render_process_id, session_id, max_size_bytes, output_period_ms,
+ web_app_id, ReplyClosure(&result, &log_id, &error_message));
+
+ WaitForReply();
+
+ // If successful, only |log_id|. If unsuccessful, only |error_message| set.
+ DCHECK_EQ(result, !log_id.empty());
+ DCHECK_EQ(!result, !error_message.empty());
+
+ if (log_id_output) {
+ *log_id_output = log_id;
+ }
+
+ if (error_message_output) {
+ *error_message_output = error_message;
+ }
+
+ return result;
+ }
+
+ bool StartRemoteLogging(const PeerConnectionKey& key,
+ const std::string& session_id,
+ std::string* log_id_output = nullptr,
+ std::string* error_message_output = nullptr) {
+ return StartRemoteLogging(key, session_id, kMaxRemoteLogFileSizeBytes, 0,
+ kWebAppId, log_id_output, error_message_output);
+ }
+
+ bool StartRemoteLogging(const PeerConnectionKey& key,
+ std::string* log_id_output = nullptr,
+ std::string* error_message_output = nullptr) {
+ return StartRemoteLogging(key, GetUniqueId(key), kMaxRemoteLogFileSizeBytes,
+ 0, kWebAppId, log_id_output,
+ error_message_output);
+ }
+
+ void ClearCacheForBrowserContext(
+ const content::BrowserContext* browser_context,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ event_log_manager_->ClearCacheForBrowserContext(
+ browser_context, delete_begin, delete_end, ReplyClosure());
+ WaitForReply();
+ }
+
+ std::vector<UploadList::UploadInfo> GetHistory(
+ BrowserContextId browser_context_id) {
+ std::vector<UploadList::UploadInfo> result;
+
+ base::RunLoop run_loop;
+
+ auto reply = [](base::RunLoop* run_loop,
+ std::vector<UploadList::UploadInfo>* output,
+ const std::vector<UploadList::UploadInfo>& input) {
+ *output = input;
+ run_loop->Quit();
+ };
+ event_log_manager_->GetHistory(browser_context_id,
+ base::BindOnce(reply, &run_loop, &result));
+ run_loop.Run();
+
+ return result;
+ }
+
+ void SetLocalLogsObserver(WebRtcLocalEventLogsObserver* observer) {
+ event_log_manager_->SetLocalLogsObserver(observer, ReplyClosure());
+ WaitForReply();
+ }
+
+ void SetRemoteLogsObserver(WebRtcRemoteEventLogsObserver* observer) {
+ event_log_manager_->SetRemoteLogsObserver(observer, ReplyClosure());
+ WaitForReply();
+ }
+
+ void SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> factory) {
+ event_log_manager_->SetWebRtcEventLogUploaderFactoryForTesting(
+ std::move(factory), ReplyClosure());
+ WaitForReply();
+ }
+
+ std::pair<bool, bool> OnWebRtcEventLogWrite(const PeerConnectionKey& key,
+ const std::string& message) {
+ std::pair<bool, bool> result;
+ event_log_manager_->OnWebRtcEventLogWrite(key.render_process_id, key.lid,
+ message, ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ void FreezeClockAt(const base::Time::Exploded& frozen_time_exploded) {
+ base::Time frozen_time;
+ ASSERT_TRUE(
+ base::Time::FromLocalExploded(frozen_time_exploded, &frozen_time));
+ frozen_clock_.SetNow(frozen_time);
+ event_log_manager_->SetClockForTesting(&frozen_clock_, ReplyClosure());
+ WaitForReply();
+ }
+
+ void SetWebRtcEventLoggingState(const PeerConnectionKey& key,
+ bool event_logging_enabled) {
+ webrtc_state_change_instructions_.emplace(key, event_logging_enabled);
+ }
+
+ void ExpectWebRtcStateChangeInstruction(const PeerConnectionKey& key,
+ bool enabled) {
+ ASSERT_FALSE(webrtc_state_change_instructions_.empty());
+ auto& instruction = webrtc_state_change_instructions_.front();
+ EXPECT_EQ(instruction.key.render_process_id, key.render_process_id);
+ EXPECT_EQ(instruction.key.lid, key.lid);
+ EXPECT_EQ(instruction.enabled, enabled);
+ webrtc_state_change_instructions_.pop();
+ }
+
+ void SetPeerConnectionTrackerProxyForTesting(
+ std::unique_ptr<WebRtcEventLogManager::PeerConnectionTrackerProxy>
+ pc_tracker_proxy) {
+ event_log_manager_->SetPeerConnectionTrackerProxyForTesting(
+ std::move(pc_tracker_proxy), ReplyClosure());
+ WaitForReply();
+ }
+
+ // Allows either creating a TestingProfile with a predetermined name
+ // (useful when trying to "reload" a profile), or one with an arbitrary name.
+ virtual std::unique_ptr<TestingProfile> CreateBrowserContext() {
+ return CreateBrowserContext(std::string());
+ }
+ virtual std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name) {
+ return CreateBrowserContext(profile_name, true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ true /* policy_allows_remote_logging */);
+ }
+ virtual std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name,
+ bool is_managed_profile,
+ bool has_device_level_policies,
+ base::Optional<bool> policy_allows_remote_logging) {
+ // If profile name not specified, select a unique name.
+ if (profile_name.empty()) {
+ static size_t index = 0;
+ profile_name = std::to_string(++index);
+ }
+
+ // Set a directory for the profile, derived from its name, so that
+ // recreating the profile will get the same directory.
+ const base::FilePath profile_path =
+ profiles_dir_.GetPath().AppendASCII(profile_name);
+ if (base::PathExists(profile_path)) {
+ EXPECT_TRUE(base::DirectoryExists(profile_path));
+ } else {
+ EXPECT_TRUE(base::CreateDirectory(profile_path));
+ }
+
+ // Prepare to specify preferences for the profile.
+ sync_preferences::PrefServiceMockFactory factory;
+ factory.set_user_prefs(base::WrapRefCounted(new TestingPrefStore()));
+ scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
+ new user_prefs::PrefRegistrySyncable);
+ sync_preferences::PrefServiceSyncable* regular_prefs =
+ factory.CreateSyncable(registry.get()).release();
+
+ // Set the preference associated with the policy for WebRTC remote-bound
+ // event logging.
+ RegisterUserProfilePrefs(registry.get());
+ if (policy_allows_remote_logging.has_value()) {
+ regular_prefs->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ policy_allows_remote_logging.value());
+ }
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ policy::PolicyMap policy_map;
+ if (has_device_level_policies) {
+ policy_map.Set("test-policy", policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_MACHINE,
+ policy::POLICY_SOURCE_PLATFORM,
+ std::make_unique<base::Value>("test"), nullptr);
+ }
+ provider_.UpdateChromePolicy(policy_map);
+#else
+ if (has_device_level_policies) {
+ // This should never happen.
+ // Device level policies cannot be set on Chrome OS and Android.
+ EXPECT_TRUE(false);
+ }
+#endif
+
+ // Build the profile.
+ TestingProfile::Builder profile_builder;
+ profile_builder.SetProfileName(profile_name);
+ profile_builder.SetPath(profile_path);
+ profile_builder.SetPrefService(base::WrapUnique(regular_prefs));
+ profile_builder.OverridePolicyConnectorIsManagedForTesting(
+ is_managed_profile);
+ std::unique_ptr<TestingProfile> profile = profile_builder.Build();
+
+ // Blocks on the unit under test's task runner, so that we won't proceed
+ // with the test (e.g. check that files were created) before finished
+ // processing this even (which is signaled to it from
+ // BrowserContext::EnableForBrowserContext).
+ WaitForPendingTasks();
+
+ return profile;
+ }
+
+ base::FilePath RemoteBoundLogsDir(BrowserContext* browser_context) const {
+ return RemoteBoundLogsDir(browser_context->GetPath());
+ }
+
+ base::FilePath RemoteBoundLogsDir(
+ const base::FilePath& browser_context_base_dir) const {
+ return GetRemoteBoundWebRtcEventLogsDir(browser_context_base_dir);
+ }
+
+ // Initiate an arbitrary synchronous operation, allowing any tasks pending
+ // on the manager's internal task queue to be completed.
+ // If given a RunLoop, we first block on it. The reason to do both is that
+ // with the RunLoop we wait on some tasks which we know also post additional
+ // tasks, then, after that chain is completed, we also wait for any potential
+ // leftovers. For example, the run loop could wait for n-1 files to be
+ // uploaded, then it is released when the last one's upload is initiated,
+ // then we wait for the last file's upload to be completed.
+ void WaitForPendingTasks(base::RunLoop* run_loop = nullptr) {
+ if (run_loop) {
+ run_loop->Run();
+ }
+
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ event_log_manager_->GetTaskRunnerForTesting()->PostTask(
+ FROM_HERE,
+ base::BindOnce([](base::WaitableEvent* event) { event->Signal(); },
+ &event));
+ event.Wait();
+ }
+
+ void SuppressUploading() {
+ if (!upload_suppressing_browser_context_) { // First suppression.
+ upload_suppressing_browser_context_ = CreateBrowserContext();
+ }
+ DCHECK(!upload_suppressing_rph_) << "Uploading already suppressed.";
+ upload_suppressing_rph_ = std::make_unique<MockRenderProcessHost>(
+ upload_suppressing_browser_context_.get());
+ const auto key = GetPeerConnectionKey(upload_suppressing_rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ void UnsuppressUploading() {
+ DCHECK(upload_suppressing_rph_) << "Uploading not suppressed.";
+ const auto key = GetPeerConnectionKey(upload_suppressing_rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ upload_suppressing_rph_.reset();
+ }
+
+ void ExpectLocalFileContents(const base::FilePath& file_path,
+ const std::string& expected_contents) {
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+ EXPECT_EQ(file_contents, expected_contents);
+ }
+
+ void ExpectRemoteFileContents(const base::FilePath& file_path,
+ const std::string& expected_event_log) {
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+
+ if (remote_log_extension_ == kWebRtcEventLogUncompressedExtension) {
+ EXPECT_EQ(file_contents, expected_event_log);
+ } else if (remote_log_extension_ == kWebRtcEventLogGzippedExtension) {
+ std::string uncompressed_log;
+ ASSERT_TRUE(
+ compression::GzipUncompress(file_contents, &uncompressed_log));
+ EXPECT_EQ(uncompressed_log, expected_event_log);
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ // When the peer connection's ID is not the focus of the test, this allows
+ // us to conveniently assign unique IDs to peer connections.
+ std::string GetUniqueId(int render_process_id, int lid) {
+ return std::to_string(render_process_id) + "_" + std::to_string(lid);
+ }
+ std::string GetUniqueId(const PeerConnectionKey& key) {
+ return GetUniqueId(key.render_process_id, key.lid);
+ }
+
+ bool UploadConditionsHold() {
+ base::RunLoop run_loop;
+ bool result;
+
+ auto callback = [](base::RunLoop* run_loop, bool* result_out, bool result) {
+ *result_out = result;
+ run_loop->QuitWhenIdle();
+ };
+
+ event_log_manager_->UploadConditionsHoldForTesting(
+ base::BindOnce(callback, &run_loop, &result));
+ run_loop.Run();
+
+ return result;
+ }
+
+ // Testing utilities.
+ content::BrowserTaskEnvironment task_environment_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+ base::test::ScopedCommandLine scoped_command_line_;
+ base::SimpleTestClock frozen_clock_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_;
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ policy::MockConfigurationPolicyProvider provider_;
+#endif
+
+ // The main loop, which allows waiting for the operations invoked on the
+ // unit-under-test to be completed. Do not use this object directly from the
+ // tests, since that would be error-prone. (Specifically, one must not produce
+ // two events that could produce replies, without waiting on the first reply
+ // in between.)
+ std::unique_ptr<base::RunLoop> run_loop_;
+
+ // Allows waiting for upload operations.
+ std::unique_ptr<base::RunLoop> uploader_run_loop_;
+
+ // Unit under test.
+ std::unique_ptr<WebRtcEventLogManager> event_log_manager_;
+
+ // Extensions associated with local/remote-bound event logs. Depends on
+ // whether they're compressed.
+ base::FilePath::StringPieceType local_log_extension_;
+ base::FilePath::StringPieceType remote_log_extension_;
+
+ // The directory which will contain all profiles.
+ base::ScopedTempDir profiles_dir_;
+
+ // Default BrowserContext and RenderProcessHost, to be used by tests which
+ // do not require anything fancy (such as seeding the BrowserContext with
+ // pre-existing logs files from a previous session, or working with multiple
+ // BrowserContext objects).
+
+ std::unique_ptr<TestingProfile> browser_context_;
+ BrowserContextId browser_context_id_;
+ std::unique_ptr<MockRenderProcessHost> rph_;
+
+ // Used for suppressing the upload of finished files, by creating an active
+ // remote-bound log associated with an independent BrowserContext which
+ // does not otherwise interfere with the test.
+ std::unique_ptr<TestingProfile> upload_suppressing_browser_context_;
+ std::unique_ptr<MockRenderProcessHost> upload_suppressing_rph_;
+
+ // The directory where we'll save local log files.
+ base::ScopedTempDir local_logs_base_dir_;
+ // local_logs_base_dir_ + log files' name prefix.
+ base::FilePath local_logs_base_path_;
+
+ // WebRtcEventLogManager instructs WebRTC, via PeerConnectionTracker, to
+ // only send WebRTC messages for certain peer connections. Some tests make
+ // sure that this is done correctly, by waiting for these notifications, then
+ // testing them.
+ // Because a single action - disabling of local logging - could crease a
+ // series of such instructions, we keep a queue of them. However, were one
+ // to actually test that scenario, one would have to account for the lack
+ // of a guarantee over the order in which these instructions are produced.
+ struct WebRtcStateChangeInstruction {
+ WebRtcStateChangeInstruction(PeerConnectionKey key, bool enabled)
+ : key(key), enabled(enabled) {}
+ PeerConnectionKey key;
+ bool enabled;
+ };
+ std::queue<WebRtcStateChangeInstruction> webrtc_state_change_instructions_;
+
+ // Observers for local/remote logging being started/stopped. By having them
+ // here, we achieve two goals:
+ // 1. Reduce boilerplate in the tests themselves.
+ // 2. Avoid lifetime issues, where the observer might be deallocated before
+ // a RenderProcessHost is deallocated (which can potentially trigger a
+ // callback on the observer).
+ NiceMock<MockWebRtcLocalEventLogsObserver> local_observer_;
+ NiceMock<MockWebRtcRemoteEventLogsObserver> remote_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManagerTestBase);
+};
+
+#if !defined(OS_ANDROID)
+
+class WebRtcEventLogManagerTest : public WebRtcEventLogManagerTestBase,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ WebRtcEventLogManagerTest() {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ // Use a low delay, or the tests would run for quite a long time.
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
+ }
+
+ ~WebRtcEventLogManagerTest() override = default;
+
+ void SetUp() override {
+ CreateWebRtcEventLogManager(Compression::GZIP_PERFECT_ESTIMATION);
+
+ WebRtcEventLogManagerTestBase::SetUp();
+
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
+ }
+};
+
+class WebRtcEventLogManagerTestCacheClearing
+ : public WebRtcEventLogManagerTest {
+ public:
+ ~WebRtcEventLogManagerTestCacheClearing() override = default;
+
+ void CreatePendingLogFiles(BrowserContext* browser_context) {
+ ASSERT_TRUE(pending_logs_.find(browser_context) == pending_logs_.end());
+ auto& elements = pending_logs_[browser_context];
+ elements = std::make_unique<BrowserContextAssociatedElements>();
+
+ for (size_t i = 0; i < kMaxActiveRemoteBoundWebRtcEventLogs; ++i) {
+ elements->rphs.push_back(
+ std::make_unique<MockRenderProcessHost>(browser_context));
+ const auto key = GetPeerConnectionKey(elements->rphs[i].get(), kLid);
+ elements->file_paths.push_back(CreatePendingRemoteLogFile(key));
+ ASSERT_TRUE(elements->file_paths[i]);
+ ASSERT_TRUE(base::PathExists(*elements->file_paths[i]));
+
+ pending_latest_mod_ = GetLastModificationTime(*elements->file_paths[i]);
+ if (pending_earliest_mod_.is_null()) { // First file.
+ pending_earliest_mod_ = pending_latest_mod_;
+ }
+ }
+ }
+
+ void ClearPendingLogFiles() { pending_logs_.clear(); }
+
+ base::Optional<base::FilePath> CreateRemoteLogFile(
+ const PeerConnectionKey& key,
+ bool pending) {
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+ EXPECT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(key));
+ if (pending) {
+ // Transition from ACTIVE to PENDING.
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+ }
+ return file_path;
+ }
+
+ base::Optional<base::FilePath> CreateActiveRemoteLogFile(
+ const PeerConnectionKey& key) {
+ return CreateRemoteLogFile(key, false);
+ }
+
+ base::Optional<base::FilePath> CreatePendingRemoteLogFile(
+ const PeerConnectionKey& key) {
+ return CreateRemoteLogFile(key, true);
+ }
+
+ protected:
+ // When closing a file, rather than check its last modification date, which
+ // is potentially expensive, WebRtcRemoteEventLogManager reads the system
+ // clock, which should be close enough. For tests, however, the difference
+ // could be enough to flake the tests, if not for this epsilon. Given the
+ // focus of the tests that use this, this epsilon can be arbitrarily large.
+ static const base::TimeDelta kEpsion;
+
+ struct BrowserContextAssociatedElements {
+ std::vector<std::unique_ptr<MockRenderProcessHost>> rphs;
+ std::vector<base::Optional<base::FilePath>> file_paths;
+ };
+
+ std::map<const BrowserContext*,
+ std::unique_ptr<BrowserContextAssociatedElements>>
+ pending_logs_;
+
+ // Latest modification times of earliest and latest pending log files.
+ base::Time pending_earliest_mod_;
+ base::Time pending_latest_mod_;
+};
+
+const base::TimeDelta WebRtcEventLogManagerTestCacheClearing::kEpsion =
+ base::TimeDelta::FromHours(1);
+
+class WebRtcEventLogManagerTestWithRemoteLoggingDisabled
+ : public WebRtcEventLogManagerTestBase,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ WebRtcEventLogManagerTestWithRemoteLoggingDisabled()
+ : feature_enabled_(GetParam()), policy_enabled_(!feature_enabled_) {
+ if (feature_enabled_) {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kWebRtcRemoteEventLog);
+ } else {
+ scoped_feature_list_.InitAndDisableFeature(
+ features::kWebRtcRemoteEventLog);
+ }
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestWithRemoteLoggingDisabled() override = default;
+
+ // Override CreateBrowserContext() to use policy_enabled_.
+ std::unique_ptr<TestingProfile> CreateBrowserContext() override {
+ return CreateBrowserContext(std::string());
+ }
+ std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name) override {
+ return CreateBrowserContext(profile_name, policy_enabled_,
+ false /* has_device_level_policies */,
+ policy_enabled_);
+ }
+ std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name,
+ bool is_managed_profile,
+ bool has_device_level_policies,
+ base::Optional<bool> policy_allows_remote_logging) override {
+ DCHECK_EQ(policy_enabled_, policy_allows_remote_logging.value());
+ return WebRtcEventLogManagerTestBase::CreateBrowserContext(
+ profile_name, is_managed_profile, has_device_level_policies,
+ policy_allows_remote_logging);
+ }
+
+ private:
+ const bool feature_enabled_; // Whether the Finch kill-switch is engaged.
+ const bool policy_enabled_; // Whether the policy is enabled for the profile.
+};
+
+class WebRtcEventLogManagerTestPolicy : public WebRtcEventLogManagerTestBase {
+ public:
+ ~WebRtcEventLogManagerTestPolicy() override = default;
+
+ // Defer to setup from the body.
+ void SetUp() override {}
+
+ void SetUp(bool feature_enabled) {
+ if (feature_enabled) {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kWebRtcRemoteEventLog);
+ } else {
+ scoped_feature_list_.InitAndDisableFeature(
+ features::kWebRtcRemoteEventLog);
+ }
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+
+ CreateWebRtcEventLogManager(Compression::GZIP_PERFECT_ESTIMATION);
+
+ WebRtcEventLogManagerTestBase::SetUp();
+ }
+};
+
+class WebRtcEventLogManagerTestUploadSuppressionDisablingFlag
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestUploadSuppressionDisablingFlag() {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
+ ::switches::kWebRtcRemoteEventLogUploadNoSuppression);
+
+ // Use a low delay, or the tests would run for quite a long time.
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
+
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestUploadSuppressionDisablingFlag() override = default;
+};
+
+class WebRtcEventLogManagerTestForNetworkConnectivity
+ : public WebRtcEventLogManagerTestBase,
+ public ::testing::WithParamInterface<
+ std::tuple<bool,
+ network::mojom::ConnectionType,
+ network::mojom::ConnectionType>> {
+ public:
+ WebRtcEventLogManagerTestForNetworkConnectivity()
+ : get_conn_type_is_sync_(std::get<0>(GetParam())),
+ supported_type_(std::get<1>(GetParam())),
+ unsupported_type_(std::get<2>(GetParam())) {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ // Use a low delay, or the tests would run for quite a long time.
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
+
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestForNetworkConnectivity() override = default;
+
+ void UnloadProfileAndSeedPendingLog() {
+ DCHECK(browser_context_path_.empty()) << "Not expected to be called twice.";
+
+ // Unload the profile, but remember where it stores its files (for sanity).
+ browser_context_path_ = browser_context_->GetPath();
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+ UnloadMainTestProfile();
+
+ // Seed the remote logs' directory with one log file, simulating the
+ // creation of logs in a previous session.
+ ASSERT_TRUE(base::CreateDirectory(remote_logs_dir));
+
+ base::FilePath file_path;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(
+ remote_logs_dir, kWebAppId, remote_log_extension_, base::Time::Now(),
+ &file_path, &file_));
+
+ expected_files_.emplace_back(browser_context_id_, file_path,
+ GetLastModificationTime(file_path));
+ }
+
+ const bool get_conn_type_is_sync_;
+ const network::mojom::ConnectionType supported_type_;
+ const network::mojom::ConnectionType unsupported_type_;
+
+ base::FilePath browser_context_path_; // For sanity over the test itself.
+ std::list<WebRtcLogFileInfo> expected_files_;
+ base::File file_;
+};
+
+class WebRtcEventLogManagerTestUploadDelay
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ ~WebRtcEventLogManagerTestUploadDelay() override = default;
+
+ void SetUp() override {
+ // Intercept and block the call to SetUp(). The test body will call
+ // the version that sets an upload delay instead.
+ }
+
+ void SetUp(size_t upload_delay_ms) {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs,
+ std::to_string(upload_delay_ms));
+
+ CreateWebRtcEventLogManager();
+
+ WebRtcEventLogManagerTestBase::SetUp();
+ }
+
+ // There's a trade-off between the test runtime and the likelihood of a
+ // false-positive (lowered when the time is increased).
+ // Since false-positives can be caught handled even if only manifesting
+ // occasionally, this value should be enough.
+ static const size_t kDefaultUploadDelayMs = 500;
+
+ // For tests where we don't intend to wait, prevent flakiness by setting
+ // an unrealistically long delay.
+ static const size_t kIntentionallyExcessiveDelayMs = 1000 * 1000 * 1000;
+};
+
+// For testing compression issues.
+class WebRtcEventLogManagerTestCompression
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestCompression() {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+ }
+
+ ~WebRtcEventLogManagerTestCompression() override = default;
+
+ void SetUp() override {
+ // Defer until Init(), which will allow the test body more control.
+ }
+
+ void Init(base::Optional<WebRtcEventLogCompression> remote_compression =
+ base::Optional<WebRtcEventLogCompression>()) {
+ CreateWebRtcEventLogManager(remote_compression);
+
+ WebRtcEventLogManagerTestBase::SetUp();
+ }
+};
+
+class WebRtcEventLogManagerTestIncognito
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestIncognito() : incognito_profile_(nullptr) {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestIncognito() override {
+ incognito_rph_.reset();
+ if (incognito_profile_) {
+ DCHECK(browser_context_);
+ browser_context_->DestroyOffTheRecordProfile();
+ }
+ }
+
+ void SetUp() override {
+ WebRtcEventLogManagerTestBase::SetUp();
+
+ incognito_profile_ = browser_context_->GetOffTheRecordProfile();
+ incognito_rph_ =
+ std::make_unique<MockRenderProcessHost>(incognito_profile_);
+ }
+
+ Profile* incognito_profile_;
+ std::unique_ptr<MockRenderProcessHost> incognito_rph_;
+};
+
+class WebRtcEventLogManagerTestHistory : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestHistory() {
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestHistory() override = default;
+
+ // Allows us to test that a time is as expected, down to UNIX time's
+ // lower resolution than our clock.
+ static bool IsSameTimeWhenTruncatedToSeconds(base::Time a, base::Time b) {
+ if (a.is_null() || b.is_null()) {
+ return false;
+ }
+ const base::TimeDelta delta = std::max(a, b) - std::min(a, b);
+ return delta.InSeconds() == 0;
+ }
+
+ // Allows us to check that the timestamps are roughly what we expect.
+ // Doing better than this would require too much effort.
+ static bool IsSmallTimeDelta(base::Time a, base::Time b) {
+ if (a.is_null() || b.is_null()) {
+ return false;
+ }
+
+ // Way more than is "small", to make sure tests don't become flaky.
+ // If the timestamp is ever off, it's likely to be off by more than this,
+ // though, or the problem would not truly be severe enough to worry about.
+ const base::TimeDelta small_delta = base::TimeDelta::FromMinutes(15);
+
+ return (std::max(a, b) - std::min(a, b) <= small_delta);
+ }
+};
+
+namespace {
+
+class PeerConnectionTrackerProxyForTesting
+ : public WebRtcEventLogManager::PeerConnectionTrackerProxy {
+ public:
+ explicit PeerConnectionTrackerProxyForTesting(
+ WebRtcEventLogManagerTestBase* test)
+ : test_(test) {}
+
+ ~PeerConnectionTrackerProxyForTesting() override = default;
+
+ void EnableWebRtcEventLogging(const PeerConnectionKey& key,
+ int output_period_ms) override {
+ test_->SetWebRtcEventLoggingState(key, true);
+ }
+ void DisableWebRtcEventLogging(const PeerConnectionKey& key) override {
+ test_->SetWebRtcEventLoggingState(key, false);
+ }
+
+ private:
+ WebRtcEventLogManagerTestBase* const test_;
+};
+
+// The factory for the following fake uploader produces a sequence of
+// uploaders which fail the test if given a file other than that which they
+// expect. The factory itself likewise fails the test if destroyed before
+// producing all expected uploaders, or if it's asked for more uploaders than
+// it expects to create. This allows us to test for sequences of uploads.
+class FileListExpectingWebRtcEventLogUploader : public WebRtcEventLogUploader {
+ public:
+ class Factory : public WebRtcEventLogUploader::Factory {
+ public:
+ Factory(std::list<WebRtcLogFileInfo>* expected_files,
+ bool result,
+ base::RunLoop* run_loop)
+ : result_(result), run_loop_(run_loop) {
+ expected_files_.swap(*expected_files);
+ if (expected_files_.empty()) {
+ run_loop_->QuitWhenIdle();
+ }
+ }
+
+ ~Factory() override { EXPECT_TRUE(expected_files_.empty()); }
+
+ std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) override {
+ if (expected_files_.empty()) {
+ EXPECT_FALSE(true); // More files uploaded than expected.
+ } else {
+ EXPECT_EQ(log_file.path, expected_files_.front().path);
+ // Because LoadMainTestProfile() and UnloadMainTestProfile() mess up the
+ // BrowserContextId in ways that would not happen in production,
+ // we cannot verify |log_file.browser_context_id| is correct.
+ // This is unimportant to the test.
+
+ base::DeleteFile(log_file.path, false);
+ expected_files_.pop_front();
+ }
+
+ if (expected_files_.empty()) {
+ run_loop_->QuitWhenIdle();
+ }
+
+ return std::make_unique<FileListExpectingWebRtcEventLogUploader>(
+ log_file, result_, std::move(callback));
+ }
+
+ private:
+ std::list<WebRtcLogFileInfo> expected_files_;
+ const bool result_;
+ base::RunLoop* const run_loop_;
+ };
+
+ // The logic is in the factory; the uploader just reports success so that the
+ // next file may become eligible for uploading.
+ FileListExpectingWebRtcEventLogUploader(const WebRtcLogFileInfo& log_file,
+ bool result,
+ UploadResultCallback callback)
+ : log_file_(log_file) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), log_file_.path, result));
+ }
+
+ ~FileListExpectingWebRtcEventLogUploader() override = default;
+
+ const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const override {
+ return log_file_;
+ }
+
+ bool Cancel() override {
+ NOTREACHED() << "Incompatible with this kind of test.";
+ return true;
+ }
+
+ private:
+ const WebRtcLogFileInfo log_file_;
+};
+
+} // namespace
+
+TEST_F(WebRtcEventLogManagerTest, PeerConnectionAddedReturnsTrue) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_TRUE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionAddedReturnsFalseIfAlreadyAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_FALSE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, PeerConnectionRemovedReturnsTrue) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionRemovedReturnsFalseIfNeverAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_FALSE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionRemovedReturnsFalseIfAlreadyRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ EXPECT_FALSE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, PeerConnectionSessionIdSetReturnsTrue) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfEmptyString) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, ""));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfPeerConnectionNeverAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfAlreadyCalledSameId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfPeerConnectionAlreadyRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfAlreadyCalledDifferentId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, "id1"));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, "id2"));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetCalledOnRecreatedPeerConnectionSanity) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest, EnableLocalLoggingReturnsTrue) {
+ EXPECT_TRUE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ EnableLocalLoggingReturnsFalseIfCalledWhenAlreadyEnabled) {
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_FALSE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest, DisableLocalLoggingReturnsTrue) {
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest, DisableLocalLoggingReturnsIfNeverEnabled) {
+ EXPECT_FALSE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest, DisableLocalLoggingReturnsIfAlreadyDisabled) {
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(DisableLocalLogging());
+ EXPECT_FALSE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsFalseAndFalseWhenAllLoggingDisabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ // Note that EnableLocalLogging() and StartRemoteLogging() weren't called.
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsFalseAndFalseForUnknownPeerConnection) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(EnableLocalLogging());
+ // Note that PeerConnectionAdded() wasn't called.
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsLocalTrueWhenPcKnownAndLocalLoggingOn) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(true, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsRemoteTrueWhenPcKnownAndRemoteLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, true));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsTrueAndTrueeWhenAllLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(true, true));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStartedNotCalledIfLocalLoggingEnabledWithoutPeerConnections) {
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ ASSERT_TRUE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStoppedNotCalledIfLocalLoggingDisabledWithoutPeerConnections) {
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStartedCalledForPeerConnectionAddedAndLocalLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStartedCalledForLocalLoggingEnabledAndPeerConnectionAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStoppedCalledAfterLocalLoggingDisabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStoppedCalledAfterPeerConnectionRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogCreatesEmptyFileWhenStarted) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogCreateAndWriteToFile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string log = "To strive, to seek, to find, and not to yield.";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(true, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, log);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogMultipleWritesToSameFile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string logs[] = {"Old age hath yet his honour and his toil;",
+ "Death closes all: but something ere the end,",
+ "Some work of noble note, may yet be done,",
+ "Not unbecoming men that strove with Gods."};
+ for (const std::string& log : logs) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(true, false));
+ }
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogFileSizeLimitNotExceeded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log = "There lies the port; the vessel puffs her sail:";
+ const size_t file_size_limit_bytes = log.length() / 2;
+
+ ASSERT_TRUE(EnableLocalLogging(file_size_limit_bytes));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // Failure is reported, because not everything could be written. The file
+ // will also be closed.
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // Additional calls to Write() have no effect.
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, "ignored"),
+ std::make_pair(false, false));
+
+ ExpectLocalFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogSanityOverUnlimitedFileSizes) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging(kWebRtcEventLogManagerUnlimitedFileSize));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string log1 = "Who let the dogs out?";
+ const std::string log2 = "Woof, woof, woof, woof, woof!";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log1), std::make_pair(true, false));
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log2), std::make_pair(true, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, log1 + log2);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogNoWriteAfterLogStopped) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string log_before = "log_before_stop";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log_before),
+ std::make_pair(true, false));
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ const std::string log_after = "log_after_stop";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log_after),
+ std::make_pair(false, false));
+
+ ExpectLocalFileContents(*file_path, log_before);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogOnlyWritesTheLogsAfterStarted) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Calls to Write() before the log was started are ignored.
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ const std::string log1 = "The lights begin to twinkle from the rocks:";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log1), std::make_pair(false, false));
+ ASSERT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+
+ base::Optional<base::FilePath> file_path;
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // Calls after the log started have an effect. The calls to Write() from
+ // before the log started are not remembered.
+ const std::string log2 = "The long day wanes: the slow moon climbs: the deep";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log2), std::make_pair(true, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, log2);
+}
+
+// Note: This test also covers the scenario LocalLogExistingFilesNotOverwritten,
+// which is therefore not explicitly tested.
+TEST_F(WebRtcEventLogManagerTest, LocalLoggingRestartCreatesNewFile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ const std::vector<std::string> logs = {"<setup>", "<punchline>", "<encore>"};
+ std::vector<base::Optional<PeerConnectionKey>> keys(logs.size());
+ std::vector<base::Optional<base::FilePath>> file_paths(logs.size());
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ON_CALL(local_observer_, OnLocalLogStarted(_, _))
+ .WillByDefault(Invoke(SaveKeyAndFilePathTo(&keys[i], &file_paths[i])));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(keys[i]);
+ ASSERT_EQ(*keys[i], key);
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, logs[i]), std::make_pair(true, false));
+ ASSERT_TRUE(DisableLocalLogging());
+ }
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ExpectLocalFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogMultipleActiveFiles) {
+ ASSERT_TRUE(EnableLocalLogging());
+
+ std::list<MockRenderProcessHost> rphs;
+ for (size_t i = 0; i < 3; ++i) {
+ rphs.emplace_back(browser_context_.get()); // (MockRenderProcessHost ctor)
+ }
+
+ std::vector<PeerConnectionKey> keys;
+ for (auto& rph : rphs) {
+ keys.push_back(GetPeerConnectionKey(&rph, kLid));
+ }
+
+ std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ON_CALL(local_observer_, OnLocalLogStarted(keys[i], _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ }
+
+ std::vector<std::string> logs;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ logs.emplace_back(std::to_string(rph_->GetID()) + std::to_string(kLid));
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(true, false));
+ }
+
+ // Make sure the file woulds be closed, so that we could safely read them.
+ ASSERT_TRUE(DisableLocalLogging());
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ExpectLocalFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogLimitActiveLocalLogFiles) {
+ ASSERT_TRUE(EnableLocalLogging());
+
+ const int kMaxLocalLogFiles =
+ static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
+ for (int i = 0; i < kMaxLocalLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ const auto last_key = GetPeerConnectionKey(rph_.get(), kMaxLocalLogFiles);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+}
+
+// When a log reaches its maximum size limit, it is closed, and no longer
+// counted towards the limit.
+TEST_F(WebRtcEventLogManagerTest, LocalLogFilledLogNotCountedTowardsLogsLimit) {
+ const std::string log = "very_short_log";
+ ASSERT_TRUE(EnableLocalLogging(log.size()));
+
+ const int kMaxLocalLogFiles =
+ static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
+ for (int i = 0; i < kMaxLocalLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ // By writing to one of the logs, we fill it and end up closing it, allowing
+ // an additional log to be written.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ EXPECT_EQ(OnWebRtcEventLogWrite(removed_key, log),
+ std::make_pair(true, false));
+
+ // We now have room for one additional log.
+ const auto last_key = GetPeerConnectionKey(rph_.get(), kMaxLocalLogFiles);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(last_key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LocalLogForRemovedPeerConnectionNotCountedTowardsLogsLimit) {
+ ASSERT_TRUE(EnableLocalLogging());
+
+ const int kMaxLocalLogFiles =
+ static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
+ for (int i = 0; i < kMaxLocalLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ // When one peer connection is removed, one log is stopped, thereby allowing
+ // an additional log to be opened.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(removed_key)).Times(1);
+ ASSERT_TRUE(PeerConnectionRemoved(removed_key));
+
+ // We now have room for one additional log.
+ const auto last_key = GetPeerConnectionKey(rph_.get(), kMaxLocalLogFiles);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(last_key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogIllegalPath) {
+ // Since the log file won't be properly opened, these will not be called.
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+
+ // See the documentation of the function for why |true| is expected despite
+ // the path being illegal.
+ const base::FilePath illegal_path(FILE_PATH_LITERAL(":!@#$%|`^&*\\/"));
+ EXPECT_TRUE(EnableLocalLogging(illegal_path));
+
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+}
+
+#if defined(OS_POSIX)
+TEST_F(WebRtcEventLogManagerTest, LocalLogLegalPathWithoutPermissionsSanity) {
+ RemoveWritePermissions(local_logs_base_dir_.GetPath());
+
+ // Since the log file won't be properly opened, these will not be called.
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+
+ // See the documentation of the function for why |true| is expected despite
+ // the path being illegal.
+ EXPECT_TRUE(EnableLocalLogging(local_logs_base_path_));
+
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+
+ // Write() has no effect (but is handled gracefully).
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "Why did the chicken cross the road?"),
+ std::make_pair(false, false));
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+
+ // Logging was enabled, even if it had no effect because of the lacking
+ // permissions; therefore, the operation of disabling it makes sense.
+ EXPECT_TRUE(DisableLocalLogging());
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogEmptyStringHandledGracefully) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // By writing a log after the empty string, we show that no odd behavior is
+ // encountered, such as closing the file (an actual bug from WebRTC).
+ const std::vector<std::string> logs = {"<setup>", "", "<encore>"};
+
+ base::Optional<base::FilePath> file_path;
+
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, logs[i]), std::make_pair(true, false));
+ }
+ ASSERT_TRUE(DisableLocalLogging());
+
+ ExpectLocalFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogFilenameMatchesExpectedFormat) {
+ using StringType = base::FilePath::StringType;
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const base::Time::Exploded frozen_time_exploded{
+ 2017, // Four digit year "2007"
+ 9, // 1-based month (values 1 = January, etc.)
+ 3, // 0-based day of week (0 = Sunday, etc.)
+ 6, // 1-based day of month (1-31)
+ 10, // Hour within the current day (0-23)
+ 43, // Minute within the current hour (0-59)
+ 29, // Second within the current minute.
+ 0 // Milliseconds within the current second (0-999)
+ };
+ ASSERT_TRUE(frozen_time_exploded.HasValidValues());
+ FreezeClockAt(frozen_time_exploded);
+
+ const StringType user_defined = FILE_PATH_LITERAL("user_defined");
+ const base::FilePath local_logs_base_path =
+ local_logs_base_dir_.GetPath().Append(user_defined);
+
+ ASSERT_TRUE(EnableLocalLogging(local_logs_base_path));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ const StringType date = FILE_PATH_LITERAL("20170906");
+ const StringType time = FILE_PATH_LITERAL("1043");
+ base::FilePath expected_path = local_logs_base_path;
+ expected_path = local_logs_base_path.InsertBeforeExtension(
+ FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
+ FILE_PATH_LITERAL("_") + NumberToStringType(rph_->GetID()) +
+ FILE_PATH_LITERAL("_") + NumberToStringType(kLid));
+ expected_path = expected_path.AddExtension(local_log_extension_);
+
+ EXPECT_EQ(file_path, expected_path);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LocalLogFilenameMatchesExpectedFormatRepeatedFilename) {
+ using StringType = base::FilePath::StringType;
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path_1;
+ base::Optional<base::FilePath> file_path_2;
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillOnce(Invoke(SaveFilePathTo(&file_path_1)))
+ .WillOnce(Invoke(SaveFilePathTo(&file_path_2)));
+
+ const base::Time::Exploded frozen_time_exploded{
+ 2017, // Four digit year "2007"
+ 9, // 1-based month (values 1 = January, etc.)
+ 3, // 0-based day of week (0 = Sunday, etc.)
+ 6, // 1-based day of month (1-31)
+ 10, // Hour within the current day (0-23)
+ 43, // Minute within the current hour (0-59)
+ 29, // Second within the current minute.
+ 0 // Milliseconds within the current second (0-999)
+ };
+ ASSERT_TRUE(frozen_time_exploded.HasValidValues());
+ FreezeClockAt(frozen_time_exploded);
+
+ const StringType user_defined_portion = FILE_PATH_LITERAL("user_defined");
+ const base::FilePath local_logs_base_path =
+ local_logs_base_dir_.GetPath().Append(user_defined_portion);
+
+ ASSERT_TRUE(EnableLocalLogging(local_logs_base_path));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path_1);
+ ASSERT_FALSE(file_path_1->empty());
+
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ const StringType date = FILE_PATH_LITERAL("20170906");
+ const StringType time = FILE_PATH_LITERAL("1043");
+ base::FilePath expected_path_1 = local_logs_base_path;
+ expected_path_1 = local_logs_base_path.InsertBeforeExtension(
+ FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
+ FILE_PATH_LITERAL("_") + NumberToStringType(rph_->GetID()) +
+ FILE_PATH_LITERAL("_") + NumberToStringType(kLid));
+ expected_path_1 = expected_path_1.AddExtension(local_log_extension_);
+
+ ASSERT_EQ(file_path_1, expected_path_1);
+
+ ASSERT_TRUE(DisableLocalLogging());
+ ASSERT_TRUE(EnableLocalLogging(local_logs_base_path));
+ ASSERT_TRUE(file_path_2);
+ ASSERT_FALSE(file_path_2->empty());
+
+ const base::FilePath expected_path_2 =
+ expected_path_1.InsertBeforeExtension(FILE_PATH_LITERAL(" (1)"));
+
+ // Focus of the test - starting the same log again produces a new file,
+ // with an expected new filename.
+ ASSERT_EQ(file_path_2, expected_path_2);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStartedNotCalledIfRemoteLoggingNotEnabled) {
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(_, _, _)).Times(0);
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStoppedNotCalledIfRemoteLoggingNotEnabled) {
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(_)).Times(0);
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStartedCalledIfRemoteLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStoppedCalledIfRemoteLoggingEnabledThenPcRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ BrowserContextInitializationCreatesDirectoryForRemoteLogs) {
+ auto browser_context = CreateBrowserContext();
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(browser_context.get());
+ EXPECT_TRUE(base::DirectoryExists(remote_logs_path));
+ EXPECT_TRUE(base::IsDirectoryEmpty(remote_logs_path));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfUnknownPeerConnection) {
+ const auto key = GetPeerConnectionKey(rph_.get(), 0);
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, "id", nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureUnknownOrInactivePeerConnection);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfUnknownSessionId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, "wrong_id", nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureUnknownOrInactivePeerConnection);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsTrueIfKnownSessionId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ EXPECT_TRUE(StartRemoteLogging(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfRestartAttempt) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(StartRemoteLogging(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureAlreadyLogging);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfUnlimitedFileSize) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId,
+ kWebRtcEventLogManagerUnlimitedFileSize, 0,
+ kWebAppId, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureUnlimitedSizeDisallowed);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsTrueIfFileSizeAtOrBelowLimit) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ EXPECT_TRUE(StartRemoteLogging(key, kSessionId, kMaxRemoteLogFileSizeBytes, 0,
+ kWebAppId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfFileSizeToSmall) {
+ const size_t min_size =
+ CreateLogFileWriterFactory(Compression::GZIP_NULL_ESTIMATION)
+ ->MinFileSizeBytes();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, min_size - 1, 0, kWebAppId,
+ nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureMaxSizeTooSmall);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfExcessivelyLargeFileSize) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId,
+ kMaxRemoteLogFileSizeBytes + 1, 0, kWebAppId,
+ nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureMaxSizeTooLarge);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfExcessivelyLargeOutputPeriodMs) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, kMaxRemoteLogFileSizeBytes,
+ kMaxOutputPeriodMs + 1, kWebAppId, nullptr,
+ &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureOutputPeriodMsTooLarge);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfPeerConnectionAlreadyClosed) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureUnknownOrInactivePeerConnection);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingDoesNotReturnIdWhenUnsuccessful) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ std::string log_id;
+ ASSERT_FALSE(StartRemoteLogging(key, kSessionId, &log_id));
+
+ EXPECT_TRUE(log_id.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsLegalIdWhenSuccessful) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+
+ std::string log_id;
+ ASSERT_TRUE(StartRemoteLogging(key, kSessionId, &log_id));
+
+ EXPECT_EQ(log_id.size(), 32u);
+ EXPECT_EQ(log_id.find_first_not_of("0123456789ABCDEF"), std::string::npos);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingSavesToFileWithCorrectFileNameFormat) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ std::string log_id;
+ ASSERT_TRUE(StartRemoteLogging(key, &log_id));
+
+ // Compare filename (without extension).
+ const std::string filename =
+ file_path->BaseName().RemoveExtension().MaybeAsASCII();
+ ASSERT_FALSE(filename.empty());
+
+ const std::string expected_filename =
+ std::string(kRemoteBoundWebRtcEventLogFileNamePrefix) + "_" +
+ std::to_string(kWebAppId) + "_" + log_id;
+ EXPECT_EQ(filename, expected_filename);
+
+ // Compare extension.
+ EXPECT_EQ(
+ base::FilePath::kExtensionSeparator + remote_log_extension_.as_string(),
+ file_path->Extension());
+}
+
+TEST_F(WebRtcEventLogManagerTest, StartRemoteLoggingCreatesEmptyFile) {
+ base::Optional<base::FilePath> file_path;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ // Close file before examining its contents.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogFileCreatedInCorrectDirectory) {
+ // Set up separate browser contexts; each one will get one log.
+ constexpr size_t kLogsNum = 3;
+ std::unique_ptr<TestingProfile> browser_contexts[kLogsNum];
+ std::vector<std::unique_ptr<MockRenderProcessHost>> rphs;
+ for (size_t i = 0; i < kLogsNum; ++i) {
+ browser_contexts[i] = CreateBrowserContext();
+ rphs.emplace_back(
+ std::make_unique<MockRenderProcessHost>(browser_contexts[i].get()));
+ }
+
+ // Prepare to store the logs' paths in distinct memory locations.
+ base::Optional<base::FilePath> file_paths[kLogsNum];
+ for (size_t i = 0; i < kLogsNum; ++i) {
+ const auto key = GetPeerConnectionKey(rphs[i].get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_paths[i])));
+ }
+
+ // Start one log for each browser context.
+ for (const auto& rph : rphs) {
+ const auto key = GetPeerConnectionKey(&*rph, kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ }
+
+ // All log files must be created in their own context's directory.
+ for (size_t i = 0; i < base::size(browser_contexts); ++i) {
+ ASSERT_TRUE(file_paths[i]);
+ EXPECT_TRUE(browser_contexts[i]->GetPath().IsParent(*file_paths[i]));
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingSanityIfDuplicateIdsInDifferentRendererProcesses) {
+ std::unique_ptr<MockRenderProcessHost> rphs[2] = {
+ std::make_unique<MockRenderProcessHost>(browser_context_.get()),
+ std::make_unique<MockRenderProcessHost>(browser_context_.get()),
+ };
+
+ PeerConnectionKey keys[2] = {GetPeerConnectionKey(rphs[0].get(), 0),
+ GetPeerConnectionKey(rphs[1].get(), 0)};
+
+ // The ID is shared, but that's not a problem, because the renderer process
+ // are different.
+ const std::string id = "shared_id";
+ ASSERT_TRUE(PeerConnectionAdded(keys[0]));
+ PeerConnectionSessionIdSet(keys[0], id);
+ ASSERT_TRUE(PeerConnectionAdded(keys[1]));
+ PeerConnectionSessionIdSet(keys[1], id);
+
+ // Make sure the logs get written to separate files.
+ base::Optional<base::FilePath> file_paths[2];
+ for (size_t i = 0; i < 2; ++i) {
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_paths[i])));
+ }
+
+ EXPECT_TRUE(StartRemoteLogging(keys[0], id));
+ EXPECT_TRUE(StartRemoteLogging(keys[1], id));
+
+ EXPECT_TRUE(file_paths[0]);
+ EXPECT_TRUE(file_paths[1]);
+ EXPECT_NE(file_paths[0], file_paths[1]);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteWritesToTheRemoteBoundFile) {
+ base::Optional<base::FilePath> file_path;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ const char* const log = "1 + 1 = 3";
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, true));
+
+ // Close file before examining its contents.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(*file_path, log);
+}
+
+TEST_F(WebRtcEventLogManagerTest, WriteToBothLocalAndRemoteFiles) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> local_path;
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&local_path)));
+
+ base::Optional<base::FilePath> remote_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&remote_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ ASSERT_TRUE(local_path);
+ ASSERT_FALSE(local_path->empty());
+ ASSERT_TRUE(remote_path);
+ ASSERT_FALSE(remote_path->empty());
+
+ const char* const log = "logloglog";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(true, true));
+
+ // Ensure the flushing of the file to disk before attempting to read them.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*local_path, log);
+ ExpectRemoteFileContents(*remote_path, log);
+}
+
+TEST_F(WebRtcEventLogManagerTest, MultipleWritesToSameRemoteBoundLogfile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string logs[] = {"ABC", "DEF", "XYZ"};
+ for (const std::string& log : logs) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, true));
+ }
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogFileSizeLimitNotExceededSingleWrite) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log = "tpyo";
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(
+ StartRemoteLogging(key, kSessionId, GzippedSize(log) - 1, 0, kWebAppId));
+
+ // Failure is reported, because not everything could be written. The file
+ // will also be closed.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // No partial writes occurred.
+ ExpectRemoteFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogFileSizeLimitNotExceededMultipleWrites) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log1 = "abcabc";
+ const std::string log2 = "defghijklmnopqrstuvwxyz";
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(
+ StartRemoteLogging(key, kSessionId, 1 + GzippedSize(log1), 0, kWebAppId));
+
+ // First write works.
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log1), std::make_pair(false, true));
+
+ // On the second write, failure is reported, because not everything could be
+ // written. The file will also be closed.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log2), std::make_pair(false, false));
+
+ ExpectRemoteFileContents(*file_path, log1);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LogMultipleActiveRemoteLogsSameBrowserContext) {
+ const std::vector<PeerConnectionKey> keys = {
+ GetPeerConnectionKey(rph_.get(), 0), GetPeerConnectionKey(rph_.get(), 1),
+ GetPeerConnectionKey(rph_.get(), 2)};
+
+ std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(keys[i]));
+ ASSERT_TRUE(StartRemoteLogging(keys[i]));
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ }
+
+ std::vector<std::string> logs;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ logs.emplace_back(std::to_string(rph_->GetID()) + std::to_string(i));
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(false, true));
+ }
+
+ // Make sure the file woulds be closed, so that we could safely read them.
+ for (auto& key : keys) {
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ExpectRemoteFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LogMultipleActiveRemoteLogsDifferentBrowserContexts) {
+ constexpr size_t kLogsNum = 3;
+ std::unique_ptr<TestingProfile> browser_contexts[kLogsNum];
+ std::vector<std::unique_ptr<MockRenderProcessHost>> rphs;
+ for (size_t i = 0; i < kLogsNum; ++i) {
+ browser_contexts[i] = CreateBrowserContext();
+ rphs.emplace_back(
+ std::make_unique<MockRenderProcessHost>(browser_contexts[i].get()));
+ }
+
+ std::vector<PeerConnectionKey> keys;
+ for (auto& rph : rphs) {
+ keys.push_back(GetPeerConnectionKey(rph.get(), kLid));
+ }
+
+ std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(keys[i]));
+ ASSERT_TRUE(StartRemoteLogging(keys[i]));
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ }
+
+ std::vector<std::string> logs;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ logs.emplace_back(std::to_string(rph_->GetID()) + std::to_string(i));
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(false, true));
+ }
+
+ // Make sure the file woulds be closed, so that we could safely read them.
+ for (auto& key : keys) {
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ExpectRemoteFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, DifferentRemoteLogsMayHaveDifferentMaximums) {
+ const std::string logs[2] = {"abra", "cadabra"};
+ std::vector<base::Optional<base::FilePath>> file_paths(base::size(logs));
+ std::vector<PeerConnectionKey> keys;
+ for (size_t i = 0; i < base::size(logs); ++i) {
+ keys.push_back(GetPeerConnectionKey(rph_.get(), i));
+ ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ const std::string session_id = GetUniqueId(keys[i]);
+ ASSERT_TRUE(PeerConnectionSessionIdSet(keys[i], session_id));
+ ASSERT_TRUE(StartRemoteLogging(keys[i], session_id, GzippedSize(logs[i]), 0,
+ kWebAppId));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ // The write is successful, but the file closed, indicating that the
+ // maximum file size has been reached.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(keys[i])).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(false, true));
+ ASSERT_TRUE(file_paths[i]);
+ ExpectRemoteFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogFileClosedWhenCapacityReached) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log = "Let X equal X.";
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key), GzippedSize(log), 0,
+ kWebAppId));
+ ASSERT_TRUE(file_path);
+
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, true));
+}
+
+#if defined(OS_POSIX)
+// TODO(crbug.com/775415): Add unit tests for lacking read permissions when
+// looking to upload the file.
+TEST_F(WebRtcEventLogManagerTest,
+ FailureToCreateRemoteLogsDirHandledGracefully) {
+ const base::FilePath browser_context_dir = browser_context_->GetPath();
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(browser_context_.get());
+
+ // Unload the profile, delete its remove logs directory, and remove write
+ // permissions from it, thereby preventing it from being created again.
+ UnloadMainTestProfile();
+ ASSERT_TRUE(base::DeleteFile(remote_logs_path, /*recursive=*/true));
+ RemoveWritePermissions(browser_context_dir);
+
+ // Graceful handling by BrowserContext::EnableForBrowserContext, despite
+ // failing to create the remote logs' directory..
+ LoadMainTestProfile();
+ EXPECT_FALSE(base::DirectoryExists(remote_logs_path));
+
+ // Graceful handling of PeerConnectionAdded: True returned because the
+ // remote-logs' manager can still safely reason about the state of peer
+ // connections even if one of its browser contexts is defective.)
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Graceful handling of StartRemoteLogging: False returned because it's
+ // impossible to write the log to a file.
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureLoggingDisabledBrowserContext);
+
+ // Graceful handling of OnWebRtcEventLogWrite: False returned because the
+ // log could not be written at all, let alone in its entirety.
+ const char* const log = "This is not a log.";
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // Graceful handling of PeerConnectionRemoved: True returned because the
+ // remote-logs' manager can still safely reason about the state of peer
+ // connections even if one of its browser contexts is defective.
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, GracefullyHandleFailureToStartRemoteLogFile) {
+ // WebRTC logging will not be turned on.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(_, _, _)).Times(0);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(_)).Times(0);
+
+ // Remove write permissions from the directory.
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(browser_context_.get());
+ ASSERT_TRUE(base::DirectoryExists(remote_logs_path));
+ RemoveWritePermissions(remote_logs_path);
+
+ // StartRemoteLogging() will now fail.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureFileCreationError);
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "abc"), std::make_pair(false, false));
+ EXPECT_TRUE(base::IsDirectoryEmpty(remote_logs_path));
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogLimitActiveLogFiles) {
+ for (int i = 0; i < kMaxActiveRemoteLogFiles + 1; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ }
+
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(StartRemoteLogging(key));
+ }
+
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(_, _, _)).Times(0);
+ const auto new_key =
+ GetPeerConnectionKey(rph_.get(), kMaxActiveRemoteLogFiles);
+ EXPECT_FALSE(StartRemoteLogging(new_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogFilledLogNotCountedTowardsLogsLimit) {
+ const std::string log = "very_short_log";
+
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key), GzippedSize(log), 0,
+ kWebAppId));
+ }
+
+ // By writing to one of the logs until it reaches capacity, we fill it,
+ // causing it to close, therefore allowing an additional log.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ EXPECT_EQ(OnWebRtcEventLogWrite(removed_key, log),
+ std::make_pair(false, true));
+
+ // We now have room for one additional log.
+ const auto new_key =
+ GetPeerConnectionKey(rph_.get(), kMaxActiveRemoteLogFiles);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(new_key, _, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(new_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(new_key));
+ ASSERT_TRUE(StartRemoteLogging(new_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogForRemovedPeerConnectionNotCountedTowardsLogsLimit) {
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(StartRemoteLogging(key));
+ }
+
+ // By removing a peer connection associated with one of the logs, we allow
+ // an additional log.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionRemoved(removed_key));
+
+ // We now have room for one additional log.
+ const auto last_key =
+ GetPeerConnectionKey(rph_.get(), kMaxActiveRemoteLogFiles);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(last_key, _, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(last_key));
+ ASSERT_TRUE(StartRemoteLogging(last_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ ActiveLogsForBrowserContextCountedTowardsItsPendingsLogsLimit) {
+ SuppressUploading();
+
+ // Produce kMaxPendingRemoteLogFiles pending logs.
+ for (int i = 0; i < kMaxPendingRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ // It is now impossible to start another *active* log for that BrowserContext,
+ // because we have too many pending logs (and active logs become pending
+ // once completed).
+ const auto forbidden =
+ GetPeerConnectionKey(rph_.get(), kMaxPendingRemoteLogFiles);
+ ASSERT_TRUE(PeerConnectionAdded(forbidden));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(forbidden));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(forbidden, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ ObserveLimitOnMaximumPendingLogsPerBrowserContext) {
+ SuppressUploading();
+
+ // Create additional BrowserContexts for the test.
+ std::unique_ptr<TestingProfile> browser_contexts[2] = {
+ CreateBrowserContext(), CreateBrowserContext()};
+ std::unique_ptr<MockRenderProcessHost> rphs[2] = {
+ std::make_unique<MockRenderProcessHost>(browser_contexts[0].get()),
+ std::make_unique<MockRenderProcessHost>(browser_contexts[1].get())};
+
+ // Allowed to start kMaxPendingRemoteLogFiles for each BrowserContext.
+ // Specifically, we can do it for the first BrowserContext.
+ for (int i = 0; i < kMaxPendingRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rphs[0].get(), i);
+ // The log could be opened:
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ // The log changes state from ACTIVE to PENDING:
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ // Not allowed to start any more remote-bound logs for the BrowserContext on
+ // which the limit was reached.
+ const auto key0 =
+ GetPeerConnectionKey(rphs[0].get(), kMaxPendingRemoteLogFiles);
+ ASSERT_TRUE(PeerConnectionAdded(key0));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key0));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key0, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed);
+
+ // Other BrowserContexts aren't limit by the previous one's limit.
+ const auto key1 = GetPeerConnectionKey(rphs[1].get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(key1));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key1));
+ EXPECT_TRUE(StartRemoteLogging(key1));
+}
+
+// This also tests the scenario UploadOrderDependsOnLastModificationTime.
+TEST_F(WebRtcEventLogManagerTest,
+ LogsFromPreviousSessionBecomePendingLogsWhenBrowserContextInitialized) {
+ // Unload the profile, but remember where it stores its files.
+ const base::FilePath browser_context_path = browser_context_->GetPath();
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+ UnloadMainTestProfile();
+
+ // Seed the remote logs' directory with log files, simulating the
+ // creation of logs in a previous session.
+ std::list<WebRtcLogFileInfo> expected_files;
+ ASSERT_TRUE(base::CreateDirectory(remote_logs_dir));
+
+ // Avoid arbitrary ordering due to files being created in the same second.
+ // This is OK in production, but can confuse the test, which expects a
+ // specific order.
+ base::Time time =
+ base::Time::Now() -
+ base::TimeDelta::FromSeconds(kMaxPendingRemoteBoundWebRtcEventLogs);
+
+ for (size_t i = 0; i < kMaxPendingRemoteBoundWebRtcEventLogs; ++i) {
+ time += base::TimeDelta::FromSeconds(1);
+
+ base::FilePath file_path;
+ base::File file;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(remote_logs_dir, kWebAppId,
+ remote_log_extension_, time,
+ &file_path, &file));
+
+ expected_files.emplace_back(browser_context_id_, file_path, time);
+ }
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+// It is possible for remote-bound logs to be compressed or uncompressed.
+// We show that logs from a previous session are captured even if they are
+// different, with regards to compression, compared to last time.
+TEST_F(WebRtcEventLogManagerTest,
+ LogsCapturedPreviouslyMadePendingEvenIfDifferentExtensionsUsed) {
+ // Unload the profile, but remember where it stores its files.
+ const base::FilePath browser_context_path = browser_context_->GetPath();
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+ UnloadMainTestProfile();
+
+ // Seed the remote logs' directory with log files, simulating the
+ // creation of logs in a previous session.
+ std::list<WebRtcLogFileInfo> expected_files;
+ ASSERT_TRUE(base::CreateDirectory(remote_logs_dir));
+
+ base::FilePath::StringPieceType extensions[] = {
+ kWebRtcEventLogUncompressedExtension, kWebRtcEventLogGzippedExtension};
+ ASSERT_LE(base::size(extensions), kMaxPendingRemoteBoundWebRtcEventLogs)
+ << "Lacking test coverage.";
+
+ // Avoid arbitrary ordering due to files being created in the same second.
+ // This is OK in production, but can confuse the test, which expects a
+ // specific order.
+ base::Time time =
+ base::Time::Now() -
+ base::TimeDelta::FromSeconds(kMaxPendingRemoteBoundWebRtcEventLogs);
+
+ for (size_t i = 0, ext = 0; i < kMaxPendingRemoteBoundWebRtcEventLogs; ++i) {
+ time += base::TimeDelta::FromSeconds(1);
+
+ const auto& extension = extensions[ext];
+ ext = (ext + 1) % base::size(extensions);
+
+ base::FilePath file_path;
+ base::File file;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(remote_logs_dir, kWebAppId, extension,
+ time, &file_path, &file));
+
+ expected_files.emplace_back(browser_context_id_, file_path, time);
+ }
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTest,
+ WhenPeerConnectionRemovedFinishedRemoteLogUploadedAndFileDeleted) {
+ // |upload_result| show that the files are deleted independent of the
+ // upload's success / failure.
+ const bool upload_result = GetParam();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, upload_result, &run_loop));
+
+ // Peer connection removal triggers next upload.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+
+ EXPECT_TRUE(
+ base::IsDirectoryEmpty(RemoteBoundLogsDir(browser_context_.get())));
+}
+
+TEST_P(WebRtcEventLogManagerTest, DestroyedRphTriggersLogUpload) {
+ // |upload_result| show that the files are deleted independent of the
+ // upload's success / failure.
+ const bool upload_result = GetParam();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, upload_result, &run_loop));
+
+ // RPH destruction stops all active logs and triggers next upload.
+ rph_.reset();
+
+ WaitForPendingTasks(&run_loop);
+
+ EXPECT_TRUE(
+ base::IsDirectoryEmpty(RemoteBoundLogsDir(browser_context_.get())));
+}
+
+// Note that SuppressUploading() and UnSuppressUploading() use the behavior
+// guaranteed by this test.
+TEST_F(WebRtcEventLogManagerTest, UploadOnlyWhenNoActivePeerConnections) {
+ const auto untracked = GetPeerConnectionKey(rph_.get(), 0);
+ const auto tracked = GetPeerConnectionKey(rph_.get(), 1);
+
+ // Suppresses the uploading of the "tracked" peer connection's log.
+ ASSERT_TRUE(PeerConnectionAdded(untracked));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(untracked));
+
+ // The tracked peer connection's log is not uploaded when finished, because
+ // another peer connection is still active.
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(tracked, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(tracked));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(tracked));
+ ASSERT_TRUE(StartRemoteLogging(tracked));
+ ASSERT_TRUE(log_file);
+ ASSERT_TRUE(PeerConnectionRemoved(tracked));
+
+ // Perform another action synchronously, so that we may be assured that the
+ // observer's lack of callbacks was not a timing fluke.
+ OnWebRtcEventLogWrite(untracked, "Ook!");
+
+ // Having been convinced that |tracked|'s log was not uploded while
+ // |untracked| was active, close |untracked| and see that |tracked|'s log
+ // is now uploaded.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_uploads = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_uploads, true, &run_loop));
+ ASSERT_TRUE(PeerConnectionRemoved(untracked));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTest, ExpiredFilesArePrunedRatherThanUploaded) {
+ constexpr size_t kExpired = 0;
+ constexpr size_t kFresh = 1;
+ DCHECK_GE(kMaxPendingRemoteBoundWebRtcEventLogs, 2u)
+ << "Please restructure the test to use separate browser contexts.";
+
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+
+ UnloadMainTestProfile();
+
+ base::FilePath file_paths[2];
+ for (size_t i = 0; i < 2; ++i) {
+ base::File file;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(
+ remote_logs_dir, kWebAppId, remote_log_extension_, base::Time::Now(),
+ &file_paths[i], &file));
+ }
+
+ // Touch() requires setting the last access time as well. Keep it current,
+ // showing that only the last modification time matters.
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(file_paths[0], &file_info));
+
+ // Set the expired file's last modification time to past max retention.
+ const base::Time expired_mod_time = base::Time::Now() -
+ kRemoteBoundWebRtcEventLogsMaxRetention -
+ base::TimeDelta::FromSeconds(1);
+ ASSERT_TRUE(base::TouchFile(file_paths[kExpired], file_info.last_accessed,
+ expired_mod_time));
+
+ // Show that the expired file is not uploaded.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {
+ WebRtcLogFileInfo(browser_context_id_, file_paths[kFresh],
+ GetLastModificationTime(file_paths[kFresh]))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Recognize the files as pending by initializing their BrowserContext.
+ LoadMainTestProfile();
+
+ WaitForPendingTasks(&run_loop);
+
+ // Both the uploaded file as well as the expired file have no been removed
+ // from local disk.
+ for (const base::FilePath& file_path : file_paths) {
+ EXPECT_FALSE(base::PathExists(file_path));
+ }
+}
+
+// TODO(crbug.com/775415): Add a test showing that a file expiring while another
+// is being uploaded, is not uploaded after the current upload is completed.
+// This is significant because Chrome might stay up for a long time.
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogEmptyStringHandledGracefully) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // By writing a log after the empty string, we show that no odd behavior is
+ // encountered, such as closing the file (an actual bug from WebRTC).
+ const std::vector<std::string> logs = {"<setup>", "", "<encore>"};
+
+ base::Optional<base::FilePath> file_path;
+
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, logs[i]), std::make_pair(false, true));
+ }
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+#if defined(OS_POSIX)
+TEST_F(WebRtcEventLogManagerTest,
+ UnopenedRemoteLogFilesNotCountedTowardsActiveLogsLimit) {
+ std::unique_ptr<TestingProfile> browser_contexts[2];
+ std::unique_ptr<MockRenderProcessHost> rphs[2];
+ for (size_t i = 0; i < 2; ++i) {
+ browser_contexts[i] = CreateBrowserContext();
+ rphs[i] =
+ std::make_unique<MockRenderProcessHost>(browser_contexts[i].get());
+ }
+
+ constexpr size_t without_permissions = 0;
+ constexpr size_t with_permissions = 1;
+
+ // Remove write permissions from one directory.
+ const base::FilePath permissions_lacking_remote_logs_path =
+ RemoteBoundLogsDir(browser_contexts[without_permissions].get());
+ ASSERT_TRUE(base::DirectoryExists(permissions_lacking_remote_logs_path));
+ RemoveWritePermissions(permissions_lacking_remote_logs_path);
+
+ // Fail to start a log associated with the permission-lacking directory.
+ const auto without_permissions_key =
+ GetPeerConnectionKey(rphs[without_permissions].get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(without_permissions_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(without_permissions_key));
+ std::string error;
+ ASSERT_FALSE(StartRemoteLogging(without_permissions_key, nullptr, &error));
+ EXPECT_EQ(error, kStartRemoteLoggingFailureFileCreationError);
+
+ // Show that this was not counted towards the limit of active files.
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto with_permissions_key =
+ GetPeerConnectionKey(rphs[with_permissions].get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(with_permissions_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(with_permissions_key));
+ EXPECT_TRUE(StartRemoteLogging(with_permissions_key));
+ }
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogManagerTest,
+ NoStartWebRtcSendingEventLogsWhenLocalEnabledWithoutPeerConnection) {
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ NoStartWebRtcSendingEventLogsWhenPeerConnectionButNoLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartWebRtcSendingEventLogsWhenLocalEnabledThenPeerConnectionAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartWebRtcSendingEventLogsWhenPeerConnectionAddedThenLocalEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartWebRtcSendingEventLogsWhenRemoteLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ InstructWebRtcToStopSendingEventLogsWhenLocalLoggingStopped) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(DisableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+// #1 - Local logging was the cause of the logs.
+TEST_F(WebRtcEventLogManagerTest,
+ InstructWebRtcToStopSendingEventLogsWhenPeerConnectionRemoved1) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+// #2 - Remote logging was the cause of the logs.
+TEST_F(WebRtcEventLogManagerTest,
+ InstructWebRtcToStopSendingEventLogsWhenPeerConnectionRemoved2) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+// #1 - Local logging added first.
+TEST_F(WebRtcEventLogManagerTest,
+ SecondLoggingTargetDoesNotInitiateWebRtcLogging1) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(StartRemoteLogging(key));
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+// #2 - Remote logging added first.
+TEST_F(WebRtcEventLogManagerTest,
+ SecondLoggingTargetDoesNotInitiateWebRtcLogging2) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ DisablingLocalLoggingWhenRemoteLoggingEnabledDoesNotStopWebRtcLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(DisableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+
+ // Cleanup
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ DisablingLocalLoggingAfterPcRemovalHasNoEffectOnWebRtcLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+ ASSERT_TRUE(DisableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+// Once a peer connection with a given key was removed, it may not again be
+// added. But, if this impossible case occurs, WebRtcEventLogManager will
+// not crash.
+TEST_F(WebRtcEventLogManagerTest, SanityOverRecreatingTheSamePeerConnection) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ OnWebRtcEventLogWrite(key, "log1");
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ OnWebRtcEventLogWrite(key, "log2");
+}
+
+// The logs would typically be binary. However, the other tests only cover ASCII
+// characters, for readability. This test shows that this is not a problem.
+TEST_F(WebRtcEventLogManagerTest, LogAllPossibleCharacters) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> local_log_file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&local_log_file_path)));
+
+ base::Optional<base::FilePath> remote_log_file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&remote_log_file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(local_log_file_path);
+ ASSERT_FALSE(local_log_file_path->empty());
+ ASSERT_TRUE(remote_log_file_path);
+ ASSERT_FALSE(remote_log_file_path->empty());
+
+ std::string all_chars;
+ for (size_t i = 0; i < 256; ++i) {
+ all_chars += static_cast<uint8_t>(i);
+ }
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, all_chars), std::make_pair(true, true));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*local_log_file_path, all_chars);
+ ExpectRemoteFileContents(*remote_log_file_path, all_chars);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogsClosedWhenRenderProcessHostExits) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+
+ // The expectation for OnLocalLogStopped() will be saturated by this
+ // destruction of the RenderProcessHost, which triggers an implicit
+ // removal of all PeerConnections associated with it.
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ rph_.reset();
+}
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogsClosedWhenRenderProcessHostExits) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ // The expectation for OnRemoteLogStopped() will be saturated by this
+ // destruction of the RenderProcessHost, which triggers an implicit
+ // removal of all PeerConnections associated with it.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ rph_.reset();
+}
+
+// Once a RenderProcessHost exits/crashes, its PeerConnections are removed,
+// which means that they can no longer suppress an upload.
+TEST_F(WebRtcEventLogManagerTest,
+ RenderProcessHostExitCanRemoveUploadSuppression) {
+ SuppressUploading();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // The above removal is not sufficient to trigger an upload (so the test will
+ // not be flaky). It's only once we destroy the RPH with which the suppressing
+ // PeerConnection is associated, that upload will take place.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *file_path, GetLastModificationTime(*file_path))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // We destroy the RPH without explicitly removing its PeerConnection (unlike
+ // a call to UnsuppressUploading()).
+ upload_suppressing_rph_.reset();
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionAddedOverDestroyedRphReturnsFalse) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ rph_.reset();
+ EXPECT_FALSE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionRemovedOverDestroyedRphReturnsFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Test
+ rph_.reset();
+ EXPECT_FALSE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionStoppedOverDestroyedRphReturnsFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Test
+ rph_.reset();
+ EXPECT_FALSE(PeerConnectionStopped(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingOverDestroyedRphReturnsFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Test
+ rph_.reset();
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureDeadRenderProcessHost);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteOverDestroyedRphReturnsFalseAndFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+
+ // Test
+ rph_.reset();
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest, DifferentProfilesCanHaveDifferentPolicies) {
+ auto policy_disabled_profile =
+ CreateBrowserContext("disabled", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ false /* policy_allows_remote_logging */);
+ auto policy_disabled_rph =
+ std::make_unique<MockRenderProcessHost>(policy_disabled_profile.get());
+ const auto disabled_key =
+ GetPeerConnectionKey(policy_disabled_rph.get(), kLid);
+
+ auto policy_enabled_profile =
+ CreateBrowserContext("enabled", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ true /* policy_allows_remote_logging */);
+ auto policy_enabled_rph =
+ std::make_unique<MockRenderProcessHost>(policy_enabled_profile.get());
+ const auto enabled_key = GetPeerConnectionKey(policy_enabled_rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(disabled_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(disabled_key));
+
+ ASSERT_TRUE(PeerConnectionAdded(enabled_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(enabled_key));
+
+ EXPECT_FALSE(StartRemoteLogging(disabled_key));
+ EXPECT_TRUE(StartRemoteLogging(enabled_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithTooLowWebAppIdRejected) {
+ const size_t web_app_id = kMinWebRtcEventLogWebAppId - 1;
+ ASSERT_LT(web_app_id, kMinWebRtcEventLogWebAppId); // Avoid wrap-around.
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithTooHighWebAppIdRejected) {
+ const size_t web_app_id = kMaxWebRtcEventLogWebAppId + 1;
+ ASSERT_GT(web_app_id, kMaxWebRtcEventLogWebAppId); // Avoid wrap-around.
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithInRangeWebAppIdAllowedMin) {
+ const size_t web_app_id = kMinWebRtcEventLogWebAppId;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithInRangeWebAppIdAllowedMax) {
+ const size_t web_app_id = kMaxWebRtcEventLogWebAppId;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+// Only one remote-bound event log allowed per
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingOverMultipleWebAppsDisallowed) {
+ // Test assumes there are at least two legal web-app IDs.
+ ASSERT_NE(kMinWebRtcEventLogWebAppId, kMaxWebRtcEventLogWebAppId);
+ const size_t web_app_ids[2] = {kMinWebRtcEventLogWebAppId,
+ kMaxWebRtcEventLogWebAppId};
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(
+ key, GetUniqueId(key), kMaxRemoteLogFileSizeBytes, 0, web_app_ids[0]));
+ EXPECT_FALSE(StartRemoteLogging(
+ key, GetUniqueId(key), kMaxRemoteLogFileSizeBytes, 0, web_app_ids[1]));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWebAppIdIncorporatedIntoFileName) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ const size_t expected_web_app_id = kWebAppId;
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0,
+ expected_web_app_id));
+ ASSERT_TRUE(file_path);
+
+ const size_t written_web_app_id =
+ ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(*file_path);
+ EXPECT_EQ(written_web_app_id, expected_web_app_id);
+}
+
+INSTANTIATE_TEST_SUITE_P(UploadCompleteResult,
+ WebRtcEventLogManagerTest,
+ ::testing::Bool());
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextRemovesPendingFilesInRange) {
+ SuppressUploading();
+
+ auto browser_context = CreateBrowserContext("name");
+ CreatePendingLogFiles(browser_context.get());
+ auto& elements = *(pending_logs_[browser_context.get()]);
+
+ const base::Time earliest_mod = pending_earliest_mod_ - kEpsion;
+ const base::Time latest_mod = pending_latest_mod_ + kEpsion;
+
+ // Test - ClearCacheForBrowserContext() removed all of the files in the range.
+ ClearCacheForBrowserContext(browser_context.get(), earliest_mod, latest_mod);
+ for (size_t i = 0; i < elements.file_paths.size(); ++i) {
+ EXPECT_FALSE(base::PathExists(*elements.file_paths[i]));
+ }
+
+ ClearPendingLogFiles();
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextCancelsActiveLogFilesIfInRange) {
+ SuppressUploading();
+
+ // Setup
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ base::Optional<base::FilePath> file_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+
+ // Test
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ClearCacheForBrowserContext(
+ browser_context_.get(), base::Time::Now() - base::TimeDelta::FromHours(1),
+ base::Time::Now() + base::TimeDelta::FromHours(1));
+ EXPECT_FALSE(base::PathExists(*file_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextCancelsFileUploadIfInRange) {
+ // This factory will enforce the expectation that the upload is cancelled.
+ // WebRtcEventLogUploaderImplTest.CancelOnOngoingUploadDeletesFile is in
+ // charge of making sure that when the upload is cancelled, the file is
+ // removed from disk.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true));
+
+ // Set up and trigger the uploading of a file.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path = CreatePendingRemoteLogFile(key);
+
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+ const base::Time mod_time = GetLastModificationTime(*file_path);
+
+ // Main part of test - the expectation set up in the the uploader factory
+ // should now be satisfied.
+ ClearCacheForBrowserContext(browser_context_.get(), mod_time - kEpsion,
+ mod_time + kEpsion);
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotRemovePendingFilesOutOfRange) {
+ SuppressUploading();
+
+ auto browser_context = CreateBrowserContext("name");
+ CreatePendingLogFiles(browser_context.get());
+ auto& elements = *(pending_logs_[browser_context.get()]);
+
+ // Get a range whose intersection with the files' range is empty.
+ const base::Time earliest_mod =
+ pending_earliest_mod_ - base::TimeDelta::FromHours(2);
+ const base::Time latest_mod =
+ pending_earliest_mod_ - base::TimeDelta::FromHours(1);
+ ASSERT_LT(latest_mod, pending_latest_mod_);
+
+ // Test - ClearCacheForBrowserContext() does not remove files not in range.
+ // (Range chosen to be earlier than the oldest file
+ ClearCacheForBrowserContext(browser_context.get(), earliest_mod, latest_mod);
+ for (size_t i = 0; i < elements.file_paths.size(); ++i) {
+ EXPECT_TRUE(base::PathExists(*elements.file_paths[i]));
+ }
+
+ ClearPendingLogFiles();
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelActiveLogFilesIfOutOfRange) {
+ SuppressUploading();
+
+ // Setup
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ base::Optional<base::FilePath> file_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+
+ // Test
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(_)).Times(0);
+ ClearCacheForBrowserContext(
+ browser_context_.get(), base::Time::Now() - base::TimeDelta::FromHours(2),
+ base::Time::Now() - base::TimeDelta::FromHours(1));
+ EXPECT_TRUE(base::PathExists(*file_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelFileUploadIfOutOfRange) {
+ // This factory will enforce the expectation that the upload is not cancelled.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
+
+ // Set up and trigger the uploading of a file.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path = CreatePendingRemoteLogFile(key);
+
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+ const base::Time mod_time = GetLastModificationTime(*file_path);
+
+ // Main part of test - the expectation set up in the the uploader factory,
+ // that the upload will not be cancelled, should be shown to hold true.
+ // should now be satisfied.
+ ClearCacheForBrowserContext(browser_context_.get(), mod_time + kEpsion,
+ mod_time + 2 * kEpsion);
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotRemovePendingFilesFromOtherProfiles) {
+ SuppressUploading();
+
+ auto cleared_browser_context = CreateBrowserContext("cleared");
+ CreatePendingLogFiles(cleared_browser_context.get());
+ auto& cleared_elements = *(pending_logs_[cleared_browser_context.get()]);
+
+ auto const uncleared_browser_context = CreateBrowserContext("pristine");
+ CreatePendingLogFiles(uncleared_browser_context.get());
+ auto& uncleared_elements = *(pending_logs_[uncleared_browser_context.get()]);
+
+ ASSERT_EQ(cleared_elements.file_paths.size(),
+ uncleared_elements.file_paths.size());
+ const size_t kFileCount = cleared_elements.file_paths.size();
+
+ const base::Time earliest_mod = pending_earliest_mod_ - kEpsion;
+ const base::Time latest_mod = pending_latest_mod_ + kEpsion;
+
+ // Test - ClearCacheForBrowserContext() only removes the files which belong
+ // to the cleared context.
+ ClearCacheForBrowserContext(cleared_browser_context.get(), earliest_mod,
+ latest_mod);
+ for (size_t i = 0; i < kFileCount; ++i) {
+ EXPECT_FALSE(base::PathExists(*cleared_elements.file_paths[i]));
+ EXPECT_TRUE(base::PathExists(*uncleared_elements.file_paths[i]));
+ }
+
+ ClearPendingLogFiles();
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelActiveLogsFromOtherProfiles) {
+ SuppressUploading();
+
+ // Remote-bound active log file that *will* be cleared.
+ auto cleared_browser_context = CreateBrowserContext("cleared");
+ auto cleared_rph =
+ std::make_unique<MockRenderProcessHost>(cleared_browser_context.get());
+ const auto cleared_key = GetPeerConnectionKey(cleared_rph.get(), kLid);
+ base::Optional<base::FilePath> cleared_file_path =
+ CreateActiveRemoteLogFile(cleared_key);
+
+ // Remote-bound active log file that will *not* be cleared.
+ auto uncleared_browser_context = CreateBrowserContext("pristine");
+ auto uncleared_rph =
+ std::make_unique<MockRenderProcessHost>(uncleared_browser_context.get());
+ const auto uncleared_key = GetPeerConnectionKey(uncleared_rph.get(), kLid);
+ base::Optional<base::FilePath> uncleared_file_path =
+ CreateActiveRemoteLogFile(uncleared_key);
+
+ // Test - ClearCacheForBrowserContext() only removes the files which belong
+ // to the cleared context.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(cleared_key)).Times(1);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(uncleared_key)).Times(0);
+ ClearCacheForBrowserContext(cleared_browser_context.get(), base::Time::Min(),
+ base::Time::Max());
+ EXPECT_FALSE(base::PathExists(*cleared_file_path));
+ EXPECT_TRUE(base::PathExists(*uncleared_file_path));
+
+ // Cleanup - uncleared_file_path will be closed as part of the shutdown. It
+ // is time to clear its expectation.
+ testing::Mock::VerifyAndClearExpectations(&remote_observer_);
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelFileUploadFromOtherProfiles) {
+ // This factory will enforce the expectation that the upload is not cancelled.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
+
+ // Set up and trigger the uploading of a file.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path = CreatePendingRemoteLogFile(key);
+
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+ const base::Time mod_time = GetLastModificationTime(*file_path);
+
+ // Main part of test - the expectation set up in the the uploader factory,
+ // that the upload will not be cancelled, should be shown to hold true.
+ // should now be satisfied.
+ auto const different_browser_context = CreateBrowserContext();
+ ClearCacheForBrowserContext(different_browser_context.get(),
+ mod_time - kEpsion, mod_time + kEpsion);
+}
+
+// Show that clearing browser cache, while it removes remote-bound logs, does
+// not interfere with local-bound logging, even if that happens on the same PC.
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotInterfereWithLocalLogs) {
+ SuppressUploading();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> local_log;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&local_log)));
+ ASSERT_TRUE(EnableLocalLogging());
+
+ // This adds a peer connection for |key|, which also triggers
+ // OnLocalLogStarted() on |local_observer_|.
+ auto pending_remote_log = CreatePendingRemoteLogFile(key);
+
+ // Test focus - local logging is uninterrupted.
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ EXPECT_TRUE(base::PathExists(*local_log));
+
+ // Sanity on the test itself; the remote log should have been cleared.
+ ASSERT_FALSE(base::PathExists(*pending_remote_log));
+}
+
+// When cache clearing cancels the active upload, the next (non-deleted) pending
+// file becomes eligible for upload.
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ UploadCancellationTriggersUploadOfNextPendingFile) {
+ // The first created file will start being uploaded, but then cancelled.
+ // The second file will never be uploaded (deleted while pending).
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true));
+
+ // Create the files that will be deleted when cache is cleared.
+ CreatePendingRemoteLogFile(GetPeerConnectionKey(rph_.get(), 0));
+ CreatePendingRemoteLogFile(GetPeerConnectionKey(rph_.get(), 1));
+
+ // Create the not-deleted file under a different profile, to easily make sure
+ // it does not fit in the ClearCacheForBrowserContext range (less fiddly than
+ // a time range).
+ auto other_browser_context = CreateBrowserContext();
+ auto other_rph =
+ std::make_unique<MockRenderProcessHost>(other_browser_context.get());
+ const auto key = GetPeerConnectionKey(other_rph.get(), kLid);
+ base::Optional<base::FilePath> other_file = CreatePendingRemoteLogFile(key);
+ ASSERT_TRUE(other_file);
+
+ // Switch the uploader factory to one that will allow us to ensure that the
+ // new file, which is not deleted, is uploaded.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {
+ WebRtcLogFileInfo(GetBrowserContextId(other_browser_context.get()),
+ *other_file, GetLastModificationTime(*other_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Clearing the cache for the first profile, should now trigger the upload
+ // of the last remaining unclear pending log file - |other_file|.
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityPeerConnectionAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_TRUE(PeerConnectionAdded(key));
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityPeerConnectionRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityPeerConnectionStopped) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ PeerConnectionStopped(key); // No crash.
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityEnableLocalLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityDisableLocalLogging) {
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(DisableLocalLogging());
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityStartRemoteLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureFeatureDisabled);
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityOnWebRtcEventLogWrite) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_FALSE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ ::testing::Bool());
+
+// This test is redundant; it is provided for completeness; see following tests.
+TEST_F(WebRtcEventLogManagerTestPolicy, StartsEnabledAllowsRemoteLogging) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// This test is redundant; it is provided for completeness; see following tests.
+TEST_F(WebRtcEventLogManagerTestPolicy, StartsDisabledRejectsRemoteLogging) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = false;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy, NotManagedRejectsRemoteLogging) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = false;
+ auto browser_context = CreateBrowserContext(
+ "name", false /* is_managed_profile */,
+ false /* has_device_level_policies */, base::nullopt);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ ManagedProfileAllowsRemoteLoggingByDefault) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, base::nullopt);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ ManagedByPlatformPoliciesAllowsRemoteLoggingByDefault) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = true;
+ auto browser_context =
+ CreateBrowserContext("name", false /* is_managed_profile */,
+ true /* has_device_level_policies */, base::nullopt);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+#endif
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledRejectsRemoteLogging1) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledRejectsRemoteLogging2) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsDisabledThenEnabledAllowsRemoteLogging1) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = false;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsDisabledThenEnabledAllowsRemoteLogging2) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = false;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsDisabledThenEnabledUploadsPendingLogFiles) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = false;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn on before StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledDoesNotUploadPendingLogFiles) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ SuppressUploading();
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ UnsuppressUploading();
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledDeletesPendingLogFiles) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ SuppressUploading();
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(log_file);
+
+ // Make the file PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(base::PathExists(*log_file)); // Test sanity; exists before.
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ WaitForPendingTasks(&run_loop);
+
+ // Test focus - file deleted without being uploaded.
+ EXPECT_FALSE(base::PathExists(*log_file));
+
+ // Still not uploaded.
+ UnsuppressUploading();
+ WaitForPendingTasks();
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledCancelsAndDeletesCurrentlyUploadedLogFile) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ // This factory expects exactly one log to be uploaded, then cancelled.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 1));
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(log_file);
+
+ // Log file's upload commences.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ASSERT_TRUE(base::PathExists(*log_file)); // Test sanity; exists before.
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ WaitForPendingTasks();
+
+ // Test focus - file deleted without being uploaded.
+ // When the test terminates, the NullWebRtcEventLogUploader::Factory's
+ // expectation that one log file was uploaded, and that the upload was
+ // cancelled, is enforced.
+ // Deletion of the file not performed by NullWebRtcEventLogUploader; instead,
+ // WebRtcEventLogUploaderImplTest.CancelOnOngoingUploadDeletesFile tests that.
+}
+
+// This test makes sure that if the policy was enabled in the past, but was
+// disabled while Chrome was not running, pending logs created during the
+// earlier session will be deleted from disk.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ PendingLogsFromPreviousSessionRemovedIfPolicyDisabledAtNewSessionStart) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ SuppressUploading();
+
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 0));
+
+ bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+
+ const base::FilePath browser_context_dir =
+ RemoteBoundLogsDir(browser_context.get());
+ ASSERT_TRUE(base::DirectoryExists(browser_context_dir));
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ // Produce an empty log file in the BrowserContext. It's not uploaded
+ // because uploading is suppressed.
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // Reload the BrowserContext, but this time with the policy disabling
+ // the feature.
+ rph.reset();
+ browser_context.reset();
+ ASSERT_TRUE(base::DirectoryExists(browser_context_dir)); // Test sanity
+ allow_remote_logging = false;
+ browser_context = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ // Test focus - pending log files removed, as well as any potential metadata
+ // associated with remote-bound logging for |browser_context|.
+ ASSERT_FALSE(base::DirectoryExists(browser_context_dir));
+
+ // When NullWebRtcEventLogUploader::Factory is destroyed, it will show that
+ // the deleted log file was never uploaded.
+ UnsuppressUploading();
+ WaitForPendingTasks();
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ PendingLogsFromPreviousSessionRemovedIfRemoteLoggingKillSwitchEngaged) {
+ SetUp(false); // Feature generally disabled (kill-switch engaged).
+
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 0));
+
+ const std::string name = "name";
+ const base::FilePath browser_context_dir =
+ profiles_dir_.GetPath().AppendASCII(name);
+ const base::FilePath remote_bound_dir =
+ RemoteBoundLogsDir(browser_context_dir);
+ ASSERT_FALSE(base::PathExists(remote_bound_dir));
+
+ base::FilePath file_path;
+ base::File file;
+ ASSERT_TRUE(base::CreateDirectory(remote_bound_dir));
+ ASSERT_TRUE(CreateRemoteBoundLogFile(remote_bound_dir, kWebAppId,
+ remote_log_extension_, base::Time::Now(),
+ &file_path, &file));
+ file.Close();
+
+ const bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+ ASSERT_EQ(browser_context->GetPath(), browser_context_dir); // Test sanity
+
+ WaitForPendingTasks();
+
+ EXPECT_FALSE(base::PathExists(remote_bound_dir));
+}
+
+TEST_F(WebRtcEventLogManagerTestUploadSuppressionDisablingFlag,
+ UploadingNotSuppressedByActivePeerConnections) {
+ SuppressUploading();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ DoNotUploadPendingLogsIfConnectedToUnsupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, unsupported_type_);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ std::list<WebRtcLogFileInfo> empty_expected_files_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_expected_files_list, true, &run_loop));
+
+ // Peer connection removal MAY trigger upload, depending on network.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ UploadPendingLogsIfConnectedToSupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, supported_type_);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Peer connection removal MAY trigger upload, depending on network.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ UploadPendingLogsIfConnectionTypeChangesFromUnsupportedToSupported) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, unsupported_type_);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ // That a peer connection upload is not initiated by this point, is verified
+ // by previous tests.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ WaitForPendingTasks();
+
+ // Test focus - an upload will be initiated after changing the network type.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+ SetConnectionType(supported_type_);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ DoNotUploadPendingLogsAtStartupIfConnectedToUnsupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, unsupported_type_);
+
+ UnloadProfileAndSeedPendingLog();
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ std::list<WebRtcLogFileInfo> empty_expected_files_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_expected_files_list, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path_);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ UploadPendingLogsAtStartupIfConnectedToSupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, supported_type_);
+
+ UnloadProfileAndSeedPendingLog();
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files_, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path_);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ UploadSupportingConnectionTypes,
+ WebRtcEventLogManagerTestForNetworkConnectivity,
+ ::testing::Combine(
+ // Wehther GetConnectionType() responds synchronously.
+ ::testing::Bool(),
+ // The upload-supporting network type to be used.
+ ::testing::Values(network::mojom::ConnectionType::CONNECTION_ETHERNET,
+ network::mojom::ConnectionType::CONNECTION_WIFI,
+ network::mojom::ConnectionType::CONNECTION_UNKNOWN),
+ // The upload-unsupporting network type to be used.
+ ::testing::Values(network::mojom::ConnectionType::CONNECTION_NONE,
+ network::mojom::ConnectionType::CONNECTION_4G)));
+
+TEST_F(WebRtcEventLogManagerTestUploadDelay, DoNotInitiateUploadBeforeDelay) {
+ SetUp(kIntentionallyExcessiveDelayMs);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // Wait a bit and see that the upload was not initiated. (Due to technical
+ // constraints, we cannot wait forever.)
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ event.TimedWait(base::TimeDelta::FromMilliseconds(500));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+// WhenPeerConnectionRemovedFinishedRemoteLogUploadedAndFileDeleted has some
+// overlap with this, but we still include this test for explicitness and
+// clarity.
+TEST_F(WebRtcEventLogManagerTestUploadDelay, InitiateUploadAfterDelay) {
+ SetUp(kDefaultUploadDelayMs);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestUploadDelay,
+ PeerConnectionAddedDuringDelaySuppressesUpload) {
+ SetUp(kIntentionallyExcessiveDelayMs);
+
+ const auto key1 = GetPeerConnectionKey(rph_.get(), 1);
+ const auto key2 = GetPeerConnectionKey(rph_.get(), 2);
+
+ ASSERT_TRUE(PeerConnectionAdded(key1));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key1));
+ ASSERT_TRUE(StartRemoteLogging(key1));
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key1));
+
+ // Test focus - after adding a peer connection, the conditions for the upload
+ // are no longer considered to hold.
+ // (Test implemented with a glimpse into the black box due to technical
+ // limitations and the desire to avoid flakiness.)
+ ASSERT_TRUE(PeerConnectionAdded(key2));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key2));
+ EXPECT_FALSE(UploadConditionsHold());
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestUploadDelay,
+ ClearCacheForBrowserContextDuringDelayCancelsItsUpload) {
+ SetUp(kIntentionallyExcessiveDelayMs);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // Test focus - after clearing browser cache, the conditions for the upload
+ // are no longer considered to hold, because the file about to be uploaded
+ // was deleted.
+ // (Test implemented with a glimpse into the black box due to technical
+ // limitations and the desire to avoid flakiness.)
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ EXPECT_FALSE(UploadConditionsHold());
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestCompression,
+ ErroredFilesDueToBadEstimationDeletedRatherThanUploaded) {
+ Init(Compression::GZIP_NULL_ESTIMATION);
+
+ const std::string log = "It's better than bad; it's good.";
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key), GzippedSize(log) - 1, 0,
+ kWebAppId));
+ ASSERT_TRUE(log_file);
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Writing fails because the budget is exceeded.
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // The file was deleted due to the error we've instigated (by using an
+ // intentionally over-optimistic estimation).
+ EXPECT_FALSE(base::PathExists(*log_file));
+
+ // If the file is incorrectly still eligible for an upload, this will trigger
+ // the upload (which will be a test failure).
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito,
+ NoRemoteBoundLogsDirectoryCreatedWhenProfileLoaded) {
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(incognito_profile_);
+ EXPECT_FALSE(base::DirectoryExists(remote_logs_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito, StartRemoteLoggingFails) {
+ const auto key = GetPeerConnectionKey(incognito_rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key));
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito,
+ StartRemoteLoggingDoesNotCreateDirectoryOrFiles) {
+ const auto key = GetPeerConnectionKey(incognito_rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_FALSE(StartRemoteLogging(key));
+
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(incognito_profile_);
+ EXPECT_FALSE(base::DirectoryExists(remote_logs_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito,
+ OnWebRtcEventLogWriteReturnsFalseForRemotePart) {
+ const auto key = GetPeerConnectionKey(incognito_rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_FALSE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory,
+ CorrectHistoryReturnedForActivelyWrittenLog) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(path);
+ ASSERT_FALSE(path->BaseName().MaybeAsASCII().empty());
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::Pending);
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.capture_time, base::Time::Now()));
+ EXPECT_EQ(history_entry.local_id, path->BaseName().MaybeAsASCII());
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(history_entry.upload_time.is_null());
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory, CorrectHistoryReturnedForPendingLog) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(path);
+ ASSERT_FALSE(path->BaseName().MaybeAsASCII().empty());
+
+ SuppressUploading();
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::Pending);
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.capture_time, base::Time::Now()));
+ EXPECT_EQ(history_entry.local_id, path->BaseName().MaybeAsASCII());
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(history_entry.upload_time.is_null());
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory,
+ CorrectHistoryReturnedForActivelyUploadedLog) {
+ // This factory expects exactly one log to be uploaded; cancellation is
+ // expected during tear-down.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 1));
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(path);
+ ASSERT_FALSE(path->BaseName().MaybeAsASCII().empty());
+
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::Pending);
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.capture_time, base::Time::Now()));
+ EXPECT_EQ(history_entry.local_id, path->BaseName().MaybeAsASCII());
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.upload_time, base::Time::Now()));
+ EXPECT_LE(history_entry.capture_time, history_entry.upload_time);
+
+ // Test tear down - trigger uploader cancellation.
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+}
+
+// See ExpiredLogFilesAreReplacedByHistoryFiles for verification of the
+// creation of history files of this type.
+TEST_F(WebRtcEventLogManagerTestHistory,
+ ExpiredLogFilesReplacedByHistoryFilesAndGetHistoryReportsAccordingly) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&log_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_path);
+ ASSERT_FALSE(log_path->BaseName().MaybeAsASCII().empty());
+
+ SuppressUploading();
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ UnloadMainTestProfile();
+
+ // Test sanity.
+ ASSERT_TRUE(base::PathExists(*log_path));
+
+ // Pretend more time than kRemoteBoundWebRtcEventLogsMaxRetention has passed.
+ const base::TimeDelta elapsed_time =
+ kRemoteBoundWebRtcEventLogsMaxRetention + base::TimeDelta::FromHours(1);
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(*log_path, &file_info));
+
+ const auto modified_capture_time = file_info.last_modified - elapsed_time;
+ ASSERT_TRUE(base::TouchFile(*log_path, file_info.last_accessed - elapsed_time,
+ modified_capture_time));
+
+ LoadMainTestProfile();
+
+ ASSERT_FALSE(base::PathExists(*log_path));
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::NotUploaded);
+ EXPECT_TRUE(IsSameTimeWhenTruncatedToSeconds(history_entry.capture_time,
+ modified_capture_time));
+ EXPECT_EQ(history_entry.local_id,
+ ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(*log_path));
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(history_entry.upload_time.is_null());
+}
+
+// Since the uploader mocks do not write the history files, it is not easy
+// to check that the correct result is returned for GetHistory() for either
+// a successful or an unsuccessful upload from the WebRtcEventLogManager level.
+// Instead, this is checked by WebRtcEventLogUploaderImplTest.
+// TODO(crbug.com/775415): Add the tests mention in the comment above.
+
+TEST_F(WebRtcEventLogManagerTestHistory, ClearingCacheRemovesHistoryFiles) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&log_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_path);
+ ASSERT_FALSE(log_path->BaseName().MaybeAsASCII().empty());
+
+ SuppressUploading();
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ UnloadMainTestProfile();
+
+ // Test sanity.
+ ASSERT_TRUE(base::PathExists(*log_path));
+
+ // Pretend more time than kRemoteBoundWebRtcEventLogsMaxRetention has passed.
+ const base::TimeDelta elapsed_time =
+ kRemoteBoundWebRtcEventLogsMaxRetention + base::TimeDelta::FromHours(1);
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(*log_path, &file_info));
+
+ const auto modified_capture_time = file_info.last_modified - elapsed_time;
+ ASSERT_TRUE(base::TouchFile(*log_path, file_info.last_accessed - elapsed_time,
+ modified_capture_time));
+
+ LoadMainTestProfile();
+
+ ASSERT_FALSE(base::PathExists(*log_path));
+
+ // Setup complete; we now have a history file on disk. Time to see that it is
+ // removed when cache is cleared.
+
+ // Sanity.
+ const auto history_path = GetWebRtcEventLogHistoryFilePath(*log_path);
+ ASSERT_TRUE(base::PathExists(history_path));
+ ASSERT_EQ(GetHistory(browser_context_id_).size(), 1u);
+
+ // Test.
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ ASSERT_FALSE(base::PathExists(history_path));
+ ASSERT_EQ(GetHistory(browser_context_id_).size(), 0u);
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory,
+ ClearingCacheDoesNotLeaveBehindHistoryForRemovedLogs) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&log_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_path);
+ ASSERT_FALSE(log_path->BaseName().MaybeAsASCII().empty());
+
+ ASSERT_TRUE(base::PathExists(*log_path));
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ ASSERT_FALSE(base::PathExists(*log_path));
+
+ const auto history = GetHistory(browser_context_id_);
+ EXPECT_EQ(history.size(), 0u);
+}
+
+// TODO(crbug.com/775415): Add a test for the limit on the number of history
+// files allowed to remain on disk.
+
+#else // defined(OS_ANDROID)
+
+class WebRtcEventLogManagerTestOnMobileDevices
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestOnMobileDevices() {
+ // features::kWebRtcRemoteEventLog not defined on mobile, and can therefore
+ // not be forced on. This test is here to make sure that when the feature
+ // is changed to be on by default, it will still be off for mobile devices.
+ CreateWebRtcEventLogManager();
+ }
+};
+
+TEST_F(WebRtcEventLogManagerTestOnMobileDevices, RemoteBoundLoggingDisabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key));
+}
+
+#endif
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc
new file mode 100644
index 00000000000..30fb9113e19
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc_event_logging {
+
+// Produce a LogFileWriter::Factory object.
+std::unique_ptr<LogFileWriter::Factory> CreateLogFileWriterFactory(
+ WebRtcEventLogCompression compression) {
+ switch (compression) {
+ case WebRtcEventLogCompression::NONE:
+ return std::make_unique<BaseLogFileWriterFactory>();
+ case WebRtcEventLogCompression::GZIP_NULL_ESTIMATION:
+ return std::make_unique<GzippedLogFileWriterFactory>(
+ std::make_unique<GzipLogCompressorFactory>(
+ std::make_unique<NullEstimator::Factory>()));
+ case WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION:
+ return std::make_unique<GzippedLogFileWriterFactory>(
+ std::make_unique<GzipLogCompressorFactory>(
+ std::make_unique<PerfectGzipEstimator::Factory>()));
+ }
+ NOTREACHED();
+ return nullptr; // Appease compiler.
+}
+
+#if defined(OS_POSIX)
+void RemoveWritePermissions(const base::FilePath& path) {
+ int permissions;
+ ASSERT_TRUE(base::GetPosixFilePermissions(path, &permissions));
+ constexpr int write_permissions = base::FILE_PERMISSION_WRITE_BY_USER |
+ base::FILE_PERMISSION_WRITE_BY_GROUP |
+ base::FILE_PERMISSION_WRITE_BY_OTHERS;
+ permissions &= ~write_permissions;
+ ASSERT_TRUE(base::SetPosixFilePermissions(path, permissions));
+}
+#endif // defined(OS_POSIX)
+
+std::unique_ptr<CompressedSizeEstimator> NullEstimator::Factory::Create()
+ const {
+ return std::make_unique<NullEstimator>();
+}
+
+size_t NullEstimator::EstimateCompressedSize(const std::string& input) const {
+ return 0;
+}
+
+std::unique_ptr<CompressedSizeEstimator> PerfectGzipEstimator::Factory::Create()
+ const {
+ return std::make_unique<PerfectGzipEstimator>();
+}
+
+PerfectGzipEstimator::PerfectGzipEstimator() {
+ // This factory will produce an optimistic compressor that will always
+ // think it can compress additional inputs, which will therefore allow
+ // us to find out what the real compressed size it, since compression
+ // will never be suppressed.
+ GzipLogCompressorFactory factory(std::make_unique<NullEstimator::Factory>());
+
+ compressor_ = factory.Create(base::Optional<size_t>());
+ DCHECK(compressor_);
+
+ std::string ignored;
+ compressor_->CreateHeader(&ignored);
+}
+
+PerfectGzipEstimator::~PerfectGzipEstimator() = default;
+
+size_t PerfectGzipEstimator::EstimateCompressedSize(
+ const std::string& input) const {
+ std::string output;
+ EXPECT_EQ(compressor_->Compress(input, &output), LogCompressor::Result::OK);
+ return output.length();
+}
+
+size_t GzippedSize(const std::string& uncompressed) {
+ PerfectGzipEstimator perfect_estimator;
+ return kGzipOverheadBytes +
+ perfect_estimator.EstimateCompressedSize(uncompressed);
+}
+
+size_t GzippedSize(const std::vector<std::string>& uncompressed) {
+ PerfectGzipEstimator perfect_estimator;
+
+ size_t result = kGzipOverheadBytes;
+ for (const std::string& str : uncompressed) {
+ result += perfect_estimator.EstimateCompressedSize(str);
+ }
+
+ return result;
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h
new file mode 100644
index 00000000000..0fe270ef634
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+namespace webrtc_event_logging {
+
+// Which type of compression, if any, LogFileWriterTest should use.
+enum class WebRtcEventLogCompression {
+ NONE,
+ GZIP_NULL_ESTIMATION,
+ GZIP_PERFECT_ESTIMATION
+};
+
+// Produce a LogFileWriter::Factory object.
+std::unique_ptr<LogFileWriter::Factory> CreateLogFileWriterFactory(
+ WebRtcEventLogCompression compression);
+
+#if defined(OS_POSIX)
+void RemoveWritePermissions(const base::FilePath& path);
+#endif // defined(OS_POSIX)
+
+// Always estimates strings to be compressed to zero bytes.
+class NullEstimator : public CompressedSizeEstimator {
+ public:
+ class Factory : public CompressedSizeEstimator::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<CompressedSizeEstimator> Create() const override;
+ };
+
+ ~NullEstimator() override = default;
+
+ size_t EstimateCompressedSize(const std::string& input) const override;
+};
+
+// Provides a perfect estimation of the compressed size by cheating - performing
+// actual compression, then reporting the resulting size.
+// This class is stateful; the number, nature and order of calls to
+// EstimateCompressedSize() is important.
+class PerfectGzipEstimator : public CompressedSizeEstimator {
+ public:
+ class Factory : public CompressedSizeEstimator::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<CompressedSizeEstimator> Create() const override;
+ };
+
+ PerfectGzipEstimator();
+
+ ~PerfectGzipEstimator() override;
+
+ size_t EstimateCompressedSize(const std::string& input) const override;
+
+ private:
+ // This compressor allows EstimateCompressedSize to return an exact estimate.
+ // EstimateCompressedSize is normally const, but here we fake it, so we set
+ // it as mutable.
+ mutable std::unique_ptr<LogCompressor> compressor_;
+};
+
+// Check the gzipped size of |uncompressed|, including header and footer,
+// assuming it were gzipped on its own.
+size_t GzippedSize(const std::string& uncompressed);
+
+// Same as other version, but with elements compressed in sequence.
+size_t GzippedSize(const std::vector<std::string>& uncompressed);
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
new file mode 100644
index 00000000000..17f542efa75
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
@@ -0,0 +1,383 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "ui/base/text/bytes_formatting.h"
+
+namespace webrtc_event_logging {
+
+namespace {
+// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
+const char kUploadContentType[] = "multipart/form-data";
+const char kBoundary[] = "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
+
+constexpr size_t kExpectedMimeOverheadBytes = 1000; // Intentional overshot.
+
+// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
+#if defined(OS_WIN)
+const char kProduct[] = "Chrome";
+#elif defined(OS_MACOSX)
+const char kProduct[] = "Chrome_Mac";
+#elif defined(OS_LINUX)
+const char kProduct[] = "Chrome_Linux";
+#elif defined(OS_ANDROID)
+const char kProduct[] = "Chrome_Android";
+#elif defined(OS_CHROMEOS)
+const char kProduct[] = "Chrome_ChromeOS";
+#else
+#error Platform not supported.
+#endif
+
+constexpr net::NetworkTrafficAnnotationTag
+ kWebrtcEventLogUploaderTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("webrtc_event_log_uploader", R"(
+ semantics {
+ sender: "WebRTC Event Log uploader module"
+ description:
+ "Uploads a WebRTC event log to a server called Crash. These logs "
+ "will not contain private information. They will be used to "
+ "improve WebRTC (fix bugs, tune performance, etc.)."
+ trigger:
+ "A Google service (e.g. Hangouts/Meet) has requested a peer "
+ "connection to be logged, and the resulting event log to be uploaded "
+ "at a time deemed to cause the least interference to the user (i.e., "
+ "when the user is not busy making other VoIP calls)."
+ data:
+ "WebRTC events such as the timing of audio playout (but not the "
+ "content), timing and size of RTP packets sent/received, etc."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "Feature controlled only through Chrome policy; "
+ "no user-facing control surface."
+ chrome_policy {
+ WebRtcEventLogCollectionAllowed {
+ WebRtcEventLogCollectionAllowed: false
+ }
+ }
+ })");
+
+void AddFileContents(const char* filename,
+ const std::string& file_contents,
+ const std::string& content_type,
+ std::string* post_data) {
+ // net::AddMultipartValueForUpload does almost what we want to do here, except
+ // that it does not add the "filename" attribute. We hack it to force it to.
+ std::string mime_value_name =
+ base::StringPrintf("%s\"; filename=\"%s\"", filename, filename);
+ net::AddMultipartValueForUpload(mime_value_name, file_contents, kBoundary,
+ content_type, post_data);
+}
+
+std::string MimeContentType() {
+ const char kBoundaryKeywordAndMisc[] = "; boundary=";
+
+ std::string content_type;
+ content_type.reserve(sizeof(content_type) + sizeof(kBoundaryKeywordAndMisc) +
+ sizeof(kBoundary));
+
+ content_type.append(kUploadContentType);
+ content_type.append(kBoundaryKeywordAndMisc);
+ content_type.append(kBoundary);
+
+ return content_type;
+}
+
+void BindURLLoaderFactoryReceiver(
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory>
+ url_loader_factory_receiver) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory =
+ g_browser_process->shared_url_loader_factory();
+ DCHECK(shared_url_loader_factory);
+ shared_url_loader_factory->Clone(std::move(url_loader_factory_receiver));
+}
+
+void OnURLLoadUploadProgress(uint64_t current, uint64_t total) {
+ ui::DataUnits unit = ui::GetByteDisplayUnits(total);
+ VLOG(1) << "WebRTC event log upload progress: "
+ << FormatBytesWithUnits(current, unit, false) << " / "
+ << FormatBytesWithUnits(total, unit, true) << ".";
+}
+} // namespace
+
+const char WebRtcEventLogUploaderImpl::kUploadURL[] =
+ "https://clients2.google.com/cr/report";
+
+std::unique_ptr<WebRtcEventLogUploader>
+WebRtcEventLogUploaderImpl::Factory::Create(const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) {
+ return std::make_unique<WebRtcEventLogUploaderImpl>(
+ log_file, std::move(callback), kMaxRemoteLogFileSizeBytes);
+}
+
+std::unique_ptr<WebRtcEventLogUploader>
+WebRtcEventLogUploaderImpl::Factory::CreateWithCustomMaxSizeForTesting(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_log_file_size_bytes) {
+ return std::make_unique<WebRtcEventLogUploaderImpl>(
+ log_file, std::move(callback), max_log_file_size_bytes);
+}
+
+WebRtcEventLogUploaderImpl::WebRtcEventLogUploaderImpl(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_log_file_size_bytes)
+ : log_file_(log_file),
+ callback_(std::move(callback)),
+ max_log_file_size_bytes_(max_log_file_size_bytes),
+ io_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
+ history_file_writer_ = WebRtcEventLogHistoryFileWriter::Create(
+ GetWebRtcEventLogHistoryFilePath(log_file_.path));
+ if (!history_file_writer_) {
+ // File either could not be created, or, if a different error occurred,
+ // Create() will have tried to remove the file it has created.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kHistoryFileCreationError);
+ ReportResult(false);
+ return;
+ }
+
+ const base::Time now = std::max(base::Time::Now(), log_file.last_modified);
+ if (!history_file_writer_->WriteCaptureTime(log_file.last_modified) ||
+ !history_file_writer_->WriteUploadTime(now)) {
+ LOG(ERROR) << "Writing to history file failed.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kHistoryFileWriteError);
+ DeleteHistoryFile(); // Avoid partial, potentially-corrupt history files.
+ ReportResult(false);
+ return;
+ }
+
+ std::string upload_data;
+ if (!PrepareUploadData(&upload_data)) {
+ // History file will reflect a failed upload attempt.
+ ReportResult(false); // UMA recorded by PrepareUploadData().
+ return;
+ }
+
+ StartUpload(upload_data);
+}
+
+WebRtcEventLogUploaderImpl::~WebRtcEventLogUploaderImpl() {
+ // WebRtcEventLogUploaderImpl objects' deletion scenarios:
+ // 1. Upload started and finished - |url_loader_| should have been reset
+ // so that we would be able to DCHECK and demonstrate that the determinant
+ // is maintained.
+ // 2. Upload started and cancelled - behave similarly to a finished upload.
+ // 3. The upload was never started, due to an early failure (e.g. file not
+ // found). In that case, |url_loader_| will not have been set.
+ // 4. Chrome shutdown.
+ if (io_task_runner_->RunsTasksInCurrentSequence()) { // Scenarios 1-3.
+ DCHECK(!url_loader_);
+ } else { // # Scenario #4 - Chrome shutdown.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ bool will_delete =
+ io_task_runner_->DeleteSoon(FROM_HERE, url_loader_.release());
+ DCHECK(!will_delete)
+ << "Task runners must have been stopped by this stage of shutdown.";
+ }
+}
+
+const WebRtcLogFileInfo& WebRtcEventLogUploaderImpl::GetWebRtcLogFileInfo()
+ const {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ return log_file_;
+}
+
+bool WebRtcEventLogUploaderImpl::Cancel() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ // The upload could already have been completed, or maybe was never properly
+ // started (due to a file read failure, etc.).
+ const bool upload_was_active = (url_loader_.get() != nullptr);
+
+ // Note that in this case, it might still be that the last bytes hit the
+ // wire right as we attempt to cancel the upload. OnURLFetchComplete, however,
+ // will not be called.
+ url_loader_.reset();
+
+ DeleteLogFile();
+ DeleteHistoryFile();
+
+ if (upload_was_active) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kUploadCancelled);
+ }
+
+ return upload_was_active;
+}
+
+bool WebRtcEventLogUploaderImpl::PrepareUploadData(std::string* upload_data) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ std::string log_file_contents;
+ if (!base::ReadFileToStringWithMaxSize(log_file_.path, &log_file_contents,
+ max_log_file_size_bytes_)) {
+ LOG(WARNING) << "Couldn't read event log file, or max file size exceeded.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileReadError);
+ return false;
+ }
+
+ DCHECK(upload_data->empty());
+ upload_data->reserve(log_file_contents.size() + kExpectedMimeOverheadBytes);
+
+ const std::string filename_str = log_file_.path.BaseName().MaybeAsASCII();
+ if (filename_str.empty()) {
+ LOG(WARNING) << "Log filename is not according to acceptable format.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileNameError);
+ return false;
+ }
+
+ const char* filename = filename_str.c_str();
+
+ net::AddMultipartValueForUpload("prod", kProduct, kBoundary, std::string(),
+ upload_data);
+ net::AddMultipartValueForUpload("ver",
+ version_info::GetVersionNumber() + "-webrtc",
+ kBoundary, std::string(), upload_data);
+ net::AddMultipartValueForUpload("guid", "0", kBoundary, std::string(),
+ upload_data);
+ net::AddMultipartValueForUpload("type", filename, kBoundary, std::string(),
+ upload_data);
+ AddFileContents(filename, log_file_contents, "application/log", upload_data);
+ net::AddMultipartFinalDelimiterForUpload(kBoundary, upload_data);
+
+ return true;
+}
+
+void WebRtcEventLogUploaderImpl::StartUpload(const std::string& upload_data) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = GURL(kUploadURL);
+ resource_request->method = "POST";
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ // Create a new mojo pipe. It's safe to pass this around and use
+ // immediately, even though it needs to finish initialization on the UI
+ // thread.
+ network::mojom::URLLoaderFactoryPtr url_loader_factory_ptr;
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(BindURLLoaderFactoryReceiver,
+ mojo::MakeRequest(&url_loader_factory_ptr)));
+
+ url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), kWebrtcEventLogUploaderTrafficAnnotation);
+ url_loader_->AttachStringForUpload(upload_data, MimeContentType());
+ url_loader_->SetOnUploadProgressCallback(
+ base::BindRepeating(OnURLLoadUploadProgress));
+
+ // See comment in destructor for an explanation about why using
+ // base::Unretained(this) is safe here.
+ url_loader_->DownloadToString(
+ url_loader_factory_ptr.get(),
+ base::BindOnce(&WebRtcEventLogUploaderImpl::OnURLLoadComplete,
+ base::Unretained(this)),
+ kWebRtcEventLogMaxUploadIdBytes);
+}
+
+void WebRtcEventLogUploaderImpl::OnURLLoadComplete(
+ std::unique_ptr<std::string> response_body) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(url_loader_);
+
+ if (response_body.get() != nullptr && response_body->empty()) {
+ LOG(WARNING) << "SimpleURLLoader reported upload successful, "
+ << "but report ID unknown.";
+ }
+
+ const bool upload_successful =
+ (response_body.get() != nullptr && !response_body->empty());
+
+ // NetError() is 0 when no error occurred.
+ UmaRecordWebRtcEventLoggingNetErrorType(url_loader_->NetError());
+
+ DCHECK(history_file_writer_);
+ if (upload_successful) {
+ if (!history_file_writer_->WriteUploadId(*response_body)) {
+ // Discard the incomplete, potentially now corrupt history file, but the
+ // upload is still considered successful.
+ LOG(ERROR) << "Failed to write upload ID to history file.";
+ DeleteHistoryFile();
+ }
+ } else {
+ LOG(WARNING) << "Upload unsuccessful.";
+ // By not writing an UploadId to the history file, it is inferrable that
+ // the upload was initiated, but did not end successfully.
+ }
+
+ UmaRecordWebRtcEventLoggingUpload(
+ upload_successful ? WebRtcEventLoggingUploadUma::kSuccess
+ : WebRtcEventLoggingUploadUma::kUploadFailure);
+
+ url_loader_.reset(); // Explicitly maintain determinant.
+
+ ReportResult(upload_successful);
+}
+
+void WebRtcEventLogUploaderImpl::ReportResult(bool result) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ // * If the upload was successful, the file is no longer needed.
+ // * If the upload failed, we don't want to retry, because we run the risk of
+ // uploading significant amounts of data once again, only for the upload to
+ // fail again after (as an example) wasting 50MBs of upload bandwidth.
+ // * If the file was not found, this will simply have no effect (other than
+ // to LOG() an error).
+ // TODO(crbug.com/775415): Provide refined retrial behavior.
+ DeleteLogFile();
+
+ // Release hold of history file, allowing it to be read, moved or deleted.
+ history_file_writer_.reset();
+
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), log_file_.path, result));
+}
+
+void WebRtcEventLogUploaderImpl::DeleteLogFile() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ const bool deletion_successful =
+ base::DeleteFile(log_file_.path, /*recursive=*/false);
+ if (!deletion_successful) {
+ // This is a somewhat serious (though unlikely) error, because now we'll
+ // try to upload this file again next time Chrome launches.
+ LOG(ERROR) << "Could not delete pending WebRTC event log file.";
+ }
+}
+
+void WebRtcEventLogUploaderImpl::DeleteHistoryFile() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (!history_file_writer_) {
+ LOG(ERROR) << "Deletion of history file attempted after uploader "
+ << "has relinquished ownership of it.";
+ return;
+ }
+ history_file_writer_->Delete();
+ history_file_writer_.reset();
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
new file mode 100644
index 00000000000..f0699503180
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
@@ -0,0 +1,148 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_UPLOADER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_UPLOADER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
+
+namespace network {
+class SimpleURLLoader;
+} // namespace network
+
+namespace webrtc_event_logging {
+
+// A sublcass of this interface will take ownership of a file, and either
+// upload it to a remote server (actual implementation), or pretend to do so
+// (in unit tests). Upon completion, success/failure will be reported by posting
+// an UploadResultCallback task to the task queue on which this object lives.
+class WebRtcEventLogUploader {
+ public:
+ using UploadResultCallback =
+ base::OnceCallback<void(const base::FilePath& log_file,
+ bool upload_successful)>;
+
+ // Since we'll need more than one instance of the abstract
+ // WebRtcEventLogUploader, we'll need an abstract factory for it.
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ // Creates uploaders. The observer is passed to each call of Create,
+ // rather than be memorized by the factory's constructor, because factories
+ // created by unit tests have no visibility into the real implementation's
+ // observer (WebRtcRemoteEventLogManager).
+ // This takes ownership of the file. The caller must not attempt to access
+ // the file after invoking Create().
+ virtual std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) = 0;
+ };
+
+ virtual ~WebRtcEventLogUploader() = default;
+
+ // Getter for the details of the file this uploader is handling.
+ // Can be called for ongoing, completed, failed or cancelled uploads.
+ virtual const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const = 0;
+
+ // Cancels the upload, then deletes the log file and its history file.
+ // Returns true if the upload was cancelled due to this call, and false if
+ // the upload was already completed or aborted before this call.
+ // (Aborted uploads are ones where the file could not be read, etc.)
+ virtual bool Cancel() = 0;
+};
+
+// Primary implementation of WebRtcEventLogUploader. Uploads log files to crash.
+// Deletes log files whether they were successfully uploaded or not.
+class WebRtcEventLogUploaderImpl : public WebRtcEventLogUploader {
+ public:
+ class Factory : public WebRtcEventLogUploader::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) override;
+
+ protected:
+ friend class WebRtcEventLogUploaderImplTest;
+
+ std::unique_ptr<WebRtcEventLogUploader> CreateWithCustomMaxSizeForTesting(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_remote_log_file_size_bytes);
+ };
+
+ WebRtcEventLogUploaderImpl(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_remote_log_file_size_bytes);
+ ~WebRtcEventLogUploaderImpl() override;
+
+ const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const override;
+
+ bool Cancel() override;
+
+ private:
+ friend class WebRtcEventLogUploaderImplTest;
+
+ // Primes the log file for uploading. Returns true if the file could be read,
+ // in which case |upload_data| will be populated with the data to be uploaded
+ // (both the log file's contents as well as history for Crash).
+ // TODO(crbug.com/775415): Avoid reading the entire file into memory.
+ bool PrepareUploadData(std::string* upload_data);
+
+ // Initiates the file's upload.
+ void StartUpload(const std::string& upload_data);
+
+ // Callback invoked when the file upload has finished.
+ // If the |url_loader_| instance it was bound to is deleted before
+ // its invocation, the callback will not be called.
+ void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
+
+ // Cleanup and posting of the result callback.
+ void ReportResult(bool result);
+
+ // Remove the log file which is owned by |this|.
+ void DeleteLogFile();
+
+ // Remove the log file which is owned by |this|.
+ void DeleteHistoryFile();
+
+ // The URL used for uploading the logs.
+ static const char kUploadURL[];
+
+ // Housekeeping information about the uploaded file (path, time of last
+ // modification, associated BrowserContext).
+ const WebRtcLogFileInfo log_file_;
+
+ // Callback posted back to signal success or failure.
+ UploadResultCallback callback_;
+
+ // Maximum allowed file size. In production code, this is a hard-coded,
+ // but unit tests may set other values.
+ const size_t max_log_file_size_bytes_;
+
+ // Owns a history file which allows the state of the uploaded log to be
+ // remembered after it has been uploaded and/or deleted.
+ std::unique_ptr<WebRtcEventLogHistoryFileWriter> history_file_writer_;
+
+ // This object is in charge of the actual upload.
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+ // The object lives on this IO-capable task runner.
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_UPLOADER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc
new file mode 100644
index 00000000000..853cc085998
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc
@@ -0,0 +1,366 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc_event_logging {
+
+using ::testing::StrictMock;
+using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+
+namespace {
+class UploadObserver {
+ public:
+ explicit UploadObserver(base::OnceClosure on_complete_callback)
+ : on_complete_callback_(std::move(on_complete_callback)) {}
+
+ // Combines the mock functionality via a helper (CompletionCallback),
+ // as well as unblocks its owner through |on_complete_callback_|.
+ void OnWebRtcEventLogUploadComplete(const base::FilePath& log_file,
+ bool upload_successful) {
+ CompletionCallback(log_file, upload_successful);
+ std::move(on_complete_callback_).Run();
+ }
+
+ MOCK_METHOD2(CompletionCallback, void(const base::FilePath&, bool));
+
+ private:
+ base::OnceClosure on_complete_callback_;
+};
+
+#if defined(OS_POSIX)
+void RemovePermissions(const base::FilePath& path, int removed_permissions) {
+ int permissions;
+ ASSERT_TRUE(base::GetPosixFilePermissions(path, &permissions));
+ permissions &= ~removed_permissions;
+ ASSERT_TRUE(base::SetPosixFilePermissions(path, permissions));
+}
+
+void RemoveReadPermissions(const base::FilePath& path) {
+ constexpr int read_permissions = base::FILE_PERMISSION_READ_BY_USER |
+ base::FILE_PERMISSION_READ_BY_GROUP |
+ base::FILE_PERMISSION_READ_BY_OTHERS;
+ RemovePermissions(path, read_permissions);
+}
+#endif // defined(OS_POSIX)
+} // namespace
+
+class WebRtcEventLogUploaderImplTest : public ::testing::Test {
+ public:
+ WebRtcEventLogUploaderImplTest()
+ : test_shared_url_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)),
+ observer_run_loop_(),
+ observer_(observer_run_loop_.QuitWhenIdleClosure()) {
+ TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(
+ test_shared_url_loader_factory_);
+
+ EXPECT_TRUE(base::Time::FromString("30 Dec 1983", &kReasonableTime));
+
+ uploader_factory_ = std::make_unique<WebRtcEventLogUploaderImpl::Factory>();
+ }
+
+ ~WebRtcEventLogUploaderImplTest() override {
+ task_environment_.RunUntilIdle();
+ }
+
+ void SetUp() override {
+ testing_profile_manager_ = std::make_unique<TestingProfileManager>(
+ TestingBrowserProcess::GetGlobal());
+ EXPECT_TRUE(profiles_dir_.CreateUniqueTempDir());
+ EXPECT_TRUE(testing_profile_manager_->SetUp(profiles_dir_.GetPath()));
+
+ testing_profile_ =
+ testing_profile_manager_->CreateTestingProfile("arbitrary_name");
+
+ browser_context_id_ = GetBrowserContextId(testing_profile_);
+
+ // Create the sub-dir for the remote-bound logs that would have been set
+ // up by WebRtcEventLogManager, if WebRtcEventLogManager were instantiated.
+ // Note that the testing profile's overall directory is a temporary one.
+ const base::FilePath logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(testing_profile_->GetPath());
+ ASSERT_TRUE(base::CreateDirectory(logs_dir));
+
+ // Create a log file and put some arbitrary data in it.
+ // Note that the testing profile's overall directory is a temporary one.
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(logs_dir, &log_file_));
+ constexpr size_t kLogFileSizeBytes = 100u;
+ const std::string file_contents(kLogFileSizeBytes, 'A');
+ ASSERT_EQ(
+ base::WriteFile(log_file_, file_contents.c_str(), file_contents.size()),
+ static_cast<int>(file_contents.size()));
+ }
+
+ // For tests which imitate a response (or several).
+ void SetURLLoaderResponse(net::HttpStatusCode http_code, int net_error) {
+ DCHECK(test_shared_url_loader_factory_);
+ const std::string kResponseId = "ec1ed029734b8f7e"; // Arbitrary.
+ test_url_loader_factory_.AddResponse(
+ GURL(WebRtcEventLogUploaderImpl::kUploadURL),
+ network::CreateURLResponseHead(http_code), kResponseId,
+ network::URLLoaderCompletionStatus(net_error));
+ }
+
+ void StartAndWaitForUpload(
+ BrowserContextId browser_context_id = BrowserContextId(),
+ base::Time last_modified_time = base::Time()) {
+ DCHECK(test_shared_url_loader_factory_);
+
+ if (last_modified_time.is_null()) {
+ last_modified_time = kReasonableTime;
+ }
+
+ const WebRtcLogFileInfo log_file_info(browser_context_id, log_file_,
+ last_modified_time);
+
+ uploader_ = uploader_factory_->Create(log_file_info, ResultCallback());
+
+ observer_run_loop_.Run(); // Observer was given quit-closure by ctor.
+ }
+
+ void StartAndWaitForUploadWithCustomMaxSize(
+ size_t max_log_size_bytes,
+ BrowserContextId browser_context_id = BrowserContextId(),
+ base::Time last_modified_time = base::Time()) {
+ DCHECK(test_shared_url_loader_factory_);
+
+ if (last_modified_time.is_null()) {
+ last_modified_time = kReasonableTime;
+ }
+
+ const WebRtcLogFileInfo log_file_info(browser_context_id, log_file_,
+ last_modified_time);
+
+ uploader_ = uploader_factory_->CreateWithCustomMaxSizeForTesting(
+ log_file_info, ResultCallback(), max_log_size_bytes);
+
+ observer_run_loop_.Run(); // Observer was given quit-closure by ctor.
+ }
+
+ void StartUploadThatWillNotTerminate(
+ BrowserContextId browser_context_id = BrowserContextId(),
+ base::Time last_modified_time = base::Time()) {
+ DCHECK(test_shared_url_loader_factory_);
+
+ if (last_modified_time.is_null()) {
+ last_modified_time = kReasonableTime;
+ }
+
+ const WebRtcLogFileInfo log_file_info(browser_context_id, log_file_,
+ last_modified_time);
+
+ uploader_ = uploader_factory_->Create(log_file_info, ResultCallback());
+ }
+
+ WebRtcEventLogUploader::UploadResultCallback ResultCallback() {
+ return base::BindOnce(&UploadObserver::OnWebRtcEventLogUploadComplete,
+ base::Unretained(&observer_));
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+
+ base::Time kReasonableTime;
+
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_;
+
+ base::RunLoop observer_run_loop_;
+
+ base::ScopedTempDir profiles_dir_;
+ std::unique_ptr<TestingProfileManager> testing_profile_manager_;
+ TestingProfile* testing_profile_; // |testing_profile_manager_| owns.
+ BrowserContextId browser_context_id_;
+
+ base::FilePath log_file_;
+
+ StrictMock<UploadObserver> observer_;
+
+ // These (uploader-factory and uploader) are the units under test.
+ std::unique_ptr<WebRtcEventLogUploaderImpl::Factory> uploader_factory_;
+ std::unique_ptr<WebRtcEventLogUploader> uploader_;
+};
+
+TEST_F(WebRtcEventLogUploaderImplTest, SuccessfulUploadReportedToObserver) {
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+ StartAndWaitForUpload();
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+// Version #1 - request reported as successful, but got an error (404) as the
+// HTTP return code.
+// Due to the simplicitly of both tests, this also tests the scenario
+// FileDeletedAfterUnsuccessfulUpload, rather than giving each its own test.
+TEST_F(WebRtcEventLogUploaderImplTest, UnsuccessfulUploadReportedToObserver1) {
+ SetURLLoaderResponse(net::HTTP_NOT_FOUND, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+// Version #2 - request reported as failed; HTTP return code ignored, even
+// if it's a purported success.
+TEST_F(WebRtcEventLogUploaderImplTest, UnsuccessfulUploadReportedToObserver2) {
+ SetURLLoaderResponse(net::HTTP_NOT_FOUND, net::ERR_FAILED);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+#if defined(OS_POSIX)
+TEST_F(WebRtcEventLogUploaderImplTest, FailureToReadFileReportedToObserver) {
+ // Show the failure was independent of the URLLoaderFactory's primed return
+ // value.
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+
+ RemoveReadPermissions(log_file_);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, NonExistentFileReportedToObserver) {
+ // Show the failure was independent of the URLLoaderFactory's primed return
+ // value.
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+
+ log_file_ = log_file_.Append(FILE_PATH_LITERAL("garbage"));
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogUploaderImplTest, FilesUpToMaxSizeUploaded) {
+ int64_t log_file_size_bytes;
+ ASSERT_TRUE(base::GetFileSize(log_file_, &log_file_size_bytes));
+
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+ StartAndWaitForUploadWithCustomMaxSize(log_file_size_bytes);
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, ExcessivelyLargeFilesNotUploaded) {
+ int64_t log_file_size_bytes;
+ ASSERT_TRUE(base::GetFileSize(log_file_, &log_file_size_bytes));
+
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUploadWithCustomMaxSize(log_file_size_bytes - 1);
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ CancelBeforeUploadCompletionReturnsTrue) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+
+ EXPECT_TRUE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, CancelOnCancelledUploadReturnsFalse) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+
+ ASSERT_TRUE(uploader_->Cancel());
+ EXPECT_FALSE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ CancelAfterUploadCompletionReturnsFalse) {
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+ StartAndWaitForUpload();
+
+ EXPECT_FALSE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, CancelOnAbortedUploadReturnsFalse) {
+ // Show the failure was independent of the URLLoaderFactory's primed return
+ // value.
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+
+ log_file_ = log_file_.Append(FILE_PATH_LITERAL("garbage"));
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+
+ EXPECT_FALSE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, CancelOnOngoingUploadDeletesFile) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+ ASSERT_TRUE(uploader_->Cancel());
+
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ GetWebRtcLogFileInfoReturnsCorrectInfoBeforeUploadDone) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+
+ const WebRtcLogFileInfo info = uploader_->GetWebRtcLogFileInfo();
+ EXPECT_EQ(info.browser_context_id, browser_context_id_);
+ EXPECT_EQ(info.path, log_file_);
+ EXPECT_EQ(info.last_modified, last_modified);
+
+ // Test tear-down.
+ ASSERT_TRUE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ GetWebRtcLogFileInfoReturnsCorrectInfoAfterUploadSucceeded) {
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+
+ const base::Time last_modified = base::Time::Now();
+ StartAndWaitForUpload(browser_context_id_, last_modified);
+
+ const WebRtcLogFileInfo info = uploader_->GetWebRtcLogFileInfo();
+ EXPECT_EQ(info.browser_context_id, browser_context_id_);
+ EXPECT_EQ(info.path, log_file_);
+ EXPECT_EQ(info.last_modified, last_modified);
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ GetWebRtcLogFileInfoReturnsCorrectInfoWhenCalledOnCancelledUpload) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+ ASSERT_TRUE(uploader_->Cancel());
+
+ const WebRtcLogFileInfo info = uploader_->GetWebRtcLogFileInfo();
+ EXPECT_EQ(info.browser_context_id, browser_context_id_);
+ EXPECT_EQ(info.path, log_file_);
+ EXPECT_EQ(info.last_modified, last_modified);
+}
+
+// TODO(crbug.com/775415): Add a unit test that shows that files with
+// non-ASCII filenames are discard. (Or, alternatively, add support for them.)
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
new file mode 100644
index 00000000000..44027f50260
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -0,0 +1,196 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+namespace {
+
+static const char kMainHtmlPage[] = "/webrtc/webrtc_getdisplaymedia_test.html";
+
+struct TestConfig {
+ const char* display_surface;
+ const char* logical_surface;
+ const char* cursor;
+};
+
+} // namespace
+
+// Base class for top level tests for getDisplayMedia().
+class WebRtcGetDisplayMediaBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void RunGetDisplayMedia(content::WebContents* tab,
+ const std::string& constraints,
+ bool is_fake_ui = false) {
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(),
+ base::StringPrintf("runGetDisplayMedia(%s);", constraints.c_str()),
+ &result));
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default. The permission is reported as granted
+ // if the fake UI is used.
+ EXPECT_EQ(result, base::mac::IsAtMostOS10_14() || is_fake_ui
+ ? "getdisplaymedia-success"
+ : "getdisplaymedia-failure");
+#else
+ EXPECT_EQ(result, "getdisplaymedia-success");
+#endif
+ }
+};
+
+// Top level test for getDisplayMedia(). Pops picker Ui and selects desktop
+// capture by default.
+class WebRtcGetDisplayMediaBrowserTestWithPicker
+ : public WebRtcGetDisplayMediaBrowserTest {
+ public:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(
+ switches::kEnableExperimentalWebPlatformFeatures);
+ command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
+ "Entire screen");
+ }
+};
+
+// Real desktop capture is flaky on below platforms.
+#if defined(OS_CHROMEOS) || defined(OS_WIN)
+#define MAYBE_GetDisplayMediaVideo DISABLED_GetDisplayMediaVideo
+#else
+#define MAYBE_GetDisplayMediaVideo GetDisplayMediaVideo
+#endif // defined(OS_CHROMEOS) || defined(OS_WIN)
+IN_PROC_BROWSER_TEST_F(WebRtcGetDisplayMediaBrowserTestWithPicker,
+ MAYBE_GetDisplayMediaVideo) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true}");
+ RunGetDisplayMedia(tab, constraints);
+}
+
+// Real desktop capture is flaky on below platforms.
+#if defined(OS_CHROMEOS) || defined(OS_WIN)
+#define MAYBE_GetDisplayMediaVideoAndAudio DISABLED_GetDisplayMediaVideoAndAudio
+// On linux debug bots, it's flaky as well.
+#elif (defined(OS_LINUX) && !defined(NDEBUG))
+#define MAYBE_GetDisplayMediaVideoAndAudio DISABLED_GetDisplayMediaVideoAndAudio
+// On linux asan bots, it's flaky as well - msan and other rel bot are fine.
+#elif (defined(OS_LINUX) && defined(ADDRESS_SANITIZER))
+#define MAYBE_GetDisplayMediaVideoAndAudio DISABLED_GetDisplayMediaVideoAndAudio
+#else
+#define MAYBE_GetDisplayMediaVideoAndAudio GetDisplayMediaVideoAndAudio
+#endif // defined(OS_CHROMEOS) || defined(OS_WIN)
+IN_PROC_BROWSER_TEST_F(WebRtcGetDisplayMediaBrowserTestWithPicker,
+ MAYBE_GetDisplayMediaVideoAndAudio) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true, audio:true}");
+ RunGetDisplayMedia(tab, constraints);
+}
+
+// Top level test for getDisplayMedia(). Skips picker UI and uses fake device
+// with specified type.
+class WebRtcGetDisplayMediaBrowserTestWithFakeUI
+ : public WebRtcGetDisplayMediaBrowserTest,
+ public testing::WithParamInterface<TestConfig> {
+ public:
+ WebRtcGetDisplayMediaBrowserTestWithFakeUI() {
+ test_config_ = GetParam();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(
+ switches::kEnableExperimentalWebPlatformFeatures);
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+ command_line->AppendSwitchASCII(
+ switches::kUseFakeDeviceForMediaStream,
+ base::StringPrintf("display-media-type=%s",
+ test_config_.display_surface));
+ }
+
+ protected:
+ TestConfig test_config_;
+};
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ GetDisplayMediaVideo) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true}");
+ RunGetDisplayMedia(tab, constraints, /*is_fake_ui=*/true);
+
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getDisplaySurfaceSetting();", &result));
+ EXPECT_EQ(result, test_config_.display_surface);
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getLogicalSurfaceSetting();", &result));
+ EXPECT_EQ(result, test_config_.logical_surface);
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getCursorSetting();", &result));
+ EXPECT_EQ(result, test_config_.cursor);
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ GetDisplayMediaVideoAndAudio) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true, audio:true}");
+ RunGetDisplayMedia(tab, constraints, /*is_fake_ui=*/true);
+
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "hasAudioTrack();", &result));
+ EXPECT_EQ(result, "true");
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ GetDisplayMediaWithConstraints) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ const int kMaxWidth = 200;
+ const int kMaxFrameRate = 6;
+ const std::string& constraints =
+ base::StringPrintf("{video: {width: {max: %d}, frameRate: {max: %d}}}",
+ kMaxWidth, kMaxFrameRate);
+ RunGetDisplayMedia(tab, constraints, /*is_fake_ui=*/true);
+
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getWidthSetting();", &result));
+ EXPECT_EQ(result, base::StringPrintf("%d", kMaxWidth));
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getFrameRateSetting();", &result));
+ EXPECT_EQ(result, base::StringPrintf("%d", kMaxFrameRate));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ testing::Values(TestConfig{"monitor", "true", "never"},
+ TestConfig{"window", "true", "never"},
+ TestConfig{"browser", "true",
+ "never"}));
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc
new file mode 100644
index 00000000000..b5fea966103
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc
@@ -0,0 +1,360 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browsing_data_remover.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/browsing_data_remover_test_util.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+
+namespace {
+
+const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+
+const char kDeviceKindAudioInput[] = "audioinput";
+const char kDeviceKindVideoInput[] = "videoinput";
+const char kDeviceKindAudioOutput[] = "audiooutput";
+
+} // namespace
+
+// Integration test for WebRTC enumerateDevices. It always uses fake devices.
+// It needs to be a browser test (and not content browser test) to be able to
+// test that labels are cleared or not depending on if access to devices has
+// been granted.
+class WebRtcGetMediaDevicesBrowserTest
+ : public WebRtcTestBase,
+ public testing::WithParamInterface<bool> {
+ public:
+ WebRtcGetMediaDevicesBrowserTest()
+ : has_audio_output_devices_initialized_(false),
+ has_audio_output_devices_(false) {
+ std::vector<base::Feature> audio_service_oop_features = {
+ features::kAudioServiceAudioStreams,
+ features::kAudioServiceOutOfProcess};
+ if (GetParam()) {
+ // Force audio service out of process to enabled.
+ audio_service_features_.InitWithFeatures(audio_service_oop_features, {});
+ } else {
+ // Force audio service out of process to disabled.
+ audio_service_features_.InitWithFeatures({}, audio_service_oop_features);
+ }
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Always use fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+
+ protected:
+ // This is used for media devices and sources.
+ struct MediaDeviceInfo {
+ std::string device_id; // Domain specific device ID.
+ std::string kind;
+ std::string label;
+ std::string group_id;
+ };
+
+ void EnumerateDevices(content::WebContents* tab,
+ std::vector<MediaDeviceInfo>* devices) {
+ std::string devices_as_json = ExecuteJavascript("enumerateDevices()", tab);
+ EXPECT_FALSE(devices_as_json.empty());
+
+ int error_code;
+ std::string error_message;
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::ReadAndReturnErrorDeprecated(
+ devices_as_json, base::JSON_ALLOW_TRAILING_COMMAS, &error_code,
+ &error_message);
+
+ ASSERT_TRUE(value.get() != NULL) << error_message;
+ EXPECT_EQ(value->type(), base::Value::Type::LIST);
+
+ base::ListValue* values;
+ ASSERT_TRUE(value->GetAsList(&values));
+ ASSERT_FALSE(values->empty());
+ bool found_audio_input = false;
+ bool found_video_input = false;
+
+ for (auto it = values->begin(); it != values->end(); ++it) {
+ const base::DictionaryValue* dict;
+ MediaDeviceInfo device;
+ ASSERT_TRUE(it->GetAsDictionary(&dict));
+ ASSERT_TRUE(dict->GetString("deviceId", &device.device_id));
+ ASSERT_TRUE(dict->GetString("kind", &device.kind));
+ ASSERT_TRUE(dict->GetString("label", &device.label));
+ ASSERT_TRUE(dict->GetString("groupId", &device.group_id));
+
+ // Should be HMAC SHA256.
+ if (!media::AudioDeviceDescription::IsDefaultDevice(device.device_id) &&
+ !(device.device_id ==
+ media::AudioDeviceDescription::kCommunicationsDeviceId)) {
+ EXPECT_EQ(64ul, device.device_id.length());
+ EXPECT_TRUE(
+ base::ContainsOnlyChars(device.device_id, "0123456789abcdef"));
+ }
+
+ EXPECT_TRUE(device.kind == kDeviceKindAudioInput ||
+ device.kind == kDeviceKindVideoInput ||
+ device.kind == kDeviceKindAudioOutput);
+ if (device.kind == kDeviceKindAudioInput) {
+ found_audio_input = true;
+ } else if (device.kind == kDeviceKindVideoInput) {
+ found_video_input = true;
+ }
+
+ EXPECT_FALSE(device.group_id.empty());
+ devices->push_back(device);
+ }
+
+ EXPECT_TRUE(found_audio_input);
+ EXPECT_TRUE(found_video_input);
+ }
+
+ static void CheckEnumerationsAreDifferent(
+ const std::vector<MediaDeviceInfo>& devices,
+ const std::vector<MediaDeviceInfo>& devices2) {
+ for (auto& device : devices) {
+ auto it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.device_id == device_info.device_id;
+ });
+ if (device.device_id == media::AudioDeviceDescription::kDefaultDeviceId ||
+ device.device_id ==
+ media::AudioDeviceDescription::kCommunicationsDeviceId) {
+ EXPECT_NE(it, devices2.end());
+ } else {
+ EXPECT_EQ(it, devices2.end());
+ }
+
+ it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.group_id == device_info.group_id;
+ });
+ EXPECT_EQ(it, devices2.end());
+ }
+ }
+
+ bool has_audio_output_devices_initialized_;
+ bool has_audio_output_devices_;
+
+ private:
+ base::test::ScopedFeatureList audio_service_features_;
+};
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ EnumerateDevicesWithoutAccess) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ // Labels should be empty if access has not been allowed.
+ for (const auto& device_info : devices) {
+ EXPECT_TRUE(device_info.label.empty());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ EnumerateDevicesWithAccess) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ EXPECT_TRUE(GetUserMediaAndAccept(tab));
+
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ // Labels should be non-empty if access has been allowed.
+ for (const auto& device_info : devices) {
+ EXPECT_TRUE(!device_info.label.empty());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdSameGroupIdDiffersAfterReload) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ ui_test_utils::NavigateToURL(browser(), url);
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab, &devices2);
+
+ EXPECT_EQ(devices.size(), devices2.size());
+ for (auto& device : devices) {
+ auto it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.device_id == device_info.device_id;
+ });
+ EXPECT_NE(it, devices2.end());
+
+ it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.group_id == device_info.group_id;
+ });
+ EXPECT_EQ(it, devices2.end());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdSameGroupIdDiffersAcrossTabs) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab1 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab1, &devices);
+
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab2 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab2, &devices2);
+
+ EXPECT_NE(tab1, tab2);
+ EXPECT_EQ(devices.size(), devices2.size());
+ for (auto& device : devices) {
+ auto it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.device_id == device_info.device_id;
+ });
+ EXPECT_NE(it, devices2.end());
+
+ it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.group_id == device_info.group_id;
+ });
+ EXPECT_EQ(it, devices2.end());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdDiffersAfterClearingCookies) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ auto* remover =
+ content::BrowserContext::GetBrowsingDataRemover(browser()->profile());
+ content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
+ remover->RemoveAndReply(
+ base::Time(), base::Time::Max(),
+ content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+ content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
+ &completion_observer);
+ completion_observer.BlockUntilCompletion();
+
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab, &devices2);
+
+ EXPECT_EQ(devices.size(), devices2.size());
+ CheckEnumerationsAreDifferent(devices, devices2);
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdDiffersAcrossTabsWithCookiesDisabled) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ CookieSettingsFactory::GetForProfile(browser()->profile())
+ ->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
+ content::WebContents* tab1 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab1, &devices);
+
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab2 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab2, &devices2);
+
+ EXPECT_NE(tab1, tab2);
+ EXPECT_EQ(devices.size(), devices2.size());
+ CheckEnumerationsAreDifferent(devices, devices2);
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdDiffersSameTabAfterReloadWithCookiesDisabled) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ CookieSettingsFactory::GetForProfile(browser()->profile())
+ ->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ ui_test_utils::NavigateToURL(browser(), url);
+ tab = browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab, &devices2);
+
+ EXPECT_EQ(devices.size(), devices2.size());
+ CheckEnumerationsAreDifferent(devices, devices2);
+}
+
+// We run these tests with the audio service both in and out of the the browser
+// process to have waterfall coverage while the feature rolls out. It should be
+// removed after launch.
+#if defined(OS_WIN) || defined(OS_MACOSX) || \
+ (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// Platforms where the out of process audio service is supported.
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcGetMediaDevicesBrowserTest,
+ ::testing::Values(true));
+#else
+// Platforms where the out of process audio service is not supported.
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcGetMediaDevicesBrowserTest,
+ ::testing::Values(false));
+#endif
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc
new file mode 100644
index 00000000000..3c49d176b84
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using webrtc_event_logging::WebRtcEventLogManager;
+
+namespace {
+const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+}
+
+class WebRTCInternalsIntegrationBrowserTest : public WebRtcTestBase {
+ public:
+ ~WebRTCInternalsIntegrationBrowserTest() override = default;
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
+
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(local_logs_dir_.CreateUniqueTempDir());
+ }
+ command_line->AppendSwitchASCII(switches::kWebRtcLocalEventLogging,
+ local_logs_dir_.GetPath().MaybeAsASCII());
+ }
+
+ // To avoid flaky tests, we need to synchronize with WebRtcEventLogger's
+ // internal task runners (if any exist) before we examine anything we
+ // expect to be produced by WebRtcEventLogger (files, etc.).
+ void WaitForEventLogProcessing() {
+ WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
+ ASSERT_TRUE(manager);
+
+ base::RunLoop run_loop;
+ manager->PostNullTaskForTesting(run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ }
+
+ bool IsDirectoryEmpty(const base::FilePath& path) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ return base::IsDirectoryEmpty(path);
+ }
+
+ base::ScopedTempDir local_logs_dir_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsIntegrationBrowserTest,
+ IntegrationWithWebRtcEventLogger) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ ASSERT_TRUE(IsDirectoryEmpty(local_logs_dir_.GetPath())); // Sanity on test.
+
+ // Local WebRTC event logging turned on from command line using the
+ // kWebRtcLocalEventLogging flag. When we set up a peer connection, it
+ // will be logged to a file under |local_logs_dir_|.
+ SetupPeerconnectionWithLocalStream(tab);
+
+ WaitForEventLogProcessing();
+
+ EXPECT_FALSE(IsDirectoryEmpty(local_logs_dir_.GetPath()));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc
new file mode 100644
index 00000000000..08e26798891
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc
@@ -0,0 +1,309 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_perf.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/perf/perf_test.h"
+#include "third_party/blink/public/common/features.h"
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+std::string MakePerfTestLabel(std::string base, bool opus_dtx) {
+ if (opus_dtx) {
+ return base + "_with_opus_dtx";
+ }
+ return base;
+}
+
+// Performance browsertest for WebRTC. This test is manual since it takes long
+// to execute and requires the reference files provided by the webrtc.DEPS
+// solution (which is only available on WebRTC internal bots).
+// Gets its metrics from "chrome://webrtc-internals".
+class WebRtcInternalsPerfBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Play a suitable, somewhat realistic video file.
+ base::FilePath input_video = test::GetReferenceFilesDir()
+ .Append(test::kReferenceFileName360p)
+ .AddExtension(test::kY4mFileExtension);
+ command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture,
+ input_video);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+
+ // Tries to extract data from peerConnectionDataStore in the webrtc-internals
+ // tab. The caller owns the parsed data. Returns NULL on failure.
+ base::DictionaryValue* GetWebrtcInternalsData(
+ content::WebContents* webrtc_internals_tab) {
+ std::string all_stats_json = ExecuteJavascript(
+ "window.domAutomationController.send("
+ " JSON.stringify(peerConnectionDataStore));",
+ webrtc_internals_tab);
+
+ std::unique_ptr<base::Value> parsed_json =
+ base::JSONReader::ReadDeprecated(all_stats_json);
+ base::DictionaryValue* result;
+ if (parsed_json.get() && parsed_json->GetAsDictionary(&result)) {
+ ignore_result(parsed_json.release());
+ return result;
+ }
+
+ return NULL;
+ }
+
+ const base::DictionaryValue* GetDataOnPeerConnection(
+ const base::DictionaryValue* all_data,
+ int peer_connection_index) {
+ base::DictionaryValue::Iterator iterator(*all_data);
+
+ for (int i = 0; i < peer_connection_index && !iterator.IsAtEnd();
+ --peer_connection_index) {
+ iterator.Advance();
+ }
+
+ const base::DictionaryValue* result;
+ if (!iterator.IsAtEnd() && iterator.value().GetAsDictionary(&result))
+ return result;
+
+ return NULL;
+ }
+
+ std::unique_ptr<base::DictionaryValue> MeasureWebRtcInternalsData(
+ int duration_msec) {
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), GURL("chrome://webrtc-internals"));
+ content::WebContents* webrtc_internals_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // TODO(https://crbug.com/1004239): Stop relying on the legacy getStats()
+ // API.
+ ChangeToLegacyGetStats(webrtc_internals_tab);
+ test::SleepInJavascript(webrtc_internals_tab, duration_msec);
+
+ return std::unique_ptr<base::DictionaryValue>(
+ GetWebrtcInternalsData(webrtc_internals_tab));
+ }
+
+ void RunsAudioVideoCall60SecsAndLogsInternalMetrics(
+ const std::string& video_codec,
+ bool prefer_hw_video_codec = false,
+ const std::string& video_codec_profile = std::string(),
+ const std::string& video_codec_print_modifier = std::string()) {
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 100000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 100000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ content::WebContents* left_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ content::WebContents* right_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ SetDefaultVideoCodec(right_tab, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ StartDetectingVideo(left_tab, "remote-view");
+ StartDetectingVideo(right_tab, "remote-view");
+
+ WaitForVideoToPlay(left_tab);
+ WaitForVideoToPlay(right_tab);
+
+ // Let values stabilize, bandwidth ramp up, etc.
+ test::SleepInJavascript(left_tab, 60000);
+
+ // Start measurements.
+ std::unique_ptr<base::DictionaryValue> all_data =
+ MeasureWebRtcInternalsData(10000);
+ ASSERT_TRUE(all_data.get() != NULL);
+
+ const base::DictionaryValue* first_pc_dict =
+ GetDataOnPeerConnection(all_data.get(), 0);
+ ASSERT_TRUE(first_pc_dict != NULL);
+ const std::string print_modifier = video_codec_print_modifier.empty()
+ ? video_codec
+ : video_codec_print_modifier;
+ test::PrintBweForVideoMetrics(*first_pc_dict, "", print_modifier);
+ test::PrintMetricsForAllStreams(*first_pc_dict, "", print_modifier);
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+ }
+
+ void RunsOneWayCall60SecsAndLogsInternalMetrics(
+ const std::string& video_codec,
+ bool opus_dtx) {
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 100000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 100000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ content::WebContents* left_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ content::WebContents* right_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithoutLocalStream(right_tab);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab, video_codec, false /* prefer_hw_codec */);
+ SetDefaultVideoCodec(right_tab, video_codec, false /* prefer_hw_codec */);
+ }
+ if (opus_dtx) {
+ EnableOpusDtx(left_tab);
+ EnableOpusDtx(right_tab);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ // Remote video will only play in one tab since the call is one-way.
+ StartDetectingVideo(right_tab, "remote-view");
+ WaitForVideoToPlay(right_tab);
+
+ // Let values stabilize, bandwidth ramp up, etc.
+ test::SleepInJavascript(left_tab, 60000);
+
+ std::unique_ptr<base::DictionaryValue> all_data =
+ MeasureWebRtcInternalsData(10000);
+ ASSERT_TRUE(all_data.get() != NULL);
+
+ // This assumes the sending peer connection is always listed first in the
+ // data store, and the receiving second.
+ const base::DictionaryValue* first_pc_dict =
+ GetDataOnPeerConnection(all_data.get(), 0);
+ ASSERT_TRUE(first_pc_dict != NULL);
+ test::PrintBweForVideoMetrics(
+ *first_pc_dict, MakePerfTestLabel("_sendonly", opus_dtx), video_codec);
+ test::PrintMetricsForSendStreams(
+ *first_pc_dict, MakePerfTestLabel("_sendonly", opus_dtx), video_codec);
+
+ const base::DictionaryValue* second_pc_dict =
+ GetDataOnPeerConnection(all_data.get(), 1);
+ ASSERT_TRUE(second_pc_dict != NULL);
+ test::PrintBweForVideoMetrics(
+ *second_pc_dict, MakePerfTestLabel("_recvonly", opus_dtx), video_codec);
+ test::PrintMetricsForRecvStreams(
+ *second_pc_dict, MakePerfTestLabel("_recvonly", opus_dtx), video_codec);
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+ }
+};
+
+// This is manual for its long execution time.
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsVp8) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics("VP8");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsVp9) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics("VP9");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsVp9Profile2) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics(
+ "VP9", true /* prefer_hw_video_codec */,
+ WebRtcTestBase::kVP9Profile2Specifier, "VP9p2");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsH264) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING)
+ << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcInternalsPerfBrowserTest."
+ "MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsH264 (test "
+ "\"OK\")";
+ return;
+ }
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics(
+ "H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsOneWayCall60SecsAndLogsInternalMetricsDefault) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsOneWayCall60SecsAndLogsInternalMetrics("", false);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsOneWayCall60SecsAndLogsInternalMetricsWithOpusDtx) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsOneWayCall60SecsAndLogsInternalMetrics("", true);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc
new file mode 100644
index 00000000000..14e87d6621d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc
@@ -0,0 +1,42 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_log_buffer.h"
+
+#include "base/logging.h"
+
+WebRtcLogBuffer::WebRtcLogBuffer()
+ : buffer_(),
+ circular_(&buffer_[0], sizeof(buffer_), sizeof(buffer_) / 2, false),
+ read_only_(false) {}
+
+WebRtcLogBuffer::~WebRtcLogBuffer() {
+#if DCHECK_IS_ON()
+ DCHECK(read_only_ || sequence_checker_.CalledOnValidSequence());
+#endif
+}
+
+void WebRtcLogBuffer::Log(const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!read_only_);
+ circular_.Write(message.c_str(), message.length());
+ const char eol = '\n';
+ circular_.Write(&eol, 1);
+}
+
+webrtc_logging::PartialCircularBuffer WebRtcLogBuffer::Read() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(read_only_);
+ return webrtc_logging::PartialCircularBuffer(&buffer_[0], sizeof(buffer_));
+}
+
+void WebRtcLogBuffer::SetComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!read_only_) << "Already set? (programmer error)";
+ read_only_ = true;
+ // Detach from the current sequence so that we can check reads on a different
+ // sequence. This is to make sure that Read()s still happen on one sequence
+ // only.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h
new file mode 100644
index 00000000000..66b3c283f60
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h
@@ -0,0 +1,47 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_BUFFER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_BUFFER_H_
+
+#include <string>
+
+#include "base/sequence_checker.h"
+#include "build/build_config.h"
+#include "components/webrtc_logging/common/partial_circular_buffer.h"
+
+#if defined(OS_ANDROID)
+const size_t kWebRtcLogSize = 1 * 1024 * 1024; // 1 MB
+#else
+const size_t kWebRtcLogSize = 6 * 1024 * 1024; // 6 MB
+#endif
+
+class WebRtcLogBuffer {
+ public:
+ WebRtcLogBuffer();
+ ~WebRtcLogBuffer();
+
+ void Log(const std::string& message);
+
+ // Returns a circular buffer instance for reading the internal log buffer.
+ // Must only be called after the log has been marked as complete
+ // (see SetComplete) and the caller must ensure that the WebRtcLogBuffer
+ // instance remains in scope for the lifetime of the returned circular buffer.
+ webrtc_logging::PartialCircularBuffer Read();
+
+ // Switches the buffer to read-only mode, where access to the internal
+ // buffer is allowed from different threads than were used to contribute
+ // to the log. Calls to Log() won't be allowed after calling
+ // SetComplete() and the call to SetComplete() must be done on the same
+ // thread as constructed the buffer and calls Log().
+ void SetComplete();
+
+ private:
+ SEQUENCE_CHECKER(sequence_checker_);
+ uint8_t buffer_[kWebRtcLogSize];
+ webrtc_logging::PartialCircularBuffer circular_;
+ bool read_only_;
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_BUFFER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc
new file mode 100644
index 00000000000..1b386f4a803
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc
@@ -0,0 +1,633 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+
+#include <stddef.h>
+#include <cstdlib>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/pickle.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "components/version_info/version_info.h"
+#include "components/webrtc_logging/browser/log_cleanup.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "components/webrtc_logging/common/partial_circular_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+const int kLogCountLimit = 5;
+const uint32_t kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB
+const int kLogListLimitLines = 50;
+
+const char kWebrtcLogUploadContentType[] = "multipart/form-data";
+const char kWebrtcLogMultipartBoundary[] =
+ "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
+
+// Adds the header section for a gzip file to the multipart |post_data|.
+void AddMultipartFileContentHeader(std::string* post_data,
+ const std::string& content_name) {
+ post_data->append("--");
+ post_data->append(kWebrtcLogMultipartBoundary);
+ post_data->append("\r\nContent-Disposition: form-data; name=\"");
+ post_data->append(content_name);
+ post_data->append("\"; filename=\"");
+ post_data->append(content_name + ".gz");
+ post_data->append("\"\r\nContent-Type: application/gzip\r\n\r\n");
+}
+
+// Adds |compressed_log| to |post_data|.
+void AddLogData(std::string* post_data, const std::string& compressed_log) {
+ AddMultipartFileContentHeader(post_data, "webrtc_log");
+ post_data->append(compressed_log);
+ post_data->append("\r\n");
+}
+
+// Adds the RTP dump data to |post_data|.
+void AddRtpDumpData(std::string* post_data,
+ const std::string& name,
+ const std::string& dump_data) {
+ AddMultipartFileContentHeader(post_data, name);
+ post_data->append(dump_data.data(), dump_data.size());
+ post_data->append("\r\n");
+}
+
+// Helper for WebRtcLogUploader::CompressLog().
+void ResizeForNextOutput(std::string* compressed_log, z_stream* stream) {
+ size_t old_size = compressed_log->size() - stream->avail_out;
+ compressed_log->resize(old_size + kIntermediateCompressionBufferBytes);
+ stream->next_out =
+ reinterpret_cast<unsigned char*>(&(*compressed_log)[old_size]);
+ stream->avail_out = kIntermediateCompressionBufferBytes;
+}
+
+} // namespace
+
+WebRtcLogUploader::UploadDoneData::UploadDoneData() = default;
+WebRtcLogUploader::UploadDoneData::UploadDoneData(
+ const WebRtcLogUploader::UploadDoneData& other) = default;
+WebRtcLogUploader::UploadDoneData::~UploadDoneData() = default;
+
+WebRtcLogUploader::WebRtcLogUploader()
+ : main_task_runner_(base::SequencedTaskRunnerHandle::Get()),
+ background_task_runner_(
+ base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT})) {}
+
+WebRtcLogUploader::~WebRtcLogUploader() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ DCHECK(pending_uploads_.empty());
+ DCHECK(shutdown_);
+}
+
+bool WebRtcLogUploader::ApplyForStartLogging() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ if (log_count_ < kLogCountLimit && !shutdown_) {
+ ++log_count_;
+ return true;
+ }
+ return false;
+}
+
+void WebRtcLogUploader::LoggingStoppedDontUpload() {
+ DecreaseLogCount();
+}
+
+void WebRtcLogUploader::LoggingStoppedDoUpload(
+ std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(log_buffer.get());
+ DCHECK(meta_data.get());
+ DCHECK(!upload_done_data.paths.directory.empty());
+
+ std::string compressed_log = CompressLog(log_buffer.get());
+
+ std::string local_log_id;
+
+ if (base::PathExists(upload_done_data.paths.directory)) {
+ webrtc_logging::DeleteOldWebRtcLogFiles(upload_done_data.paths.directory);
+
+ local_log_id = base::NumberToString(base::Time::Now().ToDoubleT());
+ base::FilePath log_file_path =
+ upload_done_data.paths.directory.AppendASCII(local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+ WriteCompressedLogToFile(compressed_log, log_file_path);
+
+ base::FilePath log_list_path =
+ webrtc_logging::TextLogList::GetWebRtcLogListFileForDirectory(
+ upload_done_data.paths.directory);
+ AddLocallyStoredLogInfoToUploadListFile(log_list_path, local_log_id);
+ }
+
+ UploadDoneData upload_done_data_with_log_id = upload_done_data;
+ upload_done_data_with_log_id.local_log_id = local_log_id;
+ PrepareMultipartPostData(compressed_log, std::move(meta_data),
+ upload_done_data_with_log_id);
+}
+
+void WebRtcLogUploader::PrepareMultipartPostData(
+ const std::string& compressed_log,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!compressed_log.empty());
+ DCHECK(meta_data.get());
+
+ std::unique_ptr<std::string> post_data(new std::string());
+ SetupMultipart(post_data.get(), compressed_log,
+ upload_done_data.paths.incoming_rtp_dump,
+ upload_done_data.paths.outgoing_rtp_dump, *meta_data.get());
+
+ // If a test has set the test string pointer, write to it and skip uploading.
+ // Still fire the upload callback so that we can run an extension API test
+ // using the test framework for that without hanging.
+ // TODO(grunell): Remove this when the api test for this feature is fully
+ // implemented according to the test plan. http://crbug.com/257329.
+ if (post_data_) {
+ *post_data_ = *post_data;
+ NotifyUploadDoneAndLogStats(net::HTTP_OK, net::OK, "", upload_done_data);
+ return;
+ }
+
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcLogUploader::UploadCompressedLog,
+ base::Unretained(this), upload_done_data,
+ std::move(post_data)));
+}
+
+void WebRtcLogUploader::UploadStoredLog(
+ const WebRtcLogUploader::UploadDoneData& upload_data) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!upload_data.local_log_id.empty());
+ DCHECK(!upload_data.paths.directory.empty());
+
+ base::FilePath native_log_path =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+
+ std::string compressed_log;
+ if (!base::ReadFileToString(native_log_path, &compressed_log)) {
+ DPLOG(WARNING) << "Could not read WebRTC log file.";
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailed",
+ upload_data.web_app_id);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ WebRtcLogUploadFailureReason::kStoredLogNotFound);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(upload_data.callback, false, "", "Log doesn't exist."));
+ return;
+ }
+
+ UploadDoneData upload_data_with_rtp = upload_data;
+
+ // Optimistically set the rtp paths to what they should be if they exist.
+ upload_data_with_rtp.paths.incoming_rtp_dump =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".rtp_in"));
+
+ upload_data_with_rtp.paths.outgoing_rtp_dump =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".rtp_out"));
+
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data(new WebRtcLogMetaDataMap());
+ {
+ std::string meta_data_contents;
+ base::FilePath meta_path =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".meta"));
+ if (base::ReadFileToString(meta_path, &meta_data_contents) &&
+ !meta_data_contents.empty()) {
+ base::Pickle pickle(&meta_data_contents[0], meta_data_contents.size());
+ base::PickleIterator it(pickle);
+ std::string key, value;
+ while (it.ReadString(&key) && it.ReadString(&value))
+ (*meta_data.get())[key] = value;
+ }
+ }
+
+ PrepareMultipartPostData(compressed_log, std::move(meta_data),
+ upload_data_with_rtp);
+}
+
+void WebRtcLogUploader::LoggingStoppedDoStore(
+ const WebRtcLogPaths& log_paths,
+ const std::string& log_id,
+ std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& done_callback) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!log_id.empty());
+ DCHECK(log_buffer.get());
+ DCHECK(!log_paths.directory.empty());
+
+ webrtc_logging::DeleteOldWebRtcLogFiles(log_paths.directory);
+
+ base::FilePath log_list_path =
+ webrtc_logging::TextLogList::GetWebRtcLogListFileForDirectory(
+ log_paths.directory);
+
+ // Store the native log with a ".gz" extension.
+ std::string compressed_log = CompressLog(log_buffer.get());
+ base::FilePath native_log_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".gz"));
+ WriteCompressedLogToFile(compressed_log, native_log_path);
+ AddLocallyStoredLogInfoToUploadListFile(log_list_path, log_id);
+
+ // Move the rtp dump files to the log directory with a name of
+ // <log id>.rtp_[in|out].
+ if (!log_paths.incoming_rtp_dump.empty()) {
+ base::FilePath rtp_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".rtp_in"));
+ base::Move(log_paths.incoming_rtp_dump, rtp_path);
+ }
+
+ if (!log_paths.outgoing_rtp_dump.empty()) {
+ base::FilePath rtp_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".rtp_out"));
+ base::Move(log_paths.outgoing_rtp_dump, rtp_path);
+ }
+
+ if (meta_data.get() && !meta_data->empty()) {
+ base::Pickle pickle;
+ for (const auto& it : *meta_data.get()) {
+ pickle.WriteString(it.first);
+ pickle.WriteString(it.second);
+ }
+ base::FilePath meta_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".meta"));
+ base::WriteFile(meta_path, static_cast<const char*>(pickle.data()),
+ pickle.size());
+ }
+
+ main_task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(done_callback, true, ""));
+
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcLogUploader::DecreaseLogCount,
+ base::Unretained(this)));
+}
+
+void WebRtcLogUploader::Shutdown() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ DCHECK(!shutdown_);
+
+ // Clear the pending uploads list, which will reset all URL loaders.
+ pending_uploads_.clear();
+ shutdown_ = true;
+}
+
+void WebRtcLogUploader::OnSimpleLoaderComplete(
+ SimpleURLLoaderList::iterator it,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> response_body) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ DCHECK(!shutdown_);
+ network::SimpleURLLoader* loader = it->get();
+ base::Optional<int> response_code;
+ if (loader->ResponseInfo() && loader->ResponseInfo()->headers) {
+ response_code = loader->ResponseInfo()->headers->response_code();
+ }
+ const int network_error_code = loader->NetError();
+ pending_uploads_.erase(it);
+ std::string report_id;
+ if (response_body)
+ report_id = std::move(*response_body);
+ // The log path can be empty here if we failed getting it before. We still
+ // upload the log if that's the case.
+ if (!upload_done_data.paths.directory.empty()) {
+ // TODO(jiayl): Add the RTP dump records to chrome://webrtc-logs.
+ base::FilePath log_list_path =
+ webrtc_logging::TextLogList::GetWebRtcLogListFileForDirectory(
+ upload_done_data.paths.directory);
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile,
+ log_list_path, upload_done_data.local_log_id,
+ report_id));
+ }
+ NotifyUploadDoneAndLogStats(response_code, network_error_code, report_id,
+ upload_done_data);
+}
+
+void WebRtcLogUploader::SetupMultipart(
+ std::string* post_data,
+ const std::string& compressed_log,
+ const base::FilePath& incoming_rtp_dump,
+ const base::FilePath& outgoing_rtp_dump,
+ const std::map<std::string, std::string>& meta_data) {
+#if defined(OS_WIN)
+ const char product[] = "Chrome";
+#elif defined(OS_MACOSX)
+ const char product[] = "Chrome_Mac";
+#elif defined(OS_LINUX)
+#if !defined(ADDRESS_SANITIZER)
+ const char product[] = "Chrome_Linux";
+#else
+ const char product[] = "Chrome_Linux_ASan";
+#endif
+#elif defined(OS_ANDROID)
+ const char product[] = "Chrome_Android";
+#elif defined(OS_CHROMEOS)
+ const char product[] = "Chrome_ChromeOS";
+#else
+#error Platform not supported.
+#endif
+ net::AddMultipartValueForUpload("prod", product, kWebrtcLogMultipartBoundary,
+ "", post_data);
+ net::AddMultipartValueForUpload("ver",
+ version_info::GetVersionNumber() + "-webrtc",
+ kWebrtcLogMultipartBoundary, "", post_data);
+ net::AddMultipartValueForUpload("guid", "0", kWebrtcLogMultipartBoundary, "",
+ post_data);
+ net::AddMultipartValueForUpload("type", "webrtc_log",
+ kWebrtcLogMultipartBoundary, "", post_data);
+
+ // Add custom meta data.
+ for (const auto& it : meta_data) {
+ net::AddMultipartValueForUpload(it.first, it.second,
+ kWebrtcLogMultipartBoundary, "", post_data);
+ }
+
+ AddLogData(post_data, compressed_log);
+
+ // Add the rtp dumps if they exist.
+ base::FilePath rtp_dumps[2] = {incoming_rtp_dump, outgoing_rtp_dump};
+ static const char* const kRtpDumpNames[2] = {"rtpdump_recv", "rtpdump_send"};
+
+ for (size_t i = 0; i < 2; ++i) {
+ if (!rtp_dumps[i].empty() && base::PathExists(rtp_dumps[i])) {
+ std::string dump_data;
+ if (base::ReadFileToString(rtp_dumps[i], &dump_data))
+ AddRtpDumpData(post_data, kRtpDumpNames[i], dump_data);
+ }
+ }
+
+ net::AddMultipartFinalDelimiterForUpload(kWebrtcLogMultipartBoundary,
+ post_data);
+}
+
+std::string WebRtcLogUploader::CompressLog(WebRtcLogBuffer* buffer) {
+ z_stream stream = {0};
+ int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ // windowBits = 15 is default, 16 is added to
+ // produce a gzip header + trailer.
+ 15 + 16,
+ 8, // memLevel = 8 is default.
+ Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(Z_OK, result);
+
+ std::string compressed_log;
+ ResizeForNextOutput(&compressed_log, &stream);
+
+ uint8_t intermediate_buffer[kIntermediateCompressionBufferBytes] = {0};
+ webrtc_logging::PartialCircularBuffer read_buffer(buffer->Read());
+ do {
+ if (stream.avail_in == 0) {
+ uint32_t read = read_buffer.Read(&intermediate_buffer[0],
+ sizeof(intermediate_buffer));
+ stream.next_in = &intermediate_buffer[0];
+ stream.avail_in = read;
+ if (read != kIntermediateCompressionBufferBytes)
+ break;
+ }
+ result = deflate(&stream, Z_SYNC_FLUSH);
+ DCHECK_EQ(Z_OK, result);
+ if (stream.avail_out == 0)
+ ResizeForNextOutput(&compressed_log, &stream);
+ } while (true);
+
+ // Ensure we have enough room in the output buffer. Easier to always just do a
+ // resize than looping around and resize if needed.
+ if (stream.avail_out < kIntermediateCompressionBufferBytes)
+ ResizeForNextOutput(&compressed_log, &stream);
+
+ result = deflate(&stream, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+ result = deflateEnd(&stream);
+ DCHECK_EQ(Z_OK, result);
+
+ compressed_log.resize(compressed_log.size() - stream.avail_out);
+ return compressed_log;
+}
+
+void WebRtcLogUploader::UploadCompressedLog(
+ const WebRtcLogUploader::UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> post_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+
+ DecreaseLogCount();
+
+ // We don't log upload failure to UMA in case of shutting down for
+ // consistency, since there are other cases during shutdown were we don't get
+ // a chance to log.
+ if (shutdown_)
+ return;
+
+ std::string content_type = kWebrtcLogUploadContentType;
+ content_type.append("; boundary=");
+ content_type.append(kWebrtcLogMultipartBoundary);
+
+ // Create traffic annotation tag.
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("webrtc_log_upload", R"(
+ semantics {
+ sender: "Webrtc Log Uploader"
+ description: "Uploads WebRTC debug logs for Hangouts."
+ trigger:
+ "When a Hangouts extension or Hangouts services extension signals "
+ "to upload via the private WebRTC logging extension API."
+ data:
+ "WebRTC specific log entries, additional system information, and "
+ "RTP packet headers for incoming and outgoing WebRTC streams. "
+ "Audio or video data is never sent."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature can be disabled by unchecking 'Report additional "
+ "diagnostics to help improve Hangouts.' in Hangouts settings."
+ policy_exception_justification:
+ "Not implemented, it would be good to do so."
+ })");
+
+ constexpr char kUploadURL[] = "https://clients2.google.com/cr/report";
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = !upload_url_for_testing_.is_empty()
+ ? upload_url_for_testing_
+ : GURL(kUploadURL);
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+ resource_request->method = "POST";
+ std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
+ network::SimpleURLLoader::Create(std::move(resource_request),
+ traffic_annotation);
+ simple_url_loader->AttachStringForUpload(*post_data, content_type);
+ auto it = pending_uploads_.insert(pending_uploads_.begin(),
+ std::move(simple_url_loader));
+ network::SimpleURLLoader* raw_loader = it->get();
+ raw_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ g_browser_process->shared_url_loader_factory().get(),
+ base::BindOnce(&WebRtcLogUploader::OnSimpleLoaderComplete,
+ base::Unretained(this), std::move(it), upload_done_data));
+}
+
+void WebRtcLogUploader::DecreaseLogCount() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ --log_count_;
+}
+
+void WebRtcLogUploader::WriteCompressedLogToFile(
+ const std::string& compressed_log,
+ const base::FilePath& log_file_path) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!compressed_log.empty());
+ base::WriteFile(log_file_path, &compressed_log[0], compressed_log.size());
+}
+
+void WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!upload_list_path.empty());
+ DCHECK(!local_log_id.empty());
+
+ std::string contents;
+
+ if (base::PathExists(upload_list_path)) {
+ if (!base::ReadFileToString(upload_list_path, &contents)) {
+ DPLOG(WARNING) << "Could not read WebRTC log list file.";
+ return;
+ }
+
+ // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
+ // for the new entry. Each line including the last ends with a '\n', so hit
+ // n will be before line n-1 (from the back).
+ int lf_count = 0;
+ int i = contents.size() - 1;
+ for (; i >= 0 && lf_count < kLogListLimitLines; --i) {
+ if (contents[i] == '\n')
+ ++lf_count;
+ }
+ if (lf_count >= kLogListLimitLines) {
+ // + 1 to compensate for the for loop decrease before the conditional
+ // check and + 1 to get the length.
+ contents.erase(0, i + 2);
+ }
+ }
+
+ // Write the log ID and capture time to the log list file. Leave the upload
+ // time and report ID empty.
+ contents += ",," + local_log_id + "," +
+ base::NumberToString(base::Time::Now().ToDoubleT()) + '\n';
+
+ int written =
+ base::WriteFile(upload_list_path, &contents[0], contents.size());
+ if (written != static_cast<int>(contents.size())) {
+ DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
+ << written;
+ }
+}
+
+// static
+void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id,
+ const std::string& report_id) {
+ DCHECK(!upload_list_path.empty());
+ DCHECK(!local_log_id.empty());
+ DCHECK(!report_id.empty());
+
+ std::string contents;
+
+ if (base::PathExists(upload_list_path)) {
+ if (!base::ReadFileToString(upload_list_path, &contents)) {
+ DPLOG(WARNING) << "Could not read WebRTC log list file.";
+ return;
+ }
+ }
+
+ // Write the Unix time and report ID to the log list file. We should be able
+ // to find the local log ID, in that case insert the data into the existing
+ // line. Otherwise add it in the end.
+ base::Time time_now = base::Time::Now();
+ std::string time_now_str = base::NumberToString(time_now.ToDoubleT());
+ size_t pos = contents.find(",," + local_log_id);
+ if (pos != std::string::npos) {
+ contents.insert(pos, time_now_str);
+ contents.insert(pos + time_now_str.length() + 1, report_id);
+ } else {
+ contents += time_now_str + "," + report_id + ",," + time_now_str + "\n";
+ }
+
+ int written =
+ base::WriteFile(upload_list_path, &contents[0], contents.size());
+ if (written != static_cast<int>(contents.size())) {
+ DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
+ << written;
+ }
+}
+
+void WebRtcLogUploader::NotifyUploadDoneAndLogStats(
+ base::Optional<int> response_code,
+ int network_error_code,
+ const std::string& report_id,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data) {
+ if (upload_done_data.callback.is_null())
+ return;
+
+ const bool success = response_code == net::HTTP_OK;
+ std::string error_message;
+ if (success) {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadSuccessful",
+ upload_done_data.web_app_id);
+ } else {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailed",
+ upload_done_data.web_app_id);
+ if (response_code.has_value()) {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ response_code.value());
+ } else {
+ DCHECK_NE(network_error_code, net::OK);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ WebRtcLogUploadFailureReason::kNetworkError);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureNetErrorCode",
+ std::abs(network_error_code));
+ }
+ error_message = base::StrCat(
+ {"Uploading failed, response code: ",
+ response_code.has_value() ? base::NumberToString(response_code.value())
+ : "<no value>"});
+ }
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(upload_done_data.callback, success, report_id,
+ error_message));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h
new file mode 100644
index 00000000000..ea558ca1a7e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h
@@ -0,0 +1,236 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UPLOADER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UPLOADER_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/browser/media/webrtc/webrtc_log_buffer.h"
+#include "url/gurl.h"
+
+namespace network {
+class SimpleURLLoader;
+}
+
+typedef struct z_stream_s z_stream;
+
+struct WebRtcLogPaths {
+ base::FilePath directory;
+ base::FilePath incoming_rtp_dump;
+ base::FilePath outgoing_rtp_dump;
+};
+
+typedef std::map<std::string, std::string> WebRtcLogMetaDataMap;
+
+// Upload failure reasons used for UMA stats. A failure reason can be one of
+// those listed here or a response code for the upload HTTP request. The
+// values in this list must be less than 100 and cannot be changed.
+struct WebRtcLogUploadFailureReason {
+ enum {
+ kInvalidState = 0,
+ kStoredLogNotFound = 1,
+ kNetworkError = 2,
+ };
+};
+
+// WebRtcLogUploader uploads WebRTC logs, keeps count of how many logs have
+// been started and denies further logs if a limit is reached. It also adds
+// the timestamp and report ID of the uploded log to a text file. There must
+// only be one object of this type.
+class WebRtcLogUploader {
+ public:
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+ typedef base::Callback<void(bool, const std::string&, const std::string&)>
+ UploadDoneCallback;
+
+ // Used when uploading is done to perform post-upload actions. |paths| is
+ // also used pre-upload.
+ struct UploadDoneData {
+ UploadDoneData();
+ UploadDoneData(const UploadDoneData& other);
+ ~UploadDoneData();
+
+ WebRtcLogPaths paths;
+ UploadDoneCallback callback;
+ std::string local_log_id;
+ // Used for statistics. See |WebRtcLoggingHandlerHost::web_app_id_|.
+ int web_app_id;
+ };
+
+ WebRtcLogUploader();
+ ~WebRtcLogUploader();
+
+ // Returns true is number of logs limit is not reached yet. Increases log
+ // count if true is returned. Must be called before UploadLog().
+ bool ApplyForStartLogging();
+
+ // Notifies that logging has stopped and that the log should not be uploaded.
+ // Decreases log count. May only be called if permission to log has been
+ // granted by calling ApplyForStartLogging() and getting true in return.
+ // After this function has been called, a new permission must be granted.
+ // Call either this function or LoggingStoppedDoUpload().
+ void LoggingStoppedDontUpload();
+
+ // Notifies that that logging has stopped and that the log should be uploaded.
+ // Decreases log count. May only be called if permission to log has been
+ // granted by calling ApplyForStartLogging() and getting true in return. After
+ // this function has been called, a new permission must be granted. Call
+ // either this function or LoggingStoppedDontUpload().
+ // |upload_done_data.local_log_id| is set and used internally and should be
+ // left empty.
+ void LoggingStoppedDoUpload(std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const UploadDoneData& upload_done_data);
+
+ // Uploads a previously stored log (see LoggingStoppedDoStore()).
+ void UploadStoredLog(const UploadDoneData& upload_data);
+
+ // Similarly to LoggingStoppedDoUpload(), we store the log in compressed
+ // format on disk but add the option to specify a unique |log_id| for later
+ // identification and potential upload.
+ void LoggingStoppedDoStore(const WebRtcLogPaths& log_paths,
+ const std::string& log_id,
+ std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& done_callback);
+
+ // Cancels URL fetcher operation by deleting all URL fetchers. This cancels
+ // any pending uploads and releases SystemURLRequestContextGetter references.
+ // Sets |shutdown_| which prevents new fetchers from being created.
+ void Shutdown();
+
+ // For testing purposes. If called, the multipart will not be uploaded, but
+ // written to |post_data_| instead.
+ void OverrideUploadWithBufferForTesting(std::string* post_data) {
+ DCHECK((post_data && !post_data_) || (!post_data && post_data_));
+ post_data_ = post_data;
+ }
+
+ // For testing purposes.
+ void SetUploadUrlForTesting(const GURL& url) {
+ DCHECK((!url.is_empty() && upload_url_for_testing_.is_empty()) ||
+ (url.is_empty() && !upload_url_for_testing_.is_empty()));
+ upload_url_for_testing_ = url;
+ }
+
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner()
+ const {
+ return background_task_runner_;
+ }
+
+ private:
+ // Allow the test class to call AddLocallyStoredLogInfoToUploadListFile.
+ friend class WebRtcLogUploaderTest;
+ FRIEND_TEST_ALL_PREFIXES(WebRtcLogUploaderTest,
+ AddLocallyStoredLogInfoToUploadListFile);
+ FRIEND_TEST_ALL_PREFIXES(WebRtcLogUploaderTest,
+ AddUploadedLogInfoToUploadListFile);
+
+ // Sets up a multipart body to be uploaded. The body is produced according
+ // to RFC 2046.
+ void SetupMultipart(std::string* post_data,
+ const std::string& compressed_log,
+ const base::FilePath& incoming_rtp_dump,
+ const base::FilePath& outgoing_rtp_dump,
+ const std::map<std::string, std::string>& meta_data);
+
+ std::string CompressLog(WebRtcLogBuffer* buffer);
+
+ void UploadCompressedLog(const UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> post_data);
+
+ void DecreaseLogCount();
+
+ // Must be called on the FILE thread.
+ void WriteCompressedLogToFile(const std::string& compressed_log,
+ const base::FilePath& log_file_path);
+
+ void PrepareMultipartPostData(const std::string& compressed_log,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const UploadDoneData& upload_done_data);
+
+ // Append information (upload time, report ID and local ID) about a log to a
+ // log list file, limited to |kLogListLimitLines| entries. This list is used
+ // for viewing the logs under chrome://webrtc-logs, see WebRtcLogUploadList.
+ // The list has the format:
+ // [upload_time],[report_id],[local_id],[capture_time]
+ // Each line represents a log.
+ // * |upload_time| is the time when the log was uploaded in Unix time.
+ // * |report_id| is the ID reported back by the server.
+ // * |local_id| is the ID for the locally stored log. It's the time stored
+ // in Unix time and it's also used as file name.
+ // * |capture_time| is the Unix time when the log was captured.
+ // AddLocallyStoredLogInfoToUploadListFile() will first be called.
+ // |upload_time| and |report_id| will be left empty in the entry written to
+ // the list file. If uploading is successful,
+ // AddUploadedLogInfoToUploadListFile() will be called and those empty fields
+ // will be filled out.
+ // Must be called on the FILE thread.
+ void AddLocallyStoredLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id);
+ static void AddUploadedLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id,
+ const std::string& report_id);
+
+ // Notifies users that upload has completed and logs UMA stats.
+ // |response_code| not having a value means that no response code could be
+ // retrieved, in which case |network_error_code| should be something other
+ // than net::OK.
+ void NotifyUploadDoneAndLogStats(base::Optional<int> response_code,
+ int network_error_code,
+ const std::string& report_id,
+ const UploadDoneData& upload_done_data);
+
+ using SimpleURLLoaderList =
+ std::list<std::unique_ptr<network::SimpleURLLoader>>;
+
+ void OnSimpleLoaderComplete(SimpleURLLoaderList::iterator it,
+ const UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> response_body);
+
+ SEQUENCE_CHECKER(main_sequence_checker_);
+
+ // Main sequence where this class was constructed.
+ scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
+
+ // Background sequence where we run background, potentially blocking,
+ // operations.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+ // Keeps track of number of currently open logs. Must only be accessed from
+ // the main sequence.
+ int log_count_ = 0;
+
+ // For testing purposes, see OverrideUploadWithBufferForTesting. Only accessed
+ // on the background sequence
+ std::string* post_data_ = nullptr;
+
+ // For testing purposes.
+ GURL upload_url_for_testing_;
+
+ // Only accessed on the main sequence.
+ SimpleURLLoaderList pending_uploads_;
+
+ // When true, don't create new URL loaders.
+ bool shutdown_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLogUploader);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UPLOADER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc
new file mode 100644
index 00000000000..b00d4b99f1a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc
@@ -0,0 +1,316 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "base/test/task_environment.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const char kTestTime[] = "time";
+const char kTestReportId[] = "report-id";
+const char kTestLocalId[] = "local-id";
+
+class WebRtcLogUploaderTest : public testing::Test {
+ public:
+ WebRtcLogUploaderTest() {}
+
+ bool VerifyNumberOfLines(int expected_lines) {
+ std::vector<std::string> lines = GetLinesFromListFile();
+ EXPECT_EQ(expected_lines, static_cast<int>(lines.size()));
+ return expected_lines == static_cast<int>(lines.size());
+ }
+
+ bool VerifyLastLineHasAllInfo() {
+ std::string last_line = GetLastLineFromListFile();
+ if (last_line.empty())
+ return false;
+ std::vector<std::string> line_parts = base::SplitString(
+ last_line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_EQ(4u, line_parts.size());
+ if (4u != line_parts.size())
+ return false;
+ // The times (indices 0 and 3) is the time when the info was written to the
+ // file which we don't know, so just verify that it's not empty.
+ EXPECT_FALSE(line_parts[0].empty());
+ EXPECT_STREQ(kTestReportId, line_parts[1].c_str());
+ EXPECT_STREQ(kTestLocalId, line_parts[2].c_str());
+ EXPECT_FALSE(line_parts[3].empty());
+ return true;
+ }
+
+ // Verify that the last line contains the correct info for a local storage.
+ bool VerifyLastLineHasLocalStorageInfoOnly() {
+ std::string last_line = GetLastLineFromListFile();
+ if (last_line.empty())
+ return false;
+ std::vector<std::string> line_parts = base::SplitString(
+ last_line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_EQ(4u, line_parts.size());
+ if (4u != line_parts.size())
+ return false;
+ EXPECT_TRUE(line_parts[0].empty());
+ EXPECT_TRUE(line_parts[1].empty());
+ EXPECT_STREQ(kTestLocalId, line_parts[2].c_str());
+ EXPECT_FALSE(line_parts[3].empty());
+ return true;
+ }
+
+ // Verify that the last line contains the correct info for an upload.
+ bool VerifyLastLineHasUploadInfoOnly() {
+ std::string last_line = GetLastLineFromListFile();
+ if (last_line.empty())
+ return false;
+ std::vector<std::string> line_parts = base::SplitString(
+ last_line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_EQ(4u, line_parts.size());
+ if (4u != line_parts.size())
+ return false;
+ EXPECT_FALSE(line_parts[0].empty());
+ EXPECT_STREQ(kTestReportId, line_parts[1].c_str());
+ EXPECT_TRUE(line_parts[2].empty());
+ EXPECT_FALSE(line_parts[3].empty());
+ return true;
+ }
+
+ bool AddLinesToTestFile(int number_of_lines) {
+ base::File test_list_file(test_list_path_,
+ base::File::FLAG_OPEN | base::File::FLAG_APPEND);
+ EXPECT_TRUE(test_list_file.IsValid());
+ if (!test_list_file.IsValid())
+ return false;
+
+ for (int i = 0; i < number_of_lines; ++i) {
+ EXPECT_EQ(static_cast<int>(sizeof(kTestTime)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestTime,
+ sizeof(kTestTime) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos(",", 1));
+ EXPECT_EQ(static_cast<int>(sizeof(kTestReportId)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestReportId,
+ sizeof(kTestReportId) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos(",", 1));
+ EXPECT_EQ(static_cast<int>(sizeof(kTestLocalId)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestLocalId,
+ sizeof(kTestLocalId) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos(",", 1));
+ EXPECT_EQ(static_cast<int>(sizeof(kTestTime)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestTime,
+ sizeof(kTestTime) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos("\n", 1));
+ }
+ return true;
+ }
+
+ std::vector<std::string> GetLinesFromListFile() {
+ std::string contents;
+ int read = base::ReadFileToString(test_list_path_, &contents);
+ EXPECT_GT(read, 0);
+ if (read == 0)
+ return std::vector<std::string>();
+ // Since every line should end with '\n', the last line should be empty. So
+ // we expect at least two lines including the final empty. Remove the empty
+ // line before returning.
+ std::vector<std::string> lines = base::SplitString(
+ contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_GT(lines.size(), 1u);
+ if (lines.size() < 2)
+ return std::vector<std::string>();
+ EXPECT_TRUE(lines.back().empty());
+ if (!lines.back().empty())
+ return std::vector<std::string>();
+ lines.pop_back();
+ return lines;
+ }
+
+ std::string GetLastLineFromListFile() {
+ std::vector<std::string> lines = GetLinesFromListFile();
+ EXPECT_GT(lines.size(), 0u);
+ if (lines.empty())
+ return std::string();
+ return lines[lines.size() - 1];
+ }
+
+ void VerifyRtpDumpInMultipart(const std::string& post_data,
+ const std::string& dump_name,
+ const std::string& dump_content) {
+ std::vector<std::string> lines = base::SplitStringUsingSubstr(
+ post_data, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ std::string name_line = "Content-Disposition: form-data; name=\"";
+ name_line.append(dump_name);
+ name_line.append("\"");
+ name_line.append("; filename=\"");
+ name_line.append(dump_name);
+ name_line.append(".gz\"");
+
+ size_t i = 0;
+ for (; i < lines.size(); ++i) {
+ if (lines[i] == name_line)
+ break;
+ }
+
+ // The RTP dump takes 4 lines: content-disposition, content-type, empty
+ // line, dump content.
+ EXPECT_LT(i, lines.size() - 3);
+
+ EXPECT_EQ("Content-Type: application/gzip", lines[i + 1]);
+ EXPECT_EQ("", lines[i + 2]);
+ EXPECT_EQ(dump_content, lines[i + 3]);
+ }
+
+ static void AddLocallyStoredLogInfoToUploadListFile(
+ WebRtcLogUploader* log_uploader,
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id) {
+ base::RunLoop run_loop;
+ log_uploader->background_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile,
+ base::Unretained(log_uploader), upload_list_path, local_log_id),
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ void FlushRunLoop() {
+ base::RunLoop run_loop;
+ base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ base::FilePath test_list_path_;
+};
+
+TEST_F(WebRtcLogUploaderTest, AddLocallyStoredLogInfoToUploadListFile) {
+ // Get a temporary filename. We don't want the file to exist to begin with
+ // since that's the normal use case, hence the delete.
+ ASSERT_TRUE(base::CreateTemporaryFile(&test_list_path_));
+ EXPECT_TRUE(base::DeleteFile(test_list_path_, false));
+ std::unique_ptr<WebRtcLogUploader> webrtc_log_uploader(
+ new WebRtcLogUploader());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(2));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ const int expected_line_limit = 50;
+ ASSERT_TRUE(AddLinesToTestFile(expected_line_limit - 2));
+ ASSERT_TRUE(VerifyNumberOfLines(expected_line_limit));
+ ASSERT_TRUE(VerifyLastLineHasAllInfo());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(expected_line_limit));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ ASSERT_TRUE(AddLinesToTestFile(10));
+ ASSERT_TRUE(VerifyNumberOfLines(60));
+ ASSERT_TRUE(VerifyLastLineHasAllInfo());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(expected_line_limit));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ webrtc_log_uploader->Shutdown();
+ FlushRunLoop();
+}
+
+TEST_F(WebRtcLogUploaderTest, AddUploadedLogInfoToUploadListFile) {
+ // Get a temporary filename. We don't want the file to exist to begin with
+ // since that's the normal use case, hence the delete.
+ ASSERT_TRUE(base::CreateTemporaryFile(&test_list_path_));
+ EXPECT_TRUE(base::DeleteFile(test_list_path_, false));
+ std::unique_ptr<WebRtcLogUploader> webrtc_log_uploader(
+ new WebRtcLogUploader());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(1));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ webrtc_log_uploader->AddUploadedLogInfoToUploadListFile(
+ test_list_path_, kTestLocalId, kTestReportId);
+ ASSERT_TRUE(VerifyNumberOfLines(1));
+ ASSERT_TRUE(VerifyLastLineHasAllInfo());
+
+ // Use a local ID that should not be found in the list.
+ webrtc_log_uploader->AddUploadedLogInfoToUploadListFile(
+ test_list_path_, "dummy id", kTestReportId);
+ ASSERT_TRUE(VerifyNumberOfLines(2));
+ ASSERT_TRUE(VerifyLastLineHasUploadInfoOnly());
+
+ webrtc_log_uploader->Shutdown();
+ FlushRunLoop();
+}
+
+TEST_F(WebRtcLogUploaderTest, AddRtpDumpsToPostedData) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ std::unique_ptr<WebRtcLogUploader> webrtc_log_uploader(
+ new WebRtcLogUploader());
+
+ std::string post_data;
+ webrtc_log_uploader->OverrideUploadWithBufferForTesting(&post_data);
+
+ // Create the fake dump files.
+ const base::FilePath incoming_dump = temp_dir.GetPath().AppendASCII("recv");
+ const base::FilePath outgoing_dump = temp_dir.GetPath().AppendASCII("send");
+ const std::string incoming_dump_content = "dummy incoming";
+ const std::string outgoing_dump_content = "dummy outgoing";
+
+ base::WriteFile(incoming_dump,
+ &incoming_dump_content[0],
+ incoming_dump_content.size());
+ base::WriteFile(outgoing_dump,
+ &outgoing_dump_content[0],
+ outgoing_dump_content.size());
+
+ WebRtcLogUploader::UploadDoneData upload_done_data;
+ upload_done_data.paths.directory = temp_dir.GetPath().AppendASCII("log");
+
+ upload_done_data.paths.incoming_rtp_dump = incoming_dump;
+ upload_done_data.paths.outgoing_rtp_dump = outgoing_dump;
+
+ std::unique_ptr<WebRtcLogBuffer> log(new WebRtcLogBuffer());
+ log->SetComplete();
+
+ base::RunLoop run_loop;
+ webrtc_log_uploader->background_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLogUploader::LoggingStoppedDoUpload,
+ base::Unretained(webrtc_log_uploader.get()),
+ std::move(log), std::make_unique<WebRtcLogMetaDataMap>(),
+ upload_done_data),
+ run_loop.QuitClosure());
+ run_loop.Run();
+
+ VerifyRtpDumpInMultipart(post_data, "rtpdump_recv", incoming_dump_content);
+ VerifyRtpDumpInMultipart(post_data, "rtpdump_send", outgoing_dump_content);
+
+ webrtc_log_uploader->Shutdown();
+ FlushRunLoop();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_util.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_util.cc
new file mode 100644
index 00000000000..f724e04d6e1
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_util.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_log_util.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/webrtc_logging/browser/log_cleanup.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "content/public/browser/browser_thread.h"
+
+// static
+void WebRtcLogUtil::DeleteOldWebRtcLogFilesForAllProfiles() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::vector<ProfileAttributesEntry*> entries =
+ g_browser_process->profile_manager()->GetProfileAttributesStorage().
+ GetAllProfilesAttributes();
+ for (ProfileAttributesEntry* entry : entries) {
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(
+ &webrtc_logging::DeleteOldWebRtcLogFiles,
+ webrtc_logging::TextLogList::
+ GetWebRtcLogDirectoryForBrowserContextPath(entry->GetPath())));
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_util.h b/chromium/chrome/browser/media/webrtc/webrtc_log_util.h
new file mode 100644
index 00000000000..8f06b483fbf
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_util.h
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UTIL_H_
+
+class WebRtcLogUtil {
+ public:
+ // Calls webrtc_logging::DeleteOldWebRtcLogFiles() for all profiles. Must be
+ // called on the UI thread.
+ static void DeleteOldWebRtcLogFilesForAllProfiles();
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UTIL_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc
new file mode 100644
index 00000000000..824464ffdca
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc
@@ -0,0 +1,589 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_logging_controller.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/supports_user_data.h"
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_process_host.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#include "content/public/browser/child_process_security_policy.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+using webrtc_event_logging::WebRtcEventLogManager;
+
+namespace {
+
+// Key used to attach the handler to the RenderProcessHost.
+constexpr char kRenderProcessHostKey[] = "kWebRtcLoggingControllerKey";
+
+} // namespace
+
+// static
+void WebRtcLoggingController::AttachToRenderProcessHost(
+ content::RenderProcessHost* host,
+ WebRtcLogUploader* log_uploader) {
+ host->SetUserData(
+ kRenderProcessHostKey,
+ std::make_unique<base::UserDataAdapter<WebRtcLoggingController>>(
+ new WebRtcLoggingController(host->GetID(), host->GetBrowserContext(),
+ log_uploader)));
+}
+
+// static
+WebRtcLoggingController* WebRtcLoggingController::FromRenderProcessHost(
+ content::RenderProcessHost* host) {
+ return base::UserDataAdapter<WebRtcLoggingController>::Get(
+ host, kRenderProcessHostKey);
+}
+
+void WebRtcLoggingController::SetMetaData(
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // Set the web app ID if there's a "client" key, otherwise leave it unchanged.
+ for (const auto& it : *meta_data) {
+ if (it.first == "client") {
+ web_app_id_ = static_cast<int>(base::PersistentHash(it.second));
+ text_log_handler_->SetWebAppId(web_app_id_);
+ break;
+ }
+ }
+
+ text_log_handler_->SetMetaData(std::move(meta_data), callback);
+}
+
+void WebRtcLoggingController::StartLogging(
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // Request a log_slot from the LogUploader and start logging.
+ if (text_log_handler_->StartLogging(log_uploader_, callback)) {
+ // Start logging in the renderer. The callback has already been fired since
+ // there is no acknowledgement when the renderer actually starts.
+ content::RenderProcessHost* host =
+ content::RenderProcessHost::FromID(render_process_id_);
+
+ // OK for this to replace an existing logging_agent_ connection.
+ host->BindReceiver(logging_agent_.BindNewPipeAndPassReceiver());
+ logging_agent_.set_disconnect_handler(
+ base::BindOnce(&WebRtcLoggingController::OnAgentDisconnected, this));
+ logging_agent_->Start(receiver_.BindNewPipeAndPassRemote());
+ }
+}
+
+void WebRtcLoggingController::StopLogging(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // Change the state to STOPPING and disable logging in the browser.
+ if (text_log_handler_->StopLogging(callback)) {
+ // Stop logging in the renderer. OnStopped will be called when this is done
+ // to change the state from STOPPING to STOPPED and fire the callback.
+ logging_agent_->Stop();
+ }
+}
+
+void WebRtcLoggingController::UploadLog(const UploadDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // This functions uploads both text logs (mandatory) and RTP dumps (optional).
+ // TODO(terelius): If there's no text log available (either because it hasn't
+ // been started or because it hasn't been stopped), the current implementation
+ // will fire an error callback and leave any RTP dumps in a local directory.
+ // Would it be better to upload whatever logs we have, or would the lack of
+ // an error callback make it harder to debug potential errors?
+
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadStarted", web_app_id_);
+
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::TriggerUpload, this, callback));
+}
+
+void WebRtcLoggingController::UploadStoredLog(
+ const std::string& log_id,
+ const UploadDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadStoredStarted",
+ web_app_id_);
+
+ // Make this a method call on log_uploader_
+
+ WebRtcLogUploader::UploadDoneData upload_data;
+ upload_data.callback = callback;
+ upload_data.local_log_id = log_id;
+ upload_data.web_app_id = web_app_id_;
+
+ log_uploader_->background_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(
+ [](WebRtcLogUploader* log_uploader,
+ WebRtcLogUploader::UploadDoneData upload_data,
+ base::RepeatingCallback<base::FilePath(void)>
+ log_directory_getter) {
+ upload_data.paths.directory = log_directory_getter.Run();
+ log_uploader->UploadStoredLog(upload_data);
+ },
+ log_uploader_, upload_data, log_directory_getter_));
+}
+
+void WebRtcLoggingController::DiscardLog(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (!text_log_handler_->ExpectLoggingStateStopped(callback)) {
+ // The callback is fired with an error message by ExpectLoggingStateStopped.
+ return;
+ }
+ log_uploader_->LoggingStoppedDontUpload();
+ text_log_handler_->DiscardLog();
+ rtp_dump_handler_.reset();
+ stop_rtp_dump_callback_.Reset();
+ FireGenericDoneCallback(callback, true, "");
+}
+
+// Stores the log locally using a hash of log_id + security origin.
+void WebRtcLoggingController::StoreLog(const std::string& log_id,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (!text_log_handler_->ExpectLoggingStateStopped(callback)) {
+ // The callback is fired with an error message by ExpectLoggingStateStopped.
+ return;
+ }
+
+ if (rtp_dump_handler_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(stop_rtp_dump_callback_, true, true));
+
+ rtp_dump_handler_->StopOngoingDumps(base::Bind(
+ &WebRtcLoggingController::StoreLogContinue, this, log_id, callback));
+ return;
+ }
+
+ StoreLogContinue(log_id, callback);
+}
+
+void WebRtcLoggingController::StoreLogContinue(
+ const std::string& log_id,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ std::unique_ptr<WebRtcLogPaths> log_paths(new WebRtcLogPaths());
+ ReleaseRtpDumps(log_paths.get());
+
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::StoreLogInDirectory, this,
+ log_id, base::Passed(&log_paths), callback));
+}
+
+void WebRtcLoggingController::StartRtpDump(
+ RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(stop_rtp_dump_callback_.is_null());
+
+ content::RenderProcessHost* host =
+ content::RenderProcessHost::FromID(render_process_id_);
+
+ // This call cannot fail.
+ stop_rtp_dump_callback_ = host->StartRtpDump(
+ type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH,
+ type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH,
+ base::Bind(&WebRtcLoggingController::OnRtpPacket, this));
+
+ if (!rtp_dump_handler_) {
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::CreateRtpDumpHandlerAndStart,
+ this, type, callback));
+ return;
+ }
+
+ DoStartRtpDump(type, callback);
+}
+
+void WebRtcLoggingController::StopRtpDump(RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (!rtp_dump_handler_) {
+ FireGenericDoneCallback(callback, false, "RTP dump has not been started.");
+ return;
+ }
+
+ if (!stop_rtp_dump_callback_.is_null()) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(stop_rtp_dump_callback_,
+ type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH,
+ type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH));
+ }
+
+ rtp_dump_handler_->StopDump(type, callback);
+}
+
+void WebRtcLoggingController::StartEventLogging(
+ const std::string& session_id,
+ size_t max_log_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ const StartEventLoggingCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ WebRtcEventLogManager::GetInstance()->StartRemoteLogging(
+ render_process_id_, session_id, max_log_size_bytes, output_period_ms,
+ web_app_id, callback);
+}
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+void WebRtcLoggingController::GetLogsDirectory(
+ const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::GrantLogsDirectoryAccess, this,
+ callback, error_callback));
+}
+
+void WebRtcLoggingController::GrantLogsDirectoryAccess(
+ const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback,
+ const base::FilePath& logs_path) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logs_path.empty()) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(error_callback, "Logs directory not available"));
+ return;
+ }
+
+ storage::IsolatedContext* isolated_context =
+ storage::IsolatedContext::GetInstance();
+ DCHECK(isolated_context);
+
+ std::string registered_name;
+ storage::IsolatedContext::ScopedFSHandle file_system =
+ isolated_context->RegisterFileSystemForPath(
+ storage::kFileSystemTypeNativeLocal, std::string(), logs_path,
+ &registered_name);
+
+ // Only granting read and delete access to reduce contention with
+ // webrtcLogging APIs that modify files in that folder.
+ content::ChildProcessSecurityPolicy* policy =
+ content::ChildProcessSecurityPolicy::GetInstance();
+ policy->GrantReadFileSystem(render_process_id_, file_system.id());
+ // Delete is needed to prevent accumulation of files.
+ policy->GrantDeleteFromFileSystem(render_process_id_, file_system.id());
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, file_system.id(), registered_name));
+}
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+void WebRtcLoggingController::OnRtpPacket(
+ std::unique_ptr<uint8_t[]> packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // |rtp_dump_handler_| could be null if we are waiting for the FILE thread to
+ // create/ensure the log directory.
+ if (rtp_dump_handler_) {
+ rtp_dump_handler_->OnRtpPacket(packet_header.get(), header_length,
+ packet_length, incoming);
+ }
+}
+
+void WebRtcLoggingController::OnAddMessages(
+ std::vector<chrome::mojom::WebRtcLoggingMessagePtr> messages) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (text_log_handler_->GetState() == WebRtcTextLogHandler::STARTED ||
+ text_log_handler_->GetState() == WebRtcTextLogHandler::STOPPING) {
+ for (auto& message : messages)
+ text_log_handler_->LogWebRtcLoggingMessage(message.get());
+ }
+}
+
+void WebRtcLoggingController::OnStopped() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (text_log_handler_->GetState() != WebRtcTextLogHandler::STOPPING) {
+ // If an out-of-order response is received, stop_callback_ may be invalid,
+ // and must not be invoked.
+ DLOG(ERROR) << "OnStopped invoked in state "
+ << text_log_handler_->GetState();
+ mojo::ReportBadMessage("WRLHH: OnStopped invoked in unexpected state.");
+ return;
+ }
+ text_log_handler_->StopDone();
+}
+
+WebRtcLoggingController::WebRtcLoggingController(
+ int render_process_id,
+ content::BrowserContext* browser_context,
+ WebRtcLogUploader* log_uploader)
+ : receiver_(this),
+ render_process_id_(render_process_id),
+ log_directory_getter_(base::BindRepeating(
+ &WebRtcLoggingController::GetLogDirectoryAndEnsureExists,
+ browser_context->GetPath())),
+ upload_log_on_render_close_(false),
+ text_log_handler_(
+ std::make_unique<WebRtcTextLogHandler>(render_process_id)),
+ rtp_dump_handler_(),
+ stop_rtp_dump_callback_(),
+ log_uploader_(log_uploader) {
+ DCHECK(log_uploader_);
+}
+
+WebRtcLoggingController::~WebRtcLoggingController() {
+ // If we hit this, then we might be leaking a log reference count (see
+ // ApplyForStartLogging).
+ DCHECK_EQ(WebRtcTextLogHandler::CLOSED, text_log_handler_->GetState());
+}
+
+void WebRtcLoggingController::OnAgentDisconnected() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (text_log_handler_->GetChannelIsClosing())
+ return;
+
+ switch (text_log_handler_->GetState()) {
+ case WebRtcTextLogHandler::STARTING:
+ case WebRtcTextLogHandler::STARTED:
+ case WebRtcTextLogHandler::STOPPING:
+ case WebRtcTextLogHandler::STOPPED:
+ text_log_handler_->ChannelClosing();
+ if (upload_log_on_render_close_) {
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::TriggerUpload, this,
+ UploadDoneCallback()));
+ } else {
+ log_uploader_->LoggingStoppedDontUpload();
+ text_log_handler_->DiscardLog();
+ }
+ break;
+ case WebRtcTextLogHandler::CLOSED:
+ // Do nothing
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void WebRtcLoggingController::TriggerUpload(
+ const UploadDoneCallback& callback,
+ const base::FilePath& log_directory) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (rtp_dump_handler_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(stop_rtp_dump_callback_, true, true));
+
+ rtp_dump_handler_->StopOngoingDumps(
+ base::Bind(&WebRtcLoggingController::DoUploadLogAndRtpDumps, this,
+ log_directory, callback));
+ return;
+ }
+
+ DoUploadLogAndRtpDumps(log_directory, callback);
+}
+
+void WebRtcLoggingController::StoreLogInDirectory(
+ const std::string& log_id,
+ std::unique_ptr<WebRtcLogPaths> log_paths,
+ const GenericDoneCallback& done_callback,
+ const base::FilePath& directory) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If channel is not closing, storing is only allowed when in STOPPED state.
+ // If channel is closing, storing is allowed for all states except CLOSED.
+ const WebRtcTextLogHandler::LoggingState text_logging_state =
+ text_log_handler_->GetState();
+ const bool channel_is_closing = text_log_handler_->GetChannelIsClosing();
+ if ((!channel_is_closing &&
+ text_logging_state != WebRtcTextLogHandler::STOPPED) ||
+ (channel_is_closing &&
+ text_log_handler_->GetState() == WebRtcTextLogHandler::CLOSED)) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(done_callback, false,
+ "Logging not stopped or no log open."));
+ return;
+ }
+
+ log_paths->directory = directory;
+
+ std::unique_ptr<WebRtcLogBuffer> log_buffer;
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data;
+ text_log_handler_->ReleaseLog(&log_buffer, &meta_data);
+ CHECK(log_buffer.get()) << "State=" << text_log_handler_->GetState()
+ << ", uorc=" << upload_log_on_render_close_;
+
+ log_uploader_->background_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcLogUploader::LoggingStoppedDoStore,
+ base::Unretained(log_uploader_), *log_paths,
+ log_id, std::move(log_buffer),
+ std::move(meta_data), done_callback));
+}
+
+void WebRtcLoggingController::DoUploadLogAndRtpDumps(
+ const base::FilePath& log_directory,
+ const UploadDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If channel is not closing, upload is only allowed when in STOPPED state.
+ // If channel is closing, uploading is allowed for all states except CLOSED.
+ const WebRtcTextLogHandler::LoggingState text_logging_state =
+ text_log_handler_->GetState();
+ const bool channel_is_closing = text_log_handler_->GetChannelIsClosing();
+ if ((!channel_is_closing &&
+ text_logging_state != WebRtcTextLogHandler::STOPPED) ||
+ (channel_is_closing &&
+ text_log_handler_->GetState() == WebRtcTextLogHandler::CLOSED)) {
+ // If the channel is not closing the log is expected to be uploaded, so
+ // it's considered a failure if it isn't.
+ // If the channel is closing we don't log failure to UMA for consistency,
+ // since there are other cases during shutdown were we don't get a chance
+ // to log.
+ if (!channel_is_closing) {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailed", web_app_id_);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ WebRtcLogUploadFailureReason::kInvalidState);
+ }
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, false, "",
+ "Logging not stopped or no log open."));
+ return;
+ }
+
+ WebRtcLogUploader::UploadDoneData upload_done_data;
+ upload_done_data.paths.directory = log_directory;
+ upload_done_data.callback = callback;
+ upload_done_data.web_app_id = web_app_id_;
+ ReleaseRtpDumps(&upload_done_data.paths);
+
+ std::unique_ptr<WebRtcLogBuffer> log_buffer;
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data;
+ text_log_handler_->ReleaseLog(&log_buffer, &meta_data);
+ CHECK(log_buffer.get()) << "State=" << text_log_handler_->GetState()
+ << ", uorc=" << upload_log_on_render_close_;
+
+ log_uploader_->background_task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLogUploader::LoggingStoppedDoUpload,
+ base::Unretained(log_uploader_), std::move(log_buffer),
+ std::move(meta_data), upload_done_data));
+}
+
+void WebRtcLoggingController::CreateRtpDumpHandlerAndStart(
+ RtpDumpType type,
+ const GenericDoneCallback& callback,
+ const base::FilePath& dump_dir) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // |rtp_dump_handler_| may be non-null if StartRtpDump is called again before
+ // GetLogDirectoryAndEnsureExists returns on the FILE thread for a previous
+ // StartRtpDump.
+ if (!rtp_dump_handler_)
+ rtp_dump_handler_.reset(new WebRtcRtpDumpHandler(dump_dir));
+
+ DoStartRtpDump(type, callback);
+}
+
+void WebRtcLoggingController::DoStartRtpDump(
+ RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(rtp_dump_handler_);
+
+ std::string error;
+ bool result = rtp_dump_handler_->StartDump(type, &error);
+ FireGenericDoneCallback(callback, result, error);
+}
+
+bool WebRtcLoggingController::ReleaseRtpDumps(WebRtcLogPaths* log_paths) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(log_paths);
+
+ if (!rtp_dump_handler_)
+ return false;
+
+ WebRtcRtpDumpHandler::ReleasedDumps rtp_dumps(
+ rtp_dump_handler_->ReleaseDumps());
+ log_paths->incoming_rtp_dump = rtp_dumps.incoming_dump_path;
+ log_paths->outgoing_rtp_dump = rtp_dumps.outgoing_dump_path;
+
+ rtp_dump_handler_.reset();
+ stop_rtp_dump_callback_.Reset();
+
+ return true;
+}
+
+void WebRtcLoggingController::FireGenericDoneCallback(
+ const GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+ DCHECK_EQ(success, error_message.empty());
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message));
+}
+
+// static
+base::FilePath WebRtcLoggingController::GetLogDirectoryAndEnsureExists(
+ const base::FilePath& browser_context_directory_path) {
+ DCHECK(!browser_context_directory_path.empty());
+ // Since we can be alive after the RenderProcessHost and the BrowserContext
+ // (profile) have gone away, we could create the log directory here after a
+ // profile has been deleted and removed from disk. If that happens it will be
+ // cleaned up (at a higher level) the next browser restart.
+ base::FilePath log_dir_path =
+ webrtc_logging::TextLogList::GetWebRtcLogDirectoryForBrowserContextPath(
+ browser_context_directory_path);
+ base::File::Error error;
+ if (!base::CreateDirectoryAndGetError(log_dir_path, &error)) {
+ DLOG(ERROR) << "Could not create WebRTC log directory, error: " << error;
+ return base::FilePath();
+ }
+ return log_dir_path;
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h
new file mode 100644
index 00000000000..ee1eca46fba
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h
@@ -0,0 +1,242 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOGGING_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOGGING_CONTROLLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/rtp_dump_type.h"
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+#include "chrome/browser/media/webrtc/webrtc_text_log_handler.h"
+#include "chrome/common/media/webrtc_logging.mojom.h"
+#include "content/public/browser/render_process_host.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class WebRtcLogUploader;
+class WebRtcRtpDumpHandler;
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+// WebRtcLoggingController handles operations regarding the WebRTC logging:
+// - Opens a connection to a WebRtcLoggingAgent that runs in the render process
+// and generates log messages.
+// - Writes basic machine info to the log.
+// - Informs the handler in the render process when to stop logging.
+// - Closes the connection to the WebRtcLoggingAgent (and thereby discarding it)
+// or triggers uploading of the log.
+// - Detects when the agent (e.g., because of a tab closure or crash) is going
+// away and possibly triggers uploading the log.
+class WebRtcLoggingController
+ : public base::RefCounted<WebRtcLoggingController>,
+ public chrome::mojom::WebRtcLoggingClient {
+ public:
+ typedef WebRtcLogUploader::GenericDoneCallback GenericDoneCallback;
+ typedef WebRtcLogUploader::UploadDoneCallback UploadDoneCallback;
+ typedef base::Callback<void(const std::string&, const std::string&)>
+ LogsDirectoryCallback;
+ typedef base::Callback<void(const std::string&)> LogsDirectoryErrorCallback;
+
+ // Argument #1: Indicate success/failure.
+ // Argument #2: If success, the log's ID. Otherwise, empty.
+ // Argument #3: If failure, the error message. Otherwise, empty.
+ typedef base::RepeatingCallback<
+ void(bool, const std::string&, const std::string&)>
+ StartEventLoggingCallback;
+
+ static void AttachToRenderProcessHost(content::RenderProcessHost* host,
+ WebRtcLogUploader* log_uploader);
+ static WebRtcLoggingController* FromRenderProcessHost(
+ content::RenderProcessHost* host);
+
+ // Sets meta data that will be uploaded along with the log and also written
+ // in the beginning of the log. Must be called on the IO thread before calling
+ // StartLogging.
+ void SetMetaData(std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback);
+
+ // Opens a log and starts logging. Must be called on the IO thread.
+ void StartLogging(const GenericDoneCallback& callback);
+
+ // Stops logging. Log will remain open until UploadLog or DiscardLog is
+ // called. Must be called on the IO thread.
+ void StopLogging(const GenericDoneCallback& callback);
+
+ // Uploads the text log and the RTP dumps. Discards the local copy. May only
+ // be called after text logging has stopped. Must be called on the IO thread.
+ void UploadLog(const UploadDoneCallback& callback);
+
+ // Uploads a log that was previously saved via a call to StoreLog().
+ // Otherwise operates in the same way as UploadLog.
+ void UploadStoredLog(const std::string& log_id,
+ const UploadDoneCallback& callback);
+
+ // Discards the log and the RTP dumps. May only be called after logging has
+ // stopped. Must be called on the IO thread.
+ void DiscardLog(const GenericDoneCallback& callback);
+
+ // Stores the log locally using a hash of log_id + security origin.
+ void StoreLog(const std::string& log_id, const GenericDoneCallback& callback);
+
+ // May be called on any thread. |upload_log_on_render_close_| is used
+ // for decision making and it's OK if it changes before the execution based
+ // on that decision has finished.
+ void set_upload_log_on_render_close(bool should_upload) {
+ upload_log_on_render_close_ = should_upload;
+ }
+
+ // Starts dumping the RTP headers for the specified direction. Must be called
+ // on the UI thread. |type| specifies which direction(s) of RTP packets should
+ // be dumped. |callback| will be called when starting the dump is done.
+ void StartRtpDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ // Stops dumping the RTP headers for the specified direction. Must be called
+ // on the UI thread. |type| specifies which direction(s) of RTP packet dumping
+ // should be stopped. |callback| will be called when stopping the dump is
+ // done.
+ void StopRtpDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ // Called when an RTP packet is sent or received. Must be called on the UI
+ // thread.
+ void OnRtpPacket(std::unique_ptr<uint8_t[]> packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming);
+
+ // Start remote-bound event logging for a specific peer connection
+ // (indicated by its session description's ID).
+ // The callback will be posted back, indicating |true| if and only if an
+ // event log was successfully started, in which case the first of the string
+ // arguments will be set to the log-ID. Otherwise, the second of the string
+ // arguments will contain the error message.
+ // This function must be called on the UI thread.
+ void StartEventLogging(const std::string& session_id,
+ size_t max_log_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ const StartEventLoggingCallback& callback);
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ // Ensures that the WebRTC Logs directory exists and then grants render
+ // process access to the 'WebRTC Logs' directory, and invokes |callback| with
+ // the ids necessary to create a DirectoryEntry object.
+ void GetLogsDirectory(const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback);
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+ // chrome::mojom::WebRtcLoggingClient methods:
+ void OnAddMessages(
+ std::vector<chrome::mojom::WebRtcLoggingMessagePtr> messages) override;
+ void OnStopped() override;
+
+ private:
+ friend class base::RefCounted<WebRtcLoggingController>;
+
+ WebRtcLoggingController(int render_process_id,
+ content::BrowserContext* browser_context,
+ WebRtcLogUploader* log_uploader);
+ ~WebRtcLoggingController() override;
+
+ void OnAgentDisconnected();
+
+ // Called after stopping RTP dumps.
+ void StoreLogContinue(const std::string& log_id,
+ const GenericDoneCallback& callback);
+
+ // Writes a formatted log |message| to the |circular_buffer_|.
+ void LogToCircularBuffer(const std::string& message);
+
+ void TriggerUpload(const UploadDoneCallback& callback,
+ const base::FilePath& log_directory);
+
+ void StoreLogInDirectory(const std::string& log_id,
+ std::unique_ptr<WebRtcLogPaths> log_paths,
+ const GenericDoneCallback& done_callback,
+ const base::FilePath& directory);
+
+ // A helper for TriggerUpload to do the real work.
+ void DoUploadLogAndRtpDumps(const base::FilePath& log_directory,
+ const UploadDoneCallback& callback);
+
+ // Create the RTP dump handler and start dumping. Must be called after making
+ // sure the log directory exists.
+ void CreateRtpDumpHandlerAndStart(RtpDumpType type,
+ const GenericDoneCallback& callback,
+ const base::FilePath& dump_dir);
+
+ // A helper for starting RTP dump assuming the RTP dump handler has been
+ // created.
+ void DoStartRtpDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ bool ReleaseRtpDumps(WebRtcLogPaths* log_paths);
+
+ void FireGenericDoneCallback(
+ const WebRtcLoggingController::GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message);
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ // Grants the render process access to the 'WebRTC Logs' directory, and
+ // invokes |callback| with the ids necessary to create a DirectoryEntry
+ // object. If the |logs_path| couldn't be created or found, |error_callback|
+ // is run.
+ void GrantLogsDirectoryAccess(
+ const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback,
+ const base::FilePath& logs_path);
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+ static base::FilePath GetLogDirectoryAndEnsureExists(
+ const base::FilePath& browser_context_directory_path);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ mojo::Receiver<chrome::mojom::WebRtcLoggingClient> receiver_;
+ mojo::Remote<chrome::mojom::WebRtcLoggingAgent> logging_agent_;
+
+ // The render process ID this object belongs to.
+ const int render_process_id_;
+
+ // A callback that needs to be run from a blocking worker pool and returns
+ // the browser context directory path associated with our renderer process.
+ base::RepeatingCallback<base::FilePath(void)> log_directory_getter_;
+
+ // True if we should upload whatever log we have when the renderer closes.
+ bool upload_log_on_render_close_;
+
+ // The text log handler owns the WebRtcLogBuffer object and keeps track of
+ // the logging state.
+ std::unique_ptr<WebRtcTextLogHandler> text_log_handler_;
+
+ // The RTP dump handler responsible for creating the RTP header dump files.
+ std::unique_ptr<WebRtcRtpDumpHandler> rtp_dump_handler_;
+
+ // The callback to call when StopRtpDump is called.
+ content::RenderProcessHost::WebRtcStopRtpDumpCallback stop_rtp_dump_callback_;
+
+ // A pointer to the log uploader that's shared for all browser contexts.
+ // Ownership lies with the browser process.
+ WebRtcLogUploader* const log_uploader_;
+
+ // Web app id used for statistics. Created as the hash of the value of a
+ // "client" meta data key, if exists. 0 means undefined, and is the hash of
+ // the empty string.
+ int web_app_id_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLoggingController);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOGGING_CONTROLLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc
new file mode 100644
index 00000000000..f502a329ef1
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc
@@ -0,0 +1,336 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+
+namespace {
+
+static const size_t kMaxOngoingRtpDumpsAllowed = 5;
+
+// The browser process wide total number of ongoing (i.e. started and not
+// released) RTP dumps. Incoming and outgoing in one WebRtcDumpHandler are
+// counted as one dump.
+// Must be accessed on the browser IO thread.
+static size_t g_ongoing_rtp_dumps = 0;
+
+void FireGenericDoneCallback(
+ const WebRtcRtpDumpHandler::GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message) {
+ DCHECK(!callback.is_null());
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message));
+}
+
+bool DumpTypeContainsIncoming(RtpDumpType type) {
+ return type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH;
+}
+
+bool DumpTypeContainsOutgoing(RtpDumpType type) {
+ return type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH;
+}
+
+} // namespace
+
+WebRtcRtpDumpHandler::WebRtcRtpDumpHandler(const base::FilePath& dump_dir)
+ : dump_dir_(dump_dir),
+ incoming_state_(STATE_NONE),
+ outgoing_state_(STATE_NONE) {}
+
+WebRtcRtpDumpHandler::~WebRtcRtpDumpHandler() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ // Reset dump writer first to stop writing.
+ if (dump_writer_) {
+ --g_ongoing_rtp_dumps;
+ dump_writer_.reset();
+ }
+
+ if (incoming_state_ != STATE_NONE && !incoming_dump_path_.empty()) {
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ incoming_dump_path_, false));
+ }
+
+ if (outgoing_state_ != STATE_NONE && !outgoing_dump_path_.empty()) {
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ outgoing_dump_path_, false));
+ }
+}
+
+bool WebRtcRtpDumpHandler::StartDump(RtpDumpType type,
+ std::string* error_message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ if (!dump_writer_ && g_ongoing_rtp_dumps >= kMaxOngoingRtpDumpsAllowed) {
+ *error_message = "Max RTP dump limit reached.";
+ DVLOG(2) << *error_message;
+ return false;
+ }
+
+ // Returns an error if any type of dump specified by the caller cannot be
+ // started.
+ if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_NONE) ||
+ (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_NONE)) {
+ *error_message =
+ "RTP dump already started for type " + base::NumberToString(type);
+ return false;
+ }
+
+ if (DumpTypeContainsIncoming(type))
+ incoming_state_ = STATE_STARTED;
+
+ if (DumpTypeContainsOutgoing(type))
+ outgoing_state_ = STATE_STARTED;
+
+ DVLOG(2) << "Start RTP dumping: type = " << type;
+
+ if (!dump_writer_) {
+ ++g_ongoing_rtp_dumps;
+
+ static const char kRecvDumpFilePrefix[] = "rtpdump_recv_";
+ static const char kSendDumpFilePrefix[] = "rtpdump_send_";
+ static const size_t kMaxDumpSize = 5 * 1024 * 1024; // 5MB
+
+ std::string dump_id = base::NumberToString(base::Time::Now().ToDoubleT());
+ incoming_dump_path_ =
+ dump_dir_.AppendASCII(std::string(kRecvDumpFilePrefix) + dump_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+
+ outgoing_dump_path_ =
+ dump_dir_.AppendASCII(std::string(kSendDumpFilePrefix) + dump_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+
+ // WebRtcRtpDumpWriter does not support changing the dump path after it's
+ // created. So we assign both incoming and outgoing dump path even if only
+ // one type of dumping has been started.
+ // For "Unretained(this)", see comments StopDump.
+ dump_writer_.reset(new WebRtcRtpDumpWriter(
+ incoming_dump_path_,
+ outgoing_dump_path_,
+ kMaxDumpSize,
+ base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached,
+ base::Unretained(this))));
+ }
+
+ return true;
+}
+
+void WebRtcRtpDumpHandler::StopDump(RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ // Returns an error if any type of dump specified by the caller cannot be
+ // stopped.
+ if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_STARTED) ||
+ (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_STARTED)) {
+ if (!callback.is_null()) {
+ FireGenericDoneCallback(
+ callback, false,
+ "RTP dump not started or already stopped for type " +
+ base::NumberToString(type));
+ }
+ return;
+ }
+
+ DVLOG(2) << "Stopping RTP dumping: type = " << type;
+
+ if (DumpTypeContainsIncoming(type))
+ incoming_state_ = STATE_STOPPING;
+
+ if (DumpTypeContainsOutgoing(type))
+ outgoing_state_ = STATE_STOPPING;
+
+ // Using "Unretained(this)" because the this object owns the writer and the
+ // writer is guaranteed to cancel the callback before it goes away. Same for
+ // the other posted tasks bound to the writer.
+ dump_writer_->EndDump(
+ type,
+ base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded,
+ base::Unretained(this),
+ callback.is_null()
+ ? base::Closure()
+ : base::Bind(&FireGenericDoneCallback, callback, true, ""),
+ type));
+}
+
+bool WebRtcRtpDumpHandler::ReadyToRelease() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ return incoming_state_ != STATE_STARTED &&
+ incoming_state_ != STATE_STOPPING &&
+ outgoing_state_ != STATE_STARTED && outgoing_state_ != STATE_STOPPING;
+}
+
+WebRtcRtpDumpHandler::ReleasedDumps WebRtcRtpDumpHandler::ReleaseDumps() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+ DCHECK(ReadyToRelease());
+
+ base::FilePath incoming_dump, outgoing_dump;
+
+ if (incoming_state_ == STATE_STOPPED) {
+ DVLOG(2) << "Incoming RTP dumps released: " << incoming_dump_path_.value();
+
+ incoming_state_ = STATE_NONE;
+ incoming_dump = incoming_dump_path_;
+ }
+
+ if (outgoing_state_ == STATE_STOPPED) {
+ DVLOG(2) << "Outgoing RTP dumps released: " << outgoing_dump_path_.value();
+
+ outgoing_state_ = STATE_NONE;
+ outgoing_dump = outgoing_dump_path_;
+ }
+ return ReleasedDumps(incoming_dump, outgoing_dump);
+}
+
+void WebRtcRtpDumpHandler::OnRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ if ((incoming && incoming_state_ != STATE_STARTED) ||
+ (!incoming && outgoing_state_ != STATE_STARTED)) {
+ return;
+ }
+
+ dump_writer_->WriteRtpPacket(
+ packet_header, header_length, packet_length, incoming);
+}
+
+void WebRtcRtpDumpHandler::StopOngoingDumps(const base::Closure& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+ DCHECK(!callback.is_null());
+
+ // No ongoing dumps, return directly.
+ if ((incoming_state_ == STATE_NONE || incoming_state_ == STATE_STOPPED) &&
+ (outgoing_state_ == STATE_NONE || outgoing_state_ == STATE_STOPPED)) {
+ callback.Run();
+ return;
+ }
+
+ // If the background task runner is working on stopping the dumps, wait for it
+ // to complete and then check the states again.
+ if (incoming_state_ == STATE_STOPPING || outgoing_state_ == STATE_STOPPING) {
+ dump_writer_->background_task_runner()->PostTaskAndReply(
+ FROM_HERE, base::DoNothing(),
+ base::BindOnce(&WebRtcRtpDumpHandler::StopOngoingDumps,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ // Either incoming or outgoing dump must be ongoing.
+ RtpDumpType type =
+ (incoming_state_ == STATE_STARTED)
+ ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH
+ : RTP_DUMP_INCOMING)
+ : RTP_DUMP_OUTGOING;
+
+ if (incoming_state_ == STATE_STARTED)
+ incoming_state_ = STATE_STOPPING;
+
+ if (outgoing_state_ == STATE_STARTED)
+ outgoing_state_ = STATE_STOPPING;
+
+ DVLOG(2) << "Stopping ongoing dumps: type = " << type;
+
+ dump_writer_->EndDump(type,
+ base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded,
+ base::Unretained(this),
+ callback,
+ type));
+}
+
+void WebRtcRtpDumpHandler::SetDumpWriterForTesting(
+ std::unique_ptr<WebRtcRtpDumpWriter> writer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ dump_writer_ = std::move(writer);
+ ++g_ongoing_rtp_dumps;
+
+ incoming_dump_path_ = dump_dir_.AppendASCII("recv");
+ outgoing_dump_path_ = dump_dir_.AppendASCII("send");
+}
+
+void WebRtcRtpDumpHandler::OnMaxDumpSizeReached() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ RtpDumpType type =
+ (incoming_state_ == STATE_STARTED)
+ ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH
+ : RTP_DUMP_INCOMING)
+ : RTP_DUMP_OUTGOING;
+ StopDump(type, GenericDoneCallback());
+}
+
+void WebRtcRtpDumpHandler::OnDumpEnded(const base::Closure& callback,
+ RtpDumpType ended_type,
+ bool incoming_success,
+ bool outgoing_success) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ if (DumpTypeContainsIncoming(ended_type)) {
+ DCHECK_EQ(STATE_STOPPING, incoming_state_);
+ incoming_state_ = STATE_STOPPED;
+
+ if (!incoming_success) {
+ base::PostTask(FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ incoming_dump_path_, false));
+
+ DVLOG(2) << "Deleted invalid incoming dump "
+ << incoming_dump_path_.value();
+ incoming_dump_path_.clear();
+ }
+ }
+
+ if (DumpTypeContainsOutgoing(ended_type)) {
+ DCHECK_EQ(STATE_STOPPING, outgoing_state_);
+ outgoing_state_ = STATE_STOPPED;
+
+ if (!outgoing_success) {
+ base::PostTask(FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ outgoing_dump_path_, false));
+
+ DVLOG(2) << "Deleted invalid outgoing dump "
+ << outgoing_dump_path_.value();
+ outgoing_dump_path_.clear();
+ }
+ }
+
+ // Release the writer when it's no longer needed.
+ if (incoming_state_ != STATE_STOPPING && outgoing_state_ != STATE_STOPPING &&
+ incoming_state_ != STATE_STARTED && outgoing_state_ != STATE_STARTED) {
+ dump_writer_.reset();
+ --g_ongoing_rtp_dumps;
+ }
+
+ // This object might be deleted after running the callback.
+ if (!callback.is_null())
+ callback.Run();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h
new file mode 100644
index 00000000000..3c40044ea5a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h
@@ -0,0 +1,139 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_HANDLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/media/webrtc/rtp_dump_type.h"
+
+class WebRtcRtpDumpWriter;
+
+// WebRtcRtpDumpHandler handles operations regarding the WebRTC RTP dump:
+// - Starts or stops the RTP dumping on behalf of the client.
+// - Stops the RTP dumping when the max dump file size is reached.
+// - Writes the dump file.
+// - Provides the dump file to the client code to be uploaded when
+// ReleaseRtpDump is called.
+// - Cleans up the dump file if not transferred to the client before the object
+// is destroyed.
+//
+// Must be created/used/destroyed on the browser IO thread.
+class WebRtcRtpDumpHandler {
+ public:
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+
+ struct ReleasedDumps {
+ ReleasedDumps(const base::FilePath& incoming_dump,
+ const base::FilePath& outgoing_dump)
+ : incoming_dump_path(incoming_dump),
+ outgoing_dump_path(outgoing_dump) {}
+
+ const base::FilePath incoming_dump_path;
+ const base::FilePath outgoing_dump_path;
+ };
+
+ // The caller must make sure |dump_dir| exists. RTP dump files are saved under
+ // |dump_dir| as "rtpdump_$DIRECTION_$TIMESTAMP.gz", where $DIRECTION is
+ // 'send' for outgoing dump or 'recv' for incoming dump. $TIMESTAMP is the
+ // dump started time converted to a double number in microsecond precision,
+ // which should guarantee the uniqueness across tabs and dump streams in
+ // practice.
+ explicit WebRtcRtpDumpHandler(const base::FilePath& dump_dir);
+ ~WebRtcRtpDumpHandler();
+
+ // Starts the specified type of dumping. Incoming/outgoing dumping can be
+ // started separately. Returns true if called in a valid state, i.e. the
+ // specified type of dump has not been started.
+ bool StartDump(RtpDumpType type, std::string* error_message);
+
+ // Stops the specified type of dumping. Incoming/outgoing dumping can be
+ // stopped separately. Returns asynchronously through |callback|, where
+ // |success| is true if StopDump is called in a valid state. The callback is
+ // called when the writer finishes writing the dumps.
+ void StopDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ // Returns true if it's valid to call ReleaseDumps, i.e. no dumping is ongoing
+ // or being stopped.
+ bool ReadyToRelease() const;
+
+ // Releases all the dumps and resets the state.
+ // It should only be called when both incoming and outgoing dumping has been
+ // stopped, i.e. ReadyToRelease() returns true. Returns the dump file paths.
+ //
+ // The caller will own the dump file after the method returns. If ReleaseDump
+ // is not called before this object goes away, the dump file will be deleted
+ // by this object.
+ ReleasedDumps ReleaseDumps();
+
+ // Adds an RTP packet to the dump. The caller must make sure it's a valid RTP
+ // packet.
+ void OnRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming);
+
+ // Stops all ongoing dumps and call |callback| when finished.
+ void StopOngoingDumps(const base::Closure& callback);
+
+ private:
+ friend class WebRtcRtpDumpHandlerTest;
+
+ // State transitions:
+ // initial --> STATE_NONE
+ // StartDump --> STATE_STARTED
+ // StopDump --> STATE_STOPPED
+ // ReleaseDump --> STATE_RELEASING
+ // ReleaseDump done --> STATE_NONE
+ enum State {
+ STATE_NONE,
+ STATE_STARTED,
+ STATE_STOPPING,
+ STATE_STOPPED,
+ };
+
+ // For unit test to inject a fake writer.
+ void SetDumpWriterForTesting(std::unique_ptr<WebRtcRtpDumpWriter> writer);
+
+ // Callback from the dump writer when the max dump size is reached.
+ void OnMaxDumpSizeReached();
+
+ // Callback from the dump writer when ending dumps finishes. Calls |callback|
+ // when finished.
+ void OnDumpEnded(const base::Closure& callback,
+ RtpDumpType ended_type,
+ bool incoming_succeeded,
+ bool outgoing_succeeded);
+
+ SEQUENCE_CHECKER(main_sequence_);
+
+ // The absolute path to the directory containing the incoming/outgoing dumps.
+ const base::FilePath dump_dir_;
+
+ // The dump file paths.
+ base::FilePath incoming_dump_path_;
+ base::FilePath outgoing_dump_path_;
+
+ // The states of the incoming and outgoing dump.
+ State incoming_state_;
+ State outgoing_state_;
+
+ // The object used to create and write the dump file.
+ std::unique_ptr<WebRtcRtpDumpWriter> dump_writer_;
+
+ base::WeakPtrFactory<WebRtcRtpDumpHandler> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcRtpDumpHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc
new file mode 100644
index 00000000000..01c742b0968
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc
@@ -0,0 +1,432 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class FakeDumpWriter : public WebRtcRtpDumpWriter {
+ public:
+ FakeDumpWriter(size_t max_dump_size,
+ const base::Closure& max_size_reached_callback,
+ bool end_dump_success)
+ : WebRtcRtpDumpWriter(base::FilePath(),
+ base::FilePath(),
+ max_dump_size,
+ base::Closure()),
+ max_dump_size_(max_dump_size),
+ current_dump_size_(0),
+ max_size_reached_callback_(max_size_reached_callback),
+ end_dump_success_(end_dump_success) {}
+
+ void WriteRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) override {
+ current_dump_size_ += header_length;
+ if (current_dump_size_ > max_dump_size_)
+ max_size_reached_callback_.Run();
+ }
+
+ void EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback) override {
+ bool incoming_success = end_dump_success_;
+ bool outgoing_success = end_dump_success_;
+
+ if (type == RTP_DUMP_INCOMING)
+ outgoing_success = false;
+ else if (type == RTP_DUMP_OUTGOING)
+ incoming_success = false;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(finished_callback, incoming_success, outgoing_success));
+ }
+
+ private:
+ size_t max_dump_size_;
+ size_t current_dump_size_;
+ base::Closure max_size_reached_callback_;
+ bool end_dump_success_;
+};
+
+class WebRtcRtpDumpHandlerTest : public testing::Test {
+ public:
+ WebRtcRtpDumpHandlerTest()
+ : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {
+ ResetDumpHandler(base::FilePath(), true);
+ }
+
+ void ResetDumpHandler(const base::FilePath& dir, bool end_dump_success) {
+ handler_.reset(new WebRtcRtpDumpHandler(
+ dir.empty() ? base::FilePath(FILE_PATH_LITERAL("dummy")) : dir));
+
+ std::unique_ptr<WebRtcRtpDumpWriter> writer(new FakeDumpWriter(
+ 10, base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached,
+ base::Unretained(handler_.get())),
+ end_dump_success));
+
+ handler_->SetDumpWriterForTesting(std::move(writer));
+ }
+
+ void DeleteDumpHandler() { handler_.reset(); }
+
+ void WriteFakeDumpFiles(const base::FilePath& dir,
+ base::FilePath* incoming_dump,
+ base::FilePath* outgoing_dump) {
+ *incoming_dump = dir.AppendASCII("recv");
+ *outgoing_dump = dir.AppendASCII("send");
+ const char dummy[] = "dummy";
+ EXPECT_GT(base::WriteFile(*incoming_dump, dummy, base::size(dummy)), 0);
+ EXPECT_GT(base::WriteFile(*outgoing_dump, dummy, base::size(dummy)), 0);
+ }
+
+ void FlushTaskRunners() {
+ base::ThreadPoolInstance::Get()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ MOCK_METHOD2(OnStopDumpFinished,
+ void(bool success, const std::string& error));
+
+ MOCK_METHOD0(OnStopOngoingDumpsFinished, void(void));
+
+ protected:
+ content::BrowserTaskEnvironment task_environment_;
+ std::unique_ptr<WebRtcRtpDumpHandler> handler_;
+};
+
+TEST_F(WebRtcRtpDumpHandlerTest, StateTransition) {
+ std::string error;
+
+ RtpDumpType types[3];
+ types[0] = RTP_DUMP_INCOMING;
+ types[1] = RTP_DUMP_OUTGOING;
+ types[2] = RTP_DUMP_BOTH;
+
+ for (size_t i = 0; i < base::size(types); ++i) {
+ DVLOG(2) << "Verifying state transition: type = " << types[i];
+
+ // Only StartDump is allowed in STATE_NONE.
+ EXPECT_CALL(*this, OnStopDumpFinished(false, testing::_));
+ handler_->StopDump(types[i],
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ WebRtcRtpDumpHandler::ReleasedDumps empty_dumps(handler_->ReleaseDumps());
+ EXPECT_TRUE(empty_dumps.incoming_dump_path.empty());
+ EXPECT_TRUE(empty_dumps.outgoing_dump_path.empty());
+ EXPECT_TRUE(handler_->StartDump(types[i], &error));
+ base::RunLoop().RunUntilIdle();
+
+ // Only StopDump is allowed in STATE_STARTED.
+ EXPECT_FALSE(handler_->StartDump(types[i], &error));
+ EXPECT_FALSE(handler_->ReadyToRelease());
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ handler_->StopDump(types[i],
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+
+ // Only ReleaseDump is allowed in STATE_STOPPED.
+ EXPECT_FALSE(handler_->StartDump(types[i], &error));
+
+ EXPECT_CALL(*this, OnStopDumpFinished(false, testing::_));
+ handler_->StopDump(types[i],
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ EXPECT_TRUE(handler_->ReadyToRelease());
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ if (types[i] == RTP_DUMP_INCOMING || types[i] == RTP_DUMP_BOTH)
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+
+ if (types[i] == RTP_DUMP_OUTGOING || types[i] == RTP_DUMP_BOTH)
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+
+ base::RunLoop().RunUntilIdle();
+ ResetDumpHandler(base::FilePath(), true);
+ }
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StoppedWhenMaxSizeReached) {
+ std::string error;
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+
+ std::vector<uint8_t> buffer(100, 0);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), true);
+ base::RunLoop().RunUntilIdle();
+
+ // Dumping should have been stopped, so ready to release.
+ WebRtcRtpDumpHandler::ReleasedDumps dumps = handler_->ReleaseDumps();
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, PacketIgnoredIfDumpingNotStarted) {
+ std::vector<uint8_t> buffer(100, 0);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), true);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), false);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, PacketIgnoredIfDumpingStopped) {
+ std::string error;
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ std::vector<uint8_t> buffer(100, 0);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), true);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, CannotStartMoreThanFiveDumps) {
+ std::string error;
+
+ handler_.reset();
+
+ std::unique_ptr<WebRtcRtpDumpHandler> handlers[6];
+
+ for (size_t i = 0; i < base::size(handlers); ++i) {
+ handlers[i].reset(new WebRtcRtpDumpHandler(base::FilePath()));
+
+ if (i < base::size(handlers) - 1) {
+ EXPECT_TRUE(handlers[i]->StartDump(RTP_DUMP_INCOMING, &error));
+ } else {
+ EXPECT_FALSE(handlers[i]->StartDump(RTP_DUMP_INCOMING, &error));
+ }
+ }
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StartStopIncomingThenStartStopOutgoing) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_)).Times(2);
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_OUTGOING, &error));
+ handler_->StopDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StartIncomingStartOutgoingThenStopBoth) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_OUTGOING, &error));
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StartBothThenStopIncomingStopOutgoing) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_)).Times(2);
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ handler_->StopDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, DumpsCleanedUpIfNotReleased) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ ResetDumpHandler(temp_dir.GetPath(), true);
+
+ base::FilePath incoming_dump, outgoing_dump;
+ WriteFakeDumpFiles(temp_dir.GetPath(), &incoming_dump, &outgoing_dump);
+
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ handler_->StopDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+
+ handler_.reset();
+ FlushTaskRunners();
+
+ EXPECT_FALSE(base::PathExists(incoming_dump));
+ EXPECT_FALSE(base::PathExists(outgoing_dump));
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, DumpDeletedIfEndDumpFailed) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Make the writer return failure on EndStream.
+ ResetDumpHandler(temp_dir.GetPath(), false);
+
+ base::FilePath incoming_dump, outgoing_dump;
+ WriteFakeDumpFiles(temp_dir.GetPath(), &incoming_dump, &outgoing_dump);
+
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_)).Times(2);
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+
+ EXPECT_FALSE(base::PathExists(incoming_dump));
+ EXPECT_TRUE(base::PathExists(outgoing_dump));
+
+ handler_->StopDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+ EXPECT_FALSE(base::PathExists(outgoing_dump));
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhileStoppingDumps) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ testing::InSequence s;
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+
+ handler_->StopDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ FlushTaskRunners();
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhileDumping) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ FlushTaskRunners();
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhenAlreadyStopped) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ {
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+
+ handler_->StopDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+ }
+
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhileStoppingOneDump) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ testing::InSequence s;
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ FlushTaskRunners();
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, DeleteHandlerBeforeStopCallback) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished())
+ .WillOnce(testing::InvokeWithoutArgs(
+ this, &WebRtcRtpDumpHandlerTest::DeleteDumpHandler));
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc
new file mode 100644
index 00000000000..d676f226252
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc
@@ -0,0 +1,451 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+
+#include <string.h>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+static const size_t kMinimumGzipOutputBufferSize = 256; // In bytes.
+
+const unsigned char kRtpDumpFileHeaderFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n";
+static const size_t kRtpDumpFileHeaderSize = 16; // In bytes.
+
+// A helper for writing the header of the dump file.
+void WriteRtpDumpFileHeaderBigEndian(base::TimeTicks start,
+ std::vector<uint8_t>* output) {
+ size_t buffer_start_pos = output->size();
+ output->resize(output->size() + kRtpDumpFileHeaderSize);
+
+ char* buffer = reinterpret_cast<char*>(&(*output)[buffer_start_pos]);
+
+ base::TimeDelta delta = start - base::TimeTicks();
+ uint32_t start_sec = delta.InSeconds();
+ base::WriteBigEndian(buffer, start_sec);
+ buffer += sizeof(start_sec);
+
+ uint32_t start_usec =
+ delta.InMilliseconds() * base::Time::kMicrosecondsPerMillisecond;
+ base::WriteBigEndian(buffer, start_usec);
+ buffer += sizeof(start_usec);
+
+ // Network source, always 0.
+ base::WriteBigEndian(buffer, uint32_t(0));
+ buffer += sizeof(uint32_t);
+
+ // UDP port, always 0.
+ base::WriteBigEndian(buffer, uint16_t(0));
+ buffer += sizeof(uint16_t);
+
+ // 2 bytes padding.
+ base::WriteBigEndian(buffer, uint16_t(0));
+}
+
+// The header size for each packet dump.
+static const size_t kPacketDumpHeaderSize = 8; // In bytes.
+
+// A helper for writing the header for each packet dump.
+// |start| is the time when the recording is started.
+// |dump_length| is the length of the packet dump including this header.
+// |packet_length| is the length of the RTP packet header.
+void WritePacketDumpHeaderBigEndian(const base::TimeTicks& start,
+ uint16_t dump_length,
+ uint16_t packet_length,
+ std::vector<uint8_t>* output) {
+ size_t buffer_start_pos = output->size();
+ output->resize(output->size() + kPacketDumpHeaderSize);
+
+ char* buffer = reinterpret_cast<char*>(&(*output)[buffer_start_pos]);
+
+ base::WriteBigEndian(buffer, dump_length);
+ buffer += sizeof(dump_length);
+
+ base::WriteBigEndian(buffer, packet_length);
+ buffer += sizeof(packet_length);
+
+ uint32_t elapsed =
+ static_cast<uint32_t>((base::TimeTicks::Now() - start).InMilliseconds());
+ base::WriteBigEndian(buffer, elapsed);
+}
+
+// Append |src_len| bytes from |src| to |dest|.
+void AppendToBuffer(const uint8_t* src,
+ size_t src_len,
+ std::vector<uint8_t>* dest) {
+ size_t old_dest_size = dest->size();
+ dest->resize(old_dest_size + src_len);
+ memcpy(&(*dest)[old_dest_size], src, src_len);
+}
+
+} // namespace
+
+// This class runs on the backround task runner, compresses and writes the
+// dump buffer to disk.
+class WebRtcRtpDumpWriter::FileWorker {
+ public:
+ explicit FileWorker(const base::FilePath& dump_path) : dump_path_(dump_path) {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+
+ memset(&stream_, 0, sizeof(stream_));
+ int result = deflateInit2(&stream_,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ // windowBits = 15 is default, 16 is added to
+ // produce a gzip header + trailer.
+ 15 + 16,
+ 8, // memLevel = 8 is default.
+ Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(Z_OK, result);
+ }
+
+ ~FileWorker() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Makes sure all allocations are freed.
+ deflateEnd(&stream_);
+ }
+
+ // Compresses the data in |buffer| and write to the dump file. If |end_stream|
+ // is true, the compression stream will be ended and the dump file cannot be
+ // written to any more.
+ void CompressAndWriteToFileOnFileThread(
+ std::unique_ptr<std::vector<uint8_t>> buffer,
+ bool end_stream,
+ FlushResult* result,
+ size_t* bytes_written) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // This is called either when the in-memory buffer is full or the dump
+ // should be ended.
+ DCHECK(!buffer->empty() || end_stream);
+
+ *result = FLUSH_RESULT_SUCCESS;
+ *bytes_written = 0;
+
+ // There may be nothing to compress/write if there is no RTP packet since
+ // the last flush.
+ if (!buffer->empty()) {
+ *bytes_written = CompressAndWriteBufferToFile(buffer.get(), result);
+ } else if (!base::PathExists(dump_path_)) {
+ // If the dump does not exist, it means there is no RTP packet recorded.
+ // Return FLUSH_RESULT_NO_DATA to indicate no dump file created.
+ *result = FLUSH_RESULT_NO_DATA;
+ }
+
+ if (end_stream && !EndDumpFile())
+ *result = FLUSH_RESULT_FAILURE;
+ }
+
+ private:
+ // Helper for CompressAndWriteToFileOnFileThread to compress and write one
+ // dump.
+ size_t CompressAndWriteBufferToFile(std::vector<uint8_t>* buffer,
+ FlushResult* result) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(buffer->size());
+
+ *result = FLUSH_RESULT_SUCCESS;
+
+ std::vector<uint8_t> compressed_buffer;
+ if (!Compress(buffer, &compressed_buffer)) {
+ DVLOG(2) << "Compressing buffer failed.";
+ *result = FLUSH_RESULT_FAILURE;
+ return 0;
+ }
+
+ int bytes_written = -1;
+
+ if (base::PathExists(dump_path_)) {
+ bytes_written =
+ base::AppendToFile(dump_path_, reinterpret_cast<const char*>(
+ compressed_buffer.data()),
+ compressed_buffer.size())
+ ? compressed_buffer.size()
+ : -1;
+ } else {
+ bytes_written = base::WriteFile(
+ dump_path_,
+ reinterpret_cast<const char*>(&compressed_buffer[0]),
+ compressed_buffer.size());
+ }
+
+ if (bytes_written == -1) {
+ DVLOG(2) << "Writing file failed: " << dump_path_.value();
+ *result = FLUSH_RESULT_FAILURE;
+ return 0;
+ }
+
+ DCHECK_EQ(static_cast<size_t>(bytes_written), compressed_buffer.size());
+ return bytes_written;
+ }
+
+ // Compresses |input| into |output|.
+ bool Compress(std::vector<uint8_t>* input, std::vector<uint8_t>* output) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ int result = Z_OK;
+
+ output->resize(std::max(kMinimumGzipOutputBufferSize, input->size()));
+
+ stream_.next_in = &(*input)[0];
+ stream_.avail_in = input->size();
+ stream_.next_out = &(*output)[0];
+ stream_.avail_out = output->size();
+
+ result = deflate(&stream_, Z_SYNC_FLUSH);
+ DCHECK_EQ(Z_OK, result);
+ DCHECK_EQ(0U, stream_.avail_in);
+
+ output->resize(output->size() - stream_.avail_out);
+
+ stream_.next_in = NULL;
+ stream_.next_out = NULL;
+ stream_.avail_out = 0;
+ return true;
+ }
+
+ // Ends the compression stream and completes the dump file.
+ bool EndDumpFile() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::vector<uint8_t> output_buffer;
+ output_buffer.resize(kMinimumGzipOutputBufferSize);
+
+ stream_.next_in = NULL;
+ stream_.avail_in = 0;
+ stream_.next_out = &output_buffer[0];
+ stream_.avail_out = output_buffer.size();
+
+ int result = deflate(&stream_, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+
+ result = deflateEnd(&stream_);
+ DCHECK_EQ(Z_OK, result);
+
+ output_buffer.resize(output_buffer.size() - stream_.avail_out);
+
+ memset(&stream_, 0, sizeof(z_stream));
+
+ DCHECK(!output_buffer.empty());
+ return base::AppendToFile(
+ dump_path_, reinterpret_cast<const char*>(output_buffer.data()),
+ output_buffer.size());
+ }
+
+ const base::FilePath dump_path_;
+
+ z_stream stream_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(FileWorker);
+};
+
+WebRtcRtpDumpWriter::WebRtcRtpDumpWriter(
+ const base::FilePath& incoming_dump_path,
+ const base::FilePath& outgoing_dump_path,
+ size_t max_dump_size,
+ const base::Closure& max_dump_size_reached_callback)
+ : max_dump_size_(max_dump_size),
+ max_dump_size_reached_callback_(max_dump_size_reached_callback),
+ total_dump_size_on_disk_(0),
+ background_task_runner_(
+ base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT})),
+ incoming_file_thread_worker_(new FileWorker(incoming_dump_path)),
+ outgoing_file_thread_worker_(new FileWorker(outgoing_dump_path)) {}
+
+WebRtcRtpDumpWriter::~WebRtcRtpDumpWriter() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ bool success = background_task_runner_->DeleteSoon(
+ FROM_HERE, incoming_file_thread_worker_.release());
+ DCHECK(success);
+
+ success = background_task_runner_->DeleteSoon(
+ FROM_HERE, outgoing_file_thread_worker_.release());
+ DCHECK(success);
+}
+
+void WebRtcRtpDumpWriter::WriteRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ static const size_t kMaxInMemoryBufferSize = 65536;
+
+ std::vector<uint8_t>* dest_buffer =
+ incoming ? &incoming_buffer_ : &outgoing_buffer_;
+
+ // We use the capacity of the buffer to indicate if the buffer has been
+ // initialized and if the dump file header has been created.
+ if (!dest_buffer->capacity()) {
+ dest_buffer->reserve(std::min(kMaxInMemoryBufferSize, max_dump_size_));
+
+ start_time_ = base::TimeTicks::Now();
+
+ // Writes the dump file header.
+ AppendToBuffer(kRtpDumpFileHeaderFirstLine,
+ base::size(kRtpDumpFileHeaderFirstLine) - 1, dest_buffer);
+ WriteRtpDumpFileHeaderBigEndian(start_time_, dest_buffer);
+ }
+
+ size_t packet_dump_length = kPacketDumpHeaderSize + header_length;
+
+ // Flushes the buffer to disk if the buffer is full.
+ if (dest_buffer->size() + packet_dump_length > dest_buffer->capacity())
+ FlushBuffer(incoming, false, FlushDoneCallback());
+
+ WritePacketDumpHeaderBigEndian(
+ start_time_, packet_dump_length, packet_length, dest_buffer);
+
+ // Writes the actual RTP packet header.
+ AppendToBuffer(packet_header, header_length, dest_buffer);
+}
+
+void WebRtcRtpDumpWriter::EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(type == RTP_DUMP_OUTGOING || incoming_file_thread_worker_ != NULL);
+ DCHECK(type == RTP_DUMP_INCOMING || outgoing_file_thread_worker_ != NULL);
+
+ bool incoming = (type == RTP_DUMP_BOTH || type == RTP_DUMP_INCOMING);
+ EndDumpContext context(type, finished_callback);
+
+ // End the incoming dump first if required. OnDumpEnded will continue to end
+ // the outgoing dump if necessary.
+ FlushBuffer(incoming,
+ true,
+ base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ incoming));
+}
+
+size_t WebRtcRtpDumpWriter::max_dump_size() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return max_dump_size_;
+}
+
+WebRtcRtpDumpWriter::EndDumpContext::EndDumpContext(
+ RtpDumpType type,
+ const EndDumpCallback& callback)
+ : type(type),
+ incoming_succeeded(false),
+ outgoing_succeeded(false),
+ callback(callback) {
+}
+
+WebRtcRtpDumpWriter::EndDumpContext::EndDumpContext(
+ const EndDumpContext& other) = default;
+
+WebRtcRtpDumpWriter::EndDumpContext::~EndDumpContext() {
+}
+
+void WebRtcRtpDumpWriter::FlushBuffer(bool incoming,
+ bool end_stream,
+ const FlushDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::unique_ptr<std::vector<uint8_t>> new_buffer(new std::vector<uint8_t>());
+
+ if (incoming) {
+ new_buffer->reserve(incoming_buffer_.capacity());
+ new_buffer->swap(incoming_buffer_);
+ } else {
+ new_buffer->reserve(outgoing_buffer_.capacity());
+ new_buffer->swap(outgoing_buffer_);
+ }
+
+ std::unique_ptr<FlushResult> result(new FlushResult(FLUSH_RESULT_FAILURE));
+
+ std::unique_ptr<size_t> bytes_written(new size_t(0));
+
+ FileWorker* worker = incoming ? incoming_file_thread_worker_.get()
+ : outgoing_file_thread_worker_.get();
+
+ // Using "Unretained(worker)" because |worker| is owner by this object and it
+ // guaranteed to be deleted on the backround task runner before this object
+ // goes away.
+ base::OnceClosure task = base::BindOnce(
+ &FileWorker::CompressAndWriteToFileOnFileThread, base::Unretained(worker),
+ std::move(new_buffer), end_stream, result.get(), bytes_written.get());
+
+ // OnFlushDone is necessary to avoid running the callback after this
+ // object is gone.
+ base::OnceClosure reply = base::BindOnce(
+ &WebRtcRtpDumpWriter::OnFlushDone, weak_ptr_factory_.GetWeakPtr(),
+ callback, std::move(result), std::move(bytes_written));
+
+ // Define the task and reply outside the method call so that getting and
+ // passing the scoped_ptr does not depend on the argument evaluation order.
+ background_task_runner_->PostTaskAndReply(FROM_HERE, std::move(task),
+ std::move(reply));
+
+ if (end_stream) {
+ bool success = background_task_runner_->DeleteSoon(
+ FROM_HERE, incoming ? incoming_file_thread_worker_.release()
+ : outgoing_file_thread_worker_.release());
+ DCHECK(success);
+ }
+}
+
+void WebRtcRtpDumpWriter::OnFlushDone(
+ const FlushDoneCallback& callback,
+ const std::unique_ptr<FlushResult>& result,
+ const std::unique_ptr<size_t>& bytes_written) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ total_dump_size_on_disk_ += *bytes_written;
+
+ if (total_dump_size_on_disk_ >= max_dump_size_ &&
+ !max_dump_size_reached_callback_.is_null()) {
+ max_dump_size_reached_callback_.Run();
+ }
+
+ // Returns success for FLUSH_RESULT_MAX_SIZE_REACHED since the dump is still
+ // valid.
+ if (!callback.is_null()) {
+ callback.Run(*result != FLUSH_RESULT_FAILURE &&
+ *result != FLUSH_RESULT_NO_DATA);
+ }
+}
+
+void WebRtcRtpDumpWriter::OnDumpEnded(EndDumpContext context,
+ bool incoming,
+ bool success) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DVLOG(2) << "Dump ended, incoming = " << incoming
+ << ", succeeded = " << success;
+
+ if (incoming)
+ context.incoming_succeeded = success;
+ else
+ context.outgoing_succeeded = success;
+
+ // End the outgoing dump if needed.
+ if (incoming && context.type == RTP_DUMP_BOTH) {
+ FlushBuffer(false,
+ true,
+ base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ false));
+ return;
+ }
+
+ // This object might be deleted after running the callback.
+ context.callback.Run(context.incoming_succeeded, context.outgoing_succeeded);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h
new file mode 100644
index 00000000000..39af6dfa1b5
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h
@@ -0,0 +1,148 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_WRITER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_WRITER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/rtp_dump_type.h"
+
+// This class is responsible for creating the compressed RTP header dump file:
+// - Adds the RTP headers to an in-memory buffer.
+// - When the in-memory buffer is full, compresses it, and writes it to the
+// disk.
+// - Notifies the caller when the on-disk file size reaches the max limit.
+// - The uncompressed dump follows the standard RTPPlay format
+// (http://www.cs.columbia.edu/irt/software/rtptools/).
+// - The caller is always responsible for cleaning up the dump file in all
+// cases.
+// - WebRtcRtpDumpWriter does not stop writing to the dump after the max size
+// limit is reached. The caller must stop calling WriteRtpPacket instead.
+//
+// This object must run on the IO thread.
+class WebRtcRtpDumpWriter {
+ public:
+ typedef base::Callback<void(bool incoming_succeeded, bool outgoing_succeeded)>
+ EndDumpCallback;
+
+ // |incoming_dump_path| and |outgoing_dump_path| are the file paths of the
+ // compressed dump files for incoming and outgoing packets respectively.
+ // |max_dump_size| is the max size of the compressed dump file in bytes.
+ // |max_dump_size_reached_callback| will be called when the on-disk file size
+ // reaches |max_dump_size|.
+ WebRtcRtpDumpWriter(const base::FilePath& incoming_dump_path,
+ const base::FilePath& outgoing_dump_path,
+ size_t max_dump_size,
+ const base::Closure& max_dump_size_reached_callback);
+
+ virtual ~WebRtcRtpDumpWriter();
+
+ // Adds a RTP packet to the dump. The caller must make sure it's a valid RTP
+ // packet. No validation is done by this method.
+ virtual void WriteRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming);
+
+ // Flushes the in-memory buffer to the disk and ends the dump. The caller must
+ // make sure the dump has not already been ended.
+ // |finished_callback| will be called to indicate whether the dump is valid.
+ // If this object is destroyed before the operation is finished, the callback
+ // will be canceled and the dump files will be deleted.
+ virtual void EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback);
+
+ size_t max_dump_size() const;
+
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner()
+ const {
+ return background_task_runner_;
+ }
+
+ private:
+ enum FlushResult {
+ // Flushing has succeeded and the dump size is under the max limit.
+ FLUSH_RESULT_SUCCESS,
+ // Nothing has been written to disk and the dump is empty.
+ FLUSH_RESULT_NO_DATA,
+ // Flushing has failed for other reasons.
+ FLUSH_RESULT_FAILURE
+ };
+
+ class FileWorker;
+
+ typedef base::Callback<void(bool)> FlushDoneCallback;
+
+ // Used by EndDump to cache the input and intermediate results.
+ struct EndDumpContext {
+ EndDumpContext(RtpDumpType type, const EndDumpCallback& callback);
+ EndDumpContext(const EndDumpContext& other);
+ ~EndDumpContext();
+
+ RtpDumpType type;
+ bool incoming_succeeded;
+ bool outgoing_succeeded;
+ EndDumpCallback callback;
+ };
+
+ // Flushes the in-memory buffer to disk. If |incoming| is true, the incoming
+ // buffer will be flushed; otherwise, the outgoing buffer will be flushed.
+ // The dump file will be ended if |end_stream| is true. |callback| will be
+ // called when flushing is done.
+ void FlushBuffer(bool incoming,
+ bool end_stream,
+ const FlushDoneCallback& callback);
+
+ // Called when FlushBuffer finishes. Checks the max dump size limit and
+ // maybe calls the |max_dump_size_reached_callback_|. Also calls |callback|
+ // with the flush result.
+ void OnFlushDone(const FlushDoneCallback& callback,
+ const std::unique_ptr<FlushResult>& result,
+ const std::unique_ptr<size_t>& bytes_written);
+
+ // Called when one type of dump has been ended. It continues to end the other
+ // dump if needed based on |context| and |incoming|, or calls the callback in
+ // |context| if no more dump needs to be ended.
+ void OnDumpEnded(EndDumpContext context, bool incoming, bool success);
+
+ // The max limit on the total size of incoming and outgoing dumps on disk.
+ const size_t max_dump_size_;
+
+ // The callback to call when the max size limit is reached.
+ const base::Closure max_dump_size_reached_callback_;
+
+ // The in-memory buffers for the uncompressed dumps.
+ std::vector<uint8_t> incoming_buffer_;
+ std::vector<uint8_t> outgoing_buffer_;
+
+ // The time when the first packet is dumped.
+ base::TimeTicks start_time_;
+
+ // The total on-disk size of the compressed incoming and outgoing dumps.
+ size_t total_dump_size_on_disk_;
+
+ // File workers must be called and deleted on the backround task runner.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+ std::unique_ptr<FileWorker> incoming_file_thread_worker_;
+ std::unique_ptr<FileWorker> outgoing_file_thread_worker_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ base::WeakPtrFactory<WebRtcRtpDumpWriter> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcRtpDumpWriter);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_WRITER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
new file mode 100644
index 00000000000..606aca3fec2
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
@@ -0,0 +1,375 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/zlib.h"
+
+static const size_t kMinimumRtpHeaderLength = 12;
+
+static void CreateFakeRtpPacketHeader(size_t csrc_count,
+ size_t extension_header_count,
+ std::vector<uint8_t>* packet_header) {
+ packet_header->resize(kMinimumRtpHeaderLength +
+ csrc_count * sizeof(uint32_t) +
+ (extension_header_count + 1) * sizeof(uint32_t));
+
+ memset(&(*packet_header)[0], 0, packet_header->size());
+
+ // First byte format: vvpxcccc, where 'vv' is the version, 'p' is padding, 'x'
+ // is the extension bit, 'cccc' is the CSRC count.
+ (*packet_header)[0] = 0;
+ (*packet_header)[0] |= (0x2 << 6); // version.
+ // The extension bit.
+ (*packet_header)[0] |= (extension_header_count > 0 ? (0x1 << 4) : 0);
+ (*packet_header)[0] |= (csrc_count & 0xf);
+
+ // Set extension length.
+ size_t offset = kMinimumRtpHeaderLength +
+ (csrc_count & 0xf) * sizeof(uint32_t) + sizeof(uint16_t);
+ base::WriteBigEndian(reinterpret_cast<char*>(&(*packet_header)[offset]),
+ static_cast<uint16_t>(extension_header_count));
+}
+
+static void FlushTaskRunner(base::SequencedTaskRunner* task_runner) {
+ base::RunLoop run_loop;
+ task_runner->PostTask(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+class WebRtcRtpDumpWriterTest : public testing::Test {
+ public:
+ WebRtcRtpDumpWriterTest()
+ : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
+ temp_dir_(new base::ScopedTempDir()) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
+
+ incoming_dump_path_ = temp_dir_->GetPath().AppendASCII("rtpdump_recv");
+ outgoing_dump_path_ = temp_dir_->GetPath().AppendASCII("rtpdump_send");
+ writer_.reset(new WebRtcRtpDumpWriter(
+ incoming_dump_path_,
+ outgoing_dump_path_,
+ 4 * 1024 * 1024,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnMaxSizeReached,
+ base::Unretained(this))));
+ }
+
+ // Verifies that the dump contains records of |rtp_packet| repeated
+ // |packet_count| times.
+ void VerifyDumps(size_t incoming_packet_count, size_t outgoing_packet_count) {
+ std::string incoming_dump;
+ std::string outgoing_dump;
+
+ if (incoming_packet_count) {
+ EXPECT_TRUE(base::ReadFileToString(incoming_dump_path_, &incoming_dump));
+ EXPECT_TRUE(VerifyCompressedDump(&incoming_dump, incoming_packet_count));
+ } else {
+ EXPECT_FALSE(base::PathExists(incoming_dump_path_));
+ }
+
+ if (outgoing_packet_count) {
+ EXPECT_TRUE(base::ReadFileToString(outgoing_dump_path_, &outgoing_dump));
+ EXPECT_TRUE(VerifyCompressedDump(&outgoing_dump, outgoing_packet_count));
+ } else {
+ EXPECT_FALSE(base::PathExists(outgoing_dump_path_));
+ }
+ }
+
+ MOCK_METHOD2(OnEndDumpDone, void(bool, bool));
+ MOCK_METHOD0(OnMaxSizeReached, void(void));
+
+ protected:
+ // Verifies the compressed dump file contains the expected number of packets.
+ bool VerifyCompressedDump(std::string* dump, size_t expected_packet_count) {
+ EXPECT_GT(dump->size(), 0U);
+
+ std::vector<uint8_t> decompressed_dump;
+ EXPECT_TRUE(Decompress(dump, &decompressed_dump));
+
+ size_t actual_packet_count = 0;
+ EXPECT_TRUE(ReadDecompressedDump(decompressed_dump, &actual_packet_count));
+ EXPECT_EQ(expected_packet_count, actual_packet_count);
+
+ return true;
+ }
+
+ // Decompresses the |input| into |output|.
+ bool Decompress(std::string* input, std::vector<uint8_t>* output) {
+ z_stream stream = {0};
+
+ int result = inflateInit2(&stream, 15 + 16);
+ EXPECT_EQ(Z_OK, result);
+
+ output->resize(input->size() * 100);
+
+ stream.next_in =
+ reinterpret_cast<unsigned char*>(const_cast<char*>(&(*input)[0]));
+ stream.avail_in = input->size();
+ stream.next_out = &(*output)[0];
+ stream.avail_out = output->size();
+
+ result = inflate(&stream, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+ result = inflateEnd(&stream);
+ DCHECK_EQ(Z_OK, result);
+
+ output->resize(output->size() - stream.avail_out);
+ return true;
+ }
+
+ // Tries to read |dump| as a rtpplay dump file and returns the number of
+ // packets found in the dump.
+ bool ReadDecompressedDump(const std::vector<uint8_t>& dump,
+ size_t* packet_count) {
+ static const char kFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n";
+ static const size_t kDumpFileHeaderSize = 4 * sizeof(uint32_t);
+
+ *packet_count = 0;
+ size_t dump_pos = 0;
+
+ // Verifies the first line.
+ EXPECT_EQ(memcmp(&dump[0], kFirstLine, base::size(kFirstLine) - 1), 0);
+
+ dump_pos += base::size(kFirstLine) - 1;
+ EXPECT_GT(dump.size(), dump_pos);
+
+ // Skips the file header.
+ dump_pos += kDumpFileHeaderSize;
+ EXPECT_GT(dump.size(), dump_pos);
+
+ // Reads each packet dump.
+ while (dump_pos < dump.size()) {
+ size_t packet_dump_length = 0;
+ if (!VerifyPacketDump(&dump[dump_pos],
+ dump.size() - dump_pos,
+ &packet_dump_length)) {
+ DVLOG(0) << "Failed to read the packet dump for packet "
+ << *packet_count << ", dump_pos = " << dump_pos
+ << ", dump_length = " << dump.size();
+ return false;
+ }
+
+ EXPECT_GE(dump.size(), dump_pos + packet_dump_length);
+ dump_pos += packet_dump_length;
+
+ (*packet_count)++;
+ }
+ return true;
+ }
+
+ // Tries to read one packet dump starting at |dump| and returns the size of
+ // the packet dump.
+ bool VerifyPacketDump(const uint8_t* dump,
+ size_t dump_length,
+ size_t* packet_dump_length) {
+ static const size_t kDumpHeaderLength = 8;
+
+ size_t dump_pos = 0;
+ base::ReadBigEndian(reinterpret_cast<const char*>(dump + dump_pos),
+ reinterpret_cast<uint16_t*>(packet_dump_length));
+ if (*packet_dump_length < kDumpHeaderLength + kMinimumRtpHeaderLength)
+ return false;
+
+ EXPECT_GE(dump_length, *packet_dump_length);
+ dump_pos += sizeof(uint16_t);
+
+ uint16_t rtp_packet_length = 0;
+ base::ReadBigEndian(reinterpret_cast<const char*>(dump + dump_pos),
+ &rtp_packet_length);
+ if (rtp_packet_length < kMinimumRtpHeaderLength)
+ return false;
+
+ dump_pos += sizeof(uint16_t);
+
+ // Skips the elapsed time field.
+ dump_pos += sizeof(uint32_t);
+
+ return IsValidRtpHeader(dump + dump_pos,
+ *packet_dump_length - kDumpHeaderLength);
+ }
+
+ // Returns true if |header| is a valid RTP header.
+ bool IsValidRtpHeader(const uint8_t* header, size_t length) {
+ if ((header[0] & 0xC0) != 0x80)
+ return false;
+
+ size_t cc_count = header[0] & 0x0F;
+ size_t header_length_without_extn = kMinimumRtpHeaderLength + 4 * cc_count;
+
+ if (length < header_length_without_extn)
+ return false;
+
+ uint16_t extension_count = 0;
+ base::ReadBigEndian(
+ reinterpret_cast<const char*>(header + header_length_without_extn + 2),
+ &extension_count);
+
+ if (length < (extension_count + 1) * 4 + header_length_without_extn)
+ return false;
+
+ return true;
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+ std::unique_ptr<base::ScopedTempDir> temp_dir_;
+ base::FilePath incoming_dump_path_;
+ base::FilePath outgoing_dump_path_;
+ std::unique_ptr<WebRtcRtpDumpWriter> writer_;
+};
+
+TEST_F(WebRtcRtpDumpWriterTest, NoDumpFileIfNoPacketDumped) {
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnEndDumpDone(false, false));
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+ EXPECT_FALSE(base::PathExists(incoming_dump_path_));
+ EXPECT_FALSE(base::PathExists(outgoing_dump_path_));
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, WriteAndFlushSmallSizeDump) {
+ std::vector<uint8_t> packet_header;
+ CreateFakeRtpPacketHeader(1, 2, &packet_header);
+
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, false);
+
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnEndDumpDone(true, true));
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ VerifyDumps(1, 1);
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, WriteOverMaxLimit) {
+ // Reset the writer with a small max size limit.
+ writer_.reset(new WebRtcRtpDumpWriter(
+ incoming_dump_path_,
+ outgoing_dump_path_,
+ 100,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnMaxSizeReached,
+ base::Unretained(this))));
+
+ std::vector<uint8_t> packet_header;
+ CreateFakeRtpPacketHeader(3, 4, &packet_header);
+
+ const size_t kPacketCount = 200;
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnMaxSizeReached()).Times(testing::AtLeast(1));
+
+ // Write enough packets to overflow the in-memory buffer and max limit.
+ for (size_t i = 0; i < kPacketCount; ++i) {
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, false);
+ }
+
+ EXPECT_CALL(*this, OnEndDumpDone(true, true));
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+ VerifyDumps(kPacketCount, kPacketCount);
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, DestroyWriterBeforeEndDumpCallback) {
+ EXPECT_CALL(*this, OnEndDumpDone(testing::_, testing::_)).Times(0);
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ writer_.reset();
+
+ // Two |RunUntilIdle()| calls are needed as the first run posts a task that
+ // we need to give a chance to run with the second call.
+ base::RunLoop().RunUntilIdle();
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, EndDumpsSeparately) {
+ std::vector<uint8_t> packet_header;
+ CreateFakeRtpPacketHeader(1, 2, &packet_header);
+
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, false);
+
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnEndDumpDone(true, false));
+ EXPECT_CALL(*this, OnEndDumpDone(false, true));
+
+ writer_->EndDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ writer_->EndDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ VerifyDumps(2, 1);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc
new file mode 100644
index 00000000000..582f04f5474
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc
@@ -0,0 +1,68 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/perf/perf_test.h"
+#include "ui/gl/gl_switches.h"
+
+static const char kSimulcastTestPage[] = "/webrtc/webrtc-simulcast.html";
+
+// Simulcast integration test. This test ensures 'a=x-google-flag:conference'
+// is working and that Chrome is capable of sending simulcast streams.
+class WebRtcSimulcastBrowserTest : public WebRtcTestBase {
+ public:
+ // TODO(phoglund): Make it possible to enable DetectErrorsInJavaScript() here.
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Just answer 'allow' to GetUserMedia invocations.
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+
+ // The video playback will not work without a GPU, so force its use here.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+
+ // Use fake devices in order to run on VMs.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+};
+
+// Fails/times out on Windows and Chrome OS. Flaky on Mac.
+// http://crbug.com/452623
+// http://crbug.com/1004546
+// MSan reports errors. http://crbug.com/452892
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) || \
+ defined(MEMORY_SANITIZER)
+#define MAYBE_TestVgaReturnsTwoSimulcastStreams DISABLED_TestVgaReturnsTwoSimulcastStreams
+#else
+#define MAYBE_TestVgaReturnsTwoSimulcastStreams TestVgaReturnsTwoSimulcastStreams
+#endif
+IN_PROC_BROWSER_TEST_F(WebRtcSimulcastBrowserTest,
+ MAYBE_TestVgaReturnsTwoSimulcastStreams) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL(kSimulcastTestPage));
+
+ content::WebContents* tab_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ ASSERT_EQ("OK", ExecuteJavascript("testVgaReturnsTwoSimulcastStreams()",
+ tab_contents));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc
new file mode 100644
index 00000000000..049fc2ce87e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc
@@ -0,0 +1,391 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace content {
+
+namespace {
+
+const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+
+const char kInboundRtp[] = "inbound-rtp";
+const char kOutboundRtp[] = "outbound-rtp";
+
+constexpr int kBitsPerByte = 8;
+
+constexpr char kMetricPrefixAudioStats[] = "WebRtcAudioStats.";
+constexpr char kMetricPrefixVideoStats[] = "WebRtcVideoStats.";
+constexpr char kMetricPrefixGetStats[] = "WebRtcGetStats.";
+constexpr char kMetricSendRateBitsPerS[] = "send_rate";
+constexpr char kMetricReceiveRateBitsPerS[] = "receive_rate";
+constexpr char kMetricInvocationTimeMs[] = "invocation_time";
+
+perf_test::PerfResultReporter SetUpAudioReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixAudioStats, story);
+ reporter.RegisterFyiMetric(kMetricSendRateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricReceiveRateBitsPerS, "bits/s");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpVideoReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoStats, story);
+ reporter.RegisterFyiMetric(kMetricSendRateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricReceiveRateBitsPerS, "bits/s");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpGetStatsReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixGetStats, story);
+ reporter.RegisterFyiMetric(kMetricInvocationTimeMs, "ms");
+ return reporter;
+}
+
+enum class GetStatsVariation {
+ PROMISE_BASED,
+ CALLBACK_BASED
+};
+
+// Sums up "RTC[In/Out]boundRTPStreamStats.bytes_[received/sent]" values.
+double GetTotalRTPStreamBytes(
+ TestStatsReportDictionary* report, const char* type,
+ const char* media_type) {
+ DCHECK(type == kInboundRtp || type == kOutboundRtp);
+ const char* bytes_name =
+ (type == kInboundRtp) ? "bytesReceived" : "bytesSent";
+ double total_bytes = 0.0;
+ report->ForEach([&type, &bytes_name, &media_type, &total_bytes](
+ const TestStatsDictionary& stats) {
+ if (stats.GetString("type") == type &&
+ stats.GetString("mediaType") == media_type) {
+ total_bytes += stats.GetNumber(bytes_name);
+ }
+ });
+ return total_bytes;
+}
+
+double GetAudioBytesSent(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kOutboundRtp, "audio");
+}
+
+double GetAudioBytesReceived(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kInboundRtp, "audio");
+}
+
+double GetVideoBytesSent(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kOutboundRtp, "video");
+}
+
+double GetVideoBytesReceived(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kInboundRtp, "video");
+}
+
+// Performance browsertest for WebRTC. This test is manual since it takes long
+// to execute and requires the reference files provided by the webrtc.DEPS
+// solution (which is only available on WebRTC internal bots).
+// Gets its metrics from the standards conformant "RTCPeerConnection.getStats".
+class WebRtcStatsPerfBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Play a suitable, somewhat realistic video file.
+ base::FilePath input_video = test::GetReferenceFilesDir()
+ .Append(test::kReferenceFileName360p)
+ .AddExtension(test::kY4mFileExtension);
+ command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture,
+ input_video);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+
+ void StartCall(const std::string& audio_codec,
+ const std::string& video_codec,
+ bool prefer_hw_video_codec,
+ const std::string& video_codec_profile) {
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 100000.";
+
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 100000.";
+
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ left_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ SetDefaultAudioCodec(left_tab_, audio_codec);
+ SetDefaultAudioCodec(right_tab_, audio_codec);
+ SetDefaultVideoCodec(left_tab_, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ SetDefaultVideoCodec(right_tab_, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ CreateDataChannel(left_tab_, "data");
+ CreateDataChannel(right_tab_, "data");
+ NegotiateCall(left_tab_, right_tab_);
+ StartDetectingVideo(left_tab_, "remote-view");
+ StartDetectingVideo(right_tab_, "remote-view");
+ WaitForVideoToPlay(left_tab_);
+ WaitForVideoToPlay(right_tab_);
+ }
+
+ void EndCall() {
+ if (left_tab_)
+ HangUp(left_tab_);
+ if (right_tab_)
+ HangUp(right_tab_);
+ }
+
+ void RunsAudioAndVideoCallCollectingMetricsWithAudioCodec(
+ const std::string& audio_codec) {
+ RunsAudioAndVideoCallCollectingMetrics(
+ audio_codec, kUseDefaultVideoCodec, false /* prefer_hw_video_codec */,
+ "" /* video_codec_profile */, "" /* video_codec_print_modifier */);
+ }
+
+ void RunsAudioAndVideoCallCollectingMetricsWithVideoCodec(
+ const std::string& video_codec,
+ bool prefer_hw_video_codec = false,
+ const std::string& video_codec_profile = std::string(),
+ const std::string& video_codec_print_modifier = std::string()) {
+ RunsAudioAndVideoCallCollectingMetrics(
+ kUseDefaultAudioCodec, video_codec, prefer_hw_video_codec,
+ video_codec_profile, video_codec_print_modifier);
+ }
+
+ void RunsAudioAndVideoCallCollectingMetrics(
+ const std::string& audio_codec,
+ const std::string& video_codec,
+ bool prefer_hw_video_codec,
+ const std::string& video_codec_profile,
+ const std::string& video_codec_print_modifier) {
+ StartCall(audio_codec, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+
+ // Call for 60 seconds so that values may stabilize, bandwidth ramp up, etc.
+ test::SleepInJavascript(left_tab_, 60000);
+
+ // The ramp-up may vary greatly and impact the resulting total bytes, to get
+ // reliable measurements we do two measurements, at 60 and 70 seconds and
+ // look at the average bytes/second in that window.
+ double audio_bytes_sent_before = 0.0;
+ double audio_bytes_received_before = 0.0;
+ double video_bytes_sent_before = 0.0;
+ double video_bytes_received_before = 0.0;
+
+ scoped_refptr<TestStatsReportDictionary> report =
+ GetStatsReportDictionary(left_tab_);
+ if (audio_codec != kUseDefaultAudioCodec) {
+ audio_bytes_sent_before = GetAudioBytesSent(report.get());
+ audio_bytes_received_before = GetAudioBytesReceived(report.get());
+
+ }
+ if (video_codec != kUseDefaultVideoCodec) {
+ video_bytes_sent_before = GetVideoBytesSent(report.get());
+ video_bytes_received_before = GetVideoBytesReceived(report.get());
+ }
+
+ double measure_duration_seconds = 10.0;
+ test::SleepInJavascript(left_tab_, static_cast<int>(
+ measure_duration_seconds * base::Time::kMillisecondsPerSecond));
+
+ report = GetStatsReportDictionary(left_tab_);
+ if (audio_codec != kUseDefaultAudioCodec) {
+ double audio_bytes_sent_after = GetAudioBytesSent(report.get());
+ double audio_bytes_received_after = GetAudioBytesReceived(report.get());
+
+ double audio_send_rate =
+ (audio_bytes_sent_after - audio_bytes_sent_before) /
+ measure_duration_seconds;
+ double audio_receive_rate =
+ (audio_bytes_received_after - audio_bytes_received_before) /
+ measure_duration_seconds;
+
+ auto reporter = SetUpAudioReporter(audio_codec);
+ reporter.AddResult(kMetricSendRateBitsPerS,
+ audio_send_rate * kBitsPerByte);
+ reporter.AddResult(kMetricReceiveRateBitsPerS,
+ audio_receive_rate * kBitsPerByte);
+ }
+ if (video_codec != kUseDefaultVideoCodec) {
+ double video_bytes_sent_after = GetVideoBytesSent(report.get());
+ double video_bytes_received_after = GetVideoBytesReceived(report.get());
+
+ double video_send_rate =
+ (video_bytes_sent_after - video_bytes_sent_before) /
+ measure_duration_seconds;
+ double video_receive_rate =
+ (video_bytes_received_after - video_bytes_received_before) /
+ measure_duration_seconds;
+
+ std::string story =
+ (video_codec_print_modifier.empty() ? video_codec
+ : video_codec_print_modifier);
+ auto reporter = SetUpVideoReporter(story);
+ reporter.AddResult(kMetricSendRateBitsPerS,
+ video_send_rate * kBitsPerByte);
+ reporter.AddResult(kMetricReceiveRateBitsPerS,
+ video_receive_rate * kBitsPerByte);
+ }
+
+ EndCall();
+ }
+
+ void RunsAudioAndVideoCallMeasuringGetStatsPerformance(
+ GetStatsVariation variation) {
+ EXPECT_TRUE(base::TimeTicks::IsHighResolution());
+
+ StartCall(kUseDefaultAudioCodec, kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */, "");
+
+ double invocation_time = 0.0;
+ switch (variation) {
+ case GetStatsVariation::PROMISE_BASED:
+ invocation_time = (MeasureGetStatsPerformance(left_tab_) +
+ MeasureGetStatsPerformance(right_tab_)) / 2.0;
+ break;
+ case GetStatsVariation::CALLBACK_BASED:
+ invocation_time =
+ (MeasureGetStatsCallbackPerformance(left_tab_) +
+ MeasureGetStatsCallbackPerformance(right_tab_)) / 2.0;
+ break;
+ }
+ auto reporter = SetUpGetStatsReporter(
+ variation == GetStatsVariation::PROMISE_BASED ? "promise" : "callback");
+ reporter.AddResult(kMetricInvocationTimeMs, invocation_time);
+
+ EndCall();
+ }
+
+ private:
+ content::WebContents* left_tab_ = nullptr;
+ content::WebContents* right_tab_ = nullptr;
+};
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_opus) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("opus");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_ISAC) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("ISAC");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_G722) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("G722");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_PCMU) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("PCMU");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_PCMA) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("PCMA");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_VP8) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec("VP8");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_VP9) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec("VP9");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_VP9Profile2) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec(
+ "VP9", true /* prefer_hw_video_codec */,
+ WebRtcTestBase::kVP9Profile2Specifier, "VP9p2");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_H264) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcPerfBrowserTest."
+ "MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_"
+ "H264 (test "
+ "\"OK\")";
+ return;
+ }
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec(
+ "H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallMeasuringGetStatsPerformance_Promise) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallMeasuringGetStatsPerformance(
+ GetStatsVariation::PROMISE_BASED);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallMeasuringGetStatsPerformance_Callback) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallMeasuringGetStatsPerformance(
+ GetStatsVariation::CALLBACK_BASED);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
new file mode 100644
index 00000000000..c0126266d57
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
@@ -0,0 +1,539 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_text_log_handler.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/cpu.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/media/webrtc_logging.mojom.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/webrtc_log.h"
+#include "content/public/common/content_features.h"
+#include "gpu/config/gpu_info.h"
+#include "media/audio/audio_manager.h"
+#include "media/webrtc/webrtc_switches.h"
+#include "net/base/ip_address.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_interfaces.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/service_manager/sandbox/features.h"
+#include "services/service_manager/sandbox/sandbox_type.h"
+
+#if defined(OS_LINUX)
+#include "base/linux_util.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/system/statistics_provider.h"
+#endif
+
+using base::NumberToString;
+
+namespace {
+
+void ForwardMessageViaTaskRunner(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::Callback<void(const std::string&)> callback,
+ const std::string& message) {
+ task_runner->PostTask(FROM_HERE,
+ base::BindOnce(std::move(callback), message));
+}
+
+std::string Format(const std::string& message,
+ base::Time timestamp,
+ base::Time start_time) {
+ int32_t interval_ms =
+ static_cast<int32_t>((timestamp - start_time).InMilliseconds());
+ return base::StringPrintf("[%03d:%03d] %s", interval_ms / 1000,
+ interval_ms % 1000, message.c_str());
+}
+
+std::string FormatMetaDataAsLogMessage(const WebRtcLogMetaDataMap& meta_data) {
+ std::string message;
+ for (auto& kv : meta_data) {
+ message += kv.first + ": " + kv.second + '\n';
+ }
+ // Remove last '\n'.
+ if (!message.empty())
+ message.erase(message.size() - 1, 1); // TODO(terelius): Use pop_back()
+ return message;
+}
+
+// For privacy reasons when logging IP addresses. The returned "sensitive
+// string" is for release builds a string with the end stripped away. Last
+// octet for IPv4 and last 80 bits (5 groups) for IPv6. String will be
+// "1.2.3.x" and "1.2.3::" respectively. For debug builds, the string is
+// not stripped.
+std::string IPAddressToSensitiveString(const net::IPAddress& address) {
+#if defined(NDEBUG)
+ std::string sensitive_address;
+ switch (address.size()) {
+ case net::IPAddress::kIPv4AddressSize: {
+ sensitive_address = address.ToString();
+ size_t find_pos = sensitive_address.rfind('.');
+ if (find_pos == std::string::npos)
+ return std::string();
+ sensitive_address.resize(find_pos);
+ sensitive_address += ".x";
+ break;
+ }
+ case net::IPAddress::kIPv6AddressSize: {
+ // TODO(grunell): Create a string of format "1:2:3:x:x:x:x:x" to clarify
+ // that the end has been stripped out.
+ net::IPAddressBytes stripped = address.bytes();
+ std::fill(stripped.begin() + 6, stripped.end(), 0);
+ sensitive_address = net::IPAddress(stripped).ToString();
+ break;
+ }
+ default: { break; }
+ }
+ return sensitive_address;
+#else
+ return address.ToString();
+#endif
+}
+
+} // namespace
+
+WebRtcTextLogHandler::WebRtcTextLogHandler(int render_process_id)
+ : render_process_id_(render_process_id), logging_state_(CLOSED) {}
+
+WebRtcTextLogHandler::~WebRtcTextLogHandler() {
+ // If the log isn't closed that means we haven't decremented the log count
+ // in the LogUploader.
+ DCHECK(logging_state_ == CLOSED || channel_is_closing_);
+ DCHECK(!log_buffer_);
+}
+
+WebRtcTextLogHandler::LoggingState WebRtcTextLogHandler::GetState() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return logging_state_;
+}
+
+bool WebRtcTextLogHandler::GetChannelIsClosing() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return channel_is_closing_;
+}
+
+void WebRtcTextLogHandler::SetMetaData(
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false, "The renderer is closing.");
+ return;
+ }
+
+ if (logging_state_ != CLOSED && logging_state_ != STARTED) {
+ FireGenericDoneCallback(callback, false,
+ "Meta data must be set before stop or upload.");
+ return;
+ }
+
+ if (logging_state_ == LoggingState::STARTED) {
+ std::string meta_data_message = FormatMetaDataAsLogMessage(*meta_data);
+ LogToCircularBuffer(meta_data_message);
+ }
+
+ if (!meta_data_) {
+ // If no meta data has been set previously, set it now.
+ meta_data_ = std::move(meta_data);
+ } else if (meta_data) {
+ // If there is existing meta data, update it and any new fields. The meta
+ // data is kept around to be uploaded separately from the log.
+ for (const auto& it : *meta_data)
+ (*meta_data_)[it.first] = it.second;
+ }
+
+ FireGenericDoneCallback(callback, true, "");
+}
+
+bool WebRtcTextLogHandler::StartLogging(WebRtcLogUploader* log_uploader,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false, "The renderer is closing.");
+ return false;
+ }
+
+ if (logging_state_ != CLOSED) {
+ FireGenericDoneCallback(callback, false, "A log is already open.");
+ return false;
+ }
+
+ if (!log_uploader->ApplyForStartLogging()) {
+ FireGenericDoneCallback(callback, false,
+ "Cannot start, maybe the maximum number of "
+ "simultaneuos logs has been reached.");
+ return false;
+ }
+
+ logging_state_ = STARTING;
+
+ DCHECK(!log_buffer_);
+ log_buffer_.reset(new WebRtcLogBuffer());
+ if (!meta_data_)
+ meta_data_.reset(new WebRtcLogMetaDataMap());
+
+ content::GetNetworkService()->GetNetworkList(
+ net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES,
+ base::BindOnce(&WebRtcTextLogHandler::OnGetNetworkInterfaceList,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+ return true;
+}
+
+void WebRtcTextLogHandler::StartDone(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false,
+ "Failed to start log. Renderer is closing.");
+ return;
+ }
+
+ DCHECK_EQ(STARTING, logging_state_);
+
+ base::UmaHistogramSparse("WebRtcTextLogging.Start", web_app_id_);
+
+ logging_started_time_ = base::Time::Now();
+ logging_state_ = STARTED;
+ FireGenericDoneCallback(callback, true, "");
+}
+
+bool WebRtcTextLogHandler::StopLogging(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false,
+ "Can't stop log. Renderer is closing.");
+ return false;
+ }
+
+ if (logging_state_ != STARTED) {
+ FireGenericDoneCallback(callback, false, "Logging not started.");
+ return false;
+ }
+
+ stop_callback_ = callback;
+ logging_state_ = STOPPING;
+
+ base::PostTask(FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&content::WebRtcLog::ClearLogMessageCallback,
+ render_process_id_));
+ return true;
+}
+
+void WebRtcTextLogHandler::StopDone() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(stop_callback_);
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(stop_callback_, false,
+ "Failed to stop log. Renderer is closing.");
+ return;
+ }
+
+ // If we aren't in STOPPING state, then there is a bug in the caller, since
+ // it is responsible for checking the state before making the call. If we do
+ // enter here in a bad state, then we can't use the stop_callback_ or we
+ // might fire the same callback multiple times.
+ DCHECK_EQ(STOPPING, logging_state_);
+ if (logging_state_ == STOPPING) {
+ logging_started_time_ = base::Time();
+ logging_state_ = STOPPED;
+ FireGenericDoneCallback(stop_callback_, true, "");
+ stop_callback_.Reset();
+ }
+}
+
+void WebRtcTextLogHandler::ChannelClosing() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logging_state_ == STARTING || logging_state_ == STARTED) {
+ base::PostTask(FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&content::WebRtcLog::ClearLogMessageCallback,
+ render_process_id_));
+ }
+ channel_is_closing_ = true;
+}
+
+void WebRtcTextLogHandler::DiscardLog() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(logging_state_ == STOPPED ||
+ (channel_is_closing_ && logging_state_ != CLOSED));
+
+ base::UmaHistogramSparse("WebRtcTextLogging.Discard", web_app_id_);
+
+ log_buffer_.reset();
+ meta_data_.reset();
+ logging_state_ = LoggingState::CLOSED;
+}
+
+void WebRtcTextLogHandler::ReleaseLog(
+ std::unique_ptr<WebRtcLogBuffer>* log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap>* meta_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(logging_state_ == STOPPED ||
+ (channel_is_closing_ && logging_state_ != CLOSED));
+ DCHECK(log_buffer_);
+ DCHECK(meta_data_);
+
+ // Checking log_buffer_ here due to seeing some crashes out in the wild.
+ // See crbug/699960 for more details.
+ // TODO(crbug/807547): Remove if condition.
+ if (log_buffer_) {
+ log_buffer_->SetComplete();
+ *log_buffer = std::move(log_buffer_);
+ }
+
+ if (meta_data_)
+ *meta_data = std::move(meta_data_);
+
+ logging_state_ = LoggingState::CLOSED;
+}
+
+void WebRtcTextLogHandler::LogToCircularBuffer(const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_NE(logging_state_, CLOSED);
+ if (log_buffer_) {
+ log_buffer_->Log(message);
+ }
+}
+
+void WebRtcTextLogHandler::LogMessage(const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logging_state_ == STARTED && !channel_is_closing_) {
+ LogToCircularBuffer(
+ Format(message, base::Time::Now(), logging_started_time_));
+ }
+}
+
+void WebRtcTextLogHandler::LogWebRtcLoggingMessage(
+ const chrome::mojom::WebRtcLoggingMessage* message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ LogToCircularBuffer(
+ Format(message->data, message->timestamp, logging_started_time_));
+}
+
+bool WebRtcTextLogHandler::ExpectLoggingStateStopped(
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logging_state_ != STOPPED) {
+ FireGenericDoneCallback(callback, false,
+ "Logging not stopped or no log open.");
+ return false;
+ }
+ return true;
+}
+
+void WebRtcTextLogHandler::FireGenericDoneCallback(
+ const GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (error_message.empty()) {
+ DCHECK(success);
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message));
+ return;
+ }
+
+ DCHECK(!success);
+
+ // Add current logging state to error message.
+ auto state_string = [&] {
+ switch (logging_state_) {
+ case LoggingState::CLOSED:
+ return "closed";
+ case LoggingState::STARTING:
+ return "starting";
+ case LoggingState::STARTED:
+ return "started";
+ case LoggingState::STOPPING:
+ return "stopping";
+ case LoggingState::STOPPED:
+ return "stopped";
+ }
+ NOTREACHED();
+ return "";
+ };
+
+ std::string error_message_with_state =
+ base::StrCat({error_message, ". State=", state_string(), ". Channel is ",
+ channel_is_closing_ ? "" : "not ", "closing."});
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message_with_state));
+}
+
+void WebRtcTextLogHandler::SetWebAppId(int web_app_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ web_app_id_ = web_app_id;
+}
+
+void WebRtcTextLogHandler::OnGetNetworkInterfaceList(
+ const GenericDoneCallback& callback,
+ const base::Optional<net::NetworkInterfaceList>& networks) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (logging_state_ != STARTING || channel_is_closing_) {
+ FireGenericDoneCallback(callback, false, "Logging cancelled.");
+ return;
+ }
+
+ // Log start time (current time). We don't use base/i18n/time_formatting.h
+ // here because we don't want the format of the current locale.
+ base::Time::Exploded now = {0};
+ base::Time::Now().LocalExplode(&now);
+ LogToCircularBuffer(base::StringPrintf("Start %d-%02d-%02d %02d:%02d:%02d",
+ now.year, now.month, now.day_of_month,
+ now.hour, now.minute, now.second));
+
+ // Write metadata if received before logging started.
+ if (meta_data_ && !meta_data_->empty()) {
+ std::string info = FormatMetaDataAsLogMessage(*meta_data_);
+ LogToCircularBuffer(info);
+ }
+
+ // Chrome version
+ LogToCircularBuffer("Chrome version: " + version_info::GetVersionNumber() +
+ " " + chrome::GetChannelName());
+
+ // OS
+ LogToCircularBuffer(base::SysInfo::OperatingSystemName() + " " +
+ base::SysInfo::OperatingSystemVersion() + " " +
+ base::SysInfo::OperatingSystemArchitecture());
+#if defined(OS_LINUX)
+ LogToCircularBuffer("Linux distribution: " + base::GetLinuxDistro());
+#endif
+
+ // CPU
+ base::CPU cpu;
+ LogToCircularBuffer(
+ "Cpu: " + NumberToString(cpu.family()) + "." +
+ NumberToString(cpu.model()) + "." + NumberToString(cpu.stepping()) +
+ ", x" + NumberToString(base::SysInfo::NumberOfProcessors()) + ", " +
+ NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB()) + "MB");
+ LogToCircularBuffer("Cpu brand: " + cpu.cpu_brand());
+
+ // Computer model
+ std::string computer_model = "Not available";
+#if defined(OS_MACOSX)
+ computer_model = base::mac::GetModelIdentifier();
+#elif defined(OS_CHROMEOS)
+ chromeos::system::StatisticsProvider::GetInstance()->GetMachineStatistic(
+ chromeos::system::kHardwareClassKey, &computer_model);
+#endif
+ LogToCircularBuffer("Computer model: " + computer_model);
+
+ // GPU
+ gpu::GPUInfo gpu_info = content::GpuDataManager::GetInstance()->GetGPUInfo();
+ const gpu::GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu();
+ LogToCircularBuffer(
+ "Gpu: machine-model-name=" + gpu_info.machine_model_name +
+ ", machine-model-version=" + gpu_info.machine_model_version +
+ ", vendor-id=" + base::NumberToString(active_gpu.vendor_id) +
+ ", device-id=" + base::NumberToString(active_gpu.device_id) +
+ ", driver-vendor=" + active_gpu.driver_vendor +
+ ", driver-version=" + active_gpu.driver_version);
+ LogToCircularBuffer("OpenGL: gl-vendor=" + gpu_info.gl_vendor +
+ ", gl-renderer=" + gpu_info.gl_renderer +
+ ", gl-version=" + gpu_info.gl_version);
+
+ // AudioService features
+ auto enabled_or_disabled_feature_string = [](auto& feature) {
+ return base::FeatureList::IsEnabled(feature) ? "enabled" : "disabled";
+ };
+ auto enabled_or_disabled_bool_string = [](bool value) {
+ return value ? "enabled" : "disabled";
+ };
+ LogToCircularBuffer(base::StrCat(
+ {"AudioService: AudioStreams=",
+ enabled_or_disabled_feature_string(features::kAudioServiceAudioStreams),
+ ", OutOfProcess=",
+ enabled_or_disabled_feature_string(features::kAudioServiceOutOfProcess),
+ ", LaunchOnStartup=",
+ enabled_or_disabled_feature_string(
+ features::kAudioServiceLaunchOnStartup),
+ ", Sandbox=",
+ enabled_or_disabled_bool_string(
+ service_manager::IsAudioSandboxEnabled()),
+ ", ApmInAudioService=",
+ enabled_or_disabled_bool_string(
+ media::IsWebRtcApmInAudioServiceEnabled())}));
+
+ // Audio manager
+ // On some platforms, this can vary depending on build flags and failure
+ // fallbacks. On Linux for example, we fallback on ALSA if PulseAudio fails to
+ // initialize. TODO(http://crbug/843202): access AudioManager name via Audio
+ // service interface.
+ media::AudioManager* audio_manager = media::AudioManager::Get();
+ LogToCircularBuffer(base::StringPrintf(
+ "Audio manager: %s",
+ audio_manager ? audio_manager->GetName() : "Out of process"));
+
+ // Network interfaces
+ const net::NetworkInterfaceList empty_network_list;
+ const net::NetworkInterfaceList& network_list =
+ networks.has_value() ? *networks : empty_network_list;
+ LogToCircularBuffer("Discovered " +
+ base::NumberToString(network_list.size()) +
+ " network interfaces:");
+ for (const auto& network : network_list) {
+ LogToCircularBuffer(
+ "Name: " + network.friendly_name + ", Address: " +
+ IPAddressToSensitiveString(network.address) + ", Type: " +
+ net::NetworkChangeNotifier::ConnectionTypeToString(network.type));
+ }
+
+ StartDone(callback);
+
+ // After the above data has been written, tell the browser to enable logging.
+ // TODO(terelius): Once we have moved over to Mojo, we could tell the
+ // renderer to start logging here, but for the time being
+ // WebRtcLoggingHandlerHost::StartLogging will be responsible for sending
+ // that IPC message.
+
+ // TODO(darin): Change SetLogMessageCallback to run on the UI thread.
+
+ auto log_message_callback = base::Bind(
+ &ForwardMessageViaTaskRunner, base::SequencedTaskRunnerHandle::Get(),
+ base::Bind(&WebRtcTextLogHandler::LogMessage,
+ weak_factory_.GetWeakPtr()));
+ base::PostTask(
+ FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&content::WebRtcLog::SetLogMessageCallback,
+ render_process_id_, std::move(log_message_callback)));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h
new file mode 100644
index 00000000000..02c5f50cc32
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h
@@ -0,0 +1,149 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_TEXT_LOG_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_TEXT_LOG_HANDLER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+#include "net/base/network_interfaces.h"
+
+namespace chrome {
+namespace mojom {
+class WebRtcLoggingMessage;
+} // namespace mojom
+} // namespace chrome
+
+class WebRtcLogBuffer;
+
+class WebRtcTextLogHandler {
+ public:
+ // States used for protecting from function calls made at non-allowed points
+ // in time. For example, StartLogging() is only allowed in CLOSED state.
+ // See also comment on |channel_is_closing_| below.
+ // Transitions: SetMetaData(): CLOSED -> CLOSED, or
+ // STARTED -> STARTED
+ // StartLogging(): CLOSED -> STARTING.
+ // StartDone(): STARTING -> STARTED.
+ // StopLogging(): STARTED -> STOPPING.
+ // StopDone(): STOPPING -> STOPPED.
+ // DiscardLog(): STOPPED -> CLOSED.
+ // ReleaseLog(): STOPPED -> CLOSED.
+ enum LoggingState {
+ CLOSED, // Logging not started, no log in memory.
+ STARTING, // Start logging is in progress.
+ STARTED, // Logging started.
+ STOPPING, // Stop logging is in progress.
+ STOPPED, // Logging has been stopped, log still open in memory.
+ };
+
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+
+ explicit WebRtcTextLogHandler(int render_process_id);
+ ~WebRtcTextLogHandler();
+
+ // Returns the current state of the log.
+ LoggingState GetState() const;
+
+ // Returns true if channel is closing.
+ bool GetChannelIsClosing() const;
+
+ // Sets meta data for log uploading. Merged with any already set meta data.
+ // Values for existing keys are overwritten. The meta data already set at log
+ // start is written to the beginning of the log. Meta data set after log start
+ // is written to the log at that time.
+ void SetMetaData(std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback);
+
+ // Opens a log and starts logging if allowed by the LogUploader.
+ // Returns false if logging could not be started.
+ bool StartLogging(WebRtcLogUploader* log_uploader,
+ const GenericDoneCallback& callback);
+
+ // Stops logging. Log will remain open until UploadLog or DiscardLog is
+ // called.
+ bool StopLogging(const GenericDoneCallback& callback);
+
+ // Called by the WebRtcLoggingHandlerHost when logging has stopped in the
+ // renderer. Should only be called in response to a
+ // WebRtcLoggingMsg_LoggingStopped IPC message.
+ void StopDone();
+
+ // Signals that the renderer is closing, which de facto stops logging but
+ // keeps the log in memory.
+ // Can be called in any state except CLOSED.
+ void ChannelClosing();
+
+ // Discards a stopped log.
+ void DiscardLog();
+
+ // Releases a stopped log to the caller.
+ void ReleaseLog(std::unique_ptr<WebRtcLogBuffer>* log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap>* meta_data);
+
+ // Adds a message to the log.
+ void LogMessage(const std::string& message);
+
+ // Adds a message to the log.
+ void LogWebRtcLoggingMessage(
+ const chrome::mojom::WebRtcLoggingMessage* message);
+
+ // Returns true if the logging state is CLOSED and fires an the callback
+ // with an error message otherwise.
+ bool ExpectLoggingStateStopped(const GenericDoneCallback& callback);
+
+ void FireGenericDoneCallback(const GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message);
+
+ void SetWebAppId(int web_app_id);
+
+ private:
+ void StartDone(const GenericDoneCallback& callback);
+
+ void LogToCircularBuffer(const std::string& message);
+
+ void OnGetNetworkInterfaceList(
+ const GenericDoneCallback& callback,
+ const base::Optional<net::NetworkInterfaceList>& networks);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // The render process ID this object belongs to.
+ const int render_process_id_;
+
+ // Should be created by StartLogging().
+ std::unique_ptr<WebRtcLogBuffer> log_buffer_;
+
+ // Should be created by StartLogging().
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data_;
+
+ GenericDoneCallback stop_callback_;
+ LoggingState logging_state_;
+
+ // True if renderer is closing. The log (if there is one) can still be
+ // released or discarded (i.e. closed). No new logs can be created. The only
+ // state change possible when channel is closing is from any state to CLOSED.
+ bool channel_is_closing_ = false;
+
+ // The system time in ms when logging is started. Reset when logging_state_
+ // changes to STOPPED.
+ base::Time logging_started_time_;
+
+ // Web app id used for statistics. See
+ // |WebRtcLoggingHandlerHost::web_app_id_|.
+ int web_app_id_ = 0;
+
+ base::WeakPtrFactory<WebRtcTextLogHandler> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcTextLogHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_TEXT_LOG_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
new file mode 100644
index 00000000000..1b90baade1a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
@@ -0,0 +1,497 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/json/json_reader.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/trace_event_analyzer.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/test/base/tracing.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "third_party/blink/public/common/features.h"
+#include "ui/gl/gl_switches.h"
+
+using trace_analyzer::TraceEvent;
+using trace_analyzer::TraceEventVector;
+using trace_analyzer::Query;
+
+namespace {
+
+// Trace events.
+static const char kStartRenderEventName[] =
+ "RemoteVideoSourceDelegate::RenderFrame";
+static const char kEnqueueFrameEventName[] =
+ "WebMediaPlayerMSCompositor::EnqueueFrame";
+static const char kSetFrameEventName[] =
+ "WebMediaPlayerMSCompositor::SetCurrentFrame";
+static const char kGetFrameEventName[] =
+ "WebMediaPlayerMSCompositor::GetCurrentFrame";
+static const char kVideoResourceEventName[] =
+ "VideoResourceUpdater::ObtainFrameResources";
+static const char kVsyncEventName[] = "Display::DrawAndSwap";
+
+// VideoFrameSubmitter dumps the delay from the handover of a decoded remote
+// VideoFrame from webrtc to the moment the OS acknowledges the swap buffers.
+static const char kVideoFrameSubmitterEventName[] = "VideoFrameSubmitter";
+
+static const char kEventMatchKey[] = "Timestamp";
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_video_display_perf_test.html";
+
+constexpr char kMetricPrefixVideoDisplayPerf[] = "WebRtcVideoDisplayPerf.";
+constexpr char kMetricSkippedFramesPercent[] = "skipped_frames";
+constexpr char kMetricPassingToRenderAlgoLatencyUs[] =
+ "passing_to_render_algorithm_latency";
+constexpr char kMetricRenderAlgoLatencyUs[] = "render_algorithm_latency";
+constexpr char kMetricCompositorPickingFrameLatencyUs[] =
+ "compositor_picking_frame_latency";
+constexpr char kMetricCompositorResourcePreparationLatencyUs[] =
+ "compositor_resource_preparation_latency";
+constexpr char kMetricVsyncLatencyUs[] = "vsync_latency";
+constexpr char kMetricTotalControlledLatencyUs[] = "total_controlled_latency";
+constexpr char kMetricTotalLatencyUs[] = "total_latency";
+constexpr char kMetricPostDecodeToRasterLatencyUs[] =
+ "post_decode_to_raster_latency";
+constexpr char kMetricWebRtcDecodeLatencyUs[] = "webrtc_decode_latency";
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoDisplayPerf, story);
+ reporter.RegisterImportantMetric(kMetricSkippedFramesPercent, "percent");
+ reporter.RegisterImportantMetric(kMetricPassingToRenderAlgoLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricRenderAlgoLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricCompositorPickingFrameLatencyUs,
+ "us");
+ reporter.RegisterImportantMetric(
+ kMetricCompositorResourcePreparationLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricVsyncLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricTotalControlledLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricTotalLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricPostDecodeToRasterLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricWebRtcDecodeLatencyUs, "us");
+ return reporter;
+}
+
+struct VideoDisplayPerfTestConfig {
+ int width;
+ int height;
+ int fps;
+ bool disable_render_smoothness_algorithm;
+};
+
+std::string VectorToString(const std::vector<double>& values) {
+ std::string ret = "";
+ for (double val : values) {
+ ret += base::StringPrintf("%.0lf,", val);
+ }
+ // Strip of trailing comma.
+ return ret.substr(0, ret.length() - 1);
+}
+
+void FindEvents(trace_analyzer::TraceAnalyzer* analyzer,
+ const std::string& event_name,
+ const Query& base_query,
+ TraceEventVector* events) {
+ Query query = Query::EventNameIs(event_name) && base_query;
+ analyzer->FindEvents(query, events);
+}
+
+void AssociateEvents(trace_analyzer::TraceAnalyzer* analyzer,
+ const std::vector<std::string>& event_names,
+ const std::string& match_string,
+ const Query& base_query) {
+ for (size_t i = 0; i < event_names.size() - 1; ++i) {
+ Query begin = Query::EventNameIs(event_names[i]);
+ Query end = Query::EventNameIs(event_names[i + 1]);
+ Query match(Query::EventArg(match_string) == Query::OtherArg(match_string));
+ analyzer->AssociateEvents(begin, end, base_query && match);
+ }
+}
+
+content::WebContents* OpenWebrtcInternalsTab(Browser* browser) {
+ chrome::AddTabAt(browser, GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser, GURL("chrome://webrtc-internals"));
+ return browser->tab_strip_model()->GetActiveWebContents();
+}
+
+std::vector<double> ParseGoogMaxDecodeFromWebrtcInternalsTab(
+ const std::string& webrtc_internals_stats_json) {
+ std::vector<double> goog_decode_ms;
+
+ std::unique_ptr<base::Value> parsed_json =
+ base::JSONReader::ReadDeprecated(webrtc_internals_stats_json);
+ base::DictionaryValue* dictionary = nullptr;
+ if (!parsed_json.get() || !parsed_json->GetAsDictionary(&dictionary))
+ return goog_decode_ms;
+ ignore_result(parsed_json.release());
+
+ // |dictionary| should have exactly two entries, one per ssrc.
+ if (!dictionary || dictionary->size() != 2u)
+ return goog_decode_ms;
+
+ // Only a given |dictionary| entry will have a "stats" entry that has a key
+ // that ends with "recv-googMaxDecodeMs" inside (it will start with the ssrc
+ // id, but we don't care about that). Then collect the string of "values" out
+ // of that key and convert those into the |goog_decode_ms| vector of doubles.
+ for (const auto& dictionary_entry : *dictionary) {
+ for (const auto& ssrc_entry : dictionary_entry.second->DictItems()) {
+ if (ssrc_entry.first != "stats")
+ continue;
+
+ for (const auto& stat_entry : ssrc_entry.second.DictItems()) {
+ if (!base::EndsWith(stat_entry.first, "recv-googMaxDecodeMs",
+ base::CompareCase::SENSITIVE)) {
+ continue;
+ }
+ base::Value* values_entry = stat_entry.second.FindKey({"values"});
+ if (!values_entry)
+ continue;
+ base::StringTokenizer values_tokenizer(values_entry->GetString(),
+ "[,]");
+ while (values_tokenizer.GetNext()) {
+ if (values_tokenizer.token_is_delim())
+ continue;
+ goog_decode_ms.push_back(atof(values_tokenizer.token().c_str()) *
+ base::Time::kMicrosecondsPerMillisecond);
+ }
+ }
+ }
+ }
+ return goog_decode_ms;
+}
+
+} // anonymous namespace
+
+// Tests the performance of Chrome displaying remote video.
+//
+// This test creates a WebRTC peer connection between two tabs and measures the
+// trace events listed in the beginning of this file on the tab receiving
+// remote video. In order to cut down from the encode cost, the tab receiving
+// remote video does not send any video to its peer.
+//
+// This test traces certain categories for a period of time. It follows the
+// lifetime of a single video frame by synchronizing on the timestamps values
+// attached to trace events. Then, it calculates the duration and related stats.
+class WebRtcVideoDisplayPerfBrowserTest
+ : public WebRtcTestBase,
+ public testing::WithParamInterface<
+ std::tuple<gfx::Size /* resolution */,
+ int /* fps */,
+ bool /* disable_render_smoothness_algorithm */>> {
+ public:
+ WebRtcVideoDisplayPerfBrowserTest() {
+ const auto& params = GetParam();
+ const gfx::Size& resolution = std::get<0>(params);
+ test_config_ = {resolution.width(), resolution.height(),
+ std::get<1>(params), std::get<2>(params)};
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+ command_line->AppendSwitchASCII(
+ switches::kUseFakeDeviceForMediaStream,
+ base::StringPrintf("fps=%d", test_config_.fps));
+ if (test_config_.disable_render_smoothness_algorithm)
+ command_line->AppendSwitch(switches::kDisableRTCSmoothnessAlgorithm);
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ void TestVideoDisplayPerf(const std::string& video_codec) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ // chrome:webrtc-internals doesn't start tracing anything until the
+ // connection(s) are up.
+ content::WebContents* webrtc_internals_tab =
+ OpenWebrtcInternalsTab(browser());
+ EXPECT_TRUE(content::ExecuteScript(
+ webrtc_internals_tab,
+ "currentGetStatsMethod = OPTION_GETSTATS_LEGACY"));
+
+ content::WebContents* left_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ base::StringPrintf(
+ "{audio: true, video: {mandatory: {minWidth: %d, maxWidth: %d, "
+ "minHeight: %d, maxHeight: %d}}}",
+ test_config_.width, test_config_.width, test_config_.height,
+ test_config_.height));
+ content::WebContents* right_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ "{audio: true, video: false}");
+ const int process_id =
+ right_tab->GetRenderViewHost()->GetProcess()->GetProcess().Pid();
+
+ const std::string disable_cpu_adaptation_constraint(
+ "{'optional': [{'googCpuOveruseDetection': false}]}");
+ SetupPeerconnectionWithConstraintsAndLocalStream(
+ left_tab, disable_cpu_adaptation_constraint);
+ SetupPeerconnectionWithConstraintsAndLocalStream(
+ right_tab, disable_cpu_adaptation_constraint);
+
+ if (!video_codec.empty()) {
+ constexpr bool kPreferHwVideoCodec = true;
+ SetDefaultVideoCodec(left_tab, video_codec, kPreferHwVideoCodec);
+ SetDefaultVideoCodec(right_tab, video_codec, kPreferHwVideoCodec);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ StartDetectingVideo(right_tab, "remote-view");
+ WaitForVideoToPlay(right_tab);
+ // Run the connection a bit to ramp up.
+ test::SleepInJavascript(left_tab, 10000);
+
+ ASSERT_TRUE(tracing::BeginTracing("media,viz,webrtc"));
+ // Run the connection for 5 seconds to collect metrics.
+ test::SleepInJavascript(left_tab, 5000);
+
+ const std::string webrtc_internals_stats_json = ExecuteJavascript(
+ "window.domAutomationController.send("
+ " JSON.stringify(peerConnectionDataStore));",
+ webrtc_internals_tab);
+ webrtc_decode_latencies_ =
+ ParseGoogMaxDecodeFromWebrtcInternalsTab(webrtc_internals_stats_json);
+ chrome::CloseWebContents(browser(), webrtc_internals_tab, false);
+
+ std::string json_events;
+ ASSERT_TRUE(tracing::EndTracing(&json_events));
+ std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer(
+ trace_analyzer::TraceAnalyzer::Create(json_events));
+ analyzer->AssociateAsyncBeginEndEvents();
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+ chrome::CloseWebContents(browser(), left_tab, false);
+ chrome::CloseWebContents(browser(), right_tab, false);
+
+ ASSERT_TRUE(CalculatePerfResults(analyzer.get(), process_id));
+ PrintResults(video_codec);
+ }
+
+ private:
+ bool CalculatePerfResults(trace_analyzer::TraceAnalyzer* analyzer,
+ int render_process_id) {
+ Query match_process_id = Query::EventPidIs(render_process_id);
+ const std::vector<std::string> chain_of_events = {
+ kStartRenderEventName, kEnqueueFrameEventName, kSetFrameEventName,
+ kGetFrameEventName, kVideoResourceEventName};
+ AssociateEvents(analyzer, chain_of_events,
+ kEventMatchKey, match_process_id);
+
+ TraceEventVector start_render_events;
+ FindEvents(analyzer, kStartRenderEventName, match_process_id,
+ &start_render_events);
+ if (start_render_events.empty())
+ return false;
+
+ // We are only interested in vsync events coming after the first render
+ // event. Earlier ones are already missed.
+ Query after_first_render_event =
+ Query::EventTime() >
+ Query::Double(start_render_events.front()->timestamp);
+ TraceEventVector vsync_events;
+ FindEvents(analyzer, kVsyncEventName, after_first_render_event,
+ &vsync_events);
+ if (vsync_events.empty())
+ return false;
+
+ size_t found_vsync_index = 0;
+ size_t skipped_frame_count = 0;
+ for (const auto* event : start_render_events) {
+ const double start = event->timestamp;
+
+ const TraceEvent* enqueue_frame_event = event->other_event;
+ if (!enqueue_frame_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double enqueue_frame_duration =
+ enqueue_frame_event->timestamp - start;
+
+ const TraceEvent* set_frame_event = enqueue_frame_event->other_event;
+ if (!set_frame_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double set_frame_duration =
+ set_frame_event->timestamp - enqueue_frame_event->timestamp;
+
+ const TraceEvent* get_frame_event = set_frame_event->other_event;
+ if (!get_frame_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double get_frame_duration =
+ get_frame_event->timestamp - set_frame_event->timestamp;
+
+ const TraceEvent* video_resource_event = get_frame_event->other_event;
+ if (!video_resource_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double resource_ready_duration =
+ video_resource_event->timestamp - get_frame_event->timestamp;
+
+ // We try to find the closest vsync event after video resource is ready.
+ const bool found_vsync = FindFirstOf(
+ vsync_events,
+ Query::EventTime() > Query::Double(video_resource_event->timestamp +
+ video_resource_event->duration),
+ found_vsync_index, &found_vsync_index);
+ if (!found_vsync) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double vsync_duration = vsync_events[found_vsync_index]->timestamp -
+ video_resource_event->timestamp;
+ const double total_duration =
+ vsync_events[found_vsync_index]->timestamp - start;
+
+ enqueue_frame_durations_.push_back(enqueue_frame_duration);
+ set_frame_durations_.push_back(set_frame_duration);
+ get_frame_durations_.push_back(get_frame_duration);
+ resource_ready_durations_.push_back(resource_ready_duration);
+ vsync_durations_.push_back(vsync_duration);
+ total_controlled_durations_.push_back(total_duration -
+ set_frame_duration);
+ total_durations_.push_back(total_duration);
+ }
+
+ if (start_render_events.size() == skipped_frame_count)
+ return false;
+
+ // Calculate the percentage by dividing by the number of frames received.
+ skipped_frame_percentage_ =
+ 100.0 * skipped_frame_count / start_render_events.size();
+
+ // |kVideoFrameSubmitterEventName| is in itself an ASYNC latency measurement
+ // from the point where the remote video decode is available (i.e.
+ // kStartRenderEventName) until the platform-dependent swap buffers, so by
+ // definition is larger than the |total_duration|.
+ TraceEventVector video_frame_submitter_events;
+ analyzer->FindEvents(Query::MatchAsyncBeginWithNext() &&
+ Query::EventNameIs(kVideoFrameSubmitterEventName),
+ &video_frame_submitter_events);
+ for (const auto* event : video_frame_submitter_events) {
+ // kVideoFrameSubmitterEventName is divided into a BEGIN, a PAST and an
+ // END steps. AssociateAsyncBeginEndEvents paired BEGIN with PAST, but we
+ // have to get to the END. Note that if there's no intermediate PAST, it
+ // means this wasn't a remote feed VideoFrame, we should not have those in
+ // this test. If there's no END, then tracing was cut short.
+ if (!event->has_other_event() ||
+ event->other_event->phase != TRACE_EVENT_PHASE_ASYNC_STEP_PAST ||
+ !event->other_event->has_other_event()) {
+ continue;
+ }
+ const auto begin = event->timestamp;
+ const auto end = event->other_event->other_event->timestamp;
+ video_frame_submmitter_latencies_.push_back(end - begin);
+ }
+
+ return true;
+ }
+
+ void PrintResults(const std::string& video_codec) {
+ std::string smoothness_indicator =
+ test_config_.disable_render_smoothness_algorithm ? "_DisableSmoothness"
+ : "";
+ std::string story = base::StringPrintf(
+ "%s_%dp%df%s", video_codec.c_str(), test_config_.height,
+ test_config_.fps, smoothness_indicator.c_str());
+ auto reporter = SetUpReporter(story);
+ reporter.AddResult(kMetricSkippedFramesPercent,
+ base::StringPrintf("%.2lf", skipped_frame_percentage_));
+ // We identify intervals in a way that can help us easily bisect the source
+ // of added latency in case of a regression. From these intervals, "Render
+ // Algorithm" can take random amount of times based on the vsync cycle it is
+ // closest to. Therefore, "Total Controlled Latency" refers to the total
+ // times without that section for semi-consistent results.
+ reporter.AddResultList(kMetricPassingToRenderAlgoLatencyUs,
+ VectorToString(enqueue_frame_durations_));
+ reporter.AddResultList(kMetricRenderAlgoLatencyUs,
+ VectorToString(set_frame_durations_));
+ reporter.AddResultList(kMetricCompositorPickingFrameLatencyUs,
+ VectorToString(get_frame_durations_));
+ reporter.AddResultList(kMetricCompositorResourcePreparationLatencyUs,
+ VectorToString(resource_ready_durations_));
+ reporter.AddResultList(kMetricVsyncLatencyUs,
+ VectorToString(vsync_durations_));
+ reporter.AddResultList(kMetricTotalControlledLatencyUs,
+ VectorToString(total_controlled_durations_));
+ reporter.AddResultList(kMetricTotalLatencyUs,
+ VectorToString(total_durations_));
+
+ reporter.AddResultList(kMetricPostDecodeToRasterLatencyUs,
+ VectorToString(video_frame_submmitter_latencies_));
+ reporter.AddResultList(kMetricWebRtcDecodeLatencyUs,
+ VectorToString(webrtc_decode_latencies_));
+ }
+
+ VideoDisplayPerfTestConfig test_config_;
+ // Containers for test results.
+ double skipped_frame_percentage_ = 0;
+ std::vector<double> enqueue_frame_durations_;
+ std::vector<double> set_frame_durations_;
+ std::vector<double> get_frame_durations_;
+ std::vector<double> resource_ready_durations_;
+ std::vector<double> vsync_durations_;
+ std::vector<double> total_controlled_durations_;
+ std::vector<double> total_durations_;
+
+ // These two put together represent the whole delay from encoded video frames
+ // to OS swap buffers call (or callback, depending on the platform).
+ std::vector<double> video_frame_submmitter_latencies_;
+ std::vector<double> webrtc_decode_latencies_;
+};
+
+// TODO(https://crbug.com/993020): Fix flakes on Windows bots.
+#if defined(OS_WIN)
+#define MAYBE_WebRtcVideoDisplayPerfBrowserTests \
+ DISABLED_WebRtcVideoDisplayPerfBrowserTests
+#else
+#define MAYBE_WebRtcVideoDisplayPerfBrowserTests \
+ WebRtcVideoDisplayPerfBrowserTests
+#endif
+INSTANTIATE_TEST_SUITE_P(MAYBE_WebRtcVideoDisplayPerfBrowserTests,
+ WebRtcVideoDisplayPerfBrowserTest,
+ testing::Combine(testing::Values(gfx::Size(1280, 720),
+ gfx::Size(1920,
+ 1080)),
+ testing::Values(30, 60),
+ testing::Bool()));
+
+IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
+ MANUAL_TestVideoDisplayPerfVP9) {
+ TestVideoDisplayPerf("VP9");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
+ MANUAL_TestVideoDisplayPerfH264) {
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcVideoDisplayPerfBrowserTest.MANUAL_"
+ "TestVideoDisplayPerfH264 "
+ "(test \"OK\")";
+ return;
+ }
+ TestVideoDisplayPerf("H264");
+}
+#endif // BUILDFLAG(RTC_USE_H264)
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc
new file mode 100644
index 00000000000..a8eebc633b3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc
@@ -0,0 +1,163 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "ui/gl/gl_switches.h"
+
+namespace {
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_video_display_perf_test.html";
+static const char kInboundRtp[] = "inbound-rtp";
+static const char kOutboundRtp[] = "outbound-rtp";
+
+constexpr int kBitsPerByte = 8;
+
+constexpr char kMetricPrefixHighBitrate[] = "WebRtcHighBitrateVideo.";
+constexpr char kMetricSendRateBitsPerS[] = "send_rate";
+constexpr char kMetricReceiveRateBitsPerS[] = "receive_rate";
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixHighBitrate, story);
+ reporter.RegisterFyiMetric(kMetricSendRateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricReceiveRateBitsPerS, "bits/s");
+ return reporter;
+}
+
+// Sums up "RTC[In/Out]boundRTPStreamStats.bytes_[received/sent]" values.
+double GetTotalRTPStreamBytes(content::TestStatsReportDictionary* report,
+ const char* type,
+ const char* media_type) {
+ DCHECK(type == kInboundRtp || type == kOutboundRtp);
+ const char* bytes_name =
+ (type == kInboundRtp) ? "bytesReceived" : "bytesSent";
+ double total_bytes = 0.0;
+ report->ForEach([&type, &bytes_name, &media_type,
+ &total_bytes](const content::TestStatsDictionary& stats) {
+ if (stats.GetString("type") == type &&
+ stats.GetString("mediaType") == media_type) {
+ total_bytes += stats.GetNumber(bytes_name);
+ }
+ });
+ return total_bytes;
+}
+
+double GetVideoBytesSent(content::TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kOutboundRtp, "video");
+}
+
+double GetVideoBytesReceived(content::TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kInboundRtp, "video");
+}
+
+} // anonymous namespace
+
+namespace content {
+
+// Tests the performance of WebRTC peer connection with high bitrate
+//
+// This test creates a WebRTC peer connection between two tabs and sets a very
+// high target bitrate to observe any perf regressions/improvements for such
+// cases. In order to achieve this, we use a fake codec that creates a dummy
+// output for the given bitrate.
+class WebRtcVideoHighBitrateBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kUseFakeCodecForPeerConnection);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ protected:
+ void SetDefaultVideoTargetBitrate(content::WebContents* tab,
+ int bits_per_second) {
+ EXPECT_EQ("ok", ExecuteJavascript(
+ base::StringPrintf("setDefaultVideoTargetBitrate(%d)",
+ bits_per_second),
+ tab));
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(WebRtcVideoHighBitrateBrowserTest,
+ MANUAL_HighBitrateEncodeDecode) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 30)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 30000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 30)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 30000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ content::WebContents* left_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ "{audio: true, video: true}");
+ content::WebContents* right_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ "{audio: true, video: false}");
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+ const int target_bits_per_second = 80000;
+ SetDefaultVideoTargetBitrate(left_tab, target_bits_per_second);
+ SetDefaultVideoTargetBitrate(right_tab, target_bits_per_second);
+ NegotiateCall(left_tab, right_tab);
+
+ // Run the connection a bit to ramp up.
+ test::SleepInJavascript(left_tab, 10000);
+
+ scoped_refptr<TestStatsReportDictionary> sender_report =
+ GetStatsReportDictionary(left_tab);
+ const double video_bytes_sent_before = GetVideoBytesSent(sender_report.get());
+ scoped_refptr<TestStatsReportDictionary> receiver_report =
+ GetStatsReportDictionary(right_tab);
+ const double video_bytes_received_before =
+ GetVideoBytesReceived(receiver_report.get());
+
+ // Collect stats.
+ const double duration_in_seconds = 5.0;
+ test::SleepInJavascript(
+ left_tab, duration_in_seconds * base::Time::kMillisecondsPerSecond);
+
+ sender_report = GetStatsReportDictionary(left_tab);
+ const double video_bytes_sent_after = GetVideoBytesSent(sender_report.get());
+ receiver_report = GetStatsReportDictionary(right_tab);
+ const double video_bytes_received_after =
+ GetVideoBytesReceived(receiver_report.get());
+
+ const double video_send_rate =
+ (video_bytes_sent_after - video_bytes_sent_before) / duration_in_seconds;
+ const double video_receive_rate =
+ (video_bytes_received_after - video_bytes_received_before) /
+ duration_in_seconds;
+
+ auto reporter = SetUpReporter("baseline_story");
+ reporter.AddResult(kMetricSendRateBitsPerS, video_send_rate * kBitsPerByte);
+ reporter.AddResult(kMetricReceiveRateBitsPerS,
+ video_receive_rate * kBitsPerByte);
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+}
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc
new file mode 100644
index 00000000000..e09ff308c75
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc
@@ -0,0 +1,367 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/infobars/core/infobar.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/python_utils.h"
+#include "testing/perf/perf_test.h"
+#include "third_party/blink/public/common/features.h"
+#include "ui/gl/gl_switches.h"
+
+namespace {
+std::string MakeLabel(const char* test_name, const std::string& video_codec) {
+ std::string codec_label = video_codec.empty() ? "" : "_" + video_codec;
+ return base::StringPrintf("%s%s", test_name, codec_label.c_str());
+}
+} // namespace
+
+static const base::FilePath::CharType kFrameAnalyzerExecutable[] =
+#if defined(OS_WIN)
+ FILE_PATH_LITERAL("frame_analyzer.exe");
+#else
+ FILE_PATH_LITERAL("frame_analyzer");
+#endif
+
+static const base::FilePath::CharType kCapturedYuvFileName[] =
+ FILE_PATH_LITERAL("captured_video.yuv");
+static const base::FilePath::CharType kCapturedWebmFileName[] =
+ FILE_PATH_LITERAL("captured_video.webm");
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+static const char kCapturingWebrtcHtmlPage[] =
+ "/webrtc/webrtc_video_quality_test.html";
+
+static const struct VideoQualityTestConfig {
+ const char* test_name;
+ int width;
+ int height;
+ const base::FilePath::CharType* reference_video;
+ const char* constraints;
+} kVideoConfigurations[] = {
+ { "360p", 640, 360,
+ test::kReferenceFileName360p,
+ WebRtcTestBase::kAudioVideoCallConstraints360p },
+ { "720p", 1280, 720,
+ test::kReferenceFileName720p,
+ WebRtcTestBase::kAudioVideoCallConstraints720p },
+};
+
+// Test the video quality of the WebRTC output.
+//
+// Prerequisites: This test case must run on a machine with a chrome playing
+// the video from the reference files located in GetReferenceFilesDir().
+// The file kReferenceY4mFileName.kY4mFileExtension is played using a
+// FileVideoCaptureDevice and its sibling with kYuvFileExtension is used for
+// comparison.
+//
+// You must also compile the frame_analyzer target before you run this
+// test to get all the tools built.
+//
+// The test runs several custom binaries - rgba_to_i420 converter and
+// frame_analyzer. Both tools can be found under third_party/webrtc/rtc_tools.
+// The test also runs a stand alone Python implementation of a WebSocket server
+// (pywebsocket) and a barcode_decoder script.
+class WebRtcVideoQualityBrowserTest : public WebRtcTestBase,
+ public testing::WithParamInterface<VideoQualityTestConfig> {
+ public:
+ WebRtcVideoQualityBrowserTest()
+ : environment_(base::Environment::Create()) {
+ test_config_ = GetParam();
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+
+ ASSERT_TRUE(temp_working_dir_.CreateUniqueTempDir());
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Set up the command line option with the expected file name. We will check
+ // its existence in HasAllRequiredResources().
+ webrtc_reference_video_y4m_ = test::GetReferenceFilesDir()
+ .Append(test_config_.reference_video)
+ .AddExtension(test::kY4mFileExtension);
+ command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture,
+ webrtc_reference_video_y4m_);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // The video playback will not work without a GPU, so force its use here.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ // Writes the captured video to a webm file.
+ void WriteCapturedWebmVideo(content::WebContents* capturing_tab,
+ const base::FilePath& webm_video_filename) {
+ std::string base64_encoded_video =
+ ExecuteJavascript("getRecordedVideoAsBase64()", capturing_tab);
+ std::string recorded_video;
+ ASSERT_TRUE(base::Base64Decode(base64_encoded_video, &recorded_video));
+ base::File video_file(webm_video_filename,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ size_t written =
+ video_file.Write(0, recorded_video.c_str(), recorded_video.length());
+ ASSERT_EQ(recorded_video.length(), written);
+ }
+
+ // Runs ffmpeg on the captured webm video and writes it to a yuv video file.
+ bool RunWebmToI420Converter(const base::FilePath& webm_video_filename,
+ const base::FilePath& yuv_video_filename,
+ const int width,
+ const int height) {
+ base::FilePath path_to_ffmpeg = test::GetToolForPlatform("ffmpeg");
+ if (!base::PathExists(path_to_ffmpeg)) {
+ LOG(ERROR) << "Missing ffmpeg: should be in " << path_to_ffmpeg.value();
+ return false;
+ }
+
+ // Set up ffmpeg to output at a certain resolution (-s) and bitrate (-b:v).
+ // This is needed because WebRTC is free to start the call at a lower
+ // resolution before ramping up. Without these flags, ffmpeg would output a
+ // video in the inital lower resolution, causing the SSIM and PSNR results
+ // to become meaningless.
+ base::CommandLine ffmpeg_command(path_to_ffmpeg);
+ ffmpeg_command.AppendArg("-i");
+ ffmpeg_command.AppendArgPath(webm_video_filename);
+ ffmpeg_command.AppendArg("-s");
+ ffmpeg_command.AppendArg(base::StringPrintf("%dx%d", width, height));
+ ffmpeg_command.AppendArg("-b:v");
+ ffmpeg_command.AppendArg(base::StringPrintf("%d", 120 * width * height));
+ ffmpeg_command.AppendArgPath(yuv_video_filename);
+
+ // We produce an output file that will later be used as an input to the
+ // barcode decoder and frame analyzer tools.
+ DVLOG(0) << "Running " << ffmpeg_command.GetCommandLineString();
+ std::string result;
+ bool ok = base::GetAppOutputAndError(ffmpeg_command, &result);
+ DVLOG(0) << "Output was:\n\n" << result;
+ return ok;
+ }
+
+ // Compares the |captured_video_filename| with the |reference_video_filename|.
+ //
+ // The barcode decoder decodes the captured video containing barcodes overlaid
+ // into every frame of the video. It produces a set of PNG images.
+ // The frames should be of size |width| x |height|.
+ // All measurements calculated are printed as perf parsable numbers to stdout.
+ bool CompareVideosAndPrintResult(
+ const std::string& test_label,
+ int width,
+ int height,
+ const base::FilePath& captured_video_filename,
+ const base::FilePath& reference_video_filename) {
+ base::FilePath path_to_analyzer = base::MakeAbsoluteFilePath(
+ GetBrowserDir().Append(kFrameAnalyzerExecutable));
+ base::FilePath path_to_compare_script = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/compare_videos.py"));
+
+ if (!base::PathExists(path_to_analyzer)) {
+ LOG(ERROR) << "Missing frame analyzer: should be in "
+ << path_to_analyzer.value()
+ << ". Try building the frame_analyzer target.";
+ return false;
+ }
+ if (!base::PathExists(path_to_compare_script)) {
+ LOG(ERROR) << "Missing video compare script: should be in "
+ << path_to_compare_script.value();
+ return false;
+ }
+
+ // Note: don't append switches to this command since it will mess up the
+ // -u in the python invocation!
+ base::CommandLine compare_command(base::CommandLine::NO_PROGRAM);
+ EXPECT_TRUE(GetPythonCommand(&compare_command));
+
+ compare_command.AppendArgPath(path_to_compare_script);
+ compare_command.AppendArg("--label=" + test_label);
+ compare_command.AppendArg("--ref_video");
+ compare_command.AppendArgPath(reference_video_filename);
+ compare_command.AppendArg("--test_video");
+ compare_command.AppendArgPath(captured_video_filename);
+ compare_command.AppendArg("--frame_analyzer");
+ compare_command.AppendArgPath(path_to_analyzer);
+ compare_command.AppendArg("--yuv_frame_width");
+ compare_command.AppendArg(base::NumberToString(width));
+ compare_command.AppendArg("--yuv_frame_height");
+ compare_command.AppendArg(base::NumberToString(height));
+
+ DVLOG(0) << "Running " << compare_command.GetCommandLineString();
+ std::string output;
+ bool ok = base::GetAppOutput(compare_command, &output);
+
+ // Print to stdout to ensure the perf numbers are parsed properly by the
+ // buildbot step. The tool should print a handful RESULT lines.
+ printf("Output was:\n\n%s\n", output.c_str());
+ bool has_result_lines = output.find("RESULT") != std::string::npos;
+ if (!ok || !has_result_lines) {
+ LOG(ERROR) << "Failed to compare videos; see output above to see what "
+ << "the error was.";
+ return false;
+ }
+ return true;
+ }
+
+ void TestVideoQuality(const std::string& video_codec,
+ bool prefer_hw_video_codec) {
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 150)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 150000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 150)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 150000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* left_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ test_config_.constraints);
+ content::WebContents* right_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kCapturingWebrtcHtmlPage),
+ test_config_.constraints);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab, video_codec, prefer_hw_video_codec);
+ SetDefaultVideoCodec(right_tab, video_codec, prefer_hw_video_codec);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ // Poll slower here to avoid flooding the log with messages: capturing and
+ // sending frames take quite a bit of time.
+ int polling_interval_msec = 1000;
+
+ EXPECT_TRUE(test::PollingWaitUntil("doneFrameCapturing()", "done-capturing",
+ right_tab, polling_interval_msec));
+
+ HangUp(left_tab);
+
+ WriteCapturedWebmVideo(right_tab,
+ GetWorkingDir().Append(kCapturedWebmFileName));
+
+ // Shut everything down to avoid having the javascript race with the
+ // analysis tools. For instance, dont have console log printouts interleave
+ // with the RESULT lines from the analysis tools (crbug.com/323200).
+ chrome::CloseWebContents(browser(), left_tab, false);
+ chrome::CloseWebContents(browser(), right_tab, false);
+
+ RunWebmToI420Converter(GetWorkingDir().Append(kCapturedWebmFileName),
+ GetWorkingDir().Append(kCapturedYuvFileName),
+ test_config_.width, test_config_.height);
+
+ ASSERT_TRUE(CompareVideosAndPrintResult(
+ MakeLabel(test_config_.test_name, video_codec), test_config_.width,
+ test_config_.height, GetWorkingDir().Append(kCapturedYuvFileName),
+ test::GetReferenceFilesDir()
+ .Append(test_config_.reference_video)
+ .AddExtension(test::kYuvFileExtension)));
+ }
+
+ protected:
+ VideoQualityTestConfig test_config_;
+
+ base::FilePath GetWorkingDir() { return temp_working_dir_.GetPath(); }
+
+ private:
+ base::FilePath GetSourceDir() {
+ base::FilePath source_dir;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir);
+ return source_dir;
+ }
+
+ base::FilePath GetBrowserDir() {
+ base::FilePath browser_dir;
+ EXPECT_TRUE(base::PathService::Get(base::DIR_MODULE, &browser_dir));
+ return browser_dir;
+ }
+
+ std::unique_ptr<base::Environment> environment_;
+ base::FilePath webrtc_reference_video_y4m_;
+ base::ScopedTempDir temp_working_dir_;
+};
+
+INSTANTIATE_TEST_SUITE_P(WebRtcVideoQualityBrowserTests,
+ WebRtcVideoQualityBrowserTest,
+ testing::ValuesIn(kVideoConfigurations));
+
+IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest,
+ MANUAL_TestVideoQualityVp8) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ TestVideoQuality("VP8", false /* prefer_hw_video_codec */);
+}
+
+// Flaky on windows.
+// TODO(crbug.com/1008766): re-enable when flakiness is investigated, diagnosed
+// and resolved.
+#if defined(OS_WIN)
+#define MAYBE_MANUAL_TestVideoQualityVp9 DISABLED_MANUAL_TestVideoQualityVp9
+#else
+#define MAYBE_MANUAL_TestVideoQualityVp9 MANUAL_TestVideoQualityVp9
+#endif
+IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest,
+ MAYBE_MANUAL_TestVideoQualityVp9) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ TestVideoQuality("VP9", true /* prefer_hw_video_codec */);
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+// Flaky on mac: crbug.com/754684
+#if defined(OS_MACOSX)
+#define MAYBE_MANUAL_TestVideoQualityH264 DISABLED_MANUAL_TestVideoQualityH264
+#else
+#define MAYBE_MANUAL_TestVideoQualityH264 MANUAL_TestVideoQualityH264
+#endif
+
+IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest,
+ MAYBE_MANUAL_TestVideoQualityH264) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcVideoQualityBrowserTest.MANUAL_TestVideoQualityH264 "
+ "(test \"OK\")";
+ return;
+ }
+ TestVideoQuality("H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc
new file mode 100644
index 00000000000..db5eeb4e8eb
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+enum class TargetVideoCaptureImplementation {
+ DEFAULT,
+#if defined(OS_WIN)
+ WIN_MEDIA_FOUNDATION
+#endif
+};
+
+const TargetVideoCaptureImplementation kTargetVideoCaptureImplementations[] = {
+ TargetVideoCaptureImplementation::DEFAULT,
+#if defined(OS_WIN)
+ TargetVideoCaptureImplementation::WIN_MEDIA_FOUNDATION
+#endif
+};
+
+// These tests runs on real webcams and ensure WebRTC can acquire webcams
+// correctly. They will do nothing if there are no webcams on the system.
+// The webcam on the system must support up to 1080p, or the test will fail.
+// This test is excellent for testing the various capture paths of WebRTC
+// on all desktop platforms.
+class WebRtcWebcamBrowserTest
+ : public WebRtcTestBase,
+ public testing::WithParamInterface<TargetVideoCaptureImplementation> {
+ public:
+ WebRtcWebcamBrowserTest() {
+#if defined(OS_WIN)
+ if (GetParam() == TargetVideoCaptureImplementation::WIN_MEDIA_FOUNDATION) {
+ scoped_feature_list_.InitAndEnableFeature(
+ media::kMediaFoundationVideoCapture);
+ } else {
+ scoped_feature_list_.InitAndDisableFeature(
+ media::kMediaFoundationVideoCapture);
+ }
+#endif
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ EXPECT_FALSE(command_line->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream));
+ EXPECT_FALSE(command_line->HasSwitch(
+ switches::kUseFakeUIForMediaStream));
+ }
+
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ std::string GetUserMediaAndGetStreamSize(content::WebContents* tab,
+ const std::string& constraints) {
+ std::string actual_stream_size;
+ if (GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(tab,
+ constraints)) {
+ StartDetectingVideo(tab, "local-view");
+ if (WaitForVideoToPlay(tab))
+ actual_stream_size = GetStreamSize(tab, "local-view");
+ CloseLastLocalStream(tab);
+ }
+ return actual_stream_size;
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// This test is manual because the test results can vary heavily depending on
+// which webcam or drivers you have on the system.
+IN_PROC_BROWSER_TEST_P(WebRtcWebcamBrowserTest,
+ MANUAL_TestAcquiringAndReacquiringWebcam) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ if (!content::IsWebcamAvailableOnSystem(tab)) {
+ DVLOG(0) << "No webcam found on bot: skipping...";
+ return;
+ }
+
+ EXPECT_EQ("320x240",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraintsQVGA));
+ EXPECT_EQ("640x480",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraintsVGA));
+ EXPECT_EQ("640x360",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraints360p));
+ EXPECT_EQ("1280x720",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraints720p));
+ EXPECT_EQ("1920x1080",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraints1080p));
+}
+
+INSTANTIATE_TEST_SUITE_P(WebRtcWebcamBrowserTests,
+ WebRtcWebcamBrowserTest,
+ testing::ValuesIn(kTargetVideoCaptureImplementations));
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util.h b/chromium/chrome/browser/media/webrtc/window_icon_util.h
new file mode 100644
index 00000000000..5bbb199bb56
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util.h
@@ -0,0 +1,14 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WINDOW_ICON_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WINDOW_ICON_UTIL_H_
+
+#include "content/public/browser/desktop_media_id.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "ui/gfx/image/image_skia.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id);
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WINDOW_ICON_UTIL_H_
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc
new file mode 100644
index 00000000000..4a826a5b573
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc
@@ -0,0 +1,21 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "content/public/browser/desktop_media_id.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK_EQ(content::DesktopMediaID::TYPE_WINDOW, id.type);
+ aura::Window* window = content::DesktopMediaID::GetNativeWindowById(id);
+ if (!window)
+ return gfx::ImageSkia();
+
+ gfx::ImageSkia* image = window->GetProperty(aura::client::kWindowIconKey);
+ if (!image)
+ image = window->GetProperty(aura::client::kAppIconKey);
+ return image ? *image : gfx::ImageSkia();
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm b/chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm
new file mode 100644
index 00000000000..0f4b122633b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/window_icon_util.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/stl_util.h"
+#include "third_party/libyuv/include/libyuv/convert_argb.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
+
+ CGWindowID ids[1];
+ ids[0] = id.id;
+ base::ScopedCFTypeRef<CFArrayRef> window_id_array(CFArrayCreate(
+ nullptr, reinterpret_cast<const void**>(&ids), base::size(ids), nullptr));
+ base::ScopedCFTypeRef<CFArrayRef> window_array(
+ CGWindowListCreateDescriptionFromArray(window_id_array));
+ if (!window_array || 0 == CFArrayGetCount(window_array)) {
+ return gfx::ImageSkia();
+ }
+
+ CFDictionaryRef window = base::mac::CFCastStrict<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFNumberRef pid_ref =
+ base::mac::GetValueFromDictionary<CFNumberRef>(window, kCGWindowOwnerPID);
+
+ int pid;
+ CFNumberGetValue(pid_ref, kCFNumberIntType, &pid);
+
+ NSImage* icon_image =
+ [[NSRunningApplication runningApplicationWithProcessIdentifier:pid] icon];
+
+ // Icon's NSImage defaults to the smallest which can be only 32x32.
+ NSRect proposed_rect = NSMakeRect(0, 0, 128, 128);
+ CGImageRef cg_icon_image =
+ [icon_image CGImageForProposedRect:&proposed_rect context:nil hints:nil];
+
+ // 4 components of 8 bits each.
+ if (CGImageGetBitsPerPixel(cg_icon_image) != 32 ||
+ CGImageGetBitsPerComponent(cg_icon_image) != 8) {
+ return gfx::ImageSkia();
+ }
+
+ // Premultiplied alpha and last (alpha channel is next to the blue channel)
+ if (CGImageGetAlphaInfo(cg_icon_image) != kCGImageAlphaPremultipliedLast) {
+ return gfx::ImageSkia();
+ }
+
+ // Ensure BGR like.
+ int byte_order = CGImageGetBitmapInfo(cg_icon_image) & kCGBitmapByteOrderMask;
+ if (byte_order != kCGBitmapByteOrderDefault &&
+ byte_order != kCGBitmapByteOrder32Big) {
+ return gfx::ImageSkia();
+ }
+
+ CGDataProviderRef provider = CGImageGetDataProvider(cg_icon_image);
+ base::ScopedCFTypeRef<CFDataRef> cf_data(CGDataProviderCopyData(provider));
+
+ int width = CGImageGetWidth(cg_icon_image);
+ int height = CGImageGetHeight(cg_icon_image);
+ int src_stride = CGImageGetBytesPerRow(cg_icon_image);
+ const uint8_t* src_data = CFDataGetBytePtr(cf_data);
+
+ SkBitmap result;
+ result.allocN32Pixels(width, height, false /* no-premultiplied */);
+
+ uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
+
+ libyuv::ABGRToARGB(src_data, src_stride, pixels_data, result.rowBytes(),
+ width, height);
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc
new file mode 100644
index 00000000000..f519648c489
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc
@@ -0,0 +1,17 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "content/public/browser/desktop_media_id.h"
+#include "ui/aura/client/aura_constants.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK_EQ(content::DesktopMediaID::TYPE_WINDOW, id.type);
+ // TODO(tonikitoo): can we make the implementation of
+ // chrome/browser/media/webrtc/window_icon_util_chromeos.cc generic
+ // enough so we can reuse it here?
+ NOTIMPLEMENTED();
+ return gfx::ImageSkia();
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_win.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_win.cc
new file mode 100644
index 00000000000..0a14a8afc7c
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_win.cc
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "ui/gfx/icon_util.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
+
+ HWND hwnd = reinterpret_cast<HWND>(id.id);
+ HICON icon_handle = 0;
+
+ SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 5,
+ (PDWORD_PTR)&icon_handle);
+ if (!icon_handle)
+ icon_handle = reinterpret_cast<HICON>(GetClassLongPtr(hwnd, GCLP_HICON));
+
+ if (!icon_handle) {
+ SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 5,
+ (PDWORD_PTR)&icon_handle);
+ }
+ if (!icon_handle) {
+ SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 5,
+ (PDWORD_PTR)&icon_handle);
+ }
+ if (!icon_handle)
+ icon_handle = reinterpret_cast<HICON>(GetClassLongPtr(hwnd, GCLP_HICONSM));
+
+ if (!icon_handle)
+ return gfx::ImageSkia();
+
+ const SkBitmap icon_bitmap = IconUtil::CreateSkBitmapFromHICON(icon_handle);
+
+ if (icon_bitmap.isNull())
+ return gfx::ImageSkia();
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(icon_bitmap);
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc
new file mode 100644
index 00000000000..272d0109da6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc
@@ -0,0 +1,74 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "ui/base/x/x11_util.h"
+#include "ui/gfx/x/x11.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+#include "ui/gfx/x/x11_error_tracker.h"
+#include "ui/gfx/x/x11_types.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
+
+ Display* display = gfx::GetXDisplay();
+ Atom property = gfx::GetAtom("_NET_WM_ICON");
+ Atom actual_type;
+ int actual_format;
+ unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
+ unsigned long size;
+ long* data;
+
+ // The |error_tracker| essentially provides an empty X error handler for
+ // the call of XGetWindowProperty. The motivation is to guard against crash
+ // for any reason that XGetWindowProperty fails. For example, at the time that
+ // XGetWindowProperty is called, the window handler (a.k.a |id.id|) may
+ // already be invalid due to the fact that the end user has closed the
+ // corresponding window, etc.
+ std::unique_ptr<gfx::X11ErrorTracker> error_tracker(
+ new gfx::X11ErrorTracker());
+ int status = XGetWindowProperty(display, id.id, property, 0L, ~0L, x11::False,
+ AnyPropertyType, &actual_type, &actual_format,
+ &size, &bytes_after,
+ reinterpret_cast<unsigned char**>(&data));
+ error_tracker.reset();
+
+ if (status != x11::Success) {
+ return gfx::ImageSkia();
+ }
+
+ // The format of |data| is concatenation of sections like
+ // [width, height, pixel data of size width * height], and the total bytes
+ // number of |data| is |size|. And here we are picking the largest icon.
+ int width = 0;
+ int height = 0;
+ int start = 0;
+ int i = 0;
+ while (i + 1 < static_cast<int>(size)) {
+ if ((i == 0 || static_cast<int>(data[i] * data[i + 1]) > width * height) &&
+ (i + 1 + data[i] * data[i + 1] < static_cast<int>(size))) {
+ width = static_cast<int>(data[i]);
+ height = static_cast<int>(data[i + 1]);
+ start = i + 2;
+ }
+ i = i + 2 + static_cast<int>(data[i] * data[i + 1]);
+ }
+
+ SkBitmap result;
+ SkImageInfo info = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType);
+ result.allocPixels(info);
+
+ uint32_t* pixels_data = reinterpret_cast<uint32_t*>(result.getPixels());
+
+ for (long y = 0; y < height; ++y) {
+ for (long x = 0; x < width; ++x) {
+ pixels_data[result.rowBytesAsPixels() * y + x] =
+ static_cast<uint32_t>(data[start + width * y + x]);
+ }
+ }
+
+ XFree(data);
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
diff --git a/chromium/chrome/browser/net/DEPS b/chromium/chrome/browser/net/DEPS
new file mode 100644
index 00000000000..35ecf445c77
--- /dev/null
+++ b/chromium/chrome/browser/net/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+ '.*_[a-z]*test.*': [
+ "+services/network/test",
+ ],
+}
diff --git a/chromium/chrome/browser/net/OWNERS b/chromium/chrome/browser/net/OWNERS
new file mode 100644
index 00000000000..64f11268482
--- /dev/null
+++ b/chromium/chrome/browser/net/OWNERS
@@ -0,0 +1,7 @@
+file://net/OWNERS
+
+per-file disk_cache_dir_policy_handler*=atwilson@chromium.org
+per-file network_quality*=file://net/nqe/OWNERS
+
+# COMPONENT: Internals>Network
+# TEAM: net-dev@chromium.org
diff --git a/chromium/chrome/browser/net/chrome_accept_header_browsertest.cc b/chromium/chrome/browser/net/chrome_accept_header_browsertest.cc
new file mode 100644
index 00000000000..fcc7a3cf634
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_accept_header_browsertest.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/mime_handler_view_mode.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/http_request.h"
+
+using ChromeAcceptHeaderTest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(ChromeAcceptHeaderTest, Check) {
+ net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTP);
+ server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+ std::string plugin_accept_header, favicon_accept_header;
+ base::RunLoop plugin_loop, favicon_loop;
+ server.RegisterRequestMonitor(base::BindLambdaForTesting(
+ [&](const net::test_server::HttpRequest& request) {
+ if (request.relative_url == "/pdf/test.pdf") {
+ auto it = request.headers.find("Accept");
+ if (it != request.headers.end())
+ plugin_accept_header = it->second;
+ plugin_loop.Quit();
+ } else if (request.relative_url == "/favicon.ico") {
+ auto it = request.headers.find("Accept");
+ if (it != request.headers.end())
+ favicon_accept_header = it->second;
+ favicon_loop.Quit();
+ }
+ }));
+ ASSERT_TRUE(server.Start());
+ GURL url = server.GetURL("/pdf/pdf_embed.html");
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ plugin_loop.Run();
+ favicon_loop.Run();
+
+ if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+ // With MimeHandlerViewInCrossProcessFrame, embedded PDF will go through the
+ // navigation code path and behaves similarly to PDF loaded inside <iframe>.
+ ASSERT_EQ(
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/"
+ "webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ plugin_accept_header);
+ } else {
+ // This is for a sub-resource request.
+ ASSERT_EQ("*/*", plugin_accept_header);
+ }
+
+ ASSERT_EQ("image/webp,image/apng,image/*,*/*;q=0.8", favicon_accept_header);
+
+ // Since the server uses local variables.
+ ASSERT_TRUE(server.ShutdownAndWaitUntilComplete());
+}
diff --git a/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory_browsertest.cc b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory_browsertest.cc
new file mode 100644
index 00000000000..7e857b3b847
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory_browsertest.cc
@@ -0,0 +1,247 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h"
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/process/process.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/time/time.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/service_process_host.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
+
+namespace {
+
+constexpr base::TimeDelta kServiceShutdownTimeout =
+ base::TimeDelta::FromSeconds(3);
+
+constexpr char kPacScript[] =
+ "function FindProxyForURL(url, host) { return 'PROXY proxy.example.com:1; "
+ "DIRECT'; }";
+
+// Dummy consumer of a ProxyResolverFactory. It just calls CreateResolver, and
+// keeps Mojo objects alive from when CreateResolver() is called until it's
+// destroyed.
+class DumbProxyResolverFactoryRequestClient
+ : public proxy_resolver::mojom::ProxyResolverFactoryRequestClient {
+ public:
+ DumbProxyResolverFactoryRequestClient() = default;
+
+ ~DumbProxyResolverFactoryRequestClient() override {
+ EXPECT_TRUE(receiver_.is_bound());
+ }
+
+ void CreateResolver(
+ proxy_resolver::mojom::ProxyResolverFactory* resolver_factory) {
+ mojo::PendingRemote<
+ proxy_resolver::mojom::ProxyResolverFactoryRequestClient>
+ resolver_client;
+ receiver_.Bind(resolver_client.InitWithNewPipeAndPassReceiver());
+ resolver_factory->CreateResolver(kPacScript,
+ resolver_.BindNewPipeAndPassReceiver(),
+ std::move(resolver_client));
+ // Wait for proxy resolver to be created, to avoid any races between
+ // creating one resolver and destroying the next one.
+ run_loop_.Run();
+ }
+
+ private:
+ // ProxyResolverFactoryRequestClient implementation:
+ void ReportResult(int32_t error) override {
+ EXPECT_EQ(net::OK, error);
+ run_loop_.Quit();
+ }
+ void Alert(const std::string& error) override {}
+ void OnError(int32_t line_number, const std::string& error) override {}
+ void ResolveDns(
+ const std::string& hostname,
+ net::ProxyResolveDnsOperation operation,
+ mojo::PendingRemote<proxy_resolver::mojom::HostResolverRequestClient>
+ client) override {}
+
+ mojo::Remote<proxy_resolver::mojom::ProxyResolver> resolver_;
+ mojo::Receiver<proxy_resolver::mojom::ProxyResolverFactoryRequestClient>
+ receiver_{this};
+ base::RunLoop run_loop_;
+};
+
+using ChromeMojoProxyResolverFactoryBrowserTest = InProcessBrowserTest;
+
+class ProxyResolverProcessObserver
+ : public content::ServiceProcessHost::Observer {
+ public:
+ ProxyResolverProcessObserver() {
+ content::ServiceProcessHost::AddObserver(this);
+ }
+
+ ~ProxyResolverProcessObserver() override {
+ content::ServiceProcessHost::RemoveObserver(this);
+ }
+
+ bool is_service_running() const { return is_service_running_; }
+
+ void WaitForLaunch() { launch_loop_.Run(); }
+ void WaitForDeath() { death_loop_.Run(); }
+
+ private:
+ // content::ServiceProcessHost::Observer:
+ void OnServiceProcessLaunched(
+ const content::ServiceProcessInfo& info) override {
+ if (!info.IsService<proxy_resolver::mojom::ProxyResolverFactory>())
+ return;
+
+ ASSERT_FALSE(is_service_running_);
+ is_service_running_ = true;
+ launch_loop_.Quit();
+ }
+
+ void OnServiceProcessTerminatedNormally(
+ const content::ServiceProcessInfo& info) override {
+ if (!info.IsService<proxy_resolver::mojom::ProxyResolverFactory>())
+ return;
+
+ ASSERT_TRUE(is_service_running_);
+ is_service_running_ = false;
+ death_loop_.Quit();
+ }
+
+ private:
+ bool is_service_running_ = false;
+ base::RunLoop launch_loop_;
+ base::RunLoop death_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverProcessObserver);
+};
+
+// Ensures the proxy resolver service is started correctly and stopped when no
+// resolvers are open.
+IN_PROC_BROWSER_TEST_F(ChromeMojoProxyResolverFactoryBrowserTest,
+ ServiceLifecycle) {
+ // Set up the ProxyResolverFactory.
+ mojo::Remote<proxy_resolver::mojom::ProxyResolverFactory> resolver_factory(
+ ChromeMojoProxyResolverFactory::CreateWithSelfOwnedReceiver());
+
+ ProxyResolverProcessObserver observer;
+
+ // Create a resolver, this should create and start the service.
+ std::unique_ptr<DumbProxyResolverFactoryRequestClient> resolver_client1 =
+ std::make_unique<DumbProxyResolverFactoryRequestClient>();
+ resolver_client1->CreateResolver(resolver_factory.get());
+ observer.WaitForLaunch();
+
+ // Create another resolver, no new service should be created (the listener
+ // will assert if that's the case).
+ std::unique_ptr<DumbProxyResolverFactoryRequestClient> resolver_client2 =
+ std::make_unique<DumbProxyResolverFactoryRequestClient>();
+ resolver_client2->CreateResolver(resolver_factory.get());
+
+ // Close one resolver.
+ resolver_client1.reset();
+
+ // The service should not be stopped as there is another resolver.
+ // Wait a little bit and check it's still running.
+ {
+ base::RunLoop run_loop;
+ base::PostDelayedTask(FROM_HERE, {content::BrowserThread::UI},
+ run_loop.QuitClosure(), kServiceShutdownTimeout);
+ run_loop.Run();
+ }
+
+ EXPECT_TRUE(observer.is_service_running());
+
+ // Close the last resolver, the service should now go away.
+ resolver_client2.reset();
+ observer.WaitForDeath();
+}
+
+// Same as above, but destroys the ProxyResolverFactory, which should have no
+// impact on resolver lifetime.
+IN_PROC_BROWSER_TEST_F(ChromeMojoProxyResolverFactoryBrowserTest,
+ DestroyFactory) {
+ // Set up the ProxyResolverFactory.
+ mojo::Remote<proxy_resolver::mojom::ProxyResolverFactory> resolver_factory(
+ ChromeMojoProxyResolverFactory::CreateWithSelfOwnedReceiver());
+
+ ProxyResolverProcessObserver observer;
+
+ // Create a resolver, this should create and start the service.
+ std::unique_ptr<DumbProxyResolverFactoryRequestClient> resolver_client1 =
+ std::make_unique<DumbProxyResolverFactoryRequestClient>();
+ resolver_client1->CreateResolver(resolver_factory.get());
+ observer.WaitForLaunch();
+
+ // Create another resolver, no new service should be created (the listener
+ // will assert if that's the case).
+ std::unique_ptr<DumbProxyResolverFactoryRequestClient> resolver_client2 =
+ std::make_unique<DumbProxyResolverFactoryRequestClient>();
+ resolver_client2->CreateResolver(resolver_factory.get());
+
+ // Destroy the factory. Should not result in the resolver being destroyed.
+ resolver_factory.reset();
+
+ // Close one resolver.
+ resolver_client1.reset();
+
+ // The service should not be stopped as there is another resolver.
+ // Wait a little bit and check it's still running.
+ {
+ base::RunLoop run_loop;
+ base::PostDelayedTask(FROM_HERE, {content::BrowserThread::UI},
+ run_loop.QuitClosure(), kServiceShutdownTimeout);
+ run_loop.Run();
+ }
+
+ EXPECT_TRUE(observer.is_service_running());
+
+ // Close the last resolver, the service should now go away.
+ resolver_client2.reset();
+ observer.WaitForDeath();
+}
+
+// Make sure the service can be started again after it's been stopped.
+IN_PROC_BROWSER_TEST_F(ChromeMojoProxyResolverFactoryBrowserTest,
+ DestroyAndCreateService) {
+ // Set up the ProxyResolverFactory.
+ mojo::Remote<proxy_resolver::mojom::ProxyResolverFactory> resolver_factory(
+ ChromeMojoProxyResolverFactory::CreateWithSelfOwnedReceiver());
+
+ base::Optional<ProxyResolverProcessObserver> observer{base::in_place};
+
+ // Create a resolver, this should create and start the service.
+ std::unique_ptr<DumbProxyResolverFactoryRequestClient> resolver_client =
+ std::make_unique<DumbProxyResolverFactoryRequestClient>();
+ resolver_client->CreateResolver(resolver_factory.get());
+ observer->WaitForLaunch();
+
+ // Close the resolver, the service should stop.
+ resolver_client.reset();
+ observer->WaitForDeath();
+
+ observer.emplace();
+ // Create a resolver again, using the same factory. This should create and
+ // start the service.
+ resolver_client = std::make_unique<DumbProxyResolverFactoryRequestClient>();
+ resolver_client->CreateResolver(resolver_factory.get());
+ observer->WaitForLaunch();
+
+ // Close the resolver again, the service should stop.
+ resolver_client.reset();
+ observer->WaitForDeath();
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/net/chrome_network_delegate.cc b/chromium/chrome/browser/net/chrome_network_delegate.cc
new file mode 100644
index 00000000000..208b68ddfb7
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_network_delegate.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/chrome_network_delegate.h"
+
+#include "base/base_paths.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/path_utils.h"
+#endif
+
+namespace {
+
+bool g_access_to_all_files_enabled = false;
+
+bool IsAccessAllowedInternal(const base::FilePath& path,
+ const base::FilePath& profile_path) {
+ if (g_access_to_all_files_enabled)
+ return true;
+
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+ return true;
+#else
+
+ std::vector<base::FilePath> whitelist;
+#if defined(OS_CHROMEOS)
+ // Use a whitelist to only allow access to files residing in the list of
+ // directories below.
+ static const base::FilePath::CharType* const kLocalAccessWhiteList[] = {
+ "/home/chronos/user/Downloads",
+ "/home/chronos/user/MyFiles",
+ "/home/chronos/user/log",
+ "/home/chronos/user/WebRTC Logs",
+ "/media",
+ "/opt/oem",
+ "/run/arc/sdcard/write/emulated/0",
+ "/usr/share/chromeos-assets",
+ "/var/log",
+ };
+
+ base::FilePath temp_dir;
+ if (base::PathService::Get(base::DIR_TEMP, &temp_dir))
+ whitelist.push_back(temp_dir);
+
+ // The actual location of "/home/chronos/user/Xyz" is the Xyz directory under
+ // the profile path ("/home/chronos/user' is a hard link to current primary
+ // logged in profile.) For the support of multi-profile sessions, we are
+ // switching to use explicit "$PROFILE_PATH/Xyz" path and here whitelist such
+ // access.
+ if (!profile_path.empty()) {
+ const base::FilePath downloads = profile_path.AppendASCII("Downloads");
+ whitelist.push_back(downloads);
+ whitelist.push_back(profile_path.AppendASCII("MyFiles"));
+ const base::FilePath webrtc_logs = profile_path.AppendASCII("WebRTC Logs");
+ whitelist.push_back(webrtc_logs);
+ }
+#elif defined(OS_ANDROID)
+ // Access to files in external storage is allowed.
+ base::FilePath external_storage_path;
+ base::PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE,
+ &external_storage_path);
+ if (external_storage_path.IsParent(path))
+ return true;
+
+ auto all_download_dirs = base::android::GetAllPrivateDownloadsDirectories();
+ for (const auto& dir : all_download_dirs)
+ whitelist.push_back(dir);
+
+ // Whitelist of other allowed directories.
+ static const base::FilePath::CharType* const kLocalAccessWhiteList[] = {
+ "/sdcard", "/mnt/sdcard",
+ };
+#endif
+
+ for (const auto* whitelisted_path : kLocalAccessWhiteList)
+ whitelist.push_back(base::FilePath(whitelisted_path));
+
+ for (const auto& whitelisted_path : whitelist) {
+ // base::FilePath::operator== should probably handle trailing separators.
+ if (whitelisted_path == path.StripTrailingSeparators() ||
+ whitelisted_path.IsParent(path)) {
+ return true;
+ }
+ }
+
+#if defined(OS_CHROMEOS)
+ // Allow access to DriveFS logs. These reside in
+ // $PROFILE_PATH/GCache/v2/<opaque id>/Logs.
+ base::FilePath path_within_gcache_v2;
+ if (profile_path.Append("GCache/v2")
+ .AppendRelativePath(path, &path_within_gcache_v2)) {
+ std::vector<std::string> components;
+ path_within_gcache_v2.GetComponents(&components);
+ if (components.size() > 1 && components[1] == "Logs") {
+ return true;
+ }
+ }
+#endif // defined(OS_CHROMEOS)
+
+ DVLOG(1) << "File access denied - " << path.value().c_str();
+ return false;
+#endif // !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+}
+
+} // namespace
+
+// static
+bool ChromeNetworkDelegate::IsAccessAllowed(
+ const base::FilePath& path,
+ const base::FilePath& profile_path) {
+ return IsAccessAllowedInternal(path, profile_path);
+}
+
+// static
+bool ChromeNetworkDelegate::IsAccessAllowed(
+ const base::FilePath& path,
+ const base::FilePath& absolute_path,
+ const base::FilePath& profile_path) {
+#if defined(OS_ANDROID)
+ // Android's whitelist relies on symbolic links (ex. /sdcard is whitelisted
+ // and commonly a symbolic link), thus do not check absolute paths.
+ return IsAccessAllowedInternal(path, profile_path);
+#else
+ return (IsAccessAllowedInternal(path, profile_path) &&
+ IsAccessAllowedInternal(absolute_path, profile_path));
+#endif
+}
+
+// static
+void ChromeNetworkDelegate::EnableAccessToAllFilesForTesting(bool enabled) {
+ g_access_to_all_files_enabled = enabled;
+}
diff --git a/chromium/chrome/browser/net/chrome_network_delegate.h b/chromium/chrome/browser/net/chrome_network_delegate.h
new file mode 100644
index 00000000000..12c8c497b87
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_network_delegate.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_CHROME_NETWORK_DELEGATE_H_
+#define CHROME_BROWSER_NET_CHROME_NETWORK_DELEGATE_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+
+// TODO(jam): rename this class.
+class ChromeNetworkDelegate {
+ public:
+ // Returns true if access to |path| is allowed. |profile_path| is used to
+ // locate certain paths on Chrome OS. See set_profile_path() for details.
+ static bool IsAccessAllowed(const base::FilePath& path,
+ const base::FilePath& profile_path);
+
+ // Like above, but also takes |path|'s absolute path in |absolute_path| to
+ // further validate access.
+ static bool IsAccessAllowed(const base::FilePath& path,
+ const base::FilePath& absolute_path,
+ const base::FilePath& profile_path);
+
+ // Enables access to all files for testing purposes. This function is used
+ // to bypass the access control for file: scheme. Calling this function
+ // with false brings back the original (production) behaviors.
+ static void EnableAccessToAllFilesForTesting(bool enabled);
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeNetworkDelegate);
+};
+
+#endif // CHROME_BROWSER_NET_CHROME_NETWORK_DELEGATE_H_
diff --git a/chromium/chrome/browser/net/chrome_network_delegate_browsertest.cc b/chromium/chrome/browser/net/chrome_network_delegate_browsertest.cc
new file mode 100644
index 00000000000..d1f01249ee2
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_network_delegate_browsertest.cc
@@ -0,0 +1,90 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base_paths.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "chrome/browser/net/chrome_network_delegate.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/base/filename_util.h"
+#include "net/base/network_delegate.h"
+#include "url/gurl.h"
+
+#if defined(OS_CHROMEOS)
+
+class ChromeNetworkDelegateBrowserTest : public InProcessBrowserTest {
+ protected:
+ ChromeNetworkDelegateBrowserTest() {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ // Access to all files via file: scheme is allowed on browser
+ // tests. Bring back the production behaviors.
+ ChromeNetworkDelegate::EnableAccessToAllFilesForTesting(false);
+ }
+
+ void SetUpOnMainThread() override {
+ base::FilePath temp_dir;
+ ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &temp_dir));
+ ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDirUnderPath(temp_dir));
+ }
+
+ base::ScopedTempDir scoped_temp_dir_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeNetworkDelegateBrowserTest);
+};
+
+// Ensure that access to a test file, that is not in an accessible location,
+// via file: scheme is rejected with ERR_ACCESS_DENIED.
+IN_PROC_BROWSER_TEST_F(ChromeNetworkDelegateBrowserTest, AccessToFile) {
+ base::FilePath test_dir;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
+ base::FilePath test_file = test_dir.AppendASCII("empty.html");
+ ASSERT_FALSE(
+ ChromeNetworkDelegate::IsAccessAllowed(test_file, base::FilePath()));
+
+ GURL url = net::FilePathToFileURL(test_file);
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver observer(web_contents);
+ ui_test_utils::NavigateToURL(browser(), url);
+ EXPECT_EQ(net::ERR_ACCESS_DENIED, observer.last_net_error_code());
+}
+
+// Ensure that access to a symbolic link, that is in an accessible location,
+// to a test file, that isn't, via file: scheme is rejected with
+// ERR_ACCESS_DENIED.
+IN_PROC_BROWSER_TEST_F(ChromeNetworkDelegateBrowserTest, AccessToSymlink) {
+ base::FilePath test_dir;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
+ base::FilePath test_file = test_dir.AppendASCII("empty.html");
+ ASSERT_FALSE(
+ ChromeNetworkDelegate::IsAccessAllowed(test_file, base::FilePath()));
+
+ base::FilePath temp_dir;
+ ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &temp_dir));
+ base::FilePath symlink = scoped_temp_dir_.GetPath().AppendASCII("symlink");
+ ASSERT_TRUE(base::CreateSymbolicLink(test_file, symlink));
+ ASSERT_TRUE(
+ ChromeNetworkDelegate::IsAccessAllowed(symlink, base::FilePath()));
+
+ GURL url = net::FilePathToFileURL(symlink);
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver observer(web_contents);
+ ui_test_utils::NavigateToURL(browser(), url);
+ EXPECT_EQ(net::ERR_ACCESS_DENIED, observer.last_net_error_code());
+}
+
+#endif // defined(OS_CHROMEOS)
diff --git a/chromium/chrome/browser/net/chrome_network_delegate_unittest.cc b/chromium/chrome/browser/net/chrome_network_delegate_unittest.cc
new file mode 100644
index 00000000000..f5251206c9c
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_network_delegate_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/chrome_network_delegate.h"
+
+#include "base/base_paths.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_ANDROID)
+#include "base/base_paths_android.h"
+#endif
+
+namespace {
+
+// Helper function to make the IsAccessAllowed test concise.
+bool IsAccessAllowed(const std::string& path,
+ const std::string& profile_path) {
+ return ChromeNetworkDelegate::IsAccessAllowed(
+ base::FilePath::FromUTF8Unsafe(path),
+ base::FilePath::FromUTF8Unsafe(profile_path));
+}
+
+} // namespace
+
+TEST(ChromeNetworkDelegateStaticTest, IsAccessAllowed) {
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+ // Platforms other than Chrome OS and Android have access to any files.
+ EXPECT_TRUE(IsAccessAllowed("/", ""));
+ EXPECT_TRUE(IsAccessAllowed("/foo.txt", ""));
+#endif
+
+#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
+ // Chrome OS and Android don't have access to random files.
+ EXPECT_FALSE(IsAccessAllowed("/", ""));
+ EXPECT_FALSE(IsAccessAllowed("/foo.txt", ""));
+ // Empty path should not be allowed.
+ EXPECT_FALSE(IsAccessAllowed("", ""));
+#endif
+
+#if defined(OS_CHROMEOS)
+ base::FilePath temp_dir;
+ ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &temp_dir));
+ // Chrome OS allows the following directories.
+ EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/Downloads", ""));
+ EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/MyFiles", ""));
+ EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/MyFiles/file.pdf", ""));
+ EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/log", ""));
+ EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/WebRTC Logs", ""));
+ EXPECT_TRUE(IsAccessAllowed("/media", ""));
+ EXPECT_TRUE(IsAccessAllowed("/opt/oem", ""));
+ EXPECT_TRUE(IsAccessAllowed("/usr/share/chromeos-assets", ""));
+ EXPECT_TRUE(IsAccessAllowed(temp_dir.AsUTF8Unsafe(), ""));
+ EXPECT_TRUE(IsAccessAllowed("/var/log", ""));
+ // Files under the directories are allowed.
+ EXPECT_TRUE(IsAccessAllowed("/var/log/foo.txt", ""));
+ // Make sure similar paths are not allowed.
+ EXPECT_FALSE(IsAccessAllowed("/home/chronos/user/log.txt", ""));
+ EXPECT_FALSE(IsAccessAllowed("/home/chronos/user", ""));
+ EXPECT_FALSE(IsAccessAllowed("/home/chronos", ""));
+
+ // If profile path is given, the following additional paths are allowed.
+ EXPECT_TRUE(IsAccessAllowed("/profile/Downloads", "/profile"));
+ EXPECT_TRUE(IsAccessAllowed("/profile/MyFiles", "/profile"));
+ EXPECT_TRUE(IsAccessAllowed("/profile/MyFiles/file.pdf", "/profile"));
+ EXPECT_TRUE(IsAccessAllowed("/profile/WebRTC Logs", "/profile"));
+
+ // GCache/v2/<opaque ID>/Logs is allowed.
+ EXPECT_TRUE(IsAccessAllowed("/profile/GCache/v2/id/Logs", "/profile"));
+ EXPECT_TRUE(
+ IsAccessAllowed("/profile/GCache/v2/id/Logs/drivefs.txt", "/profile"));
+ EXPECT_FALSE(
+ IsAccessAllowed("/profile/GCache/v2/id/logs/drivefs.txt", "/profile"));
+ EXPECT_FALSE(
+ IsAccessAllowed("/profile/GCache/v2/id/something_else", "/profile"));
+ EXPECT_FALSE(IsAccessAllowed("/profile/GCache/v2/id", "/profile"));
+ EXPECT_FALSE(IsAccessAllowed("/profile/GCache/v2", "/profile"));
+ EXPECT_FALSE(IsAccessAllowed("/home/chronos/user/GCache/v2/id/Logs", ""));
+
+#elif defined(OS_ANDROID)
+ // Android allows the following directories.
+ EXPECT_TRUE(IsAccessAllowed("/sdcard", ""));
+ EXPECT_TRUE(IsAccessAllowed("/mnt/sdcard", ""));
+ // Files under the directories are allowed.
+ EXPECT_TRUE(IsAccessAllowed("/sdcard/foo.txt", ""));
+ // Make sure similar paths are not allowed.
+ EXPECT_FALSE(IsAccessAllowed("/mnt/sdcard.txt", ""));
+ EXPECT_FALSE(IsAccessAllowed("/mnt", ""));
+
+ // Files in external storage are allowed.
+ base::FilePath external_storage_path;
+ base::PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE,
+ &external_storage_path);
+ EXPECT_TRUE(IsAccessAllowed(
+ external_storage_path.AppendASCII("foo.txt").AsUTF8Unsafe(), ""));
+ // The external storage root itself is not allowed.
+ EXPECT_FALSE(IsAccessAllowed(external_storage_path.AsUTF8Unsafe(), ""));
+#endif
+}
diff --git a/chromium/chrome/browser/net/chrome_network_service_browsertest.cc b/chromium/chrome/browser/net/chrome_network_service_browsertest.cc
new file mode 100644
index 00000000000..d982e5edbf8
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_network_service_browsertest.cc
@@ -0,0 +1,130 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/cookie_config/cookie_store_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/test/browser_test.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/extras/sqlite/cookie_crypto_delegate.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+namespace content {
+namespace {
+
+constexpr char kCookieName[] = "Name";
+constexpr char kCookieValue[] = "Value";
+
+net::CookieList GetCookies(
+ const mojo::Remote<network::mojom::CookieManager>& cookie_manager) {
+ base::RunLoop run_loop;
+ net::CookieList cookies_out;
+ cookie_manager->GetAllCookies(
+ base::BindLambdaForTesting([&](const net::CookieList& cookies) {
+ cookies_out = cookies;
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ return cookies_out;
+}
+
+void SetCookie(
+ const mojo::Remote<network::mojom::CookieManager>& cookie_manager) {
+ base::Time t = base::Time::Now();
+ net::CanonicalCookie cookie(kCookieName, kCookieValue, "www.test.com", "/", t,
+ t + base::TimeDelta::FromDays(1), base::Time(),
+ true /* secure */, false /* http-only*/,
+ net::CookieSameSite::NO_RESTRICTION,
+ net::COOKIE_PRIORITY_DEFAULT);
+ base::RunLoop run_loop;
+ cookie_manager->SetCanonicalCookie(
+ cookie, "https", net::CookieOptions(),
+ base::BindLambdaForTesting(
+ [&](net::CanonicalCookie::CookieInclusionStatus status) {
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+}
+
+void FlushCookies(
+ const mojo::Remote<network::mojom::CookieManager>& cookie_manager) {
+ base::RunLoop run_loop;
+ cookie_manager->FlushCookieStore(
+ base::BindLambdaForTesting([&]() { run_loop.Quit(); }));
+ run_loop.Run();
+}
+
+// See |NetworkServiceBrowserTest| for content's version of tests.
+class ChromeNetworkServiceBrowserTest : public InProcessBrowserTest {
+ public:
+ ChromeNetworkServiceBrowserTest() {}
+
+ protected:
+ mojo::PendingRemote<network::mojom::NetworkContext> CreateNetworkContext(
+ bool enable_encrypted_cookies) {
+ mojo::PendingRemote<network::mojom::NetworkContext> network_context;
+ network::mojom::NetworkContextParamsPtr context_params =
+ network::mojom::NetworkContextParams::New();
+ context_params->enable_encrypted_cookies = enable_encrypted_cookies;
+ context_params->cookie_path =
+ browser()->profile()->GetPath().Append(FILE_PATH_LITERAL("cookies"));
+ GetNetworkService()->CreateNetworkContext(
+ network_context.InitWithNewPipeAndPassReceiver(),
+ std::move(context_params));
+ return network_context;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeNetworkServiceBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeNetworkServiceBrowserTest, PRE_EncryptedCookies) {
+ // First set a cookie with cookie encryption enabled.
+ mojo::Remote<network::mojom::NetworkContext> context(
+ CreateNetworkContext(/*enable_encrypted_cookies=*/true));
+ mojo::Remote<network::mojom::CookieManager> cookie_manager;
+ context->GetCookieManager(cookie_manager.BindNewPipeAndPassReceiver());
+
+ SetCookie(cookie_manager);
+
+ net::CookieList cookies = GetCookies(cookie_manager);
+ ASSERT_EQ(1u, cookies.size());
+ EXPECT_EQ(kCookieName, cookies[0].Name());
+ EXPECT_EQ(kCookieValue, cookies[0].Value());
+
+ FlushCookies(cookie_manager);
+}
+
+IN_PROC_BROWSER_TEST_F(ChromeNetworkServiceBrowserTest, EncryptedCookies) {
+ net::CookieCryptoDelegate* crypto_delegate =
+ cookie_config::GetCookieCryptoDelegate();
+ std::string ciphertext;
+ crypto_delegate->EncryptString(kCookieValue, &ciphertext);
+ // These checks are only valid if crypto is enabled on the platform.
+ if (!crypto_delegate->ShouldEncrypt() || ciphertext == kCookieValue)
+ return;
+
+ // Now attempt to read the cookie with encryption disabled.
+ mojo::Remote<network::mojom::NetworkContext> context(
+ CreateNetworkContext(/*enable_encrypted_cookies=*/false));
+ mojo::Remote<network::mojom::CookieManager> cookie_manager;
+ context->GetCookieManager(cookie_manager.BindNewPipeAndPassReceiver());
+
+ net::CookieList cookies = GetCookies(cookie_manager);
+ ASSERT_EQ(1u, cookies.size());
+ EXPECT_EQ(kCookieName, cookies[0].Name());
+ EXPECT_EQ("", cookies[0].Value());
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/chrome/browser/net/chrome_network_service_restart_browsertest.cc b/chromium/chrome/browser/net/chrome_network_service_restart_browsertest.cc
new file mode 100644
index 00000000000..f8e53f408e4
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_network_service_restart_browsertest.cc
@@ -0,0 +1,101 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "services/network/public/cpp/features.h"
+
+namespace content {
+
+// |ChromeNetworkServiceRestartBrowserTest| is required to test Chrome specific
+// code such as |ChromeContentBrowserClient|.
+// See |NetworkServiceRestartBrowserTest| for content's version of tests.
+class ChromeNetworkServiceRestartBrowserTest : public InProcessBrowserTest {
+ public:
+ ChromeNetworkServiceRestartBrowserTest() {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ }
+
+ GURL GetTestURL() const {
+ // Use '/echoheader' instead of '/echo' to avoid a disk_cache bug.
+ // See https://crbug.com/792255.
+ return embedded_test_server()->GetURL("/echoheader");
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeNetworkServiceRestartBrowserTest);
+};
+
+// Make sure |StoragePartition::GetNetworkContext()| returns valid interface
+// after crash.
+IN_PROC_BROWSER_TEST_F(ChromeNetworkServiceRestartBrowserTest,
+ StoragePartitionGetNetworkContext) {
+ if (content::IsInProcessNetworkService())
+ return;
+#if defined(OS_MACOSX)
+ // |NetworkServiceTestHelper| doesn't work on browser_tests on macOS.
+ return;
+#endif
+ StoragePartition* partition =
+ BrowserContext::GetDefaultStoragePartition(browser()->profile());
+
+ network::mojom::NetworkContext* old_network_context =
+ partition->GetNetworkContext();
+ EXPECT_EQ(net::OK, LoadBasicRequest(old_network_context, GetTestURL()));
+
+ // Crash the NetworkService process. Existing interfaces should receive error
+ // notifications at some point.
+ SimulateNetworkServiceCrash();
+ // Flush the interface to make sure the error notification was received.
+ partition->FlushNetworkInterfaceForTesting();
+
+ // |partition->GetNetworkContext()| should return a valid new pointer after
+ // crash.
+ EXPECT_NE(old_network_context, partition->GetNetworkContext());
+ EXPECT_EQ(net::OK,
+ LoadBasicRequest(partition->GetNetworkContext(), GetTestURL()));
+}
+
+// Make sure |SystemNetworkContextManager::GetContext()| returns valid interface
+// after crash.
+IN_PROC_BROWSER_TEST_F(ChromeNetworkServiceRestartBrowserTest,
+ SystemNetworkContextManagerGetContext) {
+ if (content::IsInProcessNetworkService())
+ return;
+#if defined(OS_MACOSX)
+ // |NetworkServiceTestHelper| doesn't work on browser_tests on macOS.
+ return;
+#endif
+ SystemNetworkContextManager* system_network_context_manager =
+ g_browser_process->system_network_context_manager();
+
+ EXPECT_EQ(net::OK,
+ LoadBasicRequest(system_network_context_manager->GetContext(),
+ GetTestURL()));
+
+ // Crash the NetworkService process. Existing interfaces should receive error
+ // notifications at some point.
+ SimulateNetworkServiceCrash();
+ // Flush the interface to make sure the error notification was received.
+ system_network_context_manager->FlushNetworkInterfaceForTesting();
+
+ // |system_network_context_manager->GetContext()| should return a valid
+ // pointer after crash, since the NetworkContext is bound again.
+ ASSERT_NE(system_network_context_manager->GetContext(), nullptr);
+ EXPECT_EQ(net::OK,
+ LoadBasicRequest(system_network_context_manager->GetContext(),
+ GetTestURL()));
+}
+
+} // namespace content
diff --git a/chromium/chrome/browser/net/chrome_report_sender.cc b/chromium/chrome/browser/net/chrome_report_sender.cc
new file mode 100644
index 00000000000..95dee589400
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_report_sender.cc
@@ -0,0 +1,99 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/chrome_report_sender.h"
+
+#include "base/bind.h"
+#include "net/url_request/report_sender.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace {
+
+// The value doesn't really matter, as we'll delete the loader once we get the
+// response.
+static const int kMaxSize = 1024;
+
+using ErrorCallback =
+ base::OnceCallback<void(int /* net_error */, int /* http_response_code */)>;
+
+// Owns the SimpleURLLoader and will run the appropriate callback and delete
+// the loader when the response arrives.
+class SimpleURLLoaderOwner {
+ public:
+ SimpleURLLoaderOwner(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ std::unique_ptr<network::SimpleURLLoader> loader,
+ base::OnceClosure success_callback,
+ ErrorCallback error_callback)
+ : loader_(std::move(loader)),
+ success_callback_(std::move(success_callback)),
+ error_callback_(std::move(error_callback)) {
+ // We don't care to read the response, and since it can come from untrusted
+ // endpoints it's better to not buffer. So we'll match net::ReportSender by
+ // closing the loader as soon as we start getting the response.
+ loader_->SetOnResponseStartedCallback(base::BindOnce(
+ &SimpleURLLoaderOwner::OnResponseStarted, base::Unretained(this)));
+ loader_->DownloadToString(
+ url_loader_factory.get(),
+ base::BindOnce(&SimpleURLLoaderOwner::OnURLLoaderComplete,
+ base::Unretained(this)),
+ kMaxSize);
+ }
+
+ private:
+ ~SimpleURLLoaderOwner() = default;
+
+ void OnResponseStarted(const GURL& final_url,
+ const network::mojom::URLResponseHead& response_head) {
+ OnDone(&response_head, net::OK);
+ }
+
+ void OnURLLoaderComplete(std::unique_ptr<std::string> response_body) {
+ OnDone(loader_->ResponseInfo(), loader_->NetError());
+ }
+
+ void OnDone(const network::mojom::URLResponseHead* response_head,
+ int net_error) {
+ if (net_error == net::OK) {
+ if (success_callback_)
+ std::move(success_callback_).Run();
+ } else if (error_callback_) {
+ int response_code = 0;
+ if (response_head && response_head->headers)
+ response_code = response_head->headers->response_code();
+ std::move(error_callback_).Run(loader_->NetError(), response_code);
+ }
+ delete this;
+ }
+
+ std::unique_ptr<network::SimpleURLLoader> loader_;
+ base::OnceClosure success_callback_;
+ ErrorCallback error_callback_;
+};
+
+} // namespace
+
+void SendReport(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ net::NetworkTrafficAnnotationTag traffic_annotation,
+ const GURL& report_uri,
+ const std::string& content_type,
+ const std::string& report,
+ base::OnceClosure success_callback,
+ ErrorCallback error_callback) {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = report_uri;
+ resource_request->method = "POST";
+ resource_request->load_flags = net::ReportSender::kLoadFlags;
+
+ auto loader = network::SimpleURLLoader::Create(std::move(resource_request),
+ traffic_annotation);
+ loader->AttachStringForUpload(report, content_type);
+
+ new SimpleURLLoaderOwner(url_loader_factory, std::move(loader),
+ std::move(success_callback),
+ std::move(error_callback));
+}
diff --git a/chromium/chrome/browser/net/chrome_report_sender.h b/chromium/chrome/browser/net/chrome_report_sender.h
new file mode 100644
index 00000000000..c06b11f6ddd
--- /dev/null
+++ b/chromium/chrome/browser/net/chrome_report_sender.h
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_CHROME_REPORT_SENDER_H_
+#define CHROME_BROWSER_NET_CHROME_REPORT_SENDER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+class GURL;
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+// Similar to net::ReportSender but uses network::SimpleURLLoader under the
+// hood.
+void SendReport(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ net::NetworkTrafficAnnotationTag traffic_annotation,
+ const GURL& report_uri,
+ const std::string& content_type,
+ const std::string& report,
+ base::OnceClosure success_callback,
+ base::OnceCallback<void(int /* net_error */, int /* http_response_code */)>
+ error_callback);
+
+#endif // CHROME_BROWSER_NET_CHROME_REPORT_SENDER_H_
diff --git a/chromium/chrome/browser/net/cookie_policy_browsertest.cc b/chromium/chrome/browser/net/cookie_policy_browsertest.cc
new file mode 100644
index 00000000000..3729415d0fe
--- /dev/null
+++ b/chromium/chrome/browser/net/cookie_policy_browsertest.cc
@@ -0,0 +1,515 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "components/content_settings/core/common/features.h"
+#include "components/content_settings/core/common/pref_names.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+using content::BrowserThread;
+
+namespace {
+
+const std::vector<std::string> kStorageTypes{
+ "Cookie", "LocalStorage", "FileSystem", "SessionStorage",
+ "IndexedDb", "WebSql", "CacheStorage", "ServiceWorker",
+};
+
+class CookiePolicyBrowserTest : public InProcessBrowserTest {
+ protected:
+ CookiePolicyBrowserTest()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ base::FilePath path;
+ base::PathService::Get(content::DIR_TEST_DATA, &path);
+ https_server_.ServeFilesFromDirectory(path);
+ https_server_.AddDefaultHandlers(GetChromeTestDataDir());
+ ASSERT_TRUE(https_server_.Start());
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // HTTPS server only serves a valid cert for localhost, so this is needed
+ // to load pages from other hosts without an error.
+ command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
+ }
+
+ void SetBlockThirdPartyCookies(bool value) {
+ browser()->profile()->GetPrefs()->SetBoolean(prefs::kBlockThirdPartyCookies,
+ value);
+ }
+
+ void NavigateToPageWithFrame(const std::string& host) {
+ GURL main_url(https_server_.GetURL(host, "/iframe.html"));
+ ui_test_utils::NavigateToURL(browser(), main_url);
+ }
+
+ void NavigateFrameTo(const std::string& host, const std::string& path) {
+ GURL page = https_server_.GetURL(host, path);
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", page));
+ }
+
+ void ExpectFrameContent(const std::string& expected) {
+ std::string content;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ GetFrame(),
+ "window.domAutomationController.send(document.body.textContent)",
+ &content));
+ EXPECT_EQ(expected, content);
+ }
+
+ void NavigateNestedFrameTo(const std::string& host, const std::string& path) {
+ GURL url(https_server_.GetURL(host, path));
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver load_observer(web_contents);
+ ASSERT_TRUE(ExecuteScript(
+ GetFrame(),
+ base::StringPrintf("document.body.querySelector('iframe').src = '%s';",
+ url.spec().c_str())));
+ load_observer.Wait();
+ }
+
+ void ExpectNestedFrameContent(const std::string& expected) {
+ std::string content;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ GetNestedFrame(),
+ "window.domAutomationController.send(document.body.textContent)",
+ &content));
+ EXPECT_EQ(expected, content);
+ }
+
+ void ExpectCookiesOnHost(const std::string& host,
+ const std::string& expected) {
+ EXPECT_EQ(expected, content::GetCookies(browser()->profile(),
+ https_server_.GetURL(host, "/")));
+ }
+
+ void SetStorageForFrame(content::RenderFrameHost* frame) {
+ for (const auto& data_type : kStorageTypes) {
+ bool data;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ frame, "set" + data_type + "()", &data));
+ EXPECT_TRUE(data) << data_type;
+ }
+ }
+
+ void ExpectStorageForFrame(content::RenderFrameHost* frame, bool expected) {
+ for (const auto& data_type : kStorageTypes) {
+ bool data;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ frame, "has" + data_type + "();", &data));
+ EXPECT_EQ(expected, data) << data_type;
+ }
+ }
+
+ content::RenderFrameHost* GetFrame() {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ return ChildFrameAt(web_contents->GetMainFrame(), 0);
+ }
+
+ content::RenderFrameHost* GetNestedFrame() {
+ return ChildFrameAt(GetFrame(), 0);
+ }
+
+ protected:
+ net::test_server::EmbeddedTestServer https_server_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CookiePolicyBrowserTest);
+};
+
+// Visits a page that sets a first-party cookie.
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest, AllowFirstPartyCookies) {
+ SetBlockThirdPartyCookies(false);
+
+ GURL url(https_server_.GetURL("/set-cookie?cookie1"));
+
+ std::string cookie = content::GetCookies(browser()->profile(), url);
+ ASSERT_EQ("", cookie);
+
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ cookie = content::GetCookies(browser()->profile(), url);
+ EXPECT_EQ("cookie1", cookie);
+}
+
+// Visits a page that is a redirect across domain boundary to a page that sets
+// a first-party cookie.
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ AllowFirstPartyCookiesRedirect) {
+ SetBlockThirdPartyCookies(true);
+
+ GURL url(https_server_.GetURL("/server-redirect?"));
+ GURL redirected_url(https_server_.GetURL("/set-cookie?cookie2"));
+
+ // Change the host name from 127.0.0.1 to www.example.com so it triggers
+ // third-party cookie blocking if the first party for cookies URL is not
+ // changed when we follow a redirect.
+ ASSERT_EQ("127.0.0.1", redirected_url.host());
+ GURL::Replacements replacements;
+ replacements.SetHostStr("www.example.com");
+ redirected_url = redirected_url.ReplaceComponents(replacements);
+
+ std::string cookie =
+ content::GetCookies(browser()->profile(), redirected_url);
+ ASSERT_EQ("", cookie);
+
+ ui_test_utils::NavigateToURL(browser(),
+ GURL(url.spec() + redirected_url.spec()));
+
+ cookie = content::GetCookies(browser()->profile(), redirected_url);
+ EXPECT_EQ("cookie2", cookie);
+}
+
+// Third-Party Frame Tests
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ ThirdPartyCookiesIFrameAllowSetting) {
+ SetBlockThirdPartyCookies(false);
+
+ NavigateToPageWithFrame("a.com");
+
+ ExpectCookiesOnHost("b.com", "");
+
+ // Navigate iframe to a cross-site, cookie-setting endpoint, and verify that
+ // the cookie is set:
+ NavigateFrameTo("b.com", "/set-cookie?thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site, cookie-setting endpoint, and verify that the cookie
+ // is set:
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/set-cookie?thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site, cookie-setting endpoint, and verify that the cookie
+ // is set:
+ NavigateFrameTo("c.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/set-cookie?thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ ThirdPartyCookiesIFrameBlockSetting) {
+ SetBlockThirdPartyCookies(true);
+
+ NavigateToPageWithFrame("a.com");
+
+ // Navigate iframe to a cross-site, cookie-setting endpoint, and verify that
+ // the cookie is not set:
+ NavigateFrameTo("b.com", "/set-cookie?thirdparty");
+ ExpectCookiesOnHost("b.com", "");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site, cookie-setting endpoint, and verify that the cookie
+ // is not set:
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/set-cookie?thirdparty");
+ ExpectCookiesOnHost("b.com", "");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site, cookie-setting endpoint, and verify that the cookie
+ // is not set:
+ NavigateFrameTo("c.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/set-cookie?thirdparty");
+ ExpectCookiesOnHost("b.com", "");
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ ThirdPartyCookiesIFrameAllowReading) {
+ SetBlockThirdPartyCookies(false);
+
+ // Set a cookie on `b.com`.
+ content::SetCookie(browser()->profile(), https_server_.GetURL("b.com", "/"),
+ "thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+
+ NavigateToPageWithFrame("a.com");
+
+ // Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
+ // the cookie is sent:
+ NavigateFrameTo("b.com", "/echoheader?cookie");
+ ExpectFrameContent("thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site page that echos the cookie header, and verify that
+ // the cookie is sent:
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a distinct cross-site page that echos the cookie header, and
+ // verify that the cookie is not sent:
+ NavigateFrameTo("c.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("thirdparty");
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ ThirdPartyCookiesIFrameBlockReading) {
+ SetBlockThirdPartyCookies(true);
+
+ // Set a cookie on `b.com`.
+ content::SetCookie(browser()->profile(), https_server_.GetURL("b.com", "/"),
+ "thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+
+ NavigateToPageWithFrame("a.com");
+
+ // Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
+ // the cookie is not sent:
+ NavigateFrameTo("b.com", "/echoheader?cookie");
+ ExpectFrameContent("None");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site page that echos the cookie header, and verify that
+ // the cookie is not sent:
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("None");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a distinct cross-site page that echos the cookie header, and
+ // verify that the cookie is not sent:
+ NavigateFrameTo("c.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("None");
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ ThirdPartyCookiesIFrameExceptions) {
+ SetBlockThirdPartyCookies(true);
+
+ // Set a cookie on `b.com`.
+ content::SetCookie(browser()->profile(), https_server_.GetURL("b.com", "/"),
+ "thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+
+ // Allow all requests to b.com to have cookies.
+ auto cookie_settings =
+ CookieSettingsFactory::GetForProfile(browser()->profile());
+ GURL url = https_server_.GetURL("b.com", "/");
+ cookie_settings->SetCookieSetting(url, ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+
+ // Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
+ // the cookie is sent:
+ NavigateFrameTo("b.com", "/echoheader?cookie");
+ ExpectFrameContent("thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site page that echos the cookie header, and verify that
+ // the cookie is sent:
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a distinct cross-site page that echos the cookie header, and
+ // verify that the cookie is sent:
+ NavigateFrameTo("c.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("thirdparty");
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest,
+ ThirdPartyCookiesIFrameThirdPartyExceptions) {
+ SetBlockThirdPartyCookies(true);
+
+ // Set a cookie on `b.com`.
+ content::SetCookie(browser()->profile(), https_server_.GetURL("b.com", "/"),
+ "thirdparty");
+ ExpectCookiesOnHost("b.com", "thirdparty");
+
+ // Allow all requests on the top frame domain a.com to have cookies.
+ auto cookie_settings =
+ CookieSettingsFactory::GetForProfile(browser()->profile());
+ GURL url = https_server_.GetURL("a.com", "/");
+ cookie_settings->SetThirdPartyCookieSetting(
+ url, ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+
+ // Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
+ // the cookie is sent:
+ NavigateFrameTo("b.com", "/echoheader?cookie");
+ ExpectFrameContent("thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a cross-site page that echos the cookie header, and verify that
+ // the cookie is sent:
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("thirdparty");
+
+ // Navigate iframe to a cross-site frame with a frame, and navigate _that_
+ // frame to a distinct cross-site page that echos the cookie header, and
+ // verify that the cookie is sent:
+ NavigateFrameTo("c.com", "/iframe.html");
+ NavigateNestedFrameTo("b.com", "/echoheader?cookie");
+ ExpectNestedFrameContent("thirdparty");
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest, ThirdPartyIFrameStorage) {
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetFrame(), false);
+ SetStorageForFrame(GetFrame());
+ ExpectStorageForFrame(GetFrame(), true);
+
+ SetBlockThirdPartyCookies(true);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetFrame(), false);
+
+ // Allow all requests to b.com to access storage.
+ auto cookie_settings =
+ CookieSettingsFactory::GetForProfile(browser()->profile());
+ GURL a_url = https_server_.GetURL("a.com", "/");
+ GURL b_url = https_server_.GetURL("b.com", "/");
+ cookie_settings->SetCookieSetting(b_url,
+ ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetFrame(), true);
+
+ // Remove ALLOW setting.
+ cookie_settings->ResetCookieSetting(b_url);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetFrame(), false);
+
+ // Allow all third-parties on a.com to access storage.
+ cookie_settings->SetThirdPartyCookieSetting(
+ a_url, ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetFrame(), true);
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest, NestedThirdPartyIFrameStorage) {
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("c.com", "/browsing_data/site_data.html");
+
+ ExpectStorageForFrame(GetNestedFrame(), false);
+ SetStorageForFrame(GetNestedFrame());
+ ExpectStorageForFrame(GetNestedFrame(), true);
+
+ SetBlockThirdPartyCookies(true);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("c.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), false);
+
+ // Allow all requests to b.com to access storage.
+ auto cookie_settings =
+ CookieSettingsFactory::GetForProfile(browser()->profile());
+ GURL a_url = https_server_.GetURL("a.com", "/");
+ GURL c_url = https_server_.GetURL("c.com", "/");
+ cookie_settings->SetCookieSetting(c_url,
+ ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("c.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), true);
+
+ // Remove ALLOW setting.
+ cookie_settings->ResetCookieSetting(c_url);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("c.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), false);
+
+ // Allow all third-parties on a.com to access storage.
+ cookie_settings->SetThirdPartyCookieSetting(
+ a_url, ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("c.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), true);
+}
+
+IN_PROC_BROWSER_TEST_F(CookiePolicyBrowserTest, NestedFirstPartyIFrameStorage) {
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("a.com", "/browsing_data/site_data.html");
+
+ ExpectStorageForFrame(GetNestedFrame(), false);
+ SetStorageForFrame(GetNestedFrame());
+ ExpectStorageForFrame(GetNestedFrame(), true);
+
+ SetBlockThirdPartyCookies(true);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("a.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), false);
+
+ // Allow all requests to b.com to access storage.
+ auto cookie_settings =
+ CookieSettingsFactory::GetForProfile(browser()->profile());
+ GURL a_url = https_server_.GetURL("a.com", "/");
+ cookie_settings->SetCookieSetting(a_url,
+ ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("a.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), true);
+
+ // Remove ALLOW setting.
+ cookie_settings->ResetCookieSetting(a_url);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("a.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), false);
+
+ // Allow all third-parties on a.com to access storage.
+ cookie_settings->SetThirdPartyCookieSetting(
+ a_url, ContentSetting::CONTENT_SETTING_ALLOW);
+
+ NavigateToPageWithFrame("a.com");
+ NavigateFrameTo("b.com", "/iframe.html");
+ NavigateNestedFrameTo("a.com", "/browsing_data/site_data.html");
+ ExpectStorageForFrame(GetNestedFrame(), true);
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/net/disk_cache_dir_policy_handler.cc b/chromium/chrome/browser/net/disk_cache_dir_policy_handler.cc
new file mode 100644
index 00000000000..4719c1cc5d1
--- /dev/null
+++ b/chromium/chrome/browser/net/disk_cache_dir_policy_handler.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/disk_cache_dir_policy_handler.h"
+
+#include "base/files/file_path.h"
+#include "base/values.h"
+#include "chrome/browser/policy/policy_path_parser.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+
+namespace policy {
+
+DiskCacheDirPolicyHandler::DiskCacheDirPolicyHandler()
+ : TypeCheckingPolicyHandler(key::kDiskCacheDir, base::Value::Type::STRING) {
+}
+
+DiskCacheDirPolicyHandler::~DiskCacheDirPolicyHandler() {}
+
+void DiskCacheDirPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ const base::Value* value = policies.GetValue(policy_name());
+ base::FilePath::StringType string_value;
+ if (value && value->GetAsString(&string_value)) {
+ base::FilePath::StringType expanded_value =
+ policy::path_parser::ExpandPathVariables(string_value);
+ prefs->SetValue(prefs::kDiskCacheDir, base::Value(expanded_value));
+ }
+}
+
+} // namespace policy
diff --git a/chromium/chrome/browser/net/disk_cache_dir_policy_handler.h b/chromium/chrome/browser/net/disk_cache_dir_policy_handler.h
new file mode 100644
index 00000000000..13854f41174
--- /dev/null
+++ b/chromium/chrome/browser/net/disk_cache_dir_policy_handler.h
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DISK_CACHE_DIR_POLICY_HANDLER_H_
+#define CHROME_BROWSER_NET_DISK_CACHE_DIR_POLICY_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+
+namespace policy {
+
+// ConfigurationPolicyHandler for the DiskCacheDir policy.
+class DiskCacheDirPolicyHandler : public TypeCheckingPolicyHandler {
+ public:
+ DiskCacheDirPolicyHandler();
+ ~DiskCacheDirPolicyHandler() override;
+
+ // ConfigurationPolicyHandler methods:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DiskCacheDirPolicyHandler);
+};
+
+} // namespace policy
+
+#endif // CHROME_BROWSER_NET_DISK_CACHE_DIR_POLICY_HANDLER_H_
diff --git a/chromium/chrome/browser/net/disk_cache_dir_policy_handler_unittest.cc b/chromium/chrome/browser/net/disk_cache_dir_policy_handler_unittest.cc
new file mode 100644
index 00000000000..b61ae99ceaa
--- /dev/null
+++ b/chromium/chrome/browser/net/disk_cache_dir_policy_handler_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/values.h"
+#include "chrome/browser/net/disk_cache_dir_policy_handler.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class DiskCacheDirPolicyTest : public testing::Test {
+ protected:
+ PolicyMap policy_;
+ DiskCacheDirPolicyHandler handler_;
+ PrefValueMap prefs_;
+};
+
+TEST_F(DiskCacheDirPolicyTest, Default) {
+ handler_.ApplyPolicySettings(policy_, &prefs_);
+ EXPECT_FALSE(prefs_.GetValue(prefs::kDiskCacheDir, NULL));
+}
+
+TEST_F(DiskCacheDirPolicyTest, SetPolicyInvalid) {
+ // DiskCacheDir policy expects a string; give it a boolean.
+ policy_.Set(key::kDiskCacheDir, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
+ nullptr);
+ handler_.ApplyPolicySettings(policy_, &prefs_);
+ EXPECT_FALSE(prefs_.GetValue(prefs::kDiskCacheDir, NULL));
+}
+
+TEST_F(DiskCacheDirPolicyTest, SetPolicyValid) {
+ // Use a variable in the value. It should be expanded by the handler.
+ const std::string in = "${user_name}/foo";
+ policy_.Set(key::kDiskCacheDir, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(in), nullptr);
+ handler_.ApplyPolicySettings(policy_, &prefs_);
+
+ const base::Value* value;
+ ASSERT_TRUE(prefs_.GetValue(prefs::kDiskCacheDir, &value));
+ std::string out;
+ ASSERT_TRUE(value->GetAsString(&out));
+ EXPECT_NE(std::string::npos, out.find("foo"));
+ EXPECT_EQ(std::string::npos, out.find("${user_name}"));
+}
+
+} // namespace policy
diff --git a/chromium/chrome/browser/net/dns_probe_browsertest.cc b/chromium/chrome/browser/net/dns_probe_browsertest.cc
new file mode 100644
index 00000000000..56b5263917b
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_browsertest.cc
@@ -0,0 +1,956 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <set>
+
+#include "base/bind.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/default_tick_clock.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/dns_probe_service_factory.h"
+#include "chrome/browser/net/dns_probe_test_util.h"
+#include "chrome/browser/net/net_error_tab_helper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/error_page/common/net_error_info.h"
+#include "components/google/core/common/google_util.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/url_request/url_request_failed_job.h"
+#include "services/network/public/cpp/features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Bind;
+using base::BindOnce;
+using base::Callback;
+using base::Closure;
+using base::FilePath;
+using base::Unretained;
+using content::BrowserThread;
+using content::WebContents;
+using error_page::DnsProbeStatus;
+using google_util::LinkDoctorBaseURL;
+using net::URLRequestFailedJob;
+using ui_test_utils::NavigateToURL;
+using ui_test_utils::NavigateToURLBlockUntilNavigationsComplete;
+
+namespace chrome_browser_net {
+
+namespace {
+
+// Postable function to run a Closure on the UI thread. Since
+// base::PostTask returns a bool, it can't directly be posted to
+// another thread.
+void RunClosureOnUIThread(const base::Closure& closure) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, closure);
+}
+
+// Wraps DnsProbeService and delays probes until someone calls
+// StartDelayedProbes. This allows the DnsProbeBrowserTest to enforce a
+// stricter ordering of events.
+class DelayingDnsProbeService : public DnsProbeService {
+ public:
+ DelayingDnsProbeService(
+ const DnsProbeServiceFactory::NetworkContextGetter&
+ network_context_getter,
+ const DnsProbeServiceFactory::DnsConfigChangeManagerGetter&
+ dns_config_change_manager_getter)
+ : dns_probe_service_impl_(DnsProbeServiceFactory::CreateForTesting(
+ network_context_getter,
+ dns_config_change_manager_getter,
+ base::DefaultTickClock::GetInstance())) {}
+
+ ~DelayingDnsProbeService() override { EXPECT_TRUE(delayed_probes_.empty()); }
+
+ static std::unique_ptr<KeyedService> Create(
+ const DnsProbeServiceFactory::NetworkContextGetter&
+ network_context_getter,
+ const DnsProbeServiceFactory::DnsConfigChangeManagerGetter&
+ dns_config_change_manager_getter,
+ content::BrowserContext* context) {
+ return std::make_unique<DelayingDnsProbeService>(
+ network_context_getter, dns_config_change_manager_getter);
+ }
+
+ void ProbeDns(ProbeCallback callback) override {
+ delayed_probes_.push_back(std::move(callback));
+ }
+
+ void StartDelayedProbes() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ std::vector<ProbeCallback> probes;
+ probes.swap(delayed_probes_);
+
+ for (std::vector<ProbeCallback>::iterator i = probes.begin();
+ i != probes.end(); ++i) {
+ dns_probe_service_impl_->ProbeDns(std::move(*i));
+ }
+ }
+
+ int delayed_probe_count() const {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return delayed_probes_.size();
+ }
+
+ private:
+ std::unique_ptr<DnsProbeService> dns_probe_service_impl_;
+ std::vector<ProbeCallback> delayed_probes_;
+};
+
+FilePath GetMockLinkDoctorFilePath() {
+ FilePath root_http;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &root_http);
+ return root_http.AppendASCII("mock-link-doctor.json");
+}
+
+// A request that can be delayed until Resume() is called. Can also run a
+// callback if destroyed without being resumed. Resume can be called either
+// before or after a the request is started.
+class DelayableRequest {
+ public:
+ // Called by a DelayableRequest if it was set to be delayed, and has been
+ // destroyed without Undelay being called.
+ typedef base::OnceCallback<void(DelayableRequest* request)>
+ DestructionCallback;
+
+ virtual void Resume() = 0;
+
+ protected:
+ virtual ~DelayableRequest() {}
+};
+
+class DelayedURLLoader : public network::mojom::URLLoader,
+ public DelayableRequest {
+ public:
+ DelayedURLLoader(network::mojom::URLLoaderRequest request,
+ network::mojom::URLLoaderClientPtr client,
+ int net_error,
+ bool should_delay,
+ DestructionCallback destruction_callback)
+ : binding_(this, std::move(request)),
+ client_(std::move(client)),
+ net_error_(net_error),
+ should_delay_(should_delay),
+ destruction_callback_(std::move(destruction_callback)) {
+ binding_.set_connection_error_handler(base::BindOnce(
+ &DelayedURLLoader::OnConnectionError, base::Unretained(this)));
+ if (!should_delay)
+ SendResponse();
+ }
+
+ void Resume() override {
+ DCHECK(should_delay_);
+ should_delay_ = false;
+ SendResponse();
+ }
+
+ void SendResponse() {
+ if (net_error_ == net::OK) {
+ content::URLLoaderInterceptor::WriteResponse(GetMockLinkDoctorFilePath(),
+ client_.get());
+ return;
+ }
+
+ client_->OnComplete(network::URLLoaderCompletionStatus(net_error_));
+ }
+
+ private:
+ ~DelayedURLLoader() override {
+ if (should_delay_)
+ std::move(destruction_callback_).Run(this);
+ }
+
+ void OnConnectionError() { delete this; }
+
+ // mojom::URLLoader implementation:
+ void FollowRedirect(const std::vector<std::string>& removed_headers,
+ const net::HttpRequestHeaders& modified_headers,
+ const base::Optional<GURL>& new_url) override {}
+ void SetPriority(net::RequestPriority priority,
+ int32_t intra_priority_value) override {}
+ void PauseReadingBodyFromNet() override {}
+ void ResumeReadingBodyFromNet() override {}
+
+ mojo::Binding<network::mojom::URLLoader> binding_;
+ network::mojom::URLLoaderClientPtr client_;
+ int net_error_;
+ bool should_delay_;
+ DestructionCallback destruction_callback_;
+};
+
+// Interceptor for navigation correction requests. Can cause requests to
+// fail with an error, and/or delay a request until a test allows to continue.
+// Also can run a callback when a delayed request is cancelled.
+class BreakableCorrectionInterceptor {
+ public:
+ BreakableCorrectionInterceptor()
+ : net_error_(net::OK), delay_requests_(false) {}
+
+ ~BreakableCorrectionInterceptor() {
+ // All delayed requests should have been resumed or cancelled by this point.
+ EXPECT_TRUE(delayed_requests_.empty());
+ }
+
+ void InterceptURLLoaderRequest(
+ content::URLLoaderInterceptor::RequestParams* params) {
+ DelayedURLLoader* job = new DelayedURLLoader(
+ std::move(params->request), std::move(params->client), net_error_,
+ delay_requests_,
+ base::BindOnce(&BreakableCorrectionInterceptor::OnRequestDestroyed,
+ base::Unretained(this)));
+ if (delay_requests_)
+ delayed_requests_.insert(job);
+ }
+
+ void set_net_error(int net_error) { net_error_ = net_error; }
+
+ void SetDelayRequests(bool delay_requests) {
+ delay_requests_ = delay_requests;
+
+ // Resume all delayed requests if no longer delaying requests.
+ if (!delay_requests) {
+ while (!delayed_requests_.empty()) {
+ DelayableRequest* request = *delayed_requests_.begin();
+ delayed_requests_.erase(request);
+ request->Resume();
+ }
+ }
+ }
+
+ // Runs |callback| once all delayed requests have been destroyed. Does not
+ // wait for delayed requests that have been resumed.
+ void SetRequestDestructionCallback(const base::Closure& callback) {
+ ASSERT_TRUE(delayed_request_destruction_callback_.is_null());
+ if (delayed_requests_.empty()) {
+ callback.Run();
+ return;
+ }
+ delayed_request_destruction_callback_ = callback;
+ }
+
+ void OnRequestDestroyed(DelayableRequest* request) {
+ ASSERT_EQ(1u, delayed_requests_.count(request));
+ delayed_requests_.erase(request);
+ if (delayed_requests_.empty() &&
+ !delayed_request_destruction_callback_.is_null()) {
+ delayed_request_destruction_callback_.Run();
+ delayed_request_destruction_callback_.Reset();
+ }
+ }
+
+ private:
+ int net_error_;
+ bool delay_requests_;
+
+ std::set<DelayableRequest*> delayed_requests_;
+
+ base::Closure delayed_request_destruction_callback_;
+};
+
+class DnsProbeBrowserTestIOThreadHelper {
+ public:
+ void SetUpOnIOThread();
+ void CleanUpOnIOThreadAndDeleteHelper();
+
+ void SetCorrectionServiceNetError(int net_error);
+ void SetCorrectionServiceDelayRequests(bool delay_requests);
+ void SetRequestDestructionCallback(const base::Closure& callback);
+ void InterceptURLLoaderRequest(
+ content::URLLoaderInterceptor::RequestParams* params);
+
+ private:
+ std::unique_ptr<BreakableCorrectionInterceptor> interceptor_;
+};
+
+void DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ CHECK(!interceptor_);
+
+ interceptor_ = std::make_unique<BreakableCorrectionInterceptor>();
+}
+
+void DnsProbeBrowserTestIOThreadHelper::CleanUpOnIOThreadAndDeleteHelper() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ delete this;
+}
+
+void DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceNetError(
+ int net_error) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ interceptor_->set_net_error(net_error);
+}
+
+void DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceDelayRequests(
+ bool delay_requests) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ interceptor_->SetDelayRequests(delay_requests);
+}
+
+void DnsProbeBrowserTestIOThreadHelper::SetRequestDestructionCallback(
+ const base::Closure& callback) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ interceptor_->SetRequestDestructionCallback(callback);
+}
+
+void DnsProbeBrowserTestIOThreadHelper::InterceptURLLoaderRequest(
+ content::URLLoaderInterceptor::RequestParams* params) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ interceptor_->InterceptURLLoaderRequest(params);
+}
+
+class DnsProbeBrowserTest : public InProcessBrowserTest {
+ public:
+ DnsProbeBrowserTest();
+ ~DnsProbeBrowserTest() override;
+
+ void SetUpOnMainThread() override;
+ void TearDownOnMainThread() override;
+
+ protected:
+ bool InterceptURLLoaderRequest(
+ content::URLLoaderInterceptor::RequestParams* params);
+
+ // Sets the browser object that other methods apply to, and that has the
+ // DnsProbeStatus messages of its currently active tab monitored.
+ void SetActiveBrowser(Browser* browser);
+
+ // Sets the results the FakeHostResolver will return for the system and
+ // public DnsProbeRunners. Since this mocks out the
+ // NetworkContext & HostResolver used by the DnsProbeService it doesn't really
+ // give an end-to-end test, but content::TestHostResolver mocks don't affect
+ // the probes since they use HostResolverSource::DNS, so this is the best
+ // that can be done currently.
+ void SetFakeHostResolverResults(
+ std::vector<FakeHostResolver::SingleResult> system_results,
+ std::vector<FakeHostResolver::SingleResult> public_results);
+
+ void SetCorrectionServiceBroken(bool broken);
+ void SetCorrectionServiceDelayRequests(bool delay_requests);
+ void WaitForDelayedRequestDestruction();
+
+ // These functions are often used to wait for two navigations because two
+ // pages are loaded when navigation corrections are enabled: a blank page, so
+ // the user stops seeing the previous page, and then the error page, either
+ // with navigation corrections or without them (If the request failed).
+ void NavigateToDnsError(int num_navigations);
+ void NavigateToOtherError(int num_navigations);
+
+ void StartDelayedProbes(int expected_delayed_probe_count);
+ DnsProbeStatus WaitForSentStatus();
+ int pending_status_count() const { return dns_probe_status_queue_.size(); }
+
+ std::string Title();
+ bool PageContains(const std::string& expected);
+
+ // Checks that the local error page is being displayed, without navigation
+ // corrections, and with the specified status text. The status text should be
+ // either a network error or DNS probe status.
+ void ExpectDisplayingLocalErrorPage(const std::string& status_text);
+
+ // Checks that an error page with mock navigation corrections is being
+ // displayed, with the specified status text. The status text should be either
+ // a network error or DNS probe status.
+ void ExpectDisplayingCorrections(const std::string& status_text);
+
+ private:
+ void OnDnsProbeStatusSent(DnsProbeStatus dns_probe_status);
+
+ network::mojom::NetworkContext* GetNetworkContext() {
+ return network_context_.get();
+ }
+
+ network::mojom::DnsConfigChangeManagerPtr GetDnsConfigChangeManager();
+
+ std::unique_ptr<FakeHostResolverNetworkContext> network_context_;
+ std::unique_ptr<FakeDnsConfigChangeManager> dns_config_change_manager_;
+ DnsProbeBrowserTestIOThreadHelper* helper_;
+ DelayingDnsProbeService* delaying_dns_probe_service_;
+
+ // Browser that methods apply to.
+ Browser* active_browser_;
+ // Helper that current has its DnsProbeStatus messages monitored.
+ NetErrorTabHelper* monitored_tab_helper_;
+
+ std::unique_ptr<base::RunLoop> awaiting_dns_probe_status_run_loop_;
+ // Queue of statuses received but not yet consumed by WaitForSentStatus().
+ std::list<DnsProbeStatus> dns_probe_status_queue_;
+
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+DnsProbeBrowserTest::DnsProbeBrowserTest()
+ : helper_(new DnsProbeBrowserTestIOThreadHelper()),
+ active_browser_(NULL),
+ monitored_tab_helper_(NULL) {}
+
+DnsProbeBrowserTest::~DnsProbeBrowserTest() {
+ // No tests should have any unconsumed probe statuses.
+ EXPECT_EQ(0, pending_status_count());
+}
+
+void DnsProbeBrowserTest::SetUpOnMainThread() {
+ NetErrorTabHelper::set_state_for_testing(NetErrorTabHelper::TESTING_DEFAULT);
+
+ browser()->profile()->GetPrefs()->SetBoolean(
+ prefs::kAlternateErrorPagesEnabled, true);
+
+ base::PostTask(FROM_HERE, {BrowserThread::IO},
+ BindOnce(&DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread,
+ Unretained(helper_)));
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>(
+ base::BindRepeating(&DnsProbeBrowserTest::InterceptURLLoaderRequest,
+ base::Unretained(this)));
+
+ SetActiveBrowser(browser());
+}
+
+void DnsProbeBrowserTest::TearDownOnMainThread() {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::IO},
+ BindOnce(
+ &DnsProbeBrowserTestIOThreadHelper::CleanUpOnIOThreadAndDeleteHelper,
+ Unretained(helper_)));
+
+ url_loader_interceptor_.reset();
+
+ NetErrorTabHelper::set_state_for_testing(NetErrorTabHelper::TESTING_DEFAULT);
+}
+
+bool DnsProbeBrowserTest::InterceptURLLoaderRequest(
+ content::URLLoaderInterceptor::RequestParams* params) {
+ if (params->url_request.url == LinkDoctorBaseURL()) {
+ helper_->InterceptURLLoaderRequest(params);
+ return true;
+ }
+
+ if (params->url_request.url.spec() == "http://mock.http/title2.html") {
+ content::URLLoaderInterceptor::WriteResponse("chrome/test/data/title2.html",
+ params->client.get());
+ return true;
+ }
+
+ // Just returning false is enough to respond to http(s)://mock.failed.request
+ // requests, which are the only requests that come in besides the LinkDoctor
+ // requests.
+ return false;
+}
+
+void DnsProbeBrowserTest::SetActiveBrowser(Browser* browser) {
+ delaying_dns_probe_service_ = static_cast<DelayingDnsProbeService*>(
+ DnsProbeServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+ browser->profile(),
+ base::BindRepeating(
+ &DelayingDnsProbeService::Create,
+ base::BindRepeating(&DnsProbeBrowserTest::GetNetworkContext,
+ base::Unretained(this)),
+ base::BindRepeating(
+ &DnsProbeBrowserTest::GetDnsConfigChangeManager,
+ base::Unretained(this)))));
+ // If currently watching a NetErrorTabHelper, stop doing so before start
+ // watching another.
+ if (monitored_tab_helper_) {
+ monitored_tab_helper_->set_dns_probe_status_snoop_callback_for_testing(
+ NetErrorTabHelper::DnsProbeStatusSnoopCallback());
+ }
+ active_browser_ = browser;
+ monitored_tab_helper_ = NetErrorTabHelper::FromWebContents(
+ active_browser_->tab_strip_model()->GetActiveWebContents());
+ monitored_tab_helper_->set_dns_probe_status_snoop_callback_for_testing(
+ Bind(&DnsProbeBrowserTest::OnDnsProbeStatusSent, Unretained(this)));
+}
+
+void DnsProbeBrowserTest::SetFakeHostResolverResults(
+ std::vector<FakeHostResolver::SingleResult> system_results,
+ std::vector<FakeHostResolver::SingleResult> public_results) {
+ ASSERT_FALSE(network_context_);
+
+ network_context_ = std::make_unique<FakeHostResolverNetworkContext>(
+ std::move(system_results), std::move(public_results));
+}
+
+void DnsProbeBrowserTest::SetCorrectionServiceBroken(bool broken) {
+ int net_error = broken ? net::ERR_NAME_NOT_RESOLVED : net::OK;
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::IO},
+ BindOnce(&DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceNetError,
+ Unretained(helper_), net_error));
+}
+
+void DnsProbeBrowserTest::SetCorrectionServiceDelayRequests(
+ bool delay_requests) {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::IO},
+ BindOnce(
+ &DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceDelayRequests,
+ Unretained(helper_), delay_requests));
+}
+
+void DnsProbeBrowserTest::WaitForDelayedRequestDestruction() {
+ base::RunLoop run_loop;
+ base::PostTask(
+ FROM_HERE, {BrowserThread::IO},
+ BindOnce(
+ &DnsProbeBrowserTestIOThreadHelper::SetRequestDestructionCallback,
+ Unretained(helper_),
+ base::Bind(&RunClosureOnUIThread, run_loop.QuitClosure())));
+ run_loop.Run();
+}
+
+void DnsProbeBrowserTest::NavigateToDnsError(int num_navigations) {
+ NavigateToURLBlockUntilNavigationsComplete(
+ active_browser_,
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED),
+ num_navigations);
+}
+
+void DnsProbeBrowserTest::NavigateToOtherError(int num_navigations) {
+ NavigateToURLBlockUntilNavigationsComplete(
+ active_browser_,
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_REFUSED),
+ num_navigations);
+}
+
+void DnsProbeBrowserTest::StartDelayedProbes(int expected_delayed_probe_count) {
+ ASSERT_TRUE(delaying_dns_probe_service_);
+
+ int actual_delayed_probe_count =
+ delaying_dns_probe_service_->delayed_probe_count();
+ EXPECT_EQ(expected_delayed_probe_count, actual_delayed_probe_count);
+
+ delaying_dns_probe_service_->StartDelayedProbes();
+}
+
+DnsProbeStatus DnsProbeBrowserTest::WaitForSentStatus() {
+ CHECK(!awaiting_dns_probe_status_run_loop_);
+ while (dns_probe_status_queue_.empty()) {
+ awaiting_dns_probe_status_run_loop_ = std::make_unique<base::RunLoop>();
+ awaiting_dns_probe_status_run_loop_->Run();
+ awaiting_dns_probe_status_run_loop_ = nullptr;
+ }
+
+ CHECK(!dns_probe_status_queue_.empty());
+ DnsProbeStatus status = dns_probe_status_queue_.front();
+ dns_probe_status_queue_.pop_front();
+ return status;
+}
+
+// Check title by roundtripping to renderer, to make sure any probe results
+// sent before this have been applied.
+std::string DnsProbeBrowserTest::Title() {
+ std::string title;
+
+ WebContents* contents =
+ active_browser_->tab_strip_model()->GetActiveWebContents();
+
+ bool rv = content::ExecuteScriptAndExtractString(
+ contents, "domAutomationController.send(document.title);", &title);
+ if (!rv)
+ return "";
+
+ return title;
+}
+
+// Check text by roundtripping to renderer, to make sure any probe results
+// sent before this have been applied.
+bool DnsProbeBrowserTest::PageContains(const std::string& expected) {
+ std::string text_content;
+
+ bool rv = content::ExecuteScriptAndExtractString(
+ active_browser_->tab_strip_model()->GetActiveWebContents(),
+ "domAutomationController.send(document.body.textContent);",
+ &text_content);
+ if (!rv)
+ return false;
+
+ return text_content.find(expected) != std::string::npos;
+}
+
+void DnsProbeBrowserTest::ExpectDisplayingLocalErrorPage(
+ const std::string& status_text) {
+ EXPECT_FALSE(PageContains("http://mock.http/title2.html"));
+ EXPECT_TRUE(PageContains(status_text));
+}
+
+void DnsProbeBrowserTest::ExpectDisplayingCorrections(
+ const std::string& status_text) {
+ GURL url("http://mock.http/title2.html");
+ EXPECT_TRUE(PageContains(url.spec()));
+ EXPECT_TRUE(PageContains(status_text));
+}
+
+void DnsProbeBrowserTest::OnDnsProbeStatusSent(
+ DnsProbeStatus dns_probe_status) {
+ dns_probe_status_queue_.push_back(dns_probe_status);
+ if (awaiting_dns_probe_status_run_loop_)
+ awaiting_dns_probe_status_run_loop_->Quit();
+}
+
+network::mojom::DnsConfigChangeManagerPtr
+DnsProbeBrowserTest::GetDnsConfigChangeManager() {
+ network::mojom::DnsConfigChangeManagerPtr dns_config_change_manager_ptr;
+ dns_config_change_manager_ = std::make_unique<FakeDnsConfigChangeManager>(
+ mojo::MakeRequest(&dns_config_change_manager_ptr));
+ return dns_config_change_manager_ptr;
+}
+
+// Test Fixture for tests where the DNS probes should succeed.
+class DnsProbeSuccessfulProbesTest : public DnsProbeBrowserTest {
+ void SetUpOnMainThread() override {
+ SetFakeHostResolverResults(
+ {{net::OK, FakeHostResolver::kOneAddressResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse}});
+ DnsProbeBrowserTest::SetUpOnMainThread();
+ }
+};
+
+// Test Fixture for tests where the DNS probes should not resolve.
+class DnsProbeFailingProbesTest : public DnsProbeBrowserTest {
+ void SetUpOnMainThread() override {
+ SetFakeHostResolverResults(
+ {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}},
+ {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+ DnsProbeBrowserTest::SetUpOnMainThread();
+ }
+};
+
+// Test Fixture for tests where the DNS probes should fail to connect to a DNS
+// server (timeout or unreachable host).
+class DnsProbeUnreachableProbesTest : public DnsProbeBrowserTest {
+ void SetUpOnMainThread() override {
+ SetFakeHostResolverResults(
+ {{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+ DnsProbeBrowserTest::SetUpOnMainThread();
+ }
+};
+
+// Make sure probes don't break non-DNS error pages when corrections load.
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
+ OtherErrorWithCorrectionsSuccess) {
+ SetCorrectionServiceBroken(false);
+
+ NavigateToOtherError(2);
+ ExpectDisplayingCorrections("ERR_CONNECTION_REFUSED");
+}
+
+// Make sure probes don't break non-DNS error pages when corrections failed to
+// load.
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
+ OtherErrorWithCorrectionsFailure) {
+ SetCorrectionServiceBroken(true);
+
+ NavigateToOtherError(2);
+ ExpectDisplayingLocalErrorPage("ERR_CONNECTION_REFUSED");
+}
+
+// Make sure probes don't break DNS error pages when corrections load.
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
+ NxdomainProbeResultWithWorkingCorrections) {
+ SetCorrectionServiceBroken(false);
+
+ NavigateToDnsError(2);
+ ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
+
+ // One status for committing a blank page before the corrections, and one for
+ // when the error page with corrections is committed.
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
+}
+
+// Make sure probes don't break corrections when probes complete before the
+// corrections load.
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
+ NxdomainProbeResultWithWorkingSlowCorrections) {
+ SetCorrectionServiceBroken(false);
+ SetCorrectionServiceDelayRequests(true);
+
+ NavigateToDnsError(1);
+ // A blank page should be displayed while the corrections are loaded.
+ EXPECT_EQ("", Title());
+
+ // A single probe should be triggered by the error page load, and it should
+ // be ignored.
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_EQ("", Title());
+
+ StartDelayedProbes(1);
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_EQ("", Title());
+
+ content::TestNavigationObserver observer(
+ browser()->tab_strip_model()->GetActiveWebContents(), 1);
+ // The corrections finish loading.
+ SetCorrectionServiceDelayRequests(false);
+ // Wait for it to commit.
+ observer.Wait();
+ ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
+
+ // Committing the corections page should trigger sending the probe result
+ // again.
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, WaitForSentStatus());
+ ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
+}
+
+// Make sure probes update DNS error page properly when they're supposed to.
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest,
+ NoInternetProbeResultWithBrokenCorrections) {
+ SetCorrectionServiceBroken(true);
+
+ NavigateToDnsError(2);
+
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NO_INTERNET, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("DNS_PROBE_FINISHED_NO_INTERNET");
+}
+
+// Make sure probes don't break corrections when probes complete before the
+// corrections request returns an error.
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest,
+ NoInternetProbeResultWithSlowBrokenCorrections) {
+ SetCorrectionServiceBroken(true);
+ SetCorrectionServiceDelayRequests(true);
+
+ NavigateToDnsError(1);
+ // A blank page should be displayed while the corrections load.
+ EXPECT_EQ("", Title());
+
+ // A single probe should be triggered by the error page load, and it should
+ // be ignored.
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_EQ("", Title());
+
+ StartDelayedProbes(1);
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NO_INTERNET, WaitForSentStatus());
+ EXPECT_EQ("", Title());
+ EXPECT_EQ(0, pending_status_count());
+
+ content::TestNavigationObserver observer(
+ browser()->tab_strip_model()->GetActiveWebContents(), 1);
+ // The corrections request fails.
+ SetCorrectionServiceDelayRequests(false);
+ // Wait for the DNS error page to load instead.
+ observer.Wait();
+ // The page committing should result in sending the probe results again.
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NO_INTERNET, WaitForSentStatus());
+
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("DNS_PROBE_FINISHED_NO_INTERNET");
+}
+
+// Double-check to make sure sync failures don't explode.
+IN_PROC_BROWSER_TEST_F(DnsProbeFailingProbesTest,
+ SyncFailureWithBrokenCorrections) {
+ SetCorrectionServiceBroken(true);
+ NavigateToDnsError(2);
+
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
+ EXPECT_EQ(0, pending_status_count());
+}
+
+// Test that pressing the stop button cancels loading corrections.
+// TODO(mmenke): Add a test for the cross process navigation case.
+// TODO(mmenke): This test could flakily pass due to the timeout on downloading
+// the corrections. Disable that timeout for browser tests.
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest, CorrectionsLoadStopped) {
+ SetCorrectionServiceDelayRequests(true);
+ SetCorrectionServiceBroken(true);
+
+ NavigateToDnsError(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+ StartDelayedProbes(1);
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NO_INTERNET, WaitForSentStatus());
+
+ EXPECT_EQ("", Title());
+ EXPECT_EQ(0, pending_status_count());
+
+ chrome::Stop(browser());
+ WaitForDelayedRequestDestruction();
+
+ // End up displaying a blank page.
+ EXPECT_EQ("", Title());
+}
+
+// Test that pressing the stop button cancels the load of corrections, and
+// receiving a probe result afterwards does not swap in a DNS error page.
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest,
+ CorrectionsLoadStoppedSlowProbe) {
+ SetCorrectionServiceDelayRequests(true);
+ SetCorrectionServiceBroken(true);
+
+ NavigateToDnsError(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ EXPECT_EQ("", Title());
+ EXPECT_EQ(0, pending_status_count());
+
+ chrome::Stop(browser());
+ WaitForDelayedRequestDestruction();
+
+ EXPECT_EQ("", Title());
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NO_INTERNET, WaitForSentStatus());
+
+ EXPECT_EQ("", Title());
+}
+
+// Make sure probes don't run for subframe DNS errors.
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest, NoProbeInSubframe) {
+ SetCorrectionServiceBroken(false);
+
+ NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/iframe_dns_error.html"));
+
+ // By the time NavigateToURL returns, the browser will have seen the failed
+ // provisional load. If a probe was started (or considered but not run),
+ // then the NetErrorTabHelper would have sent a NetErrorInfo message. Thus,
+ // if one hasn't been sent by now, the NetErrorTabHelper has not (and won't)
+ // start a probe for this DNS error.
+ EXPECT_EQ(0, pending_status_count());
+}
+
+// Make sure browser sends NOT_RUN properly when probes are disabled.
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest, ProbesDisabled) {
+ // Disable probes (And corrections).
+ browser()->profile()->GetPrefs()->SetBoolean(
+ prefs::kAlternateErrorPagesEnabled, false);
+
+ SetCorrectionServiceBroken(true);
+
+ NavigateToDnsError(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_NOT_RUN, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
+}
+
+// Test the case that corrections are disabled, but DNS probes are enabled.
+// This is the case with Chromium builds.
+IN_PROC_BROWSER_TEST_F(DnsProbeFailingProbesTest, CorrectionsDisabled) {
+ // Disable corrections.
+ browser()->profile()->GetPrefs()->SetBoolean(
+ prefs::kAlternateErrorPagesEnabled, false);
+ // Requests to the correction service should work if any are made, so the test
+ // fails if that happens unexpectedly.
+ SetCorrectionServiceBroken(false);
+ // Normally disabling corrections disables DNS probes, so force DNS probes
+ // to be enabled.
+ NetErrorTabHelper::set_state_for_testing(
+ NetErrorTabHelper::TESTING_FORCE_ENABLED);
+
+ // Just one commit and one sent status, since corrections are disabled.
+ NavigateToDnsError(1);
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
+}
+
+// Test incognito mode. Corrections should be disabled, but DNS probes are
+// still enabled.
+IN_PROC_BROWSER_TEST_F(DnsProbeFailingProbesTest, Incognito) {
+ // Requests to the correction service should work if any are made, so the test
+ // will fail if one is requested unexpectedly.
+ SetCorrectionServiceBroken(false);
+
+ Browser* incognito = CreateIncognitoBrowser();
+ SetActiveBrowser(incognito);
+
+ // Just one commit and one sent status, since the corrections are disabled.
+ NavigateToDnsError(1);
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ // Checking the page runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
+}
+
+} // namespace
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_probe_runner.cc b/chromium/chrome/browser/net/dns_probe_runner.cc
new file mode 100644
index 00000000000..c644edb9aec
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_runner.cc
@@ -0,0 +1,139 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_probe_runner.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+
+namespace chrome_browser_net {
+
+const char DnsProbeRunner::kKnownGoodHostname[] = "google.com";
+
+namespace {
+
+DnsProbeRunner::Result EvaluateResponse(
+ int net_error,
+ const base::Optional<net::AddressList>& resolved_addresses) {
+ switch (net_error) {
+ case net::OK:
+ break;
+
+ case net::ERR_FAILED:
+ // ERR_DNS_CACHE_MISS means HostResolver was not able to attempt DNS, e.g.
+ // due to limitations from Chrome build configuration or because of
+ // incompatibilities with the DNS configuration from the system. The result
+ // Chrome would have gotten from DNS if attempted is therefore unknown.
+ case net::ERR_DNS_CACHE_MISS:
+ return DnsProbeRunner::UNKNOWN;
+
+ // ERR_NAME_NOT_RESOLVED maps to NXDOMAIN, which means the server is working
+ // but returned a wrong answer.
+ case net::ERR_NAME_NOT_RESOLVED:
+ return DnsProbeRunner::INCORRECT;
+
+ // These results mean we heard *something* from the DNS server, but it was
+ // unsuccessful (SERVFAIL) or malformed.
+ case net::ERR_DNS_MALFORMED_RESPONSE:
+ case net::ERR_DNS_SERVER_REQUIRES_TCP: // Shouldn't happen; DnsTransaction
+ // will retry with TCP.
+ case net::ERR_DNS_SERVER_FAILED:
+ case net::ERR_DNS_SORT_ERROR: // Can only happen if the server responds.
+ return DnsProbeRunner::FAILING;
+
+ // Any other error means we never reached the DNS server in the first place.
+ case net::ERR_DNS_TIMED_OUT:
+ default:
+ // Something else happened, probably at a network level.
+ return DnsProbeRunner::UNREACHABLE;
+ }
+
+ if (!resolved_addresses) {
+ // If net_error is OK, resolved_addresses should be set. The binding is not
+ // closed here since it will be closed by the caller anyway.
+ mojo::ReportBadMessage("resolved_addresses not set when net_error=OK");
+ return DnsProbeRunner::UNKNOWN;
+ } else if (resolved_addresses.value().empty()) {
+ return DnsProbeRunner::INCORRECT;
+ } else {
+ return DnsProbeRunner::CORRECT;
+ }
+}
+
+} // namespace
+
+DnsProbeRunner::DnsProbeRunner(
+ net::DnsConfigOverrides dns_config_overrides,
+ const NetworkContextGetter& network_context_getter)
+ : dns_config_overrides_(dns_config_overrides),
+ network_context_getter_(network_context_getter) {
+ CreateHostResolver();
+}
+
+DnsProbeRunner::~DnsProbeRunner() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void DnsProbeRunner::RunProbe(base::OnceClosure callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+ DCHECK(host_resolver_);
+ DCHECK(callback_.is_null());
+ DCHECK(!receiver_.is_bound());
+
+ network::mojom::ResolveHostParametersPtr parameters =
+ network::mojom::ResolveHostParameters::New();
+ parameters->dns_query_type = net::DnsQueryType::A;
+ parameters->source = net::HostResolverSource::DNS;
+ parameters->allow_cached_response = false;
+
+ host_resolver_->ResolveHost(net::HostPortPair(kKnownGoodHostname, 80),
+ std::move(parameters),
+ receiver_.BindNewPipeAndPassRemote());
+ receiver_.set_disconnect_handler(base::BindOnce(
+ &DnsProbeRunner::OnMojoConnectionError, base::Unretained(this)));
+
+ callback_ = std::move(callback);
+}
+
+bool DnsProbeRunner::IsRunning() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return !callback_.is_null();
+}
+
+void DnsProbeRunner::OnComplete(
+ int32_t result,
+ const base::Optional<net::AddressList>& resolved_addresses) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback_.is_null());
+
+ result_ = EvaluateResponse(result, resolved_addresses);
+ receiver_.reset();
+
+ // ResolveHost will call OnComplete asynchronously, so callback_ can be
+ // invoked directly here. Clear callback in case it starts a new probe
+ // immediately.
+ std::move(callback_).Run();
+}
+
+void DnsProbeRunner::CreateHostResolver() {
+ host_resolver_.reset();
+ network_context_getter_.Run()->CreateHostResolver(
+ dns_config_overrides_, host_resolver_.BindNewPipeAndPassReceiver());
+}
+
+void DnsProbeRunner::OnMojoConnectionError() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CreateHostResolver();
+ OnComplete(net::ERR_FAILED, base::nullopt);
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_probe_runner.h b/chromium/chrome/browser/net/dns_probe_runner.h
new file mode 100644
index 00000000000..65a64e99c2b
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_runner.h
@@ -0,0 +1,100 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_RUNNER_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_RUNNER_H_
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/cpp/resolve_host_client_base.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+
+namespace network {
+namespace mojom {
+class NetworkContext;
+}
+} // namespace network
+
+namespace chrome_browser_net {
+
+// Runs DNS probes using a HostResolver and evaluates the responses.
+// (Currently requests A records for google.com and expects at least one IP
+// address in the response.)
+// Used by DnsProbeService to probe the system and public DNS configurations.
+class DnsProbeRunner : public network::ResolveHostClientBase {
+ public:
+ static const char kKnownGoodHostname[];
+
+ using NetworkContextGetter =
+ base::RepeatingCallback<network::mojom::NetworkContext*(void)>;
+
+ // Used in histograms; add new entries at the bottom, and don't remove any.
+ enum Result {
+ UNKNOWN,
+ CORRECT, // Response contains at least one A record.
+ INCORRECT, // Response claimed success but included no A records.
+ FAILING, // Response included an error or was malformed.
+ UNREACHABLE // No response received (timeout, network unreachable, etc.).
+ };
+
+ // Creates a probe runner that will use |dns_config_overrides| for the dns
+ // configuration and will use |network_context_getter| to get the
+ // NetworkContext to create the HostResolver. The |network_context_getter|
+ // may be called multiple times.
+ DnsProbeRunner(net::DnsConfigOverrides dns_config_overrides,
+ const NetworkContextGetter& network_context_getter);
+ ~DnsProbeRunner() override;
+
+ // Starts a probe. |callback| will be called asynchronously when the result
+ // is ready, and will not be called if the DnsProbeRunner is destroyed before
+ // the probe finishes. Must not be called again until the callback is called,
+ // but may be called during the callback.
+ void RunProbe(base::OnceClosure callback);
+
+ // Returns true if a probe is running. Guaranteed to return true after
+ // RunProbe returns, and false during and after the callback.
+ bool IsRunning() const;
+
+ // Returns the result of the last probe.
+ Result result() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return result_;
+ }
+
+ // network::ResolveHostClientBase impl:
+ void OnComplete(
+ int32_t result,
+ const base::Optional<net::AddressList>& resolved_addresses) override;
+
+ private:
+ void CreateHostResolver();
+ void OnMojoConnectionError();
+
+ mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this};
+
+ net::DnsConfigOverrides dns_config_overrides_;
+ NetworkContextGetter network_context_getter_;
+
+ mojo::Remote<network::mojom::HostResolver> host_resolver_;
+
+ // The callback passed to |RunProbe|. Cleared right before calling the
+ // callback.
+ base::OnceClosure callback_;
+
+ Result result_{UNKNOWN};
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(DnsProbeRunner);
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_PROBE_RUNNER_H_
diff --git a/chromium/chrome/browser/net/dns_probe_runner_unittest.cc b/chromium/chrome/browser/net/dns_probe_runner_unittest.cc
new file mode 100644
index 00000000000..a967f32ef44
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_runner_unittest.cc
@@ -0,0 +1,204 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_probe_runner.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "chrome/browser/net/dns_probe_test_util.h"
+#include "content/public/test/browser_task_environment.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "services/network/test/test_network_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::RunLoop;
+using content::BrowserTaskEnvironment;
+
+namespace chrome_browser_net {
+
+namespace {
+
+class TestDnsProbeRunnerCallback {
+ public:
+ TestDnsProbeRunnerCallback() : called_(false) {}
+
+ base::OnceClosure callback() {
+ return base::BindOnce(&TestDnsProbeRunnerCallback::OnCalled,
+ base::Unretained(this));
+ }
+ bool called() const { return called_; }
+
+ private:
+ void OnCalled() {
+ EXPECT_FALSE(called_);
+ called_ = true;
+ }
+
+ bool called_;
+};
+
+class FakeNetworkContext : public network::TestNetworkContext {
+ public:
+ explicit FakeNetworkContext(
+ std::vector<FakeHostResolver::SingleResult> result_list)
+ : result_list_(std::move(result_list)) {}
+
+ void CreateHostResolver(
+ const base::Optional<net::DnsConfigOverrides>& config_overrides,
+ mojo::PendingReceiver<network::mojom::HostResolver> receiver) override {
+ ASSERT_FALSE(resolver_);
+ resolver_ = std::make_unique<FakeHostResolver>(std::move(receiver),
+ std::move(result_list_));
+ }
+
+ private:
+ std::unique_ptr<FakeHostResolver> resolver_;
+ std::vector<FakeHostResolver::SingleResult> result_list_;
+};
+
+class FirstHangingThenFakeResolverNetworkContext
+ : public network::TestNetworkContext {
+ public:
+ explicit FirstHangingThenFakeResolverNetworkContext(
+ std::vector<FakeHostResolver::SingleResult> result_list)
+ : result_list_(std::move(result_list)) {}
+
+ void CreateHostResolver(
+ const base::Optional<net::DnsConfigOverrides>& config_overrides,
+ mojo::PendingReceiver<network::mojom::HostResolver> receiver) override {
+ if (call_num == 0) {
+ resolver_ = std::make_unique<HangingHostResolver>(std::move(receiver));
+ } else {
+ resolver_ = std::make_unique<FakeHostResolver>(std::move(receiver),
+ std::move(result_list_));
+ }
+ call_num++;
+ }
+
+ void DestroyHostResolver() { resolver_ = nullptr; }
+
+ private:
+ int call_num = 0;
+ std::unique_ptr<network::mojom::HostResolver> resolver_;
+ std::vector<FakeHostResolver::SingleResult> result_list_;
+};
+
+class DnsProbeRunnerTest : public testing::Test {
+ protected:
+ void SetupTest(int query_result, FakeHostResolver::Response query_response);
+ void SetupTest(std::vector<FakeHostResolver::SingleResult> result_list);
+ void RunTest(DnsProbeRunner::Result expected_probe_results);
+
+ network::mojom::NetworkContext* network_context() const {
+ return network_context_.get();
+ }
+
+ BrowserTaskEnvironment task_environment_;
+ std::unique_ptr<network::mojom::NetworkContext> network_context_;
+ std::unique_ptr<DnsProbeRunner> runner_;
+};
+
+void DnsProbeRunnerTest::SetupTest(int query_result,
+ FakeHostResolver::Response query_response) {
+ SetupTest({{query_result, query_response}});
+}
+
+void DnsProbeRunnerTest::SetupTest(
+ std::vector<FakeHostResolver::SingleResult> result_list) {
+ network_context_ =
+ std::make_unique<FakeNetworkContext>(std::move(result_list));
+ runner_ = std::make_unique<DnsProbeRunner>(
+ net::DnsConfigOverrides(),
+ base::BindRepeating(&DnsProbeRunnerTest::network_context,
+ base::Unretained(this)));
+}
+
+void DnsProbeRunnerTest::RunTest(DnsProbeRunner::Result expected_probe_result) {
+ TestDnsProbeRunnerCallback callback;
+ runner_->RunProbe(callback.callback());
+ EXPECT_TRUE(runner_->IsRunning());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(runner_->IsRunning());
+ EXPECT_TRUE(callback.called());
+ EXPECT_EQ(expected_probe_result, runner_->result());
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_OK) {
+ SetupTest(net::OK, FakeHostResolver::kOneAddressResponse);
+ RunTest(DnsProbeRunner::CORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_EMPTY) {
+ SetupTest(net::OK, FakeHostResolver::kEmptyResponse);
+ RunTest(DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_TIMEOUT) {
+ SetupTest(net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse);
+ RunTest(DnsProbeRunner::UNREACHABLE);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_NXDOMAIN) {
+ SetupTest(net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse);
+ RunTest(DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_FAILING) {
+ SetupTest(net::ERR_DNS_SERVER_FAILED, FakeHostResolver::kNoResponse);
+ RunTest(DnsProbeRunner::FAILING);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_DnsNotRun) {
+ SetupTest(net::ERR_DNS_CACHE_MISS, FakeHostResolver::kNoResponse);
+ RunTest(DnsProbeRunner::UNKNOWN);
+}
+
+TEST_F(DnsProbeRunnerTest, TwoProbes) {
+ SetupTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+ RunTest(DnsProbeRunner::CORRECT);
+ RunTest(DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, MojoConnectionError) {
+ // Use a HostResolverGetter that returns a HangingHostResolver on the first
+ // call, and a FakeHostResolver on the second call.
+ FirstHangingThenFakeResolverNetworkContext network_context(
+ {{net::OK, FakeHostResolver::kOneAddressResponse}});
+ runner_ = std::make_unique<DnsProbeRunner>(
+ net::DnsConfigOverrides(),
+ base::BindRepeating(
+ [](network::mojom::NetworkContext* context) { return context; },
+ base::Unretained(&network_context)));
+
+ TestDnsProbeRunnerCallback callback;
+ runner_->RunProbe(callback.callback());
+ EXPECT_TRUE(runner_->IsRunning());
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(runner_->IsRunning());
+ EXPECT_FALSE(callback.called());
+ // Destroy the HangingHostResolver while runner_ is still waiting for a
+ // response. The set_connection_error_handler callback should be invoked.
+ network_context.DestroyHostResolver();
+ RunLoop().RunUntilIdle();
+ // That should cause the RunProbe callback to be called with an UNKNOWN
+ // status since the HostResolver request never returned.
+ EXPECT_FALSE(runner_->IsRunning());
+ EXPECT_TRUE(callback.called());
+ EXPECT_EQ(DnsProbeRunner::UNKNOWN, runner_->result());
+
+ // Try another probe. The DnsProbeRunner should call the HostResolverGetter
+ // again, this time getting a FakeHostResolver that will successfully return
+ // an OK result.
+ RunTest(DnsProbeRunner::CORRECT);
+}
+
+} // namespace
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_probe_service.h b/chromium/chrome/browser/net/dns_probe_service.h
new file mode 100644
index 00000000000..90b1470e10f
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_service.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_SERVICE_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_SERVICE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback.h"
+#include "components/error_page/common/net_error_info.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace chrome_browser_net {
+
+// Probes the system and public DNS servers to determine the (probable) cause
+// of a recent DNS-related page load error. Coalesces multiple probe requests
+// (perhaps from multiple tabs) and caches the results.
+//
+// Uses a single DNS attempt per config, and doesn't randomize source ports.
+//
+// Use DnsProbeServiceFactory to get a service handle.
+class DnsProbeService : public KeyedService {
+ public:
+ using ProbeCallback =
+ base::OnceCallback<void(error_page::DnsProbeStatus result)>;
+
+ // Starts a DNS probe, or uses an existing probe result if a probe is already
+ // in progress or recently completed. |callback| will be called
+ // asynchronously with the probe result.
+ virtual void ProbeDns(ProbeCallback callback) = 0;
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_PROBE_SERVICE_H_
diff --git a/chromium/chrome/browser/net/dns_probe_service_factory.cc b/chromium/chrome/browser/net/dns_probe_service_factory.cc
new file mode 100644
index 00000000000..dca6db29911
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_service_factory.cc
@@ -0,0 +1,375 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_probe_service_factory.h"
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/time.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/public/dns_protocol.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+namespace chrome_browser_net {
+
+namespace {
+
+// How long the DnsProbeService will cache the probe result for.
+// If it's older than this and we get a probe request, the service expires it
+// and starts a new probe.
+const int kMaxResultAgeMs = 5000;
+
+// The public DNS servers used by the DnsProbeService to verify internet
+// connectivity.
+const uint8_t kGooglePublicDns1[] = {8, 8, 8, 8};
+const uint8_t kGooglePublicDns2[] = {8, 8, 4, 4};
+
+error_page::DnsProbeStatus EvaluateResults(
+ DnsProbeRunner::Result system_result,
+ DnsProbeRunner::Result public_result) {
+ // If the system DNS is working, assume the domain doesn't exist.
+ if (system_result == DnsProbeRunner::CORRECT)
+ return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
+
+ // If the system DNS is unknown (e.g. on Android), but the public server is
+ // reachable, assume the domain doesn't exist.
+ if (system_result == DnsProbeRunner::UNKNOWN &&
+ public_result == DnsProbeRunner::CORRECT) {
+ return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
+ }
+
+ // If the system DNS is not working but another public server is, assume the
+ // DNS config is bad (or perhaps the DNS servers are down or broken).
+ if (public_result == DnsProbeRunner::CORRECT)
+ return error_page::DNS_PROBE_FINISHED_BAD_CONFIG;
+
+ // If the system DNS is not working and another public server is unreachable,
+ // assume the internet connection is down (note that system DNS may be a
+ // router on the LAN, so it may be reachable but returning errors.)
+ if (public_result == DnsProbeRunner::UNREACHABLE)
+ return error_page::DNS_PROBE_FINISHED_NO_INTERNET;
+
+ // Otherwise: the system DNS is not working and another public server is
+ // responding but with errors or incorrect results. This is an awkward case;
+ // an invasive captive portal or a restrictive firewall may be intercepting
+ // or rewriting DNS traffic, or the public server may itself be failing or
+ // down.
+ return error_page::DNS_PROBE_FINISHED_INCONCLUSIVE;
+}
+
+void HistogramProbe(error_page::DnsProbeStatus status,
+ base::TimeDelta elapsed) {
+ DCHECK(error_page::DnsProbeStatusIsFinished(status));
+
+ UMA_HISTOGRAM_ENUMERATION("DnsProbe.ProbeResult", status,
+ error_page::DNS_PROBE_MAX);
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.ProbeDuration2", elapsed);
+}
+
+network::mojom::NetworkContext* GetNetworkContextForProfile(
+ content::BrowserContext* context) {
+ return content::BrowserContext::GetDefaultStoragePartition(context)
+ ->GetNetworkContext();
+}
+
+network::mojom::DnsConfigChangeManagerPtr GetDnsConfigChangeManager() {
+ network::mojom::DnsConfigChangeManagerPtr dns_config_change_manager_ptr;
+ content::GetNetworkService()->GetDnsConfigChangeManager(
+ mojo::MakeRequest(&dns_config_change_manager_ptr));
+ return dns_config_change_manager_ptr;
+}
+
+net::DnsConfigOverrides SystemOverrides() {
+ net::DnsConfigOverrides overrides;
+ overrides.search = {};
+ overrides.attempts = 1;
+ overrides.randomize_ports = false;
+ return overrides;
+}
+
+net::DnsConfigOverrides PublicOverrides() {
+ net::DnsConfigOverrides overrides =
+ net::DnsConfigOverrides::CreateOverridingEverythingWithDefaults();
+ overrides.nameservers = std::vector<net::IPEndPoint>{
+ net::IPEndPoint(net::IPAddress(kGooglePublicDns1),
+ net::dns_protocol::kDefaultPort),
+ net::IPEndPoint(net::IPAddress(kGooglePublicDns2),
+ net::dns_protocol::kDefaultPort)};
+ overrides.attempts = 1;
+ overrides.randomize_ports = false;
+ return overrides;
+}
+
+class DnsProbeServiceImpl
+ : public DnsProbeService,
+ public network::mojom::DnsConfigChangeManagerClient {
+ public:
+ using NetworkContextGetter = DnsProbeServiceFactory::NetworkContextGetter;
+ using DnsConfigChangeManagerGetter =
+ DnsProbeServiceFactory::DnsConfigChangeManagerGetter;
+
+ explicit DnsProbeServiceImpl(content::BrowserContext* context);
+ DnsProbeServiceImpl(
+ const NetworkContextGetter& network_context_getter,
+ const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+ const base::TickClock* tick_clock);
+ ~DnsProbeServiceImpl() override;
+
+ // DnsProbeService implementation:
+ void ProbeDns(DnsProbeService::ProbeCallback callback) override;
+
+ // mojom::network::DnsConfigChangeManagerClient implementation:
+ void OnSystemDnsConfigChanged() override;
+
+ private:
+ enum State {
+ STATE_NO_RESULT,
+ STATE_PROBE_RUNNING,
+ STATE_RESULT_CACHED,
+ };
+
+ // Starts a probe (runs system and public probes).
+ void StartProbes();
+ void OnProbeComplete();
+ // Calls all |pending_callbacks_| with the |cached_result_|.
+ void CallCallbacks();
+ // Calls callback by posting a task to the same sequence. |pending_callbacks_|
+ // must have exactly one element.
+ void CallCallbackAsynchronously();
+ // Clears a cached probe result.
+ void ClearCachedResult();
+
+ bool CachedResultIsExpired() const;
+
+ void SetupDnsConfigChangeNotifications();
+ void OnDnsConfigChangeManagerConnectionError();
+
+ State state_;
+ std::vector<DnsProbeService::ProbeCallback> pending_callbacks_;
+ base::TimeTicks probe_start_time_;
+ error_page::DnsProbeStatus cached_result_;
+
+ NetworkContextGetter network_context_getter_;
+ DnsConfigChangeManagerGetter dns_config_change_manager_getter_;
+ mojo::Receiver<network::mojom::DnsConfigChangeManagerClient> receiver_{this};
+ // DnsProbeRunners for the system DNS configuration and a public DNS server.
+ DnsProbeRunner system_runner_;
+ DnsProbeRunner public_runner_;
+
+ // Time source for cache expiry.
+ const base::TickClock* tick_clock_; // Not owned.
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(DnsProbeServiceImpl);
+};
+
+DnsProbeServiceImpl::DnsProbeServiceImpl(content::BrowserContext* context)
+ : DnsProbeServiceImpl(
+ base::BindRepeating(&GetNetworkContextForProfile, context),
+ base::BindRepeating(&GetDnsConfigChangeManager),
+ base::DefaultTickClock::GetInstance()) {}
+
+DnsProbeServiceImpl::DnsProbeServiceImpl(
+ const NetworkContextGetter& network_context_getter,
+ const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+ const base::TickClock* tick_clock)
+ : state_(STATE_NO_RESULT),
+ network_context_getter_(network_context_getter),
+ dns_config_change_manager_getter_(dns_config_change_manager_getter),
+ system_runner_(SystemOverrides(), network_context_getter),
+ public_runner_(PublicOverrides(), network_context_getter),
+ tick_clock_(tick_clock) {
+ SetupDnsConfigChangeNotifications();
+}
+
+DnsProbeServiceImpl::~DnsProbeServiceImpl() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void DnsProbeServiceImpl::ProbeDns(ProbeCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ pending_callbacks_.push_back(std::move(callback));
+
+ if (CachedResultIsExpired())
+ ClearCachedResult();
+
+ switch (state_) {
+ case STATE_NO_RESULT:
+ StartProbes();
+ break;
+ case STATE_RESULT_CACHED:
+ CallCallbackAsynchronously();
+ break;
+ case STATE_PROBE_RUNNING:
+ // Do nothing; probe is already running, and will call the callback.
+ break;
+ }
+}
+
+void DnsProbeServiceImpl::OnSystemDnsConfigChanged() {
+ ClearCachedResult();
+}
+
+void DnsProbeServiceImpl::StartProbes() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(STATE_NO_RESULT, state_);
+
+ DCHECK(!system_runner_.IsRunning());
+ DCHECK(!public_runner_.IsRunning());
+
+ // Unretained safe because the callback will not be run if the DnsProbeRunner
+ // is destroyed.
+ system_runner_.RunProbe(base::BindOnce(&DnsProbeServiceImpl::OnProbeComplete,
+ base::Unretained(this)));
+ public_runner_.RunProbe(base::BindOnce(&DnsProbeServiceImpl::OnProbeComplete,
+ base::Unretained(this)));
+ probe_start_time_ = tick_clock_->NowTicks();
+ state_ = STATE_PROBE_RUNNING;
+
+ DCHECK(system_runner_.IsRunning());
+ DCHECK(public_runner_.IsRunning());
+}
+
+void DnsProbeServiceImpl::OnProbeComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(STATE_PROBE_RUNNING, state_);
+ DCHECK(!system_runner_.IsRunning() || !public_runner_.IsRunning());
+
+ if (system_runner_.IsRunning() || public_runner_.IsRunning())
+ return;
+
+ cached_result_ =
+ EvaluateResults(system_runner_.result(), public_runner_.result());
+ state_ = STATE_RESULT_CACHED;
+
+ HistogramProbe(cached_result_, tick_clock_->NowTicks() - probe_start_time_);
+
+ CallCallbacks();
+}
+
+void DnsProbeServiceImpl::CallCallbacks() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(STATE_RESULT_CACHED, state_);
+ DCHECK(error_page::DnsProbeStatusIsFinished(cached_result_));
+ DCHECK(!pending_callbacks_.empty());
+
+ std::vector<ProbeCallback> callbacks;
+ callbacks.swap(pending_callbacks_);
+
+ for (std::vector<ProbeCallback>::iterator i = callbacks.begin();
+ i != callbacks.end(); ++i) {
+ std::move(*i).Run(cached_result_);
+ }
+}
+
+void DnsProbeServiceImpl::CallCallbackAsynchronously() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(STATE_RESULT_CACHED, state_);
+ DCHECK(error_page::DnsProbeStatusIsFinished(cached_result_));
+ DCHECK_EQ(1U, pending_callbacks_.size());
+
+ std::vector<ProbeCallback> callbacks;
+ callbacks.swap(pending_callbacks_);
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callbacks.front()), cached_result_));
+}
+
+void DnsProbeServiceImpl::ClearCachedResult() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (state_ == STATE_RESULT_CACHED) {
+ state_ = STATE_NO_RESULT;
+ cached_result_ = error_page::DNS_PROBE_MAX;
+ }
+}
+
+bool DnsProbeServiceImpl::CachedResultIsExpired() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (state_ != STATE_RESULT_CACHED)
+ return false;
+
+ const base::TimeDelta kMaxResultAge =
+ base::TimeDelta::FromMilliseconds(kMaxResultAgeMs);
+ return tick_clock_->NowTicks() - probe_start_time_ > kMaxResultAge;
+}
+
+void DnsProbeServiceImpl::SetupDnsConfigChangeNotifications() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ dns_config_change_manager_getter_.Run()->RequestNotifications(
+ receiver_.BindNewPipeAndPassRemote());
+ receiver_.set_disconnect_handler(base::BindRepeating(
+ &DnsProbeServiceImpl::OnDnsConfigChangeManagerConnectionError,
+ base::Unretained(this)));
+}
+
+void DnsProbeServiceImpl::OnDnsConfigChangeManagerConnectionError() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Clear the cache, since the configuration may have changed while not
+ // getting notifications.
+ ClearCachedResult();
+
+ receiver_.reset();
+
+ SetupDnsConfigChangeNotifications();
+}
+
+} // namespace
+
+DnsProbeService* DnsProbeServiceFactory::GetForContext(
+ content::BrowserContext* browser_context) {
+ return static_cast<DnsProbeService*>(
+ GetInstance()->GetServiceForBrowserContext(browser_context,
+ true /* create */));
+}
+
+DnsProbeServiceFactory* DnsProbeServiceFactory::GetInstance() {
+ return base::Singleton<DnsProbeServiceFactory>::get();
+}
+
+DnsProbeServiceFactory::DnsProbeServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "DnsProbeService",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+DnsProbeServiceFactory::~DnsProbeServiceFactory() {}
+
+KeyedService* DnsProbeServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new DnsProbeServiceImpl(context);
+}
+
+content::BrowserContext* DnsProbeServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ // Create separate service for incognito profiles.
+ return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
+
+// static
+std::unique_ptr<DnsProbeService> DnsProbeServiceFactory::CreateForTesting(
+ const NetworkContextGetter& network_context_getter,
+ const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+ const base::TickClock* tick_clock) {
+ return std::make_unique<DnsProbeServiceImpl>(
+ network_context_getter, dns_config_change_manager_getter, tick_clock);
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_probe_service_factory.h b/chromium/chrome/browser/net/dns_probe_service_factory.h
new file mode 100644
index 00000000000..07fea905cd6
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_service_factory.h
@@ -0,0 +1,73 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/time/tick_clock.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+
+class KeyedService;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace network {
+namespace mojom {
+class NetworkContext;
+}
+} // namespace network
+
+namespace chrome_browser_net {
+
+class DnsProbeService;
+
+class DnsProbeServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ using NetworkContextGetter =
+ base::RepeatingCallback<network::mojom::NetworkContext*(void)>;
+ using DnsConfigChangeManagerGetter =
+ base::RepeatingCallback<network::mojom::DnsConfigChangeManagerPtr(void)>;
+
+ // Returns the DnsProbeService that supports NetworkContexts for
+ // |browser_context|.
+ static DnsProbeService* GetForContext(
+ content::BrowserContext* browser_context);
+
+ // Returns the NetworkContextServiceFactory singleton.
+ static DnsProbeServiceFactory* GetInstance();
+
+ // Creates a DnsProbeService which will use the supplied
+ // |network_context_getter| and |dns_config_change_manager_getter| instead of
+ // getting them from a BrowserContext, and uses |tick_clock| for cache
+ // expiration.
+ static std::unique_ptr<DnsProbeService> CreateForTesting(
+ const NetworkContextGetter& network_context_getter,
+ const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+ const base::TickClock* tick_clock);
+
+ private:
+ friend struct base::DefaultSingletonTraits<DnsProbeServiceFactory>;
+
+ DnsProbeServiceFactory();
+ ~DnsProbeServiceFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsProbeServiceFactory);
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_PROBE_SERVICE_FACTORY_H_
diff --git a/chromium/chrome/browser/net/dns_probe_service_factory_unittest.cc b/chromium/chrome/browser/net/dns_probe_service_factory_unittest.cc
new file mode 100644
index 00000000000..b81d6dc57ec
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_service_factory_unittest.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_probe_service_factory.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/browser/net/dns_probe_test_util.h"
+#include "components/error_page/common/net_error_info.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::RunLoop;
+using content::BrowserTaskEnvironment;
+using error_page::DnsProbeStatus;
+
+namespace chrome_browser_net {
+
+namespace {
+
+class DnsProbeServiceTest : public testing::Test {
+ public:
+ DnsProbeServiceTest()
+ : callback_called_(false), callback_result_(error_page::DNS_PROBE_MAX) {}
+
+ void Probe() {
+ service_->ProbeDns(base::BindOnce(&DnsProbeServiceTest::ProbeCallback,
+ base::Unretained(this)));
+ }
+
+ void Reset() { callback_called_ = false; }
+
+ protected:
+ network::mojom::NetworkContext* GetNetworkContext() {
+ return network_context_.get();
+ }
+
+ network::mojom::DnsConfigChangeManagerPtr GetDnsConfigChangeManager() {
+ network::mojom::DnsConfigChangeManagerPtr dns_config_change_manager_ptr;
+ dns_config_change_manager_ = std::make_unique<FakeDnsConfigChangeManager>(
+ mojo::MakeRequest(&dns_config_change_manager_ptr));
+ return dns_config_change_manager_ptr;
+ }
+
+ void ConfigureTest(
+ std::vector<FakeHostResolver::SingleResult> system_results,
+ std::vector<FakeHostResolver::SingleResult> public_results) {
+ ASSERT_FALSE(network_context_);
+
+ network_context_ = std::make_unique<FakeHostResolverNetworkContext>(
+ std::move(system_results), std::move(public_results));
+
+ service_ = DnsProbeServiceFactory::CreateForTesting(
+ base::BindRepeating(&DnsProbeServiceTest::GetNetworkContext,
+ base::Unretained(this)),
+ base::BindRepeating(&DnsProbeServiceTest::GetDnsConfigChangeManager,
+ base::Unretained(this)),
+ &tick_clock_);
+ }
+
+ void RunTest(DnsProbeStatus expected_result) {
+ Reset();
+
+ Probe();
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(expected_result, callback_result_);
+ }
+
+ void AdvanceTime(base::TimeDelta delta) { tick_clock_.Advance(delta); }
+
+ void SimulateDnsConfigChange() {
+ dns_config_change_manager_->SimulateDnsConfigChange();
+ RunLoop().RunUntilIdle();
+ }
+
+ void DestroyDnsConfigChangeManager() { dns_config_change_manager_ = nullptr; }
+
+ bool has_dns_config_change_manager() const {
+ return !!dns_config_change_manager_;
+ }
+
+ private:
+ void ProbeCallback(DnsProbeStatus result) {
+ EXPECT_FALSE(callback_called_);
+ callback_called_ = true;
+ callback_result_ = result;
+ }
+
+ base::SimpleTestTickClock tick_clock_;
+ BrowserTaskEnvironment task_environment_;
+ std::unique_ptr<FakeHostResolverNetworkContext> network_context_;
+ std::unique_ptr<FakeDnsConfigChangeManager> dns_config_change_manager_;
+ std::unique_ptr<DnsProbeService> service_;
+ bool callback_called_;
+ DnsProbeStatus callback_result_;
+};
+
+TEST_F(DnsProbeServiceTest, Probe_OK_OK) {
+ ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_OK) {
+ ConfigureTest({{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_TIMEOUT) {
+ ConfigureTest({{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_OK_FAIL) {
+ ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse}},
+ {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_FAIL_OK) {
+ ConfigureTest({{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_FAIL_FAIL) {
+ ConfigureTest({{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}},
+ {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE);
+}
+
+TEST_F(DnsProbeServiceTest, Cache) {
+ ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ // Advance clock, but not enough to expire the cache.
+ AdvanceTime(base::TimeDelta::FromSeconds(4));
+ // Cached NXDOMAIN result should persist, not the result from the new rules.
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+}
+
+TEST_F(DnsProbeServiceTest, Expire) {
+ ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ // Advance clock enough to trigger cache expiration.
+ AdvanceTime(base::TimeDelta::FromSeconds(6));
+ // New rules should apply, since a new probe should be run.
+ RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+TEST_F(DnsProbeServiceTest, DnsConfigChange) {
+ ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ // Simulate dns config change notification.
+ SimulateDnsConfigChange();
+ // New rules should apply, since a new probe should be run.
+ RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+TEST_F(DnsProbeServiceTest, MojoConnectionError) {
+ ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+ {{net::OK, FakeHostResolver::kOneAddressResponse},
+ {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+ RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ DestroyDnsConfigChangeManager();
+ RunLoop().RunUntilIdle();
+ // DnsProbeService should have detected the mojo connection error and
+ // automatically called the DnsConfigChangeManagerGetter again.
+ ASSERT_TRUE(has_dns_config_change_manager());
+ // New rules should apply, since recreating the DnsConfigChangeManagerClient
+ // should also clear the cache (can't tell if a config change might have
+ // happened while not getting notifications).
+ RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+} // namespace
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_probe_test_util.cc b/chromium/chrome/browser/net/dns_probe_test_util.cc
new file mode 100644
index 00000000000..88984b36bec
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_test_util.cc
@@ -0,0 +1,139 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_probe_test_util.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "net/base/ip_address.h"
+
+namespace chrome_browser_net {
+
+namespace {
+
+static base::Optional<net::AddressList> AddressListForResponse(
+ FakeHostResolver::Response response) {
+ base::Optional<net::AddressList> resolved_addresses;
+ switch (response) {
+ case FakeHostResolver::kNoResponse:
+ break;
+ case FakeHostResolver::kEmptyResponse:
+ resolved_addresses = net::AddressList();
+ break;
+ case FakeHostResolver::kOneAddressResponse:
+ resolved_addresses =
+ net::AddressList(net::IPEndPoint(net::IPAddress(192, 168, 1, 1), 0));
+ break;
+ }
+ return resolved_addresses;
+}
+
+} // namespace
+
+FakeHostResolver::FakeHostResolver(
+ mojo::PendingReceiver<network::mojom::HostResolver> resolver_receiver,
+ std::vector<SingleResult> result_list)
+ : receiver_(this, std::move(resolver_receiver)),
+ result_list_(result_list) {}
+
+FakeHostResolver::FakeHostResolver(
+ mojo::PendingReceiver<network::mojom::HostResolver> resolver_receiver,
+ int32_t result,
+ Response response)
+ : FakeHostResolver(std::move(resolver_receiver),
+ {SingleResult(result, response)}) {}
+
+FakeHostResolver::~FakeHostResolver() = default;
+
+void FakeHostResolver::ResolveHost(
+ const net::HostPortPair& host,
+ network::mojom::ResolveHostParametersPtr optional_parameters,
+ mojo::PendingRemote<network::mojom::ResolveHostClient>
+ pending_response_client) {
+ const SingleResult& cur_result = result_list_[next_result_];
+ if (next_result_ + 1 < result_list_.size())
+ next_result_++;
+ mojo::Remote<network::mojom::ResolveHostClient> response_client(
+ std::move(pending_response_client));
+ response_client->OnComplete(cur_result.result,
+ AddressListForResponse(cur_result.response));
+}
+
+void FakeHostResolver::MdnsListen(
+ const net::HostPortPair& host,
+ net::DnsQueryType query_type,
+ mojo::PendingRemote<network::mojom::MdnsListenClient> response_client,
+ MdnsListenCallback callback) {
+ NOTREACHED();
+}
+
+HangingHostResolver::HangingHostResolver(
+ mojo::PendingReceiver<network::mojom::HostResolver> resolver_receiver)
+ : receiver_(this, std::move(resolver_receiver)) {}
+
+HangingHostResolver::~HangingHostResolver() = default;
+
+void HangingHostResolver::ResolveHost(
+ const net::HostPortPair& host,
+ network::mojom::ResolveHostParametersPtr optional_parameters,
+ mojo::PendingRemote<network::mojom::ResolveHostClient> response_client) {
+ // Intentionally do not call response_client->OnComplete, but hang onto the
+ // |response_client| since destroying that also causes the mojo
+ // set_connection_error_handler handler to be called.
+ response_client_.Bind(std::move(response_client));
+}
+
+void HangingHostResolver::MdnsListen(
+ const net::HostPortPair& host,
+ net::DnsQueryType query_type,
+ mojo::PendingRemote<network::mojom::MdnsListenClient> response_client,
+ MdnsListenCallback callback) {
+ NOTREACHED();
+}
+
+FakeHostResolverNetworkContext::FakeHostResolverNetworkContext(
+ std::vector<FakeHostResolver::SingleResult> system_result_list,
+ std::vector<FakeHostResolver::SingleResult> public_result_list)
+ : system_result_list_(std::move(system_result_list)),
+ public_result_list_(std::move(public_result_list)) {}
+
+FakeHostResolverNetworkContext::~FakeHostResolverNetworkContext() = default;
+
+void FakeHostResolverNetworkContext::CreateHostResolver(
+ const base::Optional<net::DnsConfigOverrides>& config_overrides,
+ mojo::PendingReceiver<network::mojom::HostResolver> receiver) {
+ ASSERT_TRUE(config_overrides);
+ if (!config_overrides->nameservers) {
+ if (!system_resolver_) {
+ system_resolver_ = std::make_unique<FakeHostResolver>(
+ std::move(receiver), system_result_list_);
+ }
+ } else {
+ if (!public_resolver_) {
+ public_resolver_ = std::make_unique<FakeHostResolver>(
+ std::move(receiver), public_result_list_);
+ }
+ }
+}
+
+FakeDnsConfigChangeManager::FakeDnsConfigChangeManager(
+ network::mojom::DnsConfigChangeManagerRequest request)
+ : binding_(this, std::move(request)) {}
+
+FakeDnsConfigChangeManager::~FakeDnsConfigChangeManager() = default;
+
+void FakeDnsConfigChangeManager::RequestNotifications(
+ mojo::PendingRemote<network::mojom::DnsConfigChangeManagerClient> client) {
+ ASSERT_FALSE(client_);
+ client_.Bind(std::move(client));
+}
+
+void FakeDnsConfigChangeManager::SimulateDnsConfigChange() {
+ ASSERT_TRUE(client_);
+ client_->OnSystemDnsConfigChanged();
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_probe_test_util.h b/chromium/chrome/browser/net/dns_probe_test_util.h
new file mode 100644
index 00000000000..e9b1dbb1d6f
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_probe_test_util.h
@@ -0,0 +1,126 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
+
+#include <memory>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+#include "services/network/test/test_network_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_browser_net {
+
+class FakeHostResolver : public network::mojom::HostResolver {
+ public:
+ enum Response {
+ kNoResponse = 0,
+ kEmptyResponse = 1,
+ kOneAddressResponse = 2,
+ };
+
+ struct SingleResult {
+ SingleResult(int32_t result, Response response)
+ : result(result), response(response) {}
+ int32_t result;
+ Response response;
+ };
+
+ FakeHostResolver(
+ mojo::PendingReceiver<network::mojom::HostResolver> resolver_receiver,
+ std::vector<SingleResult> result_list);
+
+ FakeHostResolver(
+ mojo::PendingReceiver<network::mojom::HostResolver> resolver_receiver,
+ int32_t result,
+ Response response);
+
+ ~FakeHostResolver() override;
+
+ void ResolveHost(const net::HostPortPair& host,
+ network::mojom::ResolveHostParametersPtr optional_parameters,
+ mojo::PendingRemote<network::mojom::ResolveHostClient>
+ pending_response_client) override;
+
+ void MdnsListen(
+ const net::HostPortPair& host,
+ net::DnsQueryType query_type,
+ mojo::PendingRemote<network::mojom::MdnsListenClient> response_client,
+ MdnsListenCallback callback) override;
+
+ private:
+ mojo::Receiver<network::mojom::HostResolver> receiver_;
+ std::vector<SingleResult> result_list_;
+ size_t next_result_ = 0;
+};
+
+class HangingHostResolver : public network::mojom::HostResolver {
+ public:
+ explicit HangingHostResolver(
+ mojo::PendingReceiver<network::mojom::HostResolver> resolver_receiver);
+ ~HangingHostResolver() override;
+
+ void ResolveHost(const net::HostPortPair& host,
+ network::mojom::ResolveHostParametersPtr optional_parameters,
+ mojo::PendingRemote<network::mojom::ResolveHostClient>
+ response_client) override;
+
+ void MdnsListen(
+ const net::HostPortPair& host,
+ net::DnsQueryType query_type,
+ mojo::PendingRemote<network::mojom::MdnsListenClient> response_client,
+ MdnsListenCallback callback) override;
+
+ private:
+ mojo::Receiver<network::mojom::HostResolver> receiver_;
+ mojo::Remote<network::mojom::ResolveHostClient> response_client_;
+};
+
+class FakeHostResolverNetworkContext : public network::TestNetworkContext {
+ public:
+ FakeHostResolverNetworkContext(
+ std::vector<FakeHostResolver::SingleResult> system_result_list,
+ std::vector<FakeHostResolver::SingleResult> public_result_list);
+ ~FakeHostResolverNetworkContext() override;
+
+ void CreateHostResolver(
+ const base::Optional<net::DnsConfigOverrides>& config_overrides,
+ mojo::PendingReceiver<network::mojom::HostResolver> receiver) override;
+
+ private:
+ std::vector<FakeHostResolver::SingleResult> system_result_list_;
+ std::vector<FakeHostResolver::SingleResult> public_result_list_;
+ std::unique_ptr<FakeHostResolver> system_resolver_;
+ std::unique_ptr<FakeHostResolver> public_resolver_;
+};
+
+class FakeDnsConfigChangeManager
+ : public network::mojom::DnsConfigChangeManager {
+ public:
+ explicit FakeDnsConfigChangeManager(
+ network::mojom::DnsConfigChangeManagerRequest request);
+ ~FakeDnsConfigChangeManager() override;
+
+ // mojom::DnsConfigChangeManager implementation:
+ void RequestNotifications(
+ mojo::PendingRemote<network::mojom::DnsConfigChangeManagerClient> client)
+ override;
+
+ void SimulateDnsConfigChange();
+
+ private:
+ mojo::Binding<network::mojom::DnsConfigChangeManager> binding_;
+ mojo::Remote<network::mojom::DnsConfigChangeManagerClient> client_;
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
diff --git a/chromium/chrome/browser/net/dns_util.cc b/chromium/chrome/browser/net/dns_util.cc
new file mode 100644
index 00000000000..42261d69b1b
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_util.cc
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_util.h"
+
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "net/third_party/uri_template/uri_template.h"
+#include "url/gurl.h"
+
+#if defined(OS_WIN)
+#include "base/enterprise_util.h"
+#endif
+
+namespace chrome_browser_net {
+
+bool IsValidDohTemplate(const std::string& server_template,
+ std::string* server_method) {
+ std::string url_string;
+ std::string test_query = "this_is_a_test_query";
+ std::unordered_map<std::string, std::string> template_params(
+ {{"dns", test_query}});
+ std::set<std::string> vars_found;
+ bool valid_template = uri_template::Expand(server_template, template_params,
+ &url_string, &vars_found);
+ if (!valid_template) {
+ // The URI template is malformed.
+ return false;
+ }
+ GURL url(url_string);
+ if (!url.is_valid() || !url.SchemeIs("https")) {
+ // The expanded template must be a valid HTTPS URL.
+ return false;
+ }
+ if (url.host().find(test_query) != std::string::npos) {
+ // The dns variable may not be part of the hostname.
+ return false;
+ }
+ // If the template contains a dns variable, use GET, otherwise use POST.
+ DCHECK(server_method);
+ *server_method =
+ (vars_found.find("dns") == vars_found.end()) ? "POST" : "GET";
+ return true;
+}
+
+bool ShouldDisableDohForManaged() {
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ if (g_browser_process->browser_policy_connector()->HasMachineLevelPolicies())
+ return true;
+#endif
+#if defined(OS_WIN)
+ if (base::IsMachineExternallyManaged())
+ return true;
+#endif
+ return false;
+}
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/dns_util.h b/chromium/chrome/browser/net/dns_util.h
new file mode 100644
index 00000000000..7935013d0f4
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_util.h
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DNS_UTIL_H_
+#define CHROME_BROWSER_NET_DNS_UTIL_H_
+
+#include <string>
+
+namespace chrome_browser_net {
+
+// Returns true if the URI template is acceptable for sending requests. If so,
+// the |server_method| is set to "GET" if the template contains a "dns" variable
+// and to "POST" otherwise. Any "dns" variable may not be part of the hostname,
+// and the expanded template must parse to a valid HTTPS URL.
+bool IsValidDohTemplate(const std::string& server_template,
+ std::string* server_method);
+
+// Returns true if there are any active machine level policies or if the machine
+// is domain joined. This special logic is used to disable DoH by default for
+// Desktop platforms (the enterprise policy field default_for_enterprise_users
+// only applies to ChromeOS). We don't attempt enterprise detection on Android
+// at this time.
+bool ShouldDisableDohForManaged();
+
+const char kDnsOverHttpsModeOff[] = "off";
+const char kDnsOverHttpsModeAutomatic[] = "automatic";
+const char kDnsOverHttpsModeSecure[] = "secure";
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_UTIL_H_
diff --git a/chromium/chrome/browser/net/dns_util_unittest.cc b/chromium/chrome/browser/net/dns_util_unittest.cc
new file mode 100644
index 00000000000..ee112b15f03
--- /dev/null
+++ b/chromium/chrome/browser/net/dns_util_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(NetDnsUtilTest, IsValidDohTemplate) {
+ std::string server_method;
+ EXPECT_TRUE(chrome_browser_net::IsValidDohTemplate(
+ "https://dnsserver.example.net/dns-query{?dns}", &server_method));
+ EXPECT_EQ("GET", server_method);
+
+ EXPECT_TRUE(chrome_browser_net::IsValidDohTemplate(
+ "https://dnsserver.example.net/dns-query{?dns,extra}", &server_method));
+ EXPECT_EQ("GET", server_method);
+
+ EXPECT_TRUE(chrome_browser_net::IsValidDohTemplate(
+ "https://dnsserver.example.net/dns-query{?query}", &server_method));
+ EXPECT_EQ("POST", server_method);
+
+ EXPECT_TRUE(chrome_browser_net::IsValidDohTemplate(
+ "https://dnsserver.example.net/dns-query", &server_method));
+ EXPECT_EQ("POST", server_method);
+
+ EXPECT_TRUE(chrome_browser_net::IsValidDohTemplate(
+ "https://query:{dns}@dnsserver.example.net", &server_method));
+ EXPECT_EQ("GET", server_method);
+
+ EXPECT_TRUE(chrome_browser_net::IsValidDohTemplate(
+ "https://dnsserver.example.net{/dns}", &server_method));
+ EXPECT_EQ("GET", server_method);
+
+ // Invalid template format
+ EXPECT_FALSE(chrome_browser_net::IsValidDohTemplate(
+ "https://dnsserver.example.net/dns-query{{?dns}}", &server_method));
+ // Must be HTTPS
+ EXPECT_FALSE(chrome_browser_net::IsValidDohTemplate(
+ "http://dnsserver.example.net/dns-query", &server_method));
+ EXPECT_FALSE(chrome_browser_net::IsValidDohTemplate(
+ "http://dnsserver.example.net/dns-query{?dns}", &server_method));
+ // Template must expand to a valid URL
+ EXPECT_FALSE(
+ chrome_browser_net::IsValidDohTemplate("https://{?dns}", &server_method));
+ // The hostname must not contain the dns variable
+ EXPECT_FALSE(chrome_browser_net::IsValidDohTemplate(
+ "https://{dns}.dnsserver.net", &server_method));
+}
diff --git a/chromium/chrome/browser/net/errorpage_browsertest.cc b/chromium/chrome/browser/net/errorpage_browsertest.cc
new file mode 100644
index 00000000000..b4531e853c5
--- /dev/null
+++ b/chromium/chrome/browser/net/errorpage_browsertest.cc
@@ -0,0 +1,1451 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/feature_list.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/browsing_data/browsing_data_helper.h"
+#include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
+#include "chrome/browser/net/net_error_diagnostics_dialog.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/google/core/common/google_util.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_member.h"
+#include "components/prefs/pref_service.h"
+#include "components/strings/grit/components_strings.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/browsing_data_remover.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "net/base/filename_util.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/failing_http_transaction_factory.h"
+#include "net/http/http_cache.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/test/url_request/url_request_failed_job.h"
+#include "net/test/url_request/url_request_mock_data_job.h"
+#include "net/test/url_request/url_request_mock_http_job.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_test_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/network/public/cpp/features.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/chrome_browser_main_chromeos.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chromeos/tpm/stub_install_attributes.h"
+#include "components/policy/core/common/policy_types.h"
+#else
+#include "chrome/browser/policy/profile_policy_connector_builder.h"
+#endif
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+
+using content::BrowserThread;
+using content::NavigationController;
+using net::URLRequestFailedJob;
+using net::URLRequestTestJob;
+
+namespace {
+
+// Searches for first node containing |text|, and if it finds one, searches
+// through all ancestors seeing if any of them is of class "hidden". Since it
+// relies on the hidden class used by network error pages, not suitable for
+// general use.
+bool WARN_UNUSED_RESULT IsDisplayingText(Browser* browser,
+ const std::string& text) {
+ // clang-format off
+ std::string command = base::StringPrintf(R"(
+ function isNodeVisible(node) {
+ if (!node || node.classList.contains('hidden'))
+ return false;
+ if (!node.parentElement)
+ return true;
+ // Otherwise, we must check all parent nodes
+ return isNodeVisible(node.parentElement);
+ }
+ var node = document.evaluate("//*[contains(text(),'%s')]", document,
+ null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ domAutomationController.send(isNodeVisible(node));
+ )", text.c_str());
+ // clang-format on
+ bool result = false;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser->tab_strip_model()->GetActiveWebContents(), command, &result));
+ return result;
+}
+
+// Expands the more box on the currently displayed error page.
+void ToggleHelpBox(Browser* browser) {
+ EXPECT_TRUE(content::ExecuteScript(
+ browser->tab_strip_model()->GetActiveWebContents(),
+ "document.getElementById('details-button').click();"));
+}
+
+// Returns true if the diagnostics link suggestion is displayed.
+bool WARN_UNUSED_RESULT IsDisplayingDiagnosticsLink(Browser* browser) {
+ std::string command = base::StringPrintf(
+ "var diagnose_link = document.getElementById('diagnose-link');"
+ "domAutomationController.send(diagnose_link != null);");
+ bool result = false;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser->tab_strip_model()->GetActiveWebContents(), command, &result));
+ return result;
+}
+
+// Checks that the local error page is being displayed, without remotely
+// retrieved navigation corrections, and with the specified error string.
+void ExpectDisplayingLocalErrorPage(const std::string& url,
+ Browser* browser,
+ const std::string& error_string) {
+ EXPECT_TRUE(IsDisplayingText(browser, error_string));
+
+ // Locally generated error pages should not have navigation corrections.
+ EXPECT_FALSE(IsDisplayingText(browser, url));
+
+ // Locally generated error pages should not have a link with search terms.
+ EXPECT_FALSE(IsDisplayingText(browser, "search query"));
+}
+
+// Checks that the local error page is being displayed, without remotely
+// retrieved navigation corrections, and with the specified error code.
+void ExpectDisplayingLocalErrorPage(const std::string& url,
+ Browser* browser,
+ net::Error error_code) {
+ ExpectDisplayingLocalErrorPage(url, browser,
+ net::ErrorToShortString(error_code));
+}
+
+// Checks that an error page with information retrieved from the navigation
+// correction service is being displayed, with the specified specified error
+// string.
+void ExpectDisplayingNavigationCorrections(Browser* browser,
+ const std::string& error_string) {
+ EXPECT_TRUE(IsDisplayingText(browser, error_string));
+
+ // Check that the mock navigation corrections are displayed.
+ EXPECT_TRUE(IsDisplayingText(browser, "http://mock.http/title2.html"));
+
+ // Check that the search terms are displayed as a link.
+ EXPECT_TRUE(IsDisplayingText(browser, "search query"));
+
+ // The diagnostics button isn't displayed when corrections were
+ // retrieved from a remote server.
+ EXPECT_FALSE(IsDisplayingDiagnosticsLink(browser));
+}
+
+// Checks that an error page with information retrieved from the navigation
+// correction service is being displayed, with the specified specified error
+// code.
+void ExpectDisplayingNavigationCorrections(Browser* browser,
+ net::Error error_code) {
+ ExpectDisplayingNavigationCorrections(browser,
+ net::ErrorToShortString(error_code));
+}
+
+// Returns true if the platform has support for a diagnostics tool, and it
+// can be launched from |web_contents|.
+bool WebContentsCanShowDiagnosticsTool(content::WebContents* web_contents) {
+#if defined(OS_CHROMEOS)
+ // ChromeOS uses an extension instead of a diagnostics dialog.
+ return true;
+#else
+ return CanShowNetworkDiagnosticsDialog(web_contents);
+#endif
+}
+
+class ErrorPageTest : public InProcessBrowserTest {
+ public:
+ enum HistoryNavigationDirection {
+ HISTORY_NAVIGATE_BACK,
+ HISTORY_NAVIGATE_FORWARD,
+ };
+
+ ErrorPageTest() = default;
+ ~ErrorPageTest() override = default;
+
+ // Navigates the active tab to a mock url created for the file at |path|.
+ void NavigateToFileURL(const std::string& path) {
+ GURL url = embedded_test_server()->GetURL(path);
+ ui_test_utils::NavigateToURL(browser(), url);
+ }
+
+ // Navigates to the given URL and waits for |num_navigations| to occur, and
+ // the title to change to |expected_title|.
+ void NavigateToURLAndWaitForTitle(const GURL& url,
+ const std::string& expected_title,
+ int32_t num_navigations) {
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16(expected_title));
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), url, num_navigations);
+
+ EXPECT_EQ(base::ASCIIToUTF16(expected_title),
+ title_watcher.WaitAndGetTitle());
+ }
+
+ // Navigates back in the history and waits for |num_navigations| to occur, and
+ // the title to change to |expected_title|.
+ void GoBackAndWaitForTitle(const std::string& expected_title,
+ int32_t num_navigations) {
+ NavigateHistoryAndWaitForTitle(expected_title,
+ num_navigations,
+ HISTORY_NAVIGATE_BACK);
+ }
+
+ // Navigates forward in the history and waits for |num_navigations| to occur,
+ // and the title to change to |expected_title|.
+ void GoForwardAndWaitForTitle(const std::string& expected_title,
+ int32_t num_navigations) {
+ NavigateHistoryAndWaitForTitle(expected_title,
+ num_navigations,
+ HISTORY_NAVIGATE_FORWARD);
+ }
+
+ void GoBackAndWaitForNavigations(int32_t num_navigations) {
+ NavigateHistory(num_navigations, HISTORY_NAVIGATE_BACK);
+ }
+
+ void GoForwardAndWaitForNavigations(int32_t num_navigations) {
+ NavigateHistory(num_navigations, HISTORY_NAVIGATE_FORWARD);
+ }
+
+ // Navigates the browser the indicated direction in the history and waits for
+ // |num_navigations| to occur and the title to change to |expected_title|.
+ void NavigateHistoryAndWaitForTitle(const std::string& expected_title,
+ int32_t num_navigations,
+ HistoryNavigationDirection direction) {
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16(expected_title));
+
+ NavigateHistory(num_navigations, direction);
+
+ EXPECT_EQ(title_watcher.WaitAndGetTitle(),
+ base::ASCIIToUTF16(expected_title));
+ }
+
+ void NavigateHistory(int32_t num_navigations,
+ HistoryNavigationDirection direction) {
+ content::TestNavigationObserver test_navigation_observer(
+ browser()->tab_strip_model()->GetActiveWebContents(), num_navigations);
+ if (direction == HISTORY_NAVIGATE_BACK) {
+ chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
+ } else if (direction == HISTORY_NAVIGATE_FORWARD) {
+ chrome::GoForward(browser(), WindowOpenDisposition::CURRENT_TAB);
+ } else {
+ FAIL();
+ }
+ test_navigation_observer.Wait();
+ }
+};
+
+class TestFailProvisionalLoadObserver : public content::WebContentsObserver {
+ public:
+ explicit TestFailProvisionalLoadObserver(content::WebContents* contents)
+ : content::WebContentsObserver(contents) {}
+ ~TestFailProvisionalLoadObserver() override {}
+
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override {
+ if (navigation_handle->IsErrorPage())
+ fail_url_ = navigation_handle->GetURL();
+ }
+
+ const GURL& fail_url() const { return fail_url_; }
+
+ private:
+ GURL fail_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestFailProvisionalLoadObserver);
+};
+
+class DNSErrorPageTest : public ErrorPageTest {
+ public:
+ DNSErrorPageTest() {
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](DNSErrorPageTest* owner,
+ content::URLLoaderInterceptor::RequestParams* params) {
+ // Add an interceptor that serves LinkDoctor responses
+ if (google_util::LinkDoctorBaseURL() == params->url_request.url) {
+ // The origin header should exist and be opaque.
+ std::string origin;
+ bool has_origin = params->url_request.headers.GetHeader(
+ net::HttpRequestHeaders::kOrigin, &origin);
+ EXPECT_TRUE(has_origin);
+ EXPECT_EQ(origin, "null");
+ // Send RequestCreated so that anyone blocking on
+ // WaitForRequests can continue.
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&DNSErrorPageTest::RequestCreated,
+ base::Unretained(owner)));
+ content::URLLoaderInterceptor::WriteResponse(
+ "chrome/test/data/mock-link-doctor.json",
+ params->client.get());
+ return true;
+ }
+
+ // Referenced by mock Link Doctor page.
+ if (params->url_request.url.spec() ==
+ "http://mock.http/title2.html") {
+ content::URLLoaderInterceptor::WriteResponse(
+ "chrome/test/data/title2.html", params->client.get());
+ return true;
+ }
+
+ // Add an interceptor for the search engine the error page will
+ // use.
+ if (params->url_request.url.host() ==
+ owner->search_term_url_.host()) {
+ content::URLLoaderInterceptor::WriteResponse(
+ "chrome/test/data/title3.html", params->client.get());
+ return true;
+ }
+
+ return false;
+ },
+ this));
+ }
+
+ ~DNSErrorPageTest() override = default;
+
+ // When it sees a request for |path|, returns a 500 response with a body that
+ // will be sniffed as binary/octet-stream.
+ static std::unique_ptr<net::test_server::HttpResponse>
+ Return500WithBinaryBody(const std::string& path,
+ const net::test_server::HttpRequest& request) {
+ if (path != request.relative_url)
+ return nullptr;
+ return std::unique_ptr<net::test_server::HttpResponse>(
+ new net::test_server::RawHttpResponse("HTTP/1.1 500 Server Sad :(\n\n",
+ "\x01"));
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ void SetUpOnMainThread() override {
+ // All mock.http requests get served by the embedded test server.
+ host_resolver()->AddRule("mock.http", "127.0.0.1");
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ UIThreadSearchTermsData search_terms_data;
+ search_term_url_ = GURL(search_terms_data.GoogleBaseURLValue());
+ }
+
+ void WaitForRequests(int32_t requests_to_wait_for) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_EQ(-1, requests_to_wait_for_);
+ DCHECK(!run_loop_);
+
+ if (requests_to_wait_for <= num_requests_)
+ return;
+
+ requests_to_wait_for_ = requests_to_wait_for;
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+ run_loop_.reset();
+ requests_to_wait_for_ = -1;
+ EXPECT_EQ(num_requests_, requests_to_wait_for);
+ }
+
+ // Returns the total number of requests handled thus far.
+ int32_t num_requests() const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return num_requests_;
+ }
+
+ void RequestCreated() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ num_requests_++;
+ if (num_requests_ == requests_to_wait_for_)
+ run_loop_->Quit();
+ }
+
+ // Returns a GURL that results in a DNS error.
+ GURL GetDnsErrorURL() const {
+ return URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED);
+ }
+
+ private:
+ // These are only used on the UI thread.
+ int32_t num_requests_ = 0;
+ int32_t requests_to_wait_for_ = -1;
+ GURL search_term_url_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+// Test an error with a file URL, and make sure it doesn't have a
+// button to launch a network diagnostics tool.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, FileNotFound) {
+ // Create an empty temp directory, to be sure there's no file in it.
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ GURL non_existent_file_url =
+ net::FilePathToFileURL(temp_dir.GetPath().AppendASCII("marmoset"));
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), non_existent_file_url, 1);
+
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_FILE_NOT_FOUND);
+ // Should not request Link Doctor corrections for local errors.
+ EXPECT_EQ(0, num_requests());
+ // Only errors on HTTP/HTTPS pages should display a diagnostics button.
+ EXPECT_FALSE(IsDisplayingDiagnosticsLink(browser()));
+}
+
+// Check an network error page for ERR_FAILED. In particular, this should
+// not trigger a link doctor error page.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Failed) {
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), URLRequestFailedJob::GetMockHttpUrl(net::ERR_FAILED), 1);
+
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_FAILED);
+ // Should not request Link Doctor corrections for this error.
+ EXPECT_EQ(0, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame redirects to an error page.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_Basic) {
+ // The first navigation should fail and load a blank page, while it fetches
+ // the Link Doctor response. The second navigation is the Link Doctor.
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack1) {
+ NavigateToFileURL("/title2.html");
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(1, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2) {
+ NavigateToFileURL("/title2.html");
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ NavigateToFileURL("/title3.html");
+
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(2, num_requests());
+
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(2, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2AndForward) {
+ NavigateToFileURL("/title2.html");
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ NavigateToFileURL("/title3.html");
+
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(2, num_requests());
+
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+
+ GoForwardAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(3, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2Forward2) {
+ NavigateToFileURL("/title3.html");
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ NavigateToFileURL("/title2.html");
+
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(2, num_requests());
+
+ GoBackAndWaitForTitle("Title Of More Awesomeness", 1);
+
+ GoForwardAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(3, num_requests());
+
+ GoForwardAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(3, num_requests());
+}
+
+// Test that the search link on a DNS error page works.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoSearch) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Do a search and make sure the browser ends up at the right page.
+ content::TestNavigationObserver nav_observer(web_contents, 1);
+ content::TitleWatcher title_watcher(
+ web_contents,
+ base::ASCIIToUTF16("Title Of More Awesomeness"));
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('search-link').click();"),
+ base::NullCallback());
+ nav_observer.Wait();
+ EXPECT_EQ(base::ASCIIToUTF16("Title Of More Awesomeness"),
+ title_watcher.WaitAndGetTitle());
+
+ // There should have been another Link Doctor request, for tracking purposes.
+ // Have to wait for it, since the search page does not depend on having
+ // sent the tracking request.
+ WaitForRequests(2);
+
+ // Check the path and query string.
+ std::string url;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "domAutomationController.send(window.location.href);",
+ &url));
+ EXPECT_EQ("/search", GURL(url).path());
+ EXPECT_EQ("q=search%20query", GURL(url).query());
+
+ // Go back to the error page, to make sure the history is correct.
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(3, num_requests());
+}
+
+// Test that the reload button on a DNS error page works.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoReload) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ std::string url =
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Clicking the reload button should load the error page again, and there
+ // should be two commits, as before.
+ content::TestNavigationObserver nav_observer(web_contents, 2);
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('reload-button').click();"),
+ base::NullCallback());
+ nav_observer.Wait();
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+
+ // There should have been two more requests to the correction service: One
+ // for the new error page, and one for tracking purposes. Have to make sure
+ // to wait for the tracking request, since the new error page does not depend
+ // on it.
+ WaitForRequests(3);
+}
+
+// Test that the reload button on a DNS error page works after a same document
+// navigation on the error page. Error pages don't seem to do this, but some
+// traces indicate this may actually happen. This test may hang on regression.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest,
+ DNSError_DoReloadAfterSameDocumentNavigation) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ std::string url =
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Do a same-document navigation on the error page, which should not result
+ // in a new navigation.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.location='#';"), base::NullCallback());
+ content::WaitForLoadStop(web_contents);
+ // Page being displayed should not change.
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ // No new requests should have been issued.
+ EXPECT_EQ(1, num_requests());
+
+ // Clicking the reload button should load the error page again, and there
+ // should be two commits, as before.
+ content::TestNavigationObserver nav_observer2(web_contents, 2);
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('reload-button').click();"),
+ base::NullCallback());
+ nav_observer2.Wait();
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+
+ // There should have been two more requests to the correction service: One
+ // for the new error page, and one for tracking purposes. Have to make sure
+ // to wait for the tracking request, since the new error page does not depend
+ // on it.
+ WaitForRequests(3);
+}
+
+// Test that clicking links on a DNS error page works.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoClickLink) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ GURL url = embedded_test_server()->GetURL("mock.http", "/title2.html");
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Simulate a click on a link.
+
+ content::TitleWatcher title_watcher(
+ web_contents,
+ base::ASCIIToUTF16("Title Of Awesomeness"));
+ std::string link_selector =
+ "document.querySelector('a[href=\"http://mock.http/title2.html\"]')";
+ // The tracking request is triggered by onmousedown, so it catches middle
+ // mouse button clicks, as well as left clicks.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16(link_selector + ".onmousedown();"),
+ base::NullCallback());
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16(link_selector + ".click();"), base::NullCallback());
+ EXPECT_EQ(base::ASCIIToUTF16("Title Of Awesomeness"),
+ title_watcher.WaitAndGetTitle());
+
+ // There should have been a tracking request to the correction service. Have
+ // to make sure to wait the tracking request, since the new page does not
+ // depend on it.
+ WaitForRequests(2);
+}
+
+// Test that a DNS error occuring in an iframe does not result in showing
+// navigation corrections.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError_Basic) {
+ NavigateToURLAndWaitForTitle(
+ embedded_test_server()->GetURL("/iframe_dns_error.html"), "Blah", 1);
+ // We expect to have two history entries, since we started off with navigation
+ // to "about:blank" and then navigated to "iframe_dns_error.html".
+ EXPECT_EQ(2,
+ browser()->tab_strip_model()->GetActiveWebContents()->
+ GetController().GetEntryCount());
+ EXPECT_EQ(0, num_requests());
+}
+
+// This test fails regularly on win_rel trybots. See crbug.com/121540
+#if defined(OS_WIN)
+#define MAYBE_IFrameDNSError_GoBack DISABLED_IFrameDNSError_GoBack
+#else
+#define MAYBE_IFrameDNSError_GoBack IFrameDNSError_GoBack
+#endif
+// Test that a DNS error occuring in an iframe does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, MAYBE_IFrameDNSError_GoBack) {
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/title2.html"));
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/iframe_dns_error.html"));
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(0, num_requests());
+}
+
+// This test fails regularly on win_rel trybots. See crbug.com/121540
+//
+// This fails on linux_aura bringup: http://crbug.com/163931
+#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA))
+#define MAYBE_IFrameDNSError_GoBackAndForward DISABLED_IFrameDNSError_GoBackAndForward
+#else
+#define MAYBE_IFrameDNSError_GoBackAndForward IFrameDNSError_GoBackAndForward
+#endif
+// Test that a DNS error occuring in an iframe does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest,
+ MAYBE_IFrameDNSError_GoBackAndForward) {
+ NavigateToFileURL("/title2.html");
+ NavigateToFileURL("/iframe_dns_error.html");
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ GoForwardAndWaitForTitle("Blah", 1);
+ EXPECT_EQ(0, num_requests());
+}
+
+// Test that a DNS error occuring in an iframe, once the main document is
+// completed loading, does not result in an additional session history entry.
+// To ensure that the main document has completed loading, JavaScript is used to
+// inject an iframe after loading is done.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError_JavaScript) {
+ content::WebContents* wc =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ GURL fail_url =
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED);
+
+ // Load a regular web page, in which we will inject an iframe.
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/title2.html"));
+
+ // We expect to have two history entries, since we started off with navigation
+ // to "about:blank" and then navigated to "title2.html".
+ EXPECT_EQ(2, wc->GetController().GetEntryCount());
+
+ std::string script = "var frame = document.createElement('iframe');"
+ "frame.src = '" + fail_url.spec() + "';"
+ "document.body.appendChild(frame);";
+ {
+ TestFailProvisionalLoadObserver fail_observer(wc);
+ content::WindowedNotificationObserver load_observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(&wc->GetController()));
+ wc->GetMainFrame()->ExecuteJavaScriptForTests(base::ASCIIToUTF16(script),
+ base::NullCallback());
+ load_observer.Wait();
+
+ // Ensure we saw the expected failure.
+ EXPECT_EQ(fail_url, fail_observer.fail_url());
+
+ // Failed initial navigation of an iframe shouldn't be adding any history
+ // entries.
+ EXPECT_EQ(2, wc->GetController().GetEntryCount());
+ }
+
+ // Do the same test, but with an iframe that doesn't have initial URL
+ // assigned.
+ script = "var frame = document.createElement('iframe');"
+ "frame.id = 'target_frame';"
+ "document.body.appendChild(frame);";
+ {
+ content::WindowedNotificationObserver load_observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(&wc->GetController()));
+ wc->GetMainFrame()->ExecuteJavaScriptForTests(base::ASCIIToUTF16(script),
+ base::NullCallback());
+ load_observer.Wait();
+ }
+
+ script = "var f = document.getElementById('target_frame');"
+ "f.src = '" + fail_url.spec() + "';";
+ {
+ TestFailProvisionalLoadObserver fail_observer(wc);
+ content::WindowedNotificationObserver load_observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(&wc->GetController()));
+ wc->GetMainFrame()->ExecuteJavaScriptForTests(base::ASCIIToUTF16(script),
+ base::NullCallback());
+ load_observer.Wait();
+
+ EXPECT_EQ(fail_url, fail_observer.fail_url());
+ EXPECT_EQ(2, wc->GetController().GetEntryCount());
+ }
+ EXPECT_EQ(0, num_requests());
+}
+
+// Checks that navigation corrections are not loaded when we receive an actual
+// 404 page.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Page404) {
+ NavigateToURLAndWaitForTitle(embedded_test_server()->GetURL("/page404.html"),
+ "SUCCESS", 1);
+ EXPECT_EQ(0, num_requests());
+}
+
+// Checks that navigation corrections are loaded in response to a 404 page with
+// no body.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty404) {
+ // The first navigation should fail and load a blank page, while it fetches
+ // the Link Doctor response. The second navigation is the Link Doctor.
+ GURL url = embedded_test_server()->GetURL("/errorpage/empty404.html");
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(), url, 2);
+ // This depends on the non-internationalized error ID string in
+ // localized_error.cc.
+ ExpectDisplayingNavigationCorrections(
+ browser(), "HTTP ERROR 404");
+ EXPECT_EQ(1, num_requests());
+}
+
+// Checks that a local error page is shown in response to a 500 error page
+// without a body.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty500) {
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/errorpage/empty500.html"));
+ // This depends on the non-internationalized error ID string in
+ // localized_error.cc.
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), "HTTP ERROR 500");
+ EXPECT_EQ(0, num_requests());
+}
+
+// Check that the easter egg is present and initialised and is not disabled.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, CheckEasterEggIsNotDisabled) {
+ ui_test_utils::NavigateToURL(browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_INTERNET_DISCONNECTED));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Check for no disabled message container.
+ std::string command = base::StringPrintf(
+ "var hasDisableContainer = document.querySelectorAll('.snackbar').length;"
+ "domAutomationController.send(hasDisableContainer);");
+ int32_t result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+ web_contents, command, &result));
+ EXPECT_EQ(0, result);
+
+ // Presence of the canvas container.
+ command = base::StringPrintf(
+ "var runnerCanvas = document.querySelectorAll('.runner-canvas').length;"
+ "domAutomationController.send(runnerCanvas);");
+ EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+ web_contents, command, &result));
+ EXPECT_EQ(1, result);
+}
+
+// Test error page in incognito mode. The two major things are that navigation
+// corrections are not fetched (Only one navigation, display local error page),
+// and that no network diagnostic link is included, except on ChromeOS.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Incognito) {
+ Browser* incognito_browser = CreateIncognitoBrowser();
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ incognito_browser,
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED), 1);
+
+ // Verify that the expected error page is being displayed.
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ incognito_browser, net::ERR_NAME_NOT_RESOLVED);
+
+#if !defined(OS_CHROMEOS)
+ // Can't currently show the diagnostics in incognito on any platform but
+ // ChromeOS.
+ EXPECT_FALSE(WebContentsCanShowDiagnosticsTool(
+ incognito_browser->tab_strip_model()->GetActiveWebContents()));
+#endif
+
+ // Diagnostics button should be displayed, if available.
+ EXPECT_EQ(WebContentsCanShowDiagnosticsTool(
+ incognito_browser->tab_strip_model()->GetActiveWebContents()),
+ IsDisplayingDiagnosticsLink(incognito_browser));
+}
+
+class ErrorPageAutoReloadTest : public InProcessBrowserTest {
+ public:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kEnableAutoReload);
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ void InstallInterceptor(const GURL& url, int32_t requests_to_fail) {
+ requests_ = failures_ = 0;
+
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](int32_t requests_to_fail, int32_t* requests, int32_t* failures,
+ content::URLLoaderInterceptor::RequestParams* params) {
+ if (params->url_request.url.path() == "/searchdomaincheck")
+ return false;
+ if (params->url_request.url.path() == "/favicon.ico")
+ return false;
+ if (params->url_request.url.GetOrigin() ==
+ GaiaUrls::GetInstance()->gaia_url())
+ return false;
+ (*requests)++;
+ if (*failures < requests_to_fail) {
+ (*failures)++;
+ network::URLLoaderCompletionStatus status;
+ status.error_code = net::ERR_CONNECTION_RESET;
+ params->client->OnComplete(status);
+ return true;
+ }
+
+ std::string body = URLRequestTestJob::test_data_1();
+ content::URLLoaderInterceptor::WriteResponse(
+ URLRequestTestJob::test_headers(), body,
+ params->client.get());
+ return true;
+ },
+ requests_to_fail, &requests_, &failures_));
+ }
+
+ void NavigateToURLAndWaitForTitle(const GURL& url,
+ const std::string& expected_title,
+ int32_t num_navigations) {
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16(expected_title));
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), url, num_navigations);
+
+ EXPECT_EQ(base::ASCIIToUTF16(expected_title),
+ title_watcher.WaitAndGetTitle());
+ }
+
+ int32_t interceptor_requests() const { return requests_; }
+ int32_t interceptor_failures() const { return failures_; }
+
+ private:
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+ int32_t requests_;
+ int32_t failures_;
+};
+
+// Fails on official mac_trunk build. See crbug.com/465789.
+#if defined(OFFICIAL_BUILD) && defined(OS_MACOSX)
+#define MAYBE_AutoReload DISABLED_AutoReload
+#else
+#define MAYBE_AutoReload AutoReload
+#endif
+IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, MAYBE_AutoReload) {
+ GURL test_url("http://error.page.auto.reload");
+ const int32_t kRequestsToFail = 2;
+ InstallInterceptor(test_url, kRequestsToFail);
+ NavigateToURLAndWaitForTitle(test_url, "Test One", kRequestsToFail + 1);
+ // Note that the interceptor updates these variables on the IO thread,
+ // but this function reads them on the main thread. The requests have to be
+ // created (on the IO thread) before NavigateToURLAndWaitForTitle returns or
+ // this becomes racey.
+ EXPECT_EQ(kRequestsToFail, interceptor_failures());
+ EXPECT_EQ(kRequestsToFail + 1, interceptor_requests());
+}
+
+IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, ManualReloadNotSuppressed) {
+ GURL test_url("http://error.page.auto.reload");
+ const int32_t kRequestsToFail = 3;
+ InstallInterceptor(test_url, kRequestsToFail);
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), test_url, 2);
+
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(2, interceptor_requests());
+
+ ToggleHelpBox(browser());
+ EXPECT_TRUE(IsDisplayingText(
+ browser(), l10n_util::GetStringUTF8(
+ IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION_HEADER)));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver nav_observer(web_contents, 1);
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('reload-button').click();"),
+ base::NullCallback());
+ nav_observer.Wait();
+ EXPECT_FALSE(IsDisplayingText(
+ browser(), l10n_util::GetStringUTF8(
+ IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION_HEADER)));
+}
+
+// Make sure that a same document navigation does not cause issues with the
+// auto-reload timer. Note that this test was added due to this case causing
+// a crash. On regression, this test may hang due to a crashed renderer.
+IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, IgnoresSameDocumentNavigation) {
+ GURL test_url("http://error.page.auto.reload");
+ InstallInterceptor(test_url, 2);
+
+ // Wait for the error page and first autoreload, which happens immediately.
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), test_url, 2);
+
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(2, interceptor_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.location='#';"), base::NullCallback());
+ content::WaitForLoadStop(web_contents);
+
+ // Same-document navigation on an error page should not have resulted in a
+ // new navigation, so no new requests should have been issued.
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(2, interceptor_requests());
+
+ // Wait for the second auto reload, which succeeds.
+ content::TestNavigationObserver observer2(web_contents, 1);
+ observer2.Wait();
+
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(3, interceptor_requests());
+}
+
+// A test fixture that returns ERR_ADDRESS_UNREACHABLE for all navigation
+// correction requests. ERR_NAME_NOT_RESOLVED is more typical, but need to use
+// a different error for the correction service and the original page to
+// validate the right page is being displayed.
+class ErrorPageNavigationCorrectionsFailTest : public ErrorPageTest {
+ public:
+ // InProcessBrowserTest:
+ void SetUpOnMainThread() override {
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](content::URLLoaderInterceptor::RequestParams* params) {
+ if (params->url_request.url != google_util::LinkDoctorBaseURL())
+ return false;
+
+ network::URLLoaderCompletionStatus status;
+ status.error_code = net::ERR_ADDRESS_UNREACHABLE;
+ params->client->OnComplete(status);
+ return true;
+ }));
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ private:
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+// Make sure that when corrections fail to load, the network error page is
+// successfully loaded and shows a link to the diagnostics too, if appropriate.
+IN_PROC_BROWSER_TEST_F(ErrorPageNavigationCorrectionsFailTest,
+ FetchCorrectionsFails) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED),
+ 2);
+
+ // Verify that the expected error page is being displayed.
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+
+ // Diagnostics button should be displayed, if available.
+ EXPECT_EQ(WebContentsCanShowDiagnosticsTool(
+ browser()->tab_strip_model()->GetActiveWebContents()),
+ IsDisplayingDiagnosticsLink(browser()));
+}
+
+class ErrorPageOfflineTest : public ErrorPageTest {
+ void SetUpOnMainThread() override {
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](content::URLLoaderInterceptor::RequestParams* params) {
+ return false;
+ }));
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+#if defined(OS_CHROMEOS)
+ if (enroll_) {
+ // Set up fake install attributes.
+ test_install_attributes_ =
+ std::make_unique<chromeos::ScopedStubInstallAttributes>(
+ chromeos::StubInstallAttributes::CreateCloudManaged("example.com",
+ "fake-id"));
+ }
+#endif
+
+ // Sets up a mock policy provider for user and device policies.
+ EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
+ .WillRepeatedly(testing::Return(true));
+
+ policy::PolicyMap policy_map;
+#if defined(OS_CHROMEOS)
+ if (enroll_)
+ SetEnterpriseUsersDefaults(&policy_map);
+#endif
+ if (set_allow_dinosaur_easter_egg_) {
+ policy_map.Set(
+ policy::key::kAllowDinosaurEasterEgg, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(value_of_allow_dinosaur_easter_egg_),
+ nullptr);
+ }
+ policy_provider_.UpdateChromePolicy(policy_map);
+
+#if defined(OS_CHROMEOS)
+ policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+ &policy_provider_);
+#else
+ policy::PushProfilePolicyConnectorProviderForTesting(&policy_provider_);
+#endif
+
+ ErrorPageTest::SetUpInProcessBrowserTestFixture();
+ }
+
+ std::string NavigateToPageAndReadText() {
+#if defined(OS_CHROMEOS)
+ // Check enterprise enrollment
+ policy::BrowserPolicyConnectorChromeOS* connector =
+ g_browser_process->platform_part()
+ ->browser_policy_connector_chromeos();
+ EXPECT_EQ(enroll_, connector->IsEnterpriseManaged());
+#endif
+
+ ui_test_utils::NavigateToURL(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_INTERNET_DISCONNECTED));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ std::string command = base::StringPrintf(
+ "var hasText = document.querySelector('.snackbar');"
+ "domAutomationController.send(hasText ? hasText.innerText : '');");
+
+ std::string result;
+ EXPECT_TRUE(
+ content::ExecuteScriptAndExtractString(web_contents, command, &result));
+
+ return result;
+ }
+
+ // Whether to set AllowDinosaurEasterEgg policy
+ bool set_allow_dinosaur_easter_egg_ = false;
+
+ // The value of AllowDinosaurEasterEgg policy we want to set
+ bool value_of_allow_dinosaur_easter_egg_;
+
+#if defined(OS_CHROMEOS)
+ // Whether to enroll this CrOS device
+ bool enroll_ = true;
+
+ std::unique_ptr<chromeos::ScopedStubInstallAttributes>
+ test_install_attributes_;
+#endif
+
+ // Mock policy provider for both user and device policies.
+ policy::MockConfigurationPolicyProvider policy_provider_;
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+class ErrorPageOfflineTestWithAllowDinosaurTrue : public ErrorPageOfflineTest {
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ set_allow_dinosaur_easter_egg_ = true;
+ value_of_allow_dinosaur_easter_egg_ = true;
+ ErrorPageOfflineTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+
+class ErrorPageOfflineTestWithAllowDinosaurFalse : public ErrorPageOfflineTest {
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ set_allow_dinosaur_easter_egg_ = true;
+ value_of_allow_dinosaur_easter_egg_ = false;
+ ErrorPageOfflineTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+
+#if defined(OS_CHROMEOS)
+class ErrorPageOfflineTestUnEnrolledChromeOS : public ErrorPageOfflineTest {
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ set_allow_dinosaur_easter_egg_ = false;
+ enroll_ = false;
+ ErrorPageOfflineTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+#endif
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurTrue,
+ CheckEasterEggIsAllowed) {
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+}
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurFalse,
+ CheckEasterEggIsDisabled) {
+ std::string result = NavigateToPageAndReadText();
+ std::string disabled_text =
+ l10n_util::GetStringUTF8(IDS_ERRORPAGE_FUN_DISABLED);
+ EXPECT_EQ(disabled_text, result);
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTest, CheckEasterEggIsDisabled) {
+ std::string result = NavigateToPageAndReadText();
+ std::string disabled_text =
+ l10n_util::GetStringUTF8(IDS_ERRORPAGE_FUN_DISABLED);
+ EXPECT_EQ(disabled_text, result);
+}
+#else
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTest, CheckEasterEggIsAllowed) {
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+}
+#endif
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestUnEnrolledChromeOS,
+ CheckEasterEggIsAllowed) {
+ std::string result = NavigateToPageAndReadText();
+ std::string disabled_text =
+ l10n_util::GetStringUTF8(IDS_ERRORPAGE_FUN_DISABLED);
+ EXPECT_EQ("", result);
+}
+#endif
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurTrue,
+ CheckEasterEggHighScoreLoaded) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::BrowserContext* browser_context = web_contents->GetBrowserContext();
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+
+ IntegerPrefMember easter_egg_high_score;
+ easter_egg_high_score.Init(prefs::kNetworkEasterEggHighScore,
+ profile->GetPrefs());
+
+ // Set a high score in the user's profile.
+ int high_score = 1000;
+ easter_egg_high_score.SetValue(high_score);
+
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+
+ content::EvalJsResult actual_high_score = content::EvalJs(
+ web_contents,
+ "new Promise((resolve) => {"
+ " window.initializeEasterEggHighScore = function(highscore) { "
+ " resolve(highscore);"
+ " };"
+ " /* Request the initial highscore from the browser. */"
+ " errorPageController.trackEasterEgg();"
+ "});");
+
+ EXPECT_EQ(high_score, actual_high_score);
+}
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurTrue,
+ CheckEasterEggHighScoreSaved) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::BrowserContext* browser_context = web_contents->GetBrowserContext();
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+
+ IntegerPrefMember easter_egg_high_score;
+ easter_egg_high_score.Init(prefs::kNetworkEasterEggHighScore,
+ profile->GetPrefs());
+
+ // The high score should be initialized to 0.
+ EXPECT_EQ(0, easter_egg_high_score.GetValue());
+
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+
+ {
+ base::RunLoop run_loop;
+ PrefChangeRegistrar change_observer;
+ change_observer.Init(profile->GetPrefs());
+ change_observer.Add(prefs::kNetworkEasterEggHighScore,
+ run_loop.QuitClosure());
+
+ // Save a new high score.
+ EXPECT_TRUE(content::ExecJs(
+ web_contents, "errorPageController.updateEasterEggHighScore(2000);"));
+
+ // Wait for preference change.
+ run_loop.Run();
+ EXPECT_EQ(2000, easter_egg_high_score.GetValue());
+ }
+
+ {
+ base::RunLoop run_loop;
+ PrefChangeRegistrar change_observer;
+ change_observer.Init(profile->GetPrefs());
+ change_observer.Add(prefs::kNetworkEasterEggHighScore,
+ run_loop.QuitClosure());
+
+ // Reset high score back to 0.
+ EXPECT_TRUE(content::ExecJs(
+ web_contents, "errorPageController.resetEasterEggHighScore();"));
+
+ // Wait for preference change.
+ run_loop.Run();
+ EXPECT_EQ(0, easter_egg_high_score.GetValue());
+ }
+}
+
+// A test fixture that simulates failing requests for an IDN domain name.
+class ErrorPageForIDNTest : public InProcessBrowserTest {
+ public:
+ // Target hostname in different forms.
+ static const char kHostname[];
+ static const char kHostnameJSUnicode[];
+
+ // InProcessBrowserTest:
+ void SetUpOnMainThread() override {
+ // Clear AcceptLanguages to force punycode decoding.
+ browser()->profile()->GetPrefs()->SetString(
+ language::prefs::kAcceptLanguages, std::string());
+ }
+};
+
+const char ErrorPageForIDNTest::kHostname[] =
+ "xn--d1abbgf6aiiy.xn--p1ai";
+const char ErrorPageForIDNTest::kHostnameJSUnicode[] =
+ "\\u043f\\u0440\\u0435\\u0437\\u0438\\u0434\\u0435\\u043d\\u0442."
+ "\\u0440\\u0444";
+
+// Make sure error page shows correct unicode for IDN.
+IN_PROC_BROWSER_TEST_F(ErrorPageForIDNTest, IDN) {
+ // ERR_UNSAFE_PORT will not trigger navigation corrections.
+ ui_test_utils::NavigateToURL(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrlForHostname(net::ERR_UNSAFE_PORT,
+ kHostname));
+ EXPECT_TRUE(IsDisplayingText(browser(), kHostnameJSUnicode));
+}
+
+// Make sure HTTP/0.9 is disabled on non-default ports by default.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Http09WeirdPort) {
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/echo-raw?spam"));
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_INVALID_HTTP_RESPONSE);
+}
+
+// Test that redirects to invalid URLs show an error. See
+// https://crbug.com/462272.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, RedirectToInvalidURL) {
+ GURL url = embedded_test_server()->GetURL("/server-redirect?https://:");
+ ui_test_utils::NavigateToURL(browser(), url);
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_INVALID_REDIRECT);
+ // The error page should commit before the redirect, not after.
+ EXPECT_EQ(url, browser()
+ ->tab_strip_model()
+ ->GetActiveWebContents()
+ ->GetLastCommittedURL());
+}
+
+// Checks that when an HTTP error page is sniffed as a download, an error page
+// is displayed. This tests the particular case in which the response body
+// is small enough that the entire response must be read before its MIME type
+// can be determined.
+class ErrorPageSniffTest : public InProcessBrowserTest {};
+
+IN_PROC_BROWSER_TEST_F(ErrorPageSniffTest,
+ SniffSmallHttpErrorResponseAsDownload) {
+ const char kErrorPath[] = "/foo";
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &DNSErrorPageTest::Return500WithBinaryBody, kErrorPath));
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL(kErrorPath));
+
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_INVALID_RESPONSE);
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/net/file_downloader.cc b/chromium/chrome/browser/net/file_downloader.cc
new file mode 100644
index 00000000000..93edaf36fa1
--- /dev/null
+++ b/chromium/chrome/browser/net/file_downloader.cc
@@ -0,0 +1,106 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/file_downloader.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task_runner_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+const int kNumFileDownloaderRetries = 1;
+
+FileDownloader::FileDownloader(
+ const GURL& url,
+ const base::FilePath& path,
+ bool overwrite,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DownloadFinishedCallback callback,
+ const net::NetworkTrafficAnnotationTag& traffic_annotation)
+ : url_loader_factory_(url_loader_factory),
+ callback_(std::move(callback)),
+ local_path_(path) {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+ simple_url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), traffic_annotation);
+ simple_url_loader_->SetRetryOptions(
+ kNumFileDownloaderRetries,
+ network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
+ if (overwrite) {
+ simple_url_loader_->DownloadToTempFile(
+ url_loader_factory_.get(),
+ base::BindOnce(&FileDownloader::OnSimpleDownloadComplete,
+ base::Unretained(this)));
+ } else {
+ base::PostTaskAndReplyWithResult(
+ base::CreateTaskRunner(
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})
+ .get(),
+ FROM_HERE, base::Bind(&base::PathExists, local_path_),
+ base::Bind(&FileDownloader::OnFileExistsCheckDone,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+}
+
+FileDownloader::~FileDownloader() {}
+
+void FileDownloader::OnSimpleDownloadComplete(base::FilePath response_path) {
+ if (response_path.empty()) {
+ if (simple_url_loader_->ResponseInfo() &&
+ simple_url_loader_->ResponseInfo()->headers) {
+ int response_code =
+ simple_url_loader_->ResponseInfo()->headers->response_code();
+ DLOG(WARNING) << "HTTP error " << response_code
+ << " while trying to download "
+ << simple_url_loader_->GetFinalURL().spec();
+ }
+ std::move(callback_).Run(FAILED);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ base::CreateTaskRunner({base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})
+ .get(),
+ FROM_HERE, base::Bind(&base::Move, response_path, local_path_),
+ base::Bind(&FileDownloader::OnFileMoveDone,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FileDownloader::OnFileExistsCheckDone(bool exists) {
+ if (exists) {
+ std::move(callback_).Run(EXISTS);
+ } else {
+ simple_url_loader_->DownloadToTempFile(
+ url_loader_factory_.get(),
+ base::BindOnce(&FileDownloader::OnSimpleDownloadComplete,
+ base::Unretained(this)));
+ }
+}
+
+void FileDownloader::OnFileMoveDone(bool success) {
+ if (!success) {
+ DLOG(WARNING) << "Could not move file to "
+ << local_path_.LossyDisplayName();
+ }
+
+ std::move(callback_).Run(success ? DOWNLOADED : FAILED);
+}
diff --git a/chromium/chrome/browser/net/file_downloader.h b/chromium/chrome/browser/net/file_downloader.h
new file mode 100644
index 00000000000..17bc38348fb
--- /dev/null
+++ b/chromium/chrome/browser/net/file_downloader.h
@@ -0,0 +1,72 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_FILE_DOWNLOADER_H_
+#define CHROME_BROWSER_NET_FILE_DOWNLOADER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace network {
+class SimpleURLLoader;
+class SharedURLLoaderFactory;
+} // namespace network
+
+class GURL;
+
+// Helper class to download a file from a given URL and store it in a local
+// file. If |overwrite| is true, any existing file will be overwritten;
+// otherwise if the local file already exists, this will report success without
+// downloading anything.
+class FileDownloader {
+ public:
+ enum Result {
+ // The file was successfully downloaded.
+ DOWNLOADED,
+ // A local file at the given path already existed and was kept.
+ EXISTS,
+ // Downloading failed.
+ FAILED
+ };
+ using DownloadFinishedCallback = base::OnceCallback<void(Result)>;
+
+ // Directly starts the download (if necessary) and runs |callback| when done.
+ // If the instance is destroyed before it is finished, |callback| is not run.
+ FileDownloader(
+ const GURL& url,
+ const base::FilePath& path,
+ bool overwrite,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DownloadFinishedCallback callback,
+ const net::NetworkTrafficAnnotationTag& traffic_annotation);
+ ~FileDownloader();
+
+ static bool IsSuccess(Result result) { return result != FAILED; }
+
+ private:
+ void OnSimpleDownloadComplete(base::FilePath response_path);
+
+ void OnFileExistsCheckDone(bool exists);
+
+ void OnFileMoveDone(bool success);
+
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
+ DownloadFinishedCallback callback_;
+
+ std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
+
+ base::FilePath local_path_;
+
+ base::WeakPtrFactory<FileDownloader> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(FileDownloader);
+};
+
+#endif // CHROME_BROWSER_NET_FILE_DOWNLOADER_H_
diff --git a/chromium/chrome/browser/net/file_downloader_unittest.cc b/chromium/chrome/browser/net/file_downloader_unittest.cc
new file mode 100644
index 00000000000..78d3bade97c
--- /dev/null
+++ b/chromium/chrome/browser/net/file_downloader_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/file_downloader.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/public/test/test_utils.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+const char kURL[] = "https://www.url.com/path";
+const char kFilename[] = "filename.ext";
+const char kFileContents1[] = "file contents";
+const char kFileContents2[] = "different contents";
+
+class FileDownloaderTest : public testing::Test {
+ public:
+ FileDownloaderTest()
+ : test_shared_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+ path_ = dir_.GetPath().AppendASCII(kFilename);
+ ASSERT_FALSE(base::PathExists(path_));
+ }
+
+ MOCK_METHOD1(OnDownloadFinished, void(FileDownloader::Result));
+
+ protected:
+ const base::FilePath& path() const { return path_; }
+
+ void SetValidResponse() {
+ test_url_loader_factory_.AddResponse(kURL, kFileContents1);
+ }
+
+ void SetValidResponse2() {
+ test_url_loader_factory_.AddResponse(kURL, kFileContents2);
+ }
+
+ void SetFailedResponse() {
+ test_url_loader_factory_.AddResponse(
+ GURL(kURL), network::mojom::URLResponseHead::New(), std::string(),
+ network::URLLoaderCompletionStatus(net::HTTP_NOT_FOUND));
+ }
+
+ void Download(bool overwrite, FileDownloader::Result expected_result) {
+ FileDownloader downloader(
+ GURL(kURL), path_, overwrite, test_shared_loader_factory_,
+ base::BindOnce(&FileDownloaderTest::OnDownloadFinished,
+ base::Unretained(this)),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ EXPECT_CALL(*this, OnDownloadFinished(expected_result));
+ // Wait for the FileExists check to happen if necessary.
+ content::RunAllTasksUntilIdle();
+ }
+
+ private:
+ base::ScopedTempDir dir_;
+ base::FilePath path_;
+
+ base::test::TaskEnvironment task_environment_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+};
+
+TEST_F(FileDownloaderTest, Success) {
+ SetValidResponse();
+ Download(true, FileDownloader::DOWNLOADED);
+ EXPECT_TRUE(base::PathExists(path()));
+ std::string contents;
+ ASSERT_TRUE(base::ReadFileToString(path(), &contents));
+ EXPECT_EQ(std::string(kFileContents1), contents);
+}
+
+TEST_F(FileDownloaderTest, Failure) {
+ SetFailedResponse();
+ Download(true, FileDownloader::FAILED);
+ EXPECT_FALSE(base::PathExists(path()));
+}
+
+TEST_F(FileDownloaderTest, Overwrite) {
+ SetValidResponse();
+ Download(true, FileDownloader::DOWNLOADED);
+ ASSERT_TRUE(base::PathExists(path()));
+ std::string contents;
+ ASSERT_TRUE(base::ReadFileToString(path(), &contents));
+ ASSERT_EQ(std::string(kFileContents1), contents);
+
+ SetValidResponse2();
+ Download(true, FileDownloader::DOWNLOADED);
+ // The file should have been overwritten with the new contents.
+ EXPECT_TRUE(base::PathExists(path()));
+ ASSERT_TRUE(base::ReadFileToString(path(), &contents));
+ EXPECT_EQ(std::string(kFileContents2), contents);
+}
+
+TEST_F(FileDownloaderTest, DontOverwrite) {
+ SetValidResponse();
+ Download(true, FileDownloader::DOWNLOADED);
+ ASSERT_TRUE(base::PathExists(path()));
+ std::string contents;
+ ASSERT_TRUE(base::ReadFileToString(path(), &contents));
+ EXPECT_EQ(std::string(kFileContents1), contents);
+
+ SetValidResponse2();
+ Download(false, FileDownloader::EXISTS);
+ // The file should still have the old contents.
+ EXPECT_TRUE(base::PathExists(path()));
+ ASSERT_TRUE(base::ReadFileToString(path(), &contents));
+ EXPECT_EQ(std::string(kFileContents1), contents);
+}
diff --git a/chromium/chrome/browser/net/ftp_browsertest.cc b/chromium/chrome/browser/net/ftp_browsertest.cc
new file mode 100644
index 00000000000..1b3fe62df50
--- /dev/null
+++ b/chromium/chrome/browser/net/ftp_browsertest.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/download_test_observer.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class FtpBrowserTest : public InProcessBrowserTest {
+ public:
+ FtpBrowserTest()
+ : ftp_server_(net::SpawnedTestServer::TYPE_FTP,
+ base::FilePath(FILE_PATH_LITERAL("chrome/test/data/ftp"))) {
+ scoped_feature_list_.InitAndEnableFeature(features::kEnableFtp);
+ }
+
+ protected:
+ net::SpawnedTestServer ftp_server_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+void WaitForTitle(content::WebContents* contents, const char* expected_title) {
+ content::TitleWatcher title_watcher(contents,
+ base::ASCIIToUTF16(expected_title));
+
+ EXPECT_EQ(base::ASCIIToUTF16(expected_title),
+ title_watcher.WaitAndGetTitle());
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(FtpBrowserTest, BasicFtpUrlAuthentication) {
+ ASSERT_TRUE(ftp_server_.Start());
+ ui_test_utils::NavigateToURL(
+ browser(),
+ ftp_server_.GetURLWithUserAndPassword("", "chrome", "chrome"));
+
+ WaitForTitle(browser()->tab_strip_model()->GetActiveWebContents(),
+ "Index of /");
+}
+
+IN_PROC_BROWSER_TEST_F(FtpBrowserTest, DirectoryListingNavigation) {
+ ftp_server_.set_no_anonymous_ftp_user(true);
+ ASSERT_TRUE(ftp_server_.Start());
+
+ ui_test_utils::NavigateToURL(
+ browser(),
+ ftp_server_.GetURLWithUserAndPassword("", "chrome", "chrome"));
+
+ // Navigate to directory dir1/ without needing to re-authenticate
+ EXPECT_TRUE(content::ExecuteScript(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "(function() {"
+ " function navigate() {"
+ " for (const element of document.getElementsByTagName('a')) {"
+ " if (element.innerHTML == 'dir1/') {"
+ " element.click();"
+ " }"
+ " }"
+ " }"
+ " if (document.readyState === 'loading') {"
+ " document.addEventListener('DOMContentLoaded', navigate);"
+ " } else {"
+ " navigate();"
+ " }"
+ "})()"));
+
+ WaitForTitle(browser()->tab_strip_model()->GetActiveWebContents(),
+ "Index of /dir1/");
+
+ // Navigate to file `test.html`, verify that it's downloaded.
+ content::DownloadTestObserverTerminal download_test_observer_terminal(
+ content::BrowserContext::GetDownloadManager(browser()->profile()), 1,
+ content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
+
+ EXPECT_TRUE(content::ExecuteScript(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "(function() {"
+ " function navigate() {"
+ " for (const element of document.getElementsByTagName('a')) {"
+ " if (element.innerHTML == 'test.html') {"
+ " element.click();"
+ " }"
+ " }"
+ " }"
+ " if (document.readyState === 'loading') {"
+ " document.addEventListener('DOMContentLoaded', navigate);"
+ " } else {"
+ " navigate();"
+ " }"
+ "})()"));
+
+ download_test_observer_terminal.WaitForFinished();
+ EXPECT_EQ(download_test_observer_terminal.NumDownloadsSeenInState(
+ download::DownloadItem::COMPLETE),
+ 1u);
+}
diff --git a/chromium/chrome/browser/net/load_timing_browsertest.cc b/chromium/chrome/browser/net/load_timing_browsertest.cc
new file mode 100644
index 00000000000..08133863ccd
--- /dev/null
+++ b/chromium/chrome/browser/net/load_timing_browsertest.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/net/profile_network_context_service.h"
+#include "chrome/browser/net/profile_network_context_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/prefs/pref_service.h"
+#include "components/proxy_config/proxy_config_dictionary.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "url/gurl.h"
+
+// This file tests that net::LoadTimingInfo is correctly hooked up to the
+// NavigationTiming API. It depends on behavior in a large number of files
+// spread across multiple projects, so is somewhat arbitrarily put in
+// chrome/browser/net.
+
+namespace {
+
+// Structure used for retrieved from the renderer process.
+// These are milliseconds after fetch start, or -1 if not
+// present
+struct TimingDeltas {
+ int dns_start;
+ int dns_end;
+ int connect_start;
+ int ssl_start;
+ int connect_end;
+ int send_start;
+ // Must be non-negative and greater than all other times.
+ int receive_headers_end;
+};
+
+class LoadTimingBrowserTest : public InProcessBrowserTest {
+ public:
+ LoadTimingBrowserTest() {
+ }
+
+ ~LoadTimingBrowserTest() override {}
+
+ // Reads applicable times from performance.timing and writes them to
+ // |navigation_deltas|. Proxy times and send end cannot be read from the
+ // Navigation Timing API, so those are all left as null.
+ void GetResultDeltas(TimingDeltas* navigation_deltas) {
+ *navigation_deltas = TimingDeltas();
+ navigation_deltas->dns_start = GetResultDelta("domainLookupStart");
+ navigation_deltas->dns_end = GetResultDelta("domainLookupEnd");
+ navigation_deltas->connect_start = GetResultDelta("connectStart");
+ navigation_deltas->connect_end = GetResultDelta("connectEnd");
+ navigation_deltas->send_start = GetResultDelta("requestStart");
+ navigation_deltas->receive_headers_end = GetResultDelta("responseStart");
+
+ // Unlike the above times, secureConnectionStart will be zero when not
+ // applicable. In that case, leave ssl_start as null.
+ bool ssl_start_zero = false;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "window.domAutomationController.send("
+ "performance.timing.secureConnectionStart == 0);",
+ &ssl_start_zero));
+ if (!ssl_start_zero)
+ navigation_deltas->ssl_start = GetResultDelta("secureConnectionStart");
+ else
+ navigation_deltas->ssl_start = -1;
+
+ // Simple sanity checks. Make sure times that correspond to LoadTimingInfo
+ // occur between fetchStart and loadEventEnd. Relationships between
+ // intervening times are handled by the test bodies.
+ int fetch_start = GetResultDelta("fetchStart");
+ // While the input dns_start is sometimes null, when read from the
+ // NavigationTiming API, it's always non-null.
+ EXPECT_LE(fetch_start, navigation_deltas->dns_start);
+
+ int load_event_end = GetResultDelta("loadEventEnd");
+ EXPECT_LE(navigation_deltas->receive_headers_end, load_event_end);
+ }
+
+ // Returns the time between performance.timing.fetchStart and the time with
+ // the specified name. This time must be non-negative.
+ int GetResultDelta(const std::string& name) {
+ int time_ms = 0;
+ std::string command(base::StringPrintf(
+ "window.domAutomationController.send("
+ "performance.timing.%s - performance.timing.fetchStart);",
+ name.c_str()));
+ EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ command.c_str(),
+ &time_ms));
+ // Basic sanity check.
+ EXPECT_GE(time_ms, 0);
+ return time_ms;
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTest, HTTP) {
+ ASSERT_TRUE(spawned_test_server()->Start());
+ GURL url = spawned_test_server()->GetURL("chunked?waitBeforeHeaders=100");
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ TimingDeltas navigation_deltas;
+ GetResultDeltas(&navigation_deltas);
+
+ EXPECT_LE(navigation_deltas.dns_start, navigation_deltas.dns_end);
+ EXPECT_LE(navigation_deltas.dns_end, navigation_deltas.connect_start);
+ EXPECT_LE(navigation_deltas.connect_start, navigation_deltas.connect_end);
+ EXPECT_LE(navigation_deltas.connect_end, navigation_deltas.send_start);
+ EXPECT_LT(navigation_deltas.send_start,
+ navigation_deltas.receive_headers_end);
+
+ EXPECT_EQ(navigation_deltas.ssl_start, -1);
+}
+
+IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTest, HTTPS) {
+ net::SpawnedTestServer https_server(net::SpawnedTestServer::TYPE_HTTPS,
+ net::BaseTestServer::SSLOptions(),
+ base::FilePath());
+ ASSERT_TRUE(https_server.Start());
+ GURL url = https_server.GetURL("chunked?waitBeforeHeaders=100");
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ TimingDeltas navigation_deltas;
+ GetResultDeltas(&navigation_deltas);
+
+ EXPECT_LE(navigation_deltas.dns_start, navigation_deltas.dns_end);
+ EXPECT_LE(navigation_deltas.dns_end, navigation_deltas.connect_start);
+ EXPECT_LE(navigation_deltas.connect_start, navigation_deltas.ssl_start);
+ EXPECT_LE(navigation_deltas.ssl_start, navigation_deltas.connect_end);
+ EXPECT_LE(navigation_deltas.connect_end, navigation_deltas.send_start);
+ EXPECT_LT(navigation_deltas.send_start,
+ navigation_deltas.receive_headers_end);
+}
+
+// Flaky on Win10: crbug.com/997823
+#if defined(OS_WIN)
+#define MAYBE_Proxy DISABLED_Proxy
+#else
+#define MAYBE_Proxy Proxy
+#endif
+IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTest, MAYBE_Proxy) {
+ ASSERT_TRUE(spawned_test_server()->Start());
+
+ browser()->profile()->GetPrefs()->Set(
+ proxy_config::prefs::kProxy,
+ ProxyConfigDictionary::CreateFixedServers(
+ spawned_test_server()->host_port_pair().ToString(), std::string()));
+ ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
+ ->FlushProxyConfigMonitorForTesting();
+
+ // This request will fail if it doesn't go through proxy.
+ GURL url("http://does.not.resolve.test/chunked?waitBeforeHeaders=100");
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ TimingDeltas navigation_deltas;
+ GetResultDeltas(&navigation_deltas);
+
+ EXPECT_LE(navigation_deltas.dns_start, navigation_deltas.dns_end);
+ EXPECT_LE(navigation_deltas.dns_end, navigation_deltas.connect_start);
+ EXPECT_LE(navigation_deltas.connect_start, navigation_deltas.connect_end);
+ EXPECT_LE(navigation_deltas.connect_end, navigation_deltas.send_start);
+ EXPECT_LT(navigation_deltas.send_start,
+ navigation_deltas.receive_headers_end);
+
+ EXPECT_EQ(navigation_deltas.ssl_start, -1);
+}
+
+IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTest, FTP) {
+ net::SpawnedTestServer ftp_server(net::SpawnedTestServer::TYPE_FTP,
+ base::FilePath());
+ ASSERT_TRUE(ftp_server.Start());
+ GURL url = ftp_server.GetURL("/");
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ TimingDeltas navigation_deltas;
+ GetResultDeltas(&navigation_deltas);
+
+ EXPECT_EQ(navigation_deltas.dns_start, 0);
+ EXPECT_EQ(navigation_deltas.dns_end, 0);
+ EXPECT_EQ(navigation_deltas.connect_start, 0);
+ EXPECT_EQ(navigation_deltas.connect_end, 0);
+ EXPECT_EQ(navigation_deltas.receive_headers_end, 0);
+ EXPECT_EQ(navigation_deltas.ssl_start, -1);
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/net/log_net_log_browsertest.cc b/chromium/chrome/browser/net/log_net_log_browsertest.cc
new file mode 100644
index 00000000000..db8e070d0a2
--- /dev/null
+++ b/chromium/chrome/browser/net/log_net_log_browsertest.cc
@@ -0,0 +1,101 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_reader.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "services/network/public/cpp/network_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chrome_browser_net {
+namespace {
+
+// Test fixture for running tests with --log-net-log, and a parameterized value
+// for --net-log-capture-mode.
+//
+// Asserts that a netlog file was created, appears valid, and stripped cookies
+// in accordance to the --net-log-capture-mode flag.
+class LogNetLogTest : public InProcessBrowserTest,
+ public testing::WithParamInterface<const char*> {
+ public:
+ LogNetLogTest() = default;
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
+ net_log_path_ = tmp_dir_.GetPath().AppendASCII("netlog.json");
+
+ command_line->AppendSwitchPath(network::switches::kLogNetLog,
+ net_log_path_);
+
+ if (GetParam()) {
+ command_line->AppendSwitchASCII(network::switches::kNetLogCaptureMode,
+ GetParam());
+ }
+ }
+
+ void TearDownInProcessBrowserTestFixture() override { VerifyNetLog(); }
+
+ private:
+ // Verify that the netlog file was written, appears to be well formed, and
+ // includes the requested level of data.
+ void VerifyNetLog() {
+ // Read the netlog from disk.
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(net_log_path_, &file_contents))
+ << "Could not read: " << net_log_path_;
+
+ // Parse it as JSON.
+ auto parsed = base::JSONReader::ReadDeprecated(file_contents);
+ ASSERT_TRUE(parsed);
+
+ // Ensure the root value is a dictionary.
+ base::DictionaryValue* main;
+ ASSERT_TRUE(parsed->GetAsDictionary(&main));
+
+ // Ensure it has a "constants" property.
+ base::DictionaryValue* constants;
+ ASSERT_TRUE(main->GetDictionary("constants", &constants));
+ ASSERT_FALSE(constants->empty());
+
+ // Ensure it has an "events" property.
+ base::ListValue* events;
+ ASSERT_TRUE(main->GetList("events", &events));
+ ASSERT_FALSE(events->empty());
+
+ // Verify that cookies were stripped when the --net-log-capture-mode flag
+ // was omitted, and not stripped when it was given a value of
+ // IncludeSensitive
+ bool include_cookies =
+ GetParam() && base::StringPiece(GetParam()) == "IncludeSensitive";
+
+ if (include_cookies) {
+ EXPECT_TRUE(file_contents.find("Set-Cookie: name=Good;Max-Age=3600") !=
+ std::string::npos);
+ } else {
+ EXPECT_TRUE(file_contents.find("Set-Cookie: [22 bytes were stripped]") !=
+ std::string::npos);
+ }
+ }
+
+ base::FilePath net_log_path_;
+ base::ScopedTempDir tmp_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(LogNetLogTest);
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+ LogNetLogTest,
+ ::testing::Values(nullptr, "IncludeSensitive"));
+
+IN_PROC_BROWSER_TEST_P(LogNetLogTest, Basic) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/set_cookie_header.html"));
+ ui_test_utils::NavigateToURL(browser(), url);
+}
+
+} // namespace
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/net_error_diagnostics_dialog.h b/chromium/chrome/browser/net/net_error_diagnostics_dialog.h
new file mode 100644
index 00000000000..6b2f88cba88
--- /dev/null
+++ b/chromium/chrome/browser/net/net_error_diagnostics_dialog.h
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_NET_ERROR_DIAGNOSTICS_DIALOG_H_
+#define CHROME_BROWSER_NET_NET_ERROR_DIAGNOSTICS_DIALOG_H_
+
+#include <string>
+
+namespace content {
+class WebContents;
+}
+
+// Returns true if a tool for diagnosing network errors encountered when
+// requesting URLs can be shown for the provided WebContents. The ability to
+// show the diagnostic tool depends on the host platform, and whether the
+// WebContents is incognito.
+bool CanShowNetworkDiagnosticsDialog(content::WebContents* web_contents);
+
+// Shows a dialog for investigating an error received when requesting
+// |failed_url|. May only be called when CanShowNetworkDiagnosticsDialog()
+// returns true. The caller is responsible for sanitizing the url.
+void ShowNetworkDiagnosticsDialog(content::WebContents* web_contents,
+ const std::string& failed_url);
+
+#endif // CHROME_BROWSER_NET_NET_ERROR_DIAGNOSTICS_DIALOG_H_
+
diff --git a/chromium/chrome/browser/net/net_error_diagnostics_dialog_posix.cc b/chromium/chrome/browser/net/net_error_diagnostics_dialog_posix.cc
new file mode 100644
index 00000000000..2c21794a898
--- /dev/null
+++ b/chromium/chrome/browser/net/net_error_diagnostics_dialog_posix.cc
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/net_error_diagnostics_dialog.h"
+
+#include "base/logging.h"
+
+bool CanShowNetworkDiagnosticsDialog(content::WebContents* web_contents) {
+ return false;
+}
+
+void ShowNetworkDiagnosticsDialog(content::WebContents* web_contents,
+ const std::string& failed_url) {
+ NOTREACHED();
+}
diff --git a/chromium/chrome/browser/net/net_error_diagnostics_dialog_win.cc b/chromium/chrome/browser/net/net_error_diagnostics_dialog_win.cc
new file mode 100644
index 00000000000..ea046e11d3a
--- /dev/null
+++ b/chromium/chrome/browser/net/net_error_diagnostics_dialog_win.cc
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/net_error_diagnostics_dialog.h"
+
+// Winsock.h must be included before ndfapi.h.
+#include <winsock2.h> // NOLINT
+#include <ndfapi.h> // NOLINT
+#include <windows.h> // NOLINT
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/native_library.h"
+#include "base/scoped_native_library.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task_runner.h"
+#include "base/threading/thread.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/shell_dialogs/base_shell_dialog_win.h"
+#include "ui/views/win/hwnd_util.h"
+
+namespace {
+
+class NetErrorDiagnosticsDialog : public ui::BaseShellDialogImpl {
+ public:
+ NetErrorDiagnosticsDialog() {}
+ ~NetErrorDiagnosticsDialog() override {}
+
+ // NetErrorDiagnosticsDialog implementation.
+ void Show(content::WebContents* web_contents,
+ const std::string& failed_url,
+ const base::Closure& callback) {
+ DCHECK(!callback.is_null());
+
+ HWND parent =
+ views::HWNDForNativeWindow(web_contents->GetTopLevelNativeWindow());
+ if (IsRunningDialogForOwner(parent))
+ return;
+
+ std::unique_ptr<RunState> run_state = BeginRun(parent);
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+ run_state->dialog_task_runner;
+ task_runner->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(&NetErrorDiagnosticsDialog::ShowDialogOnPrivateThread,
+ base::Unretained(this), parent, failed_url),
+ base::BindOnce(&NetErrorDiagnosticsDialog::DiagnosticsDone,
+ base::Unretained(this), std::move(run_state), callback));
+ }
+
+ private:
+ void ShowDialogOnPrivateThread(HWND parent, const std::string& failed_url) {
+ NDFHANDLE incident_handle;
+ base::string16 failed_url_wide = base::UTF8ToUTF16(failed_url);
+ if (!SUCCEEDED(NdfCreateWebIncident(failed_url_wide.c_str(),
+ &incident_handle))) {
+ return;
+ }
+ NdfExecuteDiagnosis(incident_handle, parent);
+ NdfCloseIncident(incident_handle);
+ }
+
+ void DiagnosticsDone(std::unique_ptr<RunState> run_state,
+ const base::Closure& callback) {
+ EndRun(std::move(run_state));
+ callback.Run();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NetErrorDiagnosticsDialog);
+};
+
+} // namespace
+
+bool CanShowNetworkDiagnosticsDialog(content::WebContents* web_contents) {
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ // The Windows diagnostic tool logs URLs it's run with, so it shouldn't be
+ // used with incognito or guest profiles. See https://crbug.com/929141
+ return !profile->IsOffTheRecord() && !profile->IsGuestSession();
+}
+
+void ShowNetworkDiagnosticsDialog(content::WebContents* web_contents,
+ const std::string& failed_url) {
+ DCHECK(CanShowNetworkDiagnosticsDialog(web_contents));
+
+ NetErrorDiagnosticsDialog* dialog = new NetErrorDiagnosticsDialog();
+ dialog->Show(
+ web_contents, failed_url,
+ base::Bind(&base::DeletePointer<NetErrorDiagnosticsDialog>, dialog));
+}
diff --git a/chromium/chrome/browser/net/net_error_tab_helper.cc b/chromium/chrome/browser/net/net_error_tab_helper.cc
new file mode 100644
index 00000000000..2f45389e0e7
--- /dev/null
+++ b/chromium/chrome/browser/net/net_error_tab_helper.cc
@@ -0,0 +1,309 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/net_error_tab_helper.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/browser/net/dns_probe_service_factory.h"
+#include "chrome/browser/net/net_error_diagnostics_dialog.h"
+#include "chrome/browser/platform_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/render_messages.h"
+#include "components/error_page/common/net_error_info.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "ipc/ipc_message_macros.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "net/base/net_errors.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+#include "chrome/browser/offline_pages/offline_page_utils.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+using content::BrowserContext;
+using content::BrowserThread;
+using content::WebContents;
+using content::WebContentsObserver;
+using error_page::DnsProbeStatus;
+using error_page::DnsProbeStatusToString;
+using ui::PageTransition;
+
+namespace chrome_browser_net {
+
+namespace {
+
+static NetErrorTabHelper::TestingState testing_state_ =
+ NetErrorTabHelper::TESTING_DEFAULT;
+
+} // namespace
+
+NetErrorTabHelper::~NetErrorTabHelper() {
+}
+
+// static
+void NetErrorTabHelper::set_state_for_testing(TestingState state) {
+ testing_state_ = state;
+}
+
+void NetErrorTabHelper::RenderFrameCreated(
+ content::RenderFrameHost* render_frame_host) {
+ // Ignore subframe creation - only main frame error pages can link to the
+ // platform's network diagnostics dialog.
+ if (render_frame_host->GetParent())
+ return;
+
+ mojo::AssociatedRemote<chrome::mojom::NetworkDiagnosticsClient> client;
+ render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&client);
+ client->SetCanShowNetworkDiagnosticsDialog(
+ CanShowNetworkDiagnosticsDialog(web_contents()));
+}
+
+void NetErrorTabHelper::DidStartNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!navigation_handle->IsInMainFrame())
+ return;
+
+ if (navigation_handle->IsErrorPage() &&
+ PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(),
+ ui::PAGE_TRANSITION_RELOAD)) {
+ error_page::RecordEvent(
+ error_page::NETWORK_ERROR_PAGE_BROWSER_INITIATED_RELOAD);
+ }
+}
+
+void NetErrorTabHelper::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!navigation_handle->IsInMainFrame())
+ return;
+
+ if (net::IsDnsError(navigation_handle->GetNetErrorCode())) {
+ dns_error_active_ = true;
+ OnMainFrameDnsError();
+ }
+
+ // Resend status every time an error page commits; this is somewhat spammy,
+ // but ensures that the status will make it to the real error page, even if
+ // the link doctor loads a blank intermediate page or the tab switches
+ // renderer processes.
+ if (navigation_handle->IsErrorPage() && dns_error_active_) {
+ dns_error_page_committed_ = true;
+ DVLOG(1) << "Committed error page; resending status.";
+ SendInfo();
+ } else if (navigation_handle->HasCommitted() &&
+ !navigation_handle->IsErrorPage()) {
+ dns_error_active_ = false;
+ dns_error_page_committed_ = false;
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ is_showing_download_button_in_error_page_ = false;
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ }
+}
+
+bool NetErrorTabHelper::OnMessageReceived(
+ const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) {
+ if (render_frame_host != web_contents()->GetMainFrame())
+ return false;
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(NetErrorTabHelper, message)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DownloadPageLater,
+ OnDownloadPageLater)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetIsShowingDownloadButtonInErrorPage,
+ OnSetIsShowingDownloadButtonInErrorPage)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+#else
+ return false;
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+}
+
+NetErrorTabHelper::NetErrorTabHelper(WebContents* contents)
+ : WebContentsObserver(contents),
+ network_diagnostics_bindings_(contents, this),
+ network_easter_egg_bindings_(contents, this),
+ is_error_page_(false),
+ dns_error_active_(false),
+ dns_error_page_committed_(false),
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ is_showing_download_button_in_error_page_(false),
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ dns_probe_status_(error_page::DNS_PROBE_POSSIBLE) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // If this helper is under test, it won't have a WebContents.
+ if (contents)
+ InitializePref(contents);
+}
+
+void NetErrorTabHelper::OnMainFrameDnsError() {
+ if (ProbesAllowed()) {
+ // Don't start more than one probe at a time.
+ if (dns_probe_status_ != error_page::DNS_PROBE_STARTED) {
+ StartDnsProbe();
+ dns_probe_status_ = error_page::DNS_PROBE_STARTED;
+ }
+ } else {
+ dns_probe_status_ = error_page::DNS_PROBE_NOT_RUN;
+ }
+}
+
+void NetErrorTabHelper::StartDnsProbe() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(dns_error_active_);
+ DCHECK_NE(error_page::DNS_PROBE_STARTED, dns_probe_status_);
+
+ DVLOG(1) << "Starting DNS probe.";
+
+ DnsProbeService* probe_service = DnsProbeServiceFactory::GetForContext(
+ web_contents()->GetBrowserContext());
+ probe_service->ProbeDns(base::BindOnce(&NetErrorTabHelper::OnDnsProbeFinished,
+ weak_factory_.GetWeakPtr()));
+}
+
+void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_EQ(error_page::DNS_PROBE_STARTED, dns_probe_status_);
+ DCHECK(error_page::DnsProbeStatusIsFinished(result));
+
+ DVLOG(1) << "Finished DNS probe with result "
+ << DnsProbeStatusToString(result) << ".";
+
+ dns_probe_status_ = result;
+
+ if (dns_error_page_committed_)
+ SendInfo();
+}
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+void NetErrorTabHelper::OnDownloadPageLater() {
+ // Makes sure that this is coming from an error page.
+ content::NavigationEntry* entry =
+ web_contents()->GetController().GetLastCommittedEntry();
+ if (!entry || entry->GetPageType() != content::PAGE_TYPE_ERROR)
+ return;
+
+ // Only download the page for HTTP/HTTPS URLs.
+ GURL url(entry->GetVirtualURL());
+ if (!url.SchemeIsHTTPOrHTTPS())
+ return;
+
+ DownloadPageLaterHelper(url);
+}
+
+void NetErrorTabHelper::OnSetIsShowingDownloadButtonInErrorPage(
+ bool is_showing_download_button) {
+ is_showing_download_button_in_error_page_ = is_showing_download_button;
+}
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+// static
+void NetErrorTabHelper::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* prefs) {
+ // prefs::kAlternateErrorPagesEnabled is registered by
+ // NavigationCorrectionTabObserver.
+
+ prefs->RegisterIntegerPref(prefs::kNetworkEasterEggHighScore, 0,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+void NetErrorTabHelper::InitializePref(WebContents* contents) {
+ DCHECK(contents);
+
+ BrowserContext* browser_context = contents->GetBrowserContext();
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+ resolve_errors_with_web_service_.Init(
+ prefs::kAlternateErrorPagesEnabled,
+ profile->GetPrefs());
+ easter_egg_high_score_.Init(prefs::kNetworkEasterEggHighScore,
+ profile->GetPrefs());
+}
+
+bool NetErrorTabHelper::ProbesAllowed() const {
+ if (testing_state_ != TESTING_DEFAULT)
+ return testing_state_ == TESTING_FORCE_ENABLED;
+
+ // TODO(juliatuttle): Disable on mobile?
+ return *resolve_errors_with_web_service_;
+}
+
+void NetErrorTabHelper::SendInfo() {
+ DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, dns_probe_status_);
+ DCHECK(dns_error_page_committed_);
+
+ DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_);
+ content::RenderFrameHost* rfh = web_contents()->GetMainFrame();
+
+ mojo::AssociatedRemote<chrome::mojom::NetworkDiagnosticsClient> client;
+ rfh->GetRemoteAssociatedInterfaces()->GetInterface(&client);
+ client->DNSProbeStatus(dns_probe_status_);
+
+ if (!dns_probe_status_snoop_callback_.is_null())
+ dns_probe_status_snoop_callback_.Run(dns_probe_status_);
+}
+
+void NetErrorTabHelper::RunNetworkDiagnostics(const GURL& url) {
+ // Only run diagnostics on HTTP or HTTPS URLs. Shouldn't receive URLs with
+ // any other schemes, but the renderer is not trusted.
+ if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
+ return;
+
+ // Sanitize URL prior to running diagnostics on it.
+ RunNetworkDiagnosticsHelper(url.GetOrigin().spec());
+}
+
+void NetErrorTabHelper::RunNetworkDiagnosticsHelper(
+ const std::string& sanitized_url) {
+ // The button shouldn't even be shown in this case, but still best to be safe,
+ // since the renderer isn't trusted.
+ if (!CanShowNetworkDiagnosticsDialog(web_contents()))
+ return;
+
+ if (network_diagnostics_bindings_.GetCurrentTargetFrame()
+ != web_contents()->GetMainFrame()) {
+ return;
+ }
+
+ ShowNetworkDiagnosticsDialog(web_contents(), sanitized_url);
+}
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+void NetErrorTabHelper::DownloadPageLaterHelper(const GURL& page_url) {
+ offline_pages::OfflinePageUtils::ScheduleDownload(
+ web_contents(), offline_pages::kAsyncNamespace, page_url,
+ offline_pages::OfflinePageUtils::DownloadUIActionFlags::PROMPT_DUPLICATE);
+}
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+void NetErrorTabHelper::GetHighScore(GetHighScoreCallback callback) {
+ std::move(callback).Run(
+ static_cast<uint32_t>(easter_egg_high_score_.GetValue()));
+}
+
+void NetErrorTabHelper::UpdateHighScore(uint32_t high_score) {
+ if (high_score <= static_cast<uint32_t>(easter_egg_high_score_.GetValue()))
+ return;
+ easter_egg_high_score_.SetValue(static_cast<int>(high_score));
+}
+
+void NetErrorTabHelper::ResetHighScore() {
+ easter_egg_high_score_.SetValue(0);
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(NetErrorTabHelper)
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/net_error_tab_helper.h b/chromium/chrome/browser/net/net_error_tab_helper.h
new file mode 100644
index 00000000000..e00ba06f4a1
--- /dev/null
+++ b/chromium/chrome/browser/net/net_error_tab_helper.h
@@ -0,0 +1,170 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_NET_ERROR_TAB_HELPER_H_
+#define CHROME_BROWSER_NET_NET_ERROR_TAB_HELPER_H_
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/common/network_diagnostics.mojom.h"
+#include "chrome/common/network_easter_egg.mojom.h"
+#include "components/error_page/common/net_error_info.h"
+#include "components/offline_pages/buildflags/buildflags.h"
+#include "components/prefs/pref_member.h"
+#include "content/public/browser/reload_type.h"
+#include "content/public/browser/web_contents_binding_set.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+} // namespace user_prefs
+
+namespace chrome_browser_net {
+
+// A TabHelper that monitors loads for certain types of network errors and
+// does interesting things with them. Currently, starts DNS probes using the
+// DnsProbeService whenever a page fails to load with a DNS-related error.
+class NetErrorTabHelper
+ : public content::WebContentsObserver,
+ public content::WebContentsUserData<NetErrorTabHelper>,
+ public chrome::mojom::NetworkDiagnostics,
+ public chrome::mojom::NetworkEasterEgg {
+ public:
+ enum TestingState {
+ TESTING_DEFAULT,
+ TESTING_FORCE_DISABLED,
+ TESTING_FORCE_ENABLED
+ };
+
+ typedef base::Callback<void(error_page::DnsProbeStatus)>
+ DnsProbeStatusSnoopCallback;
+
+ ~NetErrorTabHelper() override;
+
+ static void set_state_for_testing(TestingState testing_state);
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* prefs);
+
+ // Sets a callback that will be called immediately after the helper sends
+ // a NetErrorHelper IPC. (Used by the DNS probe browser test to know when to
+ // check the error page for updates, instead of polling.)
+ void set_dns_probe_status_snoop_callback_for_testing(
+ const DnsProbeStatusSnoopCallback& dns_probe_status_snoop_callback) {
+ dns_probe_status_snoop_callback_ = dns_probe_status_snoop_callback;
+ }
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ bool is_showing_download_button_in_error_page() const {
+ return is_showing_download_button_in_error_page_;
+ }
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ // content::WebContentsObserver implementation.
+ void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ bool OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) override;
+
+ protected:
+ // |contents| is the WebContents of the tab this NetErrorTabHelper is
+ // attached to.
+ explicit NetErrorTabHelper(content::WebContents* contents);
+ virtual void StartDnsProbe();
+ virtual void SendInfo();
+ void OnDnsProbeFinished(error_page::DnsProbeStatus result);
+
+ error_page::DnsProbeStatus dns_probe_status() const {
+ return dns_probe_status_;
+ }
+
+ content::WebContentsFrameBindingSet<chrome::mojom::NetworkDiagnostics>&
+ network_diagnostics_bindings_for_testing() {
+ return network_diagnostics_bindings_;
+ }
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ void OnDownloadPageLater();
+ void OnSetIsShowingDownloadButtonInErrorPage(bool is_showing_download_button);
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ private:
+ friend class content::WebContentsUserData<NetErrorTabHelper>;
+
+ void OnMainFrameDnsError();
+
+ void InitializePref(content::WebContents* contents);
+ bool ProbesAllowed() const;
+
+ // chrome::mojom::NetworkDiagnostics:
+ void RunNetworkDiagnostics(const GURL& url) override;
+
+ // chrome::mojom::NetworkEasterEgg:
+ void GetHighScore(GetHighScoreCallback callback) override;
+ void UpdateHighScore(uint32_t high_score) override;
+ void ResetHighScore() override;
+
+ // Shows the diagnostics dialog after its been sanitized, virtual for
+ // testing.
+ virtual void RunNetworkDiagnosticsHelper(const std::string& sanitized_url);
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ // Virtual for testing.
+ virtual void DownloadPageLaterHelper(const GURL& url);
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ content::WebContentsFrameBindingSet<chrome::mojom::NetworkDiagnostics>
+ network_diagnostics_bindings_;
+ content::WebContentsFrameBindingSet<chrome::mojom::NetworkEasterEgg>
+ network_easter_egg_bindings_;
+
+ // True if the last provisional load that started was for an error page.
+ bool is_error_page_;
+
+ // True if the helper has seen a main frame page load fail with a DNS error,
+ // but has not yet seen a new page commit successfully afterwards.
+ bool dns_error_active_;
+
+ // True if the helper has seen an error page commit while |dns_error_active_|
+ // is true. (This should never be true if |dns_error_active_| is false.)
+ bool dns_error_page_committed_;
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ // True if download button is being shown when the error page commits.
+ bool is_showing_download_button_in_error_page_;
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ // The status of a DNS probe that may or may not have started or finished.
+ // Since the renderer can change out from under the helper (in cross-process
+ // navigations), it re-sends the status whenever an error page commits.
+ error_page::DnsProbeStatus dns_probe_status_;
+
+ // Optional callback for browser test to snoop on outgoing NetErrorInfo IPCs.
+ DnsProbeStatusSnoopCallback dns_probe_status_snoop_callback_;
+
+ // "Use a web service to resolve navigation errors" preference is required
+ // to allow probes.
+ BooleanPrefMember resolve_errors_with_web_service_;
+
+ // Preference storing the user's current easter egg game high score.
+ IntegerPrefMember easter_egg_high_score_;
+
+ base::WeakPtrFactory<NetErrorTabHelper> weak_factory_{this};
+
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+ DISALLOW_COPY_AND_ASSIGN(NetErrorTabHelper);
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_NET_ERROR_TAB_HELPER_H_
diff --git a/chromium/chrome/browser/net/net_error_tab_helper_unittest.cc b/chromium/chrome/browser/net/net_error_tab_helper_unittest.cc
new file mode 100644
index 00000000000..f3258b61813
--- /dev/null
+++ b/chromium/chrome/browser/net/net_error_tab_helper_unittest.cc
@@ -0,0 +1,404 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/net_error_tab_helper.h"
+
+#include "chrome/common/render_messages.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/error_page/common/net_error_info.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_renderer_host.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+#undef NO_ERROR // Defined in winerror.h.
+
+using chrome_browser_net::NetErrorTabHelper;
+using error_page::DnsProbeStatus;
+
+class TestNetErrorTabHelper : public NetErrorTabHelper {
+ public:
+ explicit TestNetErrorTabHelper(content::WebContents* web_contents)
+ : NetErrorTabHelper(web_contents),
+ mock_probe_running_(false),
+ last_status_sent_(error_page::DNS_PROBE_MAX),
+ mock_sent_count_(0),
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ times_download_page_later_invoked_(0),
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ times_diagnostics_dialog_invoked_(0) {
+ }
+
+ void FinishProbe(DnsProbeStatus status) {
+ EXPECT_TRUE(mock_probe_running_);
+ OnDnsProbeFinished(status);
+ mock_probe_running_ = false;
+ }
+
+ bool mock_probe_running() const { return mock_probe_running_; }
+ DnsProbeStatus last_status_sent() const { return last_status_sent_; }
+ int mock_sent_count() const { return mock_sent_count_; }
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ using NetErrorTabHelper::OnDownloadPageLater;
+
+ const GURL& download_page_later_url() const {
+ return download_page_later_url_;
+ }
+
+ int times_download_page_later_invoked() const {
+ return times_download_page_later_invoked_;
+ }
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ const std::string& network_diagnostics_url() const {
+ return network_diagnostics_url_;
+ }
+
+ int times_diagnostics_dialog_invoked() const {
+ return times_diagnostics_dialog_invoked_;
+ }
+
+ void SetCurrentTargetFrame(content::RenderFrameHost* render_frame_host) {
+ network_diagnostics_bindings_for_testing().SetCurrentTargetFrameForTesting(
+ render_frame_host);
+ }
+
+ chrome::mojom::NetworkDiagnostics* network_diagnostics_interface() {
+ return this;
+ }
+
+ private:
+ // NetErrorTabHelper implementation:
+
+ void StartDnsProbe() override {
+ EXPECT_FALSE(mock_probe_running_);
+ mock_probe_running_ = true;
+ }
+
+ void SendInfo() override {
+ last_status_sent_ = dns_probe_status();
+ mock_sent_count_++;
+ }
+
+ void RunNetworkDiagnosticsHelper(const std::string& sanitized_url) override {
+ network_diagnostics_url_ = sanitized_url;
+ times_diagnostics_dialog_invoked_++;
+ }
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ void DownloadPageLaterHelper(const GURL& url) override {
+ download_page_later_url_ = url;
+ times_download_page_later_invoked_++;
+ }
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ bool mock_probe_running_;
+ DnsProbeStatus last_status_sent_;
+ int mock_sent_count_;
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ GURL download_page_later_url_;
+ int times_download_page_later_invoked_;
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ std::string network_diagnostics_url_;
+ int times_diagnostics_dialog_invoked_;
+};
+
+class NetErrorTabHelperTest : public ChromeRenderViewHostTestHarness {
+ protected:
+ enum MainFrame { SUB_FRAME, MAIN_FRAME };
+ enum ErrorType { DNS_ERROR, OTHER_ERROR, NO_ERROR };
+
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+
+ // This will simulate the initialization of the RenderFrame in the renderer
+ // process. This is needed because WebContents does not initialize a
+ // RenderFrame on construction, and the tests expect one to exist.
+ content::RenderFrameHostTester::For(main_rfh())
+ ->InitializeRenderFrameIfNeeded();
+
+ subframe_ = content::RenderFrameHostTester::For(main_rfh())
+ ->AppendChild("subframe");
+
+ tab_helper_.reset(new TestNetErrorTabHelper(web_contents()));
+ NetErrorTabHelper::set_state_for_testing(
+ NetErrorTabHelper::TESTING_FORCE_ENABLED);
+ }
+
+ void TearDown() override {
+ // Have to shut down the helper before the profile.
+ tab_helper_.reset();
+ ChromeRenderViewHostTestHarness::TearDown();
+ }
+
+ void DidFinishNavigation(MainFrame main_frame,
+ ErrorType error_type) {
+ net::Error net_error = net::OK;
+ if (error_type == DNS_ERROR)
+ net_error = net::ERR_NAME_NOT_RESOLVED;
+ else
+ net_error = net::ERR_TIMED_OUT;
+ content::MockNavigationHandle navigation_handle(
+ bogus_url_, (main_frame == MAIN_FRAME) ? main_rfh() : subframe_);
+ navigation_handle.set_net_error_code(net_error);
+ navigation_handle.set_has_committed(true);
+ navigation_handle.set_is_error_page(true);
+ tab_helper_->DidFinishNavigation(&navigation_handle);
+ }
+
+ void FinishProbe(DnsProbeStatus status) { tab_helper_->FinishProbe(status); }
+
+ void LoadURL(const GURL& url, bool succeeded) {
+ if (succeeded) {
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(),
+ url);
+ } else {
+ content::NavigationSimulator::NavigateAndFailFromBrowser(
+ web_contents(), url, net::ERR_TIMED_OUT);
+ }
+ }
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ void NoDownloadPageLaterForNonHttpSchemes(const char* url_string,
+ bool succeeded) {
+ GURL url(url_string);
+ LoadURL(url, succeeded);
+ tab_helper()->OnDownloadPageLater();
+ EXPECT_EQ(0, tab_helper()->times_download_page_later_invoked());
+ }
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+ bool probe_running() { return tab_helper_->mock_probe_running(); }
+ DnsProbeStatus last_status_sent() { return tab_helper_->last_status_sent(); }
+ int sent_count() { return tab_helper_->mock_sent_count(); }
+
+ TestNetErrorTabHelper* tab_helper() { return tab_helper_.get(); }
+
+ private:
+ content::RenderFrameHost* subframe_;
+ std::unique_ptr<TestNetErrorTabHelper> tab_helper_;
+ GURL bogus_url_;
+};
+
+TEST_F(NetErrorTabHelperTest, Null) {
+ EXPECT_FALSE(probe_running());
+}
+
+TEST_F(NetErrorTabHelperTest, MainFrameNonDnsError) {
+ DidFinishNavigation(MAIN_FRAME, OTHER_ERROR);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+}
+
+TEST_F(NetErrorTabHelperTest, NonMainFrameDnsError) {
+ DidFinishNavigation(SUB_FRAME, DNS_ERROR);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+}
+
+// Test complete DNS error page loads. Note that the helper can see two error
+// page loads: Link Doctor loads an empty HTML page so the user knows something
+// is going on, then fails over to the normal error page if and when Link
+// Doctor fails to load or declines to provide a page.
+
+TEST_F(NetErrorTabHelperTest, ProbeResponse) {
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ DidFinishNavigation(MAIN_FRAME, NO_ERROR);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+// Send result even if a new page load has started; the error page is still
+// visible, and the user might cancel the load.
+TEST_F(NetErrorTabHelperTest, ProbeResponseAfterNewStart) {
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ DidFinishNavigation(MAIN_FRAME, NO_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(3, sent_count());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(4, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+// Don't send result if a new page has committed; the result would go to the
+// wrong page, and the error page is gone anyway.
+TEST_F(NetErrorTabHelperTest, ProbeResponseAfterNewCommit) {
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ DidFinishNavigation(MAIN_FRAME, NO_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(3, sent_count());
+}
+
+TEST_F(NetErrorTabHelperTest, MultipleDnsErrorsWithProbesWithoutErrorPages) {
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(3, sent_count());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(4, sent_count());
+}
+
+TEST_F(NetErrorTabHelperTest, MultipleDnsErrorsWithProbesAndErrorPages) {
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(4, sent_count());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(5, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(6, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NO_INTERNET,
+ last_status_sent());
+}
+
+// If multiple DNS errors occur in a row before a probe result, don't start
+// multiple probes.
+TEST_F(NetErrorTabHelperTest, CoalesceFailures) {
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ DidFinishNavigation(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(4, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(5, sent_count());
+ EXPECT_EQ(error_page::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+// Makes sure that URLs are sanitized before running the platform network
+// diagnostics tool.
+TEST_F(NetErrorTabHelperTest, SanitizeDiagnosticsUrl) {
+ tab_helper()->SetCurrentTargetFrame(web_contents()->GetMainFrame());
+ tab_helper()->network_diagnostics_interface()->RunNetworkDiagnostics(
+ GURL("http://foo:bar@somewhere:123/hats?for#goats"));
+ EXPECT_EQ("http://somewhere:123/",
+ tab_helper()->network_diagnostics_url());
+ EXPECT_EQ(1, tab_helper()->times_diagnostics_dialog_invoked());
+}
+
+// Makes sure that diagnostics aren't run on invalid URLs or URLs with
+// non-HTTP/HTTPS schemes.
+TEST_F(NetErrorTabHelperTest, NoDiagnosticsForNonHttpSchemes) {
+ const char* kUrls[] = {
+ "",
+ "http",
+ "file:///blah/blah",
+ "chrome://blah/",
+ "about:blank",
+ "file://foo/bar",
+ };
+
+ for (const char* url : kUrls) {
+ tab_helper()->SetCurrentTargetFrame(web_contents()->GetMainFrame());
+ tab_helper()->network_diagnostics_interface()
+ ->RunNetworkDiagnostics(GURL(url));
+ EXPECT_EQ(0, tab_helper()->times_diagnostics_dialog_invoked());
+ }
+}
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+TEST_F(NetErrorTabHelperTest, DownloadPageLater) {
+ GURL url("http://somewhere:123/");
+ LoadURL(url, false /*succeeded*/);
+ tab_helper()->OnDownloadPageLater();
+ EXPECT_EQ(url, tab_helper()->download_page_later_url());
+ EXPECT_EQ(1, tab_helper()->times_download_page_later_invoked());
+}
+
+TEST_F(NetErrorTabHelperTest, NoDownloadPageLaterOnNonErrorPage) {
+ GURL url("http://somewhere:123/");
+ LoadURL(url, true /*succeeded*/);
+ tab_helper()->OnDownloadPageLater();
+ EXPECT_EQ(0, tab_helper()->times_download_page_later_invoked());
+}
+
+// Makes sure that "Download page later" isn't run on URLs with non-HTTP/HTTPS
+// schemes.
+// NOTE: the test harness code in this file and in TestRendererHost don't always
+// deal with pending RFH correctly. This works because most tests only load
+// once. So workaround it by puting each test case in a separate test.
+TEST_F(NetErrorTabHelperTest, NoDownloadPageLaterForNonHttpSchemes1) {
+ NoDownloadPageLaterForNonHttpSchemes("file:///blah/blah", false);
+}
+
+TEST_F(NetErrorTabHelperTest, NoDownloadPageLaterForNonHttpSchemes2) {
+ NoDownloadPageLaterForNonHttpSchemes("chrome://blah/", false);
+}
+
+TEST_F(NetErrorTabHelperTest, NoDownloadPageLaterForNonHttpSchemes3) {
+ // about:blank always succeeds, and the test harness won't handle URLs that
+ // don't go to the network failing.
+ NoDownloadPageLaterForNonHttpSchemes("about:blank", true);
+}
+
+#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
diff --git a/chromium/chrome/browser/net/net_export_helper.cc b/chromium/chrome/browser/net/net_export_helper.cc
new file mode 100644
index 00000000000..e86c3aab564
--- /dev/null
+++ b/chromium/chrome/browser/net/net_export_helper.cc
@@ -0,0 +1,106 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/net_export_helper.h"
+
+#include "base/values.h"
+#include "chrome/browser/prerender/prerender_manager.h"
+#include "chrome/browser/prerender/prerender_manager_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "extensions/buildflags/buildflags.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/ui/webui/extensions/extension_basic_info.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension_set.h"
+#endif
+
+#if defined(OS_WIN)
+#include "chrome/browser/net/service_providers_win.h"
+#endif
+
+namespace chrome_browser_net {
+
+std::unique_ptr<base::DictionaryValue> GetPrerenderInfo(Profile* profile) {
+ std::unique_ptr<base::DictionaryValue> value;
+ prerender::PrerenderManager* prerender_manager =
+ prerender::PrerenderManagerFactory::GetForBrowserContext(profile);
+ if (prerender_manager) {
+ value = prerender_manager->CopyAsValue();
+ } else {
+ value.reset(new base::DictionaryValue());
+ value->SetBoolean("enabled", false);
+ value->SetBoolean("omnibox_enabled", false);
+ }
+ return value;
+}
+
+std::unique_ptr<base::ListValue> GetExtensionInfo(Profile* profile) {
+ auto extension_list = std::make_unique<base::ListValue>();
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ extensions::ExtensionSystem* extension_system =
+ extensions::ExtensionSystem::Get(profile);
+ if (extension_system) {
+ extensions::ExtensionService* extension_service =
+ extension_system->extension_service();
+ if (extension_service) {
+ std::unique_ptr<const extensions::ExtensionSet> extensions(
+ extensions::ExtensionRegistry::Get(profile)
+ ->GenerateInstalledExtensionsSet());
+ for (const auto& extension : *extensions) {
+ std::unique_ptr<base::DictionaryValue> extension_info(
+ new base::DictionaryValue());
+ bool enabled = extension_service->IsExtensionEnabled(extension->id());
+ extensions::GetExtensionBasicInfo(extension.get(), enabled,
+ extension_info.get());
+ extension_list->Append(std::move(extension_info));
+ }
+ }
+ }
+#endif
+ return extension_list;
+}
+
+#if defined(OS_WIN)
+std::unique_ptr<base::DictionaryValue> GetWindowsServiceProviders() {
+ auto service_providers = std::make_unique<base::DictionaryValue>();
+
+ WinsockLayeredServiceProviderList layered_providers;
+ GetWinsockLayeredServiceProviders(&layered_providers);
+ auto layered_provider_list = std::make_unique<base::ListValue>();
+ for (size_t i = 0; i < layered_providers.size(); ++i) {
+ auto service_dict = std::make_unique<base::DictionaryValue>();
+ service_dict->SetString("name", layered_providers[i].name);
+ service_dict->SetInteger("version", layered_providers[i].version);
+ service_dict->SetInteger("chain_length", layered_providers[i].chain_length);
+ service_dict->SetInteger("socket_type", layered_providers[i].socket_type);
+ service_dict->SetInteger("socket_protocol",
+ layered_providers[i].socket_protocol);
+ service_dict->SetString("path", layered_providers[i].path);
+
+ layered_provider_list->Append(std::move(service_dict));
+ }
+ service_providers->Set("service_providers", std::move(layered_provider_list));
+
+ WinsockNamespaceProviderList namespace_providers;
+ GetWinsockNamespaceProviders(&namespace_providers);
+ auto namespace_list = std::make_unique<base::ListValue>();
+ for (size_t i = 0; i < namespace_providers.size(); ++i) {
+ auto namespace_dict = std::make_unique<base::DictionaryValue>();
+ namespace_dict->SetString("name", namespace_providers[i].name);
+ namespace_dict->SetBoolean("active", namespace_providers[i].active);
+ namespace_dict->SetInteger("version", namespace_providers[i].version);
+ namespace_dict->SetInteger("type", namespace_providers[i].type);
+
+ namespace_list->Append(std::move(namespace_dict));
+ }
+ service_providers->Set("namespace_providers", std::move(namespace_list));
+
+ return service_providers;
+}
+#endif
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/net_export_helper.h b/chromium/chrome/browser/net/net_export_helper.h
new file mode 100644
index 00000000000..d406154993b
--- /dev/null
+++ b/chromium/chrome/browser/net/net_export_helper.h
@@ -0,0 +1,32 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_NET_EXPORT_HELPER_H_
+#define CHROME_BROWSER_NET_NET_EXPORT_HELPER_H_
+
+#include <memory>
+
+namespace base {
+class Value;
+class DictionaryValue;
+class ListValue;
+}
+class Profile;
+
+namespace chrome_browser_net {
+
+// Methods for getting Value summaries of net log polled data that need to be
+// retrieved on the UI thread. All functions are expected to run on the UI
+// thread. GetSessionNetworkStats() may return null if the info does not exist;
+// others will always return a Value (possibly empty).
+
+std::unique_ptr<base::DictionaryValue> GetPrerenderInfo(Profile* profile);
+std::unique_ptr<base::ListValue> GetExtensionInfo(Profile* profile);
+#if defined(OS_WIN)
+std::unique_ptr<base::DictionaryValue> GetWindowsServiceProviders();
+#endif
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_NET_EXPORT_HELPER_H_
diff --git a/chromium/chrome/browser/net/netinfo_network_quality_estimator_holdback_browsertest.cc b/chromium/chrome/browser/net/netinfo_network_quality_estimator_holdback_browsertest.cc
new file mode 100644
index 00000000000..920079cdc65
--- /dev/null
+++ b/chromium/chrome/browser/net/netinfo_network_quality_estimator_holdback_browsertest.cc
@@ -0,0 +1,220 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/metrics/field_trial_param_associator.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_impl.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/service_manager_connection.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_base.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/nqe/effective_connection_type.h"
+#include "net/nqe/network_quality_estimator.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/network_service.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/network_quality_tracker.h"
+
+// Tests if the save data header holdback works as expected.
+class NetInfoNetworkQualityEstimatorHoldbackBrowserTest
+ : public InProcessBrowserTest,
+ public testing::WithParamInterface<bool> {
+ protected:
+ NetInfoNetworkQualityEstimatorHoldbackBrowserTest() {
+ ConfigureHoldbackExperiment();
+ }
+
+ void SetUp() override {
+ test_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+ ASSERT_TRUE(test_server_.Start());
+ InProcessBrowserTest::SetUp();
+ }
+
+ void VerifyNetworkQualityNetInfoWebAPI(
+ const std::string& expected_effective_connection_type) {
+ ui_test_utils::NavigateToURL(browser(),
+ test_server_.GetURL("/net_info.html"));
+ content::NavigationEntry* entry =
+ GetWebContents()->GetController().GetVisibleEntry();
+ EXPECT_EQ(content::PAGE_TYPE_NORMAL, entry->GetPageType());
+
+ EXPECT_EQ(expected_effective_connection_type,
+ RunScriptExtractString("getEffectiveType()"));
+
+ if (expected_effective_connection_type == "slow-2g") {
+ VerifyRtt(base::TimeDelta::FromMilliseconds(3600),
+ RunScriptExtractDouble("getRtt()"));
+ VerifyDownlinkKbps(40, RunScriptExtractDouble("getDownlink()") * 1000);
+ } else if (expected_effective_connection_type == "2g") {
+ VerifyRtt(base::TimeDelta::FromMilliseconds(1800),
+ RunScriptExtractDouble("getRtt()"));
+ VerifyDownlinkKbps(75, RunScriptExtractDouble("getDownlink()") * 1000);
+ } else if (expected_effective_connection_type == "3g") {
+ VerifyRtt(base::TimeDelta::FromMilliseconds(450),
+ RunScriptExtractDouble("getRtt()"));
+ VerifyDownlinkKbps(400, RunScriptExtractDouble("getDownlink()") * 1000);
+ } else if (expected_effective_connection_type == "4g") {
+ VerifyRtt(base::TimeDelta::FromMilliseconds(175),
+ RunScriptExtractDouble("getRtt()"));
+ VerifyDownlinkKbps(1600, RunScriptExtractDouble("getDownlink()") * 1000);
+ } else {
+ DCHECK(false);
+ }
+ }
+
+ void ConfigureHoldbackExperiment() {
+ std::map<std::string, std::string> params;
+ if (GetParam()) {
+ params["web_effective_connection_type_override"] = "2G";
+ }
+ scoped_feature_list_.InitAndEnableFeatureWithParameters(
+ features::kNetworkQualityEstimatorWebHoldback, params);
+ }
+
+ // Simulates a network quality change.
+ void SimulateNetworkQualityChange(net::EffectiveConnectionType type) {
+ g_browser_process->network_quality_tracker()
+ ->ReportEffectiveConnectionTypeForTesting(type);
+
+ // Values taken from net/nqe/network_quality_estimator_params.h.
+ // TODO(tbansal): Declare the values in a common place, and read
+ // them directly.
+ if (type == net::EFFECTIVE_CONNECTION_TYPE_3G) {
+ g_browser_process->network_quality_tracker()
+ ->ReportRTTsAndThroughputForTesting(
+ base::TimeDelta::FromMilliseconds(450), 400);
+ } else if (type == net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G) {
+ g_browser_process->network_quality_tracker()
+ ->ReportRTTsAndThroughputForTesting(
+ base::TimeDelta::FromMilliseconds(3600), 40);
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ private:
+ content::WebContents* GetWebContents() const {
+ return browser()->tab_strip_model()->GetActiveWebContents();
+ }
+
+ void VerifyRtt(base::TimeDelta expected_rtt, int32_t got_rtt_milliseconds) {
+ EXPECT_EQ(0, got_rtt_milliseconds % 50)
+ << " got_rtt_milliseconds=" << got_rtt_milliseconds;
+
+ if (expected_rtt > base::TimeDelta::FromMilliseconds(3000))
+ expected_rtt = base::TimeDelta::FromMilliseconds(3000);
+
+ // The difference between the actual and the estimate value should be within
+ // 10%. Add 50 (bucket size used in Blink) to account for the cases when the
+ // sample may spill over to the next bucket due to the added noise of 10%.
+ // For example, if sample is 300 msec, after adding noise, it may become
+ // 330, and after rounding off, it would spill over to the next bucket of
+ // 350 msec.
+ EXPECT_GE((expected_rtt.InMilliseconds() * 0.1) + 50,
+ std::abs(expected_rtt.InMilliseconds() - got_rtt_milliseconds));
+ }
+
+ void VerifyDownlinkKbps(double expected_kbps, double got_kbps) {
+ // First verify that |got_kbps| is a multiple of 50.
+ int quotient = static_cast<int>(got_kbps / 50);
+ // |mod| is the remainder left after dividing |got_kbps| by 50 while
+ // restricting the quotient to integer. For example, if |got_kbps| is
+ // 1050, then mod will be 0. If |got_kbps| is 1030, mod will be 30.
+ double mod = got_kbps - 50 * quotient;
+ EXPECT_LE(0.0, mod);
+ EXPECT_GT(50.0, mod);
+ // It is possible that |mod| is not exactly 0 because of floating point
+ // computations. e.g., |got_kbps| may be 99.999999, in which case |mod|
+ // will be 49.999999.
+ EXPECT_TRUE(mod < (1e-5) || (50 - mod) < 1e-5) << " got_kbps=" << got_kbps;
+
+ if (expected_kbps > 10000)
+ expected_kbps = 10000;
+
+ // The difference between the actual and the estimate value should be within
+ // 10%. Add 50 (bucket size used in Blink) to account for the cases when the
+ // sample may spill over to the next bucket due to the added noise of 10%.
+ // For example, if sample is 300 kbps, after adding noise, it may become
+ // 330, and after rounding off, it would spill over to the next bucket of
+ // 350 kbps.
+ EXPECT_GE((expected_kbps * 0.1) + 50, std::abs(expected_kbps - got_kbps));
+ }
+
+ std::string RunScriptExtractString(const std::string& script) {
+ std::string data;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ browser()->tab_strip_model()->GetActiveWebContents(), script, &data));
+ return data;
+ }
+
+ double RunScriptExtractDouble(const std::string& script) {
+ double data = 0.0;
+ EXPECT_TRUE(ExecuteScriptAndExtractDouble(
+ browser()->tab_strip_model()->GetActiveWebContents(), script, &data));
+ return data;
+ }
+
+ int RunScriptExtractInt(const std::string& script) {
+ int data = 0;
+ EXPECT_TRUE(ExecuteScriptAndExtractInt(
+ browser()->tab_strip_model()->GetActiveWebContents(), script, &data));
+ return data;
+ }
+
+ net::EmbeddedTestServer test_server_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Make sure the changes in the effective connection typeare notified to the
+// render thread.
+IN_PROC_BROWSER_TEST_P(NetInfoNetworkQualityEstimatorHoldbackBrowserTest,
+ EffectiveConnectionTypeChangeNotified) {
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(embedded_test_server()->Start());
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/net_info.html"));
+
+ if (GetParam()) {
+ // ConfigureHoldbackExperiment() sets holdback ECT to 2G.
+ VerifyNetworkQualityNetInfoWebAPI("2g");
+ } else {
+ VerifyNetworkQualityNetInfoWebAPI("3g");
+ }
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
+ base::RunLoop().RunUntilIdle();
+
+ if (GetParam()) {
+ // ConfigureHoldbackExperiment() sets holdback ECT to 2G.
+ VerifyNetworkQualityNetInfoWebAPI("2g");
+ } else {
+ VerifyNetworkQualityNetInfoWebAPI("slow-2g");
+ }
+}
+
+// The network quality estimator web holdback is enabled only if the first
+// param is true.
+INSTANTIATE_TEST_SUITE_P(,
+ NetInfoNetworkQualityEstimatorHoldbackBrowserTest,
+ testing::Bool());
diff --git a/chromium/chrome/browser/net/network_connection_tracker_browsertest.cc b/chromium/chrome/browser/net/network_connection_tracker_browsertest.cc
new file mode 100644
index 00000000000..f3576d87459
--- /dev/null
+++ b/chromium/chrome/browser/net/network_connection_tracker_browsertest.cc
@@ -0,0 +1,215 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker.h"
+#include "base/test/bind_test_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/network_change_notifier.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+#include "services/network/public/mojom/network_service_test.mojom.h"
+
+namespace {
+
+class TestNetworkConnectionObserver
+ : public network::NetworkConnectionTracker::NetworkConnectionObserver {
+ public:
+ explicit TestNetworkConnectionObserver(
+ network::NetworkConnectionTracker* tracker)
+ : num_notifications_(0),
+ tracker_(tracker),
+ run_loop_(std::make_unique<base::RunLoop>()),
+ connection_type_(network::mojom::ConnectionType::CONNECTION_UNKNOWN) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ tracker_->AddNetworkConnectionObserver(this);
+ }
+
+ ~TestNetworkConnectionObserver() override {
+ tracker_->RemoveNetworkConnectionObserver(this);
+ }
+
+ // NetworkConnectionObserver implementation:
+ void OnConnectionChanged(network::mojom::ConnectionType type) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ network::mojom::ConnectionType queried_type;
+ bool sync = tracker_->GetConnectionType(
+ &queried_type,
+ base::BindOnce([](network::mojom::ConnectionType type) {}));
+ EXPECT_TRUE(sync);
+ EXPECT_EQ(type, queried_type);
+
+ num_notifications_++;
+ connection_type_ = type;
+ run_loop_->Quit();
+ }
+
+ void WaitForNotification() {
+ run_loop_->Run();
+ run_loop_ = std::make_unique<base::RunLoop>();
+ }
+
+ size_t num_notifications() const { return num_notifications_; }
+ network::mojom::ConnectionType connection_type() const {
+ return connection_type_;
+ }
+
+ private:
+ size_t num_notifications_;
+ network::NetworkConnectionTracker* tracker_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ network::mojom::ConnectionType connection_type_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkConnectionObserver);
+};
+
+} // namespace
+
+class NetworkConnectionTrackerBrowserTest : public InProcessBrowserTest {
+ public:
+ NetworkConnectionTrackerBrowserTest() {}
+ ~NetworkConnectionTrackerBrowserTest() override {}
+
+ // Simulates a network connection change.
+ void SimulateNetworkChange(network::mojom::ConnectionType type) {
+ if (!content::IsInProcessNetworkService()) {
+ mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
+ content::GetNetworkService()->BindTestInterface(
+ network_service_test.BindNewPipeAndPassReceiver());
+ base::RunLoop run_loop;
+ network_service_test->SimulateNetworkChange(
+ type, base::Bind([](base::RunLoop* run_loop) { run_loop->Quit(); },
+ base::Unretained(&run_loop)));
+ run_loop.Run();
+ return;
+ }
+ net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+ net::NetworkChangeNotifier::ConnectionType(type));
+ }
+
+ private:
+};
+
+// Basic test to make sure NetworkConnectionTracker is set up.
+IN_PROC_BROWSER_TEST_F(NetworkConnectionTrackerBrowserTest,
+ NetworkConnectionTracker) {
+#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
+ // NetworkService on ChromeOS doesn't yet have a NetworkChangeManager
+ // implementation. OSX uses a separate binary for service processes and
+ // browser test fixture doesn't have NetworkServiceTest mojo code.
+ return;
+#endif
+ network::NetworkConnectionTracker* tracker =
+ content::GetNetworkConnectionTracker();
+ EXPECT_NE(nullptr, tracker);
+ // Issue a GetConnectionType() request to make sure NetworkService has been
+ // started up. This way, NetworkService will receive the broadcast when
+ // SimulateNetworkChange() is called.
+ base::RunLoop run_loop;
+ network::mojom::ConnectionType ignored_type;
+ bool sync = tracker->GetConnectionType(
+ &ignored_type,
+ base::BindOnce(
+ [](base::RunLoop* run_loop, network::mojom::ConnectionType type) {
+ run_loop->Quit();
+ },
+ base::Unretained(&run_loop)));
+ if (!sync)
+ run_loop.Run();
+ TestNetworkConnectionObserver network_connection_observer(tracker);
+ SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G);
+ network_connection_observer.WaitForNotification();
+ EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_3G,
+ network_connection_observer.connection_type());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1u, network_connection_observer.num_notifications());
+}
+
+// Simulates a network service crash, and ensures that network change manager
+// binds to the restarted network service.
+IN_PROC_BROWSER_TEST_F(NetworkConnectionTrackerBrowserTest,
+ SimulateNetworkServiceCrash) {
+ // Out-of-process network service is not enabled, so network service's crash
+ // and restart aren't applicable.
+ if (!content::IsOutOfProcessNetworkService())
+ return;
+
+ network::NetworkConnectionTracker* tracker =
+ content::GetNetworkConnectionTracker();
+ EXPECT_NE(nullptr, tracker);
+
+ // Issue a GetConnectionType() request to make sure NetworkService has been
+ // started up. This way, NetworkService will receive the broadcast when
+ // SimulateNetworkChange() is called.
+ {
+ base::RunLoop run_loop;
+ network::mojom::ConnectionType ignored_type;
+ bool sync = tracker->GetConnectionType(
+ &ignored_type,
+ base::BindOnce(
+ [](base::RunLoop* run_loop, network::mojom::ConnectionType type) {
+ run_loop->Quit();
+ },
+ base::Unretained(&run_loop)));
+ if (!sync)
+ run_loop.Run();
+ }
+
+ TestNetworkConnectionObserver network_connection_observer(tracker);
+ SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G);
+
+ network_connection_observer.WaitForNotification();
+ EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_3G,
+ network_connection_observer.connection_type());
+ // Wait a bit longer to make sure only 1 notification is received and that
+ // there is no duplicate notification.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1u, network_connection_observer.num_notifications());
+
+ SimulateNetworkServiceCrash();
+
+ // Issue a GetConnectionType() request to make sure NetworkService has been
+ // started up. This way, NetworkService will receive the broadcast when
+ // SimulateNetworkChange() is called.
+ {
+ base::RunLoop run_loop;
+ network::mojom::ConnectionType ignored_type;
+ bool sync = tracker->GetConnectionType(
+ &ignored_type,
+ base::BindOnce(
+ [](base::RunLoop* run_loop, network::mojom::ConnectionType type) {
+ run_loop->Quit();
+ },
+ base::Unretained(&run_loop)));
+ if (!sync)
+ run_loop.Run();
+ }
+
+ SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_2G);
+ network_connection_observer.WaitForNotification();
+ EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_2G,
+ network_connection_observer.connection_type());
+
+ // Wait a bit longer to make sure only 2 notifications are received.
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(2u, network_connection_observer.num_notifications());
+}
diff --git a/chromium/chrome/browser/net/network_context_configuration_browsertest.cc b/chromium/chrome/browser/net/network_context_configuration_browsertest.cc
new file mode 100644
index 00000000000..8819c866fba
--- /dev/null
+++ b/chromium/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -0,0 +1,2273 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <atomic>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/guid.h"
+#include "base/location.h"
+#include "base/path_service.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/net/profile_network_context_service.h"
+#include "chrome/browser/net/profile_network_context_service_factory.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "components/content_settings/core/common/pref_names.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "components/proxy_config/proxy_config_dictionary.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/simple_url_loader_test_helper.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/system/data_pipe_utils.h"
+#include "net/base/filename_util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_options.h"
+#include "net/cookies/cookie_util.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_response_headers.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/ssl/ssl_config.h"
+#include "net/ssl/ssl_server_config.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/default_handlers.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/test/gtest_util.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+#include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/cpp/resource_response_info.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/test/test_url_loader_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "extensions/browser/browsertest_util.h"
+#include "extensions/test/test_extension_dir.h"
+#endif
+
+namespace {
+
+const char kCacheRandomPath[] = "/cacherandom";
+
+// Path using a ControllableHttpResponse that's part of the test fixture.
+const char kControllablePath[] = "/controllable";
+
+enum class NetworkServiceState {
+ kEnabled,
+ // Similar to |kEnabled|, but will simulate a crash and run tests again the
+ // restarted Network Service process.
+ kRestarted,
+};
+
+enum class NetworkContextType {
+ kSystem,
+ kSafeBrowsing,
+ kProfile,
+ kIncognitoProfile,
+ kOnDiskApp,
+ kInMemoryApp,
+ kOnDiskAppWithIncognitoProfile,
+};
+
+// This list should be kept in sync with the NetworkContextType enum.
+const NetworkContextType kNetworkContextTypes[] = {
+ NetworkContextType::kSystem,
+ NetworkContextType::kSafeBrowsing,
+ NetworkContextType::kProfile,
+ NetworkContextType::kIncognitoProfile,
+ NetworkContextType::kOnDiskApp,
+ NetworkContextType::kInMemoryApp,
+ NetworkContextType::kOnDiskAppWithIncognitoProfile,
+};
+
+struct TestCase {
+ NetworkServiceState network_service_state;
+ NetworkContextType network_context_type;
+};
+
+// Waits for the network connection type to be the specified value.
+class ConnectionTypeWaiter
+ : public network::NetworkConnectionTracker::NetworkConnectionObserver {
+ public:
+ ConnectionTypeWaiter() : tracker_(content::GetNetworkConnectionTracker()) {
+ tracker_->AddNetworkConnectionObserver(this);
+ }
+
+ ~ConnectionTypeWaiter() override {
+ tracker_->RemoveNetworkConnectionObserver(this);
+ }
+
+ void Wait(network::mojom::ConnectionType expected_type) {
+ auto current_type = network::mojom::ConnectionType::CONNECTION_UNKNOWN;
+ network::NetworkConnectionTracker::ConnectionTypeCallback callback =
+ base::BindOnce(&ConnectionTypeWaiter::OnConnectionChanged,
+ base::Unretained(this));
+ while (!tracker_->GetConnectionType(&current_type, std::move(callback)) ||
+ current_type != expected_type) {
+ run_loop_ = std::make_unique<base::RunLoop>(
+ base::RunLoop::Type::kNestableTasksAllowed);
+ run_loop_->Run();
+ }
+ }
+
+ private:
+ // network::NetworkConnectionTracker::NetworkConnectionObserver:
+ void OnConnectionChanged(network::mojom::ConnectionType type) override {
+ if (run_loop_)
+ run_loop_->Quit();
+ }
+
+ network::NetworkConnectionTracker* tracker_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+};
+
+// Tests the system, profile, and incognito profile NetworkContexts.
+class NetworkContextConfigurationBrowserTest
+ : public InProcessBrowserTest,
+ public testing::WithParamInterface<TestCase> {
+ public:
+ // Backing storage types that can used for various things (HTTP cache,
+ // cookies, etc).
+ enum class StorageType {
+ kNone,
+ kMemory,
+ kDisk,
+ };
+
+ enum class CookieType {
+ kFirstParty,
+ kThirdParty,
+ };
+
+ enum class CookiePersistenceType {
+ kSession,
+ kPersistent,
+ };
+
+ NetworkContextConfigurationBrowserTest()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+ // Have to get a port before setting up the command line, but can only set
+ // up the connection listener after there's a main thread, so can't start
+ // the test server here.
+ EXPECT_TRUE(embedded_test_server()->InitializeAndListen());
+ EXPECT_TRUE(https_server()->InitializeAndListen());
+ }
+
+ // Returns a cacheable response (10 hours) that is some random text.
+ static std::unique_ptr<net::test_server::HttpResponse> HandleCacheRandom(
+ const net::test_server::HttpRequest& request) {
+ if (request.relative_url != kCacheRandomPath)
+ return nullptr;
+
+ std::unique_ptr<net::test_server::BasicHttpResponse> response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ response->set_content(base::GenerateGUID());
+ response->set_content_type("text/plain");
+ response->AddCustomHeader("Cache-Control", "max-age=60000");
+ return std::move(response);
+ }
+
+ ~NetworkContextConfigurationBrowserTest() override {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ EXPECT_CALL(provider_, IsInitializationComplete(testing::_))
+ .WillRepeatedly(testing::Return(true));
+ policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
+ }
+
+ void SetUpOnMainThread() override {
+ // Used in a bunch of proxy tests. Should not resolve.
+ host_resolver()->AddSimulatedFailure("does.not.resolve.test");
+
+ controllable_http_response_ =
+ std::make_unique<net::test_server::ControllableHttpResponse>(
+ embedded_test_server(), kControllablePath);
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &NetworkContextConfigurationBrowserTest::HandleCacheRandom));
+ embedded_test_server()->StartAcceptingConnections();
+ net::test_server::RegisterDefaultHandlers(https_server());
+ https_server()->StartAcceptingConnections();
+
+ if (is_incognito())
+ incognito_ = CreateIncognitoBrowser();
+ SimulateNetworkServiceCrashIfNecessary();
+
+#if defined(OS_CHROMEOS)
+ // On ChromeOS the connection type comes from a fake Shill service, which
+ // is configured with a fake ethernet connection asynchronously. Wait for
+ // the connection type to be available to avoid getting notified of the
+ // connection change halfway through the test.
+ ConnectionTypeWaiter().Wait(
+ network::mojom::ConnectionType::CONNECTION_ETHERNET);
+#endif
+ }
+
+ // Returns true if the NetworkContext being tested is associated with an
+ // incognito profile.
+ bool is_incognito() const {
+ return GetParam().network_context_type ==
+ NetworkContextType::kIncognitoProfile ||
+ GetParam().network_context_type ==
+ NetworkContextType::kOnDiskAppWithIncognitoProfile;
+ }
+
+ void TearDownOnMainThread() override {
+ // Have to destroy this before the main message loop is torn down. Need to
+ // leave the embedded test server up for tests that use
+ // |live_during_shutdown_simple_loader_|. It's safe to destroy the
+ // ControllableHttpResponse before the test server.
+ controllable_http_response_.reset();
+ }
+
+ // Returns, as a string, a PAC script that will use the EmbeddedTestServer as
+ // a proxy.
+ std::string GetPacScript() const {
+ return base::StringPrintf(
+ "function FindProxyForURL(url, host){ return 'PROXY %s;' }",
+ net::HostPortPair::FromURL(embedded_test_server()->base_url())
+ .ToString()
+ .c_str());
+ }
+
+ net::EmbeddedTestServer* https_server() { return &https_server_; }
+
+ content::StoragePartition* GetStoragePartition() {
+ return GetStoragePartitionForContextType(GetParam().network_context_type);
+ }
+
+ content::StoragePartition* GetStoragePartitionForContextType(
+ NetworkContextType network_context_type) {
+ const GURL kOnDiskUrl("chrome-guest://foo/persist");
+ const GURL kInMemoryUrl("chrome-guest://foo/");
+ switch (network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ NOTREACHED() << "Network context has no storage partition";
+ return nullptr;
+ case NetworkContextType::kProfile:
+ return content::BrowserContext::GetDefaultStoragePartition(
+ browser()->profile());
+ case NetworkContextType::kIncognitoProfile:
+ DCHECK(incognito_);
+ return content::BrowserContext::GetDefaultStoragePartition(
+ incognito_->profile());
+ case NetworkContextType::kOnDiskApp:
+ return content::BrowserContext::GetStoragePartitionForSite(
+ browser()->profile(), kOnDiskUrl);
+ case NetworkContextType::kInMemoryApp:
+ return content::BrowserContext::GetStoragePartitionForSite(
+ browser()->profile(), kInMemoryUrl);
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ DCHECK(incognito_);
+ return content::BrowserContext::GetStoragePartitionForSite(
+ incognito_->profile(), kOnDiskUrl);
+ }
+ NOTREACHED();
+ return nullptr;
+ }
+
+ network::mojom::URLLoaderFactory* loader_factory() {
+ return GetLoaderFactoryForContextType(GetParam().network_context_type);
+ }
+
+ network::mojom::URLLoaderFactory* GetLoaderFactoryForContextType(
+ NetworkContextType network_context_type) {
+ switch (network_context_type) {
+ case NetworkContextType::kSystem:
+ return g_browser_process->system_network_context_manager()
+ ->GetURLLoaderFactory();
+ case NetworkContextType::kSafeBrowsing:
+ return g_browser_process->safe_browsing_service()
+ ->GetURLLoaderFactory()
+ .get();
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kOnDiskApp:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ return GetStoragePartitionForContextType(network_context_type)
+ ->GetURLLoaderFactoryForBrowserProcess()
+ .get();
+ }
+ NOTREACHED();
+ return nullptr;
+ }
+
+ network::mojom::NetworkContext* network_context() {
+ return GetNetworkContextForContextType(GetParam().network_context_type);
+ }
+
+ network::mojom::NetworkContext* GetNetworkContextForContextType(
+ NetworkContextType network_context_type) {
+ switch (network_context_type) {
+ case NetworkContextType::kSystem:
+ return g_browser_process->system_network_context_manager()
+ ->GetContext();
+ case NetworkContextType::kSafeBrowsing:
+ return g_browser_process->safe_browsing_service()->GetNetworkContext();
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kOnDiskApp:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ return GetStoragePartitionForContextType(network_context_type)
+ ->GetNetworkContext();
+ }
+ NOTREACHED();
+ return nullptr;
+ }
+
+ StorageType GetHttpCacheType() const {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ return StorageType::kNone;
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kOnDiskApp:
+ return StorageType::kDisk;
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ return StorageType::kMemory;
+ }
+ NOTREACHED();
+ return StorageType::kNone;
+ }
+
+ StorageType GetCookieStorageType() const {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ return StorageType::kMemory;
+ case NetworkContextType::kSafeBrowsing:
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kOnDiskApp:
+ return StorageType::kDisk;
+ }
+ NOTREACHED();
+ return StorageType::kNone;
+ }
+
+ // Returns the pref service with most prefs related to the NetworkContext
+ // being tested.
+ PrefService* GetPrefService() {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ return g_browser_process->local_state();
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskApp:
+ return browser()->profile()->GetPrefs();
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ // Incognito actually uses the non-incognito prefs, so this should end
+ // up being the same pref store as in the KProfile case.
+ return browser()->profile()->GetOffTheRecordProfile()->GetPrefs();
+ }
+ }
+
+ // Sets the proxy preference on a PrefService based on the NetworkContextType,
+ // and waits for it to be applied.
+ void SetProxyPref(const net::HostPortPair& host_port_pair) {
+ GetPrefService()->Set(proxy_config::prefs::kProxy,
+ ProxyConfigDictionary::CreateFixedServers(
+ host_port_pair.ToString(), std::string()));
+
+ // Wait for the new ProxyConfig to be passed over the pipe. Needed because
+ // Mojo doesn't guarantee ordering of events on different Mojo pipes, and
+ // requests are sent on a separate pipe from ProxyConfigs.
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ g_browser_process->system_network_context_manager()
+ ->FlushProxyConfigMonitorForTesting();
+ break;
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskApp:
+ ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
+ ->FlushProxyConfigMonitorForTesting();
+ break;
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ ProfileNetworkContextServiceFactory::GetForContext(
+ browser()->profile()->GetOffTheRecordProfile())
+ ->FlushProxyConfigMonitorForTesting();
+ break;
+ }
+ }
+
+ // Sends a request and expects it to be handled by embedded_test_server()
+ // acting as a proxy;
+ void TestProxyConfigured(bool expect_success) {
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ // This URL should be directed to the test server because of the proxy.
+ request->url = GURL("http://does.not.resolve.test:1872/echo");
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ if (expect_success) {
+ EXPECT_EQ(net::OK, simple_loader->NetError());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ(*simple_loader_helper.response_body(), "Echo");
+ } else {
+ EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, simple_loader->NetError());
+ ASSERT_FALSE(simple_loader_helper.response_body());
+ }
+ }
+
+ // Makes a request that hangs, and will live until browser shutdown.
+ void MakeLongLivedRequestThatHangsUntilShutdown() {
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = embedded_test_server()->GetURL(kControllablePath);
+ live_during_shutdown_simple_loader_ = network::SimpleURLLoader::Create(
+ std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS);
+ live_during_shutdown_simple_loader_helper_ =
+ std::make_unique<content::SimpleURLLoaderTestHelper>();
+
+ live_during_shutdown_simple_loader_
+ ->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(),
+ live_during_shutdown_simple_loader_helper_->GetCallback());
+
+ // Don't actually care about controlling the response, just need to wait
+ // until it sees the request, to make sure that a URLRequest has been
+ // created to potentially leak. Since the |controllable_http_response_| is
+ // not used to send a response to the request, the request just hangs until
+ // the NetworkContext is destroyed (Or the test server is shut down, but the
+ // NetworkContext should be destroyed before that happens, in this test).
+ controllable_http_response_->WaitForRequest();
+ }
+
+ // Sends a request to the test server's echoheader URL and sets
+ // |header_value_out| to the value of the specified response header. Returns
+ // false if the request fails. If non-null, uses |request| to make the
+ // request, after setting its |url| value.
+ bool FetchHeaderEcho(const std::string& header_name,
+ std::string* header_value_out,
+ std::unique_ptr<network::ResourceRequest> request =
+ nullptr) WARN_UNUSED_RESULT {
+ if (!request)
+ request = std::make_unique<network::ResourceRequest>();
+ request->url = embedded_test_server()->GetURL(
+ base::StrCat({"/echoheader?", header_name}));
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ if (simple_loader_helper.response_body()) {
+ *header_value_out = *simple_loader_helper.response_body();
+ return true;
+ }
+ return false;
+ }
+
+ // |server| should be |TYPE_HTTPS| if the cookie is third-party, because
+ // SameSite=None cookies must be Secure.
+ void SetCookie(CookieType cookie_type,
+ CookiePersistenceType cookie_expiration_type,
+ net::EmbeddedTestServer* server) {
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ std::string cookie_line = "cookie";
+ if (cookie_expiration_type == CookiePersistenceType::kPersistent)
+ cookie_line += ";max-age=3600";
+ // Third party (i.e. SameSite=None) cookies must be secure.
+ if (cookie_type == CookieType::kThirdParty)
+ cookie_line += ";SameSite=None;Secure";
+ request->url = server->GetURL("/set-cookie?" + cookie_line);
+ if (cookie_type == CookieType::kThirdParty)
+ request->site_for_cookies = GURL("http://example.com");
+ else
+ request->site_for_cookies = server->base_url();
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ EXPECT_EQ(net::OK, simple_loader->NetError());
+ }
+
+ void FlushNetworkInterface() {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ g_browser_process->system_network_context_manager()
+ ->FlushNetworkInterfaceForTesting();
+ break;
+ case NetworkContextType::kSafeBrowsing:
+ g_browser_process->safe_browsing_service()
+ ->FlushNetworkInterfaceForTesting();
+ break;
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ GetStoragePartition()->FlushNetworkInterfaceForTesting();
+ break;
+ }
+ }
+
+ Profile* GetProfile() {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskApp:
+ return browser()->profile();
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ DCHECK(incognito_);
+ return incognito_->profile();
+ }
+ }
+
+ // Gets all cookies for given URL, as a single string.
+ std::string GetCookies(const GURL& url) {
+ return GetCookiesForContextType(GetParam().network_context_type, url);
+ }
+
+ std::string GetCookiesForContextType(NetworkContextType network_context_type,
+ const GURL& url) {
+ std::string cookies;
+ base::RunLoop run_loop;
+ mojo::Remote<network::mojom::CookieManager> cookie_manager;
+ GetNetworkContextForContextType(network_context_type)
+ ->GetCookieManager(cookie_manager.BindNewPipeAndPassReceiver());
+ cookie_manager->GetCookieList(
+ url, net::CookieOptions::MakeAllInclusive(),
+ base::BindOnce(
+ [](std::string* cookies_out, base::RunLoop* run_loop,
+ const net::CookieStatusList& cookies,
+ const net::CookieStatusList& excluded_cookies) {
+ *cookies_out = net::CanonicalCookie::BuildCookieLine(cookies);
+ run_loop->Quit();
+ },
+ &cookies, &run_loop));
+ run_loop.Run();
+ return cookies;
+ }
+
+ void ForEachOtherContext(
+ base::RepeatingCallback<void(NetworkContextType network_context_type)>
+ callback) {
+ // Create Incognito Profile if needed.
+ if (!incognito_)
+ incognito_ = CreateIncognitoBrowser();
+
+ // True if the |network_context_type| corresponding to GetParam() has been
+ // found. Used to verify that kNetworkContextTypes is kept up to date, and
+ // contains no duplicates.
+ bool found_matching_type = false;
+ for (const auto network_context_type : kNetworkContextTypes) {
+ if (network_context_type == GetParam().network_context_type) {
+ EXPECT_FALSE(found_matching_type);
+ found_matching_type = true;
+ continue;
+ }
+ callback.Run(network_context_type);
+ }
+ EXPECT_TRUE(found_matching_type);
+ }
+
+ bool IsRestartStateWithInProcessNetworkService() {
+ return GetParam().network_service_state ==
+ NetworkServiceState::kRestarted &&
+ content::IsInProcessNetworkService();
+ }
+
+ void UpdateChromePolicy(const policy::PolicyMap& policy_map) {
+ provider_.UpdateChromePolicy(policy_map);
+ }
+
+ enum class WayToEnableSSLConfig { kViaPrefs, kViaPolicy };
+
+ // This helper function enables the kSSLVersionMin pref and tests that this
+ // pref is respected. kSSLVersionMin can be set in two ways: over prefs
+ // directly or over a policy. |way_to_enable| is used to determine the way to
+ // set the pref.
+ void TestEnablingSSLVersionMin(WayToEnableSSLConfig way_to_enable) {
+ // Start a TLS 1.0 server.
+ net::EmbeddedTestServer ssl_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ net::SSLServerConfig ssl_config;
+ ssl_config.version_min = net::SSL_PROTOCOL_VERSION_TLS1;
+ ssl_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1;
+ ssl_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
+ ssl_server.AddDefaultHandlers(GetChromeTestDataDir());
+ ASSERT_TRUE(ssl_server.Start());
+
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = ssl_server.GetURL("/echo");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ(*simple_loader_helper.response_body(), "Echo");
+
+ if (way_to_enable == WayToEnableSSLConfig::kViaPrefs) {
+ // Disallow TLS 1.0 via prefs.
+ g_browser_process->local_state()->SetString(prefs::kSSLVersionMin,
+ switches::kSSLVersionTLSv11);
+ } else {
+ // Disallow TLS 1.0 via policy.
+ policy::PolicyMap values;
+ values.Set(policy::key::kSSLVersionMin, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(switches::kSSLVersionTLSv11),
+ nullptr);
+ base::RunLoop run_loop;
+ PrefChangeRegistrar pref_change_registrar;
+ pref_change_registrar.Init(g_browser_process->local_state());
+ pref_change_registrar.Add(prefs::kSSLVersionMin, run_loop.QuitClosure());
+ UpdateChromePolicy(values);
+ run_loop.Run();
+ }
+
+ g_browser_process->system_network_context_manager()
+ ->FlushSSLConfigManagerForTesting();
+
+ // With the new prefs, requests to the server should be blocked.
+ request = std::make_unique<network::ResourceRequest>();
+ request->url = ssl_server.GetURL("/echo");
+ content::SimpleURLLoaderTestHelper simple_loader_helper2;
+ simple_loader = network::SimpleURLLoader::Create(
+ std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper2.GetCallback());
+ simple_loader_helper2.WaitForCallback();
+ EXPECT_FALSE(simple_loader_helper2.response_body());
+ EXPECT_EQ(net::ERR_SSL_VERSION_OR_CIPHER_MISMATCH,
+ simple_loader->NetError());
+ }
+
+ private:
+ void SimulateNetworkServiceCrashIfNecessary() {
+ if (GetParam().network_service_state != NetworkServiceState::kRestarted ||
+ content::IsInProcessNetworkService()) {
+ return;
+ }
+
+ // Make sure |network_context()| is working as expected. Use '/echoheader'
+ // instead of '/echo' to avoid a disk_cache bug.
+ // See https://crbug.com/792255.
+ int net_error = content::LoadBasicRequest(
+ network_context(), embedded_test_server()->GetURL("/echoheader"), 0, 0,
+ net::LOAD_BYPASS_PROXY);
+ EXPECT_THAT(net_error, net::test::IsOk());
+
+ // Crash the NetworkService process. Existing interfaces should receive
+ // error notifications at some point.
+ SimulateNetworkServiceCrash();
+ // Flush the interface to make sure the error notification was received.
+ FlushNetworkInterface();
+ }
+
+ Browser* incognito_ = nullptr;
+ base::test::ScopedFeatureList feature_list_;
+
+ net::EmbeddedTestServer https_server_;
+ std::unique_ptr<net::test_server::ControllableHttpResponse>
+ controllable_http_response_;
+
+ policy::MockConfigurationPolicyProvider provider_;
+ // Used in tests that need a live request during browser shutdown.
+ std::unique_ptr<network::SimpleURLLoader> live_during_shutdown_simple_loader_;
+ std::unique_ptr<content::SimpleURLLoaderTestHelper>
+ live_during_shutdown_simple_loader_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ SecureCookiesAllowedForChromeScheme) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // TODO(crbug.com/1007320): This does not work under SameSiteByDefaultCookies,
+ // (and also does not work for SameSite cookies, in general). This is not
+ // easily fixable, so disable the test for now.
+ if (net::cookie_util::IsSameSiteByDefaultCookiesEnabled())
+ return;
+ // Cookies are only allowed for chrome:// schemes requesting a secure origin,
+ // so create an HTTPS server.
+ net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ net::test_server::RegisterDefaultHandlers(&https_server);
+ ASSERT_TRUE(https_server.Start());
+ if (GetPrefService()->FindPreference(prefs::kBlockThirdPartyCookies))
+ GetPrefService()->SetBoolean(prefs::kBlockThirdPartyCookies, true);
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kPersistent,
+ embedded_test_server());
+
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = https_server.GetURL("/echoheader?Cookie");
+ request->site_for_cookies = GURL(chrome::kChromeUIPrintURL);
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ EXPECT_EQ(200, simple_loader->ResponseInfo()->headers->response_code());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ("cookie", *simple_loader_helper.response_body());
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+std::unique_ptr<net::test_server::HttpResponse> EchoCookieHeader(
+ const net::test_server::HttpRequest& request) {
+ auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+ auto it = request.headers.find("Cookie");
+ if (it != request.headers.end())
+ response->set_content(it->second);
+ response->AddCustomHeader("Access-Control-Allow-Origin", "*");
+ return response;
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ ThirdPartyCookiesAllowedForExtensions) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+
+ // Loading an extension only makes sense for profile contexts.
+ if (GetParam().network_context_type != NetworkContextType::kProfile &&
+ GetParam().network_context_type !=
+ NetworkContextType::kIncognitoProfile) {
+ return;
+ }
+ net::EmbeddedTestServer test_server;
+ net::test_server::RegisterDefaultHandlers(&test_server);
+ test_server.RegisterRequestHandler(base::BindRepeating(&EchoCookieHeader));
+ ASSERT_TRUE(test_server.Start());
+
+ GetPrefService()->SetBoolean(prefs::kBlockThirdPartyCookies, true);
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kPersistent,
+ embedded_test_server());
+
+ extensions::TestExtensionDir extension_dir;
+ extension_dir.WriteManifest(R"({
+ "name": "Cookie Test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "incognito": "split",
+ "permissions": ["<all_urls>"]
+ })");
+ extension_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");
+ extensions::ChromeTestExtensionLoader loader(GetProfile());
+ loader.set_allow_incognito_access(true);
+ scoped_refptr<const extensions::Extension> extension =
+ loader.LoadExtension(extension_dir.UnpackedPath());
+ ASSERT_TRUE(extension);
+
+ // This request will show up as cross-site because the chrome-extension URL
+ // won't match the test_server domain (127.0.0.1), but because we set
+ // |attach_same_site_cookies| to true for extension-initiated requests, this
+ // will actually be able to get the cookie.
+ GURL url = test_server.GetURL("/echocookieheader");
+ std::string script = R"((url => {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.onload = () => domAutomationController.send(xhr.responseText);
+ xhr.send();
+ }))";
+ std::string result =
+ extensions::browsertest_util::ExecuteScriptInBackgroundPage(
+ GetProfile(), extension->id(), script + "('" + url.spec() + "')");
+ EXPECT_EQ("cookie", result);
+}
+#endif
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, BasicRequest) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = embedded_test_server()->GetURL("/echo");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ ASSERT_TRUE(simple_loader->ResponseInfo());
+ ASSERT_TRUE(simple_loader->ResponseInfo()->headers);
+ EXPECT_EQ(200, simple_loader->ResponseInfo()->headers->response_code());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ("Echo", *simple_loader_helper.response_body());
+}
+
+// Make sure a cache is used when expected.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, Cache) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // Crashing the network service may corrupt the disk cache, so skip this test
+ // in tests that crash the network service and use an on-disk cache.
+ if (GetParam().network_service_state == NetworkServiceState::kRestarted &&
+ GetHttpCacheType() == StorageType::kDisk) {
+ return;
+ }
+
+ // Make a request whose response should be cached.
+ GURL request_url = embedded_test_server()->GetURL("/cachetime");
+ url::Origin request_origin =
+ url::Origin::Create(embedded_test_server()->base_url());
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = request_url;
+ request->trusted_params = network::ResourceRequest::TrustedParams();
+ request->trusted_params->network_isolation_key =
+ net::NetworkIsolationKey(request_origin, request_origin);
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_GT(simple_loader_helper.response_body()->size(), 0u);
+
+ // Stop the server.
+ ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+
+ // Make the request again, and make sure it's cached or not, according to
+ // expectations. Reuse the content::ResourceRequest, but nothing else.
+ std::unique_ptr<network::ResourceRequest> request2 =
+ std::make_unique<network::ResourceRequest>();
+ request2->url = request_url;
+ request2->trusted_params = network::ResourceRequest::TrustedParams();
+ request2->trusted_params->network_isolation_key =
+ net::NetworkIsolationKey(request_origin, request_origin);
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper2;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader2 =
+ network::SimpleURLLoader::Create(std::move(request2),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader2->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper2.GetCallback());
+ simple_loader_helper2.WaitForCallback();
+ if (GetHttpCacheType() == StorageType::kNone) {
+ // If there's no cache, and no server running, the request should have
+ // failed.
+ EXPECT_FALSE(simple_loader_helper2.response_body());
+ EXPECT_EQ(net::ERR_CONNECTION_REFUSED, simple_loader2->NetError());
+ } else {
+ // Otherwise, the request should have succeeded, and returned the same
+ // result as before.
+ ASSERT_TRUE(simple_loader_helper2.response_body());
+ EXPECT_EQ(*simple_loader_helper.response_body(),
+ *simple_loader_helper2.response_body());
+ }
+}
+
+// Make sure that NetworkContexts can't access each other's disk caches.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, CacheIsolation) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // Make a request whose response should be cached.
+ GURL request_url = embedded_test_server()->GetURL("/cachetime");
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = request_url;
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_GT(simple_loader_helper.response_body()->size(), 0u);
+
+ // Stop the server.
+ ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+
+ // Make the request again, for each other network context, and make sure the
+ // response is not cached.
+ ForEachOtherContext(
+ base::BindLambdaForTesting([&](NetworkContextType network_context_type) {
+ std::unique_ptr<network::ResourceRequest> request2 =
+ std::make_unique<network::ResourceRequest>();
+ request2->url = request_url;
+ content::SimpleURLLoaderTestHelper simple_loader_helper2;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader2 =
+ network::SimpleURLLoader::Create(std::move(request2),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader2->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ GetLoaderFactoryForContextType(network_context_type),
+ simple_loader_helper2.GetCallback());
+ simple_loader_helper2.WaitForCallback();
+ EXPECT_FALSE(simple_loader_helper2.response_body());
+ EXPECT_EQ(net::ERR_CONNECTION_REFUSED, simple_loader2->NetError());
+ }));
+}
+
+// Make sure an on-disk cache is used when expected. PRE_DiskCache populates the
+// cache. DiskCache then makes sure the cache entry is still there (Or not) as
+// expected.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, PRE_DiskCache) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // Save test URL to disk, so it can be used in the next test (Test server uses
+ // a random port, so need to know the port to try and retrieve it from the
+ // cache in the next test). The profile directory is preserved between the
+ // PRE_DiskCache and DiskCache run, so can just keep a file there.
+ GURL test_url = embedded_test_server()->GetURL(kCacheRandomPath);
+ url::Origin test_origin = url::Origin::Create(test_url);
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::FilePath save_url_file_path = browser()->profile()->GetPath().Append(
+ FILE_PATH_LITERAL("url_for_test.txt"));
+
+ // Make a request whose response should be cached.
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = test_url;
+ request->trusted_params = network::ResourceRequest::TrustedParams();
+ request->trusted_params->network_isolation_key =
+ net::NetworkIsolationKey(test_origin, test_origin);
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ EXPECT_EQ(net::OK, simple_loader->NetError());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_FALSE(simple_loader_helper.response_body()->empty());
+
+ // Write the URL and expected response to a file.
+ std::string file_data =
+ test_url.spec() + "\n" + *simple_loader_helper.response_body();
+ ASSERT_EQ(
+ static_cast<int>(file_data.length()),
+ base::WriteFile(save_url_file_path, file_data.data(), file_data.size()));
+
+ EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+}
+
+// Check if the URL loaded in PRE_DiskCache is still in the cache, across a
+// browser restart.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, DiskCache) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // Crashing the network service may corrupt the disk cache, so skip this phase
+ // in tests that crash the network service and use an on-disk cache.
+ if (GetParam().network_service_state == NetworkServiceState::kRestarted &&
+ GetHttpCacheType() == StorageType::kDisk) {
+ return;
+ }
+
+ // Load URL from the above test body to disk.
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::FilePath save_url_file_path = browser()->profile()->GetPath().Append(
+ FILE_PATH_LITERAL("url_for_test.txt"));
+ std::string file_data;
+ ASSERT_TRUE(ReadFileToString(save_url_file_path, &file_data));
+
+ size_t newline_pos = file_data.find('\n');
+ ASSERT_NE(newline_pos, std::string::npos);
+
+ GURL test_url = GURL(file_data.substr(0, newline_pos));
+ ASSERT_TRUE(test_url.is_valid()) << test_url.possibly_invalid_spec();
+ url::Origin test_origin = url::Origin::Create(test_url);
+
+ std::string original_response = file_data.substr(newline_pos + 1);
+
+ // Request the same test URL as may have been cached by PRE_DiskCache.
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = test_url;
+ request->trusted_params = network::ResourceRequest::TrustedParams();
+ request->trusted_params->network_isolation_key =
+ net::NetworkIsolationKey(test_origin, test_origin);
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ request->load_flags = net::LOAD_ONLY_FROM_CACHE;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ // The request should only succeed if there is an on-disk cache.
+ if (GetHttpCacheType() != StorageType::kDisk) {
+ EXPECT_FALSE(simple_loader_helper.response_body());
+ } else {
+ DCHECK_NE(GetParam().network_service_state,
+ NetworkServiceState::kRestarted);
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ(original_response, *simple_loader_helper.response_body());
+ }
+}
+
+// Visits a URL with an HSTS header, and makes sure it is respected.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, PRE_Hsts) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ net::test_server::EmbeddedTestServer ssl_server(
+ net::test_server::EmbeddedTestServer::TYPE_HTTPS);
+ ssl_server.SetSSLConfig(
+ net::test_server::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
+ ssl_server.AddDefaultHandlers(GetChromeTestDataDir());
+ ASSERT_TRUE(ssl_server.Start());
+
+ // Make a request whose response has an STS header.
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = ssl_server.GetURL(
+ "/set-header?Strict-Transport-Security: max-age%3D600000");
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ EXPECT_EQ(net::OK, simple_loader->NetError());
+ ASSERT_TRUE(simple_loader->ResponseInfo()->headers);
+ EXPECT_TRUE(simple_loader->ResponseInfo()->headers->HasHeaderValue(
+ "Strict-Transport-Security", "max-age=600000"));
+
+ // Make a cache-only request to make sure the HSTS entry is respected. Using a
+ // cache-only request both prevents the result from being cached, and makes
+ // the check identical to the one in the next test, which is run after the
+ // test server has been shut down.
+ GURL exected_ssl_url = ssl_server.base_url();
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("http");
+ GURL start_url = exected_ssl_url.ReplaceComponents(replacements);
+
+ request = std::make_unique<network::ResourceRequest>();
+ request->url = start_url;
+ request->load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper2;
+ simple_loader = network::SimpleURLLoader::Create(
+ std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper2.GetCallback());
+ simple_loader_helper2.WaitForCallback();
+ EXPECT_FALSE(simple_loader_helper2.response_body());
+ EXPECT_EQ(exected_ssl_url, simple_loader->GetFinalURL());
+
+ // Write the URL with HSTS information to a file, so it can be loaded in the
+ // next test. Have to use a file for this, since the server's port is random.
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::FilePath save_url_file_path = browser()->profile()->GetPath().Append(
+ FILE_PATH_LITERAL("url_for_test.txt"));
+ std::string file_data = start_url.spec();
+ ASSERT_EQ(
+ static_cast<int>(file_data.length()),
+ base::WriteFile(save_url_file_path, file_data.data(), file_data.size()));
+}
+
+// Checks if the HSTS information from the last test is still available after a
+// restart.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, Hsts) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // The network service must be cleanly shut down to guarantee HSTS information
+ // is flushed to disk, but that currently generally doesn't happen. See
+ // https://crbug.com/820996.
+ if (GetHttpCacheType() == StorageType::kDisk) {
+ return;
+ }
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::FilePath save_url_file_path = browser()->profile()->GetPath().Append(
+ FILE_PATH_LITERAL("url_for_test.txt"));
+ std::string file_data;
+ ASSERT_TRUE(ReadFileToString(save_url_file_path, &file_data));
+ GURL start_url = GURL(file_data);
+
+ // Unfortunately, loading HSTS information is loaded asynchronously from
+ // disk, so there's no way to guarantee it has loaded by the time a
+ // request is made. As a result, may have to try multiple times to verify that
+ // cached HSTS information has been loaded, when it's stored on disk.
+ while (true) {
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = start_url;
+ request->load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ EXPECT_FALSE(simple_loader_helper.response_body());
+
+ // HSTS information is currently stored on disk if-and-only-if there's an
+ // on-disk HTTP cache.
+ if (GetHttpCacheType() == StorageType::kDisk) {
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("https");
+ GURL expected_https_url = start_url.ReplaceComponents(replacements);
+ // The file may not have been loaded yet, so if the cached HSTS
+ // information was not respected, try again.
+ if (expected_https_url != simple_loader->GetFinalURL())
+ continue;
+ EXPECT_EQ(expected_https_url, simple_loader->GetFinalURL());
+ break;
+ } else {
+ EXPECT_EQ(start_url, simple_loader->GetFinalURL());
+ break;
+ }
+ }
+}
+
+// Check that the SSLConfig is hooked up. PRE_SSLConfig checks that changing
+// local_state() after start modifies the SSLConfig, SSLConfig makes sure the
+// (now modified) initial value of local_state() is respected.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, PRE_SSLConfig) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ TestEnablingSSLVersionMin(WayToEnableSSLConfig::kViaPrefs);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, SSLConfig) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // Start a TLS 1.0 server.
+ net::EmbeddedTestServer ssl_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ net::SSLServerConfig ssl_config;
+ ssl_config.version_min = net::SSL_PROTOCOL_VERSION_TLS1;
+ ssl_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1;
+ ssl_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
+ ASSERT_TRUE(ssl_server.Start());
+
+ // Making a request should fail, since PRE_SSLConfig saved a pref to disallow
+ // TLS 1.0.
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = ssl_server.GetURL("/echo");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ EXPECT_FALSE(simple_loader_helper.response_body());
+ EXPECT_EQ(net::ERR_SSL_VERSION_OR_CIPHER_MISMATCH, simple_loader->NetError());
+}
+
+// This test does the same as
+// 'NetworkContextConfigurationBrowserTest.PRE_SSLConfig' but with the
+// difference that the SSLVersionMin is set via a policy (not via the prefs).
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ SSLVersionMinSetViaPolicy) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ TestEnablingSSLVersionMin(WayToEnableSSLConfig::kViaPolicy);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, ProxyConfig) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ SetProxyPref(embedded_test_server()->host_port_pair());
+ TestProxyConfigured(/*expect_success=*/true);
+}
+
+// This test should not end in an AssertNoURLLRequests CHECK.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ ShutdownWithLiveRequest) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ MakeLongLivedRequestThatHangsUntilShutdown();
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ UserAgentAndLanguagePrefs) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // The system and SafeBrowsing network contexts aren't associated with any
+ // profile, so changing the language settings for the profile's main network
+ // context won't affect what they send.
+ bool system =
+ (GetParam().network_context_type == NetworkContextType::kSystem ||
+ GetParam().network_context_type == NetworkContextType::kSafeBrowsing);
+ // echoheader returns "None" when the header isn't there in the first place.
+ const char kNoAcceptLanguage[] = "None";
+
+ std::string accept_language, user_agent;
+ // Check default.
+ ASSERT_TRUE(FetchHeaderEcho("accept-language", &accept_language));
+ EXPECT_EQ(system ? kNoAcceptLanguage : "en-US,en;q=0.9", accept_language);
+ ASSERT_TRUE(FetchHeaderEcho("user-agent", &user_agent));
+ EXPECT_EQ(::GetUserAgent(), user_agent);
+
+ // Now change the profile a different language, and see if the headers
+ // get updated.
+ browser()->profile()->GetPrefs()->SetString(language::prefs::kAcceptLanguages,
+ "uk");
+ FlushNetworkInterface();
+ std::string accept_language2, user_agent2;
+ ASSERT_TRUE(FetchHeaderEcho("accept-language", &accept_language2));
+ EXPECT_EQ(system ? kNoAcceptLanguage : "uk", accept_language2);
+ ASSERT_TRUE(FetchHeaderEcho("user-agent", &user_agent2));
+ EXPECT_EQ(::GetUserAgent(), user_agent2);
+
+ // Try a more complicated one, with multiple languages.
+ browser()->profile()->GetPrefs()->SetString(language::prefs::kAcceptLanguages,
+ "uk, en_US");
+ FlushNetworkInterface();
+ std::string accept_language3, user_agent3;
+ ASSERT_TRUE(FetchHeaderEcho("accept-language", &accept_language3));
+ EXPECT_EQ(system ? kNoAcceptLanguage : "uk,en_US;q=0.9", accept_language3);
+ ASSERT_TRUE(FetchHeaderEcho("user-agent", &user_agent3));
+ EXPECT_EQ(::GetUserAgent(), user_agent3);
+}
+
+// First part of testing enable referrers. Check that referrers are enabled by
+// default at browser start, and referrers are indeed sent. Then disable
+// referrers, and make sure that they aren't set.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ PRE_EnableReferrers) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ const GURL kReferrer("http://referrer/");
+
+ // Referrers should be enabled by default.
+ EXPECT_TRUE(GetPrefService()->GetBoolean(prefs::kEnableReferrers));
+
+ // Send a request, make sure the referrer is sent.
+ std::string referrer;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->referrer = kReferrer;
+ ASSERT_TRUE(FetchHeaderEcho("referer", &referrer, std::move(request)));
+
+ // SafeBrowsing never sends the referrer since it doesn't need to.
+ if (GetParam().network_context_type == NetworkContextType::kSafeBrowsing) {
+ EXPECT_EQ("None", referrer);
+ } else {
+ EXPECT_EQ(kReferrer.spec(), referrer);
+ }
+
+ // Disable referrers, and flush the NetworkContext mojo interface it's set on,
+ // to avoid any races with the URLLoaderFactory pipe.
+ GetPrefService()->SetBoolean(prefs::kEnableReferrers, false);
+ FlushNetworkInterface();
+
+ // Send a request and make sure its referer is not sent.
+ std::string referrer2;
+ std::unique_ptr<network::ResourceRequest> request2 =
+ std::make_unique<network::ResourceRequest>();
+ request2->referrer = kReferrer;
+ ASSERT_TRUE(FetchHeaderEcho("referer", &referrer2, std::move(request2)));
+ EXPECT_EQ("None", referrer2);
+}
+
+// Second part of enable referrer test. Referrer should still be disabled. Make
+// sure that disable referrers option is respected after startup, as to just
+// after changing it.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ EnableReferrers) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ const GURL kReferrer("http://referrer/");
+
+ // The preference is expected to be reset in incognito mode.
+ if (is_incognito()) {
+ EXPECT_TRUE(GetPrefService()->GetBoolean(prefs::kEnableReferrers));
+ return;
+ }
+
+ // Referrers should still be disabled.
+ EXPECT_FALSE(GetPrefService()->GetBoolean(prefs::kEnableReferrers));
+
+ // Send a request and make sure its referer is not sent.
+ std::string referrer;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->referrer = kReferrer;
+ ASSERT_TRUE(FetchHeaderEcho("referer", &referrer, std::move(request)));
+ EXPECT_EQ("None", referrer);
+}
+
+// Make sure that sending referrers that violate the referrer policy results in
+// errors.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ PolicyViolatingReferrers) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = embedded_test_server()->GetURL("/echoheader?Referer");
+ request->referrer = GURL("http://referrer/");
+ request->referrer_policy = net::URLRequest::NO_REFERRER;
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ if (GetParam().network_context_type == NetworkContextType::kSafeBrowsing) {
+ // Safebrowsing ignores referrers so the requests succeed.
+ EXPECT_EQ(net::OK, simple_loader->NetError());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ("None", *simple_loader_helper.response_body());
+ } else {
+ // In all other cases, the invalid referrer causes the request to fail.
+ EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, simple_loader->NetError());
+ }
+}
+
+// Makes sure cookies are enabled by default, and saved to disk / not saved to
+// disk as expected.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ PRE_CookiesEnabled) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ EXPECT_TRUE(GetCookies(embedded_test_server()->base_url()).empty());
+
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kPersistent,
+ embedded_test_server());
+ EXPECT_FALSE(GetCookies(embedded_test_server()->base_url()).empty());
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, CookiesEnabled) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+#if defined(OS_MACOSX)
+ // TODO(https://crbug.com/880496): Fix and reenable test.
+ if (base::mac::IsOS10_11())
+ return;
+#endif
+ // Check that the cookie from the first stage of the test was / was not
+ // preserved between browser restarts, as expected.
+ bool has_cookies = !GetCookies(embedded_test_server()->base_url()).empty();
+ EXPECT_EQ(GetCookieStorageType() == StorageType::kDisk, has_cookies);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ CookieIsolation) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kPersistent,
+ embedded_test_server());
+ EXPECT_FALSE(GetCookies(embedded_test_server()->base_url()).empty());
+
+ ForEachOtherContext(
+ base::BindLambdaForTesting([&](NetworkContextType network_context_type) {
+ EXPECT_TRUE(GetCookiesForContextType(network_context_type,
+ embedded_test_server()->base_url())
+ .empty());
+ }));
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ PRE_ThirdPartyCookiesBlocked) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // The system and SafeBrowsing network contexts don't support the third party
+ // cookie blocking options, since they have no notion of third parties.
+ bool system =
+ GetParam().network_context_type == NetworkContextType::kSystem ||
+ GetParam().network_context_type == NetworkContextType::kSafeBrowsing;
+ if (system)
+ return;
+
+ GetPrefService()->SetBoolean(prefs::kBlockThirdPartyCookies, true);
+ SetCookie(CookieType::kThirdParty, CookiePersistenceType::kSession,
+ https_server());
+
+ EXPECT_TRUE(GetCookies(https_server()->base_url()).empty());
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ ThirdPartyCookiesBlocked) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // The system and SafeBrowsing network contexts don't support the third party
+ // cookie blocking options, since they have no notion of third parties.
+ bool system =
+ GetParam().network_context_type == NetworkContextType::kSystem ||
+ GetParam().network_context_type == NetworkContextType::kSafeBrowsing;
+ if (system)
+ return;
+
+ // The preference is expected to be reset in incognito mode.
+ if (is_incognito()) {
+ EXPECT_FALSE(GetPrefService()->GetBoolean(prefs::kBlockThirdPartyCookies));
+ return;
+ }
+
+ // The kBlockThirdPartyCookies pref should carry over to the next session.
+ EXPECT_TRUE(GetPrefService()->GetBoolean(prefs::kBlockThirdPartyCookies));
+ SetCookie(CookieType::kThirdParty, CookiePersistenceType::kSession,
+ https_server());
+
+ EXPECT_TRUE(GetCookies(https_server()->base_url()).empty());
+
+ // Set pref to false, third party cookies should be allowed now.
+ GetPrefService()->SetBoolean(prefs::kBlockThirdPartyCookies, false);
+ // Set a third-party cookie. It should actually get set this time.
+ SetCookie(CookieType::kThirdParty, CookiePersistenceType::kSession,
+ https_server());
+
+ EXPECT_FALSE(GetCookies(https_server()->base_url()).empty());
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+ PRE_CookieSettings) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // The system and SafeBrowsing network contexts don't respect cookie blocking
+ // options, which are per-profile.
+ bool system =
+ GetParam().network_context_type == NetworkContextType::kSystem ||
+ GetParam().network_context_type == NetworkContextType::kSafeBrowsing;
+ if (system)
+ return;
+
+ CookieSettingsFactory::GetForProfile(browser()->profile())
+ ->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
+ FlushNetworkInterface();
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kSession,
+ embedded_test_server());
+
+ EXPECT_TRUE(GetCookies(embedded_test_server()->base_url()).empty());
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, CookieSettings) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ // The system and SafeBrowsing network contexts don't respect cookie blocking
+ // options, which are per-profile.
+ bool system =
+ GetParam().network_context_type == NetworkContextType::kSystem ||
+ GetParam().network_context_type == NetworkContextType::kSafeBrowsing;
+ if (system)
+ return;
+
+ // The content settings should carry over to the next session.
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kSession,
+ embedded_test_server());
+
+ EXPECT_TRUE(GetCookies(embedded_test_server()->base_url()).empty());
+
+ // Set default setting to allow, cookies should be set now.
+ CookieSettingsFactory::GetForProfile(browser()->profile())
+ ->SetDefaultCookieSetting(CONTENT_SETTING_ALLOW);
+ FlushNetworkInterface();
+ SetCookie(CookieType::kFirstParty, CookiePersistenceType::kSession,
+ embedded_test_server());
+
+ EXPECT_FALSE(GetCookies(embedded_test_server()->base_url()).empty());
+}
+
+// Make sure file uploads work.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, UploadFile) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->method = "POST";
+ request->url = embedded_test_server()->GetURL("/echo");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ base::FilePath dir_test_data;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &dir_test_data);
+ base::FilePath path =
+ dir_test_data.Append(base::FilePath(FILE_PATH_LITERAL("simple.html")));
+ simple_loader->AttachFileForUpload(path, "text/html");
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ ASSERT_TRUE(simple_loader->ResponseInfo());
+ ASSERT_TRUE(simple_loader->ResponseInfo()->headers);
+ EXPECT_EQ(200, simple_loader->ResponseInfo()->headers->response_code());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ std::string expected_response;
+ base::ReadFileToString(path, &expected_response);
+ EXPECT_EQ(expected_response.c_str(), *simple_loader_helper.response_body());
+}
+
+class NetworkContextConfigurationFixedPortBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationFixedPortBrowserTest() {}
+ ~NetworkContextConfigurationFixedPortBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII(
+ switches::kTestingFixedHttpPort,
+ base::StringPrintf("%u", embedded_test_server()->port()));
+ LOG(WARNING) << base::StringPrintf("%u", embedded_test_server()->port());
+ }
+};
+
+// Test that the command line switch makes it to the network service and is
+// respected.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationFixedPortBrowserTest,
+ TestingFixedPort) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ // This URL does not use the port the embedded test server is using. The
+ // command line switch should make it result in the request being directed to
+ // the test server anyways.
+ request->url = GURL("http://127.0.0.1/echo");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ EXPECT_EQ(net::OK, simple_loader->NetError());
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ EXPECT_EQ(*simple_loader_helper.response_body(), "Echo");
+}
+
+class NetworkContextConfigurationProxyOnStartBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationProxyOnStartBrowserTest() {}
+ ~NetworkContextConfigurationProxyOnStartBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII(
+ switches::kProxyServer,
+ embedded_test_server()->host_port_pair().ToString());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationProxyOnStartBrowserTest);
+};
+
+// Test that when there's a proxy configuration at startup, the initial requests
+// use that configuration.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationProxyOnStartBrowserTest,
+ TestInitialProxyConfig) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ TestProxyConfigured(/*expect_success=*/true);
+}
+
+// Make sure the system URLRequestContext can handle fetching PAC scripts from
+// http URLs.
+class NetworkContextConfigurationHttpPacBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationHttpPacBrowserTest()
+ : pac_test_server_(net::test_server::EmbeddedTestServer::TYPE_HTTP) {}
+
+ ~NetworkContextConfigurationHttpPacBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ pac_test_server_.RegisterRequestHandler(base::Bind(
+ &NetworkContextConfigurationHttpPacBrowserTest::HandlePacRequest,
+ GetPacScript()));
+ EXPECT_TRUE(pac_test_server_.Start());
+
+ command_line->AppendSwitchASCII(switches::kProxyPacUrl,
+ pac_test_server_.base_url().spec().c_str());
+ }
+
+ static std::unique_ptr<net::test_server::HttpResponse> HandlePacRequest(
+ const std::string& pac_script,
+ const net::test_server::HttpRequest& request) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ response->set_content(pac_script);
+ return response;
+ }
+
+ private:
+ net::test_server::EmbeddedTestServer pac_test_server_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationHttpPacBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationHttpPacBrowserTest, HttpPac) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ TestProxyConfigured(/*expect_success=*/true);
+}
+
+// Make sure the system URLRequestContext can handle fetching PAC scripts from
+// file URLs.
+class NetworkContextConfigurationFilePacBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationFilePacBrowserTest() {}
+
+ ~NetworkContextConfigurationFilePacBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ const char kPacFileName[] = "foo.pac";
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ base::FilePath pac_file_path =
+ temp_dir_.GetPath().AppendASCII(kPacFileName);
+
+ std::string pac_script = GetPacScript();
+ ASSERT_EQ(
+ static_cast<int>(pac_script.size()),
+ base::WriteFile(pac_file_path, pac_script.c_str(), pac_script.size()));
+
+ command_line->AppendSwitchASCII(
+ switches::kProxyPacUrl, net::FilePathToFileURL(pac_file_path).spec());
+ }
+
+ private:
+ base::ScopedTempDir temp_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationFilePacBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationFilePacBrowserTest, FilePac) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ TestProxyConfigured(false);
+}
+
+// Make sure the system URLRequestContext can handle fetching PAC scripts from
+// data URLs.
+class NetworkContextConfigurationDataPacBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationDataPacBrowserTest() {}
+ ~NetworkContextConfigurationDataPacBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ std::string contents;
+ // Read in kPACScript contents.
+ command_line->AppendSwitchASCII(switches::kProxyPacUrl,
+ "data:," + GetPacScript());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationDataPacBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationDataPacBrowserTest, DataPac) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ TestProxyConfigured(/*expect_success=*/true);
+}
+
+// Make sure the system URLRequestContext can handle fetching PAC scripts from
+// ftp URLs. Unlike the other PAC tests, this test uses a PAC script that
+// results in an error, since the spawned test server is designed so that it can
+// run remotely (So can't just write a script to a local file and have the
+// server serve it).
+class NetworkContextConfigurationFtpPacBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationFtpPacBrowserTest()
+ : ftp_server_(net::SpawnedTestServer::TYPE_FTP, GetChromeTestDataDir()) {
+ EXPECT_TRUE(ftp_server_.Start());
+ }
+ ~NetworkContextConfigurationFtpPacBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII(
+ switches::kProxyPacUrl,
+ ftp_server_.GetURL("bad_server.pac").spec().c_str());
+ }
+
+ private:
+ net::SpawnedTestServer ftp_server_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationFtpPacBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationFtpPacBrowserTest, FtpPac) {
+ if (IsRestartStateWithInProcessNetworkService())
+ return;
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ // This URL should be directed to the test server because of the proxy.
+ request->url = GURL("http://does.not.resolve.test:1872/echo");
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+
+ EXPECT_EQ(net::ERR_PROXY_CONNECTION_FAILED, simple_loader->NetError());
+}
+
+// Used to test that PAC HTTPS URL stripping works. A different test server is
+// used as the "proxy" based on whether the PAC script sees the full path or
+// not. The servers aren't correctly set up to mimic HTTP proxies that tunnel
+// to an HTTPS test server, so the test fixture just watches for any incoming
+// connection.
+class NetworkContextConfigurationHttpsStrippingPacBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ NetworkContextConfigurationHttpsStrippingPacBrowserTest() {}
+
+ ~NetworkContextConfigurationHttpsStrippingPacBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Test server HostPortPair, as a string.
+ std::string test_server_host_port_pair =
+ net::HostPortPair::FromURL(embedded_test_server()->base_url())
+ .ToString();
+ // Set up a PAC file that directs to different servers based on the URL it
+ // sees.
+ std::string pac_script = base::StringPrintf(
+ "function FindProxyForURL(url, host) {"
+ // With the test URL stripped of the path, try to use the embedded test
+ // server to establish a an SSL tunnel over an HTTP proxy. The request
+ // will fail with ERR_TUNNEL_CONNECTION_FAILED.
+ " if (url == 'https://does.not.resolve.test:1872/')"
+ " return 'PROXY %s';"
+ // With the full test URL, try to use a domain that does not resolve as
+ // a proxy. Errors connecting to the proxy result in
+ // ERR_PROXY_CONNECTION_FAILED.
+ " if (url == 'https://does.not.resolve.test:1872/foo')"
+ " return 'PROXY does.not.resolve.test';"
+ // Otherwise, use direct. If a connection to "does.not.resolve.test"
+ // tries to use a direction connection, it will fail with
+ // ERR_NAME_NOT_RESOLVED. This path will also
+ // be used by the initial request in NetworkServiceState::kRestarted
+ // tests to make sure the network service process is fully started
+ // before it's crashed and restarted. Using direct in this case avoids
+ // that request failing with an unexpeced error when being directed to a
+ // bogus proxy.
+ " return 'DIRECT';"
+ "}",
+ test_server_host_port_pair.c_str());
+
+ command_line->AppendSwitchASCII(switches::kProxyPacUrl,
+ "data:," + pac_script);
+ }
+};
+
+class NetworkContextConfigurationProxySettingsBrowserTest
+ : public NetworkContextConfigurationHttpPacBrowserTest {
+ public:
+ const size_t kDefaultMaxConnectionsPerProxy = 32;
+
+ NetworkContextConfigurationProxySettingsBrowserTest() = default;
+ ~NetworkContextConfigurationProxySettingsBrowserTest() override = default;
+
+ void SetUpOnMainThread() override {
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &NetworkContextConfigurationProxySettingsBrowserTest::TrackConnections,
+ base::Unretained(this)));
+
+ expected_connections_loop_ptr_.store(nullptr);
+
+ NetworkContextConfigurationHttpPacBrowserTest::SetUpOnMainThread();
+ }
+
+ virtual size_t GetExpectedMaxConnectionsPerProxy() const {
+ return kDefaultMaxConnectionsPerProxy;
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> TrackConnections(
+ const net::test_server::HttpRequest& request) {
+ if (!base::StartsWith(request.relative_url, "/hung",
+ base::CompareCase::INSENSITIVE_ASCII))
+ return nullptr;
+
+ // Record the number of connections we're seeing.
+ CHECK(observed_request_urls_.find(request.GetURL().spec()) ==
+ observed_request_urls_.end());
+ observed_request_urls_.emplace(request.GetURL().spec());
+ CHECK_GE(GetExpectedMaxConnectionsPerProxy(),
+ observed_request_urls_.size());
+
+ // Once we've seen at least as many connections as we expect, we can quit
+ // the loop on the main test thread. The test may choose to wait for
+ // longer to see if there are any additional unexpected connections.
+ if (GetExpectedMaxConnectionsPerProxy() == observed_request_urls_.size() &&
+ expected_connections_loop_ptr_.load() != nullptr) {
+ expected_connections_loop_ptr_.load()->Quit();
+ }
+
+ // To test the number of connections per proxy, we'll hang all responses.
+ return std::make_unique<net::test_server::HungResponse>();
+ }
+
+ void RunMaxConnectionsPerProxyTest() {
+ // At this point in the test, we've set up a proxy that points to our
+ // embedded test server. We've also set things up to hang all incoming
+ // requests and record how many concurrent connections we have running. To
+ // detect the maximum number of requests per proxy, we now just have to
+ // attempt to make as many requests as possible to ensure we don't see an
+ // incorrect number of concurrent requests.
+
+ // First of all, we're going to want to wait for at least as many
+ // connections as we expect.
+ base::RunLoop expected_connections_run_loop;
+ expected_connections_loop_ptr_.store(&expected_connections_run_loop);
+
+ std::vector<std::unique_ptr<network::SimpleURLLoader>> loaders(
+ GetExpectedMaxConnectionsPerProxy());
+ for (unsigned int i = 0; i < GetExpectedMaxConnectionsPerProxy() + 1; ++i) {
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url =
+ embedded_test_server()->GetURL(base::StringPrintf("foo%u.test", i),
+ base::StringPrintf("/hung_%u", i));
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ loaders.emplace_back(std::move(simple_loader));
+ }
+ expected_connections_run_loop.Run();
+
+ // Then wait for any remaining connections that we should NOT get.
+ base::RunLoop unexpected_connections_run_loop;
+ base::RunLoop::ScopedRunTimeoutForTest run_timeout(
+ base::TimeDelta::FromMilliseconds(100),
+ base::BindLambdaForTesting(
+ [&]() { unexpected_connections_run_loop.Quit(); }));
+ unexpected_connections_run_loop.Run();
+
+ // Stop the server.
+ ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+ }
+
+ private:
+ std::atomic<base::RunLoop*> expected_connections_loop_ptr_{nullptr};
+
+ // In RunMaxConnectionsPerProxyTest(), we'll make several network requests
+ // that hang. These hung requests are assumed to last for the duration of the
+ // test. This member, which is only accessed from the server's IO thread,
+ // records each observed request to ensure we see only as many connections as
+ // we expect.
+ std::unordered_set<std::string> observed_request_urls_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationProxySettingsBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationProxySettingsBrowserTest,
+ MaxConnectionsPerProxy) {
+ RunMaxConnectionsPerProxyTest();
+}
+
+class NetworkContextConfigurationManagedProxySettingsBrowserTest
+ : public NetworkContextConfigurationProxySettingsBrowserTest {
+ public:
+ const size_t kTestMaxConnectionsPerProxy = 37;
+
+ NetworkContextConfigurationManagedProxySettingsBrowserTest() = default;
+ ~NetworkContextConfigurationManagedProxySettingsBrowserTest() override =
+ default;
+
+ void SetUpInProcessBrowserTestFixture() override {
+ NetworkContextConfigurationProxySettingsBrowserTest::
+ SetUpInProcessBrowserTestFixture();
+ policy::PolicyMap policies;
+ policies.Set(policy::key::kMaxConnectionsPerProxy,
+ policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
+ policy::POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(
+ static_cast<int>(kTestMaxConnectionsPerProxy)),
+ /*external_data_fetcher=*/nullptr);
+ UpdateChromePolicy(policies);
+ }
+
+ size_t GetExpectedMaxConnectionsPerProxy() const override {
+ return kTestMaxConnectionsPerProxy;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ NetworkContextConfigurationManagedProxySettingsBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(
+ NetworkContextConfigurationManagedProxySettingsBrowserTest,
+ MaxConnectionsPerProxy) {
+ RunMaxConnectionsPerProxyTest();
+}
+
+// Used to test that we persist Reporting clients and NEL policies to disk, but
+// only when appropriate.
+class NetworkContextConfigurationReportingAndNelBrowserTest
+ : public NetworkContextConfigurationBrowserTest {
+ public:
+ struct RequestState {
+ content::SimpleURLLoaderTestHelper helper;
+ std::unique_ptr<network::SimpleURLLoader> loader;
+ };
+
+ NetworkContextConfigurationReportingAndNelBrowserTest()
+ : https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {
+ // Set up an SSL certificate so we can make requests to "https://localhost"
+ https_server_.SetSSLConfig(
+ net::test_server::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
+ // Only start the server if we actually need it in the test (i.e., we'll
+ // call StartAcceptingConnections somewhere in the test).
+ if (!IsRestartStateWithInProcessNetworkService() &&
+ AreReportingAndNelEnabled()) {
+ EXPECT_TRUE(https_server_.InitializeAndListen());
+ }
+
+ // Make report delivery happen instantly.
+ net::ReportingPolicy policy;
+ policy.delivery_interval = base::TimeDelta::FromSeconds(0);
+ net::ReportingPolicy::UsePolicyForTesting(policy);
+ }
+
+ std::unique_ptr<RequestState> NewSimpleRequest(const GURL& url) {
+ auto request_state = std::make_unique<RequestState>();
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = url;
+ request_state->loader = network::SimpleURLLoader::Create(
+ std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS);
+ request_state->loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), request_state->helper.GetCallback());
+ return request_state;
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kShortReportingDelay);
+ // This switch will cause traffic to *any* port to go to https_server_,
+ // regardless of which arbitrary port https_server_ decides to run on.
+ // NEL and Reporting policies are only valid for a single origin.
+ if (!IsRestartStateWithInProcessNetworkService() &&
+ AreReportingAndNelEnabled()) {
+ command_line->AppendSwitchASCII(switches::kTestingFixedHttpsPort,
+ base::StringPrintf("%u", port()));
+ }
+ }
+
+ net::EmbeddedTestServer* http_server() { return &https_server_; }
+ int port() const { return https_server_.port(); }
+
+ static GURL GetCollectorURL() { return GURL("https://localhost/upload"); }
+
+ static std::string GetReportToHeader() {
+ return "Report-To: {\"endpoints\":[{\"url\":\"" + GetCollectorURL().spec() +
+ "\"}],\"max_age\":86400}\r\n";
+ }
+
+ static std::string GetNELHeader() {
+ return "NEL: "
+ "{\"report_to\":\"default\",\"max_age\":86400,\"success_fraction\":"
+ "1.0}\r\n";
+ }
+
+ void RespondWithHeaders(net::test_server::ControllableHttpResponse* resp) {
+ resp->WaitForRequest();
+ resp->Send("HTTP/1.1 200 OK\r\n");
+ resp->Send(GetReportToHeader());
+ resp->Send(GetNELHeader());
+ resp->Send("\r\n");
+ resp->Done();
+ }
+
+ void RespondWithoutHeaders(net::test_server::ControllableHttpResponse* resp) {
+ resp->WaitForRequest();
+ resp->Send("HTTP/1.1 200 OK\r\n");
+ resp->Send("\r\n");
+ resp->Done();
+ }
+
+ bool AreReportingAndNelEnabled() const {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ return false;
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kOnDiskApp:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ return true;
+ }
+ }
+
+ bool ExpectPersistenceEnabled() const {
+ switch (GetParam().network_context_type) {
+ case NetworkContextType::kSystem:
+ case NetworkContextType::kSafeBrowsing:
+ case NetworkContextType::kIncognitoProfile:
+ case NetworkContextType::kInMemoryApp:
+ case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+ return false;
+ case NetworkContextType::kProfile:
+ case NetworkContextType::kOnDiskApp:
+ return true;
+ }
+ }
+
+ private:
+ net::EmbeddedTestServer https_server_;
+};
+
+namespace {
+
+constexpr char kReportingEnabledURL[] = "https://localhost/page";
+constexpr char kCrossOriginReportingEnabledURL[] = "https://localhost:444/page";
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationReportingAndNelBrowserTest,
+ PRE_PersistReportingAndNel) {
+ if (IsRestartStateWithInProcessNetworkService() ||
+ !AreReportingAndNelEnabled()) {
+ return;
+ }
+
+ net::test_server::ControllableHttpResponse original_response(http_server(),
+ "/page");
+ net::test_server::ControllableHttpResponse upload_response(http_server(),
+ "/upload");
+ http_server()->StartAcceptingConnections();
+
+ // Make a request to /page and respond with Report-To and NEL headers, which
+ // will turn on NEL for the localhost:443 origin.
+ std::unique_ptr<RequestState> request =
+ NewSimpleRequest(GURL(kReportingEnabledURL));
+ RespondWithHeaders(&original_response);
+ request->helper.WaitForCallback();
+
+ // Wait for a request to /upload, which should happen because the previous
+ // request precipitated a NEL report.
+ upload_response.WaitForRequest();
+ EXPECT_EQ(net::test_server::METHOD_POST,
+ upload_response.http_request()->method);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationReportingAndNelBrowserTest,
+ PersistReportingAndNel) {
+ if (IsRestartStateWithInProcessNetworkService() ||
+ !AreReportingAndNelEnabled()) {
+ return;
+ }
+
+ net::test_server::ControllableHttpResponse original_response(http_server(),
+ "/page");
+ net::test_server::ControllableHttpResponse cross_origin_response(
+ http_server(), "/page");
+ net::test_server::ControllableHttpResponse upload_response(http_server(),
+ "/upload");
+ http_server()->StartAcceptingConnections();
+
+ // Make a request that will only precipitate a NEL report if we loaded a NEL
+ // policy from the store.
+ std::unique_ptr<RequestState> request =
+ NewSimpleRequest(GURL(kReportingEnabledURL));
+ RespondWithoutHeaders(&original_response);
+ request->helper.WaitForCallback();
+
+ // Make another request that will generate another upload to the same
+ // collector. If the previous request didn't generate a NEL report (e.g.,
+ // because persistence is disabled) then this'll be first upload to the NEL
+ // collector. Note that because this upload is to a different origin than the
+ // request, Reporting will make a CORS preflight request, which is what we
+ // look for.
+ std::unique_ptr<RequestState> cross_origin_request =
+ NewSimpleRequest(GURL(kCrossOriginReportingEnabledURL));
+ RespondWithHeaders(&cross_origin_response);
+ cross_origin_request->helper.WaitForCallback();
+
+ // Wait for a request to /upload, which should happen because the previous
+ // requests precipitated NEL reports.
+ //
+ // We assume that Reporting/NEL will send reports in the order we made the
+ // requests above, which seems to be the case today but may not necessarily
+ // true in general.
+ upload_response.WaitForRequest();
+ if (ExpectPersistenceEnabled()) {
+ // A NEL upload about https://localhost
+ EXPECT_EQ(net::test_server::METHOD_POST,
+ upload_response.http_request()->method);
+ } else {
+ // A CORS preflight request before a cross-origin NEL upload about
+ // https://localhost:444
+ EXPECT_EQ(net::test_server::METHOD_OPTIONS,
+ upload_response.http_request()->method);
+ }
+}
+
+// Instantiates tests with a prefix indicating which NetworkContext is being
+// tested, and a suffix of "/0" if the network service is disabled, "/1" if it's
+// enabled, and "/2" if it's enabled and restarted.
+#define TEST_CASES(network_context_type) \
+ TestCase({NetworkServiceState::kEnabled, network_context_type}), \
+ TestCase({NetworkServiceState::kRestarted, network_context_type})
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#define INSTANTIATE_EXTENSION_TESTS(TestFixture) \
+ INSTANTIATE_TEST_SUITE_P( \
+ OnDiskApp, TestFixture, \
+ ::testing::Values(TEST_CASES(NetworkContextType::kOnDiskApp))); \
+ \
+ INSTANTIATE_TEST_SUITE_P( \
+ InMemoryApp, TestFixture, \
+ ::testing::Values(TEST_CASES(NetworkContextType::kInMemoryApp))); \
+ \
+ INSTANTIATE_TEST_SUITE_P( \
+ OnDiskAppWithIncognitoProfile, TestFixture, \
+ ::testing::Values( \
+ TEST_CASES(NetworkContextType::kOnDiskAppWithIncognitoProfile)));
+#else // !BUILDFLAG(ENABLE_EXTENSIONS)
+#define INSTANTIATE_EXTENSION_TESTS(TestFixture)
+#endif // !BUILDFLAG(ENABLE_EXTENSIONS)
+
+#define INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(TestFixture) \
+ INSTANTIATE_EXTENSION_TESTS(TestFixture) \
+ INSTANTIATE_TEST_SUITE_P( \
+ SystemNetworkContext, TestFixture, \
+ ::testing::Values(TEST_CASES(NetworkContextType::kSystem))); \
+ \
+ INSTANTIATE_TEST_SUITE_P( \
+ SafeBrowsingNetworkContext, TestFixture, \
+ ::testing::Values(TEST_CASES(NetworkContextType::kSafeBrowsing))); \
+ \
+ INSTANTIATE_TEST_SUITE_P( \
+ ProfileMainNetworkContext, TestFixture, \
+ ::testing::Values(TEST_CASES(NetworkContextType::kProfile))); \
+ \
+ INSTANTIATE_TEST_SUITE_P( \
+ IncognitoProfileMainNetworkContext, TestFixture, \
+ ::testing::Values(TEST_CASES(NetworkContextType::kIncognitoProfile)))
+
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(NetworkContextConfigurationBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationFixedPortBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationProxyOnStartBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationHttpPacBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationFilePacBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationDataPacBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationFtpPacBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationHttpsStrippingPacBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationProxySettingsBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationManagedProxySettingsBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+ NetworkContextConfigurationReportingAndNelBrowserTest);
+
+} // namespace
diff --git a/chromium/chrome/browser/net/network_quality_estimator_browsertest.cc b/chromium/chrome/browser/net/network_quality_estimator_browsertest.cc
new file mode 100644
index 00000000000..e521ef39860
--- /dev/null
+++ b/chromium/chrome/browser/net/network_quality_estimator_browsertest.cc
@@ -0,0 +1,170 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/metrics/field_trial.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/features.h"
+#include "net/nqe/network_quality_estimator.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/network_quality_tracker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class TestNetworkQualityObserver
+ : public network::NetworkQualityTracker::EffectiveConnectionTypeObserver {
+ public:
+ explicit TestNetworkQualityObserver(network::NetworkQualityTracker* tracker)
+ : run_loop_wait_effective_connection_type_(
+ net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+ tracker_(tracker),
+ effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
+ tracker_->AddEffectiveConnectionTypeObserver(this);
+ }
+
+ ~TestNetworkQualityObserver() override {
+ tracker_->RemoveEffectiveConnectionTypeObserver(this);
+ }
+
+ // NetworkQualityTracker::EffectiveConnectionTypeObserver implementation:
+ void OnEffectiveConnectionTypeChanged(
+ net::EffectiveConnectionType type) override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ net::EffectiveConnectionType queried_type =
+ tracker_->GetEffectiveConnectionType();
+ EXPECT_EQ(type, queried_type);
+
+ effective_connection_type_ = type;
+ if (effective_connection_type_ != run_loop_wait_effective_connection_type_)
+ return;
+ if (run_loop_)
+ run_loop_->Quit();
+ }
+
+ void WaitForNotification(
+ net::EffectiveConnectionType run_loop_wait_effective_connection_type) {
+ if (effective_connection_type_ == run_loop_wait_effective_connection_type)
+ return;
+ ASSERT_NE(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+ run_loop_wait_effective_connection_type);
+ run_loop_.reset(new base::RunLoop());
+ run_loop_wait_effective_connection_type_ =
+ run_loop_wait_effective_connection_type;
+ run_loop_->Run();
+ run_loop_.reset();
+ }
+
+ private:
+ net::EffectiveConnectionType run_loop_wait_effective_connection_type_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ network::NetworkQualityTracker* tracker_;
+ net::EffectiveConnectionType effective_connection_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkQualityObserver);
+};
+
+void CheckEffectiveConnectionType(net::EffectiveConnectionType expected) {
+ TestNetworkQualityObserver network_quality_observer(
+ g_browser_process->network_quality_tracker());
+ network_quality_observer.WaitForNotification(expected);
+}
+
+class NetworkQualityEstimatorBrowserTest : public InProcessBrowserTest {
+ public:
+ NetworkQualityEstimatorBrowserTest() {}
+ ~NetworkQualityEstimatorBrowserTest() override {}
+
+ void SetUp() override {
+ // Must start listening (And get a port for the proxy) before calling
+ // SetUp(). Use two phase EmbeddedTestServer setup for proxy tests.
+ ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+ InProcessBrowserTest::SetUp();
+ }
+
+ void SetUpOnMainThread() override {
+ embedded_test_server()->StartAcceptingConnections();
+ }
+
+ void TearDown() override {
+ // Need to stop this before |connection_listener_| is destroyed.
+ EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+ InProcessBrowserTest::TearDown();
+ }
+};
+
+class NetworkQualityEstimatorEctCommandLineBrowserTest
+ : public NetworkQualityEstimatorBrowserTest {
+ public:
+ NetworkQualityEstimatorEctCommandLineBrowserTest() {}
+ ~NetworkQualityEstimatorEctCommandLineBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII("--force-effective-connection-type",
+ "Slow-2G");
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(NetworkQualityEstimatorEctCommandLineBrowserTest,
+ ForceECTFromCommandLine) {
+ CheckEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
+}
+
+class NetworkQualityEstimatorEctFieldTrialBrowserTest
+ : public NetworkQualityEstimatorBrowserTest {
+ public:
+ NetworkQualityEstimatorEctFieldTrialBrowserTest() {}
+ ~NetworkQualityEstimatorEctFieldTrialBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ variations::testing::ClearAllVariationParams();
+ std::map<std::string, std::string> variation_params;
+ variation_params["force_effective_connection_type"] = "2G";
+ scoped_feature_list_.InitAndEnableFeatureWithParameters(
+ net::features::kNetworkQualityEstimator, variation_params);
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(NetworkQualityEstimatorEctFieldTrialBrowserTest,
+ ForceECTUsingFieldTrial) {
+ CheckEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
+}
+
+class NetworkQualityEstimatorEctFieldTrialAndCommandLineBrowserTest
+ : public NetworkQualityEstimatorEctFieldTrialBrowserTest {
+ public:
+ NetworkQualityEstimatorEctFieldTrialAndCommandLineBrowserTest() {}
+ ~NetworkQualityEstimatorEctFieldTrialAndCommandLineBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ NetworkQualityEstimatorEctFieldTrialBrowserTest::SetUpCommandLine(
+ command_line);
+ command_line->AppendSwitchASCII("--force-effective-connection-type",
+ "Slow-2G");
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(
+ NetworkQualityEstimatorEctFieldTrialAndCommandLineBrowserTest,
+ ECTFromCommandLineOverridesFieldTrial) {
+ CheckEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/net/network_quality_estimator_prefs_browsertest.cc b/chromium/chrome/browser/net/network_quality_estimator_prefs_browsertest.cc
new file mode 100644
index 00000000000..9e377e4bc99
--- /dev/null
+++ b/chromium/chrome/browser/net/network_quality_estimator_prefs_browsertest.cc
@@ -0,0 +1,245 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/deferred_sequenced_task_runner.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/process/memory.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_impl.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/metrics/subprocess_metrics_provider.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_base.h"
+#include "content/public/test/browser_test_utils.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/filename_util.h"
+#include "net/nqe/effective_connection_type.h"
+#include "net/nqe/network_quality_estimator.h"
+#include "net/nqe/network_quality_estimator_params.h"
+#include "services/network/network_service.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/network_quality_tracker.h"
+#include "services/network/public/mojom/network_service_test.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void SimulateNetworkQualityChangeOnNetworkThread(
+ net::EffectiveConnectionType type) {
+ network::NetworkService::GetNetworkServiceForTesting()
+ ->network_quality_estimator()
+ ->SimulateNetworkQualityChangeForTesting(type);
+}
+
+// Retries fetching |histogram_name| until it contains at least |count| samples.
+void RetryForHistogramUntilCountReached(base::HistogramTester* histogram_tester,
+ const std::string& histogram_name,
+ size_t count) {
+ while (true) {
+ const std::vector<base::Bucket> buckets =
+ histogram_tester->GetAllSamples(histogram_name);
+ size_t total_count = 0;
+ for (const auto& bucket : buckets)
+ total_count += bucket.count;
+ if (total_count >= count)
+ return;
+ content::FetchHistogramsFromChildProcesses();
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+}
+
+int GetHistogramSamplesSum(base::HistogramTester* histogram_tester,
+ const std::string& histogram_name) {
+ const std::vector<base::Bucket> buckets =
+ histogram_tester->GetAllSamples(histogram_name);
+ size_t sum = 0;
+ for (const auto& bucket : buckets)
+ sum += (bucket.count * bucket.min);
+ return sum;
+}
+
+class TestNetworkQualityObserver
+ : public network::NetworkQualityTracker::EffectiveConnectionTypeObserver {
+ public:
+ explicit TestNetworkQualityObserver(network::NetworkQualityTracker* tracker)
+ : run_loop_wait_effective_connection_type_(
+ net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+ run_loop_(std::make_unique<base::RunLoop>()),
+ tracker_(tracker),
+ effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
+ tracker_->AddEffectiveConnectionTypeObserver(this);
+ }
+
+ ~TestNetworkQualityObserver() override {
+ tracker_->RemoveEffectiveConnectionTypeObserver(this);
+ }
+
+ // NetworkQualityTracker::EffectiveConnectionTypeObserver implementation:
+ void OnEffectiveConnectionTypeChanged(
+ net::EffectiveConnectionType type) override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ net::EffectiveConnectionType queried_type =
+ tracker_->GetEffectiveConnectionType();
+ EXPECT_EQ(type, queried_type);
+
+ effective_connection_type_ = type;
+ if (effective_connection_type_ != run_loop_wait_effective_connection_type_)
+ return;
+ run_loop_->Quit();
+ }
+
+ void WaitForNotification(
+ net::EffectiveConnectionType run_loop_wait_effective_connection_type) {
+ if (effective_connection_type_ == run_loop_wait_effective_connection_type)
+ return;
+ ASSERT_NE(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+ run_loop_wait_effective_connection_type);
+ run_loop_wait_effective_connection_type_ =
+ run_loop_wait_effective_connection_type;
+ run_loop_->Run();
+ run_loop_.reset(new base::RunLoop());
+ }
+
+ private:
+ net::EffectiveConnectionType run_loop_wait_effective_connection_type_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ network::NetworkQualityTracker* tracker_;
+ net::EffectiveConnectionType effective_connection_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkQualityObserver);
+};
+
+} // namespace
+
+class NetworkQualityEstimatorPrefsBrowserTest : public InProcessBrowserTest {
+ public:
+ // Simulates a network quality change.
+ void SimulateNetworkQualityChange(net::EffectiveConnectionType type) {
+ if (!content::IsOutOfProcessNetworkService()) {
+ content::GetNetworkTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&SimulateNetworkQualityChangeOnNetworkThread, type));
+ return;
+ }
+
+ mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+ content::StoragePartition* partition =
+ content::BrowserContext::GetDefaultStoragePartition(
+ browser()->profile());
+ DCHECK(partition->GetNetworkContext());
+ DCHECK(content::GetNetworkService());
+
+ mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
+ content::GetNetworkService()->BindTestInterface(
+ network_service_test.BindNewPipeAndPassReceiver());
+ base::RunLoop run_loop;
+ network_service_test->SimulateNetworkQualityChange(
+ type, base::BindOnce([](base::RunLoop* run_loop) { run_loop->Quit(); },
+ base::Unretained(&run_loop)));
+ run_loop.Run();
+ }
+
+ base::HistogramTester histogram_tester;
+};
+
+// Verify that prefs are read at startup, and the read prefs are notified to the
+// network quality estimator.
+IN_PROC_BROWSER_TEST_F(NetworkQualityEstimatorPrefsBrowserTest,
+ ReadPrefsAtStartup) {
+ // The check below ensures that "NQE.Prefs.ReadSize" contains at least one
+ // sample. This implies that NQE was notified of the read prefs.
+ RetryForHistogramUntilCountReached(&histogram_tester, "NQE.Prefs.ReadSize",
+ 1);
+}
+
+// Verify that prefs are read at startup.
+IN_PROC_BROWSER_TEST_F(NetworkQualityEstimatorPrefsBrowserTest,
+ ReadPrefsAtStartupCustomPrefFile) {
+ // The check below ensures that "NQE.Prefs.ReadSize" contains at least one
+ // sample. This implies that NQE was notified of the read prefs.
+ RetryForHistogramUntilCountReached(&histogram_tester, "NQE.Prefs.ReadSize",
+ 1);
+
+ base::HistogramTester histogram_tester2;
+
+ // Create network context with JSON pref store pointing to the temp file.
+ mojo::PendingRemote<network::mojom::NetworkContext> network_context;
+ network::mojom::NetworkContextParamsPtr context_params =
+ network::mojom::NetworkContextParams::New();
+ context_params->http_server_properties_path =
+ browser()->profile()->GetPath().Append(
+ FILE_PATH_LITERAL("Temp Network Persistent State"));
+
+ auto state = base::MakeRefCounted<JsonPrefStore>(
+ context_params->http_server_properties_path.value());
+
+ base::DictionaryValue pref_value;
+ base::Value value("2G");
+ pref_value.Set("network_id_foo",
+ base::Value::ToUniquePtrValue(value.Clone()));
+ state->SetValue("net.network_qualities",
+ base::Value::ToUniquePtrValue(pref_value.Clone()), 0);
+
+ // Wait for the pending commit to finish before creating the network context.
+ base::RunLoop loop;
+ state->CommitPendingWrite(
+ base::BindOnce([](base::RunLoop* loop) { loop->Quit(); }, &loop));
+ loop.Run();
+
+ content::GetNetworkService()->CreateNetworkContext(
+ network_context.InitWithNewPipeAndPassReceiver(),
+ std::move(context_params));
+
+ RetryForHistogramUntilCountReached(&histogram_tester2, "NQE.Prefs.ReadSize",
+ 1);
+ // Pref value must be read from the temp file.
+ EXPECT_LE(1,
+ GetHistogramSamplesSum(&histogram_tester2, "NQE.Prefs.ReadSize"));
+}
+
+// Verify that prefs are read at startup, and written to later.
+IN_PROC_BROWSER_TEST_F(NetworkQualityEstimatorPrefsBrowserTest, PrefsWritten) {
+ // The check below ensures that "NQE.Prefs.ReadSize" contains at least one
+ // sample. This implies that NQE was notified of the read prefs.
+ RetryForHistogramUntilCountReached(&histogram_tester, "NQE.Prefs.ReadSize",
+ 1);
+
+ // Change in network quality is guaranteed to trigger a pref write.
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+ TestNetworkQualityObserver network_quality_observer(
+ g_browser_process->network_quality_tracker());
+ network_quality_observer.WaitForNotification(
+ net::EFFECTIVE_CONNECTION_TYPE_2G);
+
+ RetryForHistogramUntilCountReached(&histogram_tester, "NQE.Prefs.WriteCount",
+ 1);
+}
diff --git a/chromium/chrome/browser/net/network_quality_netinfo_browsertest.cc b/chromium/chrome/browser/net/network_quality_netinfo_browsertest.cc
new file mode 100644
index 00000000000..17dedc8195f
--- /dev/null
+++ b/chromium/chrome/browser/net/network_quality_netinfo_browsertest.cc
@@ -0,0 +1,163 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_base.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/nqe/effective_connection_type.h"
+#include "services/network/test/test_network_quality_tracker.h"
+
+namespace {
+
+void VerifyDownlinkKbps(double expected_kbps, double got_kbps) {
+ // First verify that |got_kbps| is a multiple of 50.
+ int quotient = static_cast<int>(got_kbps / 50);
+ // |mod| is the remainder left after dividing |got_kbps| by 50 while
+ // restricting the quotient to integer. For example, if |got_kbps| is
+ // 1050, then mod will be 0. If |got_kbps| is 1030, mod will be 30.
+ double mod = got_kbps - 50 * quotient;
+ EXPECT_LE(0.0, mod);
+ EXPECT_GT(50.0, mod);
+ // It is possible that |mod| is not exactly 0 because of floating point
+ // computations. e.g., |got_kbps| may be 99.999999, in which case |mod|
+ // will be 49.999999.
+ EXPECT_TRUE(mod < (1e-5) || (50 - mod) < 1e-5) << " got_kbps=" << got_kbps;
+
+ if (expected_kbps > 10000)
+ expected_kbps = 10000;
+
+ // The difference between the actual and the estimate value should be within
+ // 10%. Add 50 (bucket size used in Blink) to account for the cases when the
+ // sample may spill over to the next bucket due to the added noise of 10%.
+ // For example, if sample is 300 kbps, after adding noise, it may become 330,
+ // and after rounding off, it would spill over to the next bucket of 350 kbps.
+ EXPECT_GE((expected_kbps * 0.1) + 50, std::abs(expected_kbps - got_kbps))
+ << " expected_kbps=" << expected_kbps << " got_kbps=" << got_kbps;
+}
+
+} // namespace
+
+class NetInfoBrowserTest : public InProcessBrowserTest {
+ public:
+ network::NetworkQualityTracker* GetNetworkQualityTracker() const {
+ return g_browser_process->network_quality_tracker();
+ }
+
+ protected:
+ void SetUpOnMainThread() override {}
+
+ // Runs |script| repeatedly until the |script| returns |expected_value|.
+ void RunScriptUntilExpectedStringValueMet(const std::string& script,
+ const std::string& expected_value) {
+ while (true) {
+ if (RunScriptExtractString(script) == expected_value)
+ return;
+ base::RunLoop().RunUntilIdle();
+ }
+ }
+
+ // Repeatedly runs NetInfo JavaScript API to get RTT until it returns
+ // |expected_rtt|.
+ void RunGetRttUntilExpectedValueReached(base::TimeDelta expected_rtt) {
+ if (expected_rtt > base::TimeDelta::FromMilliseconds(3000))
+ expected_rtt = base::TimeDelta::FromMilliseconds(3000);
+
+ while (true) {
+ int32_t got_rtt_milliseconds = RunScriptExtractInt("getRtt()");
+ EXPECT_EQ(0, got_rtt_milliseconds % 50)
+ << " got_rtt_milliseconds=" << got_rtt_milliseconds;
+
+ // The difference between the actual and the estimate value should be
+ // within 10%. Add 50 (bucket size used in Blink) to account for the cases
+ // when the sample may spill over to the next bucket due to the added
+ // noise of 10%. For example, if sample is 300 msec, after adding noise,
+ // it may become 330, and after rounding off, it would spill over to the
+ // next bucket of 350 msec.
+ if (expected_rtt.InMilliseconds() * 0.1 + 50 >=
+ std::abs(expected_rtt.InMilliseconds() - got_rtt_milliseconds)) {
+ return;
+ }
+ }
+ }
+
+ double RunScriptExtractDouble(const std::string& script) {
+ double data = 0.0;
+ EXPECT_TRUE(ExecuteScriptAndExtractDouble(
+ browser()->tab_strip_model()->GetActiveWebContents(), script, &data));
+ return data;
+ }
+
+ private:
+ std::string RunScriptExtractString(const std::string& script) {
+ std::string data;
+ EXPECT_TRUE(ExecuteScriptAndExtractString(
+ browser()->tab_strip_model()->GetActiveWebContents(), script, &data));
+ return data;
+ }
+
+ int RunScriptExtractInt(const std::string& script) {
+ int data = 0;
+ EXPECT_TRUE(ExecuteScriptAndExtractInt(
+ browser()->tab_strip_model()->GetActiveWebContents(), script, &data));
+ return data;
+ }
+};
+
+// Make sure the changes in the effective connection type are notified to the
+// render thread.
+IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest,
+ EffectiveConnectionTypeChangeNotfied) {
+ embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+ EXPECT_TRUE(embedded_test_server()->Start());
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/net_info.html"));
+
+ // Change effective connection type so that the renderer process is notified.
+ // Changing the effective connection type from 2G to 3G is guaranteed to
+ // generate the notification to the renderers, irrespective of the current
+ // effective connection type.
+ GetNetworkQualityTracker()->ReportEffectiveConnectionTypeForTesting(
+ net::EFFECTIVE_CONNECTION_TYPE_2G);
+ RunScriptUntilExpectedStringValueMet("getEffectiveType()", "2g");
+ GetNetworkQualityTracker()->ReportEffectiveConnectionTypeForTesting(
+ net::EFFECTIVE_CONNECTION_TYPE_3G);
+ RunScriptUntilExpectedStringValueMet("getEffectiveType()", "3g");
+}
+
+// Make sure the changes in the network quality are notified to the render
+// thread, and the changed network quality is accessible via Javascript API.
+IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityChangeNotified) {
+ GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting(
+ base::TimeDelta::FromSeconds(1), 300);
+
+ embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+ EXPECT_TRUE(embedded_test_server()->Start());
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/net_info.html"));
+
+ RunGetRttUntilExpectedValueReached(base::TimeDelta::FromSeconds(1));
+ // If the updated RTT is available via JavaScript, then downlink must have
+ // been updated too.
+ VerifyDownlinkKbps(300, RunScriptExtractDouble("getDownlink()") * 1000);
+
+ // Verify that the network quality change is accessible via Javascript API.
+ GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting(
+ base::TimeDelta::FromSeconds(10), 3000);
+ RunGetRttUntilExpectedValueReached(base::TimeDelta::FromSeconds(10));
+ VerifyDownlinkKbps(3000, RunScriptExtractDouble("getDownlink()") * 1000);
+}
diff --git a/chromium/chrome/browser/net/network_quality_tracker_browsertest.cc b/chromium/chrome/browser/net/network_quality_tracker_browsertest.cc
new file mode 100644
index 00000000000..e43c56e3a24
--- /dev/null
+++ b/chromium/chrome/browser/net/network_quality_tracker_browsertest.cc
@@ -0,0 +1,277 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/deferred_sequenced_task_runner.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_impl.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_base.h"
+#include "content/public/test/browser_test_utils.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/nqe/effective_connection_type.h"
+#include "net/nqe/network_quality_estimator.h"
+#include "services/network/network_service.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/network_quality_tracker.h"
+#include "services/network/public/mojom/network_service_test.mojom.h"
+
+namespace network {
+
+namespace {
+
+// Simulates a network quality change. This is only called when network service
+// is running in the browser process, in which case, the network quality
+// estimator lives on the network thread.
+void SimulateNetworkQualityChangeOnNetworkThread(
+ net::EffectiveConnectionType type) {
+ network::NetworkService::GetNetworkServiceForTesting()
+ ->network_quality_estimator()
+ ->SimulateNetworkQualityChangeForTesting(type);
+ base::RunLoop().RunUntilIdle();
+}
+
+class TestNetworkQualityObserver
+ : public NetworkQualityTracker::EffectiveConnectionTypeObserver {
+ public:
+ explicit TestNetworkQualityObserver(NetworkQualityTracker* tracker)
+ : num_notifications_(0),
+ run_loop_wait_effective_connection_type_(
+ net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+ run_loop_(std::make_unique<base::RunLoop>()),
+ tracker_(tracker),
+ effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
+ tracker_->AddEffectiveConnectionTypeObserver(this);
+ }
+
+ ~TestNetworkQualityObserver() override {
+ tracker_->RemoveEffectiveConnectionTypeObserver(this);
+ }
+
+ // NetworkQualityTracker::EffectiveConnectionTypeObserver implementation:
+ void OnEffectiveConnectionTypeChanged(
+ net::EffectiveConnectionType type) override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ net::EffectiveConnectionType queried_type =
+ tracker_->GetEffectiveConnectionType();
+ EXPECT_EQ(type, queried_type);
+
+ num_notifications_++;
+ effective_connection_type_ = type;
+ if (effective_connection_type_ != run_loop_wait_effective_connection_type_)
+ return;
+ run_loop_->Quit();
+ }
+
+ void WaitForNotification(
+ net::EffectiveConnectionType run_loop_wait_effective_connection_type) {
+ if (effective_connection_type_ == run_loop_wait_effective_connection_type)
+ return;
+ ASSERT_NE(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+ run_loop_wait_effective_connection_type);
+ run_loop_wait_effective_connection_type_ =
+ run_loop_wait_effective_connection_type;
+ run_loop_->Run();
+ run_loop_.reset(new base::RunLoop());
+ }
+
+ size_t num_notifications() const { return num_notifications_; }
+ net::EffectiveConnectionType effective_connection_type() const {
+ return effective_connection_type_;
+ }
+ base::TimeDelta http_rtt() const { return tracker_->GetHttpRTT(); }
+ base::TimeDelta transport_rtt() const { return tracker_->GetTransportRTT(); }
+ int32_t downlink_bandwidth_kbps() const {
+ return tracker_->GetDownstreamThroughputKbps();
+ }
+
+ private:
+ size_t num_notifications_;
+ net::EffectiveConnectionType run_loop_wait_effective_connection_type_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ NetworkQualityTracker* tracker_;
+ net::EffectiveConnectionType effective_connection_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkQualityObserver);
+};
+
+} // namespace
+
+class NetworkQualityTrackerBrowserTest : public InProcessBrowserTest {
+ public:
+ NetworkQualityTrackerBrowserTest() {}
+ ~NetworkQualityTrackerBrowserTest() override {}
+
+ // Simulates a network quality change.
+ void SimulateNetworkQualityChange(net::EffectiveConnectionType type) {
+ if (!content::IsOutOfProcessNetworkService()) {
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ base::CreateSequencedTaskRunner({content::BrowserThread::IO});
+ if (content::IsInProcessNetworkService())
+ task_runner = content::GetNetworkTaskRunner();
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&SimulateNetworkQualityChangeOnNetworkThread, type));
+ return;
+ }
+
+ mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+ content::StoragePartition* partition =
+ content::BrowserContext::GetDefaultStoragePartition(
+ browser()->profile());
+ DCHECK(partition->GetNetworkContext());
+ DCHECK(content::GetNetworkService());
+
+ mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
+ content::GetNetworkService()->BindTestInterface(
+ network_service_test.BindNewPipeAndPassReceiver());
+ base::RunLoop run_loop;
+ network_service_test->SimulateNetworkQualityChange(
+ type, base::BindOnce([](base::RunLoop* run_loop) { run_loop->Quit(); },
+ base::Unretained(&run_loop)));
+ run_loop.Run();
+ }
+};
+
+// Basic test to make sure NetworkQualityTracker is set up, and observers are
+// notified.
+IN_PROC_BROWSER_TEST_F(NetworkQualityTrackerBrowserTest,
+ NetworkQualityTracker) {
+ // Change the network quality to UNKNOWN to prevent any spurious
+ // notifications.
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
+ base::RunLoop().RunUntilIdle();
+
+ NetworkQualityTracker* tracker = g_browser_process->network_quality_tracker();
+ EXPECT_NE(nullptr, tracker);
+
+ base::RunLoop run_loop;
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_4G);
+ TestNetworkQualityObserver network_quality_observer(tracker);
+ network_quality_observer.WaitForNotification(
+ net::EFFECTIVE_CONNECTION_TYPE_4G);
+ EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_4G,
+ network_quality_observer.effective_connection_type());
+
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+ network_quality_observer.WaitForNotification(
+ net::EFFECTIVE_CONNECTION_TYPE_3G);
+ EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
+ network_quality_observer.effective_connection_type());
+
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that not too many effective connection type observations are
+ // received. Note that setting the effective connection type to UNKNOWN above,
+ // and adding the observer is racy. In some cases, the observer may receiver
+ // the notification about effective connection type being UNKNOWN, followed
+ // by other notifications.
+ EXPECT_LE(1u, network_quality_observer.num_notifications());
+ EXPECT_GE(5u, network_quality_observer.num_notifications());
+
+ // Typical RTT and downlink values when effective connection type is 3G. Taken
+ // from net::NetworkQualityEstimatorParams.
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(450),
+ network_quality_observer.http_rtt());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
+ network_quality_observer.transport_rtt());
+ EXPECT_EQ(400, network_quality_observer.downlink_bandwidth_kbps());
+}
+
+// Basic test to make sure NetworkQualityTracker is set up, and clients are
+// notified as soon as they request notifications from the
+// NetworkQualityEstimatorManager.
+IN_PROC_BROWSER_TEST_F(NetworkQualityTrackerBrowserTest,
+ NetworkQualityTrackerNotifiedOnInitialization) {
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+ base::RunLoop().RunUntilIdle();
+
+ NetworkQualityTracker* tracker = g_browser_process->network_quality_tracker();
+ EXPECT_NE(nullptr, tracker);
+
+ base::RunLoop run_loop;
+ TestNetworkQualityObserver network_quality_observer(tracker);
+ network_quality_observer.WaitForNotification(
+ net::EFFECTIVE_CONNECTION_TYPE_2G);
+ EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+ network_quality_observer.effective_connection_type());
+ // Typical RTT and downlink values when effective connection type is 2G. Taken
+ // from net::NetworkQualityEstimatorParams.
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
+ network_quality_observer.http_rtt());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500),
+ network_quality_observer.transport_rtt());
+ EXPECT_EQ(75, network_quality_observer.downlink_bandwidth_kbps());
+}
+
+// Simulates a network service crash, and ensures that network quality estimate
+// manager binds to the restarted network service.
+IN_PROC_BROWSER_TEST_F(NetworkQualityTrackerBrowserTest,
+ SimulateNetworkServiceCrash) {
+ // Network service is not running out of process, so cannot be crashed.
+ if (!content::IsOutOfProcessNetworkService())
+ return;
+
+ // Change the network quality to UNKNOWN to prevent any spurious
+ // notifications.
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
+ base::RunLoop().RunUntilIdle();
+
+ NetworkQualityTracker* tracker = g_browser_process->network_quality_tracker();
+ EXPECT_NE(nullptr, tracker);
+
+ base::RunLoop run_loop;
+ TestNetworkQualityObserver network_quality_observer(tracker);
+ EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+ network_quality_observer.effective_connection_type());
+
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+ network_quality_observer.WaitForNotification(
+ net::EFFECTIVE_CONNECTION_TYPE_3G);
+ EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
+ network_quality_observer.effective_connection_type());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1u, network_quality_observer.num_notifications());
+ // Typical RTT and downlink values when effective connection type is 3G. Taken
+ // from net::NetworkQualityEstimatorParams.
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(450),
+ network_quality_observer.http_rtt());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
+ network_quality_observer.transport_rtt());
+ EXPECT_EQ(400, network_quality_observer.downlink_bandwidth_kbps());
+
+ SimulateNetworkServiceCrash();
+ // Flush the network interface to make sure it notices the crash.
+ content::BrowserContext::GetDefaultStoragePartition(browser()->profile())
+ ->FlushNetworkInterfaceForTesting();
+
+ base::RunLoop().RunUntilIdle();
+
+ SimulateNetworkQualityChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+ network_quality_observer.WaitForNotification(
+ net::EFFECTIVE_CONNECTION_TYPE_2G);
+ EXPECT_LE(2u, network_quality_observer.num_notifications());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
+ network_quality_observer.http_rtt());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500),
+ network_quality_observer.transport_rtt());
+ EXPECT_EQ(75, network_quality_observer.downlink_bandwidth_kbps());
+}
+
+} // namespace network
diff --git a/chromium/chrome/browser/net/network_request_metrics_browsertest.cc b/chromium/chrome/browser/net/network_request_metrics_browsertest.cc
new file mode 100644
index 00000000000..5b112c88062
--- /dev/null
+++ b/chromium/chrome/browser/net/network_request_metrics_browsertest.cc
@@ -0,0 +1,620 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/download/download_prefs.h"
+#include "chrome/browser/metrics/subprocess_metrics_provider.h"
+#include "chrome/browser/predictors/loading_predictor_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/resource_load_info.mojom.h"
+#include "content/public/common/resource_type.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/base/filename_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_info.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+// Used for the main frame, in subresource tests.
+constexpr char kUninterestingMainFramePath[] =
+ "/uninteresting/main_frame/path/";
+
+// Used for the "interesting" request individual tests focus on.
+constexpr char kInterestingPath[] = "/interesting/path/";
+
+enum class RequestType {
+ kMainFrame,
+ kSubFrame,
+ kImage,
+ kScript,
+};
+
+enum class HeadersReceived {
+ kHeadersReceived,
+ kNoHeadersReceived,
+};
+
+enum class NetworkAccessed {
+ kNetworkAccessed,
+ kNoNetworkAccessed,
+};
+
+// Utility class to wait until the main resource load is complete. This is to
+// make sure, in the cancel tests, the main resource is fully loaded before the
+// navigation is cancelled, to ensure the main frame load histograms are in a
+// consistent state, and can be checked at the end of each test. To avoid races,
+// create this class before starting a navigation.
+class WaitForMainFrameResourceObserver : public content::WebContentsObserver {
+ public:
+ explicit WaitForMainFrameResourceObserver(WebContents* web_contents)
+ : content::WebContentsObserver(web_contents) {}
+ ~WaitForMainFrameResourceObserver() override {}
+
+ // content::WebContentsObserver implementation:
+ void ResourceLoadComplete(
+ RenderFrameHost* render_frame_host,
+ const content::GlobalRequestID& request_id,
+ const content::mojom::ResourceLoadInfo& resource_load_info) override {
+ EXPECT_EQ(ResourceType::kMainFrame, resource_load_info.resource_type);
+ EXPECT_EQ(net::OK, resource_load_info.net_error);
+ run_loop_.Quit();
+ }
+
+ void Wait() { run_loop_.Run(); }
+
+ private:
+ base::RunLoop run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(WaitForMainFrameResourceObserver);
+};
+
+// This test fixture tests code in content/. The fixture itself is in chrome/
+// because SubprocessMetricsProvider is a chrome-only test class.
+class NetworkRequestMetricsBrowserTest
+ : public InProcessBrowserTest,
+ public testing::WithParamInterface<RequestType> {
+ public:
+ NetworkRequestMetricsBrowserTest() {
+ scoped_feature_list_.InitAndDisableFeature(
+ predictors::kSpeculativePreconnectFeature);
+ }
+ ~NetworkRequestMetricsBrowserTest() override {}
+
+ // ContentBrowserTest implementation:
+ void SetUpOnMainThread() override {
+ uninteresting_main_frame_response_ =
+ std::make_unique<net::test_server::ControllableHttpResponse>(
+ embedded_test_server(), kUninterestingMainFramePath);
+ interesting_http_response_ =
+ std::make_unique<net::test_server::ControllableHttpResponse>(
+ embedded_test_server(), kInterestingPath);
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ // Need to make this after test setup, to make sure the initial about:blank
+ // load is not counted.
+ histograms_ = std::make_unique<base::HistogramTester>();
+ }
+
+ // For non-RequestType::kMainFrame tests, returns the contents of the main
+ // frame, based on the RequestType.
+ std::string GetMainFrameContents(const std::string subresource_path) {
+ switch (GetParam()) {
+ case RequestType::kSubFrame:
+ return base::StringPrintf("<iframe src='%s'></iframe>",
+ subresource_path.c_str());
+ case RequestType::kImage:
+ return base::StringPrintf("<img src='%s'>", subresource_path.c_str());
+ break;
+ case RequestType::kScript:
+ return base::StringPrintf("<script src='%s'></script>",
+ subresource_path.c_str());
+ break;
+ case RequestType::kMainFrame:
+ NOTREACHED();
+ }
+ return std::string();
+ }
+
+ void StartNavigatingAndWaitForRequest() {
+ GURL interesting_url = embedded_test_server()->GetURL(kInterestingPath);
+ if (GetParam() == RequestType::kMainFrame) {
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), interesting_url, WindowOpenDisposition::CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_NONE);
+ } else {
+ WaitForMainFrameResourceObserver wait_for_main_frame_resource_observer(
+ active_web_contents());
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(),
+ embedded_test_server()->GetURL(kUninterestingMainFramePath),
+ WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
+ uninteresting_main_frame_response_->WaitForRequest();
+ uninteresting_main_frame_response_->Send(
+ "HTTP/1.1 200 Peachy\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n");
+ uninteresting_main_frame_response_->Send(
+ GetMainFrameContents(interesting_url.spec()));
+ uninteresting_main_frame_response_->Done();
+ wait_for_main_frame_resource_observer.Wait();
+ }
+
+ interesting_http_response_->WaitForRequest();
+ }
+
+ net::test_server::ControllableHttpResponse* interesting_http_response() {
+ return interesting_http_response_.get();
+ }
+
+ // Checks all relevant histograms. |expected_net_error| is the expected result
+ // of the RequestType specified by the test parameter.
+ void CheckHistograms(int expected_net_error,
+ HeadersReceived headers_received,
+ NetworkAccessed network_accessed) {
+ // Some metrics may come from the renderer. This call ensures that those
+ // metrics are available.
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
+ if (GetParam() == RequestType::kMainFrame) {
+ histograms_->ExpectTotalCount("Net.ErrorCodesForImages2", 0);
+
+ histograms_->ExpectUniqueSample("Net.ErrorCodesForMainFrame4",
+ -expected_net_error, 1);
+
+ if (headers_received == HeadersReceived::kHeadersReceived) {
+ histograms_->ExpectUniqueSample(
+ "Net.ConnectionInfo.MainFrame",
+ net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1, 1);
+ } else {
+ histograms_->ExpectTotalCount("Net.ConnectionInfo.MainFrame", 0);
+ }
+
+ // Favicon may or may not have been loaded.
+ EXPECT_GE(
+ 1u,
+ histograms_->GetAllSamples("Net.ErrorCodesForSubresources3").size());
+ EXPECT_GE(
+ 1u,
+ histograms_->GetAllSamples("Net.ConnectionInfo.SubResource").size());
+
+ return;
+ }
+
+ // If not testing the main frame, there should also be just one result for
+ // the main frame.
+ histograms_->ExpectUniqueSample("Net.ErrorCodesForMainFrame4", -net::OK, 1);
+
+ // Some fuzziness here because of the favicon. It should typically succeed,
+ // but allow it to have been aborted, too, since the test server won't
+ // return a valid icon.
+ std::vector<base::Bucket> buckets =
+ histograms_->GetAllSamples("Net.ErrorCodesForSubresources3");
+ bool found_expected_load = false;
+ bool found_favicon_load = false;
+ for (auto& bucket : buckets) {
+ if (!found_expected_load && bucket.min == -expected_net_error) {
+ found_expected_load = true;
+ bucket.count--;
+ }
+ if (!found_favicon_load && bucket.count > 0 &&
+ (bucket.min == -net::OK || bucket.min == -net::ERR_ABORTED)) {
+ found_favicon_load = true;
+ bucket.count--;
+ }
+ EXPECT_EQ(0, bucket.count)
+ << "Found unexpected load with result: " << bucket.min;
+ }
+ EXPECT_TRUE(found_expected_load);
+
+ if (GetParam() != RequestType::kImage) {
+ histograms_->ExpectTotalCount("Net.ErrorCodesForImages2", 0);
+ } else {
+ histograms_->ExpectUniqueSample("Net.ErrorCodesForImages2",
+ -expected_net_error, 1);
+ }
+
+ // A subresource load requires a main frame load, which is only logged for
+ // network URLs.
+ if (network_accessed == NetworkAccessed::kNetworkAccessed) {
+ histograms_->ExpectUniqueSample(
+ "Net.ConnectionInfo.MainFrame",
+ net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1, 1);
+ if (headers_received == HeadersReceived::kHeadersReceived) {
+ // Favicon request may or may not have received a response.
+ size_t subresources =
+ histograms_->GetAllSamples("Net.ConnectionInfo.SubResource").size();
+ EXPECT_LE(1u, subresources);
+ EXPECT_GE(2u, subresources);
+ } else {
+ histograms_->ExpectTotalCount("Net.ConnectionInfo.SubResource", 0);
+ }
+ } else {
+ histograms_->ExpectTotalCount("Net.ConnectionInfo.MainFrame", 0);
+ histograms_->ExpectTotalCount("Net.ConnectionInfo.SubResource", 0);
+ }
+ }
+
+ // Checks all relevant histograms in the case a new main frame navigation
+ // interrupted the first one. The request identified by GetParam() is expected
+ // to fail with net::ERR_ABORTED.
+ void CheckHistogramsAfterMainFrameInterruption() {
+ // Some metrics may come from the renderer. This call ensures that those
+ // metrics are available.
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
+ if (GetParam() == RequestType::kMainFrame) {
+ // Can't check Net.ErrorCodesForSubresources3, due to the favicon, which
+ // Chrome may or may not have attempted to load.
+ histograms_->ExpectTotalCount("Net.ErrorCodesForImages2", 0);
+
+ histograms_->ExpectTotalCount("Net.ErrorCodesForMainFrame4", 2);
+ EXPECT_EQ(1, histograms_->GetBucketCount("Net.ErrorCodesForMainFrame4",
+ -net::ERR_ABORTED));
+ EXPECT_EQ(1, histograms_->GetBucketCount("Net.ErrorCodesForMainFrame4",
+ -net::OK));
+ return;
+ }
+
+ histograms_->ExpectUniqueSample("Net.ErrorCodesForMainFrame4", -net::OK, 2);
+
+ // Some fuzziness here because of the favicon. It should typically succeed,
+ // but allow it to have been aborted, too, since the test server won't
+ // return a valid icon.
+ std::vector<base::Bucket> buckets =
+ histograms_->GetAllSamples("Net.ErrorCodesForSubresources3");
+ bool found_expected_load = false;
+ int found_favicon_loads = 0;
+ for (auto& bucket : buckets) {
+ if (!found_expected_load && bucket.min == -net::ERR_ABORTED) {
+ found_expected_load = true;
+ bucket.count--;
+ }
+ // Allow up to two favicon loads, one for the original page load, that was
+ // interrupted by a new load, and one for the new page load.
+ if (found_favicon_loads < 2 && bucket.count > 0 &&
+ (bucket.min == -net::OK || bucket.min == -net::ERR_ABORTED)) {
+ found_favicon_loads++;
+ bucket.count--;
+ }
+ EXPECT_EQ(0, bucket.count)
+ << "Found unexpected load with result: " << bucket.min;
+ }
+ EXPECT_TRUE(found_expected_load);
+
+ if (GetParam() != RequestType::kImage) {
+ histograms_->ExpectTotalCount("Net.ErrorCodesForImages2", 0);
+ } else {
+ histograms_->ExpectUniqueSample("Net.ErrorCodesForImages2",
+ -net::ERR_ABORTED, 1);
+ }
+ }
+
+ // Send headers and a partial body to |interesting_http_response_|. Doesn't
+ // terminate the response, so the socket can either be closed, or the request
+ // aborted.
+ void SendHeadersPartialBody() {
+ // Sending a body that's too short will result in an error after all the
+ // bytes are read.
+ interesting_http_response_->Send("HTTP/1.1 200 Peachy\r\n");
+ // Send a MIME type to avoid MIME sniffing (Shouldn't matter, but it's one
+ // less thing to worry about).
+ if (GetParam() == RequestType::kImage) {
+ interesting_http_response_->Send("Content-Type: image/png\r\n");
+ } else if (GetParam() == RequestType::kScript) {
+ interesting_http_response_->Send("Content-Type: text/css\r\n");
+ } else {
+ interesting_http_response_->Send("Content-Type: text/html\r\n");
+ }
+ interesting_http_response_->Send("Content-Length: 50\r\n\r\n");
+ // This is the first byte of the PNG header, to avoid any chance of the
+ // request being cancelled for not looking like a PNG.
+ interesting_http_response_->Send("\x89");
+ }
+
+ content::WebContents* active_web_contents() {
+ return browser()->tab_strip_model()->GetActiveWebContents();
+ }
+
+ base::HistogramTester* histograms() { return histograms_.get(); }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+ std::unique_ptr<net::test_server::ControllableHttpResponse>
+ uninteresting_main_frame_response_;
+ std::unique_ptr<net::test_server::ControllableHttpResponse>
+ interesting_http_response_;
+ std::unique_ptr<base::HistogramTester> histograms_;
+};
+
+// Testing before headers / during body is most interesting in the frame case,
+// as it checks the before and after commit case, which follow very different
+// paths.
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest,
+ NetErrorBeforeHeaders) {
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ // Not sending any body will result in failing with ERR_EMPTY_RESPONSE,
+ // without receiving any headers so the load won't be committed until the
+ // error page is seen.
+ interesting_http_response()->Done();
+ navigation_observer.Wait();
+
+ CheckHistograms(net::ERR_EMPTY_RESPONSE, HeadersReceived::kNoHeadersReceived,
+ NetworkAccessed::kNetworkAccessed);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, NetErrorDuringBody) {
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ SendHeadersPartialBody();
+ interesting_http_response()->Done();
+ navigation_observer.Wait();
+
+ CheckHistograms(net::ERR_CONTENT_LENGTH_MISMATCH,
+ HeadersReceived::kHeadersReceived,
+ NetworkAccessed::kNetworkAccessed);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, CancelBeforeHeaders) {
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ active_web_contents()->Stop();
+ navigation_observer.Wait();
+
+ CheckHistograms(net::ERR_ABORTED, HeadersReceived::kNoHeadersReceived,
+ NetworkAccessed::kNetworkAccessed);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, CancelDuringBody) {
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ SendHeadersPartialBody();
+
+ // Unfortunately, there's no way to ensure that the body has partially been
+ // received, so can only wait and hope. If the partial body hasn't been
+ // recieved by the time Stop() is called, the test should still pass, however.
+ base::RunLoop run_loop;
+ base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1));
+ run_loop.Run();
+
+ active_web_contents()->Stop();
+ navigation_observer.Wait();
+
+ CheckHistograms(net::ERR_ABORTED, HeadersReceived::kHeadersReceived,
+ NetworkAccessed::kNetworkAccessed);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest,
+ InterruptedBeforeHeaders) {
+ StartNavigatingAndWaitForRequest();
+
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ // Can't use ui_test_utils::NavigateToURLWithDisposition(), as it will wait
+ // for the current load to stop, rather than interrupting it.
+ browser()->OpenURL(OpenURLParams(
+ embedded_test_server()->GetURL("/echo"), Referrer(),
+ WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED,
+ false /* is_renderer_initiated */));
+ navigation_observer.Wait();
+
+ CheckHistogramsAfterMainFrameInterruption();
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest,
+ InterruptedCancelDuringBody) {
+ StartNavigatingAndWaitForRequest();
+ SendHeadersPartialBody();
+
+ // Unfortunately, there's no way to ensure that the body has partially been
+ // received, so can only wait and hope. If the partial body hasn't been
+ // recieved by the time Stop() is called, the test should still pass, however.
+ base::RunLoop run_loop;
+ base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1));
+ run_loop.Run();
+
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ // Can't use ui_test_utils::NavigateToURLWithDisposition(), as it will wait
+ // for the current load to stop, rather than interrupting it.
+ browser()->OpenURL(OpenURLParams(
+ embedded_test_server()->GetURL("/echo"), Referrer(),
+ WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED,
+ false /* is_renderer_initiated */));
+ navigation_observer.Wait();
+
+ CheckHistogramsAfterMainFrameInterruption();
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, SuccessWithBody) {
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ interesting_http_response()->Send("HTTP/1.1 200 Peachy\r\n\r\n");
+ interesting_http_response()->Send("Grapefruit");
+ interesting_http_response()->Done();
+ navigation_observer.Wait();
+
+ CheckHistograms(net::OK, HeadersReceived::kHeadersReceived,
+ NetworkAccessed::kNetworkAccessed);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, SuccessWithEmptyBody) {
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ interesting_http_response()->Send("HTTP/1.1 200 Peachy\r\n");
+ interesting_http_response()->Send("Content-Length: 0\r\n\r\n");
+ interesting_http_response()->Done();
+ navigation_observer.Wait();
+
+ CheckHistograms(net::OK, HeadersReceived::kHeadersReceived,
+ NetworkAccessed::kNetworkAccessed);
+}
+
+// Downloads should not be logged (Either as successes or failures).
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, Download) {
+ // Only frames can be converted to downloads.
+ if (GetParam() != RequestType::kMainFrame &&
+ GetParam() != RequestType::kSubFrame) {
+ return;
+ }
+
+ browser()->profile()->GetPrefs()->SetInteger(
+ prefs::kDownloadRestrictions,
+ static_cast<int>(DownloadPrefs::DownloadRestriction::ALL_FILES));
+ browser()->profile()->GetPrefs()->SetBoolean(prefs::kPromptForDownload,
+ false);
+
+ // Need this to wait for the download to be fully cancelled to avoid a
+ // confirmation prompt on quit.
+ DownloadTestObserverTerminal download_test_observer_terminal(
+ BrowserContext::GetDownloadManager(browser()->profile()), 1,
+ DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
+
+ TestNavigationObserver navigation_observer(active_web_contents(), 1);
+ StartNavigatingAndWaitForRequest();
+ interesting_http_response()->Send("HTTP/1.1 200 Peachy\r\n");
+ interesting_http_response()->Send(
+ "Content-Type: binary/octet-stream\r\n\r\n");
+ interesting_http_response()->Send("\x01");
+ interesting_http_response()->Done();
+ navigation_observer.Wait();
+
+ download_test_observer_terminal.WaitForFinished();
+
+ // Some metrics may come from the renderer. This call ensures that those
+ // metrics are available.
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
+ if (GetParam() == RequestType::kMainFrame) {
+ histograms()->ExpectTotalCount("Net.ErrorCodesForImages2", 0);
+ histograms()->ExpectTotalCount("Net.ErrorCodesForMainFrame4", 0);
+ histograms()->ExpectTotalCount("Net.ConnectionInfo.MainFrame", 0);
+ // Favicon may or may not have been loaded.
+ EXPECT_GE(
+ 1u,
+ histograms()->GetAllSamples("Net.ErrorCodesForSubresources3").size());
+ EXPECT_GE(
+ 1u,
+ histograms()->GetAllSamples("Net.ConnectionInfo.SubResource").size());
+
+ return;
+ }
+
+ // If not testing the main frame, there should also be just one result for
+ // the main frame.
+ histograms()->ExpectUniqueSample("Net.ErrorCodesForMainFrame4", -net::OK, 1);
+
+ // Some fuzziness here because of the favicon. It should typically succeed,
+ // but allow it to have been aborted, too, since the test server won't
+ // return a valid icon.
+ std::vector<base::Bucket> buckets =
+ histograms()->GetAllSamples("Net.ErrorCodesForSubresources3");
+ bool found_favicon_load = false;
+ for (auto& bucket : buckets) {
+ if (!found_favicon_load && bucket.count > 0 &&
+ (bucket.min == -net::OK || bucket.min == -net::ERR_ABORTED)) {
+ found_favicon_load = true;
+ bucket.count--;
+ }
+ EXPECT_EQ(0, bucket.count)
+ << "Found unexpected load with result: " << bucket.min;
+ }
+
+ histograms()->ExpectUniqueSample(
+ "Net.ConnectionInfo.MainFrame",
+ net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1, 1);
+ // Favicon request may or may not have received a response.
+ size_t subresources =
+ histograms()->GetAllSamples("Net.ConnectionInfo.SubResource").size();
+ EXPECT_LE(0u, subresources);
+ EXPECT_GE(1u, subresources);
+}
+
+// A few tests for file:// URLs, so that URLs not handled by the network service
+// itself have some coverage.
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, FileURLError) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir_;
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ base::FilePath main_frame_path = temp_dir_.GetPath().AppendASCII("main.html");
+ if (GetParam() != RequestType::kMainFrame) {
+ std::string main_frame_data = GetMainFrameContents("subresource");
+ ASSERT_EQ(static_cast<int>(main_frame_data.length()),
+ base::WriteFile(main_frame_path, main_frame_data.c_str(),
+ main_frame_data.length()));
+ }
+
+ ui_test_utils::NavigateToURL(browser(),
+ net::FilePathToFileURL(main_frame_path));
+ CheckHistograms(net::ERR_FILE_NOT_FOUND, HeadersReceived::kNoHeadersReceived,
+ NetworkAccessed::kNoNetworkAccessed);
+}
+
+IN_PROC_BROWSER_TEST_P(NetworkRequestMetricsBrowserTest, FileURLSuccess) {
+ const char kSubresourcePath[] = "subresource";
+
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir_;
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ base::FilePath main_frame_path = temp_dir_.GetPath().AppendASCII("main.html");
+ std::string main_frame_data = "foo";
+ if (GetParam() != RequestType::kMainFrame)
+ main_frame_data = GetMainFrameContents(kSubresourcePath);
+ ASSERT_EQ(static_cast<int>(main_frame_data.length()),
+ base::WriteFile(main_frame_path, main_frame_data.c_str(),
+ main_frame_data.length()));
+ if (GetParam() != RequestType::kMainFrame) {
+ std::string subresource_data = "foo";
+ ASSERT_EQ(
+ static_cast<int>(subresource_data.length()),
+ base::WriteFile(temp_dir_.GetPath().AppendASCII(kSubresourcePath),
+ subresource_data.c_str(), subresource_data.length()));
+ }
+
+ ui_test_utils::NavigateToURL(browser(),
+ net::FilePathToFileURL(main_frame_path));
+ CheckHistograms(net::OK, HeadersReceived::kNoHeadersReceived,
+ NetworkAccessed::kNoNetworkAccessed);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ NetworkRequestMetricsBrowserTest,
+ testing::Values(RequestType::kMainFrame,
+ RequestType::kSubFrame,
+ RequestType::kImage,
+ RequestType::kScript));
+
+} // namespace
+} // namespace content
diff --git a/chromium/chrome/browser/net/nss_context.cc b/chromium/chrome/browser/net/nss_context.cc
new file mode 100644
index 00000000000..67aa6bf89ef
--- /dev/null
+++ b/chromium/chrome/browser/net/nss_context.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/nss_context.h"
+
+#include "base/bind.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_context.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// Relays callback to the right message loop.
+void DidGetCertDBOnIOThread(
+ const scoped_refptr<base::SequencedTaskRunner>& response_task_runner,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback,
+ net::NSSCertDatabase* cert_db) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ response_task_runner->PostTask(FROM_HERE, base::BindOnce(callback, cert_db));
+}
+
+// Gets NSSCertDatabase for the resource context.
+void GetCertDBOnIOThread(
+ content::ResourceContext* context,
+ const scoped_refptr<base::SequencedTaskRunner>& response_task_runner,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ // Note that the callback will be used only if the cert database hasn't yet
+ // been initialized.
+ net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext(
+ context,
+ base::Bind(&DidGetCertDBOnIOThread, response_task_runner, callback));
+
+ if (cert_db)
+ DidGetCertDBOnIOThread(response_task_runner, callback, cert_db);
+}
+
+} // namespace
+
+void GetNSSCertDatabaseForProfile(
+ Profile* profile,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::IO},
+ base::BindOnce(&GetCertDBOnIOThread, profile->GetResourceContext(),
+ base::ThreadTaskRunnerHandle::Get(), callback));
+}
+
diff --git a/chromium/chrome/browser/net/nss_context.h b/chromium/chrome/browser/net/nss_context.h
new file mode 100644
index 00000000000..3c6f2b588fc
--- /dev/null
+++ b/chromium/chrome/browser/net/nss_context.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_NSS_CONTEXT_H_
+#define CHROME_BROWSER_NET_NSS_CONTEXT_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+#include "crypto/scoped_nss_types.h"
+
+class Profile;
+
+namespace net {
+class NSSCertDatabase;
+}
+
+namespace content {
+class ResourceContext;
+} // namespace content
+
+// Returns a pointer to the NSSCertDatabase for the user associated with
+// |context|, if it is ready. If it is not ready and |callback| is non-null, the
+// |callback| will be run once the DB is initialized. Ownership is not
+// transferred, but the caller may save the pointer, which will remain valid for
+// the lifetime of the ResourceContext.
+// Must be called only on the IO thread.
+net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
+ content::ResourceContext* context,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback)
+ WARN_UNUSED_RESULT;
+
+#if defined(OS_CHROMEOS)
+// Enables the system key slot in the NSSCertDatabase for the user associated
+// with |context|.
+// Must be called only on the IO thread.
+void EnableNSSSystemKeySlotForResourceContext(
+ content::ResourceContext* context);
+#endif
+
+// Gets a pointer to the NSSCertDatabase for the user associated with |context|.
+// It's a wrapper around |GetNSSCertDatabaseForResourceContext| which makes
+// sure it's called on IO thread (with |profile|'s resource context). The
+// callback will be called on the originating message loop.
+// It's accessing profile, so it should be called on the UI thread.
+void GetNSSCertDatabaseForProfile(
+ Profile* profile,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback);
+
+#endif // CHROME_BROWSER_NET_NSS_CONTEXT_H_
diff --git a/chromium/chrome/browser/net/nss_context_chromeos.cc b/chromium/chrome/browser/net/nss_context_chromeos.cc
new file mode 100644
index 00000000000..9a2e69590eb
--- /dev/null
+++ b/chromium/chrome/browser/net/nss_context_chromeos.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/nss_context.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/supports_user_data.h"
+#include "chrome/browser/profiles/profile_io_data.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/nss_util_internal.h"
+#include "net/cert/nss_cert_database_chromeos.h"
+
+namespace {
+
+void* kDatabaseManagerKey = &kDatabaseManagerKey;
+
+class NSSCertDatabaseChromeOSManager : public base::SupportsUserData::Data {
+ public:
+ typedef base::Callback<void(net::NSSCertDatabaseChromeOS*)>
+ GetNSSCertDatabaseCallback;
+ explicit NSSCertDatabaseChromeOSManager(const std::string& username_hash)
+ : username_hash_(username_hash) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ crypto::ScopedPK11Slot private_slot(crypto::GetPrivateSlotForChromeOSUser(
+ username_hash,
+ base::Bind(&NSSCertDatabaseChromeOSManager::DidGetPrivateSlot,
+ weak_ptr_factory_.GetWeakPtr())));
+ if (private_slot)
+ DidGetPrivateSlot(std::move(private_slot));
+ }
+
+ ~NSSCertDatabaseChromeOSManager() override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ }
+
+ net::NSSCertDatabaseChromeOS* GetNSSCertDatabase(
+ const GetNSSCertDatabaseCallback& callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ if (nss_cert_database_)
+ return nss_cert_database_.get();
+
+ ready_callback_list_.push_back(callback);
+ return NULL;
+ }
+
+ private:
+ typedef std::vector<GetNSSCertDatabaseCallback> ReadyCallbackList;
+
+ void DidGetPrivateSlot(crypto::ScopedPK11Slot private_slot) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ nss_cert_database_.reset(new net::NSSCertDatabaseChromeOS(
+ crypto::GetPublicSlotForChromeOSUser(username_hash_),
+ std::move(private_slot)));
+
+ ReadyCallbackList callback_list;
+ callback_list.swap(ready_callback_list_);
+ for (ReadyCallbackList::iterator i = callback_list.begin();
+ i != callback_list.end();
+ ++i) {
+ (*i).Run(nss_cert_database_.get());
+ }
+ }
+
+ std::string username_hash_;
+ std::unique_ptr<net::NSSCertDatabaseChromeOS> nss_cert_database_;
+ ReadyCallbackList ready_callback_list_;
+ base::WeakPtrFactory<NSSCertDatabaseChromeOSManager> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(NSSCertDatabaseChromeOSManager);
+};
+
+std::string GetUsername(content::ResourceContext* context) {
+ return ProfileIOData::FromResourceContext(context)->username_hash();
+}
+
+net::NSSCertDatabaseChromeOS* GetNSSCertDatabaseChromeOS(
+ content::ResourceContext* context,
+ const NSSCertDatabaseChromeOSManager::GetNSSCertDatabaseCallback&
+ callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ NSSCertDatabaseChromeOSManager* manager =
+ static_cast<NSSCertDatabaseChromeOSManager*>(
+ context->GetUserData(kDatabaseManagerKey));
+ if (!manager) {
+ manager = new NSSCertDatabaseChromeOSManager(GetUsername(context));
+ context->SetUserData(kDatabaseManagerKey, base::WrapUnique(manager));
+ }
+ return manager->GetNSSCertDatabase(callback);
+}
+
+void CallWithNSSCertDatabase(
+ const base::Callback<void(net::NSSCertDatabase*)>& callback,
+ net::NSSCertDatabaseChromeOS* db) {
+ callback.Run(db);
+}
+
+void SetSystemSlot(crypto::ScopedPK11Slot system_slot,
+ net::NSSCertDatabaseChromeOS* db) {
+ db->SetSystemSlot(std::move(system_slot));
+}
+
+void SetSystemSlotOfDBForResourceContext(content::ResourceContext* context,
+ crypto::ScopedPK11Slot system_slot) {
+ base::Callback<void(net::NSSCertDatabaseChromeOS*)> callback =
+ base::Bind(&SetSystemSlot, base::Passed(&system_slot));
+
+ net::NSSCertDatabaseChromeOS* db =
+ GetNSSCertDatabaseChromeOS(context, callback);
+ if (db)
+ callback.Run(db);
+}
+
+} // namespace
+
+net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
+ content::ResourceContext* context,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback) {
+ return GetNSSCertDatabaseChromeOS(
+ context, base::Bind(&CallWithNSSCertDatabase, callback));
+}
+
+void EnableNSSSystemKeySlotForResourceContext(
+ content::ResourceContext* context) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ base::Callback<void(crypto::ScopedPK11Slot)> callback =
+ base::Bind(&SetSystemSlotOfDBForResourceContext, context);
+ crypto::ScopedPK11Slot system_slot = crypto::GetSystemNSSKeySlot(callback);
+ if (system_slot)
+ callback.Run(std::move(system_slot));
+}
diff --git a/chromium/chrome/browser/net/nss_context_chromeos_browsertest.cc b/chromium/chrome/browser/net/nss_context_chromeos_browsertest.cc
new file mode 100644
index 00000000000..d17dfc90ac2
--- /dev/null
+++ b/chromium/chrome/browser/net/nss_context_chromeos_browsertest.cc
@@ -0,0 +1,208 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/nss_context.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/chromeos/login/login_manager_test.h"
+#include "chrome/browser/chromeos/login/startup_utils.h"
+#include "chrome/browser/chromeos/login/ui/user_adding_screen.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/cert/nss_cert_database.h"
+
+namespace {
+
+constexpr char kTestUser1[] = "test-user1@gmail.com";
+constexpr char kTestUser1GaiaId[] = "1111111111";
+constexpr char kTestUser2[] = "test-user2@gmail.com";
+constexpr char kTestUser2GaiaId[] = "2222222222";
+
+void NotCalledDbCallback(net::NSSCertDatabase* db) { ASSERT_TRUE(false); }
+
+// DBTester handles retrieving the NSSCertDatabase for a given profile, and
+// doing some simple sanity checks.
+// Browser test cases run on the UI thread, while the nss_context access needs
+// to happen on the IO thread. The DBTester class encapsulates the thread
+// posting and waiting on the UI thread so that the test case body can be
+// written linearly.
+class DBTester {
+ public:
+ explicit DBTester(Profile* profile) : profile_(profile), db_(NULL) {}
+
+ // Initial retrieval of cert database. It may be asynchronous or synchronous.
+ // Returns true if the database was retrieved successfully.
+ bool DoGetDBTests() {
+ base::RunLoop run_loop;
+ base::PostTask(
+ FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&DBTester::GetDBAndDoTestsOnIOThread,
+ base::Unretained(this), profile_->GetResourceContext(),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ return !!db_;
+ }
+
+ // Test retrieving the database again, should be called after DoGetDBTests.
+ void DoGetDBAgainTests() {
+ base::RunLoop run_loop;
+ base::PostTask(
+ FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&DBTester::DoGetDBAgainTestsOnIOThread,
+ base::Unretained(this), profile_->GetResourceContext(),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ }
+
+ void DoNotEqualsTests(DBTester* other_tester) {
+ // The DB and its NSS slots should be different for each profile.
+ EXPECT_NE(db_, other_tester->db_);
+ EXPECT_NE(db_->GetPublicSlot().get(),
+ other_tester->db_->GetPublicSlot().get());
+ }
+
+ private:
+ void GetDBAndDoTestsOnIOThread(content::ResourceContext* context,
+ const base::Closure& done_callback) {
+ net::NSSCertDatabase* db = GetNSSCertDatabaseForResourceContext(
+ context,
+ base::Bind(&DBTester::DoTestsOnIOThread,
+ base::Unretained(this),
+ done_callback));
+ if (db) {
+ DVLOG(1) << "got db synchronously";
+ DoTestsOnIOThread(done_callback, db);
+ } else {
+ DVLOG(1) << "getting db asynchronously...";
+ }
+ }
+
+ void DoTestsOnIOThread(const base::Closure& done_callback,
+ net::NSSCertDatabase* db) {
+ db_ = db;
+ EXPECT_TRUE(db);
+ if (db) {
+ EXPECT_TRUE(db->GetPublicSlot().get());
+ // Public and private slot are the same in tests.
+ EXPECT_EQ(db->GetPublicSlot().get(), db->GetPrivateSlot().get());
+ }
+
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI}, done_callback);
+ }
+
+ void DoGetDBAgainTestsOnIOThread(content::ResourceContext* context,
+ const base::Closure& done_callback) {
+ net::NSSCertDatabase* db = GetNSSCertDatabaseForResourceContext(
+ context, base::Bind(&NotCalledDbCallback));
+ // Should always be synchronous now.
+ EXPECT_TRUE(db);
+ // Should return the same db as before.
+ EXPECT_EQ(db_, db);
+
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI}, done_callback);
+ }
+
+ Profile* profile_;
+ net::NSSCertDatabase* db_;
+};
+
+class UserAddingFinishObserver : public chromeos::UserAddingScreen::Observer {
+ public:
+ UserAddingFinishObserver() {
+ chromeos::UserAddingScreen::Get()->AddObserver(this);
+ }
+
+ ~UserAddingFinishObserver() override {
+ chromeos::UserAddingScreen::Get()->RemoveObserver(this);
+ }
+
+ void WaitUntilUserAddingFinishedOrCancelled() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (finished_)
+ return;
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+ }
+
+ // chromeos::UserAddingScreen::Observer:
+ void OnUserAddingFinished() override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ finished_ = true;
+ if (run_loop_)
+ run_loop_->Quit();
+ }
+
+ void OnUserAddingStarted() override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ finished_ = false;
+ }
+
+ private:
+ bool finished_ = false;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ DISALLOW_COPY_AND_ASSIGN(UserAddingFinishObserver);
+};
+
+} // namespace
+
+class NSSContextChromeOSBrowserTest : public chromeos::LoginManagerTest {
+ public:
+ NSSContextChromeOSBrowserTest()
+ : LoginManagerTest(true /* should_launch_browser */,
+ true /* should_initialize_webui */) {}
+ ~NSSContextChromeOSBrowserTest() override {}
+};
+
+IN_PROC_BROWSER_TEST_F(NSSContextChromeOSBrowserTest, PRE_TwoUsers) {
+ // Initialization for ChromeOS multi-profile test infrastructure.
+ RegisterUser(AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId));
+ RegisterUser(AccountId::FromUserEmailGaiaId(kTestUser2, kTestUser2GaiaId));
+ chromeos::StartupUtils::MarkOobeCompleted();
+}
+
+IN_PROC_BROWSER_TEST_F(NSSContextChromeOSBrowserTest, TwoUsers) {
+ user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+
+ // Log in first user and get their DB.
+ const AccountId account_id1(
+ AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId));
+ LoginUser(account_id1);
+ Profile* profile1 = chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(
+ user_manager->FindUser(account_id1));
+ ASSERT_TRUE(profile1);
+
+ DBTester tester1(profile1);
+ ASSERT_TRUE(tester1.DoGetDBTests());
+
+ // Log in second user and get their DB.
+ UserAddingFinishObserver observer;
+ chromeos::UserAddingScreen::Get()->Start();
+ base::RunLoop().RunUntilIdle();
+
+ const AccountId account_id2(
+ AccountId::FromUserEmailGaiaId(kTestUser2, kTestUser2GaiaId));
+ AddUser(account_id2);
+ observer.WaitUntilUserAddingFinishedOrCancelled();
+
+ Profile* profile2 = chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(
+ user_manager->FindUser(account_id2));
+ ASSERT_TRUE(profile2);
+
+ DBTester tester2(profile2);
+ ASSERT_TRUE(tester2.DoGetDBTests());
+
+ // Get both DBs again to check that the same object is returned.
+ tester1.DoGetDBAgainTests();
+ tester2.DoGetDBAgainTests();
+
+ // Check that each user has a separate DB and NSS slots.
+ tester1.DoNotEqualsTests(&tester2);
+}
diff --git a/chromium/chrome/browser/net/nss_context_linux.cc b/chromium/chrome/browser/net/nss_context_linux.cc
new file mode 100644
index 00000000000..867fba19415
--- /dev/null
+++ b/chromium/chrome/browser/net/nss_context_linux.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/nss_context.h"
+
+#include "content/public/browser/browser_thread.h"
+#include "crypto/nss_util.h"
+#include "net/cert/nss_cert_database.h"
+
+namespace {
+net::NSSCertDatabase* g_nss_cert_database = NULL;
+} // namespace
+
+net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
+ content::ResourceContext* context,
+ const base::Callback<void(net::NSSCertDatabase*)>& callback) {
+ // This initialization is not thread safe. This CHECK ensures that this code
+ // is only run on a single thread.
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+ if (!g_nss_cert_database) {
+ // Linux has only a single persistent slot compared to ChromeOS's separate
+ // public and private slot.
+ // Redirect any slot usage to this persistent slot on Linux.
+ crypto::EnsureNSSInit();
+ g_nss_cert_database = new net::NSSCertDatabase(
+ crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()) /* public slot */,
+ crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()) /* private slot */);
+ }
+ return g_nss_cert_database;
+}
diff --git a/chromium/chrome/browser/net/prediction_options.cc b/chromium/chrome/browser/net/prediction_options.cc
new file mode 100644
index 00000000000..493fb0792bc
--- /dev/null
+++ b/chromium/chrome/browser/net/prediction_options.cc
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/prediction_options.h"
+
+#include "base/logging.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/network_change_notifier.h"
+
+namespace chrome_browser_net {
+
+namespace {
+
+// Since looking up preferences and current network connection are presumably
+// both cheap, we do not cache them here.
+NetworkPredictionStatus CanPrefetchAndPrerender(
+ int network_prediction_options) {
+ switch (network_prediction_options) {
+ case NETWORK_PREDICTION_ALWAYS:
+ case NETWORK_PREDICTION_WIFI_ONLY:
+ if (base::FeatureList::IsEnabled(
+ features::kPredictivePrefetchingAllowedOnAllConnectionTypes) ||
+ !net::NetworkChangeNotifier::IsConnectionCellular(
+ net::NetworkChangeNotifier::GetConnectionType())) {
+ return NetworkPredictionStatus::ENABLED;
+ }
+ return NetworkPredictionStatus::DISABLED_DUE_TO_NETWORK;
+ default:
+ DCHECK_EQ(NETWORK_PREDICTION_NEVER, network_prediction_options);
+ return NetworkPredictionStatus::DISABLED_ALWAYS;
+ }
+}
+
+bool CanPreresolveAndPreconnect(int network_prediction_options) {
+ // DNS preresolution and TCP preconnect are performed even on cellular
+ // networks if the user setting is WIFI_ONLY.
+ return network_prediction_options != NETWORK_PREDICTION_NEVER;
+}
+
+} // namespace
+
+void RegisterPredictionOptionsProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterIntegerPref(
+ prefs::kNetworkPredictionOptions,
+ NETWORK_PREDICTION_DEFAULT,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+NetworkPredictionStatus CanPrefetchAndPrerenderUI(PrefService* prefs) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(prefs);
+ return CanPrefetchAndPrerender(
+ prefs->GetInteger(prefs::kNetworkPredictionOptions));
+}
+
+bool CanPreresolveAndPreconnectUI(PrefService* prefs) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(prefs);
+ return CanPreresolveAndPreconnect(
+ prefs->GetInteger(prefs::kNetworkPredictionOptions));
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/prediction_options.h b/chromium/chrome/browser/net/prediction_options.h
new file mode 100644
index 00000000000..2ae6a109309
--- /dev/null
+++ b/chromium/chrome/browser/net/prediction_options.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PREDICTION_OPTIONS_H_
+#define CHROME_BROWSER_NET_PREDICTION_OPTIONS_H_
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class PrefService;
+
+namespace chrome_browser_net {
+
+// Enum describing when to allow network predictions based on connection type.
+// The numerical value is stored in the prefs file, therefore the same enum
+// with the same order must be used by the platform-dependent components.
+enum NetworkPredictionOptions {
+ // TODO(newt): collapse ALWAYS and WIFI_ONLY into a single value. See
+ // crbug.com/585297
+ NETWORK_PREDICTION_ALWAYS,
+ NETWORK_PREDICTION_WIFI_ONLY,
+ NETWORK_PREDICTION_NEVER,
+ NETWORK_PREDICTION_DEFAULT = NETWORK_PREDICTION_WIFI_ONLY,
+};
+
+enum class NetworkPredictionStatus {
+ ENABLED,
+ DISABLED_ALWAYS,
+ DISABLED_DUE_TO_NETWORK,
+};
+
+void RegisterPredictionOptionsProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry);
+
+// Determines whether prefetching and prerendering are enabled, based on
+// preferences and network type.
+NetworkPredictionStatus CanPrefetchAndPrerenderUI(PrefService* prefs);
+
+// Determines whether TCP preconnect and DNS preresolution are enabled, based on
+// preferences.
+bool CanPreresolveAndPreconnectUI(PrefService* prefs);
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_PREDICTION_OPTIONS_H_
diff --git a/chromium/chrome/browser/net/probe_message.cc b/chromium/chrome/browser/net/probe_message.cc
new file mode 100644
index 00000000000..4ab7e3e28ee
--- /dev/null
+++ b/chromium/chrome/browser/net/probe_message.cc
@@ -0,0 +1,128 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/probe_message.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/logging.h"
+
+namespace chrome_browser_net {
+
+const uint32_t ProbeMessage::kVersion = 2;
+const uint32_t ProbeMessage::kMaxNumberProbePackets = 21;
+const uint32_t ProbeMessage::kMaxProbePacketBytes = 1500;
+// Maximum pacing interval is 300 seconds (for testing NAT binding).
+const uint32_t ProbeMessage::kMaxPacingIntervalMicros = 300000000;
+const char ProbeMessage::kEncodingString[] =
+ "T\xd3?\xa5h2\x9c\x8en\xf1Q6\xbc{\xc6-4\xfa$f\xb9[\xa6\xcd@6,\xdf\xb3i-\xe6"
+ "v\x9eV\x8dXd\xd9kE\xf6=\xbeO";
+
+ProbeMessage::ProbeMessage() {}
+
+bool ProbeMessage::ParseInput(const std::string& input,
+ ProbePacket* probe_packet) const {
+ // Encode is used for decoding here.
+ std::string input_decoded = Encode(input);
+
+ bool parse_result = probe_packet->ParseFromString(input_decoded);
+ if (!parse_result) {
+ DVLOG(1) << "ProtoBuffer string parsing error. Input size:" << input.size();
+ return false;
+ }
+ const ProbePacket_Header& header = probe_packet->header();
+ DVLOG(2) << "version " << header.version() << " checksum "
+ << header.checksum() << " type " << header.type();
+ if (header.version() != kVersion) {
+ DVLOG(1) << "Bad version number: " << header.version()
+ << " expected: " << kVersion;
+ return false;
+ }
+
+ // Checksum is computed on padding only.
+ if (probe_packet->has_padding()) {
+ DVLOG(3) << "received padding: " << probe_packet->padding();
+ uint32_t computed_checksum = Checksum(probe_packet->padding());
+ if (computed_checksum != header.checksum()) {
+ DVLOG(1) << "Checksum mismatch. Got: " << header.checksum()
+ << " expected: " << computed_checksum;
+ return false;
+ }
+ }
+
+ if (header.type() != ProbePacket_Type_HELLO_REPLY &&
+ header.type() != ProbePacket_Type_PROBE_REPLY) {
+ DVLOG(1) << "Received unknown packet type:" << header.type();
+ return false;
+ }
+ return true;
+}
+
+uint32_t ProbeMessage::Checksum(const std::string& str) const {
+ uint32_t ret = 0;
+ for (std::string::const_iterator i = str.begin(); i != str.end(); ++i) {
+ ret += static_cast<uint8_t>(*i);
+ }
+ return ret;
+}
+
+void ProbeMessage::GenerateProbeRequest(const ProbePacket_Token& token,
+ uint32_t group_id,
+ uint32_t probe_size,
+ uint32_t pacing_interval_micros,
+ uint32_t number_probe_packets,
+ ProbePacket* probe_packet) {
+ DCHECK_LE(number_probe_packets, kMaxNumberProbePackets);
+ DCHECK_LE(probe_size, kMaxProbePacketBytes);
+ DCHECK_LE(pacing_interval_micros, kMaxPacingIntervalMicros);
+
+ SetPacketHeader(ProbePacket_Type_PROBE_REQUEST, probe_packet);
+ *(probe_packet->mutable_token()) = token;
+ probe_packet->set_group_id(group_id);
+ probe_packet->set_probe_size_bytes(probe_size);
+ probe_packet->set_pacing_interval_micros(pacing_interval_micros);
+ probe_packet->set_number_probe_packets(number_probe_packets);
+
+ // Add padding to mitigate amplification attack.
+ std::string* padding = probe_packet->mutable_padding();
+ int padding_size = probe_size - probe_packet->ByteSize();
+ padding->append(std::string(std::max(0, padding_size), 0));
+ probe_packet->mutable_header()->set_checksum(Checksum(*padding));
+ DVLOG(3) << "Request size " << probe_packet->ByteSize() << " probe size "
+ << probe_size;
+ DCHECK_LE(probe_size, static_cast<uint32_t>(probe_packet->ByteSize()));
+}
+
+void ProbeMessage::SetPacketHeader(ProbePacket_Type packet_type,
+ ProbePacket* probe_packet) const {
+ ProbePacket_Header* header = probe_packet->mutable_header();
+ header->set_version(kVersion);
+ header->set_type(packet_type);
+}
+
+std::string ProbeMessage::Encode(const std::string& input) const {
+
+ std::string output(input.size(), 0);
+ int key_pos = 0;
+ // kEncodingString contains a ending '\0' character, excluded for encoding.
+ int key_size = sizeof(kEncodingString) - 1;
+ for (size_t i = 0; i < input.size(); ++i) {
+ output[i] = input[i] ^ kEncodingString[key_pos];
+ ++key_pos;
+ if (key_pos >= key_size)
+ key_pos = 0;
+ }
+ return output;
+}
+
+std::string ProbeMessage::MakeEncodedPacket(
+ const ProbePacket& probe_packet) const {
+ std::string output;
+ probe_packet.SerializeToString(&output);
+ return Encode(output);
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/probe_message.h b/chromium/chrome/browser/net/probe_message.h
new file mode 100644
index 00000000000..6fa046666da
--- /dev/null
+++ b/chromium/chrome/browser/net/probe_message.h
@@ -0,0 +1,63 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROBE_MESSAGE_H_
+#define CHROME_BROWSER_NET_PROBE_MESSAGE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "chrome/browser/net/probe_message.pb.h"
+
+namespace chrome_browser_net {
+
+// Packet format between client and server is defined in probe_message.proto.
+class ProbeMessage {
+ public:
+ ProbeMessage();
+
+ // Generate a ProbeRequest packet.
+ void GenerateProbeRequest(const ProbePacket_Token& received_token,
+ uint32_t group_id,
+ uint32_t probe_size,
+ uint32_t pacing_interval_micros,
+ uint32_t number_probe_packets,
+ ProbePacket* output);
+ // Make an encoded packet (string) from a ProbePacket object.
+ std::string MakeEncodedPacket(const ProbePacket& packet) const;
+ // Fill some common fields in the packet header.
+ void SetPacketHeader(ProbePacket_Type packet_type,
+ ProbePacket* probe_packet) const;
+ // Parse the input string (which is an encoded packet) and save the result to
+ // packet. Return true if there is no error and false otherwise.
+ bool ParseInput(const std::string& input, ProbePacket* packet) const;
+
+ static const uint32_t kMaxProbePacketBytes;
+
+ private:
+ // For unittest.
+ friend class ProbeMessageTest;
+ FRIEND_TEST_ALL_PREFIXES(ProbeMessageTest, TestChecksum);
+ FRIEND_TEST_ALL_PREFIXES(ProbeMessageTest, TestEncode);
+ FRIEND_TEST_ALL_PREFIXES(ProbeMessageTest, TestGenerateProbeRequest);
+ FRIEND_TEST_ALL_PREFIXES(ProbeMessageTest, TestSetPacketHeader);
+
+ // Compute the checksum of the padding string.
+ uint32_t Checksum(const std::string& str) const;
+
+ // Encode the packet with kEncodingString. This is also used for decoding.
+ std::string Encode(const std::string& input) const;
+
+ static const uint32_t kVersion;
+ static const uint32_t kMaxNumberProbePackets;
+ static const uint32_t kMaxPacingIntervalMicros;
+ static const char kEncodingString[];
+
+ DISALLOW_COPY_AND_ASSIGN(ProbeMessage);
+};
+} // namespace chrome_browser_net
+#endif // CHROME_BROWSER_NET_PROBE_MESSAGE_H_
diff --git a/chromium/chrome/browser/net/probe_message.proto b/chromium/chrome/browser/net/probe_message.proto
new file mode 100644
index 00000000000..6338634b6a1
--- /dev/null
+++ b/chromium/chrome/browser/net/probe_message.proto
@@ -0,0 +1,48 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This protocol buffer defines the message format between the probe clients
+// and the probe servers.
+
+syntax = "proto2";
+
+package chrome_browser_net;
+
+// Chrome requires this.
+option optimize_for = LITE_RUNTIME;
+
+// Next available id: 10.
+message ProbePacket {
+ enum Type {
+ UNKNOWN = 0;
+ HELLO_REQUEST = 1;
+ HELLO_REPLY = 2;
+ PROBE_REQUEST = 3;
+ PROBE_REPLY = 4;
+ }
+
+ message Header {
+ required uint32 version = 1;
+ optional uint32 checksum = 2;
+ optional Type type = 3;
+ }
+
+ message Token {
+ required uint64 timestamp_micros = 1;
+ required bytes hash = 2; // 16-byte MD5 hash value for authorization.
+ }
+
+ optional Header header = 1;
+ optional Token token = 2; // For authorization.
+ // For differentiating different batches of packets.
+ optional uint32 group_id = 3;
+ optional uint32 packet_index = 4; // Packet index in each batch.
+ optional uint32 probe_size_bytes = 5; // Specify the probe packet size.
+ // Time duration between sending two consecutive packets.
+ optional uint32 pacing_interval_micros = 6;
+ // Total number of probe packets to send from the server.
+ optional uint32 number_probe_packets = 7;
+ optional int64 server_processing_micros = 9;
+ optional bytes padding = 8;
+}
diff --git a/chromium/chrome/browser/net/probe_message_unittest.cc b/chromium/chrome/browser/net/probe_message_unittest.cc
new file mode 100644
index 00000000000..b3d89708517
--- /dev/null
+++ b/chromium/chrome/browser/net/probe_message_unittest.cc
@@ -0,0 +1,107 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <string.h>
+
+#include "chrome/browser/net/probe_message.h"
+#include "chrome/browser/net/probe_message.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_browser_net {
+
+class ProbeMessageTest : public ::testing::Test {
+ protected:
+ ProbeMessageTest() {}
+
+ ~ProbeMessageTest() override {}
+};
+
+TEST_F(ProbeMessageTest, TestGenerateProbeRequest) {
+ ProbeMessage pm;
+ ProbePacket_Token token;
+ token.set_timestamp_micros(1000000U);
+ token.mutable_hash()->assign("1x1x");
+ uint32_t group_id = 1;
+ uint32_t probe_size = 500;
+ uint32_t pacing_interval_micros = 1000000;
+ uint32_t number_probe_packets = 21;
+ ProbePacket probe_packet;
+ pm.GenerateProbeRequest(token,
+ group_id,
+ probe_size,
+ pacing_interval_micros,
+ number_probe_packets,
+ &probe_packet);
+
+ EXPECT_EQ(probe_packet.header().type(), ProbePacket_Type_PROBE_REQUEST);
+ EXPECT_EQ(probe_packet.header().version(), ProbeMessage::kVersion);
+ EXPECT_EQ(probe_packet.group_id(), group_id);
+ EXPECT_EQ(probe_packet.probe_size_bytes(), probe_size);
+ EXPECT_EQ(probe_packet.pacing_interval_micros(), pacing_interval_micros);
+ EXPECT_EQ(probe_packet.number_probe_packets(), number_probe_packets);
+ EXPECT_GE(probe_packet.ByteSize(), static_cast<int>(probe_size));
+}
+
+TEST_F(ProbeMessageTest, TestSetPacketHeader) {
+ ProbeMessage pm;
+ ProbePacket probe_packet;
+ pm.SetPacketHeader(ProbePacket_Type_HELLO_REQUEST, &probe_packet);
+ EXPECT_EQ(probe_packet.header().type(), ProbePacket_Type_HELLO_REQUEST);
+ EXPECT_EQ(probe_packet.header().version(), ProbeMessage::kVersion);
+
+ pm.SetPacketHeader(ProbePacket_Type_PROBE_REPLY, &probe_packet);
+ EXPECT_EQ(probe_packet.header().type(), ProbePacket_Type_PROBE_REPLY);
+}
+
+TEST_F(ProbeMessageTest, TestMakeEncodePacketAndParseInput) {
+ ProbeMessage pm;
+ ProbePacket in_packet;
+ uint32_t version = 2;
+ ProbePacket_Type type = ProbePacket_Type_HELLO_REPLY;
+ uint32_t number_probe_packets = 2;
+ uint32_t group_id = 5;
+ in_packet.mutable_header()->set_version(version);
+ in_packet.mutable_header()->set_type(type);
+ in_packet.set_number_probe_packets(number_probe_packets);
+ in_packet.set_group_id(group_id);
+
+ // Encode it to string.
+ std::string output = pm.MakeEncodedPacket(in_packet);
+ // Parse to ProbePacket.
+ ProbePacket out_packet;
+ pm.ParseInput(output, &out_packet);
+
+ EXPECT_EQ(out_packet.header().type(), type);
+ EXPECT_EQ(out_packet.header().version(), version);
+ EXPECT_EQ(out_packet.number_probe_packets(), number_probe_packets);
+ EXPECT_EQ(out_packet.group_id(), group_id);
+}
+
+TEST_F(ProbeMessageTest, TestChecksum) {
+ ProbeMessage pm;
+ std::string str("ABC");
+ uint32_t computed_checksum = pm.Checksum(str);
+ uint32_t expected_sum = 0;
+ for (unsigned i = 0; i < str.size(); ++i)
+ expected_sum += static_cast<uint8_t>(str[i]);
+ EXPECT_EQ(computed_checksum, expected_sum);
+}
+
+TEST_F(ProbeMessageTest, TestEncode) {
+ ProbeMessage pm;
+ std::string original("ABC");
+ std::string output = pm.Encode(original);
+ std::string expected_str(original.size(), 0);
+ for (unsigned i = 0; i < original.size(); ++i) {
+ expected_str[i] = original[i] ^ ProbeMessage::kEncodingString[i];
+ }
+ EXPECT_EQ(output, expected_str);
+
+ // Do it again to decode.
+ std::string twice_encoded = pm.Encode(output);
+ EXPECT_EQ(twice_encoded, original);
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/profile_network_context_service.cc b/chromium/chrome/browser/net/profile_network_context_service.cc
new file mode 100644
index 00000000000..2813526e1ad
--- /dev/null
+++ b/chromium/chrome/browser/net/profile_network_context_service.cc
@@ -0,0 +1,704 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/profile_network_context_service.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
+#include "chrome/browser/domain_reliability/service_factory.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_content_client.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_paths_internal.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/certificate_transparency/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/shared_cors_origin_access_list.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/common/url_constants.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "net/base/features.h"
+#include "net/http/http_auth_preferences.h"
+#include "net/http/http_util.h"
+#include "services/network/public/cpp/cors/origin_access_list.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/policy_cert_service.h"
+#include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chromeos/constants/chromeos_switches.h"
+#include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
+#endif
+
+#if BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED)
+#include "chrome/browser/net/trial_comparison_cert_verifier_controller.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/constants.h"
+#endif
+
+namespace {
+
+bool* g_discard_domain_reliability_uploads_for_testing = nullptr;
+
+std::vector<std::string> TranslateStringArray(const base::ListValue* list) {
+ std::vector<std::string> strings;
+ for (const base::Value& value : *list) {
+ DCHECK(value.is_string());
+ strings.push_back(value.GetString());
+ }
+ return strings;
+}
+
+std::string ComputeAcceptLanguageFromPref(const std::string& language_pref) {
+ std::string accept_languages_str =
+ base::FeatureList::IsEnabled(features::kUseNewAcceptLanguageHeader)
+ ? net::HttpUtil::ExpandLanguageList(language_pref)
+ : language_pref;
+ return net::HttpUtil::GenerateAcceptLanguageHeader(accept_languages_str);
+}
+
+#if defined(OS_CHROMEOS)
+bool ShouldUseBuiltinCertVerifier(Profile* profile) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(chromeos::switches::kForceCertVerifierBuiltin))
+ return true;
+
+ if (chromeos::ProfileHelper::Get()->IsSigninProfile(profile) ||
+ chromeos::ProfileHelper::Get()->IsLockScreenAppProfile(profile)) {
+ return true;
+ }
+
+ const PrefService::Preference* builtin_cert_verifier_enabled_pref =
+ g_browser_process->local_state()->FindPreference(
+ prefs::kBuiltinCertificateVerifierEnabled);
+ if (builtin_cert_verifier_enabled_pref->IsManaged())
+ return builtin_cert_verifier_enabled_pref->GetValue()->GetBool();
+
+ return base::FeatureList::IsEnabled(
+ net::features::kCertVerifierBuiltinFeature);
+}
+
+network::mojom::AdditionalCertificatesPtr GetAdditionalCertificates(
+ const policy::PolicyCertService* policy_cert_service,
+ const base::FilePath& storage_partition_path) {
+ auto additional_certificates = network::mojom::AdditionalCertificates::New();
+ policy_cert_service->GetPolicyCertificatesForStoragePartition(
+ storage_partition_path, &(additional_certificates->all_certificates),
+ &(additional_certificates->trust_anchors));
+ return additional_certificates;
+}
+
+#endif // defined (OS_CHROMEOS)
+
+void InitializeCorsExtraSafelistedRequestHeaderNamesForProfile(
+ Profile* profile,
+ std::vector<std::string>* extra_safelisted_request_header_names) {
+ PrefService* pref = profile->GetPrefs();
+ bool has_managed_mitigation_list =
+ pref->IsManagedPreference(prefs::kCorsMitigationList);
+
+ // Set default mitigation parameters managed by the server for normal users
+ // and enterprise users separately.
+ const char* const feature_param_name =
+ has_managed_mitigation_list
+ ? "extra-safelisted-request-headers-for-enterprise"
+ : "extra-safelisted-request-headers";
+ *extra_safelisted_request_header_names =
+ SplitString(base::GetFieldTrialParamValueByFeature(
+ features::kExtraSafelistedRequestHeadersForOutOfBlinkCors,
+ feature_param_name),
+ ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+ // We trust and append |pref|'s values only when they are set by the managed
+ // policy. Chrome does not have any interface to set this preference manually.
+ if (has_managed_mitigation_list) {
+ for (const auto& header_name_value :
+ *pref->GetList(prefs::kCorsMitigationList)) {
+ extra_safelisted_request_header_names->push_back(
+ header_name_value.GetString());
+ }
+ }
+}
+
+} // namespace
+
+ProfileNetworkContextService::ProfileNetworkContextService(Profile* profile)
+ : profile_(profile), proxy_config_monitor_(profile) {
+ PrefService* profile_prefs = profile->GetPrefs();
+ quic_allowed_.Init(
+ prefs::kQuicAllowed, profile_prefs,
+ base::Bind(&ProfileNetworkContextService::DisableQuicIfNotAllowed,
+ base::Unretained(this)));
+ pref_accept_language_.Init(
+ language::prefs::kAcceptLanguages, profile_prefs,
+ base::BindRepeating(&ProfileNetworkContextService::UpdateAcceptLanguage,
+ base::Unretained(this)));
+ enable_referrers_.Init(
+ prefs::kEnableReferrers, profile_prefs,
+ base::BindRepeating(&ProfileNetworkContextService::UpdateReferrersEnabled,
+ base::Unretained(this)));
+ cookie_settings_ = CookieSettingsFactory::GetForProfile(profile);
+ cookie_settings_observer_.Add(cookie_settings_.get());
+
+ DisableQuicIfNotAllowed();
+
+ // Observe content settings so they can be synced to the network service.
+ HostContentSettingsMapFactory::GetForProfile(profile_)->AddObserver(this);
+
+ pref_change_registrar_.Init(profile_prefs);
+
+#if defined(OS_CHROMEOS)
+ using_builtin_cert_verifier_ = ShouldUseBuiltinCertVerifier(profile_);
+ VLOG(0) << "Using " << (using_builtin_cert_verifier_ ? "built-in" : "legacy")
+ << " cert verifier.";
+#endif
+ // When any of the following CT preferences change, we schedule an update
+ // to aggregate the actual update using a |ct_policy_update_timer_|.
+ pref_change_registrar_.Add(
+ certificate_transparency::prefs::kCTRequiredHosts,
+ base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+ base::Unretained(this)));
+ pref_change_registrar_.Add(
+ certificate_transparency::prefs::kCTExcludedHosts,
+ base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+ base::Unretained(this)));
+ pref_change_registrar_.Add(
+ certificate_transparency::prefs::kCTExcludedSPKIs,
+ base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+ base::Unretained(this)));
+ pref_change_registrar_.Add(
+ certificate_transparency::prefs::kCTExcludedLegacySPKIs,
+ base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+ base::Unretained(this)));
+
+ // Reflects CORS mitigation list policy updates dynamically.
+ pref_change_registrar_.Add(
+ prefs::kCorsMitigationList,
+ base::BindRepeating(
+ &ProfileNetworkContextService::UpdateCorsMitigationList,
+ base::Unretained(this)));
+}
+
+ProfileNetworkContextService::~ProfileNetworkContextService() {}
+
+mojo::Remote<network::mojom::NetworkContext>
+ProfileNetworkContextService::CreateNetworkContext(
+ bool in_memory,
+ const base::FilePath& relative_partition_path) {
+ mojo::Remote<network::mojom::NetworkContext> network_context;
+
+ content::GetNetworkService()->CreateNetworkContext(
+ network_context.BindNewPipeAndPassReceiver(),
+ CreateNetworkContextParams(in_memory, relative_partition_path));
+
+ if ((!in_memory && !profile_->IsOffTheRecord())) {
+ // TODO(jam): delete this code 1 year after Network Service shipped to all
+ // stable users, which would be after M83 branches.
+ base::FilePath media_cache_path = GetPartitionPath(relative_partition_path)
+ .Append(chrome::kMediaCacheDirname);
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::TaskPriority::BEST_EFFORT, base::MayBlock(),
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile), media_cache_path,
+ true /* recursive */));
+ }
+
+ std::vector<network::mojom::NetworkContext*> contexts{network_context.get()};
+ UpdateCTPolicyForContexts(contexts);
+
+ return network_context;
+}
+
+#if defined(OS_CHROMEOS)
+void ProfileNetworkContextService::UpdateAdditionalCertificates() {
+ const policy::PolicyCertService* policy_cert_service =
+ policy::PolicyCertServiceFactory::GetForProfile(profile_);
+ if (!policy_cert_service)
+ return;
+ content::BrowserContext::ForEachStoragePartition(
+ profile_, base::BindRepeating(
+ [](const policy::PolicyCertService* policy_cert_service,
+ content::StoragePartition* storage_partition) {
+ auto additional_certificates = GetAdditionalCertificates(
+ policy_cert_service, storage_partition->GetPath());
+ storage_partition->GetNetworkContext()
+ ->UpdateAdditionalCertificates(
+ std::move(additional_certificates));
+ },
+ policy_cert_service));
+}
+#endif
+
+void ProfileNetworkContextService::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterBooleanPref(prefs::kQuicAllowed, true);
+}
+
+// static
+void ProfileNetworkContextService::RegisterLocalStatePrefs(
+ PrefRegistrySimple* registry) {
+ registry->RegisterListPref(prefs::kHSTSPolicyBypassList);
+}
+
+void ProfileNetworkContextService::DisableQuicIfNotAllowed() {
+ if (!quic_allowed_.IsManaged())
+ return;
+
+ // If QUIC is allowed, do nothing (re-enabling QUIC is not supported).
+ if (quic_allowed_.GetValue())
+ return;
+
+ g_browser_process->system_network_context_manager()->DisableQuic();
+}
+
+void ProfileNetworkContextService::UpdateAcceptLanguage() {
+ content::BrowserContext::ForEachStoragePartition(
+ profile_, base::BindRepeating(
+ [](const std::string& accept_language,
+ content::StoragePartition* storage_partition) {
+ storage_partition->GetNetworkContext()->SetAcceptLanguage(
+ accept_language);
+ },
+ ComputeAcceptLanguage()));
+}
+
+void ProfileNetworkContextService::OnThirdPartyCookieBlockingChanged(
+ bool block_third_party_cookies) {
+ content::BrowserContext::ForEachStoragePartition(
+ profile_, base::BindRepeating(
+ [](bool block_third_party_cookies,
+ content::StoragePartition* storage_partition) {
+ storage_partition->GetCookieManagerForBrowserProcess()
+ ->BlockThirdPartyCookies(block_third_party_cookies);
+ },
+ block_third_party_cookies));
+}
+
+std::string ProfileNetworkContextService::ComputeAcceptLanguage() const {
+ return ComputeAcceptLanguageFromPref(pref_accept_language_.GetValue());
+}
+
+void ProfileNetworkContextService::UpdateReferrersEnabled() {
+ content::BrowserContext::ForEachStoragePartition(
+ profile_,
+ base::BindRepeating(
+ [](bool enable_referrers,
+ content::StoragePartition* storage_partition) {
+ storage_partition->GetNetworkContext()->SetEnableReferrers(
+ enable_referrers);
+ },
+ enable_referrers_.GetValue()));
+}
+
+void ProfileNetworkContextService::UpdateCTPolicyForContexts(
+ const std::vector<network::mojom::NetworkContext*>& contexts) {
+ auto* prefs = profile_->GetPrefs();
+ const base::ListValue* ct_required =
+ prefs->GetList(certificate_transparency::prefs::kCTRequiredHosts);
+ const base::ListValue* ct_excluded =
+ prefs->GetList(certificate_transparency::prefs::kCTExcludedHosts);
+ const base::ListValue* ct_excluded_spkis =
+ prefs->GetList(certificate_transparency::prefs::kCTExcludedSPKIs);
+ const base::ListValue* ct_excluded_legacy_spkis =
+ prefs->GetList(certificate_transparency::prefs::kCTExcludedLegacySPKIs);
+
+ std::vector<std::string> required(TranslateStringArray(ct_required));
+ std::vector<std::string> excluded(TranslateStringArray(ct_excluded));
+ std::vector<std::string> excluded_spkis(
+ TranslateStringArray(ct_excluded_spkis));
+ std::vector<std::string> excluded_legacy_spkis(
+ TranslateStringArray(ct_excluded_legacy_spkis));
+
+ for (auto* context : contexts) {
+ context->SetCTPolicy(required, excluded, excluded_spkis,
+ excluded_legacy_spkis);
+ }
+}
+
+void ProfileNetworkContextService::UpdateCTPolicy() {
+ std::vector<network::mojom::NetworkContext*> contexts;
+ content::BrowserContext::ForEachStoragePartition(
+ profile_,
+ base::BindRepeating(
+ [](std::vector<network::mojom::NetworkContext*>* contexts_ptr,
+ content::StoragePartition* storage_partition) {
+ contexts_ptr->push_back(storage_partition->GetNetworkContext());
+ },
+ &contexts));
+
+ UpdateCTPolicyForContexts(contexts);
+}
+
+void ProfileNetworkContextService::ScheduleUpdateCTPolicy() {
+ ct_policy_update_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(0),
+ this,
+ &ProfileNetworkContextService::UpdateCTPolicy);
+}
+
+void ProfileNetworkContextService::UpdateCorsMitigationList() {
+ std::vector<std::string> cors_extra_safelisted_request_header_names;
+ InitializeCorsExtraSafelistedRequestHeaderNamesForProfile(
+ profile_, &cors_extra_safelisted_request_header_names);
+
+ content::BrowserContext::ForEachStoragePartition(
+ profile_, base::BindRepeating(
+ [](std::vector<std::string>*
+ cors_extra_safelisted_request_header_names,
+ content::StoragePartition* storage_partition) {
+ storage_partition->GetNetworkContext()
+ ->SetCorsExtraSafelistedRequestHeaderNames(
+ *cors_extra_safelisted_request_header_names);
+ },
+ &cors_extra_safelisted_request_header_names));
+}
+
+// static
+network::mojom::CookieManagerParamsPtr
+ProfileNetworkContextService::CreateCookieManagerParams(
+ Profile* profile,
+ const content_settings::CookieSettings& cookie_settings) {
+ auto out = network::mojom::CookieManagerParams::New();
+ out->block_third_party_cookies =
+ cookie_settings.ShouldBlockThirdPartyCookies();
+ out->secure_origin_cookies_allowed_schemes.push_back(
+ content::kChromeUIScheme);
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ out->third_party_cookies_allowed_schemes.push_back(
+ extensions::kExtensionScheme);
+#endif
+
+ ContentSettingsForOneType settings;
+ HostContentSettingsMap* host_content_settings_map =
+ HostContentSettingsMapFactory::GetForProfile(profile);
+ host_content_settings_map->GetSettingsForOneType(
+ CONTENT_SETTINGS_TYPE_COOKIES, std::string(), &settings);
+ out->settings = std::move(settings);
+
+ ContentSettingsForOneType settings_for_legacy_cookie_access;
+ host_content_settings_map->GetSettingsForOneType(
+ CONTENT_SETTINGS_TYPE_LEGACY_COOKIE_ACCESS, std::string(),
+ &settings_for_legacy_cookie_access);
+ out->settings_for_legacy_cookie_access =
+ std::move(settings_for_legacy_cookie_access);
+ return out;
+}
+
+void ProfileNetworkContextService::FlushProxyConfigMonitorForTesting() {
+ proxy_config_monitor_.FlushForTesting();
+}
+
+void ProfileNetworkContextService::SetDiscardDomainReliabilityUploadsForTesting(
+ bool value) {
+ g_discard_domain_reliability_uploads_for_testing = new bool(value);
+}
+
+network::mojom::NetworkContextParamsPtr
+ProfileNetworkContextService::CreateNetworkContextParams(
+ bool in_memory,
+ const base::FilePath& relative_partition_path) {
+ if (profile_->IsOffTheRecord())
+ in_memory = true;
+ base::FilePath path(GetPartitionPath(relative_partition_path));
+
+ network::mojom::NetworkContextParamsPtr network_context_params =
+ g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams();
+
+ network_context_params->context_name = std::string("main");
+
+ network_context_params->accept_language = ComputeAcceptLanguage();
+ network_context_params->enable_referrers = enable_referrers_.GetValue();
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kShortReportingDelay)) {
+ network_context_params->reporting_delivery_interval =
+ base::TimeDelta::FromMilliseconds(100);
+ }
+
+ InitializeCorsExtraSafelistedRequestHeaderNamesForProfile(
+ profile_,
+ &network_context_params->cors_extra_safelisted_request_header_names);
+ network_context_params->cors_mode =
+ profile_->ShouldEnableOutOfBlinkCors()
+ ? network::mojom::NetworkContextParams::CorsMode::kEnable
+ : network::mojom::NetworkContextParams::CorsMode::kDisable;
+
+ // Always enable the HTTP cache.
+ network_context_params->http_cache_enabled = true;
+
+ // Allow/disallow ambient authentication with default credentials based on the
+ // profile type.
+ // TODO(https://crbug.com/458508): Allow this behavior to be controllable by
+ // policy.
+ if (profile_->IsGuestSession()) {
+ network_context_params->allow_default_credentials =
+ base::FeatureList::IsEnabled(
+ features::kEnableAmbientAuthenticationInGuestSession)
+ ? net::HttpAuthPreferences::ALLOW_DEFAULT_CREDENTIALS
+ : net::HttpAuthPreferences::DISALLOW_DEFAULT_CREDENTIALS;
+ } else if (profile_->IsIncognitoProfile()) {
+ network_context_params->allow_default_credentials =
+ base::FeatureList::IsEnabled(
+ features::kEnableAmbientAuthenticationInIncognito)
+ ? net::HttpAuthPreferences::ALLOW_DEFAULT_CREDENTIALS
+ : net::HttpAuthPreferences::DISALLOW_DEFAULT_CREDENTIALS;
+ } else {
+ network_context_params->allow_default_credentials =
+ net::HttpAuthPreferences::ALLOW_DEFAULT_CREDENTIALS;
+ }
+
+ network_context_params->cookie_manager_params =
+ CreateCookieManagerParams(profile_, *cookie_settings_);
+
+ // Configure on-disk storage for non-OTR profiles. OTR profiles just use
+ // default behavior (in memory storage, default sizes).
+ if (!in_memory) {
+ PrefService* local_state = g_browser_process->local_state();
+ // Configure the HTTP cache path and size.
+ base::FilePath base_cache_path;
+ chrome::GetUserCacheDirectory(path, &base_cache_path);
+ base::FilePath disk_cache_dir =
+ local_state->GetFilePath(prefs::kDiskCacheDir);
+ if (!disk_cache_dir.empty())
+ base_cache_path = disk_cache_dir.Append(base_cache_path.BaseName());
+ network_context_params->http_cache_path =
+ base_cache_path.Append(chrome::kCacheDirname);
+ network_context_params->http_cache_max_size =
+ local_state->GetInteger(prefs::kDiskCacheSize);
+
+ // Currently this just contains HttpServerProperties, but that will likely
+ // change.
+ network_context_params->http_server_properties_path =
+ path.Append(chrome::kNetworkPersistentStateFilename);
+
+ base::FilePath cookie_path = path;
+ cookie_path = cookie_path.Append(chrome::kCookieFilename);
+ network_context_params->cookie_path = cookie_path;
+
+#if BUILDFLAG(ENABLE_REPORTING)
+ base::FilePath reporting_and_nel_store_path = path;
+ reporting_and_nel_store_path = reporting_and_nel_store_path.Append(
+ chrome::kReportingAndNelStoreFilename);
+ network_context_params->reporting_and_nel_store_path =
+ reporting_and_nel_store_path;
+#endif // BUILDFLAG(ENABLE_REPORTING)
+
+ if (relative_partition_path.empty()) { // This is the main partition.
+ network_context_params->restore_old_session_cookies =
+ profile_->ShouldRestoreOldSessionCookies();
+ network_context_params->persist_session_cookies =
+ profile_->ShouldPersistSessionCookies();
+ } else {
+ // Copy behavior of ProfileImplIOData::InitializeAppRequestContext.
+ network_context_params->restore_old_session_cookies = false;
+ network_context_params->persist_session_cookies = false;
+ }
+
+ network_context_params->transport_security_persister_path = path;
+ }
+ const base::ListValue* hsts_policy_bypass_list =
+ g_browser_process->local_state()->GetList(prefs::kHSTSPolicyBypassList);
+ for (const auto& value : *hsts_policy_bypass_list) {
+ std::string string_value;
+ if (!value.GetAsString(&string_value)) {
+ continue;
+ }
+ network_context_params->hsts_policy_bypass_list.push_back(string_value);
+ }
+
+ // NOTE(mmenke): Keep these protocol handlers and
+ // ProfileIOData::SetUpJobFactoryDefaultsForBuilder in sync with
+ // ProfileIOData::IsHandledProtocol().
+ // TODO(mmenke): Find a better way of handling tracking supported schemes.
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
+ network_context_params->enable_ftp_url_support =
+ base::FeatureList::IsEnabled(features::kEnableFtp);
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
+
+ proxy_config_monitor_.AddToNetworkContextParams(network_context_params.get());
+
+ network_context_params->enable_certificate_reporting = true;
+ network_context_params->enable_expect_ct_reporting = true;
+
+#if BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED)
+ if (!in_memory && !network_context_params->use_builtin_cert_verifier &&
+ TrialComparisonCertVerifierController::MaybeAllowedForProfile(profile_)) {
+ mojo::PendingRemote<network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client;
+ auto config_client_receiver =
+ config_client.InitWithNewPipeAndPassReceiver();
+
+ network_context_params->trial_comparison_cert_verifier_params =
+ network::mojom::TrialComparisonCertVerifierParams::New();
+
+ if (!trial_comparison_cert_verifier_controller_) {
+ trial_comparison_cert_verifier_controller_ =
+ std::make_unique<TrialComparisonCertVerifierController>(profile_);
+ }
+ trial_comparison_cert_verifier_controller_->AddClient(
+ std::move(config_client),
+ network_context_params->trial_comparison_cert_verifier_params
+ ->report_client.InitWithNewPipeAndPassReceiver());
+ network_context_params->trial_comparison_cert_verifier_params
+ ->initial_allowed =
+ trial_comparison_cert_verifier_controller_->IsAllowed();
+ network_context_params->trial_comparison_cert_verifier_params
+ ->config_client_receiver = std::move(config_client_receiver);
+ }
+#endif
+
+ if (domain_reliability::DomainReliabilityServiceFactory::
+ ShouldCreateService()) {
+ network_context_params->enable_domain_reliability = true;
+ network_context_params->domain_reliability_upload_reporter =
+ domain_reliability::DomainReliabilityServiceFactory::
+ kUploadReporterString;
+ network_context_params->discard_domain_reliablity_uploads =
+ g_discard_domain_reliability_uploads_for_testing
+ ? *g_discard_domain_reliability_uploads_for_testing
+ : !g_browser_process->local_state()->GetBoolean(
+ metrics::prefs::kMetricsReportingEnabled);
+ }
+
+ if (data_reduction_proxy::params::IsEnabledWithNetworkService()) {
+ auto* drp_settings =
+ DataReductionProxyChromeSettingsFactory::GetForBrowserContext(profile_);
+ if (drp_settings) {
+ mojo::Remote<network::mojom::CustomProxyConfigClient> config_client;
+ network_context_params->custom_proxy_config_client_receiver =
+ config_client.BindNewPipeAndPassReceiver();
+ drp_settings->AddCustomProxyConfigClient(std::move(config_client));
+ }
+ }
+
+#if defined(OS_CHROMEOS)
+ // Note: On non-ChromeOS platforms, the |use_builtin_cert_verifier| param
+ // value is inherited from CreateDefaultNetworkContextParams.
+ network_context_params->use_builtin_cert_verifier =
+ using_builtin_cert_verifier_;
+
+ bool profile_supports_policy_certs = false;
+ if (chromeos::ProfileHelper::IsSigninProfile(profile_))
+ profile_supports_policy_certs = true;
+ user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+ if (user_manager) {
+ const user_manager::User* user =
+ chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
+ // No need to initialize NSS for users with empty username hash:
+ // Getters for a user's NSS slots always return NULL slot if the user's
+ // username hash is empty, even when the NSS is not initialized for the
+ // user.
+ if (user && !user->username_hash().empty()) {
+ network_context_params->username_hash = user->username_hash();
+ network_context_params->nss_path = profile_->GetPath();
+ profile_supports_policy_certs = true;
+ }
+ }
+ if (profile_supports_policy_certs &&
+ policy::PolicyCertServiceFactory::CreateAndStartObservingForProfile(
+ profile_)) {
+ const policy::PolicyCertService* policy_cert_service =
+ policy::PolicyCertServiceFactory::GetForProfile(profile_);
+ network_context_params->initial_additional_certificates =
+ GetAdditionalCertificates(policy_cert_service,
+ GetPartitionPath(relative_partition_path));
+ }
+#endif
+
+ // Should be initialized with existing per-profile CORS access lists.
+ network_context_params->cors_origin_access_list =
+ profile_->GetSharedCorsOriginAccessList()
+ ->GetOriginAccessList()
+ .CreateCorsOriginAccessPatternsList();
+
+ return network_context_params;
+}
+
+base::FilePath ProfileNetworkContextService::GetPartitionPath(
+ const base::FilePath& relative_partition_path) {
+ base::FilePath path = profile_->GetPath();
+ if (!relative_partition_path.empty())
+ path = path.Append(relative_partition_path);
+ return path;
+}
+
+void ProfileNetworkContextService::OnContentSettingChanged(
+ const ContentSettingsPattern& primary_pattern,
+ const ContentSettingsPattern& secondary_pattern,
+ ContentSettingsType content_type,
+ const std::string& resource_identifier) {
+ if (content_type != CONTENT_SETTINGS_TYPE_COOKIES &&
+ content_type != CONTENT_SETTINGS_TYPE_LEGACY_COOKIE_ACCESS &&
+ content_type != CONTENT_SETTINGS_TYPE_DEFAULT) {
+ return;
+ }
+
+ if (content_type == CONTENT_SETTINGS_TYPE_COOKIES ||
+ content_type == CONTENT_SETTINGS_TYPE_DEFAULT) {
+ ContentSettingsForOneType cookies_settings;
+ HostContentSettingsMapFactory::GetForProfile(profile_)
+ ->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_COOKIES, std::string(),
+ &cookies_settings);
+ content::BrowserContext::ForEachStoragePartition(
+ profile_, base::BindRepeating(
+ [](ContentSettingsForOneType settings,
+ content::StoragePartition* storage_partition) {
+ storage_partition->GetCookieManagerForBrowserProcess()
+ ->SetContentSettings(settings);
+ },
+ cookies_settings));
+ }
+
+ if (content_type == CONTENT_SETTINGS_TYPE_LEGACY_COOKIE_ACCESS ||
+ content_type == CONTENT_SETTINGS_TYPE_DEFAULT) {
+ ContentSettingsForOneType legacy_cookie_access_settings;
+ HostContentSettingsMapFactory::GetForProfile(profile_)
+ ->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_LEGACY_COOKIE_ACCESS,
+ std::string(), &legacy_cookie_access_settings);
+ content::BrowserContext::ForEachStoragePartition(
+ profile_, base::BindRepeating(
+ [](ContentSettingsForOneType settings,
+ content::StoragePartition* storage_partition) {
+ storage_partition->GetCookieManagerForBrowserProcess()
+ ->SetContentSettingsForLegacyCookieAccess(settings);
+ },
+ legacy_cookie_access_settings));
+ }
+}
diff --git a/chromium/chrome/browser/net/profile_network_context_service.h b/chromium/chrome/browser/net/profile_network_context_service.h
new file mode 100644
index 00000000000..4574f59fce3
--- /dev/null
+++ b/chromium/chrome/browser/net/profile_network_context_service.h
@@ -0,0 +1,162 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROFILE_NETWORK_CONTEXT_SERVICE_H_
+#define CHROME_BROWSER_NET_PROFILE_NETWORK_CONTEXT_SERVICE_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/scoped_observer.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
+#include "chrome/browser/net/proxy_config_monitor.h"
+#include "components/content_settings/core/browser/content_settings_observer.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_member.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/net_buildflags.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+
+class PrefRegistrySimple;
+class Profile;
+class TrialComparisonCertVerifierController;
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// KeyedService that initializes and provides access to the NetworkContexts for
+// a Profile. This will eventually replace ProfileIOData.
+class ProfileNetworkContextService
+ : public KeyedService,
+ public content_settings::Observer,
+ public content_settings::CookieSettings::Observer {
+ public:
+ explicit ProfileNetworkContextService(Profile* profile);
+ ~ProfileNetworkContextService() override;
+
+ // Creates a NetworkContext for the BrowserContext, using the specified
+ // parameters. An empty |relative_partition_path| corresponds to the main
+ // network context.
+ mojo::Remote<network::mojom::NetworkContext> CreateNetworkContext(
+ bool in_memory,
+ const base::FilePath& relative_partition_path);
+
+#if defined(OS_CHROMEOS)
+ void UpdateAdditionalCertificates();
+
+ bool using_builtin_cert_verifier() { return using_builtin_cert_verifier_; }
+#endif
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+ static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
+
+ // Packages up configuration info in |profile| and |cookie_settings| into a
+ // mojo-friendly form.
+ static network::mojom::CookieManagerParamsPtr CreateCookieManagerParams(
+ Profile* profile,
+ const content_settings::CookieSettings& cookie_settings);
+
+ // Flushes all pending proxy configuration changes.
+ void FlushProxyConfigMonitorForTesting();
+
+ static void SetDiscardDomainReliabilityUploadsForTesting(bool value);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ProfileNetworkContextServiceBrowsertest,
+ DefaultCacheSize);
+ FRIEND_TEST_ALL_PREFIXES(ProfileNetworkContextServiceDiskCacheBrowsertest,
+ DiskCacheSize);
+ FRIEND_TEST_ALL_PREFIXES(
+ ProfileNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest,
+ Test);
+
+ // Checks |quic_allowed_|, and disables QUIC if needed.
+ void DisableQuicIfNotAllowed();
+
+ // Forwards changes to |pref_accept_language_| to the NetworkContext, after
+ // formatting them as appropriate.
+ void UpdateAcceptLanguage();
+
+ // Forwards changes to |block_third_party_cookies_| to the NetworkContext.
+ void UpdateBlockThirdPartyCookies();
+
+ // Computes appropriate value of Accept-Language header based on
+ // |pref_accept_language_|
+ std::string ComputeAcceptLanguage() const;
+
+ void UpdateReferrersEnabled();
+
+ // Update the CTPolicy for the given NetworkContexts.
+ void UpdateCTPolicyForContexts(
+ const std::vector<network::mojom::NetworkContext*>& contexts);
+
+ // Update the CTPolicy for the all of profiles_'s NetworkContexts.
+ void UpdateCTPolicy();
+
+ void ScheduleUpdateCTPolicy();
+
+ // Update the CORS mitigation list for the all of profiles_'s NetworkContexts.
+ void UpdateCorsMitigationList();
+
+ // Creates parameters for the NetworkContext. Use |in_memory| instead of
+ // |profile_->IsOffTheRecord()| because sometimes normal profiles want off the
+ // record partitions (e.g. for webview tag).
+ network::mojom::NetworkContextParamsPtr CreateNetworkContextParams(
+ bool in_memory,
+ const base::FilePath& relative_partition_path);
+
+ // Returns the path for a given storage partition.
+ base::FilePath GetPartitionPath(
+ const base::FilePath& relative_partition_path);
+
+ // content_settings::Observer:
+ void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern,
+ const ContentSettingsPattern& secondary_pattern,
+ ContentSettingsType content_type,
+ const std::string& resource_identifier) override;
+
+ // content_settings::CookieSettings::Observer:
+ void OnThirdPartyCookieBlockingChanged(
+ bool block_third_party_cookies) override;
+
+ Profile* const profile_;
+
+ ProxyConfigMonitor proxy_config_monitor_;
+
+ BooleanPrefMember quic_allowed_;
+ StringPrefMember pref_accept_language_;
+ BooleanPrefMember enable_referrers_;
+ PrefChangeRegistrar pref_change_registrar_;
+
+ scoped_refptr<content_settings::CookieSettings> cookie_settings_;
+ ScopedObserver<content_settings::CookieSettings,
+ content_settings::CookieSettings::Observer>
+ cookie_settings_observer_{this};
+
+ // Used to post schedule CT policy updates
+ base::OneShotTimer ct_policy_update_timer_;
+
+#if BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED)
+ // Controls the cert verification trial. May be null if the trial is disabled
+ // or not allowed for this profile.
+ std::unique_ptr<TrialComparisonCertVerifierController>
+ trial_comparison_cert_verifier_controller_;
+#endif
+
+#if defined(OS_CHROMEOS)
+ bool using_builtin_cert_verifier_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ProfileNetworkContextService);
+};
+
+#endif // CHROME_BROWSER_NET_PROFILE_NETWORK_CONTEXT_SERVICE_H_
diff --git a/chromium/chrome/browser/net/profile_network_context_service_browsertest.cc b/chromium/chrome/browser/net/profile_network_context_service_browsertest.cc
new file mode 100644
index 00000000000..0aee0562ce4
--- /dev/null
+++ b/chromium/chrome/browser/net/profile_network_context_service_browsertest.cc
@@ -0,0 +1,588 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/profile_network_context_service.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/profile_network_context_service.h"
+#include "chrome/browser/net/profile_network_context_service_factory.h"
+#include "chrome/browser/policy/policy_test_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths_internal.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/simple_url_loader_test_helper.h"
+#include "mojo/public/cpp/system/data_pipe_utils.h"
+#include "net/base/load_flags.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/cors/cors.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/chromeos/policy/login_policy_test_base.h"
+#include "chrome/browser/chromeos/policy/user_policy_test_helper.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "components/policy/policy_constants.h"
+#include "net/base/features.h"
+#endif // defined(OS_CHROMEOS)
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+#include "chrome/browser/policy/policy_test_utils.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "net/base/features.h"
+#endif
+
+// Most tests for this class are in NetworkContextConfigurationBrowserTest.
+class ProfileNetworkContextServiceBrowsertest : public InProcessBrowserTest {
+ public:
+ ProfileNetworkContextServiceBrowsertest() {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ }
+
+ ~ProfileNetworkContextServiceBrowsertest() override {}
+
+ void SetUpOnMainThread() override {
+ loader_factory_ = content::BrowserContext::GetDefaultStoragePartition(
+ browser()->profile())
+ ->GetURLLoaderFactoryForBrowserProcess()
+ .get();
+ }
+
+ network::mojom::URLLoaderFactory* loader_factory() const {
+ return loader_factory_;
+ }
+
+ private:
+ network::mojom::URLLoaderFactory* loader_factory_ = nullptr;
+};
+
+IN_PROC_BROWSER_TEST_F(ProfileNetworkContextServiceBrowsertest,
+ DiskCacheLocation) {
+ // Run a request that caches the response, to give the network service time to
+ // create a cache directory.
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = embedded_test_server()->GetURL("/cachetime");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ ASSERT_TRUE(simple_loader_helper.response_body());
+
+ base::FilePath expected_cache_path;
+ chrome::GetUserCacheDirectory(browser()->profile()->GetPath(),
+ &expected_cache_path);
+ expected_cache_path = expected_cache_path.Append(chrome::kCacheDirname);
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ EXPECT_TRUE(base::PathExists(expected_cache_path));
+}
+
+IN_PROC_BROWSER_TEST_F(ProfileNetworkContextServiceBrowsertest,
+ DefaultCacheSize) {
+ // We don't have a great way of directly checking that the disk cache has the
+ // correct max size, but we can make sure that we set up our network context
+ // params correctly.
+ ProfileNetworkContextService* profile_network_context_service =
+ ProfileNetworkContextServiceFactory::GetForContext(browser()->profile());
+ base::FilePath empty_relative_partition_path;
+ network::mojom::NetworkContextParamsPtr network_context_params_ptr =
+ profile_network_context_service->CreateNetworkContextParams(
+ /*in_memory=*/false, empty_relative_partition_path);
+ EXPECT_EQ(0, network_context_params_ptr->http_cache_max_size);
+}
+
+IN_PROC_BROWSER_TEST_F(ProfileNetworkContextServiceBrowsertest, BrotliEnabled) {
+ // Brotli is only used over encrypted connections.
+ net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ https_server.AddDefaultHandlers(
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = https_server.GetURL("/echoheader?accept-encoding");
+
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ ASSERT_TRUE(simple_loader_helper.response_body());
+ std::vector<std::string> encodings =
+ base::SplitString(*simple_loader_helper.response_body(), ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ EXPECT_TRUE(base::Contains(encodings, "br"));
+}
+
+// Test subclass that adds switches::kDiskCacheDir and switches::kDiskCacheSize
+// to the command line, to make sure they're respected.
+class ProfileNetworkContextServiceDiskCacheBrowsertest
+ : public ProfileNetworkContextServiceBrowsertest {
+ public:
+ const int64_t kCacheSize = 7;
+
+ ProfileNetworkContextServiceDiskCacheBrowsertest() {
+ EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ ~ProfileNetworkContextServiceDiskCacheBrowsertest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchPath(switches::kDiskCacheDir,
+ temp_dir_.GetPath());
+ command_line->AppendSwitchASCII(switches::kDiskCacheSize,
+ base::NumberToString(kCacheSize));
+ }
+
+ const base::FilePath& TempPath() { return temp_dir_.GetPath(); }
+
+ private:
+ base::ScopedTempDir temp_dir_;
+};
+
+// Makes sure switches::kDiskCacheDir is hooked up correctly.
+IN_PROC_BROWSER_TEST_F(ProfileNetworkContextServiceDiskCacheBrowsertest,
+ DiskCacheLocation) {
+ // Make sure command line switch is hooked up to the pref.
+ ASSERT_EQ(TempPath(), g_browser_process->local_state()->GetFilePath(
+ prefs::kDiskCacheDir));
+
+ // Run a request that caches the response, to give the network service time to
+ // create a cache directory.
+ std::unique_ptr<network::ResourceRequest> request =
+ std::make_unique<network::ResourceRequest>();
+ request->url = embedded_test_server()->GetURL("/cachetime");
+ content::SimpleURLLoaderTestHelper simple_loader_helper;
+ std::unique_ptr<network::SimpleURLLoader> simple_loader =
+ network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory(), simple_loader_helper.GetCallback());
+ simple_loader_helper.WaitForCallback();
+ ASSERT_TRUE(simple_loader_helper.response_body());
+
+ // Cache directory should now exist.
+ base::FilePath expected_cache_path =
+ TempPath()
+ .Append(browser()->profile()->GetPath().BaseName())
+ .Append(chrome::kCacheDirname);
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ EXPECT_TRUE(base::PathExists(expected_cache_path));
+}
+
+// Makes sure switches::kDiskCacheSize is hooked up correctly.
+IN_PROC_BROWSER_TEST_F(ProfileNetworkContextServiceDiskCacheBrowsertest,
+ DiskCacheSize) {
+ // Make sure command line switch is hooked up to the pref.
+ ASSERT_EQ(kCacheSize, g_browser_process->local_state()->GetInteger(
+ prefs::kDiskCacheSize));
+
+ // We don't have a great way of directly checking that the disk cache has the
+ // correct max size, but we can make sure that we set up our network context
+ // params correctly.
+ ProfileNetworkContextService* profile_network_context_service =
+ ProfileNetworkContextServiceFactory::GetForContext(browser()->profile());
+ base::FilePath empty_relative_partition_path;
+ network::mojom::NetworkContextParamsPtr network_context_params_ptr =
+ profile_network_context_service->CreateNetworkContextParams(
+ /*in_memory=*/false, empty_relative_partition_path);
+ EXPECT_EQ(kCacheSize, network_context_params_ptr->http_cache_max_size);
+}
+
+#if defined(OS_CHROMEOS)
+// Base class for verifying which certificate verifier is being used on Chrome
+// OS depending on feature state and policies.
+class ProfileNetworkContextServiceCertVerifierBrowsertestBase
+ : public policy::LoginPolicyTestBase {
+ public:
+ ProfileNetworkContextServiceCertVerifierBrowsertestBase() = default;
+ ~ProfileNetworkContextServiceCertVerifierBrowsertestBase() override = default;
+
+ protected:
+ void SetPolicyValue(base::StringPiece policy_key, base::Value value) {
+ policy_values_.SetKey(policy_key, std::move(value));
+ user_policy_helper()->SetPolicy(policy_values_,
+ base::Value(base::Value::Type::DICTIONARY));
+ }
+
+ bool IsSigninProfileUsingBuiltinCertVerifier() {
+ Profile* const profile = chromeos::ProfileHelper::GetSigninProfile();
+ ProfileNetworkContextService* const service =
+ ProfileNetworkContextServiceFactory::GetForContext(profile);
+ return service->using_builtin_cert_verifier();
+ }
+
+ bool IsActiveProfileUsingBuiltinCertVerifier() {
+ Profile* const profile = GetProfileForActiveUser();
+ ProfileNetworkContextService* const service =
+ ProfileNetworkContextServiceFactory::GetForContext(profile);
+ return service->using_builtin_cert_verifier();
+ }
+
+ base::test::ScopedFeatureList scoped_feature_list_;
+
+ private:
+ base::Value policy_values_{base::Value::Type::DICTIONARY};
+
+ DISALLOW_COPY_AND_ASSIGN(
+ ProfileNetworkContextServiceCertVerifierBrowsertestBase);
+};
+
+// When using this class, the built-in certificate verifier has been enabled
+// using the UseBuiltinCertVerifier feature.
+class ProfileNetworkContextServiceCertVerifierBuiltinEnabledBrowsertest
+ : public ProfileNetworkContextServiceCertVerifierBrowsertestBase {
+ public:
+ ProfileNetworkContextServiceCertVerifierBuiltinEnabledBrowsertest() = default;
+ ~ProfileNetworkContextServiceCertVerifierBuiltinEnabledBrowsertest()
+ override = default;
+
+ void SetUpInProcessBrowserTestFixture() override {
+ scoped_feature_list_.InitAndEnableFeature(
+ net::features::kCertVerifierBuiltinFeature);
+ ProfileNetworkContextServiceCertVerifierBrowsertestBase::
+ SetUpInProcessBrowserTestFixture();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ ProfileNetworkContextServiceCertVerifierBuiltinEnabledBrowsertest);
+};
+
+// If the built-in cert verifier is enabled and no policy is present, it should
+// be enabled on the sign-in screen and in the user profile.
+IN_PROC_BROWSER_TEST_F(
+ ProfileNetworkContextServiceCertVerifierBuiltinEnabledBrowsertest,
+ TurnedOnByFeature) {
+ SkipToLoginScreen();
+ EXPECT_TRUE(IsSigninProfileUsingBuiltinCertVerifier());
+
+ LogIn(kAccountId, kAccountPassword, kEmptyServices);
+
+ EXPECT_TRUE(IsActiveProfileUsingBuiltinCertVerifier());
+}
+
+// If the built-in cert verifier is enabled, but user policy says to disable it,
+// it should be disabled in the user profile.
+IN_PROC_BROWSER_TEST_F(
+ ProfileNetworkContextServiceCertVerifierBuiltinEnabledBrowsertest,
+ TurnedOffByLegacyPolicy) {
+ SkipToLoginScreen();
+
+ SetPolicyValue(policy::key::kBuiltinCertificateVerifierEnabled,
+ base::Value(false));
+ LogIn(kAccountId, kAccountPassword, kEmptyServices);
+
+ EXPECT_FALSE(IsActiveProfileUsingBuiltinCertVerifier());
+}
+
+// When using this class, the built-in certificate verifier has been disabled
+// using the UseBuiltinCertVerifier feature.
+class ProfileNetworkContextServiceCertVerifierBuiltinDisabledBrowsertest
+ : public ProfileNetworkContextServiceCertVerifierBrowsertestBase {
+ public:
+ ProfileNetworkContextServiceCertVerifierBuiltinDisabledBrowsertest() =
+ default;
+ ~ProfileNetworkContextServiceCertVerifierBuiltinDisabledBrowsertest()
+ override = default;
+
+ void SetUpInProcessBrowserTestFixture() override {
+ scoped_feature_list_.InitAndDisableFeature(
+ net::features::kCertVerifierBuiltinFeature);
+ ProfileNetworkContextServiceCertVerifierBrowsertestBase::
+ SetUpInProcessBrowserTestFixture();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ ProfileNetworkContextServiceCertVerifierBuiltinDisabledBrowsertest);
+};
+
+// If the built-in cert verifier feature is disabled, it should be disabled in
+// user profiles but enabled in the sign-in profile.
+IN_PROC_BROWSER_TEST_F(
+ ProfileNetworkContextServiceCertVerifierBuiltinDisabledBrowsertest,
+ TurnedOffByFeature) {
+ SkipToLoginScreen();
+ EXPECT_TRUE(IsSigninProfileUsingBuiltinCertVerifier());
+
+ LogIn(kAccountId, kAccountPassword, kEmptyServices);
+
+ EXPECT_FALSE(IsActiveProfileUsingBuiltinCertVerifier());
+}
+
+// If the built-in cert verifier feature is disabled, but policy force-enables
+// it for a profile, it should be enabled in the profile.
+IN_PROC_BROWSER_TEST_F(
+ ProfileNetworkContextServiceCertVerifierBuiltinDisabledBrowsertest,
+ TurnedOffByFeatureOverrideByPolicy) {
+ SkipToLoginScreen();
+ EXPECT_TRUE(IsSigninProfileUsingBuiltinCertVerifier());
+
+ SetPolicyValue(policy::key::kBuiltinCertificateVerifierEnabled,
+ base::Value(true));
+ LogIn(kAccountId, kAccountPassword, kEmptyServices);
+
+ EXPECT_TRUE(IsActiveProfileUsingBuiltinCertVerifier());
+}
+#elif BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+class ProfileNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest
+ : public policy::PolicyTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ scoped_feature_list_.InitWithFeatureState(
+ net::features::kCertVerifierBuiltinFeature,
+ /*enabled=*/GetParam());
+ policy::PolicyTest::SetUpInProcessBrowserTestFixture();
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(
+ ProfileNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest,
+ Test) {
+ ProfileNetworkContextService* profile_network_context_service =
+ ProfileNetworkContextServiceFactory::GetForContext(browser()->profile());
+ base::FilePath empty_relative_partition_path;
+ network::mojom::NetworkContextParamsPtr network_context_params_ptr =
+ profile_network_context_service->CreateNetworkContextParams(
+ /*in_memory=*/false, empty_relative_partition_path);
+ EXPECT_EQ(GetParam(), network_context_params_ptr->use_builtin_cert_verifier);
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+ // If the BuiltinCertificateVerifierEnabled policy is set it should override
+ // the feature flag.
+ policy::PolicyMap policies;
+ SetPolicy(&policies, policy::key::kBuiltinCertificateVerifierEnabled,
+ std::make_unique<base::Value>(true));
+ UpdateProviderPolicy(policies);
+
+ network_context_params_ptr =
+ profile_network_context_service->CreateNetworkContextParams(
+ /*in_memory=*/false, empty_relative_partition_path);
+ EXPECT_TRUE(network_context_params_ptr->use_builtin_cert_verifier);
+
+ SetPolicy(&policies, policy::key::kBuiltinCertificateVerifierEnabled,
+ std::make_unique<base::Value>(false));
+ UpdateProviderPolicy(policies);
+
+ network_context_params_ptr =
+ profile_network_context_service->CreateNetworkContextParams(
+ /*in_memory=*/false, empty_relative_partition_path);
+ EXPECT_FALSE(network_context_params_ptr->use_builtin_cert_verifier);
+#endif
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ ProfileNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest,
+ ::testing::Bool());
+#endif
+
+enum class CorsTestMode {
+ kWithCorsMitigationListPolicy,
+ kWithoutCorsMitigationListPolicy,
+};
+
+class CorsExtraSafelistedHeaderNamesTest
+ : public policy::PolicyTest,
+ public ::testing::WithParamInterface<CorsTestMode> {
+ public:
+ CorsExtraSafelistedHeaderNamesTest() {
+ switch (GetParam()) {
+ case CorsTestMode::kWithCorsMitigationListPolicy: {
+ auto list = std::make_unique<base::ListValue>();
+ list->AppendString("bar");
+ policy::PolicyMap policies;
+ policies.Set(policy::key::kCorsMitigationList,
+ policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
+ policy::POLICY_SOURCE_CLOUD, std::move(list), nullptr);
+ provider_.UpdateChromePolicy(policies);
+ scoped_feature_list_.InitWithFeaturesAndParameters(
+ {{network::features::kOutOfBlinkCors, {}},
+ {features::kExtraSafelistedRequestHeadersForOutOfBlinkCors,
+ {{"extra-safelisted-request-headers-for-enterprise", "foo"}}}},
+ {});
+ break;
+ }
+ case CorsTestMode::kWithoutCorsMitigationListPolicy:
+ scoped_feature_list_.InitWithFeaturesAndParameters(
+ {{network::features::kOutOfBlinkCors, {}},
+ {features::kExtraSafelistedRequestHeadersForOutOfBlinkCors,
+ {{"extra-safelisted-request-headers", "foo,bar"}}}},
+ {});
+ break;
+ }
+ }
+
+ // Override to avoid conflict between the |scoped_feature_list_| and
+ // |command_line| that PolicyTest::SetUpCommandLine will introduce.
+ // TODO(crbug.com/1002483): Remove this workaround.
+ void SetUpCommandLine(base::CommandLine* command_line) override {}
+
+ void SetUpOnMainThread() override {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ // This base::Unretained is safe because |this| outlives
+ // |cross_origin_test_server_|.
+ cross_origin_test_server_.RegisterRequestHandler(
+ base::BindRepeating(&CorsExtraSafelistedHeaderNamesTest::HandleRequest,
+ base::Unretained(this)));
+ ASSERT_TRUE(cross_origin_test_server_.Start());
+
+ PolicyTest::SetUpOnMainThread();
+ }
+
+ void LoadAndWait(const GURL& url) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ base::string16 expected_title(base::ASCIIToUTF16("OK"));
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+ title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
+ ui_test_utils::NavigateToURL(browser(), url);
+ ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
+ }
+
+ uint16_t cross_origin_port() { return cross_origin_test_server_.port(); }
+ size_t options_count() {
+ base::AutoLock lock(lock_);
+ return options_count_;
+ }
+ size_t get_count() {
+ base::AutoLock lock(lock_);
+ return get_count_;
+ }
+
+ const net::EmbeddedTestServer& cross_origin_test_server() const {
+ return cross_origin_test_server_;
+ }
+
+ static constexpr char kTestPath[] =
+ "/cors-extra-safelisted-header-names.html";
+
+ private:
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ response->set_code(net::HTTP_OK);
+ response->AddCustomHeader(
+ network::cors::header_names::kAccessControlAllowOrigin, "*");
+ if (request.method == net::test_server::METHOD_OPTIONS) {
+ response->AddCustomHeader(
+ network::cors::header_names::kAccessControlAllowMethods,
+ "GET, OPTIONS");
+ response->AddCustomHeader(
+ network::cors::header_names::kAccessControlAllowHeaders, "baz");
+ response->AddCustomHeader(
+ network::cors::header_names::kAccessControlMaxAge, "60");
+ base::AutoLock lock(lock_);
+ options_count_++;
+ } else if (request.method == net::test_server::METHOD_GET) {
+ base::AutoLock lock(lock_);
+ get_count_++;
+ }
+ return response;
+ }
+
+ base::test::ScopedFeatureList scoped_feature_list_;
+ net::EmbeddedTestServer cross_origin_test_server_;
+ base::Lock lock_;
+
+ size_t options_count_ GUARDED_BY(lock_) = 0;
+ size_t get_count_ GUARDED_BY(lock_) = 0;
+};
+
+constexpr char CorsExtraSafelistedHeaderNamesTest::kTestPath[];
+
+IN_PROC_BROWSER_TEST_P(CorsExtraSafelistedHeaderNamesTest, RequestWithFoo) {
+ GURL url(cross_origin_test_server().GetURL("/hello"));
+ LoadAndWait(embedded_test_server()->GetURL(base::StringPrintf(
+ "%s?url=%s&headers=foo", kTestPath, url.spec().c_str())));
+ EXPECT_EQ(0u, options_count());
+ EXPECT_EQ(1u, get_count());
+}
+
+IN_PROC_BROWSER_TEST_P(CorsExtraSafelistedHeaderNamesTest, RequestWithBar) {
+ GURL url(cross_origin_test_server().GetURL("/hello"));
+ LoadAndWait(embedded_test_server()->GetURL(base::StringPrintf(
+ "%s?url=%s&headers=bar", kTestPath, url.spec().c_str())));
+ EXPECT_EQ(0u, options_count());
+ EXPECT_EQ(1u, get_count());
+}
+
+IN_PROC_BROWSER_TEST_P(CorsExtraSafelistedHeaderNamesTest, RequestWithFooBar) {
+ GURL url(cross_origin_test_server().GetURL("/hello"));
+ LoadAndWait(embedded_test_server()->GetURL(base::StringPrintf(
+ "%s?url=%s&headers=foo,bar", kTestPath, url.spec().c_str())));
+ EXPECT_EQ(0u, options_count());
+ EXPECT_EQ(1u, get_count());
+}
+
+IN_PROC_BROWSER_TEST_P(CorsExtraSafelistedHeaderNamesTest, RequestWithBaz) {
+ GURL url(cross_origin_test_server().GetURL("/hello"));
+ LoadAndWait(embedded_test_server()->GetURL(base::StringPrintf(
+ "%s?url=%s&headers=baz", kTestPath, url.spec().c_str())));
+ EXPECT_EQ(1u, options_count());
+ EXPECT_EQ(1u, get_count());
+}
+
+IN_PROC_BROWSER_TEST_P(CorsExtraSafelistedHeaderNamesTest, RequestWithFooBaz) {
+ GURL url(cross_origin_test_server().GetURL("/hello"));
+ LoadAndWait(embedded_test_server()->GetURL(base::StringPrintf(
+ "%s?url=%s&headers=foo,baz", kTestPath, url.spec().c_str())));
+ EXPECT_EQ(1u, options_count());
+ EXPECT_EQ(1u, get_count());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ WithCorsMitigationListPolicy,
+ CorsExtraSafelistedHeaderNamesTest,
+ testing::Values(CorsTestMode::kWithCorsMitigationListPolicy));
+
+INSTANTIATE_TEST_SUITE_P(
+ WithoutCorsMitigationListPolicy,
+ CorsExtraSafelistedHeaderNamesTest,
+ testing::Values(CorsTestMode::kWithoutCorsMitigationListPolicy));
diff --git a/chromium/chrome/browser/net/profile_network_context_service_factory.cc b/chromium/chrome/browser/net/profile_network_context_service_factory.cc
new file mode 100644
index 00000000000..3ab33703e4c
--- /dev/null
+++ b/chromium/chrome/browser/net/profile_network_context_service_factory.cc
@@ -0,0 +1,40 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/profile_network_context_service_factory.h"
+
+#include "chrome/browser/net/profile_network_context_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+ProfileNetworkContextService*
+ProfileNetworkContextServiceFactory::GetForContext(
+ content::BrowserContext* browser_context) {
+ return static_cast<ProfileNetworkContextService*>(
+ GetInstance()->GetServiceForBrowserContext(browser_context, true));
+}
+
+ProfileNetworkContextServiceFactory*
+ProfileNetworkContextServiceFactory::GetInstance() {
+ return base::Singleton<ProfileNetworkContextServiceFactory>::get();
+}
+
+ProfileNetworkContextServiceFactory::ProfileNetworkContextServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ProfileNetworkContextService",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+ProfileNetworkContextServiceFactory::~ProfileNetworkContextServiceFactory() {}
+
+KeyedService* ProfileNetworkContextServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* profile) const {
+ return new ProfileNetworkContextService(Profile::FromBrowserContext(profile));
+}
+
+content::BrowserContext*
+ProfileNetworkContextServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ // Create separate service for incognito profiles.
+ return context;
+}
diff --git a/chromium/chrome/browser/net/profile_network_context_service_factory.h b/chromium/chrome/browser/net/profile_network_context_service_factory.h
new file mode 100644
index 00000000000..baebf1a6a4d
--- /dev/null
+++ b/chromium/chrome/browser/net/profile_network_context_service_factory.h
@@ -0,0 +1,46 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROFILE_NETWORK_CONTEXT_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_NET_PROFILE_NETWORK_CONTEXT_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class KeyedService;
+class ProfileNetworkContextService;
+
+namespace contenet {
+class BrowserContext;
+}
+
+class ProfileNetworkContextServiceFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ // Returns the ProfileNetworkContextService that supports NetworkContexts for
+ // |browser_context|.
+ static ProfileNetworkContextService* GetForContext(
+ content::BrowserContext* browser_context);
+
+ // Returns the NetworkContextServiceFactory singleton.
+ static ProfileNetworkContextServiceFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ ProfileNetworkContextServiceFactory>;
+
+ ProfileNetworkContextServiceFactory();
+ ~ProfileNetworkContextServiceFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfileNetworkContextServiceFactory);
+};
+
+#endif // CHROME_BROWSER_NET_PROFILE_NETWORK_CONTEXT_SERVICE_FACTORY_H_
diff --git a/chromium/chrome/browser/net/proxy_browsertest.cc b/chromium/chrome/browser/net/proxy_browsertest.cc
new file mode 100644
index 00000000000..e1ac8cacc9d
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_browsertest.cc
@@ -0,0 +1,302 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/bind_test_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/net/proxy_test_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/login/login_handler.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/base/load_flags.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/simple_connection_listener.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/test/test_data_directory.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/net/dhcp_wpad_url_client.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace {
+
+// Verify kPACScript is installed as the PAC script.
+void VerifyProxyScript(Browser* browser) {
+ ui_test_utils::NavigateToURL(browser, GURL("http://google.com"));
+
+ // Verify we get the ERR_PROXY_CONNECTION_FAILED screen.
+ bool result = false;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser->tab_strip_model()->GetActiveWebContents(),
+ "var textContent = document.body.textContent;"
+ "var hasError = textContent.indexOf('ERR_PROXY_CONNECTION_FAILED') >= 0;"
+ "domAutomationController.send(hasError);",
+ &result));
+ EXPECT_TRUE(result);
+}
+
+// This class observes chrome::NOTIFICATION_AUTH_NEEDED and supplies
+// the credential which is required by the test proxy server.
+// "foo:bar" is the required username and password for our test proxy server.
+class LoginPromptObserver : public content::NotificationObserver {
+ public:
+ LoginPromptObserver() : auth_handled_(false) {}
+
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override {
+ if (type == chrome::NOTIFICATION_AUTH_NEEDED) {
+ LoginNotificationDetails* login_details =
+ content::Details<LoginNotificationDetails>(details).ptr();
+ // |login_details->handler()| is the associated LoginHandler object.
+ // SetAuth() will close the login dialog.
+ login_details->handler()->SetAuth(base::ASCIIToUTF16("foo"),
+ base::ASCIIToUTF16("bar"));
+ auth_handled_ = true;
+ }
+ }
+
+ bool auth_handled() const { return auth_handled_; }
+
+ private:
+ bool auth_handled_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoginPromptObserver);
+};
+
+// Test that the browser can establish a WebSocket connection via a proxy
+// that requires basic authentication. This test also checks the headers
+// arrive at WebSocket server.
+IN_PROC_BROWSER_TEST_F(ProxyBrowserTest, BasicAuthWSConnect) {
+ // Launch WebSocket server.
+ net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS,
+ net::GetWebSocketTestDataDirectory());
+ ASSERT_TRUE(ws_server.Start());
+
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::NavigationController* controller = &tab->GetController();
+ content::NotificationRegistrar registrar;
+ // The proxy server will request basic authentication.
+ // |observer| supplies the credential.
+ LoginPromptObserver observer;
+ registrar.Add(&observer, chrome::NOTIFICATION_AUTH_NEEDED,
+ content::Source<content::NavigationController>(controller));
+
+ content::TitleWatcher watcher(tab, base::ASCIIToUTF16("PASS"));
+ watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
+
+ // Visit a page that tries to establish WebSocket connection. The title
+ // of the page will be 'PASS' on success.
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("http");
+ ui_test_utils::NavigateToURL(browser(),
+ ws_server.GetURL("proxied_request_check.html")
+ .ReplaceComponents(replacements));
+
+ const base::string16 result = watcher.WaitAndGetTitle();
+ EXPECT_TRUE(base::EqualsASCII(result, "PASS"));
+ EXPECT_TRUE(observer.auth_handled());
+}
+
+// Fetches a PAC script via an http:// URL, and ensures that requests to
+// http://www.google.com fail with ERR_PROXY_CONNECTION_FAILED (by virtue of
+// PAC file having selected a non-existent PROXY server).
+class BaseHttpProxyScriptBrowserTest : public InProcessBrowserTest {
+ public:
+ BaseHttpProxyScriptBrowserTest() {
+ http_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+ }
+ ~BaseHttpProxyScriptBrowserTest() override {}
+
+ void SetUp() override {
+ ASSERT_TRUE(http_server_.Start());
+ InProcessBrowserTest::SetUp();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII(
+ switches::kProxyPacUrl,
+ http_server_.GetURL("/" + GetPacFilename()).spec());
+ }
+
+ protected:
+ virtual std::string GetPacFilename() = 0;
+ net::EmbeddedTestServer http_server_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BaseHttpProxyScriptBrowserTest);
+};
+
+// Tests the use of a PAC script that rejects requests to http://www.google.com/
+class HttpProxyScriptBrowserTest : public BaseHttpProxyScriptBrowserTest {
+ public:
+ HttpProxyScriptBrowserTest() = default;
+ ~HttpProxyScriptBrowserTest() override {}
+
+ std::string GetPacFilename() override {
+ // PAC script that sends all requests to an invalid proxy server.
+ return "bad_server.pac";
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyScriptBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(HttpProxyScriptBrowserTest, Verify) {
+ VerifyProxyScript(browser());
+}
+
+#if defined(OS_CHROMEOS)
+// Tests the use of a PAC script set via Web Proxy Autodiscovery Protocol.
+// TODO(crbug.com/991867): Add a test case for when DhcpWpadUrlClient
+// returns an empty PAC URL.
+class WPADHttpProxyScriptBrowserTest : public HttpProxyScriptBrowserTest {
+ public:
+ WPADHttpProxyScriptBrowserTest() = default;
+ ~WPADHttpProxyScriptBrowserTest() override {}
+
+ void SetUp() override {
+ ASSERT_TRUE(http_server_.Start());
+ pac_url_ = http_server_.GetURL("/" + GetPacFilename());
+ chromeos::DhcpWpadUrlClient::SetPacUrlForTesting(pac_url_);
+ InProcessBrowserTest::SetUp();
+ }
+
+ void TearDown() override {
+ chromeos::DhcpWpadUrlClient::ClearPacUrlForTesting();
+ InProcessBrowserTest::TearDown();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kProxyAutoDetect);
+ }
+
+ private:
+ GURL pac_url_;
+ DISALLOW_COPY_AND_ASSIGN(WPADHttpProxyScriptBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(WPADHttpProxyScriptBrowserTest, Verify) {
+ VerifyProxyScript(browser());
+}
+#endif // defined(OS_CHROMEOS)
+
+// Tests the use of a PAC script that rejects requests to http://www.google.com/
+// when myIpAddress() and myIpAddressEx() appear to be working.
+class MyIpAddressProxyScriptBrowserTest
+ : public BaseHttpProxyScriptBrowserTest {
+ public:
+ MyIpAddressProxyScriptBrowserTest() = default;
+ ~MyIpAddressProxyScriptBrowserTest() override {}
+
+ std::string GetPacFilename() override {
+ // PAC script that sends all requests to an invalid proxy server provided
+ // myIpAddress() and myIpAddressEx() are not loopback addresses.
+ return "my_ip_address.pac";
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MyIpAddressProxyScriptBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(MyIpAddressProxyScriptBrowserTest, Verify) {
+ VerifyProxyScript(browser());
+}
+
+// Fetch PAC script via a hanging http:// URL.
+class HangingPacRequestProxyScriptBrowserTest : public InProcessBrowserTest {
+ public:
+ HangingPacRequestProxyScriptBrowserTest() {}
+ ~HangingPacRequestProxyScriptBrowserTest() override {}
+
+ void SetUp() override {
+ // Must start listening (And get a port for the proxy) before calling
+ // SetUp().
+ ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+ InProcessBrowserTest::SetUp();
+ }
+
+ void TearDown() override {
+ // Need to stop this before |connection_listener_| is destroyed.
+ EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+ InProcessBrowserTest::TearDown();
+ }
+
+ void SetUpOnMainThread() override {
+ // This must be created after the main message loop has been set up.
+ // Waits for one connection. Additional connections are fine.
+ connection_listener_ =
+ std::make_unique<net::test_server::SimpleConnectionListener>(
+ 1, net::test_server::SimpleConnectionListener::
+ ALLOW_ADDITIONAL_CONNECTIONS);
+ embedded_test_server()->SetConnectionListener(connection_listener_.get());
+ embedded_test_server()->StartAcceptingConnections();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII(
+ switches::kProxyPacUrl, embedded_test_server()->GetURL("/hung").spec());
+ }
+
+ protected:
+ std::unique_ptr<net::test_server::SimpleConnectionListener>
+ connection_listener_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HangingPacRequestProxyScriptBrowserTest);
+};
+
+// Check that the URLRequest for a PAC that is still alive during shutdown is
+// safely cleaned up. This test relies on AssertNoURLRequests being called on
+// the main URLRequestContext.
+IN_PROC_BROWSER_TEST_F(HangingPacRequestProxyScriptBrowserTest, Shutdown) {
+ // Request that should hang while trying to request the PAC script.
+ // Enough requests are created on startup that this probably isn't needed, but
+ // best to be safe.
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = GURL("http://blah/");
+ auto simple_loader = network::SimpleURLLoader::Create(
+ std::move(resource_request), TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ auto* storage_partition =
+ content::BrowserContext::GetDefaultStoragePartition(browser()->profile());
+ auto url_loader_factory =
+ storage_partition->GetURLLoaderFactoryForBrowserProcess();
+ simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ url_loader_factory.get(),
+ base::BindOnce([](std::unique_ptr<std::string> body) {
+ ADD_FAILURE() << "This request should never complete.";
+ }));
+
+ connection_listener_->WaitForConnections();
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/net/proxy_config_monitor.cc b/chromium/chrome/browser/net/proxy_config_monitor.cc
new file mode 100644
index 00000000000..342db7adee5
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_config_monitor.cc
@@ -0,0 +1,158 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/proxy_config_monitor.h"
+
+#include <utility>
+
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/proxy_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#endif // defined(OS_CHROMEOS)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/api/proxy/proxy_api.h"
+#endif
+
+using content::BrowserThread;
+
+ProxyConfigMonitor::ProxyConfigMonitor(Profile* profile) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(profile);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ profile_ = profile;
+#endif
+
+// If this is the ChromeOS sign-in profile, just create the tracker from global
+// state.
+#if defined(OS_CHROMEOS)
+ if (chromeos::ProfileHelper::IsSigninProfile(profile)) {
+ pref_proxy_config_tracker_ =
+ ProxyServiceFactory::CreatePrefProxyConfigTrackerOfLocalState(
+ g_browser_process->local_state());
+ }
+#endif // defined(OS_CHROMEOS)
+
+ if (!pref_proxy_config_tracker_) {
+ pref_proxy_config_tracker_ =
+ ProxyServiceFactory::CreatePrefProxyConfigTrackerOfProfile(
+ profile->GetPrefs(), g_browser_process->local_state());
+ }
+
+ proxy_config_service_ = ProxyServiceFactory::CreateProxyConfigService(
+ pref_proxy_config_tracker_.get());
+
+ proxy_config_service_->AddObserver(this);
+}
+
+ProxyConfigMonitor::ProxyConfigMonitor(PrefService* local_state) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+
+ pref_proxy_config_tracker_ =
+ ProxyServiceFactory::CreatePrefProxyConfigTrackerOfLocalState(
+ local_state);
+
+ proxy_config_service_ = ProxyServiceFactory::CreateProxyConfigService(
+ pref_proxy_config_tracker_.get());
+
+ proxy_config_service_->AddObserver(this);
+}
+
+ProxyConfigMonitor::~ProxyConfigMonitor() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+ proxy_config_service_->RemoveObserver(this);
+ pref_proxy_config_tracker_->DetachFromPrefService();
+}
+
+void ProxyConfigMonitor::AddToNetworkContextParams(
+ network::mojom::NetworkContextParams* network_context_params) {
+ mojo::PendingRemote<network::mojom::ProxyConfigClient> proxy_config_client;
+ network_context_params->proxy_config_client_receiver =
+ proxy_config_client.InitWithNewPipeAndPassReceiver();
+ proxy_config_client_set_.Add(std::move(proxy_config_client));
+
+ poller_receiver_set_.Add(this,
+ network_context_params->proxy_config_poller_client
+ .InitWithNewPipeAndPassReceiver());
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ error_receiver_set_.Add(this, network_context_params->proxy_error_client
+ .InitWithNewPipeAndPassReceiver());
+#endif
+
+ net::ProxyConfigWithAnnotation proxy_config;
+ net::ProxyConfigService::ConfigAvailability availability =
+ proxy_config_service_->GetLatestProxyConfig(&proxy_config);
+ if (availability != net::ProxyConfigService::CONFIG_PENDING)
+ network_context_params->initial_proxy_config = proxy_config;
+}
+
+void ProxyConfigMonitor::FlushForTesting() {
+ proxy_config_client_set_.FlushForTesting();
+}
+
+void ProxyConfigMonitor::OnProxyConfigChanged(
+ const net::ProxyConfigWithAnnotation& config,
+ net::ProxyConfigService::ConfigAvailability availability) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+ for (const auto& proxy_config_client : proxy_config_client_set_) {
+ switch (availability) {
+ case net::ProxyConfigService::CONFIG_VALID:
+ proxy_config_client->OnProxyConfigUpdated(config);
+ break;
+ case net::ProxyConfigService::CONFIG_UNSET:
+ proxy_config_client->OnProxyConfigUpdated(
+ net::ProxyConfigWithAnnotation::CreateDirect());
+ break;
+ case net::ProxyConfigService::CONFIG_PENDING:
+ NOTREACHED();
+ break;
+ }
+ }
+}
+
+void ProxyConfigMonitor::OnLazyProxyConfigPoll() {
+ proxy_config_service_->OnLazyPoll();
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+void ProxyConfigMonitor::OnPACScriptError(int32_t line_number,
+ const std::string& details) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ extensions::ProxyEventRouter::GetInstance()->OnPACScriptError(
+ g_browser_process->extension_event_router_forwarder(), profile_,
+ line_number, base::UTF8ToUTF16(details));
+}
+
+void ProxyConfigMonitor::OnRequestMaybeFailedDueToProxySettings(
+ int32_t net_error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+
+ if (net_error >= 0) {
+ // If the error is obviously wrong, don't dispatch it to extensions. If the
+ // PAC executor process is compromised, then |net_error| could be attacker
+ // controlled.
+ return;
+ }
+
+ extensions::ProxyEventRouter::GetInstance()->OnProxyError(
+ g_browser_process->extension_event_router_forwarder(), profile_,
+ net_error);
+}
+#endif
diff --git a/chromium/chrome/browser/net/proxy_config_monitor.h b/chromium/chrome/browser/net/proxy_config_monitor.h
new file mode 100644
index 00000000000..abffb6dbdbf
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_config_monitor.h
@@ -0,0 +1,100 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROXY_CONFIG_MONITOR_H_
+#define CHROME_BROWSER_NET_PROXY_CONFIG_MONITOR_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "build/buildflag.h"
+#include "extensions/buildflags/buildflags.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "net/proxy_resolution/proxy_config_service.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+#include "services/network/public/mojom/network_service.mojom-forward.h"
+#include "services/network/public/mojom/proxy_config.mojom-forward.h"
+#include "services/network/public/mojom/proxy_config_with_annotation.mojom.h"
+
+namespace net {
+class ProxyConfigWithAnnotation;
+}
+
+class Profile;
+class PrefProxyConfigTracker;
+class PrefService;
+
+// Tracks the ProxyConfig to use, and passes any updates to a NetworkContext's
+// ProxyConfigClient. This also responds to errors related to proxy settings
+// from the NetworkContext, and forwards them any listening Chrome extensions
+// associated with the profile.
+class ProxyConfigMonitor : public net::ProxyConfigService::Observer,
+ public network::mojom::ProxyConfigPollerClient
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ,
+ public network::mojom::ProxyErrorClient
+#endif
+
+{
+ public:
+ // Creates a ProxyConfigMonitor that gets proxy settings from |profile| and
+ // watches for changes. The created ProxyConfigMonitor must be destroyed
+ // before |profile|.
+ explicit ProxyConfigMonitor(Profile* profile);
+
+ // Creates a ProxyConfigMonitor that gets proxy settings from the
+ // |local_state|, for use with NetworkContexts not
+ // associated with a profile. Must be destroyed before |local_state|.
+ explicit ProxyConfigMonitor(PrefService* local_state);
+
+ ~ProxyConfigMonitor() override;
+
+ // Populates proxy-related fields of |network_context_params|. Updated
+ // ProxyConfigs will be sent to a NetworkContext created with those params
+ // whenever the configuration changes. Can be called more than once to inform
+ // multiple NetworkContexts of proxy changes.
+ void AddToNetworkContextParams(
+ network::mojom::NetworkContextParams* network_context_params);
+
+ // Flushes all pending data on the pipe, blocking the current thread until
+ // they're received, to allow tests to wait until all pending proxy
+ // configuration changes have been applied.
+ void FlushForTesting();
+
+ private:
+ // net::ProxyConfigService::Observer implementation:
+ void OnProxyConfigChanged(
+ const net::ProxyConfigWithAnnotation& config,
+ net::ProxyConfigService::ConfigAvailability availability) override;
+
+ // network::mojom::ProxyConfigPollerClient implementation:
+ void OnLazyProxyConfigPoll() override;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // network::mojom::ProxyErrorClient implementation:
+ void OnPACScriptError(int32_t line_number,
+ const std::string& details) override;
+ void OnRequestMaybeFailedDueToProxySettings(int32_t net_error) override;
+#endif
+
+ std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
+ // Monitors global and Profile prefs related to proxy configuration.
+ std::unique_ptr<PrefProxyConfigTracker> pref_proxy_config_tracker_;
+
+ mojo::ReceiverSet<network::mojom::ProxyConfigPollerClient>
+ poller_receiver_set_;
+
+ mojo::RemoteSet<network::mojom::ProxyConfigClient> proxy_config_client_set_;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ mojo::ReceiverSet<network::mojom::ProxyErrorClient> error_receiver_set_;
+ Profile* profile_ = nullptr;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyConfigMonitor);
+};
+
+#endif // CHROME_BROWSER_NET_PROXY_CONFIG_MONITOR_H_
diff --git a/chromium/chrome/browser/net/proxy_service_factory.cc b/chromium/chrome/browser/net/proxy_service_factory.cc
new file mode 100644
index 00000000000..5b436ce90ee
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_service_factory.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/proxy_service_factory.h"
+
+#include <utility>
+
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/proxy_resolution/proxy_config_service.h"
+#include "net/proxy_resolution/proxy_resolution_service.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/network/proxy/proxy_config_service_impl.h"
+#endif // defined(OS_CHROMEOS)
+
+using content::BrowserThread;
+
+// static
+std::unique_ptr<net::ProxyConfigService>
+ProxyServiceFactory::CreateProxyConfigService(PrefProxyConfigTracker* tracker) {
+ // The linux gsettings-based proxy settings getter relies on being initialized
+ // from the UI thread. The system proxy config service could also get created
+ // without full browser process by launching service manager alone.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+
+ std::unique_ptr<net::ProxyConfigService> base_service;
+
+#if !defined(OS_CHROMEOS)
+ // On ChromeOS, base service is NULL; chromeos::ProxyConfigServiceImpl
+ // determines the effective proxy config to take effect in the network layer,
+ // be it from prefs or system (which is network shill on chromeos).
+
+ // For other platforms, create a baseline service that provides proxy
+ // configuration in case nothing is configured through prefs (Note: prefs
+ // include command line and configuration policy).
+
+ base_service = net::ProxyResolutionService::CreateSystemProxyConfigService(
+ base::ThreadTaskRunnerHandle::Get());
+#endif // !defined(OS_CHROMEOS)
+
+ return tracker->CreateTrackingProxyConfigService(std::move(base_service));
+}
+
+// static
+std::unique_ptr<PrefProxyConfigTracker>
+ProxyServiceFactory::CreatePrefProxyConfigTrackerOfProfile(
+ PrefService* profile_prefs,
+ PrefService* local_state_prefs) {
+#if defined(OS_CHROMEOS)
+ return std::make_unique<chromeos::ProxyConfigServiceImpl>(
+ profile_prefs, local_state_prefs, nullptr);
+#else
+ return std::make_unique<PrefProxyConfigTrackerImpl>(profile_prefs, nullptr);
+#endif // defined(OS_CHROMEOS)
+}
+
+// static
+std::unique_ptr<PrefProxyConfigTracker>
+ProxyServiceFactory::CreatePrefProxyConfigTrackerOfLocalState(
+ PrefService* local_state_prefs) {
+#if defined(OS_CHROMEOS)
+ return std::make_unique<chromeos::ProxyConfigServiceImpl>(
+ nullptr, local_state_prefs, nullptr);
+#else
+ return std::make_unique<PrefProxyConfigTrackerImpl>(local_state_prefs,
+ nullptr);
+#endif // defined(OS_CHROMEOS)
+}
diff --git a/chromium/chrome/browser/net/proxy_service_factory.h b/chromium/chrome/browser/net/proxy_service_factory.h
new file mode 100644
index 00000000000..5f35c19efc1
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_service_factory.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROXY_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_NET_PROXY_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+class PrefProxyConfigTracker;
+class PrefService;
+
+namespace net {
+class ProxyConfigService;
+}
+
+class ProxyServiceFactory {
+ public:
+ // Creates a ProxyConfigService that delivers the system preferences
+ // (or the respective ChromeOS equivalent).
+ static std::unique_ptr<net::ProxyConfigService> CreateProxyConfigService(
+ PrefProxyConfigTracker* tracker);
+
+ // Creates a PrefProxyConfigTracker that tracks preferences of a
+ // profile. On ChromeOS it additionaly tracks local state for shared proxy
+ // settings. This tracker should be used if the profile's preferences should
+ // be respected. On ChromeOS's signin screen this is for example not the case.
+ static std::unique_ptr<PrefProxyConfigTracker>
+ CreatePrefProxyConfigTrackerOfProfile(PrefService* profile_prefs,
+ PrefService* local_state_prefs);
+
+ // Creates a PrefProxyConfigTracker that tracks local state only. This tracker
+ // should be used for the system request context and the signin screen
+ // (ChromeOS only).
+ static std::unique_ptr<PrefProxyConfigTracker>
+ CreatePrefProxyConfigTrackerOfLocalState(PrefService* local_state_prefs);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ProxyServiceFactory);
+};
+
+#endif // CHROME_BROWSER_NET_PROXY_SERVICE_FACTORY_H_
diff --git a/chromium/chrome/browser/net/proxy_test_utils.cc b/chromium/chrome/browser/net/proxy_test_utils.cc
new file mode 100644
index 00000000000..e26cec4d40f
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_test_utils.cc
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/proxy_test_utils.h"
+
+#include "base/test/bind_test_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "google_apis/gaia/gaia_urls.h"
+
+ProxyBrowserTest::ProxyBrowserTest()
+ : proxy_server_(net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+ base::FilePath()) {}
+
+ProxyBrowserTest::~ProxyBrowserTest() {}
+
+void ProxyBrowserTest::SetUp() {
+ ASSERT_TRUE(proxy_server_.Start());
+ // Block the GaiaAuthFetcher related requests, they somehow interfere with
+ // the test when the network service is running.
+ url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>(
+ base::BindLambdaForTesting(
+ [&](content::URLLoaderInterceptor::RequestParams* params) -> bool {
+ if (params->url_request.url.host() ==
+ GaiaUrls::GetInstance()->gaia_url().host()) {
+ return true;
+ }
+ return false;
+ }));
+ InProcessBrowserTest::SetUp();
+}
+
+void ProxyBrowserTest::PostRunTestOnMainThread() {
+ url_loader_interceptor_.reset();
+ InProcessBrowserTest::PostRunTestOnMainThread();
+}
+
+void ProxyBrowserTest::SetUpCommandLine(base::CommandLine* command_line) {
+ command_line->AppendSwitchASCII(switches::kProxyServer,
+ proxy_server_.host_port_pair().ToString());
+
+ // TODO(https://crbug.com/901896): Don't rely on proxying localhost (Relied
+ // on by BasicAuthWSConnect)
+ command_line->AppendSwitchASCII(
+ switches::kProxyBypassList,
+ net::ProxyBypassRules::GetRulesToSubtractImplicit());
+}
diff --git a/chromium/chrome/browser/net/proxy_test_utils.h b/chromium/chrome/browser/net/proxy_test_utils.h
new file mode 100644
index 00000000000..22ac775495a
--- /dev/null
+++ b/chromium/chrome/browser/net/proxy_test_utils.h
@@ -0,0 +1,33 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROXY_TEST_UTILS_H_
+#define CHROME_BROWSER_NET_PROXY_TEST_UTILS_H_
+
+#include "chrome/test/base/in_process_browser_test.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+namespace content {
+class URLLoaderInterceptor;
+}
+
+class ProxyBrowserTest : public InProcessBrowserTest {
+ public:
+ ProxyBrowserTest();
+ ~ProxyBrowserTest() override;
+
+ // InProcessBrowserTest::
+ void SetUp() override;
+ void PostRunTestOnMainThread() override;
+ void SetUpCommandLine(base::CommandLine* command_line) override;
+
+ protected:
+ net::SpawnedTestServer proxy_server_;
+
+ private:
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+ DISALLOW_COPY_AND_ASSIGN(ProxyBrowserTest);
+};
+
+#endif // CHROME_BROWSER_NET_PROXY_TEST_UTILS_H_
diff --git a/chromium/chrome/browser/net/referrer.cc b/chromium/chrome/browser/net/referrer.cc
new file mode 100644
index 00000000000..ea3a6550718
--- /dev/null
+++ b/chromium/chrome/browser/net/referrer.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/referrer.h"
+
+#include <limits.h>
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace chrome_browser_net {
+
+//------------------------------------------------------------------------------
+// Smoothing parameter for updating subresource_use_rate_.
+
+// We always combine our old expected value, weighted by some factor W (we use
+// kWeightingForOldConnectsExpectedValue), with the new expected value Enew.
+// The new "expected value" is the number of actual connections made due to the
+// current navigations.
+// That means that IF we end up needing to connect, we should apply the formula:
+// Eupdated = Eold * W + Enew * (1 - W)
+// If we visit the containing url, but don't end up needing a connection, then
+// Enew == 0, so we use the formula:
+// Eupdated = Eold * W
+// To achieve the above updating algorithm, we end up doing the multiplication
+// by W every time we contemplate doing a preconnection (i.e., when we navigate
+// to the containing URL, and consider doing a preconnection), and then IFF we
+// learn that we really needed a connection to the subresource, we complete the
+// above algorithm by adding the (1 - W) for each connection we make.
+
+// We weight the new expected value by a factor which is in the range of 0.0 to
+// 1.0.
+static const double kWeightingForOldConnectsExpectedValue = 0.66;
+
+// To estimate the expected value of the number of connections that we'll need
+// when a referrer is navigated to, we start with the following low initial
+// value.
+// Each time we do indeed (again) need the subresource, this value will get
+// increased.
+// Each time we navigate to the refererrer but never end up needing this
+// subresource, the value will decrease.
+// Very conservative is 0.0, which will mean that we have to wait for a while
+// before doing much speculative acvtivity. We do persist results, so we'll
+// save the asymptotic (correct?) learned answer in the long run.
+// Some browsers blindly make 2 connections all the time, so we'll use that as
+// a starting point.
+static const double kInitialConnectsExpectedValue = 2.0;
+
+Referrer::Referrer() : use_count_(1) {}
+
+void Referrer::SuggestHost(const GURL& url) {
+ // Limit how large our list can get, in case we make mistakes about what
+ // hostnames are in sub-resources (example: Some advertisments have a link to
+ // the ad agency, and then provide a "surprising" redirect to the advertised
+ // entity, which then (mistakenly) appears to be a subresource on the page
+ // hosting the ad).
+ // TODO(jar): Do experiments to optimize the max count of suggestions.
+ static const size_t kMaxSuggestions = 10;
+
+ if (!url.has_host()) // TODO(jar): Is this really needed????
+ return;
+ DCHECK(url == url.GetWithEmptyPath());
+ auto it = find(url);
+ if (it != end()) {
+ it->second.SubresourceIsNeeded();
+ return;
+ }
+
+ if (kMaxSuggestions <= size()) {
+ DeleteLeastUseful();
+ DCHECK(kMaxSuggestions > size());
+ }
+ (*this)[url].SubresourceIsNeeded();
+}
+
+void Referrer::DeleteLeastUseful() {
+ // Find the item with the lowest value. Most important is preconnection_rate,
+ // and least is lifetime (age).
+ GURL least_useful_url;
+ double lowest_rate_seen = 0.0;
+ // We use longs for durations because we will use multiplication on them.
+ int64_t least_useful_lifetime = 0; // Duration in milliseconds.
+
+ const base::Time kNow(base::Time::Now()); // Avoid multiple calls.
+ for (auto it = begin(); it != end(); ++it) {
+ int64_t lifetime = (kNow - it->second.birth_time()).InMilliseconds();
+ double rate = it->second.subresource_use_rate();
+ if (least_useful_url.has_host()) {
+ if (rate > lowest_rate_seen)
+ continue;
+ if (lifetime <= least_useful_lifetime)
+ continue;
+ }
+ least_useful_url = it->first;
+ lowest_rate_seen = rate;
+ least_useful_lifetime = lifetime;
+ }
+ if (least_useful_url.has_host())
+ erase(least_useful_url);
+}
+
+void Referrer::Deserialize(const base::Value& value) {
+ if (value.type() != base::Value::Type::LIST)
+ return;
+ const base::ListValue* subresource_list(
+ static_cast<const base::ListValue*>(&value));
+ size_t index = 0; // Bounds checking is done by subresource_list->Get*().
+ while (true) {
+ std::string url_spec;
+ if (!subresource_list->GetString(index++, &url_spec))
+ return;
+ double rate;
+ if (!subresource_list->GetDouble(index++, &rate))
+ return;
+
+ GURL url(url_spec);
+ // TODO(jar): We could be more direct, and change birth date or similar to
+ // show that this is a resurrected value we're adding in. I'm not yet sure
+ // of how best to optimize the learning and pruning (Trim) algorithm at this
+ // level, so for now, we just suggest subresources, which leaves them all
+ // with the same birth date (typically start of process).
+ SuggestHost(url);
+ (*this)[url].SetSubresourceUseRate(rate);
+ }
+}
+
+std::unique_ptr<base::ListValue> Referrer::Serialize() const {
+ auto subresource_list = std::make_unique<base::ListValue>();
+ for (auto it = begin(); it != end(); ++it) {
+ subresource_list->AppendString(it->first.spec());
+ subresource_list->AppendDouble(it->second.subresource_use_rate());
+ }
+ return subresource_list;
+}
+
+//------------------------------------------------------------------------------
+
+ReferrerValue::ReferrerValue()
+ : birth_time_(base::Time::Now()),
+ navigation_count_(0),
+ preconnection_count_(0),
+ preresolution_count_(0),
+ subresource_use_rate_(kInitialConnectsExpectedValue) {
+}
+
+void ReferrerValue::SubresourceIsNeeded() {
+ DCHECK_GE(kWeightingForOldConnectsExpectedValue, 0);
+ DCHECK_LE(kWeightingForOldConnectsExpectedValue, 1.0);
+ ++navigation_count_;
+ subresource_use_rate_ += 1 - kWeightingForOldConnectsExpectedValue;
+}
+
+void ReferrerValue::ReferrerWasObserved() {
+ subresource_use_rate_ *= kWeightingForOldConnectsExpectedValue;
+ // Note: the use rate is temporarilly possibly incorect, as we need to find
+ // out if we really end up connecting. This will happen in a few hundred
+ // milliseconds (when content arrives, etc.).
+ // Value of subresource_use_rate_ should be sampled before this call.
+}
+
+} // namespace chrome_browser_net
diff --git a/chromium/chrome/browser/net/referrer.h b/chromium/chrome/browser/net/referrer.h
new file mode 100644
index 00000000000..626ddc4f39f
--- /dev/null
+++ b/chromium/chrome/browser/net/referrer.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This class helps to remember what domains may be needed to be resolved when a
+// navigation takes place to a given URL. This information is gathered when a
+// navigation to a subresource identifies a referring URL.
+// When future navigations take place to known referrer sites, then we
+// speculatively either pre-warm a TCP/IP conneciton, or at a minimum, resolve
+// the host name via DNS.
+
+// All access to this class is performed via the Predictor class, which only
+// operates on the IO thread.
+
+#ifndef CHROME_BROWSER_NET_REFERRER_H_
+#define CHROME_BROWSER_NET_REFERRER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "url/gurl.h"
+
+namespace chrome_browser_net {
+
+//------------------------------------------------------------------------------
+// For each hostname in a Referrer, we have a ReferrerValue. It indicates
+// exactly how much value (re: latency reduction, or connection use) has
+// resulted from having this entry.
+class ReferrerValue {
+ public:
+ ReferrerValue();
+
+ // Used during deserialization.
+ void SetSubresourceUseRate(double rate) { subresource_use_rate_ = rate; }
+
+ base::Time birth_time() const { return birth_time_; }
+
+ // Record the fact that we navigated to the associated subresource URL. This
+ // will increase the value of the expected subresource_use_rate_
+ void SubresourceIsNeeded();
+
+ // Record the fact that the referrer of this subresource was observed. This
+ // will diminish the expected subresource_use_rate_ (and will only be
+ // counteracted later if we really needed this subresource as a consequence
+ // of our associated referrer.)
+ void ReferrerWasObserved();
+
+ int64_t navigation_count() const { return navigation_count_; }
+ double subresource_use_rate() const { return subresource_use_rate_; }
+
+ int64_t preconnection_count() const { return preconnection_count_; }
+ void IncrementPreconnectionCount() { ++preconnection_count_; }
+
+ int64_t preresolution_count() const { return preresolution_count_; }
+ void preresolution_increment() { ++preresolution_count_; }
+
+ private:
+ const base::Time birth_time_;
+
+ // The number of times this item was navigated to with the fixed referrer.
+ int64_t navigation_count_;
+
+ // The number of times this item was preconnected as a consequence of its
+ // referrer.
+ int64_t preconnection_count_;
+
+ // The number of times this item was pre-resolved (via DNS) as a consequence
+ // of its referrer.
+ int64_t preresolution_count_;
+
+ // A smoothed estimate of the expected number of connections that will be made
+ // to this subresource.
+ double subresource_use_rate_;
+};
+
+//------------------------------------------------------------------------------
+// A list of domain names to pre-resolve. The names are the keys to this map,
+// and the values indicate the amount of benefit derived from having each name
+// around.
+typedef std::map<GURL, ReferrerValue> SubresourceMap;
+
+//------------------------------------------------------------------------------
+// There is one Referrer instance for each hostname that has acted as an HTTP
+// referer (note mispelling is intentional) for a hostname that was otherwise
+// unexpectedly navgated towards ("unexpected" in the sense that the hostname
+// was probably needed as a subresource of a page, and was not otherwise
+// predictable until the content with the reference arrived). Most typically,
+// an outer page was a page fetched by the user, and this instance lists names
+// in SubresourceMap which are subresources and that were needed to complete the
+// rendering of the outer page.
+class Referrer : public SubresourceMap {
+ public:
+ Referrer();
+ void IncrementUseCount() { ++use_count_; }
+ int64_t use_count() const { return use_count_; }
+
+ // Add the indicated url to the list that are resolved via DNS when the user
+ // navigates to this referrer. Note that if the list is long, an entry may be
+ // discarded to make room for this insertion.
+ void SuggestHost(const GURL& url);
+
+ // Provide methods for persisting, and restoring contents into a Value class.
+ std::unique_ptr<base::ListValue> Serialize() const;
+ void Deserialize(const base::Value& referrers);
+
+ private:
+ // Helper function for pruning list. Metric for usefulness is "large accrued
+ // value," in the form of latency_ savings associated with a host name. We
+ // also give credit for a name being newly added, by scalling latency per
+ // lifetime (time since birth). For instance, when two names have accrued
+ // the same latency_ savings, the older one is less valuable as it didn't
+ // accrue savings as quickly.
+ void DeleteLeastUseful();
+
+ // The number of times this referer had its subresources scanned for possible
+ // preconnection or DNS preresolution.
+ int64_t use_count_;
+
+ // We put these into a std::map<>, so we need copy constructors.
+ // DISALLOW_COPY_AND_ASSIGN(Referrer);
+ // TODO(jar): Consider optimization to use pointers to these instances, and
+ // avoid deep copies during re-alloc of the containing map.
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_REFERRER_H_
diff --git a/chromium/chrome/browser/net/reporting_browsertest.cc b/chromium/chrome/browser/net/reporting_browsertest.cc
new file mode 100644
index 00000000000..03eed3f603a
--- /dev/null
+++ b/chromium/chrome/browser/net/reporting_browsertest.cc
@@ -0,0 +1,268 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/values_test_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/ssl/cert_verifier_browser_test.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/result_codes.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "services/network/public/cpp/features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class ReportingBrowserTest : public CertVerifierBrowserTest {
+ public:
+ ReportingBrowserTest()
+ : https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
+ ~ReportingBrowserTest() override = default;
+
+ void SetUp() override;
+ void SetUpOnMainThread() override;
+
+ net::EmbeddedTestServer* server() { return &https_server_; }
+ int port() const { return https_server_.port(); }
+
+ net::test_server::ControllableHttpResponse* original_response() {
+ return original_response_.get();
+ }
+
+ net::test_server::ControllableHttpResponse* upload_response() {
+ return upload_response_.get();
+ }
+
+ GURL GetReportingEnabledURL() const {
+ return GURL(base::StringPrintf("https://example.com:%d/original", port()));
+ }
+
+ GURL GetCollectorURL() const {
+ return GURL(base::StringPrintf("https://example.com:%d/upload", port()));
+ }
+
+ std::string GetReportToHeader() const {
+ return "Report-To: {\"endpoints\":[{\"url\":\"" + GetCollectorURL().spec() +
+ "\"}],\"max_age\":86400}\r\n";
+ }
+
+ std::string GetNELHeader() const {
+ return "NEL: "
+ "{\"report_to\":\"default\",\"max_age\":86400,\"success_fraction\":"
+ "1.0}\r\n";
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+ net::EmbeddedTestServer https_server_;
+ std::unique_ptr<net::test_server::ControllableHttpResponse>
+ original_response_;
+ std::unique_ptr<net::test_server::ControllableHttpResponse> upload_response_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingBrowserTest);
+};
+
+void ReportingBrowserTest::SetUp() {
+ scoped_feature_list_.InitWithFeatures(
+ {network::features::kReporting, network::features::kNetworkErrorLogging,
+ features::kCrashReporting},
+ {});
+ CertVerifierBrowserTest::SetUp();
+
+ // Make report delivery happen instantly.
+ net::ReportingPolicy policy;
+ policy.delivery_interval = base::TimeDelta::FromSeconds(0);
+ net::ReportingPolicy::UsePolicyForTesting(policy);
+}
+
+void ReportingBrowserTest::SetUpOnMainThread() {
+ CertVerifierBrowserTest::SetUpOnMainThread();
+
+ host_resolver()->AddRule("*", "127.0.0.1");
+
+ original_response_ =
+ std::make_unique<net::test_server::ControllableHttpResponse>(server(),
+ "/original");
+ upload_response_ =
+ std::make_unique<net::test_server::ControllableHttpResponse>(server(),
+ "/upload");
+
+ // Reporting and NEL will ignore configurations headers if the response
+ // doesn't come from an HTTPS origin, or if the origin's certificate is
+ // invalid. Our test certs are valid, so we need a mock certificate verifier
+ // to trick the Reporting stack into paying attention to our test headers.
+ mock_cert_verifier()->set_default_result(net::OK);
+ ASSERT_TRUE(server()->Start());
+}
+
+std::unique_ptr<base::Value> ParseReportUpload(const std::string& payload) {
+ auto parsed_payload = base::test::ParseJsonDeprecated(payload);
+ // Clear out any non-reproducible fields.
+ for (auto& report : parsed_payload->GetList()) {
+ report.RemoveKey("age");
+ report.RemovePath({"body", "elapsed_time"});
+ auto* user_agent =
+ report.FindKeyOfType("user_agent", base::Value::Type::STRING);
+ if (user_agent != nullptr)
+ *user_agent = base::Value("Mozilla/1.0");
+ }
+ return parsed_payload;
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(ReportingBrowserTest, TestReportingHeadersProcessed) {
+ NavigateParams params(browser(), GetReportingEnabledURL(),
+ ui::PAGE_TRANSITION_LINK);
+ Navigate(&params);
+
+ original_response()->WaitForRequest();
+ original_response()->Send("HTTP/1.1 204 OK\r\n");
+ original_response()->Send(GetReportToHeader());
+ original_response()->Send(GetNELHeader());
+ original_response()->Send("\r\n");
+ original_response()->Done();
+
+ upload_response()->WaitForRequest();
+ auto actual = ParseReportUpload(upload_response()->http_request()->content);
+ upload_response()->Send("HTTP/1.1 204 OK\r\n");
+ upload_response()->Send("\r\n");
+ upload_response()->Done();
+
+ // Verify the contents of the report that we received.
+ EXPECT_TRUE(actual != nullptr);
+ auto expected = base::test::ParseJsonDeprecated(base::StringPrintf(
+ R"json(
+ [
+ {
+ "body": {
+ "protocol": "http/1.1",
+ "referrer": "",
+ "sampling_fraction": 1.0,
+ "server_ip": "127.0.0.1",
+ "method": "GET",
+ "status_code": 204,
+ "phase": "application",
+ "type": "ok",
+ },
+ "type": "network-error",
+ "url": "https://example.com:%d/original",
+ "user_agent": "Mozilla/1.0",
+ },
+ ]
+ )json",
+ port()));
+ EXPECT_EQ(*expected, *actual);
+}
+
+// These tests intentionally crash a render process, and so fail ASan tests.
+// Flaky timeouts on Win7 Tests (dbg)(1); see https://crbug.com/985255.
+#if defined(ADDRESS_SANITIZER) || (defined(OS_WIN) && !defined(NDEBUG))
+#define MAYBE_CrashReport DISABLED_CrashReport
+#define MAYBE_CrashReportUnresponsive DISABLED_CrashReportUnresponsive
+#else
+#define MAYBE_CrashReport CrashReport
+#define MAYBE_CrashReportUnresponsive CrashReportUnresponsive
+#endif
+
+IN_PROC_BROWSER_TEST_F(ReportingBrowserTest, MAYBE_CrashReport) {
+ content::WebContents* contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver navigation_observer(contents);
+
+ // Navigate to reporting-enabled page.
+ NavigateParams params(browser(), GetReportingEnabledURL(),
+ ui::PAGE_TRANSITION_LINK);
+ Navigate(&params);
+
+ original_response()->WaitForRequest();
+ original_response()->Send("HTTP/1.1 200 OK\r\n");
+ original_response()->Send(GetReportToHeader());
+ original_response()->Send("\r\n");
+ original_response()->Done();
+ navigation_observer.Wait();
+
+ // Simulate a crash on the page.
+ content::ScopedAllowRendererCrashes allow_renderer_crashes(contents);
+ contents->GetController().LoadURL(GURL(content::kChromeUICrashURL),
+ content::Referrer(),
+ ui::PAGE_TRANSITION_TYPED, std::string());
+
+ upload_response()->WaitForRequest();
+ auto response = ParseReportUpload(upload_response()->http_request()->content);
+ upload_response()->Send("HTTP/1.1 200 OK\r\n");
+ upload_response()->Send("\r\n");
+ upload_response()->Done();
+
+ // Verify the contents of the report that we received.
+ EXPECT_TRUE(response != nullptr);
+ auto report = response->GetList().begin();
+ auto* type = report->FindKeyOfType("type", base::Value::Type::STRING);
+ auto* url = report->FindKeyOfType("url", base::Value::Type::STRING);
+
+ EXPECT_EQ("crash", type->GetString());
+ EXPECT_EQ(base::StringPrintf("https://example.com:%d/original", port()),
+ url->GetString());
+}
+
+IN_PROC_BROWSER_TEST_F(ReportingBrowserTest, MAYBE_CrashReportUnresponsive) {
+ content::WebContents* contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver navigation_observer(contents);
+
+ // Navigate to reporting-enabled page.
+ NavigateParams params(browser(), GetReportingEnabledURL(),
+ ui::PAGE_TRANSITION_LINK);
+ Navigate(&params);
+
+ original_response()->WaitForRequest();
+ original_response()->Send("HTTP/1.1 200 OK\r\n");
+ original_response()->Send(GetReportToHeader());
+ original_response()->Send("\r\n");
+ original_response()->Done();
+ navigation_observer.Wait();
+
+ // Simulate the page being killed due to being unresponsive.
+ content::ScopedAllowRendererCrashes allow_renderer_crashes(contents);
+ contents->GetMainFrame()->GetProcess()->Shutdown(content::RESULT_CODE_HUNG);
+
+ upload_response()->WaitForRequest();
+ auto response = ParseReportUpload(upload_response()->http_request()->content);
+ upload_response()->Send("HTTP/1.1 200 OK\r\n");
+ upload_response()->Send("\r\n");
+ upload_response()->Done();
+
+ // Verify the contents of the report that we received.
+ EXPECT_TRUE(response != nullptr);
+ auto report = response->GetList().begin();
+ auto* type = report->FindKeyOfType("type", base::Value::Type::STRING);
+ auto* url = report->FindKeyOfType("url", base::Value::Type::STRING);
+ auto* body = report->FindKeyOfType("body", base::Value::Type::DICTIONARY);
+ auto* reason = body->FindKeyOfType("reason", base::Value::Type::STRING);
+
+ EXPECT_EQ("crash", type->GetString());
+ EXPECT_EQ(base::StringPrintf("https://example.com:%d/original", port()),
+ url->GetString());
+ EXPECT_EQ("unresponsive", reason->GetString());
+}
diff --git a/chromium/chrome/browser/net/secure_dns_policy_handler.cc b/chromium/chrome/browser/net/secure_dns_policy_handler.cc
new file mode 100644
index 00000000000..a8d2d92fbed
--- /dev/null
+++ b/chromium/chrome/browser/net/secure_dns_policy_handler.cc
@@ -0,0 +1,68 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/secure_dns_policy_handler.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/net/dns_util.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+SecureDnsPolicyHandler::SecureDnsPolicyHandler() {}
+
+SecureDnsPolicyHandler::~SecureDnsPolicyHandler() {}
+
+bool SecureDnsPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ const base::Value* mode = policies.GetValue(key::kDnsOverHttpsMode);
+ if (!mode)
+ return false;
+
+ std::string mode_str;
+ if (!mode->GetAsString(&mode_str)) {
+ errors->AddError(key::kDnsOverHttpsMode, IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::STRING));
+ return false;
+ } else if (mode_str.size() == 0) {
+ errors->AddError(key::kDnsOverHttpsMode, IDS_POLICY_NOT_SPECIFIED_ERROR);
+ return false;
+ } else if (mode_str == chrome_browser_net::kDnsOverHttpsModeSecure) {
+ errors->AddError(key::kDnsOverHttpsMode,
+ IDS_POLICY_SECURE_DNS_MODE_NOT_SUPPORTED_ERROR);
+ } else if (mode_str != chrome_browser_net::kDnsOverHttpsModeOff &&
+ mode_str != chrome_browser_net::kDnsOverHttpsModeAutomatic) {
+ errors->AddError(key::kDnsOverHttpsMode,
+ IDS_POLICY_INVALID_SECURE_DNS_MODE_ERROR);
+ return false;
+ }
+
+ // Try to apply any setting that is valid
+ return true;
+}
+
+void SecureDnsPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ const base::Value* mode = policies.GetValue(key::kDnsOverHttpsMode);
+
+ std::string mode_str = mode->GetString();
+ // TODO(http://crbug.com/955454): Include secure in conditional when
+ // support is implemented.
+ if (mode_str == chrome_browser_net::kDnsOverHttpsModeAutomatic) {
+ prefs->SetString(prefs::kDnsOverHttpsMode, mode_str);
+ } else {
+ // Captures "off" and "secure".
+ prefs->SetString(prefs::kDnsOverHttpsMode,
+ chrome_browser_net::kDnsOverHttpsModeOff);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/chrome/browser/net/secure_dns_policy_handler.h b/chromium/chrome/browser/net/secure_dns_policy_handler.h
new file mode 100644
index 00000000000..ce6e4eac526
--- /dev/null
+++ b/chromium/chrome/browser/net/secure_dns_policy_handler.h
@@ -0,0 +1,33 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_SECURE_DNS_POLICY_HANDLER_H_
+#define CHROME_BROWSER_NET_SECURE_DNS_POLICY_HANDLER_H_
+
+#include "base/macros.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+
+class PrefValueMap;
+
+namespace policy {
+
+// Handles DnsOverHttpsMode policy.
+class SecureDnsPolicyHandler : public ConfigurationPolicyHandler {
+ public:
+ SecureDnsPolicyHandler();
+ ~SecureDnsPolicyHandler() override;
+
+ // ConfigurationPolicyHandler methods:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SecureDnsPolicyHandler);
+};
+
+} // namespace policy
+
+#endif // CHROME_BROWSER_NET_SECURE_DNS_POLICY_HANDLER_H_
diff --git a/chromium/chrome/browser/net/secure_dns_policy_handler_unittest.cc b/chromium/chrome/browser/net/secure_dns_policy_handler_unittest.cc
new file mode 100644
index 00000000000..32f964eca9f
--- /dev/null
+++ b/chromium/chrome/browser/net/secure_dns_policy_handler_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/secure_dns_policy_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/net/dns_util.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+class SecureDnsPolicyHandlerTest : public testing::Test {
+ protected:
+ void SetPolicyValue(const std::string& policy,
+ std::unique_ptr<base::Value> value) {
+ policies_.Set(policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, std::move(value), nullptr);
+ }
+ bool CheckPolicySettings() {
+ return handler_.CheckPolicySettings(policies_, &errors_);
+ }
+ void ApplyPolicySettings() {
+ handler_.ApplyPolicySettings(policies_, &prefs_);
+ }
+
+ void CheckAndApplyPolicySettings() {
+ if (CheckPolicySettings())
+ ApplyPolicySettings();
+ }
+
+ PolicyErrorMap& errors() { return errors_; }
+ PrefValueMap& prefs() { return prefs_; }
+
+ private:
+ PolicyMap policies_;
+ PolicyErrorMap errors_;
+ PrefValueMap prefs_;
+ SecureDnsPolicyHandler handler_;
+};
+
+TEST_F(SecureDnsPolicyHandlerTest, PolicyNotSet) {
+ CheckAndApplyPolicySettings();
+
+ // Shouldn't error.
+ EXPECT_EQ(errors().size(), 0U);
+
+ // Pref should not be set.
+ const base::Value* pref_value;
+ EXPECT_FALSE(prefs().GetValue(prefs::kDnsOverHttpsMode, &pref_value));
+}
+
+TEST_F(SecureDnsPolicyHandlerTest, EmptyPolicyValue) {
+ SetPolicyValue(key::kDnsOverHttpsMode,
+ std::make_unique<base::Value>(std::string()));
+
+ CheckAndApplyPolicySettings();
+
+ // Should have an error
+ auto expected_error =
+ l10n_util::GetStringUTF16(IDS_POLICY_NOT_SPECIFIED_ERROR);
+ EXPECT_EQ(errors().size(), 1U);
+ EXPECT_EQ(errors().begin()->second, expected_error);
+
+ // Pref should not be set.
+ const base::Value* pref_value;
+ EXPECT_FALSE(prefs().GetValue(prefs::kDnsOverHttpsMode, &pref_value));
+}
+
+TEST_F(SecureDnsPolicyHandlerTest, InvalidPolicyValue) {
+ SetPolicyValue(key::kDnsOverHttpsMode,
+ std::make_unique<base::Value>("invalid"));
+
+ CheckAndApplyPolicySettings();
+
+ // Should have an error
+ auto expected_error =
+ l10n_util::GetStringUTF16(IDS_POLICY_INVALID_SECURE_DNS_MODE_ERROR);
+ EXPECT_EQ(errors().size(), 1U);
+ EXPECT_EQ(errors().begin()->second, expected_error);
+
+ // Pref should not be set.
+ const base::Value* pref_value;
+ EXPECT_FALSE(prefs().GetValue(prefs::kDnsOverHttpsMode, &pref_value));
+}
+
+TEST_F(SecureDnsPolicyHandlerTest, InvalidPolicyType) {
+ // Give an int to a string-enum policy.
+ SetPolicyValue(key::kDnsOverHttpsMode, std::make_unique<base::Value>(1));
+
+ CheckAndApplyPolicySettings();
+
+ // Should have an error
+ auto expected_error = l10n_util::GetStringFUTF16(
+ IDS_POLICY_TYPE_ERROR,
+ base::ASCIIToUTF16(base::Value::GetTypeName(base::Value::Type::STRING)));
+ EXPECT_EQ(errors().size(), 1U);
+ EXPECT_EQ(errors().begin()->second, expected_error);
+
+ // Pref should not be set.
+ const base::Value* pref_value;
+ EXPECT_FALSE(prefs().GetValue(prefs::kDnsOverHttpsMode, &pref_value));
+}
+
+// TODO(http://crbug.com/955454) This test should be removed once secure is a
+// valid policy value.
+TEST_F(SecureDnsPolicyHandlerTest, PolicyValueSecureShouldError) {
+ // Secure will eventually be a valid option, but for the moment it should
+ // error.
+ SetPolicyValue(key::kDnsOverHttpsMode,
+ std::make_unique<base::Value>(
+ chrome_browser_net::kDnsOverHttpsModeSecure));
+
+ CheckAndApplyPolicySettings();
+
+ // Should have an error
+ auto expected_error =
+ l10n_util::GetStringUTF16(IDS_POLICY_SECURE_DNS_MODE_NOT_SUPPORTED_ERROR);
+ EXPECT_EQ(errors().size(), 1U);
+ EXPECT_EQ(errors().begin()->second, expected_error);
+
+ std::string mode;
+ EXPECT_TRUE(prefs().GetString(prefs::kDnsOverHttpsMode, &mode));
+ // Pref should have changed to "off."
+ EXPECT_EQ(mode, chrome_browser_net::kDnsOverHttpsModeOff);
+}
+
+TEST_F(SecureDnsPolicyHandlerTest, ValidPolicyValueOff) {
+ const std::string test_policy_value =
+ chrome_browser_net::kDnsOverHttpsModeOff;
+
+ SetPolicyValue(key::kDnsOverHttpsMode,
+ std::make_unique<base::Value>(test_policy_value));
+
+ CheckAndApplyPolicySettings();
+
+ // Shouldn't error.
+ EXPECT_EQ(errors().size(), 0U);
+
+ std::string mode;
+ EXPECT_TRUE(prefs().GetString(prefs::kDnsOverHttpsMode, &mode));
+ // Pref should now be the test value.
+ EXPECT_EQ(mode, test_policy_value);
+}
+
+TEST_F(SecureDnsPolicyHandlerTest, ValidPolicyValueAutomatic) {
+ const std::string test_policy_value =
+ chrome_browser_net::kDnsOverHttpsModeAutomatic;
+
+ SetPolicyValue(key::kDnsOverHttpsMode,
+ std::make_unique<base::Value>(test_policy_value));
+
+ CheckAndApplyPolicySettings();
+
+ // Shouldn't error.
+ EXPECT_EQ(errors().size(), 0U);
+
+ std::string mode;
+ EXPECT_TRUE(prefs().GetString(prefs::kDnsOverHttpsMode, &mode));
+ // Pref should now be the test value.
+ EXPECT_EQ(mode, test_policy_value);
+}
+
+} // namespace policy
diff --git a/chromium/chrome/browser/net/service_providers_win.cc b/chromium/chrome/browser/net/service_providers_win.cc
new file mode 100644
index 00000000000..4eb2e4f1872
--- /dev/null
+++ b/chromium/chrome/browser/net/service_providers_win.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/service_providers_win.h"
+
+#include <winsock2.h>
+#include <Ws2spi.h>
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+
+WinsockLayeredServiceProvider::WinsockLayeredServiceProvider() {
+}
+
+WinsockLayeredServiceProvider::WinsockLayeredServiceProvider(
+ const WinsockLayeredServiceProvider& other) = default;
+
+WinsockLayeredServiceProvider::~WinsockLayeredServiceProvider() {
+}
+
+void GetWinsockNamespaceProviders(
+ WinsockNamespaceProviderList* namespace_list) {
+
+ // Find out how just how much memory is needed. If we get the expected error,
+ // the memory needed is written to size.
+ DWORD size = 0;
+ if (WSAEnumNameSpaceProviders(&size, NULL) != SOCKET_ERROR ||
+ GetLastError() != WSAEFAULT) {
+ NOTREACHED();
+ return;
+ }
+
+ std::unique_ptr<char[]> namespace_provider_bytes(new char[size]);
+ WSANAMESPACE_INFO* namespace_providers =
+ reinterpret_cast<WSANAMESPACE_INFO*>(namespace_provider_bytes.get());
+
+ int num_namespace_providers = WSAEnumNameSpaceProviders(&size,
+ namespace_providers);
+ if (num_namespace_providers == SOCKET_ERROR) {
+ NOTREACHED();
+ return;
+ }
+
+ for (int i = 0; i < num_namespace_providers; ++i) {
+ WinsockNamespaceProvider provider;
+
+ provider.name = namespace_providers[i].lpszIdentifier;
+ provider.active = TRUE == namespace_providers[i].fActive;
+ provider.version = namespace_providers[i].dwVersion;
+ provider.type = namespace_providers[i].dwNameSpace;
+
+ namespace_list->push_back(provider);
+ }
+}
+
+void GetWinsockLayeredServiceProviders(
+ WinsockLayeredServiceProviderList* service_list) {
+ // Find out how just how much memory is needed. If we get the expected error,
+ // the memory needed is written to size.
+ DWORD size = 0;
+ int error;
+ if (SOCKET_ERROR != WSCEnumProtocols(NULL, NULL, &size, &error) ||
+ error != WSAENOBUFS) {
+ NOTREACHED();
+ return;
+ }
+
+ std::unique_ptr<char[]> service_provider_bytes(new char[size]);
+ WSAPROTOCOL_INFOW* service_providers =
+ reinterpret_cast<WSAPROTOCOL_INFOW*>(service_provider_bytes.get());
+
+ int num_service_providers = WSCEnumProtocols(NULL, service_providers, &size,
+ &error);
+ if (num_service_providers == SOCKET_ERROR) {
+ NOTREACHED();
+ return;
+ }
+
+ for (int i = 0; i < num_service_providers; ++i) {
+ WinsockLayeredServiceProvider service_provider;
+
+ service_provider.name = service_providers[i].szProtocol;
+ service_provider.version = service_providers[i].iVersion;
+ service_provider.socket_type = service_providers[i].iSocketType;
+ service_provider.socket_protocol = service_providers[i].iProtocol;
+ service_provider.chain_length = service_providers[i].ProtocolChain.ChainLen;
+
+ // TODO(mmenke): Add categories under Vista and later.
+ // http://msdn.microsoft.com/en-us/library/ms742239%28v=VS.85%29.aspx
+
+ wchar_t path[MAX_PATH];
+ int path_length = base::size(path);
+ if (0 == WSCGetProviderPath(&service_providers[i].ProviderId, path,
+ &path_length, &error)) {
+ service_provider.path = path;
+ }
+
+ service_list->push_back(service_provider);
+ }
+
+ return;
+}
+
diff --git a/chromium/chrome/browser/net/service_providers_win.h b/chromium/chrome/browser/net/service_providers_win.h
new file mode 100644
index 00000000000..49994655315
--- /dev/null
+++ b/chromium/chrome/browser/net/service_providers_win.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_SERVICE_PROVIDERS_WIN_H_
+#define CHROME_BROWSER_NET_SERVICE_PROVIDERS_WIN_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+
+struct WinsockNamespaceProvider {
+ base::string16 name;
+ int version;
+ bool active;
+ int type;
+};
+typedef std::vector<WinsockNamespaceProvider> WinsockNamespaceProviderList;
+
+struct WinsockLayeredServiceProvider {
+ WinsockLayeredServiceProvider();
+ WinsockLayeredServiceProvider(const WinsockLayeredServiceProvider& other);
+ ~WinsockLayeredServiceProvider();
+
+ base::string16 name;
+ base::string16 path;
+ int version;
+ int chain_length;
+ int socket_type;
+ int socket_protocol;
+};
+typedef std::vector<WinsockLayeredServiceProvider>
+ WinsockLayeredServiceProviderList;
+
+// Returns all the Winsock namespace providers.
+void GetWinsockNamespaceProviders(WinsockNamespaceProviderList* namespace_list);
+
+// Returns all the Winsock layered service providers and their paths.
+void GetWinsockLayeredServiceProviders(
+ WinsockLayeredServiceProviderList* service_list);
+
+#endif // CHROME_BROWSER_NET_SERVICE_PROVIDERS_WIN_H_
diff --git a/chromium/chrome/browser/net/system_network_context_manager.cc b/chromium/chrome/browser/net/system_network_context_manager.cc
new file mode 100644
index 00000000000..d3abdb5cfb4
--- /dev/null
+++ b/chromium/chrome/browser/net/system_network_context_manager.cc
@@ -0,0 +1,863 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/system_network_context_manager.h"
+
+#include <algorithm>
+#include <set>
+#include <unordered_map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/build_time.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/process/process_handle.h"
+#include "base/sequence_checker.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/component_updater/crl_set_component_installer.h"
+#include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h"
+#include "chrome/browser/net/dns_util.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "chrome/browser/ssl/ssl_config_service_manager.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/certificate_transparency/ct_known_logs.h"
+#include "components/flags_ui/pref_service_flags_storage.h"
+#include "components/net_log/net_export_file_writer.h"
+#include "components/network_session_configurator/common/network_features.h"
+#include "components/os_crypt/os_crypt.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "components/variations/variations_associated_data.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/cors_exempt_headers.h"
+#include "content/public/browser/network_context_client_base.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/mime_handler_view_mode.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/common/user_agent.h"
+#include "crypto/sha2.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "net/base/features.h"
+#include "net/net_buildflags.h"
+#include "net/third_party/uri_template/uri_template.h"
+#include "services/network/network_service.h"
+#include "services/network/public/cpp/cross_thread_shared_url_loader_factory_info.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
+#include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/chromeos/net/dhcp_wpad_url_client.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "chrome/common/chrome_paths_internal.h"
+#include "chrome/grit/chromium_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/constants.h"
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+namespace {
+
+constexpr bool kCertificateTransparencyEnabled =
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(OFFICIAL_BUILD) && \
+ !defined(OS_ANDROID)
+ // Certificate Transparency is only enabled if:
+ // - Desktop (!OS_ANDROID); OS_IOS does not use this file
+ // - base::GetBuildTime() is deterministic to the source (OFFICIAL_BUILD)
+ // - The build in reliably updatable (GOOGLE_CHROME_BRANDING)
+ true;
+#else
+ false;
+#endif
+
+bool g_enable_certificate_transparency = kCertificateTransparencyEnabled;
+
+// The global instance of the SystemNetworkContextmanager.
+SystemNetworkContextManager* g_system_network_context_manager = nullptr;
+
+void GetStubResolverConfig(
+ PrefService* local_state,
+ bool* insecure_stub_resolver_enabled,
+ net::DnsConfig::SecureDnsMode* secure_dns_mode,
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>*
+ dns_over_https_servers) {
+ DCHECK(!dns_over_https_servers->has_value());
+
+ *insecure_stub_resolver_enabled =
+ local_state->GetBoolean(prefs::kBuiltInDnsClientEnabled);
+
+ std::string doh_mode;
+ if (!local_state->FindPreference(prefs::kDnsOverHttpsMode)->IsManaged() &&
+ chrome_browser_net::ShouldDisableDohForManaged())
+ doh_mode = chrome_browser_net::kDnsOverHttpsModeOff;
+ else
+ doh_mode = local_state->GetString(prefs::kDnsOverHttpsMode);
+
+ if (doh_mode == chrome_browser_net::kDnsOverHttpsModeSecure)
+ *secure_dns_mode = net::DnsConfig::SecureDnsMode::SECURE;
+ else if (doh_mode == chrome_browser_net::kDnsOverHttpsModeAutomatic)
+ *secure_dns_mode = net::DnsConfig::SecureDnsMode::AUTOMATIC;
+ else
+ *secure_dns_mode = net::DnsConfig::SecureDnsMode::OFF;
+
+ std::string doh_templates =
+ local_state->GetString(prefs::kDnsOverHttpsTemplates);
+ std::string server_method;
+ if (!doh_templates.empty() &&
+ *secure_dns_mode != net::DnsConfig::SecureDnsMode::OFF) {
+ for (const std::string& server_template :
+ SplitString(doh_templates, " ", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY)) {
+ if (!chrome_browser_net::IsValidDohTemplate(server_template,
+ &server_method)) {
+ continue;
+ }
+
+ if (!dns_over_https_servers->has_value()) {
+ *dns_over_https_servers = base::make_optional<
+ std::vector<network::mojom::DnsOverHttpsServerPtr>>();
+ }
+
+ network::mojom::DnsOverHttpsServerPtr dns_over_https_server =
+ network::mojom::DnsOverHttpsServer::New();
+ dns_over_https_server->server_template = server_template;
+ dns_over_https_server->use_post = (server_method == "POST");
+ (*dns_over_https_servers)->emplace_back(std::move(dns_over_https_server));
+ }
+ }
+}
+
+void OnStubResolverConfigChanged(PrefService* local_state,
+ const std::string& pref_name) {
+ bool insecure_stub_resolver_enabled;
+ net::DnsConfig::SecureDnsMode secure_dns_mode;
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>
+ dns_over_https_servers;
+ GetStubResolverConfig(local_state, &insecure_stub_resolver_enabled,
+ &secure_dns_mode, &dns_over_https_servers);
+ content::GetNetworkService()->ConfigureStubHostResolver(
+ insecure_stub_resolver_enabled, secure_dns_mode,
+ std::move(dns_over_https_servers));
+}
+
+// Constructs HttpAuthStaticParams based on |local_state|.
+network::mojom::HttpAuthStaticParamsPtr CreateHttpAuthStaticParams(
+ PrefService* local_state) {
+ network::mojom::HttpAuthStaticParamsPtr auth_static_params =
+ network::mojom::HttpAuthStaticParams::New();
+
+ // TODO(https://crbug/549273): Allow this to change after startup.
+ auth_static_params->supported_schemes =
+ base::SplitString(local_state->GetString(prefs::kAuthSchemes), ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ auth_static_params->gssapi_library_name =
+ local_state->GetString(prefs::kGSSAPILibraryName);
+#endif
+
+ return auth_static_params;
+}
+
+// Constructs HttpAuthDynamicParams based on |local_state|.
+network::mojom::HttpAuthDynamicParamsPtr CreateHttpAuthDynamicParams(
+ PrefService* local_state) {
+ network::mojom::HttpAuthDynamicParamsPtr auth_dynamic_params =
+ network::mojom::HttpAuthDynamicParams::New();
+
+ auth_dynamic_params->server_allowlist =
+ local_state->GetString(prefs::kAuthServerWhitelist);
+ auth_dynamic_params->delegate_allowlist =
+ local_state->GetString(prefs::kAuthNegotiateDelegateWhitelist);
+ auth_dynamic_params->negotiate_disable_cname_lookup =
+ local_state->GetBoolean(prefs::kDisableAuthNegotiateCnameLookup);
+ auth_dynamic_params->enable_negotiate_port =
+ local_state->GetBoolean(prefs::kEnableAuthNegotiatePort);
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ auth_dynamic_params->delegate_by_kdc_policy =
+ local_state->GetBoolean(prefs::kAuthNegotiateDelegateByKdcPolicy);
+#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+
+#if defined(OS_POSIX)
+ auth_dynamic_params->ntlm_v2_enabled =
+ local_state->GetBoolean(prefs::kNtlmV2Enabled);
+#endif // defined(OS_POSIX)
+
+#if defined(OS_ANDROID)
+ auth_dynamic_params->android_negotiate_account_type =
+ local_state->GetString(prefs::kAuthAndroidNegotiateAccountType);
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+ policy::BrowserPolicyConnectorChromeOS* connector =
+ g_browser_process->platform_part()->browser_policy_connector_chromeos();
+ auth_dynamic_params->allow_gssapi_library_load =
+ connector->IsActiveDirectoryManaged() ||
+ local_state->GetBoolean(prefs::kKerberosEnabled);
+#endif
+
+ return auth_dynamic_params;
+}
+
+void OnAuthPrefsChanged(PrefService* local_state,
+ const std::string& pref_name) {
+ content::GetNetworkService()->ConfigureHttpAuthPrefs(
+ CreateHttpAuthDynamicParams(local_state));
+}
+
+// Check the AsyncDns field trial and return true if it should be enabled. On
+// Android this includes checking the Android version in the field trial.
+bool ShouldEnableAsyncDns() {
+ bool feature_can_be_enabled = true;
+#if defined(OS_ANDROID)
+ int min_sdk =
+ base::GetFieldTrialParamByFeatureAsInt(features::kAsyncDns, "min_sdk", 0);
+ if (base::android::BuildInfo::GetInstance()->sdk_int() < min_sdk)
+ feature_can_be_enabled = false;
+#endif
+ return feature_can_be_enabled &&
+ base::FeatureList::IsEnabled(features::kAsyncDns);
+}
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+bool ShouldUseBuiltinCertVerifier(PrefService* local_state) {
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+ const PrefService::Preference* builtin_cert_verifier_enabled_pref =
+ local_state->FindPreference(prefs::kBuiltinCertificateVerifierEnabled);
+ if (builtin_cert_verifier_enabled_pref->IsManaged())
+ return builtin_cert_verifier_enabled_pref->GetValue()->GetBool();
+#endif
+
+ return base::FeatureList::IsEnabled(
+ net::features::kCertVerifierBuiltinFeature);
+}
+#endif
+
+} // namespace
+
+// SharedURLLoaderFactory backed by a SystemNetworkContextManager and its
+// network context. Transparently handles crashes.
+class SystemNetworkContextManager::URLLoaderFactoryForSystem
+ : public network::SharedURLLoaderFactory {
+ public:
+ explicit URLLoaderFactoryForSystem(SystemNetworkContextManager* manager)
+ : manager_(manager) {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+ }
+
+ // mojom::URLLoaderFactory implementation:
+
+ void CreateLoaderAndStart(network::mojom::URLLoaderRequest request,
+ int32_t routing_id,
+ int32_t request_id,
+ uint32_t options,
+ const network::ResourceRequest& url_request,
+ network::mojom::URLLoaderClientPtr client,
+ const net::MutableNetworkTrafficAnnotationTag&
+ traffic_annotation) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!manager_)
+ return;
+ manager_->GetURLLoaderFactory()->CreateLoaderAndStart(
+ std::move(request), routing_id, request_id, options, url_request,
+ std::move(client), traffic_annotation);
+ }
+
+ void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
+ override {
+ if (!manager_)
+ return;
+ manager_->GetURLLoaderFactory()->Clone(std::move(receiver));
+ }
+
+ // SharedURLLoaderFactory implementation:
+ std::unique_ptr<network::SharedURLLoaderFactoryInfo> Clone() override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return std::make_unique<network::CrossThreadSharedURLLoaderFactoryInfo>(
+ this);
+ }
+
+ void Shutdown() { manager_ = nullptr; }
+
+ private:
+ friend class base::RefCounted<URLLoaderFactoryForSystem>;
+ ~URLLoaderFactoryForSystem() override {}
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ SystemNetworkContextManager* manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryForSystem);
+};
+
+network::mojom::NetworkContext* SystemNetworkContextManager::GetContext() {
+ if (!network_service_network_context_ ||
+ !network_service_network_context_.is_connected()) {
+ // This should call into OnNetworkServiceCreated(), which will re-create
+ // the network service, if needed. There's a chance that it won't be
+ // invoked, if the NetworkContext has encountered an error but the
+ // NetworkService has not yet noticed its pipe was closed. In that case,
+ // trying to create a new NetworkContext would fail, anyways, and hopefully
+ // a new NetworkContext will be created on the next GetContext() call.
+ content::GetNetworkService();
+ DCHECK(network_service_network_context_);
+ }
+ return network_service_network_context_.get();
+}
+
+network::mojom::URLLoaderFactory*
+SystemNetworkContextManager::GetURLLoaderFactory() {
+ // Create the URLLoaderFactory as needed.
+ if (url_loader_factory_ && !url_loader_factory_.encountered_error()) {
+ return url_loader_factory_.get();
+ }
+
+ network::mojom::URLLoaderFactoryParamsPtr params =
+ network::mojom::URLLoaderFactoryParams::New();
+ params->process_id = network::mojom::kBrowserProcessId;
+ params->is_corb_enabled = false;
+ params->is_trusted = true;
+ GetContext()->CreateURLLoaderFactory(mojo::MakeRequest(&url_loader_factory_),
+ std::move(params));
+ return url_loader_factory_.get();
+}
+
+scoped_refptr<network::SharedURLLoaderFactory>
+SystemNetworkContextManager::GetSharedURLLoaderFactory() {
+ return shared_url_loader_factory_;
+}
+
+// static
+SystemNetworkContextManager* SystemNetworkContextManager::CreateInstance(
+ PrefService* pref_service) {
+ DCHECK(!g_system_network_context_manager);
+ g_system_network_context_manager =
+ new SystemNetworkContextManager(pref_service);
+ return g_system_network_context_manager;
+}
+
+// static
+bool SystemNetworkContextManager::HasInstance() {
+ return !!g_system_network_context_manager;
+}
+
+// static
+SystemNetworkContextManager* SystemNetworkContextManager::GetInstance() {
+ if (!g_system_network_context_manager) {
+ // Initialize the network service, which will trigger
+ // ChromeContentBrowserClient::OnNetworkServiceCreated(), which calls
+ // CreateInstance() to initialize |g_system_network_context_manager|.
+ content::GetNetworkService();
+
+ // TODO(crbug.com/981057): There should be a DCHECK() here to make sure
+ // |g_system_network_context_manager| has been created, but that is not
+ // true in many unit tests.
+ }
+
+ return g_system_network_context_manager;
+}
+
+// static
+void SystemNetworkContextManager::DeleteInstance() {
+ DCHECK(g_system_network_context_manager);
+ delete g_system_network_context_manager;
+ g_system_network_context_manager = nullptr;
+}
+
+SystemNetworkContextManager::SystemNetworkContextManager(
+ PrefService* local_state)
+ : local_state_(local_state),
+ ssl_config_service_manager_(
+ SSLConfigServiceManager::CreateDefaultManager(local_state_)),
+ proxy_config_monitor_(local_state_) {
+#if !defined(OS_ANDROID)
+ // QuicAllowed was not part of Android policy.
+ const base::Value* value =
+ g_browser_process->policy_service()
+ ->GetPolicies(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
+ std::string()))
+ .GetValue(policy::key::kQuicAllowed);
+ if (value)
+ value->GetAsBoolean(&is_quic_allowed_);
+#endif
+ shared_url_loader_factory_ = new URLLoaderFactoryForSystem(this);
+
+ pref_change_registrar_.Init(local_state_);
+
+ // Update the DnsClient and DoH default preferences based on the corresponding
+ // features before registering change callbacks for these preferences.
+ local_state_->SetDefaultPrefValue(prefs::kBuiltInDnsClientEnabled,
+ base::Value(ShouldEnableAsyncDns()));
+ std::string default_doh_mode = chrome_browser_net::kDnsOverHttpsModeOff;
+ std::string default_doh_templates = "";
+ if (base::FeatureList::IsEnabled(features::kDnsOverHttps)) {
+ if (features::kDnsOverHttpsFallbackParam.Get()) {
+ default_doh_mode = chrome_browser_net::kDnsOverHttpsModeAutomatic;
+ } else {
+ default_doh_mode = chrome_browser_net::kDnsOverHttpsModeSecure;
+ }
+ default_doh_templates = features::kDnsOverHttpsTemplatesParam.Get();
+ }
+ local_state_->SetDefaultPrefValue(prefs::kDnsOverHttpsMode,
+ base::Value(default_doh_mode));
+ local_state_->SetDefaultPrefValue(prefs::kDnsOverHttpsTemplates,
+ base::Value(default_doh_templates));
+
+ // If the user has explicitly enabled or disabled the DoH experiment in
+ // chrome://flags, store that choice in the user prefs so that it can be
+ // persisted after the experiment ends. Also make sure to remove the stored
+ // prefs value if the user has changed their chrome://flags selection to the
+ // default.
+ flags_ui::PrefServiceFlagsStorage flags_storage(local_state_);
+ std::set<std::string> entries = flags_storage.GetFlags();
+ if (entries.count("dns-over-https@1")) {
+ // The user has "Enabled" selected.
+ local_state_->SetString(prefs::kDnsOverHttpsMode,
+ chrome_browser_net::kDnsOverHttpsModeAutomatic);
+ } else if (entries.count("dns-over-https@2")) {
+ // The user has "Disabled" selected.
+ local_state_->SetString(prefs::kDnsOverHttpsMode,
+ chrome_browser_net::kDnsOverHttpsModeOff);
+ } else {
+ // The user has "Default" selected.
+ local_state_->ClearPref(prefs::kDnsOverHttpsMode);
+ }
+
+ PrefChangeRegistrar::NamedChangeCallback dns_pref_callback =
+ base::BindRepeating(&OnStubResolverConfigChanged,
+ base::Unretained(local_state_));
+ pref_change_registrar_.Add(prefs::kBuiltInDnsClientEnabled,
+ dns_pref_callback);
+ pref_change_registrar_.Add(prefs::kDnsOverHttpsMode, dns_pref_callback);
+ pref_change_registrar_.Add(prefs::kDnsOverHttpsTemplates, dns_pref_callback);
+
+ PrefChangeRegistrar::NamedChangeCallback auth_pref_callback =
+ base::BindRepeating(&OnAuthPrefsChanged, base::Unretained(local_state_));
+ pref_change_registrar_.Add(prefs::kAuthServerWhitelist, auth_pref_callback);
+ pref_change_registrar_.Add(prefs::kAuthNegotiateDelegateWhitelist,
+ auth_pref_callback);
+ pref_change_registrar_.Add(prefs::kDisableAuthNegotiateCnameLookup,
+ auth_pref_callback);
+ pref_change_registrar_.Add(prefs::kEnableAuthNegotiatePort,
+ auth_pref_callback);
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ pref_change_registrar_.Add(prefs::kAuthNegotiateDelegateByKdcPolicy,
+ auth_pref_callback);
+#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+
+#if defined(OS_POSIX)
+ pref_change_registrar_.Add(prefs::kNtlmV2Enabled, auth_pref_callback);
+#endif // defined(OS_POSIX)
+
+#if defined(OS_ANDROID)
+ pref_change_registrar_.Add(prefs::kAuthAndroidNegotiateAccountType,
+ auth_pref_callback);
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+ pref_change_registrar_.Add(prefs::kKerberosEnabled, auth_pref_callback);
+#endif // defined(OS_CHROMEOS)
+
+ local_state_->SetDefaultPrefValue(
+ prefs::kEnableReferrers,
+ base::Value(!base::FeatureList::IsEnabled(features::kNoReferrers)));
+ enable_referrers_.Init(
+ prefs::kEnableReferrers, local_state_,
+ base::BindRepeating(&SystemNetworkContextManager::UpdateReferrersEnabled,
+ base::Unretained(this)));
+}
+
+SystemNetworkContextManager::~SystemNetworkContextManager() {
+ shared_url_loader_factory_->Shutdown();
+}
+
+void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) {
+ // Register the DnsClient and DoH preferences. The feature list has not been
+ // initialized yet, so setting the preference defaults here to reflect the
+ // corresponding features will only cause the preference defaults to reflect
+ // the feature defaults (feature values set via the command line will not be
+ // captured). Thus, the preference defaults are updated in the constructor
+ // for SystemNetworkContextManager, at which point the feature list is ready.
+ registry->RegisterBooleanPref(prefs::kBuiltInDnsClientEnabled, false);
+ registry->RegisterStringPref(prefs::kDnsOverHttpsMode, std::string());
+ registry->RegisterStringPref(prefs::kDnsOverHttpsTemplates, std::string());
+
+ // Static auth params
+ registry->RegisterStringPref(prefs::kAuthSchemes,
+ "basic,digest,ntlm,negotiate");
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ registry->RegisterStringPref(prefs::kGSSAPILibraryName, std::string());
+#endif // defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+
+ // Dynamic auth params.
+ registry->RegisterBooleanPref(prefs::kDisableAuthNegotiateCnameLookup, false);
+ registry->RegisterBooleanPref(prefs::kEnableAuthNegotiatePort, false);
+ registry->RegisterStringPref(prefs::kAuthServerWhitelist, std::string());
+ registry->RegisterStringPref(prefs::kAuthNegotiateDelegateWhitelist,
+ std::string());
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ registry->RegisterBooleanPref(prefs::kAuthNegotiateDelegateByKdcPolicy,
+ false);
+#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+
+#if defined(OS_POSIX)
+ registry->RegisterBooleanPref(
+ prefs::kNtlmV2Enabled,
+ base::FeatureList::IsEnabled(features::kNtlmV2Enabled));
+#endif // defined(OS_POSIX)
+#if defined(OS_ANDROID)
+ registry->RegisterStringPref(prefs::kAuthAndroidNegotiateAccountType,
+ std::string());
+#endif // defined(OS_ANDROID)
+
+ // Per-NetworkContext pref. The pref value from |local_state_| is used for
+ // the system NetworkContext, and the per-profile pref values are used for
+ // the profile NetworkContexts.
+ registry->RegisterBooleanPref(prefs::kEnableReferrers, true);
+
+ registry->RegisterBooleanPref(prefs::kQuickCheckEnabled, true);
+
+ registry->RegisterIntegerPref(prefs::kMaxConnectionsPerProxy, -1);
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+ // Note that the default value is not relevant because the pref is only
+ // evaluated when it is managed.
+ registry->RegisterBooleanPref(prefs::kBuiltinCertificateVerifierEnabled,
+ false);
+#endif
+}
+
+void SystemNetworkContextManager::OnNetworkServiceCreated(
+ network::mojom::NetworkService* network_service) {
+ // Disable QUIC globally, if needed.
+ if (!is_quic_allowed_)
+ network_service->DisableQuic();
+
+ network_service->SetUpHttpAuth(CreateHttpAuthStaticParams(local_state_));
+ network_service->ConfigureHttpAuthPrefs(
+ CreateHttpAuthDynamicParams(local_state_));
+
+ // TODO(lukasza): https://crbug.com/944162: Once
+ // kMimeHandlerViewInCrossProcessFrame feature ships, unconditionally include
+ // the MIME types below in GetNeverSniffedMimeTypes in
+ // services/network/cross_origin_read_blocking.cc. Without
+ // kMimeHandlerViewInCrossProcessFrame feature, PDFs and other similar
+ // MimeHandlerView-handled resources may be fetched from a cross-origin
+ // renderer (see https://crbug.com/929300).
+ if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+ network_service->AddExtraMimeTypesForCorb(
+ {"application/msexcel",
+ "application/mspowerpoint",
+ "application/msword",
+ "application/msword-template",
+ "application/pdf",
+ "application/vnd.ces-quickpoint",
+ "application/vnd.ces-quicksheet",
+ "application/vnd.ces-quickword",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-excel.sheet.macroenabled.12",
+ "application/vnd.ms-powerpoint",
+ "application/vnd.ms-powerpoint.presentation.macroenabled.12",
+ "application/vnd.ms-word",
+ "application/vnd.ms-word.document.12",
+ "application/vnd.ms-word.document.macroenabled.12",
+ "application/vnd.msword",
+ "application/"
+ "vnd.openxmlformats-officedocument.presentationml.presentation",
+ "application/"
+ "vnd.openxmlformats-officedocument.presentationml.template",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
+ "application/"
+ "vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/"
+ "vnd.openxmlformats-officedocument.wordprocessingml.template",
+ "application/vnd.presentation-openxml",
+ "application/vnd.presentation-openxmlm",
+ "application/vnd.spreadsheet-openxml",
+ "application/vnd.wordprocessing-openxml",
+ "text/csv"});
+ }
+
+ int max_connections_per_proxy =
+ local_state_->GetInteger(prefs::kMaxConnectionsPerProxy);
+ if (max_connections_per_proxy != -1)
+ network_service->SetMaxConnectionsPerProxy(max_connections_per_proxy);
+
+ // The system NetworkContext must be created first, since it sets
+ // |primary_network_context| to true.
+ network_service_network_context_.reset();
+ network_service->CreateNetworkContext(
+ network_service_network_context_.BindNewPipeAndPassReceiver(),
+ CreateNetworkContextParams());
+
+ mojo::PendingRemote<network::mojom::NetworkContextClient> client_remote;
+ mojo::MakeSelfOwnedReceiver(
+ std::make_unique<content::NetworkContextClientBase>(),
+ client_remote.InitWithNewPipeAndPassReceiver());
+ network_service_network_context_->SetClient(std::move(client_remote));
+
+ // Configure the stub resolver. This must be done after the system
+ // NetworkContext is created, but before anything has the chance to use it.
+ bool insecure_stub_resolver_enabled;
+ net::DnsConfig::SecureDnsMode secure_dns_mode;
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>
+ dns_over_https_servers;
+ GetStubResolverConfig(local_state_, &insecure_stub_resolver_enabled,
+ &secure_dns_mode, &dns_over_https_servers);
+ content::GetNetworkService()->ConfigureStubHostResolver(
+ insecure_stub_resolver_enabled, secure_dns_mode,
+ std::move(dns_over_https_servers));
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+
+ // Set up crypt config. This should be kept in sync with the OSCrypt parts of
+ // ChromeBrowserMainPartsLinux::PreProfileInit.
+ network::mojom::CryptConfigPtr config = network::mojom::CryptConfig::New();
+ config->store = command_line.GetSwitchValueASCII(switches::kPasswordStore);
+ config->product_name = l10n_util::GetStringUTF8(IDS_PRODUCT_NAME);
+ config->should_use_preference =
+ command_line.HasSwitch(switches::kEnableEncryptionSelection);
+ chrome::GetDefaultUserDataDirectory(&config->user_data_path);
+ content::GetNetworkService()->SetCryptConfig(std::move(config));
+#endif
+#if defined(OS_MACOSX)
+ content::GetNetworkService()->SetEncryptionKey(
+ OSCrypt::GetRawEncryptionKey());
+#endif
+
+ // Asynchronously reapply the most recently received CRLSet (if any).
+ component_updater::CRLSetPolicy::ReconfigureAfterNetworkRestart();
+}
+
+void SystemNetworkContextManager::DisableQuic() {
+ is_quic_allowed_ = false;
+
+ // Disabling QUIC for a profile disables QUIC globally. As a side effect, new
+ // Profiles will also have QUIC disabled because the network service will
+ // disable QUIC.
+ content::GetNetworkService()->DisableQuic();
+}
+
+void SystemNetworkContextManager::AddSSLConfigToNetworkContextParams(
+ network::mojom::NetworkContextParams* network_context_params) {
+ ssl_config_service_manager_->AddToNetworkContextParams(
+ network_context_params);
+}
+
+network::mojom::NetworkContextParamsPtr
+SystemNetworkContextManager::CreateDefaultNetworkContextParams() {
+ network::mojom::NetworkContextParamsPtr network_context_params =
+ network::mojom::NetworkContextParams::New();
+ content::UpdateCorsExemptHeader(network_context_params.get());
+ variations::UpdateCorsExemptHeaderForVariations(network_context_params.get());
+
+ network_context_params->enable_brotli = true;
+
+ network_context_params->user_agent = GetUserAgent();
+
+ // Disable referrers by default. Any consumer that enables referrers should
+ // respect prefs::kEnableReferrers from the appropriate pref store.
+ network_context_params->enable_referrers = false;
+
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+
+ std::string quic_user_agent_id;
+
+ if (base::FeatureList::IsEnabled(blink::features::kFreezeUserAgent)) {
+ quic_user_agent_id = "";
+ } else {
+ quic_user_agent_id = chrome::GetChannelName();
+ if (!quic_user_agent_id.empty())
+ quic_user_agent_id.push_back(' ');
+ quic_user_agent_id.append(
+ version_info::GetProductNameAndVersionForUserAgent());
+ quic_user_agent_id.push_back(' ');
+ quic_user_agent_id.append(
+ content::BuildOSCpuInfo(false /* include_android_build_number */));
+ }
+ network_context_params->quic_user_agent_id = quic_user_agent_id;
+
+ // TODO(eroman): Figure out why this doesn't work in single-process mode,
+ // or if it does work, now.
+ // Should be possible now that a private isolate is used.
+ // http://crbug.com/474654
+ if (!command_line.HasSwitch(switches::kWinHttpProxyResolver)) {
+ if (command_line.HasSwitch(switches::kSingleProcess)) {
+ LOG(ERROR) << "Cannot use V8 Proxy resolver in single process mode.";
+ } else {
+ network_context_params->proxy_resolver_factory =
+ ChromeMojoProxyResolverFactory::CreateWithSelfOwnedReceiver();
+#if defined(OS_CHROMEOS)
+ network_context_params->dhcp_wpad_url_client =
+ chromeos::DhcpWpadUrlClient::CreateWithSelfOwnedReceiver();
+#endif // defined(OS_CHROMEOS)
+ }
+ }
+
+ network_context_params->pac_quick_check_enabled =
+ local_state_->GetBoolean(prefs::kQuickCheckEnabled);
+
+ // Use the SystemNetworkContextManager to populate and update SSL
+ // configuration. The SystemNetworkContextManager is owned by the
+ // BrowserProcess itself, so will only be destroyed on shutdown, at which
+ // point, all NetworkContexts will be destroyed as well.
+ AddSSLConfigToNetworkContextParams(network_context_params.get());
+
+#if !defined(OS_ANDROID)
+
+ if (g_enable_certificate_transparency) {
+ network_context_params->enforce_chrome_ct_policy = true;
+ network_context_params->ct_log_update_time = base::GetBuildTime();
+
+ std::vector<std::string> operated_by_google_logs =
+ certificate_transparency::GetLogsOperatedByGoogle();
+ std::vector<std::pair<std::string, base::TimeDelta>> disqualified_logs =
+ certificate_transparency::GetDisqualifiedLogs();
+ for (const auto& ct_log : certificate_transparency::GetKnownLogs()) {
+ // TODO(rsleevi): https://crbug.com/702062 - Remove this duplication.
+ network::mojom::CTLogInfoPtr log_info = network::mojom::CTLogInfo::New();
+ log_info->public_key = std::string(ct_log.log_key, ct_log.log_key_length);
+ log_info->name = ct_log.log_name;
+
+ std::string log_id = crypto::SHA256HashString(log_info->public_key);
+ log_info->operated_by_google =
+ std::binary_search(std::begin(operated_by_google_logs),
+ std::end(operated_by_google_logs), log_id);
+ auto it = std::lower_bound(
+ std::begin(disqualified_logs), std::end(disqualified_logs), log_id,
+ [](const auto& disqualified_log, const std::string& log_id) {
+ return disqualified_log.first < log_id;
+ });
+ if (it != std::end(disqualified_logs) && it->first == log_id) {
+ log_info->disqualified_at = it->second;
+ }
+ network_context_params->ct_logs.push_back(std::move(log_info));
+ }
+ }
+#endif
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+ network_context_params->use_builtin_cert_verifier =
+ ShouldUseBuiltinCertVerifier(local_state_);
+#endif
+
+ return network_context_params;
+}
+
+net_log::NetExportFileWriter*
+SystemNetworkContextManager::GetNetExportFileWriter() {
+ if (!net_export_file_writer_) {
+ net_export_file_writer_ = std::make_unique<net_log::NetExportFileWriter>();
+ }
+ return net_export_file_writer_.get();
+}
+
+void SystemNetworkContextManager::FlushSSLConfigManagerForTesting() {
+ ssl_config_service_manager_->FlushForTesting();
+}
+
+void SystemNetworkContextManager::FlushProxyConfigMonitorForTesting() {
+ proxy_config_monitor_.FlushForTesting();
+}
+
+void SystemNetworkContextManager::FlushNetworkInterfaceForTesting() {
+ DCHECK(network_service_network_context_);
+ network_service_network_context_.FlushForTesting();
+ if (url_loader_factory_)
+ url_loader_factory_.FlushForTesting();
+}
+
+void SystemNetworkContextManager::GetStubResolverConfigForTesting(
+ bool* insecure_stub_resolver_enabled,
+ net::DnsConfig::SecureDnsMode* secure_dns_mode,
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>*
+ dns_over_https_servers) {
+ GetStubResolverConfig(g_browser_process->local_state(),
+ insecure_stub_resolver_enabled, secure_dns_mode,
+ dns_over_https_servers);
+}
+
+network::mojom::HttpAuthStaticParamsPtr
+SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting() {
+ return CreateHttpAuthStaticParams(g_browser_process->local_state());
+}
+
+network::mojom::HttpAuthDynamicParamsPtr
+SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting() {
+ return CreateHttpAuthDynamicParams(g_browser_process->local_state());
+}
+
+void SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
+ base::Optional<bool> enabled) {
+ g_enable_certificate_transparency =
+ enabled.value_or(kCertificateTransparencyEnabled);
+}
+
+network::mojom::NetworkContextParamsPtr
+SystemNetworkContextManager::CreateNetworkContextParams() {
+ // TODO(mmenke): Set up parameters here (in memory cookie store, etc).
+ network::mojom::NetworkContextParamsPtr network_context_params =
+ CreateDefaultNetworkContextParams();
+
+ network_context_params->context_name = std::string("system");
+
+ network_context_params->enable_referrers = enable_referrers_.GetValue();
+
+ network_context_params->http_cache_enabled = false;
+
+ // These are needed for PAC scripts that use FTP URLs.
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
+ network_context_params->enable_ftp_url_support =
+ base::FeatureList::IsEnabled(features::kEnableFtp);
+#endif
+
+ network_context_params->primary_network_context = true;
+
+ proxy_config_monitor_.AddToNetworkContextParams(network_context_params.get());
+
+ return network_context_params;
+}
+
+void SystemNetworkContextManager::UpdateReferrersEnabled() {
+ GetContext()->SetEnableReferrers(enable_referrers_.GetValue());
+}
diff --git a/chromium/chrome/browser/net/system_network_context_manager.h b/chromium/chrome/browser/net/system_network_context_manager.h
new file mode 100644
index 00000000000..ba3804ef63b
--- /dev/null
+++ b/chromium/chrome/browser/net/system_network_context_manager.h
@@ -0,0 +1,195 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_SYSTEM_NETWORK_CONTEXT_MANAGER_H_
+#define CHROME_BROWSER_NET_SYSTEM_NETWORK_CONTEXT_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "chrome/browser/net/proxy_config_monitor.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_member.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/mojom/host_resolver.mojom-forward.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/network_service.mojom-forward.h"
+#include "services/network/public/mojom/ssl_config.mojom-forward.h"
+#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
+
+class PrefRegistrySimple;
+class PrefService;
+class SSLConfigServiceManager;
+
+namespace network {
+namespace mojom {
+class URLLoaderFactory;
+}
+class SharedURLLoaderFactory;
+} // namespace network
+
+namespace net_log {
+class NetExportFileWriter;
+}
+
+// Responsible for creating and managing access to the system NetworkContext.
+// Lives on the UI thread. The NetworkContext this owns is intended for requests
+// not associated with a profile. It stores no data on disk, and has no HTTP
+// cache, but it does have ephemeral cookie and channel ID stores. It also does
+// not have access to HTTP proxy auth information the user has entered or that
+// comes from extensions, and similarly, has no extension-provided per-profile
+// proxy configuration information.
+//
+// This class is also responsible for configuring global NetworkService state.
+//
+// The "system" NetworkContext will either share a URLRequestContext with
+// IOThread's SystemURLRequestContext and be part of IOThread's NetworkService
+// (If the network service is disabled) or be an independent NetworkContext
+// using the actual network service.
+//
+// This class is intended to eventually replace IOThread. Handling the two cases
+// differently allows this to be used in production without breaking anything or
+// requiring two separate paths, while IOThread consumers slowly transition over
+// to being compatible with the network service.
+class SystemNetworkContextManager {
+ public:
+ ~SystemNetworkContextManager();
+
+ // Creates the global instance of SystemNetworkContextManager. If an
+ // instance already exists, this will cause a DCHECK failure.
+ static SystemNetworkContextManager* CreateInstance(PrefService* pref_service);
+
+ // Checks if the global SystemNetworkContextManager has been created.
+ static bool HasInstance();
+
+ // Gets the global SystemNetworkContextManager instance. If it has not been
+ // created yet, NetworkService is called, which will cause the
+ // SystemNetworkContextManager to be created.
+ static SystemNetworkContextManager* GetInstance();
+
+ // Destroys the global SystemNetworkContextManager instance.
+ static void DeleteInstance();
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Returns the System NetworkContext. May only be called after SetUp(). Does
+ // any initialization of the NetworkService that may be needed when first
+ // called.
+ network::mojom::NetworkContext* GetContext();
+
+ // Returns a URLLoaderFactory owned by the SystemNetworkContextManager that is
+ // backed by the SystemNetworkContext. Allows sharing of the URLLoaderFactory.
+ // Prefer this to creating a new one. Call Clone() on the value returned by
+ // this method to get a URLLoaderFactory that can be used on other threads.
+ network::mojom::URLLoaderFactory* GetURLLoaderFactory();
+
+ // Returns a SharedURLLoaderFactory owned by the SystemNetworkContextManager
+ // that is backed by the SystemNetworkContext.
+ scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory();
+
+ // Called when content creates a NetworkService. Creates the
+ // SystemNetworkContext, if the network service is enabled.
+ void OnNetworkServiceCreated(network::mojom::NetworkService* network_service);
+
+ // Permanently disables QUIC, both for NetworkContexts using the IOThread's
+ // NetworkService, and for those using the network service (if enabled).
+ void DisableQuic();
+
+ // Returns an mojo::PendingReceiver<SSLConfigClient> that can be passed as a
+ // NetorkContextParam.
+ mojo::PendingReceiver<network::mojom::SSLConfigClient>
+ GetSSLConfigClientReceiver();
+
+ // Populates |initial_ssl_config| and |ssl_config_client_receiver| members of
+ // |network_context_params|. As long as the SystemNetworkContextManager
+ // exists, any NetworkContext created with the params will continue to get
+ // SSL configuration updates.
+ void AddSSLConfigToNetworkContextParams(
+ network::mojom::NetworkContextParams* network_context_params);
+
+ // Returns default set of parameters for configuring the network service.
+ network::mojom::NetworkContextParamsPtr CreateDefaultNetworkContextParams();
+
+ // Returns a shared global NetExportFileWriter instance, used by net-export.
+ // It lives here so it can outlive chrome://net-export/ if the tab is closed
+ // or destroyed, and so that it's destroyed before Mojo is shut down.
+ net_log::NetExportFileWriter* GetNetExportFileWriter();
+
+ // Flushes all pending SSL configuration changes.
+ void FlushSSLConfigManagerForTesting();
+
+ // Flushes all pending proxy configuration changes.
+ void FlushProxyConfigMonitorForTesting();
+
+ // Call |FlushForTesting()| on Network Service related interfaces. For test
+ // use only.
+ void FlushNetworkInterfaceForTesting();
+
+ // Returns configuration that would be sent to the stub DNS resolver.
+ static void GetStubResolverConfigForTesting(
+ bool* insecure_stub_resolver_enabled,
+ net::DnsConfig::SecureDnsMode* secure_dns_mode,
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>*
+ dns_over_https_servers);
+
+ static network::mojom::HttpAuthStaticParamsPtr
+ GetHttpAuthStaticParamsForTesting();
+ static network::mojom::HttpAuthDynamicParamsPtr
+ GetHttpAuthDynamicParamsForTesting();
+
+ // Enables Certificate Transparency and enforcing the Chrome Certificate
+ // Transparency Policy. For test use only. Use base::nullopt_t to reset to
+ // the default state.
+ static void SetEnableCertificateTransparencyForTesting(
+ base::Optional<bool> enabled);
+
+ private:
+ class URLLoaderFactoryForSystem;
+
+ // Constructor. |pref_service| must out live this object.
+ explicit SystemNetworkContextManager(PrefService* pref_service);
+
+ void UpdateReferrersEnabled();
+
+ // Creates parameters for the NetworkContext. May only be called once, since
+ // it initializes some class members.
+ network::mojom::NetworkContextParamsPtr CreateNetworkContextParams();
+
+ // The PrefService to retrieve all the pref values.
+ PrefService* local_state_;
+
+ // This is an instance of the default SSLConfigServiceManager for the current
+ // platform and it gets SSL preferences from the BrowserProcess's local_state
+ // object. It's shared with other NetworkContexts.
+ std::unique_ptr<SSLConfigServiceManager> ssl_config_service_manager_;
+
+ ProxyConfigMonitor proxy_config_monitor_;
+
+ // NetworkContext using the network service, if the network service is
+ // enabled. mojo::NullRemote(), otherwise.
+ mojo::Remote<network::mojom::NetworkContext> network_service_network_context_;
+
+ // URLLoaderFactory backed by the NetworkContext returned by GetContext(), so
+ // consumers don't all need to create their own factory.
+ scoped_refptr<URLLoaderFactoryForSystem> shared_url_loader_factory_;
+ network::mojom::URLLoaderFactoryPtr url_loader_factory_;
+
+ bool is_quic_allowed_ = true;
+
+ PrefChangeRegistrar pref_change_registrar_;
+
+ BooleanPrefMember enable_referrers_;
+
+ // Initialized on first access.
+ std::unique_ptr<net_log::NetExportFileWriter> net_export_file_writer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemNetworkContextManager);
+};
+
+#endif // CHROME_BROWSER_NET_SYSTEM_NETWORK_CONTEXT_MANAGER_H_
diff --git a/chromium/chrome/browser/net/system_network_context_manager_browsertest.cc b/chromium/chrome/browser/net/system_network_context_manager_browsertest.cc
new file mode 100644
index 00000000000..4f73f5627b5
--- /dev/null
+++ b/chromium/chrome/browser/net/system_network_context_manager_browsertest.cc
@@ -0,0 +1,499 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/system_network_context_manager.h"
+
+#include <string>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/optional.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/dns_util.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/prefs/pref_service.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/user_agent.h"
+#include "services/network/public/cpp/network_service_buildflags.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+#include "chrome/browser/policy/policy_test_utils.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "net/base/features.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/win_util.h"
+#endif
+
+namespace {
+
+void GetStubResolverConfig(
+ bool* insecure_stub_resolver_enabled,
+ net::DnsConfig::SecureDnsMode* secure_dns_mode,
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>*
+ dns_over_https_servers) {
+ dns_over_https_servers->reset();
+
+ SystemNetworkContextManager::GetStubResolverConfigForTesting(
+ insecure_stub_resolver_enabled, secure_dns_mode, dns_over_https_servers);
+}
+
+// Checks that the values returned by GetStubResolverConfigForTesting() match
+// |async_dns_feature_enabled| (With empty DNS over HTTPS prefs). Then sets
+// various DoH modes and DoH template strings and makes sure the settings are
+// respected.
+void RunStubResolverConfigTests(bool async_dns_feature_enabled) {
+ // Mark as not enterprise managed.
+#if defined(OS_WIN)
+ base::win::ScopedDomainStateForTesting scoped_domain(false);
+#endif
+ // Check initial state.
+ bool insecure_stub_resolver_enabled = !async_dns_feature_enabled;
+ net::DnsConfig::SecureDnsMode secure_dns_mode;
+ base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>
+ dns_over_https_servers;
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::OFF, secure_dns_mode);
+ EXPECT_FALSE(dns_over_https_servers.has_value());
+
+ std::string good_post_template = "https://foo.test/";
+ std::string good_get_template = "https://bar.test/dns-query{?dns}";
+ std::string bad_template = "dns-query{?dns}";
+ std::string good_then_bad_template = good_get_template + " " + bad_template;
+ std::string bad_then_good_template = bad_template + " " + good_get_template;
+ std::string multiple_good_templates =
+ " " + good_get_template + " " + good_post_template + " ";
+
+ PrefService* local_state = g_browser_process->local_state();
+ local_state->SetString(prefs::kDnsOverHttpsMode,
+ chrome_browser_net::kDnsOverHttpsModeSecure);
+ local_state->SetString(prefs::kDnsOverHttpsTemplates, bad_template);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::SECURE, secure_dns_mode);
+ EXPECT_FALSE(dns_over_https_servers.has_value());
+
+ local_state->SetString(prefs::kDnsOverHttpsTemplates, good_post_template);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::SECURE, secure_dns_mode);
+ ASSERT_TRUE(dns_over_https_servers.has_value());
+ ASSERT_EQ(1u, dns_over_https_servers->size());
+ EXPECT_EQ(good_post_template, dns_over_https_servers->at(0)->server_template);
+ EXPECT_EQ(true, dns_over_https_servers->at(0)->use_post);
+
+ local_state->SetString(prefs::kDnsOverHttpsMode,
+ chrome_browser_net::kDnsOverHttpsModeAutomatic);
+ local_state->SetString(prefs::kDnsOverHttpsTemplates, bad_template);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::AUTOMATIC, secure_dns_mode);
+ EXPECT_FALSE(dns_over_https_servers.has_value());
+
+ local_state->SetString(prefs::kDnsOverHttpsTemplates, good_then_bad_template);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::AUTOMATIC, secure_dns_mode);
+ ASSERT_TRUE(dns_over_https_servers.has_value());
+ ASSERT_EQ(1u, dns_over_https_servers->size());
+ EXPECT_EQ(good_get_template, dns_over_https_servers->at(0)->server_template);
+ EXPECT_FALSE(dns_over_https_servers->at(0)->use_post);
+
+ local_state->SetString(prefs::kDnsOverHttpsTemplates, bad_then_good_template);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::AUTOMATIC, secure_dns_mode);
+ ASSERT_TRUE(dns_over_https_servers.has_value());
+ ASSERT_EQ(1u, dns_over_https_servers->size());
+ EXPECT_EQ(good_get_template, dns_over_https_servers->at(0)->server_template);
+ EXPECT_FALSE(dns_over_https_servers->at(0)->use_post);
+
+ local_state->SetString(prefs::kDnsOverHttpsTemplates,
+ multiple_good_templates);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::AUTOMATIC, secure_dns_mode);
+ ASSERT_TRUE(dns_over_https_servers.has_value());
+ ASSERT_EQ(2u, dns_over_https_servers->size());
+ EXPECT_EQ(good_get_template, dns_over_https_servers->at(0)->server_template);
+ EXPECT_FALSE(dns_over_https_servers->at(0)->use_post);
+ EXPECT_EQ(good_post_template, dns_over_https_servers->at(1)->server_template);
+ EXPECT_TRUE(dns_over_https_servers->at(1)->use_post);
+
+ local_state->SetString(prefs::kDnsOverHttpsMode,
+ chrome_browser_net::kDnsOverHttpsModeOff);
+ local_state->SetString(prefs::kDnsOverHttpsTemplates, good_get_template);
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::OFF, secure_dns_mode);
+ EXPECT_FALSE(dns_over_https_servers.has_value());
+
+ local_state->SetString(prefs::kDnsOverHttpsMode, "no_match");
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::OFF, secure_dns_mode);
+ EXPECT_FALSE(dns_over_https_servers.has_value());
+
+ // Test case with policy BuiltInDnsClientEnabled enabled. The DoH fields
+ // should be unaffected.
+ local_state->Set(prefs::kBuiltInDnsClientEnabled,
+ base::Value(!async_dns_feature_enabled));
+ GetStubResolverConfig(&insecure_stub_resolver_enabled, &secure_dns_mode,
+ &dns_over_https_servers);
+ EXPECT_EQ(!async_dns_feature_enabled, insecure_stub_resolver_enabled);
+ EXPECT_EQ(net::DnsConfig::SecureDnsMode::OFF, secure_dns_mode);
+ EXPECT_FALSE(dns_over_https_servers.has_value());
+}
+
+} // namespace
+
+using SystemNetworkContextManagerBrowsertest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest,
+ StubResolverDefaultConfig) {
+ RunStubResolverConfigTests(base::FeatureList::IsEnabled(features::kAsyncDns));
+}
+
+IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest,
+ StaticAuthParams) {
+ // Test defaults.
+ network::mojom::HttpAuthStaticParamsPtr static_params =
+ SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting();
+ EXPECT_THAT(static_params->supported_schemes,
+ testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
+ EXPECT_EQ("", static_params->gssapi_library_name);
+
+ // Test that prefs are reflected in params.
+
+ PrefService* local_state = g_browser_process->local_state();
+
+ local_state->SetString(prefs::kAuthSchemes, "basic");
+ static_params =
+ SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting();
+ EXPECT_THAT(static_params->supported_schemes, testing::ElementsAre("basic"));
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ const char dev_null[] = "/dev/null";
+ local_state->SetString(prefs::kGSSAPILibraryName, dev_null);
+ static_params =
+ SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting();
+ EXPECT_EQ(dev_null, static_params->gssapi_library_name);
+#endif
+}
+
+IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest, AuthParams) {
+ // Test defaults.
+ network::mojom::HttpAuthDynamicParamsPtr dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(false, dynamic_params->negotiate_disable_cname_lookup);
+ EXPECT_EQ(false, dynamic_params->enable_negotiate_port);
+ EXPECT_EQ("", dynamic_params->server_allowlist);
+ EXPECT_EQ("", dynamic_params->delegate_allowlist);
+ EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
+
+ PrefService* local_state = g_browser_process->local_state();
+
+ local_state->SetBoolean(prefs::kDisableAuthNegotiateCnameLookup, true);
+ dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(true, dynamic_params->negotiate_disable_cname_lookup);
+ EXPECT_EQ(false, dynamic_params->enable_negotiate_port);
+ EXPECT_EQ("", dynamic_params->server_allowlist);
+ EXPECT_EQ("", dynamic_params->delegate_allowlist);
+ EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
+
+ local_state->SetBoolean(prefs::kEnableAuthNegotiatePort, true);
+ dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(true, dynamic_params->negotiate_disable_cname_lookup);
+ EXPECT_EQ(true, dynamic_params->enable_negotiate_port);
+ EXPECT_EQ("", dynamic_params->server_allowlist);
+ EXPECT_EQ("", dynamic_params->delegate_allowlist);
+ EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
+
+ const char kServerAllowList[] = "foo";
+ local_state->SetString(prefs::kAuthServerWhitelist, kServerAllowList);
+ dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(true, dynamic_params->negotiate_disable_cname_lookup);
+ EXPECT_EQ(true, dynamic_params->enable_negotiate_port);
+ EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
+ EXPECT_EQ("", dynamic_params->delegate_allowlist);
+
+ const char kDelegateAllowList[] = "bar, baz";
+ local_state->SetString(prefs::kAuthNegotiateDelegateWhitelist,
+ kDelegateAllowList);
+ dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(true, dynamic_params->negotiate_disable_cname_lookup);
+ EXPECT_EQ(true, dynamic_params->enable_negotiate_port);
+ EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
+ EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist);
+ EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ local_state->SetBoolean(prefs::kAuthNegotiateDelegateByKdcPolicy, true);
+ dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(true, dynamic_params->negotiate_disable_cname_lookup);
+ EXPECT_EQ(true, dynamic_params->enable_negotiate_port);
+ EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
+ EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist);
+ EXPECT_TRUE(dynamic_params->delegate_by_kdc_policy);
+#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+ // The kerberos.enabled pref is false and the device is not Active Directory
+ // managed by default.
+ EXPECT_EQ(false, dynamic_params->allow_gssapi_library_load);
+ local_state->SetBoolean(prefs::kKerberosEnabled, true);
+ dynamic_params =
+ SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
+ EXPECT_EQ(true, dynamic_params->allow_gssapi_library_load);
+#endif // defined(OS_CHROMEOS)
+}
+
+class SystemNetworkContextManagerStubResolverBrowsertest
+ : public SystemNetworkContextManagerBrowsertest,
+ public testing::WithParamInterface<bool> {
+ public:
+ SystemNetworkContextManagerStubResolverBrowsertest() {
+ scoped_feature_list_.InitWithFeatureState(features::kAsyncDns, GetParam());
+ }
+ ~SystemNetworkContextManagerStubResolverBrowsertest() override {}
+
+ void SetUpOnMainThread() override {}
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerStubResolverBrowsertest,
+ StubResolverConfig) {
+ RunStubResolverConfigTests(GetParam());
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ SystemNetworkContextManagerStubResolverBrowsertest,
+ ::testing::Bool());
+
+class SystemNetworkContextManagerReferrersFeatureBrowsertest
+ : public SystemNetworkContextManagerBrowsertest,
+ public testing::WithParamInterface<bool> {
+ public:
+ SystemNetworkContextManagerReferrersFeatureBrowsertest() {
+ scoped_feature_list_.InitWithFeatureState(features::kNoReferrers,
+ GetParam());
+ }
+ ~SystemNetworkContextManagerReferrersFeatureBrowsertest() override {}
+
+ void SetUpOnMainThread() override {}
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that toggling the kNoReferrers feature correctly changes the default
+// value of the kEnableReferrers pref.
+IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerReferrersFeatureBrowsertest,
+ TestDefaultReferrerReflectsFeatureValue) {
+ ASSERT_TRUE(!!g_browser_process);
+ PrefService* local_state = g_browser_process->local_state();
+ ASSERT_TRUE(!!local_state);
+ EXPECT_NE(local_state->GetBoolean(prefs::kEnableReferrers), GetParam());
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ SystemNetworkContextManagerReferrersFeatureBrowsertest,
+ ::testing::Bool());
+
+class SystemNetworkContextManagerFreezeQUICUaBrowsertest
+ : public SystemNetworkContextManagerBrowsertest,
+ public testing::WithParamInterface<bool> {
+ public:
+ SystemNetworkContextManagerFreezeQUICUaBrowsertest() {
+ scoped_feature_list_.InitWithFeatureState(blink::features::kFreezeUserAgent,
+ GetParam());
+ }
+ ~SystemNetworkContextManagerFreezeQUICUaBrowsertest() override {}
+
+ void SetUpOnMainThread() override {}
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+bool ContainsSubstring(std::string super, std::string sub) {
+ return super.find(sub) != std::string::npos;
+}
+
+IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerFreezeQUICUaBrowsertest,
+ QUICUaConfig) {
+ network::mojom::NetworkContextParamsPtr network_context_params =
+ g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams();
+
+ std::string quic_ua = network_context_params->quic_user_agent_id;
+
+ if (GetParam()) { // if the UA Freeze feature is turned on
+ EXPECT_EQ("", quic_ua);
+ } else {
+ EXPECT_TRUE(ContainsSubstring(quic_ua, chrome::GetChannelName()));
+ EXPECT_TRUE(ContainsSubstring(
+ quic_ua, version_info::GetProductNameAndVersionForUserAgent()));
+ EXPECT_TRUE(ContainsSubstring(quic_ua, content::BuildOSCpuInfo(false)));
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ SystemNetworkContextManagerFreezeQUICUaBrowsertest,
+ ::testing::Bool());
+
+class SystemNetworkContextManagerWPADQuickCheckBrowsertest
+ : public SystemNetworkContextManagerBrowsertest,
+ public testing::WithParamInterface<bool> {
+ public:
+ SystemNetworkContextManagerWPADQuickCheckBrowsertest() = default;
+ ~SystemNetworkContextManagerWPADQuickCheckBrowsertest() override = default;
+};
+
+IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerWPADQuickCheckBrowsertest,
+ WPADQuickCheckPref) {
+ PrefService* local_state = g_browser_process->local_state();
+ local_state->SetBoolean(prefs::kQuickCheckEnabled, GetParam());
+
+ network::mojom::NetworkContextParamsPtr network_context_params =
+ g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams();
+ EXPECT_EQ(GetParam(), network_context_params->pac_quick_check_enabled);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ SystemNetworkContextManagerWPADQuickCheckBrowsertest,
+ ::testing::Bool());
+
+class SystemNetworkContextManagerCertificateTransparencyBrowsertest
+ : public SystemNetworkContextManagerBrowsertest,
+ public testing::WithParamInterface<base::Optional<bool>> {
+ public:
+ SystemNetworkContextManagerCertificateTransparencyBrowsertest() {
+ SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
+ GetParam());
+ }
+ ~SystemNetworkContextManagerCertificateTransparencyBrowsertest() override {
+ SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
+ base::nullopt);
+ }
+};
+
+#if BUILDFLAG(IS_CT_SUPPORTED)
+IN_PROC_BROWSER_TEST_P(
+ SystemNetworkContextManagerCertificateTransparencyBrowsertest,
+ CertificateTransparencyConfig) {
+ network::mojom::NetworkContextParamsPtr context_params =
+ g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams();
+
+ const bool kDefault =
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(OFFICIAL_BUILD) && \
+ !defined(OS_ANDROID)
+ true;
+#else
+ false;
+#endif
+
+ EXPECT_EQ(GetParam().value_or(kDefault),
+ context_params->enforce_chrome_ct_policy);
+ EXPECT_NE(GetParam().value_or(kDefault), context_params->ct_logs.empty());
+
+ if (GetParam().value_or(kDefault)) {
+ bool has_google_log = false;
+ bool has_disqualified_log = false;
+ for (const auto& ct_log : context_params->ct_logs) {
+ has_google_log |= ct_log->operated_by_google;
+ has_disqualified_log |= ct_log->disqualified_at.has_value();
+ }
+ EXPECT_TRUE(has_google_log);
+ EXPECT_TRUE(has_disqualified_log);
+ }
+}
+#endif
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ SystemNetworkContextManagerCertificateTransparencyBrowsertest,
+ ::testing::Values(base::nullopt, true, false));
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+class SystemNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest
+ : public policy::PolicyTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ scoped_feature_list_.InitWithFeatureState(
+ net::features::kCertVerifierBuiltinFeature,
+ /*enabled=*/GetParam());
+ policy::PolicyTest::SetUpInProcessBrowserTestFixture();
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(
+ SystemNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest,
+ Test) {
+ // If no BuiltinCertificateVerifierEnabled policy is set, the
+ // use_builtin_cert_verifier param should be set from the feature flag.
+ EXPECT_EQ(GetParam(), g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams()
+ ->use_builtin_cert_verifier);
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+ // If the BuiltinCertificateVerifierEnabled policy is set it should override
+ // the feature flag.
+ policy::PolicyMap policies;
+ SetPolicy(&policies, policy::key::kBuiltinCertificateVerifierEnabled,
+ std::make_unique<base::Value>(true));
+ UpdateProviderPolicy(policies);
+ EXPECT_TRUE(g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams()
+ ->use_builtin_cert_verifier);
+
+ SetPolicy(&policies, policy::key::kBuiltinCertificateVerifierEnabled,
+ std::make_unique<base::Value>(false));
+ UpdateProviderPolicy(policies);
+ EXPECT_FALSE(g_browser_process->system_network_context_manager()
+ ->CreateDefaultNetworkContextParams()
+ ->use_builtin_cert_verifier);
+#endif // BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ SystemNetworkContextServiceCertVerifierBuiltinFeaturePolicyTest,
+ ::testing::Bool());
+#endif // BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
diff --git a/chromium/chrome/browser/net/trial_comparison_cert_verifier_browsertest.cc b/chromium/chrome/browser/net/trial_comparison_cert_verifier_browsertest.cc
new file mode 100644
index 00000000000..05873ef06f9
--- /dev/null
+++ b/chromium/chrome/browser/net/trial_comparison_cert_verifier_browsertest.cc
@@ -0,0 +1,136 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/metrics/subprocess_metrics_provider.h"
+#include "chrome/browser/net/trial_comparison_cert_verifier_controller.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "net/base/features.h"
+#include "net/cert/trial_comparison_cert_verifier.h"
+#include "net/net_buildflags.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class TrialComparisonCertVerifierTest : public InProcessBrowserTest {
+ public:
+ TrialComparisonCertVerifierTest()
+ : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+ protected:
+ net::EmbeddedTestServer https_test_server_;
+};
+
+IN_PROC_BROWSER_TEST_F(TrialComparisonCertVerifierTest, TrialDisabled) {
+ ASSERT_TRUE(https_test_server_.Start());
+ base::HistogramTester histograms;
+ ui_test_utils::NavigateToURL(browser(),
+ https_test_server_.GetURL("/title1.html"));
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 1);
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency_TrialPrimary", 0);
+}
+
+class TrialComparisonCertVerifierFeatureEnabledTest
+ : public TrialComparisonCertVerifierTest {
+ public:
+ TrialComparisonCertVerifierFeatureEnabledTest() {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ // None of these tests should generate a report, but set the trial to
+ // uma_only mode anyway just to be safe.
+ scoped_feature_->InitAndEnableFeatureWithParameters(
+ features::kCertDualVerificationTrialFeature, {{"uma_only", "true"}});
+ }
+
+ ~TrialComparisonCertVerifierFeatureEnabledTest() override {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(
+ false);
+ }
+
+ protected:
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_;
+};
+
+IN_PROC_BROWSER_TEST_F(TrialComparisonCertVerifierFeatureEnabledTest,
+ TrialEnabledPrefDisabled) {
+ ASSERT_TRUE(https_test_server_.Start());
+ base::HistogramTester histograms;
+ ui_test_utils::NavigateToURL(browser(),
+ https_test_server_.GetURL("/title1.html"));
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 1);
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency_TrialPrimary", 0);
+}
+
+IN_PROC_BROWSER_TEST_F(TrialComparisonCertVerifierFeatureEnabledTest,
+ TrialEnabledPrefEnabled) {
+ safe_browsing::SetExtendedReportingPref(browser()->profile()->GetPrefs(),
+ true);
+
+ ASSERT_TRUE(https_test_server_.Start());
+ base::HistogramTester histograms;
+ ui_test_utils::NavigateToURL(browser(),
+ https_test_server_.GetURL("/title1.html"));
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 1);
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+ if (base::FeatureList::IsEnabled(
+ net::features::kCertVerifierBuiltinFeature)) {
+ // If both the dual cert verifier trial feature and the builtin verifier
+ // feature are enabled, the dual cert verifier trial should not be used.
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency_TrialPrimary", 0);
+ return;
+ }
+#endif
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency_TrialPrimary", 1);
+}
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+class TrialComparisonCertVerifierFeatureOverridenByBuiltinVerifierTest
+ : public TrialComparisonCertVerifierTest {
+ public:
+ TrialComparisonCertVerifierFeatureOverridenByBuiltinVerifierTest() {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ scoped_feature_->InitWithFeaturesAndParameters(
+ // None of these tests should generate a report, but set the trial to
+ // uma_only mode anyway just to be safe.
+ {{features::kCertDualVerificationTrialFeature, {{"uma_only", "true"}}},
+ // Enable the builtin verifier.
+ {net::features::kCertVerifierBuiltinFeature, {}}},
+ {});
+ }
+
+ ~TrialComparisonCertVerifierFeatureOverridenByBuiltinVerifierTest() override {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(
+ false);
+ }
+
+ protected:
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+ TrialComparisonCertVerifierFeatureOverridenByBuiltinVerifierTest,
+ TrialEnabledPrefEnabledBuiltVerifierEnabled) {
+ safe_browsing::SetExtendedReportingPref(browser()->profile()->GetPrefs(),
+ true);
+
+ ASSERT_TRUE(https_test_server_.Start());
+ base::HistogramTester histograms;
+ ui_test_utils::NavigateToURL(browser(),
+ https_test_server_.GetURL("/title1.html"));
+ SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 1);
+ // If both the dual cert verifier trial feature and the builtin verifier
+ // feature are enabled, the dual cert verifier trial should not be used.
+ histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency_TrialPrimary", 0);
+}
+#endif
diff --git a/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.cc b/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.cc
new file mode 100644
index 00000000000..c547a628db4
--- /dev/null
+++ b/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.cc
@@ -0,0 +1,145 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/trial_comparison_cert_verifier_controller.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/location.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/task/post_task.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/safe_browsing/certificate_reporting_service.h"
+#include "chrome/browser/safe_browsing/certificate_reporting_service_factory.h"
+#include "chrome/browser/ssl/certificate_error_report.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_features.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/features.h"
+#include "net/net_buildflags.h"
+
+namespace {
+
+// Certificate reports are only sent from official builds, but this flag can be
+// set by tests.
+bool g_is_fake_official_build_for_cert_verifier_testing = false;
+
+} // namespace
+
+TrialComparisonCertVerifierController::TrialComparisonCertVerifierController(
+ Profile* profile)
+ : profile_(profile) {
+ if (!MaybeAllowedForProfile(profile_)) {
+ // Don't bother registering pref change notifier if the trial could never be
+ // enabled.
+ return;
+ }
+
+ pref_change_registrar_.Init(profile_->GetPrefs());
+ pref_change_registrar_.Add(
+ prefs::kSafeBrowsingScoutReportingEnabled,
+ base::BindRepeating(&TrialComparisonCertVerifierController::RefreshState,
+ base::Unretained(this)));
+}
+
+TrialComparisonCertVerifierController::
+ ~TrialComparisonCertVerifierController() = default;
+
+// static
+bool TrialComparisonCertVerifierController::MaybeAllowedForProfile(
+ Profile* profile) {
+ bool is_official_build = g_is_fake_official_build_for_cert_verifier_testing;
+#if defined(OFFICIAL_BUILD) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ is_official_build = true;
+#endif
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
+ // If the builtin verifier is enabled as the default verifier, the trial does
+ // not make sense.
+ if (base::FeatureList::IsEnabled(net::features::kCertVerifierBuiltinFeature))
+ return false;
+#endif
+
+ return is_official_build &&
+ base::FeatureList::IsEnabled(
+ features::kCertDualVerificationTrialFeature) &&
+ !profile->IsOffTheRecord();
+}
+
+void TrialComparisonCertVerifierController::AddClient(
+ mojo::PendingRemote<network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client,
+ mojo::PendingReceiver<
+ network::mojom::TrialComparisonCertVerifierReportClient>
+ report_client_receiver) {
+ receiver_set_.Add(this, std::move(report_client_receiver));
+ config_client_set_.Add(std::move(config_client));
+}
+
+bool TrialComparisonCertVerifierController::IsAllowed() const {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // Only allow on non-incognito profiles which have SBER opt-in set.
+ // See design doc for more details:
+ // https://docs.google.com/document/d/1AM1CD42bC6LHWjKg-Hkid_RLr2DH6OMzstH9-pGSi-g
+
+ if (!MaybeAllowedForProfile(profile_))
+ return false;
+
+ const PrefService& prefs = *profile_->GetPrefs();
+
+ return safe_browsing::IsExtendedReportingEnabled(prefs);
+}
+
+void TrialComparisonCertVerifierController::SendTrialReport(
+ const std::string& hostname,
+ const scoped_refptr<net::X509Certificate>& unverified_cert,
+ bool enable_rev_checking,
+ bool require_rev_checking_local_anchors,
+ bool enable_sha1_local_anchors,
+ bool disable_symantec_enforcement,
+ const net::CertVerifyResult& primary_result,
+ const net::CertVerifyResult& trial_result,
+ network::mojom::CertVerifierDebugInfoPtr debug_info) {
+ if (!IsAllowed() ||
+ base::GetFieldTrialParamByFeatureAsBool(
+ features::kCertDualVerificationTrialFeature, "uma_only", false)) {
+ return;
+ }
+
+ CertificateErrorReport report(hostname, *unverified_cert, enable_rev_checking,
+ require_rev_checking_local_anchors,
+ enable_sha1_local_anchors,
+ disable_symantec_enforcement, primary_result,
+ trial_result, std::move(debug_info));
+
+ report.AddNetworkTimeInfo(g_browser_process->network_time_tracker());
+ report.AddChromeChannel(chrome::GetChannel());
+
+ std::string serialized_report;
+ if (!report.Serialize(&serialized_report))
+ return;
+
+ CertificateReportingServiceFactory::GetForBrowserContext(profile_)->Send(
+ serialized_report);
+}
+
+// static
+void TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(
+ bool fake_official_build) {
+ g_is_fake_official_build_for_cert_verifier_testing = fake_official_build;
+}
+
+void TrialComparisonCertVerifierController::RefreshState() {
+ const bool is_allowed = IsAllowed();
+ for (auto& client : config_client_set_)
+ client->OnTrialConfigUpdated(is_allowed);
+}
diff --git a/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.h b/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.h
new file mode 100644
index 00000000000..bbe51a8b781
--- /dev/null
+++ b/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller.h
@@ -0,0 +1,82 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_TRIAL_COMPARISON_CERT_VERIFIER_CONTROLLER_H_
+#define CHROME_BROWSER_NET_TRIAL_COMPARISON_CERT_VERIFIER_CONTROLLER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_verifier.h"
+#include "services/network/public/mojom/trial_comparison_cert_verifier.mojom.h"
+
+class Profile;
+
+class TrialComparisonCertVerifierController
+ : public network::mojom::TrialComparisonCertVerifierReportClient {
+ public:
+ // Creates a TrialComparisonCertVerifierController using |profile| for
+ // preferences and reporting.
+ // |profile| must outlive the TrialComparisonCertVerifierController.
+ explicit TrialComparisonCertVerifierController(Profile* profile);
+ ~TrialComparisonCertVerifierController() override;
+
+ // Returns true if the trial could potentially be enabled for |profile|;
+ static bool MaybeAllowedForProfile(Profile* profile);
+
+ // Adds a client to the controller, sending trial configuration updates to
+ // |config_client|, and receiving trial reports from |report_client_receiver|.
+ void AddClient(mojo::PendingRemote<
+ network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client,
+ mojo::PendingReceiver<
+ network::mojom::TrialComparisonCertVerifierReportClient>
+ report_client_receiver);
+
+ // Returns true if the trial is enabled and SBER flag is set for this
+ // profile.
+ bool IsAllowed() const;
+
+ // TrialComparisonCertVerifierReportClient implementation:
+ void SendTrialReport(
+ const std::string& hostname,
+ const scoped_refptr<net::X509Certificate>& unverified_cert,
+ bool enable_rev_checking,
+ bool require_rev_checking_local_anchors,
+ bool enable_sha1_local_anchors,
+ bool disable_symantec_enforcement,
+ const net::CertVerifyResult& primary_result,
+ const net::CertVerifyResult& trial_result,
+ network::mojom::CertVerifierDebugInfoPtr debug_info) override;
+
+ static void SetFakeOfficialBuildForTesting(bool fake_official_build);
+
+ private:
+ void RefreshState();
+
+ Profile* profile_;
+ PrefChangeRegistrar pref_change_registrar_;
+
+ mojo::ReceiverSet<network::mojom::TrialComparisonCertVerifierReportClient>
+ receiver_set_;
+
+ mojo::RemoteSet<network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrialComparisonCertVerifierController);
+};
+
+#endif // CHROME_BROWSER_NET_TRIAL_COMPARISON_CERT_VERIFIER_CONTROLLER_H_
diff --git a/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller_unittest.cc b/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller_unittest.cc
new file mode 100644
index 00000000000..571ca8bbe1d
--- /dev/null
+++ b/chromium/chrome/browser/net/trial_comparison_cert_verifier_controller_unittest.cc
@@ -0,0 +1,548 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/trial_comparison_cert_verifier_controller.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/browser/safe_browsing/certificate_reporting_service_factory.h"
+#include "chrome/browser/safe_browsing/certificate_reporting_service_test_utils.h"
+#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
+#include "chrome/browser/ssl/cert_logger.pb.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/gtest_util.h"
+#include "net/test/test_data_directory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/trial_comparison_cert_verifier.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using certificate_reporting_test_utils::CertificateReportingServiceTestHelper;
+using certificate_reporting_test_utils::ReportExpectation;
+using certificate_reporting_test_utils::RetryStatus;
+using net::test::IsError;
+using testing::_;
+using testing::Mock;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+
+namespace {
+
+MATCHER_P(CertChainMatches, expected_cert, "") {
+ net::CertificateList actual_certs =
+ net::X509Certificate::CreateCertificateListFromBytes(
+ arg.data(), arg.size(),
+ net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+ if (actual_certs.empty()) {
+ *result_listener << "failed to parse arg";
+ return false;
+ }
+ std::vector<std::string> actual_der_certs;
+ for (const auto& cert : actual_certs) {
+ actual_der_certs.emplace_back(
+ net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()));
+ }
+
+ std::vector<std::string> expected_der_certs;
+ expected_der_certs.emplace_back(
+ net::x509_util::CryptoBufferAsStringPiece(expected_cert->cert_buffer()));
+ for (const auto& buffer : expected_cert->intermediate_buffers()) {
+ expected_der_certs.emplace_back(
+ net::x509_util::CryptoBufferAsStringPiece(buffer.get()));
+ }
+
+ return actual_der_certs == expected_der_certs;
+}
+
+} // namespace
+
+class MockTrialComparisonCertVerifierConfigClient
+ : public network::mojom::TrialComparisonCertVerifierConfigClient {
+ public:
+ MockTrialComparisonCertVerifierConfigClient(
+ mojo::PendingReceiver<
+ network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client_receiver)
+ : receiver_(this, std::move(config_client_receiver)) {}
+
+ MOCK_METHOD1(OnTrialConfigUpdated, void(bool allowed));
+
+ private:
+ mojo::Receiver<network::mojom::TrialComparisonCertVerifierConfigClient>
+ receiver_;
+};
+
+class TrialComparisonCertVerifierControllerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ cert_chain_1_ = CreateCertificateChainFromFile(
+ net::GetTestCertsDirectory(), "multi-root-chain1.pem",
+ net::X509Certificate::FORMAT_AUTO);
+ ASSERT_TRUE(cert_chain_1_);
+ leaf_cert_1_ = net::X509Certificate::CreateFromBuffer(
+ bssl::UpRef(cert_chain_1_->cert_buffer()), {});
+ ASSERT_TRUE(leaf_cert_1_);
+ cert_chain_2_ = CreateCertificateChainFromFile(
+ net::GetTestCertsDirectory(), "multi-root-chain2.pem",
+ net::X509Certificate::FORMAT_AUTO);
+ ASSERT_TRUE(cert_chain_2_);
+
+ ok_result_.verified_cert = cert_chain_1_;
+ bad_result_.verified_cert = cert_chain_2_;
+ bad_result_.cert_status = net::CERT_STATUS_DATE_INVALID;
+
+ reporting_service_test_helper_ =
+ base::MakeRefCounted<CertificateReportingServiceTestHelper>();
+ CertificateReportingServiceFactory::GetInstance()
+ ->SetReportEncryptionParamsForTesting(
+ reporting_service_test_helper()->server_public_key(),
+ reporting_service_test_helper()->server_public_key_version());
+ CertificateReportingServiceFactory::GetInstance()
+ ->SetURLLoaderFactoryForTesting(reporting_service_test_helper_);
+ reporting_service_test_helper()->SetFailureMode(
+ certificate_reporting_test_utils::REPORTS_SUCCESSFUL);
+
+ // Creating the profile before the SafeBrowsingService ensures the
+ // ServiceManagerConnection is initialized.
+ profile_manager_ = std::make_unique<TestingProfileManager>(
+ TestingBrowserProcess::GetGlobal());
+ ASSERT_TRUE(profile_manager_->SetUp());
+ ASSERT_TRUE(g_browser_process->profile_manager());
+ profile_ = profile_manager_->CreateTestingProfile("profile1");
+
+ sb_service_ =
+ base::MakeRefCounted<safe_browsing::TestSafeBrowsingService>();
+ TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(
+ sb_service_.get());
+ g_browser_process->safe_browsing_service()->Initialize();
+
+ // Initialize CertificateReportingService for |profile_|.
+ ASSERT_TRUE(reporting_service());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void CreateController(Profile* profile) {
+ mojo::PendingRemote<network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client;
+ auto config_client_receiver =
+ config_client.InitWithNewPipeAndPassReceiver();
+
+ trial_controller_ =
+ std::make_unique<TrialComparisonCertVerifierController>(profile);
+ trial_controller_->AddClient(std::move(config_client),
+ report_client_.BindNewPipeAndPassReceiver());
+
+ mock_config_client_ = std::make_unique<
+ StrictMock<MockTrialComparisonCertVerifierConfigClient>>(
+ std::move(config_client_receiver));
+ }
+
+ void CreateController() { CreateController(profile()); }
+
+ void TearDown() override {
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+
+ // Ensure mock expectations are checked.
+ mock_config_client_ = nullptr;
+
+ if (TestingBrowserProcess::GetGlobal()->safe_browsing_service()) {
+ TestingBrowserProcess::GetGlobal()->safe_browsing_service()->ShutDown();
+ TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(nullptr);
+ }
+
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(
+ false);
+ }
+
+ TestingProfile* profile() { return profile_; }
+ sync_preferences::TestingPrefServiceSyncable* pref_service() {
+ return profile_->GetTestingPrefService();
+ }
+ TrialComparisonCertVerifierController& trial_controller() {
+ return *trial_controller_;
+ }
+ network::mojom::TrialComparisonCertVerifierReportClient* report_client() {
+ return report_client_.get();
+ }
+ MockTrialComparisonCertVerifierConfigClient& mock_config_client() {
+ return *mock_config_client_;
+ }
+ CertificateReportingServiceTestHelper* reporting_service_test_helper() {
+ return reporting_service_test_helper_.get();
+ }
+ CertificateReportingService* reporting_service() const {
+ return CertificateReportingServiceFactory::GetForBrowserContext(profile_);
+ }
+
+ protected:
+ scoped_refptr<net::X509Certificate> cert_chain_1_;
+ scoped_refptr<net::X509Certificate> cert_chain_2_;
+ scoped_refptr<net::X509Certificate> leaf_cert_1_;
+ net::CertVerifyResult ok_result_;
+ net::CertVerifyResult bad_result_;
+
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_;
+
+ private:
+ scoped_refptr<CertificateReportingServiceTestHelper>
+ reporting_service_test_helper_;
+ content::BrowserTaskEnvironment task_environment_;
+ scoped_refptr<safe_browsing::SafeBrowsingService> sb_service_;
+ std::unique_ptr<TestingProfileManager> profile_manager_;
+ TestingProfile* profile_;
+
+ mojo::Remote<network::mojom::TrialComparisonCertVerifierReportClient>
+ report_client_;
+ std::unique_ptr<TrialComparisonCertVerifierController> trial_controller_;
+ std::unique_ptr<StrictMock<MockTrialComparisonCertVerifierConfigClient>>
+ mock_config_client_;
+};
+
+TEST_F(TrialComparisonCertVerifierControllerTest, NothingEnabled) {
+ CreateController();
+
+ // Trial should not be allowed.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Enable the SBER pref, shouldn't matter since it's a non-official build and
+ // field trial isn't enabled.
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+ // Trial still not allowed, and OnTrialConfigUpdated should not be called
+ // either.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Attempting to send a report should also do nothing.
+ report_client()->SendTrialReport(
+ "hostname", leaf_cert_1_, false, false, false, false, ok_result_,
+ ok_result_, network::mojom::CertVerifierDebugInfo::New());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // Expect no report since the trial is not allowed.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+}
+
+TEST_F(TrialComparisonCertVerifierControllerTest,
+ OfficialBuildTrialNotEnabled) {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ CreateController();
+
+ EXPECT_FALSE(trial_controller().IsAllowed());
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+ // Trial still not allowed, and OnTrialConfigUpdated should not be called
+ // either.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Attempting to send a report should do nothing.
+ report_client()->SendTrialReport(
+ "hostname", leaf_cert_1_, false, false, false, false, ok_result_,
+ ok_result_, network::mojom::CertVerifierDebugInfo::New());
+
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+
+ // Expect no report since the trial is not allowed.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+}
+
+TEST_F(TrialComparisonCertVerifierControllerTest,
+ NotOfficialBuildTrialEnabled) {
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ scoped_feature_->InitAndEnableFeature(
+ features::kCertDualVerificationTrialFeature);
+ CreateController();
+
+ EXPECT_FALSE(trial_controller().IsAllowed());
+#if defined(OFFICIAL_BUILD) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // In a real official build, expect the trial config to be updated.
+ EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
+#endif
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+#if defined(OFFICIAL_BUILD) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // In a real official build, expect the trial to be allowed now. (Don't
+ // need to test sending reports here, since that'll be tested by
+ // OfficialBuildTrialEnabled.)
+ EXPECT_TRUE(trial_controller().IsAllowed());
+#else
+ // Trial still not allowed, and OnTrialConfigUpdated should not be called
+ // either.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Attempting to send a report should do nothing.
+ report_client()->SendTrialReport(
+ "hostname", leaf_cert_1_, false, false, false, false, ok_result_,
+ ok_result_, network::mojom::CertVerifierDebugInfo::New());
+
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+
+ // Expect no report since the trial is not allowed.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+#endif
+}
+
+TEST_F(TrialComparisonCertVerifierControllerTest, OfficialBuildTrialEnabled) {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ scoped_feature_->InitAndEnableFeature(
+ features::kCertDualVerificationTrialFeature);
+ CreateController();
+
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Enable the SBER pref, which should trigger the OnTrialConfigUpdated
+ // callback.
+ EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+ // Trial should now be allowed.
+ EXPECT_TRUE(trial_controller().IsAllowed());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // OnTrialConfigUpdated should have been called.
+ Mock::VerifyAndClear(&mock_config_client());
+
+ // Report should be sent.
+ report_client()->SendTrialReport(
+ "127.0.0.1", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+
+ // Expect a report.
+ std::vector<std::string> full_reports;
+ reporting_service_test_helper()->WaitForRequestsDestroyed(
+ ReportExpectation::Successful({{"127.0.0.1", RetryStatus::NOT_RETRIED}}),
+ &full_reports);
+
+ ASSERT_EQ(1U, full_reports.size());
+
+ chrome_browser_ssl::CertLoggerRequest report;
+ ASSERT_TRUE(report.ParseFromString(full_reports[0]));
+
+ EXPECT_EQ(0, report.cert_error_size());
+ EXPECT_EQ(0, report.cert_status_size());
+
+ ASSERT_TRUE(report.has_features_info());
+ ASSERT_TRUE(report.features_info().has_trial_verification_info());
+ const chrome_browser_ssl::TrialVerificationInfo& trial_info =
+ report.features_info().trial_verification_info();
+ ASSERT_EQ(1, trial_info.cert_error_size());
+ EXPECT_EQ(chrome_browser_ssl::CertLoggerRequest::ERR_CERT_DATE_INVALID,
+ trial_info.cert_error()[0]);
+ EXPECT_EQ(0, trial_info.cert_status_size());
+
+ EXPECT_THAT(report.unverified_cert_chain(), CertChainMatches(leaf_cert_1_));
+ EXPECT_THAT(report.cert_chain(), CertChainMatches(cert_chain_1_));
+ EXPECT_THAT(trial_info.cert_chain(), CertChainMatches(cert_chain_2_));
+
+ // Disable the SBER pref again, which should trigger the OnTrialConfigUpdated
+ // callback.
+ EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(false)).Times(1);
+ safe_browsing::SetExtendedReportingPref(pref_service(), false);
+
+ // Not allowed now.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Attempting to send a report should do nothing now.
+ report_client()->SendTrialReport(
+ "hostname", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // Expect no report since the trial is not allowed.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+}
+
+TEST_F(TrialComparisonCertVerifierControllerTest,
+ OfficialBuildTrialEnabledTwoClients) {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ scoped_feature_->InitAndEnableFeature(
+ features::kCertDualVerificationTrialFeature);
+ CreateController();
+
+ mojo::Remote<network::mojom::TrialComparisonCertVerifierReportClient>
+ report_client_2;
+
+ mojo::PendingRemote<network::mojom::TrialComparisonCertVerifierConfigClient>
+ config_client_2;
+ auto config_client_2_receiver =
+ config_client_2.InitWithNewPipeAndPassReceiver();
+
+ trial_controller().AddClient(std::move(config_client_2),
+ report_client_2.BindNewPipeAndPassReceiver());
+
+ StrictMock<MockTrialComparisonCertVerifierConfigClient> mock_config_client_2(
+ std::move(config_client_2_receiver));
+
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Enable the SBER pref, which should trigger the OnTrialConfigUpdated
+ // callback.
+ EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
+ EXPECT_CALL(mock_config_client_2, OnTrialConfigUpdated(true)).Times(1);
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+ // Trial should now be allowed.
+ EXPECT_TRUE(trial_controller().IsAllowed());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // OnTrialConfigUpdated should have been called.
+ Mock::VerifyAndClear(&mock_config_client());
+ Mock::VerifyAndClear(&mock_config_client_2);
+
+ // Report should be sent.
+ report_client()->SendTrialReport(
+ "127.0.0.1", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+ report_client_2->SendTrialReport(
+ "127.0.0.2", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+
+ // Expect a report.
+ std::vector<std::string> full_reports;
+ reporting_service_test_helper()->WaitForRequestsDestroyed(
+ ReportExpectation::Successful({{"127.0.0.1", RetryStatus::NOT_RETRIED},
+ {"127.0.0.2", RetryStatus::NOT_RETRIED}}),
+ &full_reports);
+
+ ASSERT_EQ(2U, full_reports.size());
+
+ chrome_browser_ssl::CertLoggerRequest report;
+ for (size_t i = 0; i < 2; ++i) {
+ ASSERT_TRUE(report.ParseFromString(full_reports[i]));
+
+ EXPECT_EQ(0, report.cert_error_size());
+ EXPECT_EQ(0, report.cert_status_size());
+
+ ASSERT_TRUE(report.has_features_info());
+ ASSERT_TRUE(report.features_info().has_trial_verification_info());
+ const chrome_browser_ssl::TrialVerificationInfo& trial_info =
+ report.features_info().trial_verification_info();
+ ASSERT_EQ(1, trial_info.cert_error_size());
+ EXPECT_EQ(chrome_browser_ssl::CertLoggerRequest::ERR_CERT_DATE_INVALID,
+ trial_info.cert_error()[0]);
+ EXPECT_EQ(0, trial_info.cert_status_size());
+
+ EXPECT_THAT(report.unverified_cert_chain(), CertChainMatches(leaf_cert_1_));
+ EXPECT_THAT(report.cert_chain(), CertChainMatches(cert_chain_1_));
+ EXPECT_THAT(trial_info.cert_chain(), CertChainMatches(cert_chain_2_));
+ }
+
+ // Disable the SBER pref again, which should trigger the OnTrialConfigUpdated
+ // callback.
+ EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(false)).Times(1);
+ EXPECT_CALL(mock_config_client_2, OnTrialConfigUpdated(false)).Times(1);
+ safe_browsing::SetExtendedReportingPref(pref_service(), false);
+
+ // Not allowed now.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Attempting to send a report should do nothing now.
+ report_client()->SendTrialReport(
+ "hostname", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+ report_client_2->SendTrialReport(
+ "hostname2", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // Expect no report since the trial is not allowed.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+}
+
+TEST_F(TrialComparisonCertVerifierControllerTest,
+ OfficialBuildTrialEnabledUmaOnly) {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ scoped_feature_->InitAndEnableFeatureWithParameters(
+ features::kCertDualVerificationTrialFeature, {{"uma_only", "true"}});
+ CreateController();
+
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Enable the SBER pref, which should trigger the OnTrialConfigUpdated
+ // callback.
+ EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+ // Trial should now be allowed.
+ EXPECT_TRUE(trial_controller().IsAllowed());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // OnTrialConfigUpdated should have been called.
+ Mock::VerifyAndClear(&mock_config_client());
+
+ // In uma_only mode, the network service will generate a report, but the
+ // trial controller will not send it to the reporting service.
+ report_client()->SendTrialReport(
+ "127.0.0.1", leaf_cert_1_, false, false, false, false, ok_result_,
+ bad_result_, network::mojom::CertVerifierDebugInfo::New());
+
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+
+ // Expect no reports in uma_only mode.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+}
+
+TEST_F(TrialComparisonCertVerifierControllerTest,
+ IncognitoOfficialBuildTrialEnabled) {
+ TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
+ scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
+ scoped_feature_->InitAndEnableFeature(
+ features::kCertDualVerificationTrialFeature);
+ CreateController(profile()->GetOffTheRecordProfile());
+
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Enable the SBER pref, shouldn't matter since it's an incognito profile.
+ safe_browsing::SetExtendedReportingPref(pref_service(), true);
+
+ // Trial still not allowed, and OnTrialConfigUpdated should not be called
+ // either.
+ EXPECT_FALSE(trial_controller().IsAllowed());
+
+ // Attempting to send a report should also do nothing.
+ report_client()->SendTrialReport(
+ "hostname", leaf_cert_1_, false, false, false, false, ok_result_,
+ ok_result_, network::mojom::CertVerifierDebugInfo::New());
+ // Ensure any in-flight mojo calls get run.
+ base::RunLoop().RunUntilIdle();
+ // Expect no report since the trial is not allowed.
+ reporting_service_test_helper()->ExpectNoRequests(reporting_service());
+}
diff --git a/chromium/chrome/browser/net/variations_http_headers_browsertest.cc b/chromium/chrome/browser/net/variations_http_headers_browsertest.cc
new file mode 100644
index 00000000000..65b41240a37
--- /dev/null
+++ b/chromium/chrome/browser/net/variations_http_headers_browsertest.cc
@@ -0,0 +1,482 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/net/variations_http_headers.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/strcat.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_browser_main.h"
+#include "chrome/browser/chrome_browser_main_extra_parts.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "components/variations/variations_http_header_provider.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/network_connection_change_simulator.h"
+#include "content/public/test/simple_url_loader_test_helper.h"
+#include "net/base/escape.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
+#include "url/gurl.h"
+
+namespace {
+
+class VariationHeaderSetter : public ChromeBrowserMainExtraParts {
+ public:
+ VariationHeaderSetter() = default;
+ ~VariationHeaderSetter() override = default;
+
+ // ChromeBrowserMainExtraParts:
+ void PostEarlyInitialization() override {
+ // Set up some fake variations.
+ auto* variations_provider =
+ variations::VariationsHttpHeaderProvider::GetInstance();
+ variations_provider->ForceVariationIds({"12", "456", "t789"}, "");
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VariationHeaderSetter);
+};
+
+class VariationsHttpHeadersBrowserTest : public InProcessBrowserTest {
+ public:
+ VariationsHttpHeadersBrowserTest()
+ : https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
+ ~VariationsHttpHeadersBrowserTest() override = default;
+
+ void CreatedBrowserMainParts(content::BrowserMainParts* parts) override {
+ static_cast<ChromeBrowserMainParts*>(parts)->AddParts(
+ new VariationHeaderSetter());
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(server()->InitializeAndListen());
+ InProcessBrowserTest::SetUp();
+ }
+
+ void SetUpOnMainThread() override {
+ InProcessBrowserTest::SetUpOnMainThread();
+
+ content::NetworkConnectionChangeSimulator().SetConnectionType(
+ network::mojom::ConnectionType::CONNECTION_ETHERNET);
+
+ host_resolver()->AddRule("*", "127.0.0.1");
+
+ base::FilePath test_data_dir;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
+ server()->ServeFilesFromDirectory(test_data_dir);
+
+ server()->RegisterRequestHandler(
+ base::BindRepeating(&VariationsHttpHeadersBrowserTest::RequestHandler,
+ base::Unretained(this)));
+
+ server()->StartAcceptingConnections();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InProcessBrowserTest::SetUpCommandLine(command_line);
+
+ command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
+ }
+
+ const net::EmbeddedTestServer* server() const { return &https_server_; }
+ net::EmbeddedTestServer* server() { return &https_server_; }
+
+ GURL GetGoogleUrlWithPath(const std::string& path) const {
+ return server()->GetURL("www.google.com", path);
+ }
+
+ GURL GetGoogleUrl() const { return GetGoogleUrlWithPath("/landing.html"); }
+
+ GURL GetGoogleRedirectUrl1() const {
+ return GetGoogleUrlWithPath("/redirect");
+ }
+
+ GURL GetGoogleRedirectUrl2() const {
+ return GetGoogleUrlWithPath("/redirect2");
+ }
+
+ GURL GetExampleUrlWithPath(const std::string& path) const {
+ return server()->GetURL("www.example.com", path);
+ }
+
+ GURL GetExampleUrl() const { return GetExampleUrlWithPath("/landing.html"); }
+
+ // Returns whether a given |header| has been received for a |url|. If
+ // |url| has not been observed, fails an EXPECT and returns false.
+ bool HasReceivedHeader(const GURL& url, const std::string& header) const {
+ auto it = received_headers_.find(url);
+ EXPECT_TRUE(it != received_headers_.end());
+ if (it == received_headers_.end())
+ return false;
+ return it->second.find(header) != it->second.end();
+ }
+
+ void ClearReceivedHeaders() { received_headers_.clear(); }
+
+ bool FetchResource(const GURL& url) {
+ if (!url.is_valid())
+ return false;
+ std::string script(
+ "var xhr = new XMLHttpRequest();"
+ "xhr.open('GET', '");
+ script += url.spec() +
+ "', true);"
+ "xhr.onload = function (e) {"
+ " if (xhr.readyState === 4) {"
+ " window.domAutomationController.send(xhr.status === 200);"
+ " }"
+ "};"
+ "xhr.onerror = function () {"
+ " window.domAutomationController.send(false);"
+ "};"
+ "xhr.send(null)";
+ return ExecuteScript(script);
+ }
+
+ content::WebContents* GetWebContents() {
+ return browser()->tab_strip_model()->GetActiveWebContents();
+ }
+
+ // Registers a service worker for google.com root scope.
+ void RegisterServiceWorker(const std::string& worker_path) {
+ GURL url =
+ GetGoogleUrlWithPath("/service_worker/create_service_worker.html");
+ EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+ EXPECT_EQ("DONE", EvalJs(GetWebContents(),
+ base::StringPrintf("register('%s', '/');",
+ worker_path.c_str())));
+ }
+
+ // Registers the given service worker for google.com then tests navigation and
+ // subresource requests through the worker have X-Client-Data when
+ // appropriate.
+ void ServiceWorkerTest(const std::string& worker_path) {
+ RegisterServiceWorker(worker_path);
+
+ // Navigate to a Google URL.
+ GURL page_url =
+ GetGoogleUrlWithPath("/service_worker/fetch_from_page.html");
+ ui_test_utils::NavigateToURL(browser(), page_url);
+ EXPECT_TRUE(HasReceivedHeader(page_url, "X-Client-Data"));
+ // Check that there is a controller to check that the test is really testing
+ // service worker.
+ EXPECT_EQ(true,
+ EvalJs(GetWebContents(), "!!navigator.serviceWorker.controller"));
+
+ // Verify subresource requests from the page also have X-Client-Data.
+ EXPECT_EQ("hello", EvalJs(GetWebContents(),
+ base::StrCat({"fetch_from_page('",
+ GetGoogleUrl().spec(), "');"})));
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleUrl(), "X-Client-Data"));
+
+ // But not if they are to non-Google domains.
+ EXPECT_EQ("hello", EvalJs(GetWebContents(),
+ base::StrCat({"fetch_from_page('",
+ GetExampleUrl().spec(), "');"})));
+ EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
+ }
+
+ // Creates a worker and tests that the main script and import scripts have
+ // X-Client-Data when appropriate. |page| is the page that creates the
+ // specified |worker|, which should be an "import_*_worker.js" script that is
+ // expected to import "empty.js" (as a relative path) and also accept an
+ // "import=" parameter specifying another script to import. This allows
+ // testing that the empty.js import request for google.com has the header, and
+ // an import request to example.com does not have the header.
+ void WorkerScriptTest(const std::string& page, const std::string& worker) {
+ // Build a worker URL for a google.com worker that imports
+ // an example.com script.
+ GURL absolute_import = GetExampleUrlWithPath("/workers/empty.js");
+ const std::string worker_path = base::StrCat(
+ {worker, "?import=",
+ net::EscapeQueryParamValue(absolute_import.spec(), false)});
+ GURL worker_url = GetGoogleUrlWithPath(worker_path);
+
+ // Build the page URL that tells the page to create the worker.
+ const std::string page_path = base::StrCat(
+ {page,
+ "?worker_url=", net::EscapeQueryParamValue(worker_url.spec(), false)});
+ GURL page_url = GetGoogleUrlWithPath(page_path);
+
+ // Navigate and test.
+ EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
+ EXPECT_EQ("DONE", EvalJs(GetWebContents(), "waitForMessage();"));
+
+ // The header should be on the main script request.
+ EXPECT_TRUE(HasReceivedHeader(worker_url, "X-Client-Data"));
+
+ // And on import script requests to Google.
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleUrlWithPath("/workers/empty.js"),
+ "X-Client-Data"));
+
+ // But not on requests not to Google.
+ EXPECT_FALSE(HasReceivedHeader(absolute_import, "X-Client-Data"));
+ }
+
+ private:
+ bool ExecuteScript(const std::string& script) {
+ bool xhr_result = false;
+ // The JS call will fail if disallowed because the process will be killed.
+ bool execute_result =
+ ExecuteScriptAndExtractBool(GetWebContents(), script, &xhr_result);
+ return xhr_result && execute_result;
+ }
+
+ // Custom request handler that record request headers and simulates a redirect
+ // from google.com to example.com.
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request);
+
+ net::EmbeddedTestServer https_server_;
+
+ // Stores the observed HTTP Request headers.
+ std::map<GURL, net::test_server::HttpRequest::HeaderMap> received_headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsHttpHeadersBrowserTest);
+};
+
+std::unique_ptr<net::test_server::HttpResponse>
+VariationsHttpHeadersBrowserTest::RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ // Retrieve the host name (without port) from the request headers.
+ std::string host = "";
+ if (request.headers.find("Host") != request.headers.end())
+ host = request.headers.find("Host")->second;
+ if (host.find(':') != std::string::npos)
+ host = host.substr(0, host.find(':'));
+
+ // Recover the original URL of the request by replacing the host name in
+ // request.GetURL() (which is 127.0.0.1) with the host name from the request
+ // headers.
+ url::Replacements<char> replacements;
+ replacements.SetHost(host.c_str(), url::Component(0, host.length()));
+ GURL original_url = request.GetURL().ReplaceComponents(replacements);
+
+ // Memorize the request headers for this URL for later verification.
+ received_headers_[original_url] = request.headers;
+
+ // Set up a test server that redirects according to the
+ // following redirect chain:
+ // https://www.google.com:<port>/redirect
+ // --> https://www.google.com:<port>/redirect2
+ // --> https://www.example.com:<port>/
+ auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
+ if (request.relative_url == GetGoogleRedirectUrl1().path()) {
+ http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", GetGoogleRedirectUrl2().spec());
+ } else if (request.relative_url == GetGoogleRedirectUrl2().path()) {
+ http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", GetExampleUrl().spec());
+ } else if (request.relative_url == GetExampleUrl().path()) {
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("hello");
+ http_response->set_content_type("text/plain");
+ } else {
+ return nullptr;
+ }
+ return http_response;
+}
+
+} // namespace
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to network requests to Google but stripped on redirects.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
+ TestStrippingHeadersFromResourceRequest) {
+ ui_test_utils::NavigateToURL(browser(), GetGoogleRedirectUrl1());
+
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
+ EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
+}
+
+// Verify in an integration that that the variations header (X-Client-Data) is
+// correctly attached and stripped from network requests.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
+ TestStrippingHeadersFromSubresourceRequest) {
+ GURL url = server()->GetURL("/simple_page.html");
+ ui_test_utils::NavigateToURL(browser(), url);
+ EXPECT_TRUE(FetchResource(GetGoogleRedirectUrl1()));
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
+ EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ VariationsHttpHeadersBrowserTest,
+ TestStrippingHeadersFromRequestUsingSimpleURLLoaderWithProfileNetworkContext) {
+ GURL url = GetGoogleRedirectUrl1();
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+
+ std::unique_ptr<network::SimpleURLLoader> loader =
+ variations::CreateSimpleURLLoaderWithVariationsHeaderUnknownSignedIn(
+ std::move(resource_request), variations::InIncognito::kNo,
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ content::StoragePartition* partition =
+ content::BrowserContext::GetDefaultStoragePartition(browser()->profile());
+ network::SharedURLLoaderFactory* loader_factory =
+ partition->GetURLLoaderFactoryForBrowserProcess().get();
+ content::SimpleURLLoaderTestHelper loader_helper;
+ loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory, loader_helper.GetCallback());
+
+ // Wait for the response to complete.
+ loader_helper.WaitForCallback();
+ EXPECT_EQ(net::OK, loader->NetError());
+ EXPECT_TRUE(loader_helper.response_body());
+
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
+ EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ VariationsHttpHeadersBrowserTest,
+ TestStrippingHeadersFromRequestUsingSimpleURLLoaderWithGlobalSystemNetworkContext) {
+ GURL url = GetGoogleRedirectUrl1();
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+
+ std::unique_ptr<network::SimpleURLLoader> loader =
+ variations::CreateSimpleURLLoaderWithVariationsHeaderUnknownSignedIn(
+ std::move(resource_request), variations::InIncognito::kNo,
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ network::SharedURLLoaderFactory* loader_factory =
+ g_browser_process->system_network_context_manager()
+ ->GetSharedURLLoaderFactory()
+ .get();
+ content::SimpleURLLoaderTestHelper loader_helper;
+ loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory, loader_helper.GetCallback());
+
+ // Wait for the response to complete.
+ loader_helper.WaitForCallback();
+ EXPECT_EQ(net::OK, loader->NetError());
+ EXPECT_TRUE(loader_helper.response_body());
+
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
+ EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
+ EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
+}
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to service worker navigation preload requests. Regression test
+// for https://crbug.com/873061.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
+ ServiceWorkerNavigationPreload) {
+ // Register a service worker that uses navigation preload.
+ RegisterServiceWorker("/service_worker/navigation_preload_worker.js");
+
+ // Verify "X-Client-Data" is present on the navigation to Google.
+ // Also test that "Service-Worker-Navigation-Preload" is present to verify
+ // we are really testing the navigation preload request.
+ ui_test_utils::NavigateToURL(browser(), GetGoogleUrl());
+ EXPECT_TRUE(HasReceivedHeader(GetGoogleUrl(), "X-Client-Data"));
+ EXPECT_TRUE(
+ HasReceivedHeader(GetGoogleUrl(), "Service-Worker-Navigation-Preload"));
+}
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to requests after the service worker falls back to network.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
+ ServiceWorkerNetworkFallback) {
+ ServiceWorkerTest("/service_worker/network_fallback_worker.js");
+}
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to requests after the service worker does
+// respondWith(fetch(request)).
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
+ ServiceWorkerRespondWithFetch) {
+ ServiceWorkerTest("/service_worker/respond_with_fetch_worker.js");
+}
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to requests for service worker scripts when installing and updating.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, ServiceWorkerScript) {
+ // Register a service worker that imports scripts.
+ GURL absolute_import = GetExampleUrlWithPath("/service_worker/empty.js");
+ const std::string worker_path =
+ "/service_worker/import_scripts_worker.js?import=" +
+ net::EscapeQueryParamValue(absolute_import.spec(), false);
+ RegisterServiceWorker(worker_path);
+
+ // Test that the header is present on the main script request.
+ EXPECT_TRUE(
+ HasReceivedHeader(GetGoogleUrlWithPath(worker_path), "X-Client-Data"));
+
+ // And on import script requests to Google.
+ EXPECT_TRUE(HasReceivedHeader(
+ GetGoogleUrlWithPath("/service_worker/empty.js"), "X-Client-Data"));
+
+ // But not on requests not to Google.
+ EXPECT_FALSE(HasReceivedHeader(absolute_import, "X-Client-Data"));
+
+ // Prepare for the update case.
+ ClearReceivedHeaders();
+
+ // Tries to update the service worker.
+ EXPECT_EQ("DONE", EvalJs(GetWebContents(), "update();"));
+
+ // Test that the header is present on the main script request.
+ EXPECT_TRUE(
+ HasReceivedHeader(GetGoogleUrlWithPath(worker_path), "X-Client-Data"));
+
+ if (blink::ServiceWorkerUtils::IsImportedScriptUpdateCheckEnabled()) {
+ // And on import script requests to Google.
+ EXPECT_TRUE(HasReceivedHeader(
+ GetGoogleUrlWithPath("/service_worker/empty.js"), "X-Client-Data"));
+ // But not on requests not to Google.
+ EXPECT_FALSE(HasReceivedHeader(absolute_import, "X-Client-Data"));
+ }
+}
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to requests for shared worker scripts.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, SharedWorkerScript) {
+ WorkerScriptTest("/workers/create_shared_worker.html",
+ "/workers/import_scripts_shared_worker.js");
+}
+
+// Verify in an integration test that the variations header (X-Client-Data) is
+// attached to requests for dedicated worker scripts.
+IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
+ DedicatedWorkerScript) {
+ WorkerScriptTest("/workers/create_dedicated_worker.html",
+ "/workers/import_scripts_dedicated_worker.js");
+}
diff --git a/chromium/chrome/browser/net/websocket_browsertest.cc b/chromium/chrome/browser/net/websocket_browsertest.cc
new file mode 100644
index 00000000000..03853a6a518
--- /dev/null
+++ b/chromium/chrome/browser/net/websocket_browsertest.cc
@@ -0,0 +1,357 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/login/login_handler.h"
+#include "chrome/browser/ui/login/login_handler_test_utils.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/test/test_data_directory.h"
+#include "url/gurl.h"
+
+namespace {
+
+class WebSocketBrowserTest : public InProcessBrowserTest {
+ public:
+ WebSocketBrowserTest()
+ : ws_server_(net::SpawnedTestServer::TYPE_WS,
+ net::GetWebSocketTestDataDirectory()),
+ wss_server_(net::SpawnedTestServer::TYPE_WSS,
+ SSLOptions(SSLOptions::CERT_OK),
+ net::GetWebSocketTestDataDirectory()) {}
+
+ protected:
+ void NavigateToHTTP(const std::string& path) {
+ // Visit a HTTP page for testing.
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("http");
+ ui_test_utils::NavigateToURL(
+ browser(), ws_server_.GetURL(path).ReplaceComponents(replacements));
+ }
+
+ void NavigateToHTTPS(const std::string& path) {
+ // Visit a HTTPS page for testing.
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("https");
+ ui_test_utils::NavigateToURL(
+ browser(), wss_server_.GetURL(path).ReplaceComponents(replacements));
+ }
+
+ // Prepare the title watcher.
+ void SetUpOnMainThread() override {
+ watcher_.reset(new content::TitleWatcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16("PASS")));
+ watcher_->AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
+ }
+
+ void TearDownOnMainThread() override { watcher_.reset(); }
+
+ std::string WaitAndGetTitle() {
+ return base::UTF16ToUTF8(watcher_->WaitAndGetTitle());
+ }
+
+ net::SpawnedTestServer ws_server_;
+ net::SpawnedTestServer wss_server_;
+
+ private:
+ typedef net::SpawnedTestServer::SSLOptions SSLOptions;
+ std::unique_ptr<content::TitleWatcher> watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketBrowserTest);
+};
+
+// Framework for tests using the connect_to.html page served by a separate HTTP
+// server.
+class WebSocketBrowserConnectToTest : public WebSocketBrowserTest {
+ protected:
+ WebSocketBrowserConnectToTest() {
+ http_server_.ServeFilesFromSourceDirectory(
+ net::GetWebSocketTestDataDirectory());
+ }
+
+ // The title watcher and HTTP server are set up automatically by the test
+ // framework. Each test case still needs to configure and start the
+ // WebSocket server(s) it needs.
+ void SetUpOnMainThread() override {
+ WebSocketBrowserTest::SetUpOnMainThread();
+ ASSERT_TRUE(http_server_.Start());
+ }
+
+ // Supply a ws: or wss: URL to connect to.
+ void ConnectTo(GURL url) {
+ ASSERT_TRUE(http_server_.Started());
+ std::string query("url=" + url.spec());
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query);
+ ui_test_utils::NavigateToURL(browser(),
+ http_server_.GetURL("/connect_to.html")
+ .ReplaceComponents(replacements));
+ }
+
+ private:
+ net::EmbeddedTestServer http_server_;
+};
+
+// Automatically fill in any login prompts that appear with the supplied
+// credentials.
+class AutoLogin : public content::NotificationObserver {
+ public:
+ AutoLogin(const std::string& username,
+ const std::string& password,
+ content::NavigationController* navigation_controller)
+ : username_(base::UTF8ToUTF16(username)),
+ password_(base::UTF8ToUTF16(password)),
+ logged_in_(false) {
+ registrar_.Add(
+ this,
+ chrome::NOTIFICATION_AUTH_NEEDED,
+ content::Source<content::NavigationController>(navigation_controller));
+ }
+
+ // NotificationObserver implementation
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override {
+ DCHECK_EQ(chrome::NOTIFICATION_AUTH_NEEDED, type);
+ LoginHandler* login_handler =
+ content::Details<LoginNotificationDetails>(details)->handler();
+ login_handler->SetAuth(username_, password_);
+ logged_in_ = true;
+ }
+
+ bool logged_in() const { return logged_in_; }
+
+ private:
+ const base::string16 username_;
+ const base::string16 password_;
+ bool logged_in_;
+
+ content::NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoLogin);
+};
+
+// Test that the browser can handle a WebSocket frame split into multiple TCP
+// segments.
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, WebSocketSplitSegments) {
+ // Launch a WebSocket server.
+ ASSERT_TRUE(ws_server_.Start());
+
+ NavigateToHTTP("split_packet_check.html");
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, SecureWebSocketSplitRecords) {
+ // Launch a secure WebSocket server.
+ ASSERT_TRUE(wss_server_.Start());
+
+ NavigateToHTTPS("split_packet_check.html");
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+// Flaky failing on Win10 only. http://crbug.com/616958
+#if defined(OS_WIN)
+#define MAYBE_SendCloseFrameWhenTabIsClosed \
+ DISABLED_SendCloseFrameWhenTabIsClosed
+#else
+#define MAYBE_SendCloseFrameWhenTabIsClosed SendCloseFrameWhenTabIsClosed
+#endif
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest,
+ MAYBE_SendCloseFrameWhenTabIsClosed) {
+ // Launch a WebSocket server.
+ ASSERT_TRUE(ws_server_.Start());
+
+ {
+ // Create a new tab, establish a WebSocket connection and close the tab.
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::unique_ptr<content::WebContents> new_tab =
+ content::WebContents::Create(
+ content::WebContents::CreateParams(tab->GetBrowserContext()));
+ content::WebContents* raw_new_tab = new_tab.get();
+ browser()->tab_strip_model()->AppendWebContents(std::move(new_tab), true);
+ ASSERT_EQ(raw_new_tab, browser()->tab_strip_model()->GetWebContentsAt(1));
+
+ content::TitleWatcher connected_title_watcher(
+ raw_new_tab, base::ASCIIToUTF16("CONNECTED"));
+ connected_title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16("CLOSED"));
+ NavigateToHTTP("connect_and_be_observed.html");
+ const base::string16 result = connected_title_watcher.WaitAndGetTitle();
+ EXPECT_TRUE(base::EqualsASCII(result, "CONNECTED"));
+
+ content::WebContentsDestroyedWatcher destroyed_watcher(raw_new_tab);
+ browser()->tab_strip_model()->CloseWebContentsAt(1, 0);
+ destroyed_watcher.Wait();
+ }
+
+ NavigateToHTTP("close_observer.html");
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, WebSocketBasicAuthInHTTPURL) {
+ // Launch a basic-auth-protected WebSocket server.
+ ws_server_.set_websocket_basic_auth(true);
+ ASSERT_TRUE(ws_server_.Start());
+
+ // Open connect_check.html via HTTP with credentials in the URL.
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("http");
+ ui_test_utils::NavigateToURL(
+ browser(),
+ ws_server_.GetURLWithUserAndPassword("connect_check.html", "test", "test")
+ .ReplaceComponents(replacements));
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, WebSocketBasicAuthInHTTPSURL) {
+ // Launch a basic-auth-protected secure WebSocket server.
+ wss_server_.set_websocket_basic_auth(true);
+ ASSERT_TRUE(wss_server_.Start());
+
+ // Open connect_check.html via HTTPS with credentials in the URL.
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("https");
+ ui_test_utils::NavigateToURL(
+ browser(),
+ wss_server_.GetURLWithUserAndPassword(
+ "connect_check.html", "test", "test")
+ .ReplaceComponents(replacements));
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+// This test verifies that login details entered by the user into the login
+// prompt to authenticate the main page are re-used for WebSockets from the same
+// origin.
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest,
+ ReuseMainPageBasicAuthCredentialsForWebSocket) {
+ // Launch a basic-auth-protected WebSocket server.
+ ws_server_.set_websocket_basic_auth(true);
+ ASSERT_TRUE(ws_server_.Start());
+
+ content::NavigationController* navigation_controller =
+ &browser()->tab_strip_model()->GetActiveWebContents()->GetController();
+ AutoLogin auto_login("test", "test", navigation_controller);
+
+ WindowedAuthNeededObserver auth_needed_waiter(navigation_controller);
+ NavigateToHTTP("connect_check.html");
+ auth_needed_waiter.Wait();
+
+ EXPECT_TRUE(auto_login.logged_in());
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
+ WebSocketBasicAuthInWSURL) {
+ // Launch a basic-auth-protected WebSocket server.
+ ws_server_.set_websocket_basic_auth(true);
+ ASSERT_TRUE(ws_server_.Start());
+
+ ConnectTo(ws_server_.GetURLWithUserAndPassword(
+ "echo-with-no-extension", "test", "test"));
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
+ WebSocketBasicAuthInWSURLBadCreds) {
+ // Launch a basic-auth-protected WebSocket server.
+ ws_server_.set_websocket_basic_auth(true);
+ ASSERT_TRUE(ws_server_.Start());
+
+ ConnectTo(ws_server_.GetURLWithUserAndPassword(
+ "echo-with-no-extension", "wrong-user", "wrong-password"));
+
+ EXPECT_EQ("FAIL", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
+ WebSocketBasicAuthNoCreds) {
+ // Launch a basic-auth-protected WebSocket server.
+ ws_server_.set_websocket_basic_auth(true);
+ ASSERT_TRUE(ws_server_.Start());
+
+ ConnectTo(ws_server_.GetURL("echo-with-no-extension"));
+
+ EXPECT_EQ("FAIL", WaitAndGetTitle());
+}
+
+// HTTPS connection limits should not be applied to wss:. This is only tested
+// for secure connections here because the unencrypted case is tested in the
+// Blink layout tests, and browser tests are expensive to run.
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, SSLConnectionLimit) {
+ ASSERT_TRUE(wss_server_.Start());
+
+ NavigateToHTTPS("multiple-connections.html");
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+// Regression test for crbug.com/903553005
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, WebSocketAppliesHSTS) {
+ net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ https_server.SetSSLConfig(
+ net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
+ https_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+ net::SpawnedTestServer wss_server(
+ net::SpawnedTestServer::TYPE_WSS,
+ net::SpawnedTestServer::SSLOptions(
+ net::SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN),
+ net::GetWebSocketTestDataDirectory());
+ // This test sets HSTS on localhost. To avoid being redirected to https, start
+ // the http server on 127.0.0.1 instead.
+ net::EmbeddedTestServer http_server;
+ http_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+ ASSERT_TRUE(https_server.Start());
+ ASSERT_TRUE(http_server.Start());
+ ASSERT_TRUE(wss_server.StartInBackground());
+
+ // Set HSTS on localhost.
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16("SET"));
+ ui_test_utils::NavigateToURL(browser(),
+ https_server.GetURL("/websocket/set-hsts.html"));
+ const base::string16 result = title_watcher.WaitAndGetTitle();
+ EXPECT_TRUE(base::EqualsASCII(result, "SET"));
+
+ // Verify that it applies to WebSockets.
+ ASSERT_TRUE(wss_server.BlockUntilStarted());
+ GURL wss_url = wss_server.GetURL("echo-with-no-extension");
+ std::string scheme("ws");
+ GURL::Replacements scheme_replacement;
+ scheme_replacement.SetSchemeStr(scheme);
+ GURL ws_url = wss_url.ReplaceComponents(scheme_replacement);
+
+ // An https: URL won't work here here because the mixed content policy
+ // disallows connections to unencrypted WebSockets from encrypted pages.
+ GURL http_url =
+ http_server.GetURL("/websocket/check-hsts.html#" + ws_url.spec());
+
+ ui_test_utils::NavigateToURL(browser(), http_url);
+
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+} // namespace
diff --git a/chromium/chrome/browser/prefs/DEPS b/chromium/chrome/browser/prefs/DEPS
new file mode 100644
index 00000000000..d5dd4a0bf76
--- /dev/null
+++ b/chromium/chrome/browser/prefs/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+services/preferences/public/mojom",
+]
+# TODO(crbug.com/654988): Remove once the pref service is always used.
+specific_include_rules = {
+ "profile_pref_store_manager\.cc": [
+ "+services/preferences/tracked",
+ ],
+}
diff --git a/chromium/chrome/browser/prefs/OWNERS b/chromium/chrome/browser/prefs/OWNERS
new file mode 100644
index 00000000000..538512cd888
--- /dev/null
+++ b/chromium/chrome/browser/prefs/OWNERS
@@ -0,0 +1,7 @@
+battre@chromium.org
+gab@chromium.org
+
+per-file pref_service_incognito_whitelist.cc=rhalavati@chromium.org
+
+# COMPONENT: UI>Browser>Preferences
+# TEAM: chromium-dev@chromium.org
diff --git a/chromium/chrome/browser/prefs/browser_prefs.cc b/chromium/chrome/browser/prefs/browser_prefs.cc
new file mode 100644
index 00000000000..a960db92420
--- /dev/null
+++ b/chromium/chrome/browser/prefs/browser_prefs.cc
@@ -0,0 +1,1223 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/browser_prefs.h"
+
+#include <string>
+
+#include "base/trace_event/trace_event.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/browser/about_flags.h"
+#include "chrome/browser/accessibility/accessibility_labels_service.h"
+#include "chrome/browser/accessibility/accessibility_ui.h"
+#include "chrome/browser/accessibility/invert_bubble_prefs.h"
+#include "chrome/browser/availability/availability_prober.h"
+#include "chrome/browser/browser_process_impl.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/chromeos/policy/tpm_auto_update_mode_policy_handler.h"
+#include "chrome/browser/chromeos/scheduler_configuration_manager.h"
+#include "chrome/browser/component_updater/component_updater_prefs.h"
+#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
+#include "chrome/browser/devtools/devtools_window.h"
+#include "chrome/browser/download/download_prefs.h"
+#include "chrome/browser/engagement/important_sites_util.h"
+#include "chrome/browser/external_protocol/external_protocol_handler.h"
+#include "chrome/browser/first_run/first_run.h"
+#include "chrome/browser/gpu/gpu_mode_manager.h"
+#include "chrome/browser/intranet_redirect_detector.h"
+#include "chrome/browser/lifetime/browser_shutdown.h"
+#include "chrome/browser/media/media_device_id_salt.h"
+#include "chrome/browser/media/media_engagement_service.h"
+#include "chrome/browser/media/media_storage_id_salt.h"
+#include "chrome/browser/media/router/media_router_feature.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
+#include "chrome/browser/memory/enterprise_memory_limit_pref_observer.h"
+#include "chrome/browser/metrics/chrome_metrics_service_client.h"
+#include "chrome/browser/net/net_error_tab_helper.h"
+#include "chrome/browser/net/prediction_options.h"
+#include "chrome/browser/net/profile_network_context_service.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/notifications/notification_channels_provider_android.h"
+#include "chrome/browser/notifications/notifier_state_tracker.h"
+#include "chrome/browser/notifications/platform_notification_service_impl.h"
+#include "chrome/browser/pepper_flash_settings_manager.h"
+#include "chrome/browser/policy/developer_tools_policy_handler.h"
+#include "chrome/browser/policy/webusb_allow_devices_for_urls_policy_handler.h"
+#include "chrome/browser/prefs/chrome_pref_service_factory.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/prefs/origin_trial_prefs.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/browser/previews/previews_lite_page_redirect_decider.h"
+#include "chrome/browser/previews/previews_offline_helper.h"
+#include "chrome/browser/profiles/chrome_version_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_impl.h"
+#include "chrome/browser/profiles/profile_info_cache.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/push_messaging/push_messaging_app_identifier.h"
+#include "chrome/browser/renderer_host/pepper/device_id_fetcher.h"
+#include "chrome/browser/rlz/chrome_rlz_tracker_delegate.h"
+#include "chrome/browser/search/search.h"
+#include "chrome/browser/sharing/sharing_sync_preference.h"
+#include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
+#include "chrome/browser/ssl/ssl_config_service_manager.h"
+#include "chrome/browser/task_manager/task_manager_interface.h"
+#include "chrome/browser/tracing/chrome_tracing_delegate.h"
+#include "chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker.h"
+#include "chrome/browser/ui/browser_ui_prefs.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/navigation_correction_tab_observer.h"
+#include "chrome/browser/ui/network_profile_bubble.h"
+#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
+#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
+#include "chrome/browser/ui/tabs/pinned_tab_codec.h"
+#include "chrome/browser/ui/webui/flags_ui.h"
+#include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
+#include "chrome/browser/ui/webui/print_preview/policy_settings.h"
+#include "chrome/browser/ui/webui/print_preview/sticky_settings.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/secure_origin_whitelist.h"
+#include "components/autofill/core/common/autofill_prefs.h"
+#include "components/browsing_data/core/pref_names.h"
+#include "components/certificate_transparency/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_prefs.h"
+#include "components/dom_distiller/core/distilled_page_prefs.h"
+#include "components/feature_engagement/buildflags.h"
+#include "components/flags_ui/pref_service_flags_storage.h"
+#include "components/gcm_driver/gcm_channel_status_syncer.h"
+#include "components/image_fetcher/core/cache/image_cache.h"
+#include "components/invalidation/impl/invalidator_registrar_with_memory.h"
+#include "components/invalidation/impl/per_user_topic_registration_manager.h"
+#include "components/language/content/browser/geo_language_provider.h"
+#include "components/language/content/browser/ulp_language_code_locator/ulp_language_code_locator.h"
+#include "components/language/core/browser/language_prefs.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/network_time/network_time_tracker.h"
+#include "components/ntp_snippets/content_suggestions_service.h"
+#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
+#include "components/ntp_snippets/remote/remote_suggestions_scheduler_impl.h"
+#include "components/ntp_snippets/remote/request_throttler.h"
+#include "components/ntp_snippets/user_classifier.h"
+#include "components/ntp_tiles/most_visited_sites.h"
+#include "components/offline_pages/buildflags/buildflags.h"
+#include "components/omnibox/browser/document_provider.h"
+#include "components/omnibox/browser/zero_suggest_provider.h"
+#include "components/optimization_guide/optimization_guide_prefs.h"
+#include "components/password_manager/core/browser/password_bubble_experiment.h"
+#include "components/password_manager/core/browser/password_manager.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/payments/core/payment_prefs.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/browser/url_blacklist_manager.h"
+#include "components/policy/core/common/policy_statistics_collector.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
+#include "components/rappor/rappor_service_impl.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "components/search_engines/template_url_prepopulate_data.h"
+#include "components/sessions/core/session_id_generator.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/subresource_filter/content/browser/ruleset_service.h"
+#include "components/sync/base/sync_prefs.h"
+#include "components/sync_device_info/device_info_prefs.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+#include "components/sync_sessions/session_sync_prefs.h"
+#include "components/translate/core/browser/translate_prefs.h"
+#include "components/update_client/update_client.h"
+#include "components/variations/service/variations_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/buildflags/buildflags.h"
+#include "net/http/http_server_properties_manager.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "printing/buildflags/buildflags.h"
+#include "rlz/buildflags/buildflags.h"
+
+#if BUILDFLAG(ENABLE_APP_LIST)
+#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
+#endif
+
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+#include "chrome/browser/background/background_mode_manager.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/accessibility/animation_policy_prefs.h"
+#include "chrome/browser/apps/platform_apps/shortcut_manager.h"
+#include "chrome/browser/extensions/activity_log/activity_log.h"
+#include "chrome/browser/extensions/api/commands/command_service.h"
+#include "chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h"
+#include "chrome/browser/extensions/api/tabs/tabs_api.h"
+#include "chrome/browser/extensions/extension_web_ui.h"
+#include "chrome/browser/extensions/ntp_overridden_bubble_delegate.h"
+#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
+#include "chrome/browser/ui/webui/extensions/extensions_ui.h"
+#include "extensions/browser/api/audio/audio_api.h"
+#include "extensions/browser/api/runtime/runtime_api.h"
+#include "extensions/browser/extension_prefs.h"
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
+#include "chrome/browser/chromeos/kerberos/kerberos_credentials_manager.h"
+#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service.h"
+#include "chrome/browser/chromeos/settings/stats_reporting_controller.h"
+#include "chrome/browser/component_updater/metadata_table_chromeos.h"
+#else
+#include "chrome/browser/extensions/api/enterprise_reporting_private/prefs.h"
+#endif
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+#include "chrome/browser/offline_pages/prefetch/offline_metrics_collector_impl.h"
+#include "chrome/browser/offline_pages/prefetch/prefetch_background_task_handler_impl.h"
+#include "components/offline_pages/core/prefetch/prefetch_prefs.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "chrome/browser/plugins/plugin_info_host_impl.h"
+#include "chrome/browser/plugins/plugins_resource_service.h"
+#endif
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+#include "chrome/browser/supervised_user/child_accounts/child_account_service.h"
+#include "chrome/browser/supervised_user/supervised_user_service.h"
+#include "chrome/browser/supervised_user/supervised_user_whitelist_service.h"
+#endif
+
+#if BUILDFLAG(ENABLE_SERVICE_DISCOVERY)
+#include "chrome/browser/ui/webui/local_discovery/local_discovery_ui.h"
+#endif
+
+#if BUILDFLAG(ENABLE_LEGACY_DESKTOP_IN_PRODUCT_HELP)
+#include "chrome/browser/feature_engagement/session_duration_updater.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/bookmarks/partner_bookmarks_shim.h"
+#include "chrome/browser/android/explore_sites/history_statistics_reporter.h"
+#include "chrome/browser/android/ntp/recent_tabs_page_prefs.h"
+#include "chrome/browser/android/oom_intervention/oom_intervention_decider.h"
+#include "chrome/browser/android/preferences/browser_prefs_android.h"
+#include "chrome/browser/android/usage_stats/usage_stats_bridge.h"
+#include "chrome/browser/geolocation/geolocation_permission_context_android.h"
+#include "chrome/browser/media/android/cdm/media_drm_origin_id_manager.h"
+#include "components/cdm/browser/media_drm_storage_impl.h"
+#include "components/feed/buildflags.h"
+#include "components/ntp_snippets/category_rankers/click_based_category_ranker.h"
+#include "components/ntp_tiles/popular_sites_impl.h"
+#if BUILDFLAG(ENABLE_FEED_IN_CHROME)
+#include "components/feed/core/feed_scheduler_host.h"
+#include "components/feed/core/refresh_throttler.h"
+#include "components/feed/core/user_classifier.h"
+#endif // BUILDFLAG(ENABLE_FEED_IN_CHROME)
+#else // defined(OS_ANDROID)
+#include "chrome/browser/enterprise_reporting/prefs.h"
+#include "chrome/browser/gcm/gcm_product_util.h"
+#include "chrome/browser/metrics/tab_stats_tracker.h"
+#include "chrome/browser/search/instant_service.h"
+#include "chrome/browser/search/promos/promo_service.h"
+#include "chrome/browser/search/search_suggest/search_suggest_service.h"
+#include "chrome/browser/signin/signin_promo.h"
+#include "chrome/browser/ui/startup/startup_browser_creator.h"
+#include "chrome/browser/ui/webui/foreign_session_handler.h"
+#include "chrome/browser/ui/webui/history_ui.h"
+#include "chrome/browser/ui/webui/settings/settings_ui.h"
+#include "chrome/browser/upgrade_detector/upgrade_detector.h"
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+#include "ash/public/cpp/ash_pref_names.h"
+#include "ash/public/cpp/ash_prefs.h"
+#include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_cryptohome_remover.h"
+#include "chrome/browser/chromeos/arc/policy/arc_policy_bridge.h"
+#include "chrome/browser/chromeos/arc/session/arc_session_manager.h"
+#include "chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_service.h"
+#include "chrome/browser/chromeos/child_accounts/screen_time_controller.h"
+#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "chrome/browser/chromeos/cryptauth/cryptauth_device_id_provider_impl.h"
+#include "chrome/browser/chromeos/customization/customization_document.h"
+#include "chrome/browser/chromeos/extensions/echo_private_api.h"
+#include "chrome/browser/chromeos/extensions/login_screen/login/login_api.h"
+#include "chrome/browser/chromeos/file_system_provider/registry.h"
+#include "chrome/browser/chromeos/first_run/first_run.h"
+#include "chrome/browser/chromeos/guest_os/guest_os_pref_names.h"
+#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
+#include "chrome/browser/chromeos/login/demo_mode/demo_mode_detector.h"
+#include "chrome/browser/chromeos/login/demo_mode/demo_mode_resources_remover.h"
+#include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
+#include "chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h"
+#include "chrome/browser/chromeos/login/quick_unlock/fingerprint_storage.h"
+#include "chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs.h"
+#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
+#include "chrome/browser/chromeos/login/saml/saml_profile_prefs.h"
+#include "chrome/browser/chromeos/login/screens/reset_screen.h"
+#include "chrome/browser/chromeos/login/session/user_session_manager.h"
+#include "chrome/browser/chromeos/login/startup_utils.h"
+#include "chrome/browser/chromeos/login/users/avatar/user_image_manager.h"
+#include "chrome/browser/chromeos/login/users/avatar/user_image_sync_observer.h"
+#include "chrome/browser/chromeos/login/users/chrome_user_manager_impl.h"
+#include "chrome/browser/chromeos/login/users/multi_profile_user_controller.h"
+#include "chrome/browser/chromeos/net/network_throttling_observer.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions.h"
+#include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
+#include "chrome/browser/chromeos/policy/app_install_event_log_manager_wrapper.h"
+#include "chrome/browser/chromeos/policy/app_install_event_logger.h"
+#include "chrome/browser/chromeos/policy/auto_enrollment_client_impl.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
+#include "chrome/browser/chromeos/policy/dm_token_storage.h"
+#include "chrome/browser/chromeos/policy/external_data_handlers/device_wallpaper_image_external_data_handler.h"
+#include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
+#include "chrome/browser/chromeos/policy/status_collector/status_collector.h"
+#include "chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h"
+#include "chrome/browser/chromeos/power/power_metrics_reporter.h"
+#include "chrome/browser/chromeos/preferences.h"
+#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
+#include "chrome/browser/chromeos/printing/history/print_job_history_service.h"
+#include "chrome/browser/chromeos/printing/synced_printers_manager.h"
+#include "chrome/browser/chromeos/release_notes/release_notes_storage.h"
+#include "chrome/browser/chromeos/resource_reporter/resource_reporter.h"
+#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
+#include "chrome/browser/chromeos/settings/device_settings_cache.h"
+#include "chrome/browser/chromeos/system/automatic_reboot_manager.h"
+#include "chrome/browser/chromeos/system/input_device_settings.h"
+#include "chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h"
+#include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
+#include "chrome/browser/media/protected_media_identifier_permission_context.h"
+#include "chrome/browser/metrics/chromeos_metrics_provider.h"
+#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
+#include "chrome/browser/ui/webui/certificates_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/enable_debugging_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/hid_detection_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "chrome/browser/upgrade_detector/upgrade_detector_chromeos.h"
+#include "chromeos/audio/audio_devices_pref_handler_impl.h"
+#include "chromeos/components/account_manager/account_manager.h"
+#include "chromeos/constants/chromeos_switches.h"
+#include "chromeos/network/fast_transition_observer.h"
+#include "chromeos/network/proxy/proxy_config_handler.h"
+#include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
+#include "chromeos/services/multidevice_setup/multidevice_setup_service.h"
+#include "chromeos/timezone/timezone_resolver.h"
+#include "components/arc/arc_prefs.h"
+#include "components/invalidation/impl/fcm_invalidation_service.h"
+#include "components/invalidation/impl/invalidator_storage.h"
+#include "components/onc/onc_pref_names.h"
+#include "components/quirks/quirks_manager.h"
+#include "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h"
+#else
+#include "chrome/browser/extensions/default_apps.h"
+#endif
+
+#if defined(OS_CHROMEOS) && BUILDFLAG(ENABLE_APP_LIST)
+#include "chrome/browser/chromeos/apps/apk_web_app_service.h"
+#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
+#include "chrome/browser/ui/cocoa/confirm_quit.h"
+#include "components/os_crypt/os_crypt.h"
+#endif
+
+#if defined(OS_WIN)
+#include "chrome/browser/component_updater/sw_reporter_installer_win.h"
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#include "chrome/browser/win/conflicts/incompatible_applications_updater.h"
+#include "chrome/browser/win/conflicts/module_database.h"
+#include "chrome/browser/win/conflicts/third_party_conflicts_manager.h"
+#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#include "chrome/browser/safe_browsing/chrome_cleaner/settings_resetter_win.h"
+#include "chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_prefs_manager.h"
+#endif
+
+#if defined(OS_WIN) || defined(OS_MACOSX) || \
+ (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+#include "chrome/browser/browser_switcher/browser_switcher_prefs.h"
+#endif
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#include "chrome/browser/ui/startup/default_browser_prompt.h"
+#endif
+
+#if defined(TOOLKIT_VIEWS)
+#include "chrome/browser/ui/browser_view_prefs.h"
+#endif
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/media/unified_autoplay_config.h"
+#include "components/ntp_tiles/custom_links_manager_impl.h"
+#endif
+
+namespace {
+
+// Deprecated 8/2018.
+const char kDnsPrefetchingStartupList[] = "dns_prefetching.startup_list";
+const char kDnsPrefetchingHostReferralList[] =
+ "dns_prefetching.host_referral_list";
+
+// Deprecated 9/2018
+const char kGeolocationAccessToken[] = "geolocation.access_token";
+const char kGoogleServicesPasswordHash[] = "google.services.password_hash";
+const char kModuleConflictBubbleShown[] = "module_conflict.bubble_shown";
+const char kOptionsWindowLastTabIndex[] = "options_window.last_tab_index";
+const char kTrustedDownloadSources[] = "trusted_download_sources";
+#if defined(OS_WIN)
+const char kLastWelcomedOSVersion[] = "browser.last_welcomed_os_version";
+#endif
+const char kSupervisedUserCreationAllowed[] =
+ "profile.managed_user_creation_allowed";
+
+// Deprecated 10/2018
+const char kReverseAutologinEnabled[] = "reverse_autologin.enabled";
+
+// Deprecated 11/2018.
+const char kNetworkQualities[] = "net.network_qualities";
+const char kForceSessionSync[] = "settings.history_recorded";
+const char kOnboardDuringNUX[] = "browser.onboard_during_nux";
+const char kNuxOnboardGroup[] = "browser.onboard_group";
+// This pref is particularly large, taking up 15+% of the prefs file, so should
+// perhaps be kept around longer than the others.
+const char kHttpServerProperties[] = "net.http_server_properties";
+
+// Deprecated 1/2019.
+const char kNextUpdateCheck[] = "extensions.autoupdate.next_check";
+const char kLastUpdateCheck[] = "extensions.autoupdate.last_check";
+
+// Deprecated 3/2019.
+const char kCurrentThemeImages[] = "extensions.theme.images";
+const char kCurrentThemeColors[] = "extensions.theme.colors";
+const char kCurrentThemeTints[] = "extensions.theme.tints";
+const char kCurrentThemeDisplayProperties[] = "extensions.theme.properties";
+
+#if defined(OS_ANDROID)
+// Deprecated 4/2019.
+const char kDismissedAssetDownloadSuggestions[] =
+ "ntp_suggestions.downloads.assets.dismissed_ids";
+const char kDismissedOfflinePageDownloadSuggestions[] =
+ "ntp_suggestions.downloads.offline_pages.dismissed_ids";
+
+// Deprecated 4/2019.
+const char kBreakingNewsSubscriptionDataToken[] =
+ "ntp_suggestions.breaking_news_subscription_data.token";
+const char kBreakingNewsSubscriptionDataIsAuthenticated[] =
+ "ntp_suggestions.breaking_news_subscription_data.is_authenticated";
+const char kBreakingNewsGCMSubscriptionTokenCache[] =
+ "ntp_suggestions.breaking_news_gcm_subscription_token_cache";
+const char kBreakingNewsGCMLastTokenValidationTime[] =
+ "ntp_suggestions.breaking_news_gcm_last_token_validation_time";
+const char kBreakingNewsGCMLastForcedSubscriptionTime[] =
+ "ntp_suggestions.breaking_news_gcm_last_forced_subscription_time";
+
+// Deprecated 4/2019.
+const char kContentSuggestionsConsecutiveIgnoredPrefName[] =
+ "ntp.content_suggestions.notifications.consecutive_ignored";
+const char kContentSuggestionsNotificationsSentDay[] =
+ "ntp.content_suggestions.notifications.sent_day";
+const char kContentSuggestionsNotificationsSentCount[] =
+ "ntp.content_suggestions.notifications.sent_count";
+const char kNotificationIDWithinCategory[] =
+ "ContentSuggestionsNotificationIDWithinCategory";
+
+// Deprecated 5/2019.
+const char kContentSuggestionsNotificationsEnabled[] =
+ "ntp.content_suggestions.notifications.enabled";
+
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+// Deprecated 5/2019
+const char kSignInPromoShowOnFirstRunAllowed[] =
+ "sync_promo.show_on_first_run_allowed";
+const char kSignInPromoShowNTPBubble[] = "sync_promo.show_ntp_bubble";
+#endif // !defined(OS_ANDROID)
+
+// Deprecated 5/2019
+const char kBookmarkAppCreationLaunchType[] =
+ "extensions.bookmark_app_creation_launch_type";
+
+// Deprecated 6/2019
+const char kMediaCacheSize[] = "browser.media_cache_size";
+
+#if defined(OS_WIN)
+// Deprecated 6/2019
+const char kHasSeenWin10PromoPage[] = "browser.has_seen_win10_promo_page";
+#endif // defined(OS_WIN)
+
+// Deprecated 7/2019
+const char kSignedInTime[] = "signin.signedin_time";
+
+#if !defined(OS_ANDROID)
+// Deprecated 7/2019
+const char kNtpActivateHideShortcutsFieldTrial[] =
+ "ntp.activate_hide_shortcuts_field_trial";
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_ANDROID)
+// Deprecated 7/2019
+// WebAuthn prefs were being erroneously stored on Android. They are registered
+// on other platforms.
+const char kWebAuthnLastTransportUsedPrefName[] =
+ "webauthn.last_transport_used";
+const char kWebAuthnBlePairedMacAddressesPrefName[] =
+ "webauthn.ble.paired_mac_addresses";
+#endif // defined(OS_ANDROID)
+
+// Deprecated 7/2019
+const char kLastKnownGoogleURL[] = "browser.last_known_google_url";
+const char kLastPromptedGoogleURL[] = "browser.last_prompted_google_url";
+#if defined(USE_X11)
+constexpr char kLocalProfileId[] = "profile.local_profile_id";
+#endif
+
+// Deprecated 8/2019
+const char kInsecureExtensionUpdatesEnabled[] =
+ "extension_updates.insecure_extension_updates_enabled";
+
+const char kLastStartupTimestamp[] = "startup_metric.last_startup_timestamp";
+const char kLastStartupVersion[] = "startup_metric.last_startup_version";
+const char kSameVersionStartupCount[] =
+ "startup_metric.same_version_startup_count";
+
+// Deprecated 8/2019
+const char kHintLoadedCounts[] = "optimization_guide.hint_loaded_counts";
+
+// Deprecated 9/2019
+const char kGoogleServicesUsername[] = "google.services.username";
+const char kGoogleServicesUserAccountId[] = "google.services.user_account_id";
+const char kDataReductionProxySavingsClearedNegativeSystemClock[] =
+ "data_reduction.savings_cleared_negative_system_clock";
+
+#if defined(OS_CHROMEOS)
+// Deprecated 10/2019
+const char kDisplayRotationAcceleratorDialogHasBeenAccepted[] =
+ "settings.a11y.display_rotation_accelerator_dialog_has_been_accepted";
+#endif // defined(OS_CHROMEOS)
+
+// Register prefs used only for migration (clearing or moving to a new key).
+void RegisterProfilePrefsForMigration(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterListPref(kDnsPrefetchingStartupList);
+ registry->RegisterListPref(kDnsPrefetchingHostReferralList);
+
+ registry->RegisterStringPref(kGeolocationAccessToken, std::string());
+ registry->RegisterStringPref(kGoogleServicesPasswordHash, std::string());
+ registry->RegisterIntegerPref(kModuleConflictBubbleShown, 0);
+ registry->RegisterIntegerPref(kOptionsWindowLastTabIndex, 0);
+ registry->RegisterStringPref(kTrustedDownloadSources, std::string());
+ registry->RegisterBooleanPref(kSupervisedUserCreationAllowed, true);
+
+ registry->RegisterBooleanPref(kReverseAutologinEnabled, true);
+
+ registry->RegisterDictionaryPref(kNetworkQualities, PrefRegistry::LOSSY_PREF);
+ registry->RegisterBooleanPref(kForceSessionSync, false);
+ registry->RegisterBooleanPref(kOnboardDuringNUX, false);
+ registry->RegisterIntegerPref(kNuxOnboardGroup, 0);
+ registry->RegisterDictionaryPref(kHttpServerProperties,
+ PrefRegistry::LOSSY_PREF);
+ registry->RegisterIntegerPref(kLastUpdateCheck, 0);
+ registry->RegisterIntegerPref(kNextUpdateCheck, 0);
+
+ registry->RegisterDictionaryPref(kCurrentThemeImages);
+ registry->RegisterDictionaryPref(kCurrentThemeColors);
+ registry->RegisterDictionaryPref(kCurrentThemeTints);
+ registry->RegisterDictionaryPref(kCurrentThemeDisplayProperties);
+
+#if defined(OS_ANDROID)
+ registry->RegisterListPref(kDismissedAssetDownloadSuggestions);
+ registry->RegisterListPref(kDismissedOfflinePageDownloadSuggestions);
+
+ registry->RegisterStringPref(kBreakingNewsSubscriptionDataToken,
+ std::string());
+ registry->RegisterBooleanPref(kBreakingNewsSubscriptionDataIsAuthenticated,
+ false);
+ registry->RegisterStringPref(kBreakingNewsGCMSubscriptionTokenCache,
+ std::string());
+ registry->RegisterInt64Pref(kBreakingNewsGCMLastTokenValidationTime, 0);
+ registry->RegisterInt64Pref(kBreakingNewsGCMLastForcedSubscriptionTime, 0);
+
+ registry->RegisterIntegerPref(kContentSuggestionsConsecutiveIgnoredPrefName,
+ 0);
+ registry->RegisterIntegerPref(kContentSuggestionsNotificationsSentDay, 0);
+ registry->RegisterIntegerPref(kContentSuggestionsNotificationsSentCount, 0);
+ registry->RegisterStringPref(kNotificationIDWithinCategory, std::string());
+ registry->RegisterBooleanPref(kContentSuggestionsNotificationsEnabled, true);
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+ registry->RegisterBooleanPref(kSignInPromoShowOnFirstRunAllowed, true);
+ registry->RegisterBooleanPref(kSignInPromoShowNTPBubble, false);
+#endif // !defined(OS_ANDROID)
+
+ registry->RegisterIntegerPref(kBookmarkAppCreationLaunchType, 0);
+
+ registry->RegisterIntegerPref(kMediaCacheSize, 0);
+
+ registry->RegisterInt64Pref(kSignedInTime, 0);
+
+#if defined(OS_ANDROID)
+ registry->RegisterStringPref(kWebAuthnLastTransportUsedPrefName,
+ std::string());
+ registry->RegisterListPref(kWebAuthnBlePairedMacAddressesPrefName);
+#endif // defined(OS_ANDROID)
+
+ registry->RegisterStringPref(kLastKnownGoogleURL, std::string());
+ registry->RegisterStringPref(kLastPromptedGoogleURL, std::string());
+#if defined(USE_X11)
+ registry->RegisterIntegerPref(kLocalProfileId, 0);
+#endif
+
+ registry->RegisterBooleanPref(kInsecureExtensionUpdatesEnabled, false);
+
+ registry->RegisterDictionaryPref(kHintLoadedCounts);
+ registry->RegisterStringPref(kGoogleServicesUsername, std::string());
+ registry->RegisterStringPref(kGoogleServicesUserAccountId, std::string());
+ registry->RegisterInt64Pref(
+ kDataReductionProxySavingsClearedNegativeSystemClock, 0);
+
+#if defined(OS_CHROMEOS)
+ registry->RegisterBooleanPref(
+ kDisplayRotationAcceleratorDialogHasBeenAccepted, false);
+#endif // defined(OS_CHROMEOS)
+}
+
+} // namespace
+
+void RegisterLocalState(PrefRegistrySimple* registry) {
+ // Please keep this list alphabetized.
+ browser_shutdown::RegisterPrefs(registry);
+ data_reduction_proxy::RegisterPrefs(registry);
+ BrowserProcessImpl::RegisterPrefs(registry);
+ ChromeContentBrowserClient::RegisterLocalStatePrefs(registry);
+ ChromeMetricsServiceClient::RegisterPrefs(registry);
+ ChromeTracingDelegate::RegisterPrefs(registry);
+ component_updater::RegisterPrefs(registry);
+ ExternalProtocolHandler::RegisterPrefs(registry);
+ flags_ui::PrefServiceFlagsStorage::RegisterPrefs(registry);
+ GpuModeManager::RegisterPrefs(registry);
+ signin::IdentityManager::RegisterLocalStatePrefs(registry);
+ IntranetRedirectDetector::RegisterPrefs(registry);
+ language::GeoLanguageProvider::RegisterLocalStatePrefs(registry);
+ language::UlpLanguageCodeLocator::RegisterLocalStatePrefs(registry);
+ memory::EnterpriseMemoryLimitPrefObserver::RegisterPrefs(registry);
+ network_time::NetworkTimeTracker::RegisterPrefs(registry);
+ OriginTrialPrefs::RegisterPrefs(registry);
+ password_manager::PasswordManager::RegisterLocalPrefs(registry);
+ PrefProxyConfigTrackerImpl::RegisterPrefs(registry);
+ ProfileAttributesEntry::RegisterLocalStatePrefs(registry);
+ ProfileInfoCache::RegisterPrefs(registry);
+ ProfileNetworkContextService::RegisterLocalStatePrefs(registry);
+ profiles::RegisterPrefs(registry);
+ rappor::RapporServiceImpl::RegisterPrefs(registry);
+ RegisterScreenshotPrefs(registry);
+ safe_browsing::RegisterLocalStatePrefs(registry);
+ secure_origin_whitelist::RegisterPrefs(registry);
+ sessions::SessionIdGenerator::RegisterPrefs(registry);
+ SSLConfigServiceManager::RegisterPrefs(registry);
+ subresource_filter::IndexedRulesetVersion::RegisterPrefs(registry);
+ SystemNetworkContextManager::RegisterPrefs(registry);
+ update_client::RegisterPrefs(registry);
+ variations::VariationsService::RegisterPrefs(registry);
+
+ policy::BrowserPolicyConnector::RegisterPrefs(registry);
+ policy::PolicyStatisticsCollector::RegisterPrefs(registry);
+
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+ BackgroundModeManager::RegisterPrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS) and defined(OS_CHROMEOS)
+ chromeos::EasyUnlockService::RegisterPrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ PluginsResourceService::RegisterPrefs(registry);
+#endif
+
+#if defined(OS_ANDROID)
+ ::android::RegisterPrefs(registry);
+#else
+ media_router::RegisterLocalStatePrefs(registry);
+ // The native GCM is used on Android instead.
+ gcm::GCMChannelStatusSyncer::RegisterPrefs(registry);
+ gcm::RegisterPrefs(registry);
+ metrics::TabStatsTracker::RegisterPrefs(registry);
+ RegisterBrowserPrefs(registry);
+ StartupBrowserCreator::RegisterLocalStatePrefs(registry);
+ task_manager::TaskManagerInterface::RegisterPrefs(registry);
+ UpgradeDetector::RegisterPrefs(registry);
+ enterprise_reporting::RegisterLocalStatePrefs(registry);
+#if !defined(OS_CHROMEOS)
+ RegisterDefaultBrowserPromptPrefs(registry);
+#endif // !defined(OS_CHROMEOS)
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+ arc::prefs::RegisterLocalStatePrefs(registry);
+ ChromeOSMetricsProvider::RegisterPrefs(registry);
+ chromeos::ArcKioskAppManager::RegisterPrefs(registry);
+ chromeos::AudioDevicesPrefHandlerImpl::RegisterPrefs(registry);
+ chromeos::ChromeUserManagerImpl::RegisterPrefs(registry);
+ chromeos::DemoModeDetector::RegisterPrefs(registry);
+ chromeos::DemoModeResourcesRemover::RegisterLocalStatePrefs(registry);
+ chromeos::DemoSession::RegisterLocalStatePrefs(registry);
+ chromeos::DemoSetupController::RegisterLocalStatePrefs(registry);
+ chromeos::DeviceOAuth2TokenService::RegisterPrefs(registry);
+ chromeos::device_settings_cache::RegisterPrefs(registry);
+ chromeos::echo_offer::RegisterPrefs(registry);
+ chromeos::EnableDebuggingScreenHandler::RegisterPrefs(registry);
+ chromeos::FastTransitionObserver::RegisterPrefs(registry);
+ chromeos::HIDDetectionScreenHandler::RegisterPrefs(registry);
+ chromeos::KerberosCredentialsManager::RegisterLocalStatePrefs(registry);
+ chromeos::KioskAppManager::RegisterPrefs(registry);
+ chromeos::KioskCryptohomeRemover::RegisterPrefs(registry);
+ chromeos::language_prefs::RegisterPrefs(registry);
+ chromeos::MultiProfileUserController::RegisterPrefs(registry);
+ chromeos::NetworkThrottlingObserver::RegisterPrefs(registry);
+ chromeos::PowerMetricsReporter::RegisterLocalStatePrefs(registry);
+ chromeos::power::auto_screen_brightness::MetricsReporter::
+ RegisterLocalStatePrefs(registry);
+ chromeos::Preferences::RegisterPrefs(registry);
+ chromeos::ResetScreen::RegisterPrefs(registry);
+ chromeos::ResourceReporter::RegisterPrefs(registry);
+ chromeos::SchedulerConfigurationManager::RegisterLocalStatePrefs(registry);
+ chromeos::ServicesCustomizationDocument::RegisterPrefs(registry);
+ chromeos::SigninScreenHandler::RegisterPrefs(registry);
+ chromeos::StartupUtils::RegisterPrefs(registry);
+ chromeos::StatsReportingController::RegisterLocalStatePrefs(registry);
+ chromeos::system::AutomaticRebootManager::RegisterPrefs(registry);
+ chromeos::TimeZoneResolver::RegisterPrefs(registry);
+ chromeos::UserImageManager::RegisterPrefs(registry);
+ chromeos::UserSessionManager::RegisterPrefs(registry);
+ component_updater::MetadataTable::RegisterPrefs(registry);
+ cryptauth::CryptAuthDeviceIdProviderImpl::RegisterLocalPrefs(registry);
+ extensions::ExtensionAssetsManagerChromeOS::RegisterPrefs(registry);
+ extensions::lock_screen_data::LockScreenItemStorage::RegisterLocalState(
+ registry);
+ extensions::login_api::RegisterLocalStatePrefs(registry);
+ invalidation::FCMInvalidationService::RegisterPrefs(registry);
+ invalidation::InvalidatorStorage::RegisterPrefs(registry);
+ ::onc::RegisterPrefs(registry);
+ policy::AutoEnrollmentClientImpl::RegisterPrefs(registry);
+ policy::BrowserPolicyConnectorChromeOS::RegisterPrefs(registry);
+ policy::DeviceCloudPolicyManagerChromeOS::RegisterPrefs(registry);
+ policy::DeviceStatusCollector::RegisterPrefs(registry);
+ policy::DeviceWallpaperImageExternalDataHandler::RegisterPrefs(registry);
+ policy::DMTokenStorage::RegisterPrefs(registry);
+ policy::PolicyCertServiceFactory::RegisterPrefs(registry);
+ policy::TPMAutoUpdateModePolicyHandler::RegisterPrefs(registry);
+ policy::WebUsbAllowDevicesForUrlsPolicyHandler::RegisterPrefs(registry);
+ quirks::QuirksManager::RegisterPrefs(registry);
+ UpgradeDetectorChromeos::RegisterPrefs(registry);
+ syncer::PerUserTopicRegistrationManager::RegisterPrefs(registry);
+ syncer::InvalidatorRegistrarWithMemory::RegisterPrefs(registry);
+#endif
+
+#if defined(OS_MACOSX)
+ confirm_quit::RegisterLocalState(registry);
+ OSCrypt::RegisterLocalPrefs(registry);
+ QuitWithAppsController::RegisterPrefs(registry);
+ system_media_permissions::RegisterSystemMediaPermissionStatesPrefs(registry);
+#endif
+
+#if defined(OS_WIN)
+ registry->RegisterBooleanPref(prefs::kRendererCodeIntegrityEnabled, true);
+ component_updater::RegisterPrefsForSwReporter(registry);
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ IncompatibleApplicationsUpdater::RegisterLocalStatePrefs(registry);
+ ModuleDatabase::RegisterLocalStatePrefs(registry);
+ ThirdPartyConflictsManager::RegisterLocalStatePrefs(registry);
+#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+ registry->RegisterBooleanPref(kHasSeenWin10PromoPage, false); // DEPRECATED
+ registry->RegisterStringPref(kLastWelcomedOSVersion, std::string());
+#endif // defined(OS_WIN)
+
+ // Obsolete. See MigrateObsoleteBrowserPrefs().
+ registry->RegisterIntegerPref(metrics::prefs::kStabilityExecutionPhase, 0);
+#if !defined(OS_ANDROID)
+ registry->RegisterBooleanPref(kNtpActivateHideShortcutsFieldTrial, false);
+#endif // !defined(OS_ANDROID)
+ registry->RegisterInt64Pref(kLastStartupTimestamp, 0);
+ registry->RegisterStringPref(kLastStartupVersion, std::string());
+ registry->RegisterIntegerPref(kSameVersionStartupCount, 0);
+
+#if defined(TOOLKIT_VIEWS)
+ RegisterBrowserViewLocalPrefs(registry);
+#endif
+}
+
+// Register prefs applicable to all profiles.
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
+ const std::string& locale) {
+ TRACE_EVENT0("browser", "chrome::RegisterProfilePrefs");
+ // User prefs. Please keep this list alphabetized.
+ AccessibilityLabelsService::RegisterProfilePrefs(registry);
+ AccessibilityUIMessageHandler::RegisterProfilePrefs(registry);
+ AvailabilityProber::RegisterProfilePrefs(registry);
+ autofill::prefs::RegisterProfilePrefs(registry);
+ browsing_data::prefs::RegisterBrowserUserPrefs(registry);
+ certificate_transparency::prefs::RegisterPrefs(registry);
+ ChromeContentBrowserClient::RegisterProfilePrefs(registry);
+ ChromeSSLHostStateDelegate::RegisterProfilePrefs(registry);
+ ChromeVersionService::RegisterProfilePrefs(registry);
+ chrome_browser_net::NetErrorTabHelper::RegisterProfilePrefs(registry);
+ chrome_browser_net::RegisterPredictionOptionsProfilePrefs(registry);
+ chrome_prefs::RegisterProfilePrefs(registry);
+ dom_distiller::DistilledPagePrefs::RegisterProfilePrefs(registry);
+ DocumentProvider::RegisterProfilePrefs(registry);
+ DownloadPrefs::RegisterProfilePrefs(registry);
+ HostContentSettingsMap::RegisterProfilePrefs(registry);
+ image_fetcher::ImageCache::RegisterProfilePrefs(registry);
+ ImportantSitesUtil::RegisterProfilePrefs(registry);
+ IncognitoModePrefs::RegisterProfilePrefs(registry);
+ language::LanguagePrefs::RegisterProfilePrefs(registry);
+ MediaCaptureDevicesDispatcher::RegisterProfilePrefs(registry);
+ MediaDeviceIDSalt::RegisterProfilePrefs(registry);
+ MediaEngagementService::RegisterProfilePrefs(registry);
+ MediaStorageIdSalt::RegisterProfilePrefs(registry);
+ MediaStreamDevicesController::RegisterProfilePrefs(registry);
+ NavigationCorrectionTabObserver::RegisterProfilePrefs(registry);
+ NotifierStateTracker::RegisterProfilePrefs(registry);
+ ntp_snippets::ContentSuggestionsService::RegisterProfilePrefs(registry);
+ ntp_snippets::RemoteSuggestionsProviderImpl::RegisterProfilePrefs(registry);
+ ntp_snippets::RemoteSuggestionsSchedulerImpl::RegisterProfilePrefs(registry);
+ ntp_snippets::RequestThrottler::RegisterProfilePrefs(registry);
+ ntp_snippets::UserClassifier::RegisterProfilePrefs(registry);
+ ntp_tiles::MostVisitedSites::RegisterProfilePrefs(registry);
+ optimization_guide::prefs::RegisterProfilePrefs(registry);
+ password_bubble_experiment::RegisterPrefs(registry);
+ password_manager::PasswordManager::RegisterProfilePrefs(registry);
+ payments::RegisterProfilePrefs(registry);
+ PlatformNotificationServiceImpl::RegisterProfilePrefs(registry);
+ policy::DeveloperToolsPolicyHandler::RegisterProfilePrefs(registry);
+ policy::URLBlacklistManager::RegisterProfilePrefs(registry);
+ PrefProxyConfigTrackerImpl::RegisterProfilePrefs(registry);
+ PrefsTabHelper::RegisterProfilePrefs(registry, locale);
+ PreviewsLitePageRedirectDecider::RegisterProfilePrefs(registry);
+ PreviewsOfflineHelper::RegisterProfilePrefs(registry);
+ Profile::RegisterProfilePrefs(registry);
+ ProfileImpl::RegisterProfilePrefs(registry);
+ ProfileNetworkContextService::RegisterProfilePrefs(registry);
+ ProtocolHandlerRegistry::RegisterProfilePrefs(registry);
+ PushMessagingAppIdentifier::RegisterProfilePrefs(registry);
+ RegisterBrowserUserPrefs(registry);
+ safe_browsing::RegisterProfilePrefs(registry);
+ SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs(registry);
+ SessionStartupPref::RegisterProfilePrefs(registry);
+ SharingSyncPreference::RegisterProfilePrefs(registry);
+ sync_sessions::SessionSyncPrefs::RegisterProfilePrefs(registry);
+ syncer::DeviceInfoPrefs::RegisterProfilePrefs(registry);
+ syncer::SyncPrefs::RegisterProfilePrefs(registry);
+ syncer::PerUserTopicRegistrationManager::RegisterProfilePrefs(registry);
+ syncer::InvalidatorRegistrarWithMemory::RegisterProfilePrefs(registry);
+ TemplateURLPrepopulateData::RegisterProfilePrefs(registry);
+ translate::TranslatePrefs::RegisterProfilePrefs(registry);
+ ZeroSuggestProvider::RegisterProfilePrefs(registry);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ExtensionWebUI::RegisterProfilePrefs(registry);
+ RegisterAnimationPolicyPrefs(registry);
+ ToolbarActionsBar::RegisterProfilePrefs(registry);
+ extensions::api::CryptotokenRegisterProfilePrefs(registry);
+ extensions::ActivityLog::RegisterProfilePrefs(registry);
+ extensions::AudioAPI::RegisterUserPrefs(registry);
+ extensions::ExtensionPrefs::RegisterProfilePrefs(registry);
+ extensions::ExtensionsUI::RegisterProfilePrefs(registry);
+ extensions::NtpOverriddenBubbleDelegate::RegisterPrefs(registry);
+ extensions::RuntimeAPI::RegisterPrefs(registry);
+ update_client::RegisterProfilePrefs(registry);
+ web_app::WebAppProvider::RegisterProfilePrefs(registry);
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+#if BUILDFLAG(ENABLE_LEGACY_DESKTOP_IN_PRODUCT_HELP)
+ feature_engagement::SessionDurationUpdater::RegisterProfilePrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ PluginInfoHostImpl::RegisterUserPrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+ printing::PolicySettings::RegisterProfilePrefs(registry);
+ printing::StickySettings::RegisterProfilePrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_SERVICE_DISCOVERY)
+ LocalDiscoveryUI::RegisterProfilePrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+ ChildAccountService::RegisterProfilePrefs(registry);
+ SupervisedUserService::RegisterProfilePrefs(registry);
+ SupervisedUserWhitelistService::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(OS_ANDROID)
+ ntp_tiles::PopularSitesImpl::RegisterProfilePrefs(registry);
+ variations::VariationsService::RegisterProfilePrefs(registry);
+ GeolocationPermissionContextAndroid::RegisterProfilePrefs(registry);
+ PartnerBookmarksShim::RegisterProfilePrefs(registry);
+ RecentTabsPagePrefs::RegisterProfilePrefs(registry);
+ usage_stats::UsageStatsBridge::RegisterProfilePrefs(registry);
+#if BUILDFLAG(ENABLE_FEED_IN_CHROME)
+ feed::FeedSchedulerHost::RegisterProfilePrefs(registry);
+ feed::RefreshThrottler::RegisterProfilePrefs(registry);
+ feed::UserClassifier::RegisterProfilePrefs(registry);
+#endif // BUILDFLAG(ENABLE_FEED_IN_CHROME)
+#else
+ AppShortcutManager::RegisterProfilePrefs(registry);
+ DeviceIDFetcher::RegisterProfilePrefs(registry);
+ DevToolsWindow::RegisterProfilePrefs(registry);
+#if BUILDFLAG(ENABLE_APP_LIST)
+ app_list::AppListSyncableService::RegisterProfilePrefs(registry);
+#endif // BUILDFLAG(ENABLE_APP_LIST)
+ extensions::CommandService::RegisterProfilePrefs(registry);
+ extensions::TabsCaptureVisibleTabFunction::RegisterProfilePrefs(registry);
+ NewTabUI::RegisterProfilePrefs(registry);
+ PepperFlashSettingsManager::RegisterProfilePrefs(registry);
+ PinnedTabCodec::RegisterProfilePrefs(registry);
+ signin::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(OS_ANDROID)
+ cdm::MediaDrmStorageImpl::RegisterProfilePrefs(registry);
+ MediaDrmOriginIdManager::RegisterProfilePrefs(registry);
+ explore_sites::HistoryStatisticsReporter::RegisterPrefs(registry);
+ ntp_snippets::ClickBasedCategoryRanker::RegisterProfilePrefs(registry);
+ OomInterventionDecider::RegisterProfilePrefs(registry);
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+ browser_sync::ForeignSessionHandler::RegisterProfilePrefs(registry);
+ ChromeAuthenticatorRequestDelegate::RegisterProfilePrefs(registry);
+ first_run::RegisterProfilePrefs(registry);
+ HatsService::RegisterProfilePrefs(registry);
+ InstantService::RegisterProfilePrefs(registry);
+ PromoService::RegisterProfilePrefs(registry);
+ SearchSuggestService::RegisterProfilePrefs(registry);
+ gcm::GCMChannelStatusSyncer::RegisterProfilePrefs(registry);
+ gcm::RegisterProfilePrefs(registry);
+ media_router::RegisterProfilePrefs(registry);
+ ntp_tiles::CustomLinksManagerImpl::RegisterProfilePrefs(registry);
+ StartupBrowserCreator::RegisterProfilePrefs(registry);
+#endif
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ default_apps::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(OS_CHROMEOS)
+ arc::prefs::RegisterProfilePrefs(registry);
+ certificate_manager::CertificatesHandler::RegisterProfilePrefs(registry);
+ chromeos::AccountManager::RegisterPrefs(registry);
+ chromeos::assistant::prefs::RegisterProfilePrefsForBrowser(registry);
+ chromeos::CupsPrintersManager::RegisterProfilePrefs(registry);
+ chromeos::first_run::RegisterProfilePrefs(registry);
+ chromeos::file_system_provider::RegisterProfilePrefs(registry);
+ chromeos::KerberosCredentialsManager::RegisterProfilePrefs(registry);
+ chromeos::KeyPermissions::RegisterProfilePrefs(registry);
+ chromeos::multidevice_setup::MultiDeviceSetupService::RegisterProfilePrefs(
+ registry);
+ chromeos::MultiProfileUserController::RegisterProfilePrefs(registry);
+ chromeos::ReleaseNotesStorage::RegisterProfilePrefs(registry);
+ chromeos::quick_unlock::FingerprintStorage::RegisterProfilePrefs(registry);
+ chromeos::quick_unlock::PinStoragePrefs::RegisterProfilePrefs(registry);
+ chromeos::Preferences::RegisterProfilePrefs(registry);
+ chromeos::PrintJobHistoryService::RegisterProfilePrefs(registry);
+ chromeos::SyncedPrintersManager::RegisterProfilePrefs(registry);
+ chromeos::parent_access::ParentAccessService::RegisterProfilePrefs(registry);
+ chromeos::quick_unlock::RegisterProfilePrefs(registry);
+ chromeos::RegisterSamlProfilePrefs(registry);
+ chromeos::ScreenTimeController::RegisterProfilePrefs(registry);
+ chromeos::ServicesCustomizationDocument::RegisterProfilePrefs(registry);
+ chromeos::UserImageSyncObserver::RegisterProfilePrefs(registry);
+ crostini::prefs::RegisterProfilePrefs(registry);
+ chromeos::attestation::TpmChallengeKey::RegisterProfilePrefs(registry);
+ extensions::EPKPChallengeKey::RegisterProfilePrefs(registry);
+ flags_ui::PrefServiceFlagsStorage::RegisterProfilePrefs(registry);
+ guest_os::prefs::RegisterProfilePrefs(registry);
+ lock_screen_apps::StateController::RegisterProfilePrefs(registry);
+ plugin_vm::prefs::RegisterProfilePrefs(registry);
+ policy::AppInstallEventLogger::RegisterProfilePrefs(registry);
+ policy::AppInstallEventLogManagerWrapper::RegisterProfilePrefs(registry);
+ policy::StatusCollector::RegisterProfilePrefs(registry);
+ ::onc::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(OS_CHROMEOS) && BUILDFLAG(ENABLE_APP_LIST)
+ app_list::ArcAppReinstallSearchProvider::RegisterProfilePrefs(registry);
+ ArcAppListPrefs::RegisterProfilePrefs(registry);
+ chromeos::ApkWebAppService::RegisterProfilePrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_RLZ)
+ ChromeRLZTrackerDelegate::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(OS_WIN)
+ component_updater::RegisterProfilePrefsForSwReporter(registry);
+ NetworkProfileBubble::RegisterProfilePrefs(registry);
+ safe_browsing::SettingsResetPromptPrefsManager::RegisterProfilePrefs(
+ registry);
+ safe_browsing::PostCleanupSettingsResetter::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(OS_WIN) || defined(OS_MACOSX) || \
+ (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+ browser_switcher::BrowserSwitcherPrefs::RegisterProfilePrefs(registry);
+#endif
+
+#if defined(TOOLKIT_VIEWS)
+ accessibility_prefs::RegisterInvertBubbleUserPrefs(registry);
+ RegisterBrowserViewProfilePrefs(registry);
+#endif
+
+#if defined(OS_CHROMEOS)
+ RegisterChromeLauncherUserPrefs(registry);
+#endif
+
+#if !defined(OS_ANDROID)
+ HistoryUI::RegisterProfilePrefs(registry);
+ settings::SettingsUI::RegisterProfilePrefs(registry);
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ offline_pages::OfflineMetricsCollectorImpl::RegisterPrefs(registry);
+ offline_pages::prefetch_prefs::RegisterPrefs(registry);
+#endif
+
+#if defined(OS_ANDROID)
+ NotificationChannelsProviderAndroid::RegisterProfilePrefs(registry);
+#endif
+
+#if !defined(OS_ANDROID)
+ UnifiedAutoplayConfig::RegisterProfilePrefs(registry);
+#endif
+
+#if !defined(OS_CHROMEOS) && BUILDFLAG(ENABLE_EXTENSIONS)
+ extensions::enterprise_reporting::RegisterProfilePrefs(registry);
+#endif
+
+#if !defined(OS_ANDROID)
+ enterprise_reporting::RegisterProfilePrefs(registry);
+#endif
+
+ RegisterProfilePrefsForMigration(registry);
+}
+
+void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
+ RegisterUserProfilePrefs(registry, g_browser_process->GetApplicationLocale());
+}
+
+void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
+ const std::string& locale) {
+ RegisterProfilePrefs(registry, locale);
+
+#if defined(OS_ANDROID)
+ ::android::RegisterUserProfilePrefs(registry);
+#endif
+#if defined(OS_CHROMEOS)
+ ash::RegisterUserProfilePrefs(registry);
+#endif
+}
+
+void RegisterScreenshotPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterBooleanPref(prefs::kDisableScreenshots, false);
+}
+
+#if defined(OS_CHROMEOS)
+void RegisterSigninProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
+ RegisterProfilePrefs(registry, g_browser_process->GetApplicationLocale());
+ ash::RegisterSigninProfilePrefs(registry);
+}
+
+#endif
+
+// This method should be periodically pruned of year+ old migrations.
+void MigrateObsoleteBrowserPrefs(Profile* profile, PrefService* local_state) {
+ // Added 12/2018.
+ local_state->ClearPref(metrics::prefs::kStabilityExecutionPhase);
+
+#if defined(OS_ANDROID)
+ // Added 9/2018
+ local_state->ClearPref(
+ metrics::prefs::kStabilityCrashCountWithoutGmsCoreUpdateObsolete);
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_WIN)
+ // Added 9/2018
+ local_state->ClearPref(kLastWelcomedOSVersion);
+#endif
+#if defined(OS_CHROMEOS)
+ // Added 12/2018
+ local_state->ClearPref(prefs::kCarrierDealPromoShown);
+#endif
+
+#if defined(OS_WIN)
+ // Added 6/2019.
+ local_state->ClearPref(kHasSeenWin10PromoPage);
+#endif // defined(OS_WIN)
+
+#if !defined(OS_ANDROID)
+ // Added 7/2019.
+ local_state->ClearPref(kNtpActivateHideShortcutsFieldTrial);
+#endif // !defined(OS_ANDROID)
+
+ // Added 8/2019.
+ local_state->ClearPref(kLastStartupTimestamp);
+ local_state->ClearPref(kLastStartupVersion);
+ local_state->ClearPref(kSameVersionStartupCount);
+}
+
+// This method should be periodically pruned of year+ old migrations.
+void MigrateObsoleteProfilePrefs(Profile* profile) {
+ PrefService* profile_prefs = profile->GetPrefs();
+
+ // Added 8/2018.
+ autofill::prefs::MigrateDeprecatedAutofillPrefs(profile_prefs);
+
+ // Added 8/2018
+ profile_prefs->ClearPref(kDnsPrefetchingStartupList);
+ profile_prefs->ClearPref(kDnsPrefetchingHostReferralList);
+
+ // Added 9/2018
+ profile_prefs->ClearPref(kGeolocationAccessToken);
+ profile_prefs->ClearPref(kGoogleServicesPasswordHash);
+ profile_prefs->ClearPref(kModuleConflictBubbleShown);
+ profile_prefs->ClearPref(kOptionsWindowLastTabIndex);
+ profile_prefs->ClearPref(kTrustedDownloadSources);
+ profile_prefs->ClearPref(kSupervisedUserCreationAllowed);
+
+ // Added 10/2018
+ profile_prefs->ClearPref(kReverseAutologinEnabled);
+
+ // Added 11/2018.
+ profile_prefs->ClearPref(kNetworkQualities);
+ profile_prefs->ClearPref(kForceSessionSync);
+ profile_prefs->ClearPref(kOnboardDuringNUX);
+ profile_prefs->ClearPref(kNuxOnboardGroup);
+ profile_prefs->ClearPref(kHttpServerProperties);
+
+#if defined(OS_CHROMEOS)
+ // Added 12/2018.
+ profile_prefs->ClearPref(prefs::kDataSaverPromptsShown);
+
+ // Added 4/2019
+ guest_os::GuestOsSharePath::MigratePersistedPathsToMultiVM(profile_prefs);
+#endif
+
+ // Added 1/2019.
+ profile_prefs->ClearPref(kLastUpdateCheck);
+ profile_prefs->ClearPref(kNextUpdateCheck);
+
+ syncer::MigrateSessionsToProxyTabsPrefs(profile_prefs);
+ syncer::ClearObsoleteUserTypePrefs(profile_prefs);
+
+ // Added 2/2019.
+ syncer::ClearObsoleteClearServerDataPrefs(profile_prefs);
+ syncer::ClearObsoleteAuthErrorPrefs(profile_prefs);
+
+ // Added 3/2019.
+ syncer::ClearObsoleteFirstSyncTime(profile_prefs);
+ syncer::ClearObsoleteSyncLongPollIntervalSeconds(profile_prefs);
+
+ // Added 3/2019.
+ profile_prefs->ClearPref(kCurrentThemeImages);
+ profile_prefs->ClearPref(kCurrentThemeColors);
+ profile_prefs->ClearPref(kCurrentThemeTints);
+ profile_prefs->ClearPref(kCurrentThemeDisplayProperties);
+
+#if defined(OS_ANDROID)
+ // Added 4/2019.
+ profile_prefs->ClearPref(kDismissedAssetDownloadSuggestions);
+ profile_prefs->ClearPref(kDismissedOfflinePageDownloadSuggestions);
+
+ // Added 4/2019.
+ profile_prefs->ClearPref(kBreakingNewsSubscriptionDataToken);
+ profile_prefs->ClearPref(kBreakingNewsSubscriptionDataIsAuthenticated);
+ profile_prefs->ClearPref(kBreakingNewsGCMSubscriptionTokenCache);
+ profile_prefs->ClearPref(kBreakingNewsGCMLastTokenValidationTime);
+ profile_prefs->ClearPref(kBreakingNewsGCMLastForcedSubscriptionTime);
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+ // Added 4/2019.
+ syncer::ClearObsoleteSyncSpareBootstrapToken(profile_prefs);
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_ANDROID)
+ // Added 4/2019.
+ profile_prefs->ClearPref(kContentSuggestionsConsecutiveIgnoredPrefName);
+ profile_prefs->ClearPref(kContentSuggestionsNotificationsSentDay);
+ profile_prefs->ClearPref(kContentSuggestionsNotificationsSentCount);
+ profile_prefs->ClearPref(kNotificationIDWithinCategory);
+
+ // Added 5/2019.
+ profile_prefs->ClearPref(kContentSuggestionsNotificationsEnabled);
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+ // Deprecated 5/2019
+ profile_prefs->ClearPref(kSignInPromoShowOnFirstRunAllowed);
+ profile_prefs->ClearPref(kSignInPromoShowNTPBubble);
+#endif // !defined(OS_ANDROID)
+
+ // Added 5/2019.
+ profile_prefs->ClearPref(kBookmarkAppCreationLaunchType);
+
+ // Added 6/2019.
+ profile_prefs->ClearPref(kMediaCacheSize);
+#if defined(OS_MACOSX)
+ profile_prefs->ClearPref(password_manager::prefs::kKeychainMigrationStatus);
+#endif // defined(OS_MACOSX)
+
+ // Added 7/2019.
+ syncer::MigrateSyncSuppressedPref(profile_prefs);
+ profile_prefs->ClearPref(kSignedInTime);
+ syncer::ClearObsoleteMemoryPressurePrefs(profile_prefs);
+ profile_prefs->ClearPref(kLastKnownGoogleURL);
+ profile_prefs->ClearPref(kLastPromptedGoogleURL);
+
+#if defined(OS_ANDROID)
+ // Added 7/2019.
+ profile_prefs->ClearPref(kWebAuthnLastTransportUsedPrefName);
+ profile_prefs->ClearPref(kWebAuthnBlePairedMacAddressesPrefName);
+#endif // defined(OS_ANDROID)
+
+ // Added 7/2019.
+#if defined(USE_X11)
+ profile_prefs->ClearPref(kLocalProfileId);
+#endif
+
+ // Added 8/2019
+ profile_prefs->ClearPref(kInsecureExtensionUpdatesEnabled);
+ profile_prefs->ClearPref(kHintLoadedCounts);
+
+ // Added 9/2019
+ profile_prefs->ClearPref(kGoogleServicesUsername);
+ profile_prefs->ClearPref(kGoogleServicesUserAccountId);
+ profile_prefs->ClearPref(
+ kDataReductionProxySavingsClearedNegativeSystemClock);
+
+ // Added 10/2019.
+ syncer::DeviceInfoPrefs::MigrateRecentLocalCacheGuidsPref(profile_prefs);
+#if defined(OS_CHROMEOS)
+ // Added 10/2019.
+ profile_prefs->ClearPref(kDisplayRotationAcceleratorDialogHasBeenAccepted);
+#endif // defined(OS_CHROMEOS)
+}
diff --git a/chromium/chrome/browser/prefs/browser_prefs.h b/chromium/chrome/browser/prefs/browser_prefs.h
new file mode 100644
index 00000000000..05f6c207446
--- /dev/null
+++ b/chromium/chrome/browser/prefs/browser_prefs.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_BROWSER_PREFS_H_
+#define CHROME_BROWSER_PREFS_BROWSER_PREFS_H_
+
+#include <set>
+
+#include "build/build_config.h"
+#include "components/prefs/pref_value_store.h"
+
+class PrefRegistrySimple;
+class PrefService;
+class Profile;
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// Register all prefs that will be used via the local state PrefService.
+void RegisterLocalState(PrefRegistrySimple* registry);
+
+void RegisterScreenshotPrefs(PrefRegistrySimple* registry);
+
+// Register all prefs that will be used via a PrefService attached to a user
+// Profile using the locale of |g_browser_process|.
+void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+// Register all prefs that will be used via a PrefService attached to a user
+// Profile with the given |locale|.
+void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
+ const std::string& locale);
+
+#if defined(OS_CHROMEOS)
+// Register all prefs that will be used via a PrefService attached to the
+// sign-in profile using the locale of |g_browser_process|.
+void RegisterSigninProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+#endif
+
+// Migrate/cleanup deprecated prefs in |local_state|. Over time, long deprecated
+// prefs should be removed as new ones are added, but this call should never go
+// away (even if it becomes an empty call for some time) as it should remain
+// *the* place to drop deprecated browser prefs at.
+void MigrateObsoleteBrowserPrefs(Profile* profile, PrefService* local_state);
+
+// Migrate/cleanup deprecated prefs in |profile|'s pref store. Over time, long
+// deprecated prefs should be removed as new ones are added, but this call
+// should never go away (even if it becomes an empty call for some time) as it
+// should remain *the* place to drop deprecated profile prefs at.
+void MigrateObsoleteProfilePrefs(Profile* profile);
+
+#endif // CHROME_BROWSER_PREFS_BROWSER_PREFS_H_
diff --git a/chromium/chrome/browser/prefs/chrome_command_line_pref_store.cc b/chromium/chrome/browser/prefs/chrome_command_line_pref_store.cc
new file mode 100644
index 00000000000..60cd72cedbb
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_command_line_pref_store.cc
@@ -0,0 +1,177 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ash/public/cpp/ash_switches.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/browser_sync/browser_sync_switches.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/proxy_config/proxy_config_dictionary.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "components/sync/base/pref_names.h"
+#include "content/public/common/content_switches.h"
+#include "services/network/public/cpp/network_switches.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/display/display_switches.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/constants/chromeos_switches.h"
+#endif
+
+const CommandLinePrefStore::SwitchToPreferenceMapEntry
+ ChromeCommandLinePrefStore::string_switch_map_[] = {
+ {switches::kLang, language::prefs::kApplicationLocale},
+ {data_reduction_proxy::switches::kDataReductionProxy,
+ data_reduction_proxy::prefs::kDataReductionProxy},
+ {switches::kAuthServerWhitelist, prefs::kAuthServerWhitelist},
+ {switches::kSSLVersionMin, prefs::kSSLVersionMin},
+ {switches::kSSLVersionMax, prefs::kSSLVersionMax},
+#if defined(OS_ANDROID)
+ {switches::kAuthAndroidNegotiateAccountType,
+ prefs::kAuthAndroidNegotiateAccountType},
+#endif
+#if defined(OS_CHROMEOS)
+ {switches::kSchedulerConfiguration, prefs::kSchedulerConfiguration},
+#endif
+};
+
+const CommandLinePrefStore::SwitchToPreferenceMapEntry
+ ChromeCommandLinePrefStore::path_switch_map_[] = {
+ { switches::kDiskCacheDir, prefs::kDiskCacheDir },
+ { switches::kLocalSyncBackendDir, syncer::prefs::kLocalSyncBackendDir },
+};
+
+const CommandLinePrefStore::BooleanSwitchToPreferenceMapEntry
+ ChromeCommandLinePrefStore::boolean_switch_map_[] = {
+ {switches::kDisable3DAPIs, prefs::kDisable3DAPIs, true},
+ {switches::kEnableCloudPrintProxy, prefs::kCloudPrintProxyEnabled,
+ true},
+ {switches::kAllowOutdatedPlugins, prefs::kPluginsAllowOutdated, true},
+ {switches::kNoPings, prefs::kEnableHyperlinkAuditing, false},
+ {switches::kAllowRunningInsecureContent,
+ prefs::kWebKitAllowRunningInsecureContent, true},
+ {switches::kAllowCrossOriginAuthPrompt,
+ prefs::kAllowCrossOriginAuthPrompt, true},
+ {switches::kDisablePrintPreview, prefs::kPrintPreviewDisabled, true},
+#if defined(OS_CHROMEOS)
+ {chromeos::switches::kEnableTouchpadThreeFingerClick,
+ prefs::kEnableTouchpadThreeFingerClick, true},
+ {switches::kEnableUnifiedDesktop,
+ prefs::kUnifiedDesktopEnabledByDefault, true},
+ {chromeos::switches::kEnableCastReceiver, prefs::kCastReceiverEnabled,
+ true},
+#endif
+ {switches::kEnableLocalSyncBackend,
+ syncer::prefs::kEnableLocalSyncBackend, true},
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+ {switches::kUseSystemDefaultPrinter,
+ prefs::kPrintPreviewUseSystemDefaultPrinter, true},
+#endif
+ {switches::kSitePerProcess, prefs::kSitePerProcess, true},
+};
+
+const CommandLinePrefStore::SwitchToPreferenceMapEntry
+ ChromeCommandLinePrefStore::integer_switch_map_[] = {
+ {switches::kDiskCacheSize, prefs::kDiskCacheSize}};
+
+ChromeCommandLinePrefStore::ChromeCommandLinePrefStore(
+ const base::CommandLine* command_line)
+ : CommandLinePrefStore(command_line) {
+ ApplySimpleSwitches();
+ ApplyProxyMode();
+ ValidateProxySwitches();
+ ApplySSLSwitches();
+ ApplyBackgroundModeSwitches();
+}
+
+ChromeCommandLinePrefStore::~ChromeCommandLinePrefStore() {}
+
+bool ChromeCommandLinePrefStore::ValidateProxySwitches() {
+ if (command_line()->HasSwitch(switches::kNoProxyServer) &&
+ (command_line()->HasSwitch(switches::kProxyAutoDetect) ||
+ command_line()->HasSwitch(switches::kProxyServer) ||
+ command_line()->HasSwitch(switches::kProxyPacUrl) ||
+ command_line()->HasSwitch(switches::kProxyBypassList))) {
+ LOG(WARNING) << "Additional command-line proxy switches specified when --"
+ << switches::kNoProxyServer << " was also specified.";
+ return false;
+ }
+ return true;
+}
+
+void ChromeCommandLinePrefStore::ApplySimpleSwitches() {
+ // Look for each switch we know about and set its preference accordingly.
+ ApplyStringSwitches(string_switch_map_, base::size(string_switch_map_));
+ ApplyPathSwitches(path_switch_map_, base::size(path_switch_map_));
+ ApplyIntegerSwitches(integer_switch_map_, base::size(integer_switch_map_));
+ ApplyBooleanSwitches(boolean_switch_map_, base::size(boolean_switch_map_));
+}
+
+void ChromeCommandLinePrefStore::ApplyProxyMode() {
+ if (command_line()->HasSwitch(switches::kNoProxyServer)) {
+ SetValue(
+ proxy_config::prefs::kProxy,
+ std::make_unique<base::Value>(ProxyConfigDictionary::CreateDirect()),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ } else if (command_line()->HasSwitch(switches::kProxyPacUrl)) {
+ std::string pac_script_url =
+ command_line()->GetSwitchValueASCII(switches::kProxyPacUrl);
+ SetValue(proxy_config::prefs::kProxy,
+ std::make_unique<base::Value>(
+ ProxyConfigDictionary::CreatePacScript(pac_script_url, false)),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ } else if (command_line()->HasSwitch(switches::kProxyAutoDetect)) {
+ SetValue(proxy_config::prefs::kProxy,
+ std::make_unique<base::Value>(
+ ProxyConfigDictionary::CreateAutoDetect()),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ } else if (command_line()->HasSwitch(switches::kProxyServer)) {
+ std::string proxy_server =
+ command_line()->GetSwitchValueASCII(switches::kProxyServer);
+ std::string bypass_list =
+ command_line()->GetSwitchValueASCII(switches::kProxyBypassList);
+ SetValue(
+ proxy_config::prefs::kProxy,
+ std::make_unique<base::Value>(ProxyConfigDictionary::CreateFixedServers(
+ proxy_server, bypass_list)),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ }
+}
+
+void ChromeCommandLinePrefStore::ApplySSLSwitches() {
+ if (command_line()->HasSwitch(switches::kCipherSuiteBlacklist)) {
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue());
+ list_value->AppendStrings(base::SplitString(
+ command_line()->GetSwitchValueASCII(switches::kCipherSuiteBlacklist),
+ ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL));
+ SetValue(prefs::kCipherSuiteBlacklist, std::move(list_value),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ }
+}
+
+void ChromeCommandLinePrefStore::ApplyBackgroundModeSwitches() {
+ if (command_line()->HasSwitch(switches::kDisableExtensions)) {
+ SetValue(prefs::kBackgroundModeEnabled,
+ std::make_unique<base::Value>(false),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ }
+}
diff --git a/chromium/chrome/browser/prefs/chrome_command_line_pref_store.h b/chromium/chrome/browser/prefs/chrome_command_line_pref_store.h
new file mode 100644
index 00000000000..a37280f137d
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_command_line_pref_store.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_CHROME_COMMAND_LINE_PREF_STORE_H_
+#define CHROME_BROWSER_PREFS_CHROME_COMMAND_LINE_PREF_STORE_H_
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/prefs/command_line_pref_store.h"
+
+// This PrefStore keeps track of preferences set by command-line switches,
+// such as proxy settings.
+class ChromeCommandLinePrefStore : public CommandLinePrefStore {
+ public:
+ explicit ChromeCommandLinePrefStore(const base::CommandLine* command_line);
+
+ protected:
+ ~ChromeCommandLinePrefStore() override;
+
+ // Logs a message and returns false if the proxy switches are
+ // self-contradictory. Protected so it can be used in unit testing.
+ // TODO(bauerb): make this method public and remove the subclass, which calls
+ // this method, from the test.
+ bool ValidateProxySwitches();
+
+ private:
+ friend class TestCommandLinePrefStore;
+
+ // Using the string and boolean maps, apply command-line switches to their
+ // corresponding preferences in this pref store.
+ void ApplySimpleSwitches();
+
+ // Determines the proxy mode preference from the given proxy switches.
+ void ApplyProxyMode();
+
+ // Apply the SSL/TLS preferences from the given switches.
+ void ApplySSLSwitches();
+
+ // Determines whether the background mode is force-disabled.
+ void ApplyBackgroundModeSwitches();
+
+ // Mappings of command line switches to prefs.
+ static const BooleanSwitchToPreferenceMapEntry boolean_switch_map_[];
+ static const SwitchToPreferenceMapEntry string_switch_map_[];
+ static const SwitchToPreferenceMapEntry path_switch_map_[];
+ static const SwitchToPreferenceMapEntry integer_switch_map_[];
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeCommandLinePrefStore);
+};
+
+#endif // CHROME_BROWSER_PREFS_CHROME_COMMAND_LINE_PREF_STORE_H_
diff --git a/chromium/chrome/browser/prefs/chrome_command_line_pref_store_proxy_unittest.cc b/chromium/chrome/browser/prefs/chrome_command_line_pref_store_proxy_unittest.cc
new file mode 100644
index 00000000000..62e885a16b5
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_command_line_pref_store_proxy_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+
+#include <gtest/gtest.h>
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
+#include "components/sync_preferences/pref_service_mock_factory.h"
+#include "content/public/common/content_switches.h"
+#include "net/proxy_resolution/proxy_config_service_common_unittest.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Test parameter object for testing command line proxy configuration.
+struct CommandLineTestParams {
+ // Short description to identify the test.
+ const char* description;
+
+ // The command line to build a ProxyConfig from.
+ struct SwitchValue {
+ const char* name;
+ const char* value;
+ } switches[2];
+
+ // Expected outputs (fields of the ProxyConfig).
+ bool is_null;
+ bool auto_detect;
+ GURL pac_url;
+ net::ProxyRulesExpectation proxy_rules;
+};
+
+void PrintTo(const CommandLineTestParams& params, std::ostream* os) {
+ *os << params.description;
+}
+
+static const CommandLineTestParams kCommandLineTestParams[] = {
+ {
+ "Empty command line",
+ // Input
+ {},
+ // Expected result
+ true, // is_null
+ false, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::Empty(),
+ },
+ {
+ "No proxy",
+ // Input
+ {
+ {switches::kNoProxyServer, NULL},
+ },
+ // Expected result
+ false, // is_null
+ false, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::Empty(),
+ },
+ {
+ "No proxy with extra parameters.",
+ // Input
+ {
+ {switches::kNoProxyServer, NULL},
+ {switches::kProxyServer, "http://proxy:8888"},
+ },
+ // Expected result
+ false, // is_null
+ false, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::Empty(),
+ },
+ {
+ "Single proxy.",
+ // Input
+ {
+ {switches::kProxyServer, "http://proxy:8888"},
+ },
+ // Expected result
+ false, // is_null
+ false, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::Single("proxy:8888", // single proxy
+ ""), // bypass rules
+ },
+ {
+ "Per scheme proxy.",
+ // Input
+ {
+ {switches::kProxyServer, "http=httpproxy:8888;ftp=ftpproxy:8889"},
+ },
+ // Expected result
+ false, // is_null
+ false, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::PerScheme("httpproxy:8888", // http
+ "", // https
+ "ftpproxy:8889", // ftp
+ ""), // bypass rules
+ },
+ {
+ "Per scheme proxy with bypass URLs.",
+ // Input
+ {
+ {switches::kProxyServer, "http=httpproxy:8888;ftp=ftpproxy:8889"},
+ {switches::kProxyBypassList,
+ ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8"},
+ },
+ // Expected result
+ false, // is_null
+ false, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::PerScheme(
+ "httpproxy:8888", // http
+ "", // https
+ "ftpproxy:8889", // ftp
+ "*.google.com,foo.com:99,1.2.3.4:22,127.0.0.1/8"),
+ },
+ {
+ "Pac URL",
+ // Input
+ {
+ {switches::kProxyPacUrl, "http://wpad/wpad.dat"},
+ },
+ // Expected result
+ false, // is_null
+ false, // auto_detect
+ GURL("http://wpad/wpad.dat"), // pac_url
+ net::ProxyRulesExpectation::Empty(),
+ },
+ {
+ "Autodetect",
+ // Input
+ {
+ {switches::kProxyAutoDetect, NULL},
+ },
+ // Expected result
+ false, // is_null
+ true, // auto_detect
+ GURL(), // pac_url
+ net::ProxyRulesExpectation::Empty(),
+ },
+};
+
+} // namespace
+
+class ChromeCommandLinePrefStoreProxyTest
+ : public testing::TestWithParam<CommandLineTestParams> {
+ protected:
+ ChromeCommandLinePrefStoreProxyTest()
+ : command_line_(base::CommandLine::NO_PROGRAM) {}
+
+ net::ProxyConfigWithAnnotation* proxy_config() { return &proxy_config_; }
+
+ void SetUp() override {
+ for (size_t i = 0; i < base::size(GetParam().switches); i++) {
+ const char* name = GetParam().switches[i].name;
+ const char* value = GetParam().switches[i].value;
+ if (name && value)
+ command_line_.AppendSwitchASCII(name, value);
+ else if (name)
+ command_line_.AppendSwitch(name);
+ }
+ scoped_refptr<PrefRegistrySimple> registry = new PrefRegistrySimple;
+ PrefProxyConfigTrackerImpl::RegisterPrefs(registry.get());
+ sync_preferences::PrefServiceMockFactory factory;
+ factory.set_command_line_prefs(
+ new ChromeCommandLinePrefStore(&command_line_));
+ pref_service_ = factory.Create(registry.get());
+ PrefProxyConfigTrackerImpl::ReadPrefConfig(pref_service_.get(),
+ &proxy_config_);
+ }
+
+ private:
+ base::CommandLine command_line_;
+ std::unique_ptr<PrefService> pref_service_;
+ net::ProxyConfigWithAnnotation proxy_config_;
+};
+
+TEST_P(ChromeCommandLinePrefStoreProxyTest, CommandLine) {
+ EXPECT_EQ(GetParam().auto_detect, proxy_config()->value().auto_detect());
+ EXPECT_EQ(GetParam().pac_url, proxy_config()->value().pac_url());
+ EXPECT_TRUE(
+ GetParam().proxy_rules.Matches(proxy_config()->value().proxy_rules()));
+}
+
+INSTANTIATE_TEST_SUITE_P(ChromeCommandLinePrefStoreProxyTestInstance,
+ ChromeCommandLinePrefStoreProxyTest,
+ testing::ValuesIn(kCommandLineTestParams));
diff --git a/chromium/chrome/browser/prefs/chrome_command_line_pref_store_ssl_manager_unittest.cc b/chromium/chrome/browser/prefs/chrome_command_line_pref_store_ssl_manager_unittest.cc
new file mode 100644
index 00000000000..ba08fd05151
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_command_line_pref_store_ssl_manager_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+#include "chrome/browser/ssl/ssl_config_service_manager.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_store.h"
+#include "components/sync_preferences/pref_service_mock_factory.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/network/public/mojom/ssl_config.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class CommandLinePrefStoreSSLManagerTest : public testing::Test {
+ public:
+ CommandLinePrefStoreSSLManagerTest() {}
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+// Test that command-line settings for SSL versions are respected and that they
+// do not persist to the preferences files.
+TEST_F(CommandLinePrefStoreSSLManagerTest, CommandLinePrefs) {
+ scoped_refptr<TestingPrefStore> local_state_store(new TestingPrefStore());
+
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+ command_line.AppendSwitchASCII(switches::kSSLVersionMin, "tls1.1");
+ command_line.AppendSwitchASCII(switches::kSSLVersionMax, "tls1.2");
+
+ sync_preferences::PrefServiceMockFactory factory;
+ factory.set_user_prefs(local_state_store);
+ factory.set_command_line_prefs(new ChromeCommandLinePrefStore(&command_line));
+ scoped_refptr<PrefRegistrySimple> registry = new PrefRegistrySimple;
+ std::unique_ptr<PrefService> local_state(factory.Create(registry.get()));
+
+ SSLConfigServiceManager::RegisterPrefs(registry.get());
+ network::mojom::NetworkContextParamsPtr context_params =
+ network::mojom::NetworkContextParams::New();
+ std::unique_ptr<SSLConfigServiceManager> config_manager(
+ SSLConfigServiceManager::CreateDefaultManager(local_state.get()));
+ config_manager->AddToNetworkContextParams(context_params.get());
+
+ // Command-line flags should be respected.
+ EXPECT_EQ(network::mojom::SSLVersion::kTLS11,
+ context_params->initial_ssl_config->version_min);
+ EXPECT_EQ(network::mojom::SSLVersion::kTLS12,
+ context_params->initial_ssl_config->version_max);
+
+ // Explicitly double-check the settings are not in the preference store.
+ const PrefService::Preference* version_min_pref =
+ local_state->FindPreference(prefs::kSSLVersionMin);
+ EXPECT_FALSE(version_min_pref->IsUserModifiable());
+
+ const PrefService::Preference* version_max_pref =
+ local_state->FindPreference(prefs::kSSLVersionMax);
+ EXPECT_FALSE(version_max_pref->IsUserModifiable());
+
+ std::string version_min_str;
+ std::string version_max_str;
+ EXPECT_FALSE(
+ local_state_store->GetString(prefs::kSSLVersionMin, &version_min_str));
+ EXPECT_FALSE(
+ local_state_store->GetString(prefs::kSSLVersionMax, &version_max_str));
+}
diff --git a/chromium/chrome/browser/prefs/chrome_command_line_pref_store_unittest.cc b/chromium/chrome/browser/prefs/chrome_command_line_pref_store_unittest.cc
new file mode 100644
index 00000000000..8753173ce1a
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_command_line_pref_store_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/proxy_config/proxy_config_dictionary.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/ui_base_switches.h"
+
+namespace {
+
+const char unknown_bool[] = "unknown_switch";
+const char unknown_string[] = "unknown_other_switch";
+
+} // namespace
+
+class TestCommandLinePrefStore : public ChromeCommandLinePrefStore {
+ public:
+ explicit TestCommandLinePrefStore(base::CommandLine* cl)
+ : ChromeCommandLinePrefStore(cl) {}
+
+ bool ProxySwitchesAreValid() {
+ return ValidateProxySwitches();
+ }
+
+ void VerifyProxyMode(ProxyPrefs::ProxyMode expected_mode) {
+ const base::Value* value = nullptr;
+ ASSERT_TRUE(GetValue(proxy_config::prefs::kProxy, &value));
+ ASSERT_TRUE(value->is_dict());
+ ProxyConfigDictionary dict(value->Clone());
+ ProxyPrefs::ProxyMode actual_mode;
+ ASSERT_TRUE(dict.GetMode(&actual_mode));
+ EXPECT_EQ(expected_mode, actual_mode);
+ }
+
+ void VerifySSLCipherSuites(const char* const* ciphers,
+ size_t cipher_count) {
+ const base::Value* value = nullptr;
+ ASSERT_TRUE(GetValue(prefs::kCipherSuiteBlacklist, &value));
+ ASSERT_TRUE(value->is_list());
+ ASSERT_EQ(cipher_count, value->GetList().size());
+
+ std::string cipher_string;
+ for (const base::Value& cipher_string : value->GetList()) {
+ ASSERT_TRUE(cipher_string.is_string());
+ EXPECT_EQ(*ciphers++, cipher_string.GetString());
+ }
+ }
+
+ private:
+ ~TestCommandLinePrefStore() override {}
+};
+
+// Tests a simple string pref on the command line.
+TEST(ChromeCommandLinePrefStoreTest, SimpleStringPref) {
+ base::CommandLine cl(base::CommandLine::NO_PROGRAM);
+ cl.AppendSwitchASCII(switches::kLang, "hi-MOM");
+ scoped_refptr<ChromeCommandLinePrefStore> store =
+ new ChromeCommandLinePrefStore(&cl);
+
+ const base::Value* actual = nullptr;
+ EXPECT_TRUE(store->GetValue(language::prefs::kApplicationLocale, &actual));
+ std::string result;
+ EXPECT_TRUE(actual->GetAsString(&result));
+ EXPECT_EQ("hi-MOM", result);
+}
+
+// Tests a simple boolean pref on the command line.
+TEST(ChromeCommandLinePrefStoreTest, SimpleBooleanPref) {
+ base::CommandLine cl(base::CommandLine::NO_PROGRAM);
+ cl.AppendSwitch(switches::kNoProxyServer);
+ scoped_refptr<TestCommandLinePrefStore> store =
+ new TestCommandLinePrefStore(&cl);
+
+ store->VerifyProxyMode(ProxyPrefs::MODE_DIRECT);
+}
+
+// Tests a command line with no recognized prefs.
+TEST(ChromeCommandLinePrefStoreTest, NoPrefs) {
+ base::CommandLine cl(base::CommandLine::NO_PROGRAM);
+ cl.AppendSwitch(unknown_string);
+ cl.AppendSwitchASCII(unknown_bool, "a value");
+ scoped_refptr<ChromeCommandLinePrefStore> store =
+ new ChromeCommandLinePrefStore(&cl);
+
+ const base::Value* actual = nullptr;
+ EXPECT_FALSE(store->GetValue(unknown_bool, &actual));
+ EXPECT_FALSE(store->GetValue(unknown_string, &actual));
+}
+
+// Tests a complex command line with multiple known and unknown switches.
+TEST(ChromeCommandLinePrefStoreTest, MultipleSwitches) {
+ base::CommandLine cl(base::CommandLine::NO_PROGRAM);
+ cl.AppendSwitch(unknown_string);
+ cl.AppendSwitchASCII(switches::kProxyServer, "proxy");
+ cl.AppendSwitchASCII(switches::kProxyBypassList, "list");
+ cl.AppendSwitchASCII(unknown_bool, "a value");
+ scoped_refptr<TestCommandLinePrefStore> store =
+ new TestCommandLinePrefStore(&cl);
+
+ const base::Value* actual = nullptr;
+ EXPECT_FALSE(store->GetValue(unknown_bool, &actual));
+ EXPECT_FALSE(store->GetValue(unknown_string, &actual));
+
+ store->VerifyProxyMode(ProxyPrefs::MODE_FIXED_SERVERS);
+
+ const base::Value* value = nullptr;
+ ASSERT_TRUE(store->GetValue(proxy_config::prefs::kProxy, &value));
+ ASSERT_TRUE(value->is_dict());
+ ProxyConfigDictionary dict(value->Clone());
+
+ std::string string_result;
+
+ ASSERT_TRUE(dict.GetProxyServer(&string_result));
+ EXPECT_EQ("proxy", string_result);
+
+ ASSERT_TRUE(dict.GetBypassList(&string_result));
+ EXPECT_EQ("list", string_result);
+}
+
+// Tests proxy switch validation.
+TEST(ChromeCommandLinePrefStoreTest, ProxySwitchValidation) {
+ base::CommandLine cl(base::CommandLine::NO_PROGRAM);
+
+ // No switches.
+ scoped_refptr<TestCommandLinePrefStore> store =
+ new TestCommandLinePrefStore(&cl);
+ EXPECT_TRUE(store->ProxySwitchesAreValid());
+
+ // Only no-proxy.
+ cl.AppendSwitch(switches::kNoProxyServer);
+ scoped_refptr<TestCommandLinePrefStore> store2 =
+ new TestCommandLinePrefStore(&cl);
+ EXPECT_TRUE(store2->ProxySwitchesAreValid());
+
+ // Another proxy switch too.
+ cl.AppendSwitch(switches::kProxyAutoDetect);
+ scoped_refptr<TestCommandLinePrefStore> store3 =
+ new TestCommandLinePrefStore(&cl);
+ EXPECT_FALSE(store3->ProxySwitchesAreValid());
+
+ // All proxy switches except no-proxy.
+ base::CommandLine cl2(base::CommandLine::NO_PROGRAM);
+ cl2.AppendSwitch(switches::kProxyAutoDetect);
+ cl2.AppendSwitchASCII(switches::kProxyServer, "server");
+ cl2.AppendSwitchASCII(switches::kProxyPacUrl, "url");
+ cl2.AppendSwitchASCII(switches::kProxyBypassList, "list");
+ scoped_refptr<TestCommandLinePrefStore> store4 =
+ new TestCommandLinePrefStore(&cl2);
+ EXPECT_TRUE(store4->ProxySwitchesAreValid());
+}
+
+TEST(ChromeCommandLinePrefStoreTest, ManualProxyModeInference) {
+ base::CommandLine cl1(base::CommandLine::NO_PROGRAM);
+ cl1.AppendSwitch(unknown_string);
+ cl1.AppendSwitchASCII(switches::kProxyServer, "proxy");
+ scoped_refptr<TestCommandLinePrefStore> store1 =
+ new TestCommandLinePrefStore(&cl1);
+ store1->VerifyProxyMode(ProxyPrefs::MODE_FIXED_SERVERS);
+
+ base::CommandLine cl2(base::CommandLine::NO_PROGRAM);
+ cl2.AppendSwitchASCII(switches::kProxyPacUrl, "proxy");
+ scoped_refptr<TestCommandLinePrefStore> store2 =
+ new TestCommandLinePrefStore(&cl2);
+ store2->VerifyProxyMode(ProxyPrefs::MODE_PAC_SCRIPT);
+
+ base::CommandLine cl3(base::CommandLine::NO_PROGRAM);
+ cl3.AppendSwitchASCII(switches::kProxyServer, std::string());
+ scoped_refptr<TestCommandLinePrefStore> store3 =
+ new TestCommandLinePrefStore(&cl3);
+ store3->VerifyProxyMode(ProxyPrefs::MODE_DIRECT);
+}
+
+TEST(ChromeCommandLinePrefStoreTest, DisableSSLCipherSuites) {
+ base::CommandLine cl1(base::CommandLine::NO_PROGRAM);
+ cl1.AppendSwitchASCII(switches::kCipherSuiteBlacklist,
+ "0x0004,0x0005");
+ scoped_refptr<TestCommandLinePrefStore> store1 =
+ new TestCommandLinePrefStore(&cl1);
+ const char* const expected_ciphers1[] = {
+ "0x0004",
+ "0x0005",
+ };
+ store1->VerifySSLCipherSuites(expected_ciphers1,
+ base::size(expected_ciphers1));
+
+ base::CommandLine cl2(base::CommandLine::NO_PROGRAM);
+ cl2.AppendSwitchASCII(switches::kCipherSuiteBlacklist,
+ "0x0004, WHITESPACE_IGNORED TEST , 0x0005");
+ scoped_refptr<TestCommandLinePrefStore> store2 =
+ new TestCommandLinePrefStore(&cl2);
+ const char* const expected_ciphers2[] = {
+ "0x0004",
+ "WHITESPACE_IGNORED TEST",
+ "0x0005",
+ };
+ store2->VerifySSLCipherSuites(expected_ciphers2,
+ base::size(expected_ciphers2));
+
+ base::CommandLine cl3(base::CommandLine::NO_PROGRAM);
+ cl3.AppendSwitchASCII(switches::kCipherSuiteBlacklist,
+ "0x0004;MOAR;0x0005");
+ scoped_refptr<TestCommandLinePrefStore> store3 =
+ new TestCommandLinePrefStore(&cl3);
+ const char* const expected_ciphers3[] = {
+ "0x0004;MOAR;0x0005"
+ };
+ store3->VerifySSLCipherSuites(expected_ciphers3,
+ base::size(expected_ciphers3));
+}
diff --git a/chromium/chrome/browser/prefs/chrome_pref_model_associator_client.cc b/chromium/chrome/browser/prefs/chrome_pref_model_associator_client.cc
new file mode 100644
index 00000000000..a628aa6a4fd
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_pref_model_associator_client.cc
@@ -0,0 +1,57 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/chrome_pref_model_associator_client.h"
+
+#include <cstdint>
+
+#include "base/memory/singleton.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/browser/website_settings_info.h"
+#include "components/content_settings/core/browser/website_settings_registry.h"
+
+// static
+ChromePrefModelAssociatorClient*
+ChromePrefModelAssociatorClient::GetInstance() {
+ return base::Singleton<ChromePrefModelAssociatorClient>::get();
+}
+
+ChromePrefModelAssociatorClient::ChromePrefModelAssociatorClient() {}
+
+ChromePrefModelAssociatorClient::~ChromePrefModelAssociatorClient() {}
+
+bool ChromePrefModelAssociatorClient::IsMergeableListPreference(
+ const std::string& pref_name) const {
+ return pref_name == prefs::kURLsToRestoreOnStartup;
+}
+
+bool ChromePrefModelAssociatorClient::IsMergeableDictionaryPreference(
+ const std::string& pref_name) const {
+ const content_settings::WebsiteSettingsRegistry& registry =
+ *content_settings::WebsiteSettingsRegistry::GetInstance();
+ for (const content_settings::WebsiteSettingsInfo* info : registry) {
+ if (info->pref_name() == pref_name)
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<base::Value>
+ChromePrefModelAssociatorClient::MaybeMergePreferenceValues(
+ const std::string& pref_name,
+ const base::Value& local_value,
+ const base::Value& server_value) const {
+ if (pref_name == prefs::kNetworkEasterEggHighScore) {
+ uint32_t local_high_score;
+ if (!local_value.GetAsInteger(reinterpret_cast<int*>(&local_high_score)))
+ return nullptr;
+ uint32_t server_high_score;
+ if (!server_value.GetAsInteger(reinterpret_cast<int*>(&server_high_score)))
+ return nullptr;
+ return std::make_unique<base::Value>(
+ static_cast<int>(std::max(local_high_score, server_high_score)));
+ }
+
+ return nullptr;
+}
diff --git a/chromium/chrome/browser/prefs/chrome_pref_model_associator_client.h b/chromium/chrome/browser/prefs/chrome_pref_model_associator_client.h
new file mode 100644
index 00000000000..d11606a688d
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_pref_model_associator_client.h
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_CHROME_PREF_MODEL_ASSOCIATOR_CLIENT_H_
+#define CHROME_BROWSER_PREFS_CHROME_PREF_MODEL_ASSOCIATOR_CLIENT_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/sync_preferences/pref_model_associator_client.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+class ChromePrefModelAssociatorClient
+ : public sync_preferences::PrefModelAssociatorClient {
+ public:
+ // Returns the global instance.
+ static ChromePrefModelAssociatorClient* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ChromePrefModelAssociatorClient>;
+
+ ChromePrefModelAssociatorClient();
+ ~ChromePrefModelAssociatorClient() override;
+
+ // sync_preferences::PrefModelAssociatorClient implementation.
+ bool IsMergeableListPreference(const std::string& pref_name) const override;
+ bool IsMergeableDictionaryPreference(
+ const std::string& pref_name) const override;
+ std::unique_ptr<base::Value> MaybeMergePreferenceValues(
+ const std::string& pref_name,
+ const base::Value& local_value,
+ const base::Value& server_value) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromePrefModelAssociatorClient);
+};
+
+#endif // CHROME_BROWSER_PREFS_CHROME_PREF_MODEL_ASSOCIATOR_CLIENT_H_
diff --git a/chromium/chrome/browser/prefs/chrome_pref_service_factory.cc b/chromium/chrome/browser/prefs/chrome_pref_service_factory.cc
new file mode 100644
index 00000000000..6b6e1fe677f
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_pref_service_factory.cc
@@ -0,0 +1,540 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/chrome_pref_service_factory.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+#include "chrome/browser/prefs/chrome_pref_model_associator_client.h"
+#include "chrome/browser/prefs/profile_pref_store_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/glue/sync_start_util.h"
+#include "chrome/browser/ui/profile_error_dialog.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/grit/browser_resources.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/component_updater/pref_names.h"
+#include "components/policy/core/browser/configuration_policy_pref_store.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/default_pref_store.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_registry.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "components/search_engines/default_search_manager.h"
+#include "components/search_engines/search_engines_pref_names.h"
+#include "components/signin/public/base/signin_pref_names.h"
+#include "components/sync/base/model_type.h"
+#include "components/sync/base/pref_names.h"
+#include "components/sync_preferences/pref_model_associator.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+#include "components/sync_preferences/pref_service_syncable_factory.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/buildflags/buildflags.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "rlz/buildflags/buildflags.h"
+#include "services/preferences/public/cpp/tracked/configuration.h"
+#include "services/preferences/public/cpp/tracked/pref_names.h"
+#include "sql/error_delegate_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/browser/pref_names.h"
+#endif
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+#include "chrome/browser/supervised_user/supervised_user_pref_store.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/enterprise_util.h"
+#if BUILDFLAG(ENABLE_RLZ)
+#include "rlz/lib/machine_id.h"
+#endif // BUILDFLAG(ENABLE_RLZ)
+#endif // defined(OS_WIN)
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+using EnforcementLevel =
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel;
+using PrefTrackingStrategy =
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy;
+using ValueType = prefs::mojom::TrackedPreferenceMetadata::ValueType;
+
+namespace {
+
+#if defined(OS_WIN)
+// Whether we are in testing mode; can be enabled via
+// DisableDomainCheckForTesting(). Forces startup checks to ignore the presence
+// of a domain when determining the active SettingsEnforcement group.
+bool g_disable_domain_check_for_testing = false;
+#endif // OS_WIN
+
+// These preferences must be kept in sync with the TrackedPreference enum in
+// tools/metrics/histograms/enums.xml. To add a new preference, append it to the
+// array and add a corresponding value to the histogram enum. Each tracked
+// preference must be given a unique reporting ID.
+// See CleanupDeprecatedTrackedPreferences() in pref_hash_filter.cc to remove a
+// deprecated tracked preference.
+const prefs::TrackedPreferenceMetadata kTrackedPrefs[] = {
+ {0, prefs::kShowHomeButton, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {1, prefs::kHomePageIsNewTabPage, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {2, prefs::kHomePage, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {3, prefs::kRestoreOnStartup, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {4, prefs::kURLsToRestoreOnStartup, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ {5, extensions::pref_names::kExtensions, EnforcementLevel::NO_ENFORCEMENT,
+ PrefTrackingStrategy::SPLIT, ValueType::IMPERSONAL},
+#endif
+ {6, prefs::kGoogleServicesLastUsername, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::PERSONAL},
+ {7, prefs::kSearchProviderOverrides, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#if !defined(OS_ANDROID)
+ {11, prefs::kPinnedTabs, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#endif
+ {14, DefaultSearchManager::kDefaultSearchProviderDataPrefName,
+ EnforcementLevel::NO_ENFORCEMENT, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+ {// Protecting kPreferenceResetTime does two things:
+ // 1) It ensures this isn't accidently set by someone stomping the pref
+ // file.
+ // 2) More importantly, it declares kPreferenceResetTime as a protected
+ // pref which is required for it to be visible when queried via the
+ // SegregatedPrefStore. This is because it's written directly in the
+ // protected JsonPrefStore by that store's PrefHashFilter if there was
+ // a reset in FilterOnLoad and SegregatedPrefStore will not look for it
+ // in the protected JsonPrefStore unless it's declared as a protected
+ // preference here.
+ 15, user_prefs::kPreferenceResetTime, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ // kSyncRemainingRollbackTries is deprecated and will be removed a few
+ // releases after M50.
+ {18, prefs::kSafeBrowsingIncidentsSent, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#if defined(OS_WIN)
+ {19, prefs::kSwReporterPromptVersion, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#endif
+#if defined(OS_WIN)
+ {22, prefs::kSwReporterPromptSeed, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#endif
+ {23, prefs::kGoogleServicesAccountId, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::PERSONAL},
+ {24, prefs::kGoogleServicesLastAccountId, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::PERSONAL},
+#if defined(OS_WIN)
+ {25, prefs::kSettingsResetPromptPromptWave,
+ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+ {26, prefs::kSettingsResetPromptLastTriggeredForDefaultSearch,
+ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+ {27, prefs::kSettingsResetPromptLastTriggeredForStartupUrls,
+ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+ {28, prefs::kSettingsResetPromptLastTriggeredForHomepage,
+ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+#endif // defined(OS_WIN)
+ {29, prefs::kMediaStorageIdSalt, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ {30, prefs::kModuleBlacklistCacheMD5Digest,
+ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+#endif
+#if defined(OS_WIN)
+ {31, prefs::kSwReporterReportingEnabled, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#endif // defined(OS_WIN)
+
+ // See note at top, new items added here also need to be added to
+ // histograms.xml's TrackedPreference enum.
+};
+
+// One more than the last tracked preferences ID above.
+const size_t kTrackedPrefsReportingIDsCount =
+ kTrackedPrefs[base::size(kTrackedPrefs) - 1].reporting_id + 1;
+
+// Each group enforces a superset of the protection provided by the previous
+// one.
+enum SettingsEnforcementGroup {
+ GROUP_NO_ENFORCEMENT,
+ // Enforce protected settings on profile loads.
+ GROUP_ENFORCE_ALWAYS,
+ // Also enforce extension default search.
+ GROUP_ENFORCE_ALWAYS_WITH_DSE,
+ // Also enforce extension settings and default search.
+ GROUP_ENFORCE_ALWAYS_WITH_EXTENSIONS_AND_DSE,
+ // The default enforcement group contains all protection features.
+ GROUP_ENFORCE_DEFAULT
+};
+
+SettingsEnforcementGroup GetSettingsEnforcementGroup() {
+# if defined(OS_WIN)
+ if (!g_disable_domain_check_for_testing) {
+ static bool first_call = true;
+ static const bool is_managed = base::IsMachineExternallyManaged();
+ if (first_call) {
+ UMA_HISTOGRAM_BOOLEAN("Settings.TrackedPreferencesNoEnforcementOnDomain",
+ is_managed);
+ first_call = false;
+ }
+ if (is_managed)
+ return GROUP_NO_ENFORCEMENT;
+ }
+#endif
+
+ struct {
+ const char* group_name;
+ SettingsEnforcementGroup group;
+ } static const kEnforcementLevelMap[] = {
+ { chrome_prefs::internals::kSettingsEnforcementGroupNoEnforcement,
+ GROUP_NO_ENFORCEMENT },
+ { chrome_prefs::internals::kSettingsEnforcementGroupEnforceAlways,
+ GROUP_ENFORCE_ALWAYS },
+ { chrome_prefs::internals::
+ kSettingsEnforcementGroupEnforceAlwaysWithDSE,
+ GROUP_ENFORCE_ALWAYS_WITH_DSE },
+ { chrome_prefs::internals::
+ kSettingsEnforcementGroupEnforceAlwaysWithExtensionsAndDSE,
+ GROUP_ENFORCE_ALWAYS_WITH_EXTENSIONS_AND_DSE },
+ };
+
+ // Use the strongest enforcement setting in the absence of a field trial
+ // config on Windows and MacOS. Remember to update the OFFICIAL_BUILD section
+ // of extension_startup_browsertest.cc and pref_hash_browsertest.cc when
+ // updating the default value below.
+ // TODO(gab): Enforce this on all platforms.
+ SettingsEnforcementGroup enforcement_group =
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ GROUP_ENFORCE_DEFAULT;
+#else
+ GROUP_NO_ENFORCEMENT;
+#endif
+ bool group_determined_from_trial = false;
+ base::FieldTrial* trial =
+ base::FieldTrialList::Find(
+ chrome_prefs::internals::kSettingsEnforcementTrialName);
+ if (trial) {
+ const std::string& group_name = trial->group_name();
+ for (size_t i = 0; i < base::size(kEnforcementLevelMap); ++i) {
+ if (kEnforcementLevelMap[i].group_name == group_name) {
+ enforcement_group = kEnforcementLevelMap[i].group;
+ group_determined_from_trial = true;
+ break;
+ }
+ }
+ }
+ UMA_HISTOGRAM_BOOLEAN("Settings.EnforcementGroupDeterminedFromTrial",
+ group_determined_from_trial);
+ return enforcement_group;
+}
+
+// Returns the effective preference tracking configuration.
+std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+GetTrackingConfiguration() {
+ const SettingsEnforcementGroup enforcement_group =
+ GetSettingsEnforcementGroup();
+
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> result;
+ for (size_t i = 0; i < base::size(kTrackedPrefs); ++i) {
+ prefs::mojom::TrackedPreferenceMetadataPtr data =
+ prefs::ConstructTrackedMetadata(kTrackedPrefs[i]);
+
+ if (GROUP_NO_ENFORCEMENT == enforcement_group) {
+ // Remove enforcement for all tracked preferences.
+ data->enforcement_level = EnforcementLevel::NO_ENFORCEMENT;
+ }
+
+ if (enforcement_group >= GROUP_ENFORCE_ALWAYS_WITH_DSE &&
+ data->name ==
+ DefaultSearchManager::kDefaultSearchProviderDataPrefName) {
+ // Specifically enable default search settings enforcement.
+ data->enforcement_level = EnforcementLevel::ENFORCE_ON_LOAD;
+ }
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (enforcement_group >= GROUP_ENFORCE_ALWAYS_WITH_EXTENSIONS_AND_DSE &&
+ data->name == extensions::pref_names::kExtensions) {
+ // Specifically enable extension settings enforcement.
+ data->enforcement_level = EnforcementLevel::ENFORCE_ON_LOAD;
+ }
+#endif
+
+ result.push_back(std::move(data));
+ }
+ return result;
+}
+
+std::unique_ptr<ProfilePrefStoreManager> CreateProfilePrefStoreManager(
+ const base::FilePath& profile_path) {
+ std::string legacy_device_id;
+#if defined(OS_WIN) && BUILDFLAG(ENABLE_RLZ)
+ // This is used by
+ // chrome/browser/apps/platform_apps/api/music_manager_private/device_id_win.cc
+ // but that API is private (http://crbug.com/276485) and other platforms are
+ // not available synchronously.
+ // As part of improving pref metrics on other platforms we may want to find
+ // ways to defer preference loading until the device ID can be used.
+ rlz_lib::GetMachineId(&legacy_device_id);
+#endif
+ std::string seed;
+ CHECK(ui::ResourceBundle::HasSharedInstance());
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ seed = ui::ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_PREF_HASH_SEED_BIN)
+ .as_string();
+#endif
+ return std::make_unique<ProfilePrefStoreManager>(profile_path, seed,
+ legacy_device_id);
+}
+
+void PrepareFactory(sync_preferences::PrefServiceSyncableFactory* factory,
+ const base::FilePath& pref_filename,
+ policy::PolicyService* policy_service,
+ SupervisedUserSettingsService* supervised_user_settings,
+ scoped_refptr<PersistentPrefStore> user_pref_store,
+ scoped_refptr<PrefStore> extension_prefs,
+ bool async,
+ policy::BrowserPolicyConnector* policy_connector) {
+ factory->SetManagedPolicies(policy_service, policy_connector);
+ factory->SetRecommendedPolicies(policy_service, policy_connector);
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+ if (supervised_user_settings) {
+ scoped_refptr<PrefStore> supervised_user_prefs =
+ base::MakeRefCounted<SupervisedUserPrefStore>(supervised_user_settings);
+ DCHECK(async || supervised_user_prefs->IsInitializationComplete());
+ factory->set_supervised_user_prefs(supervised_user_prefs);
+ }
+#endif
+
+ factory->set_async(async);
+ factory->set_extension_prefs(std::move(extension_prefs));
+ factory->set_command_line_prefs(
+ base::MakeRefCounted<ChromeCommandLinePrefStore>(
+ base::CommandLine::ForCurrentProcess()));
+ factory->set_read_error_callback(base::BindRepeating(
+ &chrome_prefs::HandlePersistentPrefStoreReadError, pref_filename));
+ factory->set_user_prefs(std::move(user_pref_store));
+ factory->SetPrefModelAssociatorClient(
+ ChromePrefModelAssociatorClient::GetInstance());
+}
+
+class ResetOnLoadObserverImpl : public prefs::mojom::ResetOnLoadObserver {
+ public:
+ explicit ResetOnLoadObserverImpl(const base::FilePath& profile_path)
+ : profile_path_(profile_path) {}
+
+ void OnResetOnLoad() override {
+ // A StartSyncFlare used to kick sync early in case of a reset event. This
+ // is done since sync may bring back the user's server value post-reset
+ // which could potentially cause a "settings flash" between the factory
+ // default and the re-instantiated server value. Starting sync ASAP
+ // minimizes the window before the server value is re-instantiated (this
+ // window can otherwise be as long as 10 seconds by default).
+ sync_start_util::GetFlareForSyncableService(profile_path_)
+ .Run(syncer::PREFERENCES);
+ }
+
+ private:
+ const base::FilePath profile_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResetOnLoadObserverImpl);
+};
+
+} // namespace
+
+namespace chrome_prefs {
+
+namespace internals {
+
+// Group modifications should be reflected in first_run_browsertest.cc and
+// pref_hash_browsertest.cc.
+const char kSettingsEnforcementTrialName[] = "SettingsEnforcement";
+const char kSettingsEnforcementGroupNoEnforcement[] = "no_enforcement";
+const char kSettingsEnforcementGroupEnforceAlways[] = "enforce_always";
+const char kSettingsEnforcementGroupEnforceAlwaysWithDSE[] =
+ "enforce_always_with_dse";
+const char kSettingsEnforcementGroupEnforceAlwaysWithExtensionsAndDSE[] =
+ "enforce_always_with_extensions_and_dse";
+
+} // namespace internals
+
+std::unique_ptr<PrefService> CreateLocalState(
+ const base::FilePath& pref_filename,
+ policy::PolicyService* policy_service,
+ scoped_refptr<PrefRegistry> pref_registry,
+ bool async,
+ std::unique_ptr<PrefValueStore::Delegate> delegate,
+ policy::BrowserPolicyConnector* policy_connector) {
+ sync_preferences::PrefServiceSyncableFactory factory;
+ PrepareFactory(&factory, pref_filename, policy_service,
+ nullptr, // supervised_user_settings
+ base::MakeRefCounted<JsonPrefStore>(
+ pref_filename, std::unique_ptr<PrefFilter>()),
+ nullptr, // extension_prefs
+ async, policy_connector);
+
+ return factory.Create(std::move(pref_registry), std::move(delegate));
+}
+
+std::unique_ptr<sync_preferences::PrefServiceSyncable> CreateProfilePrefs(
+ const base::FilePath& profile_path,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate,
+ policy::PolicyService* policy_service,
+ SupervisedUserSettingsService* supervised_user_settings,
+ scoped_refptr<PrefStore> extension_prefs,
+ scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry,
+ policy::BrowserPolicyConnector* connector,
+ bool async,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ std::unique_ptr<PrefValueStore::Delegate> delegate) {
+ TRACE_EVENT0("browser", "chrome_prefs::CreateProfilePrefs");
+
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver> reset_on_load_observer;
+ mojo::MakeSelfOwnedReceiver(
+ std::make_unique<ResetOnLoadObserverImpl>(profile_path),
+ reset_on_load_observer.InitWithNewPipeAndPassReceiver());
+ sync_preferences::PrefServiceSyncableFactory factory;
+ scoped_refptr<PersistentPrefStore> user_pref_store =
+ CreateProfilePrefStoreManager(profile_path)
+ ->CreateProfilePrefStore(
+ GetTrackingConfiguration(), kTrackedPrefsReportingIDsCount,
+ std::move(io_task_runner), std::move(reset_on_load_observer),
+ std::move(validation_delegate));
+ PrepareFactory(&factory, profile_path, policy_service,
+ supervised_user_settings, std::move(user_pref_store),
+ std::move(extension_prefs), async, connector);
+ return factory.CreateSyncable(std::move(pref_registry), std::move(delegate));
+}
+
+void InstallPoliciesOnLocalState(
+ PrefService* preexisting_local_state,
+ policy::PolicyService* policy_service,
+ std::unique_ptr<PrefValueStore::Delegate> delegate) {
+ sync_preferences::PrefServiceSyncableFactory factory;
+ policy::BrowserPolicyConnector* policy_connector =
+ g_browser_process->browser_policy_connector();
+ factory.SetManagedPolicies(policy_service, policy_connector);
+ factory.SetRecommendedPolicies(policy_service, policy_connector);
+ factory.ChangePrefValueStore(preexisting_local_state, std::move(delegate));
+}
+
+void DisableDomainCheckForTesting() {
+#if defined(OS_WIN)
+ g_disable_domain_check_for_testing = true;
+#endif // OS_WIN
+}
+
+bool InitializePrefsFromMasterPrefs(
+ const base::FilePath& profile_path,
+ std::unique_ptr<base::DictionaryValue> master_prefs) {
+ return CreateProfilePrefStoreManager(profile_path)
+ ->InitializePrefsFromMasterPrefs(GetTrackingConfiguration(),
+ kTrackedPrefsReportingIDsCount,
+ std::move(master_prefs));
+}
+
+base::Time GetResetTime(Profile* profile) {
+ return ProfilePrefStoreManager::GetResetTime(profile->GetPrefs());
+}
+
+void ClearResetTime(Profile* profile) {
+ ProfilePrefStoreManager::ClearResetTime(profile->GetPrefs());
+}
+
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
+ ProfilePrefStoreManager::RegisterProfilePrefs(registry);
+}
+
+void HandlePersistentPrefStoreReadError(
+ const base::FilePath& pref_filename,
+ PersistentPrefStore::PrefReadError error) {
+ // The error callback is always invoked back on the main thread (which is
+ // BrowserThread::UI unless called during early initialization before the main
+ // thread is promoted to BrowserThread::UI).
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+
+ // Sample the histogram also for the successful case in order to get a
+ // baseline on the success rate in addition to the error distribution.
+ UMA_HISTOGRAM_ENUMERATION("PrefService.ReadError", error,
+ PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM);
+
+ if (error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
+#if !defined(OS_CHROMEOS)
+ // Failing to load prefs on startup is a bad thing(TM). See bug 38352 for
+ // an example problem that this can cause.
+ // Do some diagnosis and try to avoid losing data.
+ int message_id = 0;
+ if (error <= PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE) {
+ message_id = IDS_PREFERENCES_CORRUPT_ERROR;
+ } else if (error != PersistentPrefStore::PREF_READ_ERROR_NO_FILE) {
+ message_id = IDS_PREFERENCES_UNREADABLE_ERROR;
+ }
+
+ if (message_id) {
+ // Note: ThreadTaskRunnerHandle() is usually BrowserThread::UI but during
+ // early startup it can be ChromeBrowserMainParts::DeferringTaskRunner
+ // which will forward to BrowserThread::UI when it's initialized.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ShowProfileErrorDialog, ProfileErrorType::PREFERENCES,
+ message_id,
+ sql::GetCorruptFileDiagnosticsInfo(pref_filename)));
+ }
+#else
+ // On ChromeOS error screen with message about broken local state
+ // will be displayed.
+
+ // A supplementary error message about broken local state - is included
+ // in logs and user feedbacks.
+ if (error != PersistentPrefStore::PREF_READ_ERROR_NONE &&
+ error != PersistentPrefStore::PREF_READ_ERROR_NO_FILE) {
+ LOG(ERROR) << "An error happened during prefs loading: " << error;
+ }
+#endif
+ }
+}
+
+} // namespace chrome_prefs
diff --git a/chromium/chrome/browser/prefs/chrome_pref_service_factory.h b/chromium/chrome/browser/prefs/chrome_pref_service_factory.h
new file mode 100644
index 00000000000..c86c0643f13
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_pref_service_factory.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_CHROME_PREF_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_PREFS_CHROME_PREF_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "services/preferences/public/mojom/tracked_preference_validation_delegate.mojom-forward.h"
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+class SequencedTaskRunner;
+class Time;
+}
+
+namespace policy {
+class PolicyService;
+class BrowserPolicyConnector;
+}
+
+namespace sync_preferences {
+class PrefServiceSyncable;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class PrefRegistry;
+class PrefService;
+
+class PrefStore;
+class Profile;
+class SupervisedUserSettingsService;
+
+namespace chrome_prefs {
+
+namespace internals {
+
+extern const char kSettingsEnforcementTrialName[];
+extern const char kSettingsEnforcementGroupNoEnforcement[];
+extern const char kSettingsEnforcementGroupEnforceAlways[];
+extern const char kSettingsEnforcementGroupEnforceAlwaysWithDSE[];
+extern const char kSettingsEnforcementGroupEnforceAlwaysWithExtensionsAndDSE[];
+
+} // namespace internals
+
+// Factory methods that create and initialize a new instance of a
+// PrefService for Chrome with the applicable PrefStores. The
+// |pref_filename| points to the user preference file. This is the
+// usual way to create a new PrefService.
+// |extension_pref_store| is used as the source for extension-controlled
+// preferences and may be NULL.
+// |policy_service| is used as the source for mandatory or recommended
+// policies.
+// |pref_registry| keeps the list of registered prefs and their default values.
+// If |async| is true, asynchronous version is used.
+// Notifies using PREF_INITIALIZATION_COMPLETED in the end. Details is set to
+// the created PrefService or NULL if creation has failed. Note, it is
+// guaranteed that in asynchronous version initialization happens after this
+// function returned.
+std::unique_ptr<PrefService> CreateLocalState(
+ const base::FilePath& pref_filename,
+ policy::PolicyService* policy_service,
+ scoped_refptr<PrefRegistry> pref_registry,
+ bool async,
+ std::unique_ptr<PrefValueStore::Delegate> delegate,
+ policy::BrowserPolicyConnector* policy_connector);
+
+std::unique_ptr<sync_preferences::PrefServiceSyncable> CreateProfilePrefs(
+ const base::FilePath& pref_filename,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate,
+ policy::PolicyService* policy_service,
+ SupervisedUserSettingsService* supervised_user_settings,
+ scoped_refptr<PrefStore> extension_prefs,
+ scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry,
+ policy::BrowserPolicyConnector* connector,
+ bool async,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ std::unique_ptr<PrefValueStore::Delegate> delegate);
+
+// Installs policy related PrefStores on |preexisting_local_state|.
+// |preexisting_local_state| instance is a local state that has user PrefStore
+// and commandline PrefStore initialized. It is missing the mandatory and
+// recommended PrefStores and this method will add them to it.
+// |policy_service| is used as the source for mandatory or recommended
+// policies.
+// |delegate| is passed to listen to PrefStore initialization events.
+void InstallPoliciesOnLocalState(
+ PrefService* preexisting_local_state,
+ policy::PolicyService* policy_service,
+ std::unique_ptr<PrefValueStore::Delegate> delegate);
+
+// Call before startup tasks kick in to ignore the presence of a domain when
+// determining the active SettingsEnforcement group. For testing only.
+void DisableDomainCheckForTesting();
+
+// Initializes the preferences for the profile at |profile_path| with the
+// preference values in |master_prefs|. Returns true on success.
+bool InitializePrefsFromMasterPrefs(
+ const base::FilePath& profile_path,
+ std::unique_ptr<base::DictionaryValue> master_prefs);
+
+// Retrieves the time of the last preference reset event, if any, for the
+// provided profile. If no reset has occurred, returns a null |Time|.
+base::Time GetResetTime(Profile* profile);
+
+// Clears the time of the last preference reset event, if any, for the provided
+// profile.
+void ClearResetTime(Profile* profile);
+
+// Register user prefs used by chrome preference system.
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+// Shows notifications which correspond to PersistentPrefStore's reading errors.
+void HandlePersistentPrefStoreReadError(
+ const base::FilePath& pref_filename,
+ PersistentPrefStore::PrefReadError error);
+
+} // namespace chrome_prefs
+
+#endif // CHROME_BROWSER_PREFS_CHROME_PREF_SERVICE_FACTORY_H_
diff --git a/chromium/chrome/browser/prefs/chrome_pref_service_unittest.cc b/chromium/chrome/browser/prefs/chrome_pref_service_unittest.cc
new file mode 100644
index 00000000000..5e31462ccdd
--- /dev/null
+++ b/chromium/chrome/browser/prefs/chrome_pref_service_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/common/web_preferences.h"
+#include "content/public/test/test_renderer_host.h"
+
+using content::RenderViewHostTester;
+using content::WebPreferences;
+
+TEST(ChromePrefServiceTest, UpdateCommandLinePrefStore) {
+ TestingPrefServiceSimple prefs;
+ prefs.registry()->RegisterBooleanPref(prefs::kCloudPrintProxyEnabled, false);
+
+ // Check to make sure the value is as expected.
+ const PrefService::Preference* pref =
+ prefs.FindPreference(prefs::kCloudPrintProxyEnabled);
+ ASSERT_TRUE(pref);
+ const base::Value* value = pref->GetValue();
+ ASSERT_TRUE(value);
+ EXPECT_EQ(base::Value::Type::BOOLEAN, value->type());
+ bool actual_bool_value = true;
+ EXPECT_TRUE(value->GetAsBoolean(&actual_bool_value));
+ EXPECT_FALSE(actual_bool_value);
+
+ // Change the command line.
+ base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
+ cmd_line.AppendSwitch(switches::kEnableCloudPrintProxy);
+
+ // Call UpdateCommandLinePrefStore and check to see if the value has changed.
+ prefs.UpdateCommandLinePrefStore(new ChromeCommandLinePrefStore(&cmd_line));
+ pref = prefs.FindPreference(prefs::kCloudPrintProxyEnabled);
+ ASSERT_TRUE(pref);
+ value = pref->GetValue();
+ ASSERT_TRUE(value);
+ EXPECT_EQ(base::Value::Type::BOOLEAN, value->type());
+ actual_bool_value = false;
+ EXPECT_TRUE(value->GetAsBoolean(&actual_bool_value));
+ EXPECT_TRUE(actual_bool_value);
+}
+
+class ChromePrefServiceWebKitPrefs : public ChromeRenderViewHostTestHarness {
+ protected:
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+
+ // Supply our own profile so we use the correct profile data. The test
+ // harness is not supposed to overwrite a profile if it's already created.
+
+ // Set some (WebKit) user preferences.
+ sync_preferences::TestingPrefServiceSyncable* pref_services =
+ profile()->GetTestingPrefService();
+ pref_services->SetUserPref(prefs::kDefaultCharset,
+ std::make_unique<base::Value>("utf8"));
+ pref_services->SetUserPref(prefs::kWebKitDefaultFontSize,
+ std::make_unique<base::Value>(20));
+ pref_services->SetUserPref(prefs::kWebKitTextAreasAreResizable,
+ std::make_unique<base::Value>(false));
+ pref_services->SetUserPref("webkit.webprefs.foo",
+ std::make_unique<base::Value>("bar"));
+ }
+};
+
+// Tests to see that webkit preferences are properly loaded and copied over
+// to a WebPreferences object.
+TEST_F(ChromePrefServiceWebKitPrefs, PrefsCopied) {
+ WebPreferences webkit_prefs =
+ RenderViewHostTester::For(rvh())->TestComputeWebPreferences();
+
+ // These values have been overridden by the profile preferences.
+ EXPECT_EQ("UTF-8", webkit_prefs.default_encoding);
+#if !defined(OS_ANDROID)
+ EXPECT_EQ(20, webkit_prefs.default_font_size);
+#else
+ // This pref is not configurable on Android so the default of 16 is always
+ // used.
+ EXPECT_EQ(16, webkit_prefs.default_font_size);
+#endif
+ EXPECT_FALSE(webkit_prefs.text_areas_are_resizable);
+
+ // These should still be the default values.
+#if defined(OS_MACOSX)
+ const char kDefaultFont[] = "Times";
+#elif defined(OS_CHROMEOS)
+ const char kDefaultFont[] = "Tinos";
+#else
+ const char kDefaultFont[] = "Times New Roman";
+#endif
+ EXPECT_EQ(base::ASCIIToUTF16(kDefaultFont),
+ webkit_prefs.standard_font_family_map[prefs::kWebKitCommonScript]);
+ EXPECT_TRUE(webkit_prefs.javascript_enabled);
+
+#if defined(OS_ANDROID)
+ // Touch event enabled only on Android.
+ EXPECT_TRUE(webkit_prefs.touch_event_feature_detection_enabled);
+#else
+ EXPECT_FALSE(webkit_prefs.touch_event_feature_detection_enabled);
+#endif
+}
diff --git a/chromium/chrome/browser/prefs/in_process_service_factory_factory.cc b/chromium/chrome/browser/prefs/in_process_service_factory_factory.cc
new file mode 100644
index 00000000000..326f1466915
--- /dev/null
+++ b/chromium/chrome/browser/prefs/in_process_service_factory_factory.cc
@@ -0,0 +1,41 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/in_process_service_factory_factory.h"
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/core/simple_dependency_manager.h"
+#include "services/preferences/public/cpp/in_process_service_factory.h"
+#include "services/service_manager/public/cpp/service.h"
+
+// static
+InProcessPrefServiceFactoryFactory*
+InProcessPrefServiceFactoryFactory::GetInstance() {
+ return base::Singleton<InProcessPrefServiceFactoryFactory>::get();
+}
+
+// static
+prefs::InProcessPrefServiceFactory*
+InProcessPrefServiceFactoryFactory::GetInstanceForKey(SimpleFactoryKey* key) {
+ return static_cast<prefs::InProcessPrefServiceFactory*>(
+ GetInstance()->GetServiceForKey(key, true));
+}
+
+InProcessPrefServiceFactoryFactory::InProcessPrefServiceFactoryFactory()
+ : SimpleKeyedServiceFactory("InProcessPrefServiceFactory",
+ SimpleDependencyManager::GetInstance()) {}
+
+InProcessPrefServiceFactoryFactory::~InProcessPrefServiceFactoryFactory() =
+ default;
+
+std::unique_ptr<KeyedService>
+InProcessPrefServiceFactoryFactory::BuildServiceInstanceFor(
+ SimpleFactoryKey* key) const {
+ return std::make_unique<prefs::InProcessPrefServiceFactory>();
+}
+
+SimpleFactoryKey* InProcessPrefServiceFactoryFactory::GetKeyToUse(
+ SimpleFactoryKey* key) const {
+ return key;
+}
diff --git a/chromium/chrome/browser/prefs/in_process_service_factory_factory.h b/chromium/chrome/browser/prefs/in_process_service_factory_factory.h
new file mode 100644
index 00000000000..5c9e7b3899d
--- /dev/null
+++ b/chromium/chrome/browser/prefs/in_process_service_factory_factory.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_IN_PROCESS_SERVICE_FACTORY_FACTORY_H_
+#define CHROME_BROWSER_PREFS_IN_PROCESS_SERVICE_FACTORY_FACTORY_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/keyed_service/core/simple_keyed_service_factory.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace prefs {
+class InProcessPrefServiceFactory;
+}
+
+class InProcessPrefServiceFactoryFactory : public SimpleKeyedServiceFactory {
+ public:
+ static InProcessPrefServiceFactoryFactory* GetInstance();
+
+ static prefs::InProcessPrefServiceFactory* GetInstanceForKey(
+ SimpleFactoryKey* key);
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ InProcessPrefServiceFactoryFactory>;
+
+ InProcessPrefServiceFactoryFactory();
+ ~InProcessPrefServiceFactoryFactory() override;
+
+ std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+ SimpleFactoryKey* key) const override;
+ SimpleFactoryKey* GetKeyToUse(SimpleFactoryKey* key) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(InProcessPrefServiceFactoryFactory);
+};
+
+#endif // CHROME_BROWSER_PREFS_IN_PROCESS_SERVICE_FACTORY_FACTORY_H_
diff --git a/chromium/chrome/browser/prefs/incognito_mode_prefs.cc b/chromium/chrome/browser/prefs/incognito_mode_prefs.cc
new file mode 100644
index 00000000000..881d7e6934a
--- /dev/null
+++ b/chromium/chrome/browser/prefs/incognito_mode_prefs.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+
+#include <stdint.h>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/task/post_task.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <objbase.h>
+#include <wpcapi.h>
+#include <wrl/client.h>
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/singleton.h"
+#endif // OS_WIN
+
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/partner_browser_customizations.h"
+#endif // defined(OS_ANDROID)
+
+using content::BrowserThread;
+
+#if defined(OS_WIN)
+namespace {
+
+// This singleton allows us to attempt to calculate the Platform Parental
+// Controls enabled value on a worker thread before the UI thread needs the
+// value. If the UI thread finishes sooner than we expect, that's no worse than
+// today where we block.
+class PlatformParentalControlsValue {
+ public:
+ static PlatformParentalControlsValue* GetInstance() {
+ return base::Singleton<PlatformParentalControlsValue>::get();
+ }
+
+ bool is_enabled() const {
+ return is_enabled_;
+ }
+
+ private:
+ friend struct base::DefaultSingletonTraits<PlatformParentalControlsValue>;
+
+ // Histogram enum for tracking the thread that checked parental controls.
+ enum class ThreadType {
+ UI = 0,
+ BLOCKING,
+ COUNT,
+ };
+
+ PlatformParentalControlsValue()
+ : is_enabled_(IsParentalControlActivityLoggingOn()) {}
+
+ ~PlatformParentalControlsValue() = default;
+
+ // Returns true if Windows Parental control activity logging is enabled. This
+ // feature is available on Windows 7 and beyond. This function should be
+ // called on a COM Initialized thread and is potentially blocking.
+ static bool IsParentalControlActivityLoggingOn() {
+ ThreadType thread_type = ThreadType::BLOCKING;
+ if (BrowserThread::IsThreadInitialized(BrowserThread::UI) &&
+ content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ thread_type = ThreadType::UI;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION(
+ "IncognitoModePrefs.WindowsParentalControlsInitThread",
+ static_cast<int32_t>(thread_type),
+ static_cast<int32_t>(ThreadType::COUNT));
+
+ base::Time begin_time = base::Time::Now();
+ bool result = IsParentalControlActivityLoggingOnImpl();
+ UMA_HISTOGRAM_TIMES("IncognitoModePrefs.WindowsParentalControlsInitTime",
+ base::Time::Now() - begin_time);
+ return result;
+ }
+
+ // Does the work of determining if Windows Parental control activity logging
+ // is enabled.
+ static bool IsParentalControlActivityLoggingOnImpl() {
+ // Since we can potentially block, make sure the thread is okay with this.
+ base::ScopedBlockingCall scoped_blocking_call(
+ FROM_HERE, base::BlockingType::MAY_BLOCK);
+ Microsoft::WRL::ComPtr<IWindowsParentalControlsCore> parent_controls;
+ HRESULT hr = ::CoCreateInstance(__uuidof(WindowsParentalControls), nullptr,
+ CLSCTX_ALL, IID_PPV_ARGS(&parent_controls));
+ if (FAILED(hr))
+ return false;
+
+ Microsoft::WRL::ComPtr<IWPCSettings> settings;
+ hr = parent_controls->GetUserSettings(nullptr, settings.GetAddressOf());
+ if (FAILED(hr))
+ return false;
+
+ unsigned long restrictions = 0;
+ settings->GetRestrictions(&restrictions);
+
+ return (restrictions & WPCFLAG_LOGGING_REQUIRED) ==
+ WPCFLAG_LOGGING_REQUIRED;
+ }
+
+ const bool is_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformParentalControlsValue);
+};
+
+} // namespace
+#endif // OS_WIN
+
+// static
+// Sadly, this is required until c++17.
+constexpr IncognitoModePrefs::Availability
+ IncognitoModePrefs::kDefaultAvailability;
+
+// static
+bool IncognitoModePrefs::IntToAvailability(int in_value,
+ Availability* out_value) {
+ if (in_value < 0 || in_value >= AVAILABILITY_NUM_TYPES) {
+ *out_value = kDefaultAvailability;
+ return false;
+ }
+ *out_value = static_cast<Availability>(in_value);
+ return true;
+}
+
+// static
+IncognitoModePrefs::Availability IncognitoModePrefs::GetAvailability(
+ const PrefService* pref_service) {
+ return GetAvailabilityInternal(pref_service, CHECK_PARENTAL_CONTROLS);
+}
+
+// static
+void IncognitoModePrefs::SetAvailability(PrefService* prefs,
+ const Availability availability) {
+ prefs->SetInteger(prefs::kIncognitoModeAvailability, availability);
+}
+
+// static
+void IncognitoModePrefs::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterIntegerPref(prefs::kIncognitoModeAvailability,
+ kDefaultAvailability);
+}
+
+// static
+bool IncognitoModePrefs::ShouldLaunchIncognito(
+ const base::CommandLine& command_line,
+ const PrefService* prefs) {
+ // Note: This code only checks parental controls if the user requested
+ // to launch in incognito mode or if it was forced via prefs. This way,
+ // the parental controls check (which can be quite slow) can be avoided
+ // most of the time.
+ const bool should_use_incognito =
+ command_line.HasSwitch(switches::kIncognito) ||
+ GetAvailabilityInternal(prefs, DONT_CHECK_PARENTAL_CONTROLS) ==
+ IncognitoModePrefs::FORCED;
+ return should_use_incognito &&
+ GetAvailabilityInternal(prefs, CHECK_PARENTAL_CONTROLS) !=
+ IncognitoModePrefs::DISABLED;
+}
+
+// static
+bool IncognitoModePrefs::CanOpenBrowser(Profile* profile) {
+ if (profile->IsGuestSession())
+ return true;
+
+ switch (GetAvailability(profile->GetPrefs())) {
+ case IncognitoModePrefs::ENABLED:
+ return true;
+
+ case IncognitoModePrefs::DISABLED:
+ return !profile->IsOffTheRecord();
+
+ case IncognitoModePrefs::FORCED:
+ return profile->IsOffTheRecord();
+
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+#if defined(OS_WIN)
+// static
+void IncognitoModePrefs::InitializePlatformParentalControls() {
+ base::CreateCOMSTATaskRunner(
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE})
+ ->PostTask(FROM_HERE, base::BindOnce(base::IgnoreResult(
+ &PlatformParentalControlsValue::GetInstance)));
+}
+#endif
+
+// static
+bool IncognitoModePrefs::ArePlatformParentalControlsEnabled() {
+#if defined(OS_WIN)
+ return PlatformParentalControlsValue::GetInstance()->is_enabled();
+#elif defined(OS_ANDROID)
+ return chrome::android::PartnerBrowserCustomizations::IsIncognitoDisabled();
+#else
+ return false;
+#endif
+}
+
+// static
+IncognitoModePrefs::Availability IncognitoModePrefs::GetAvailabilityInternal(
+ const PrefService* pref_service,
+ GetAvailabilityMode mode) {
+ DCHECK(pref_service);
+ int pref_value = pref_service->GetInteger(prefs::kIncognitoModeAvailability);
+ Availability result = kDefaultAvailability;
+ bool valid = IntToAvailability(pref_value, &result);
+ DCHECK(valid);
+ if (result != IncognitoModePrefs::DISABLED &&
+ mode == CHECK_PARENTAL_CONTROLS && ArePlatformParentalControlsEnabled()) {
+ if (result == IncognitoModePrefs::FORCED)
+ LOG(ERROR) << "Ignoring FORCED incognito. Parental control logging on";
+ return IncognitoModePrefs::DISABLED;
+ }
+ return result;
+}
diff --git a/chromium/chrome/browser/prefs/incognito_mode_prefs.h b/chromium/chrome/browser/prefs/incognito_mode_prefs.h
new file mode 100644
index 00000000000..03ad9282af7
--- /dev/null
+++ b/chromium/chrome/browser/prefs/incognito_mode_prefs.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_INCOGNITO_MODE_PREFS_H_
+#define CHROME_BROWSER_PREFS_INCOGNITO_MODE_PREFS_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+
+class PrefService;
+class Profile;
+
+namespace base {
+class CommandLine;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// Specifies Incognito mode availability preferences.
+class IncognitoModePrefs {
+ public:
+ // Possible values for Incognito mode availability. Please, do not change
+ // the order of entries since numeric values are exposed to users.
+ enum Availability {
+ // Incognito mode enabled. Users may open pages in both Incognito mode and
+ // normal mode (usually the default behaviour).
+ ENABLED = 0,
+ // Incognito mode disabled. Users may not open pages in Incognito mode.
+ // Only normal mode is available for browsing.
+ DISABLED,
+ // Incognito mode forced. Users may open pages *ONLY* in Incognito mode.
+ // Normal mode is not available for browsing.
+ FORCED,
+
+ AVAILABILITY_NUM_TYPES
+ };
+
+ static constexpr Availability kDefaultAvailability = ENABLED;
+
+ // Register incognito related preferences.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Returns kIncognitoModeAvailability preference value stored
+ // in the given pref service.
+ static Availability GetAvailability(const PrefService* prefs);
+
+ // Sets kIncognitoModeAvailability preference to the specified availability
+ // value.
+ static void SetAvailability(PrefService* prefs,
+ const Availability availability);
+
+ // Converts in_value into the corresponding Availability value. Returns true
+ // if conversion is successful (in_value is valid). Otherwise, returns false
+ // and *out_value is set to ENABLED.
+ static bool IntToAvailability(int in_value, Availability* out_value);
+
+ // Returns true if the browser should start in incognito mode.
+ static bool ShouldLaunchIncognito(const base::CommandLine& command_line,
+ const PrefService* prefs);
+
+ // Returns true if |profile| can open a new Browser. This checks the incognito
+ // availability policies and verifies if the |profile| type is allowed to
+ // open new windows.
+ static bool CanOpenBrowser(Profile* profile);
+
+#if defined(OS_WIN)
+ // Calculates and caches the platform parental controls enable value on a
+ // worker thread.
+ static void InitializePlatformParentalControls();
+#endif
+
+ // Returns whether parental controls have been enabled on the platform. This
+ // method evaluates and caches if the platform controls have been enabled on
+ // the first call, which must be on the UI thread when IO and blocking are
+ // allowed. Subsequent calls may be from any thread.
+ static bool ArePlatformParentalControlsEnabled() WARN_UNUSED_RESULT;
+
+ private:
+ // Specifies whether parental controls should be checked. See comment below.
+ enum GetAvailabilityMode {
+ CHECK_PARENTAL_CONTROLS,
+ DONT_CHECK_PARENTAL_CONTROLS,
+ };
+
+ // Internal version of GetAvailability() that specifies whether parental
+ // controls should be checked (which is expensive and not always necessary
+ // to do - such as when checking for FORCED state).
+ static Availability GetAvailabilityInternal(const PrefService* pref_service,
+ GetAvailabilityMode mode);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(IncognitoModePrefs);
+};
+
+#endif // CHROME_BROWSER_PREFS_INCOGNITO_MODE_PREFS_H_
diff --git a/chromium/chrome/browser/prefs/incognito_mode_prefs_unittest.cc b/chromium/chrome/browser/prefs/incognito_mode_prefs_unittest.cc
new file mode 100644
index 00000000000..0fe41caa0e1
--- /dev/null
+++ b/chromium/chrome/browser/prefs/incognito_mode_prefs_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+
+#include "base/test/gtest_util.h"
+#include "chrome/common/pref_names.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class IncognitoModePrefsTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ IncognitoModePrefs::RegisterProfilePrefs(prefs_.registry());
+ }
+
+ sync_preferences::TestingPrefServiceSyncable prefs_;
+};
+
+TEST_F(IncognitoModePrefsTest, IntToAvailability) {
+ ASSERT_EQ(0, IncognitoModePrefs::ENABLED);
+ ASSERT_EQ(1, IncognitoModePrefs::DISABLED);
+ ASSERT_EQ(2, IncognitoModePrefs::FORCED);
+
+ IncognitoModePrefs::Availability incognito;
+ EXPECT_TRUE(IncognitoModePrefs::IntToAvailability(0, &incognito));
+ EXPECT_EQ(IncognitoModePrefs::ENABLED, incognito);
+ EXPECT_TRUE(IncognitoModePrefs::IntToAvailability(1, &incognito));
+ EXPECT_EQ(IncognitoModePrefs::DISABLED, incognito);
+ EXPECT_TRUE(IncognitoModePrefs::IntToAvailability(2, &incognito));
+ EXPECT_EQ(IncognitoModePrefs::FORCED, incognito);
+
+ EXPECT_FALSE(IncognitoModePrefs::IntToAvailability(10, &incognito));
+ EXPECT_EQ(IncognitoModePrefs::kDefaultAvailability, incognito);
+ EXPECT_FALSE(IncognitoModePrefs::IntToAvailability(-1, &incognito));
+ EXPECT_EQ(IncognitoModePrefs::kDefaultAvailability, incognito);
+}
+
+TEST_F(IncognitoModePrefsTest, GetAvailability) {
+ prefs_.SetUserPref(
+ prefs::kIncognitoModeAvailability,
+ std::make_unique<base::Value>(IncognitoModePrefs::ENABLED));
+ EXPECT_EQ(IncognitoModePrefs::ENABLED,
+ IncognitoModePrefs::GetAvailability(&prefs_));
+
+ prefs_.SetUserPref(
+ prefs::kIncognitoModeAvailability,
+ std::make_unique<base::Value>(IncognitoModePrefs::DISABLED));
+ EXPECT_EQ(IncognitoModePrefs::DISABLED,
+ IncognitoModePrefs::GetAvailability(&prefs_));
+
+ prefs_.SetUserPref(prefs::kIncognitoModeAvailability,
+ std::make_unique<base::Value>(IncognitoModePrefs::FORCED));
+ EXPECT_EQ(IncognitoModePrefs::FORCED,
+ IncognitoModePrefs::GetAvailability(&prefs_));
+}
+
+typedef IncognitoModePrefsTest IncognitoModePrefsDeathTest;
+
+TEST_F(IncognitoModePrefsDeathTest, GetAvailabilityBadValue) {
+ prefs_.SetUserPref(prefs::kIncognitoModeAvailability,
+ std::make_unique<base::Value>(-1));
+ EXPECT_DCHECK_DEATH({
+ IncognitoModePrefs::Availability availability =
+ IncognitoModePrefs::GetAvailability(&prefs_);
+ EXPECT_EQ(IncognitoModePrefs::ENABLED, availability);
+ });
+}
diff --git a/chromium/chrome/browser/prefs/origin_trial_prefs.cc b/chromium/chrome/browser/prefs/origin_trial_prefs.cc
new file mode 100644
index 00000000000..65e2407d31f
--- /dev/null
+++ b/chromium/chrome/browser/prefs/origin_trial_prefs.cc
@@ -0,0 +1,15 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/origin_trial_prefs.h"
+
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+
+// static
+void OriginTrialPrefs::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterStringPref(prefs::kOriginTrialPublicKey, "");
+ registry->RegisterListPref(prefs::kOriginTrialDisabledFeatures);
+ registry->RegisterListPref(prefs::kOriginTrialDisabledTokens);
+}
diff --git a/chromium/chrome/browser/prefs/origin_trial_prefs.h b/chromium/chrome/browser/prefs/origin_trial_prefs.h
new file mode 100644
index 00000000000..3e8baafb666
--- /dev/null
+++ b/chromium/chrome/browser/prefs/origin_trial_prefs.h
@@ -0,0 +1,15 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_ORIGIN_TRIAL_PREFS_H_
+#define CHROME_BROWSER_PREFS_ORIGIN_TRIAL_PREFS_H_
+
+class PrefRegistrySimple;
+
+class OriginTrialPrefs {
+ public:
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+};
+
+#endif // CHROME_BROWSER_PREFS_ORIGIN_TRIAL_PREFS_H_
diff --git a/chromium/chrome/browser/prefs/pref_functional_browsertest.cc b/chromium/chrome/browser/prefs/pref_functional_browsertest.cc
new file mode 100644
index 00000000000..00fac2797cb
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_functional_browsertest.cc
@@ -0,0 +1,246 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/download/download_prefs.h"
+#include "chrome/browser/net/prediction_options.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/content_settings/core/browser/website_settings_info.h"
+#include "components/content_settings/core/browser/website_settings_registry.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "components/content_settings/core/common/pref_names.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/download_test_observer.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+using content::BrowserContext;
+using content::DownloadManager;
+
+class PrefsFunctionalTest : public InProcessBrowserTest {
+ protected:
+ // Create a DownloadTestObserverTerminal that will wait for the
+ // specified number of downloads to finish.
+ std::unique_ptr<content::DownloadTestObserver> CreateWaiter(
+ Browser* browser,
+ int num_downloads) {
+ DownloadManager* download_manager =
+ BrowserContext::GetDownloadManager(browser->profile());
+
+ content::DownloadTestObserver* downloads_observer =
+ new content::DownloadTestObserverTerminal(
+ download_manager,
+ num_downloads,
+ content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+ return base::WrapUnique(downloads_observer);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestDownloadDirPref) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ base::FilePath new_download_dir =
+ DownloadPrefs(browser()->profile()).DownloadPath().AppendASCII("subdir");
+ base::FilePath downloaded_pkg =
+ new_download_dir.AppendASCII("a_zip_file.zip");
+
+ // Set pref to download in new_download_dir.
+ browser()->profile()->GetPrefs()->SetFilePath(
+ prefs::kDownloadDefaultDirectory, new_download_dir);
+
+ // Create a downloads observer.
+ std::unique_ptr<content::DownloadTestObserver> downloads_observer(
+ CreateWaiter(browser(), 1));
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/downloads/a_zip_file.zip"));
+ // Waits for the download to complete.
+ downloads_observer->WaitForFinished();
+
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ EXPECT_TRUE(base::PathExists(downloaded_pkg));
+}
+
+// Verify image content settings show or hide images.
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestImageContentSettings) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/settings/image_page.html"));
+
+ bool result = false;
+ std::string script =
+ "for (i=0; i < document.images.length; i++) {"
+ " if ((document.images[i].naturalWidth != 0) &&"
+ " (document.images[i].naturalHeight != 0)) {"
+ " window.domAutomationController.send(true);"
+ " }"
+ "}"
+ "window.domAutomationController.send(false);";
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ script,
+ &result));
+ EXPECT_TRUE(result);
+
+ browser()->profile()->GetPrefs()->SetInteger(
+ content_settings::WebsiteSettingsRegistry::GetInstance()
+ ->Get(CONTENT_SETTINGS_TYPE_IMAGES)
+ ->default_value_pref_name(),
+ CONTENT_SETTING_BLOCK);
+
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/settings/image_page.html"));
+
+ result = false;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ script,
+ &result));
+ EXPECT_FALSE(result);
+}
+
+// Verify that enabling/disabling Javascript in prefs works.
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestJavascriptEnableDisable) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
+ prefs::kWebKitJavascriptEnabled));
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/javaScriptTitle.html"));
+ EXPECT_EQ(base::ASCIIToUTF16("Title from script javascript enabled"),
+ browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
+ browser()->profile()->GetPrefs()->SetBoolean(prefs::kWebKitJavascriptEnabled,
+ false);
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/javaScriptTitle.html"));
+ EXPECT_EQ(base::ASCIIToUTF16("This is html title"),
+ browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
+}
+
+// Verify restore for bookmark bar visibility.
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest,
+ TestSessionRestoreShowBookmarkBar) {
+ EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
+ bookmarks::prefs::kShowBookmarkBar));
+ browser()->profile()->GetPrefs()->SetBoolean(
+ bookmarks::prefs::kShowBookmarkBar, true);
+ EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
+ bookmarks::prefs::kShowBookmarkBar));
+
+ EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
+ bookmarks::prefs::kShowBookmarkBar));
+ EXPECT_EQ(BookmarkBar::SHOW, browser()->bookmark_bar_state());
+}
+
+// Verify images are not blocked in incognito mode.
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestImagesNotBlockedInIncognito) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url = embedded_test_server()->GetURL("/settings/image_page.html");
+ Browser* incognito_browser = CreateIncognitoBrowser();
+ ui_test_utils::NavigateToURL(incognito_browser, url);
+
+ bool result = false;
+ std::string script =
+ "for (i=0; i < document.images.length; i++) {"
+ " if ((document.images[i].naturalWidth != 0) &&"
+ " (document.images[i].naturalHeight != 0)) {"
+ " window.domAutomationController.send(true);"
+ " }"
+ "}"
+ "window.domAutomationController.send(false);";
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ incognito_browser->tab_strip_model()->GetActiveWebContents(),
+ script,
+ &result));
+ EXPECT_TRUE(result);
+}
+
+// Verify setting homepage preference to newtabpage across restarts. Part1
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, PRE_TestHomepageNewTabpagePrefs) {
+ browser()->profile()->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage,
+ true);
+}
+
+// Verify setting homepage preference to newtabpage across restarts. Part2
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestHomepageNewTabpagePrefs) {
+ EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
+ prefs::kHomePageIsNewTabPage));
+}
+
+// Verify setting homepage preference to specific url. Part1
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, PRE_TestHomepagePrefs) {
+ GURL home_page_url("http://www.google.com");
+
+ PrefService* prefs = browser()->profile()->GetPrefs();
+ prefs->SetBoolean(prefs::kHomePageIsNewTabPage, false);
+ const PrefService::Preference* pref =
+ prefs->FindPreference(prefs::kHomePage);
+ if (pref && !pref->IsManaged()) {
+ prefs->SetString(prefs::kHomePage, home_page_url.spec());
+ }
+}
+
+// Verify setting homepage preference to specific url. Part2
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestHomepagePrefs) {
+ GURL home_page_url("http://www.google.com");
+
+ PrefService* prefs = browser()->profile()->GetPrefs();
+ EXPECT_FALSE(prefs->GetBoolean(prefs::kHomePageIsNewTabPage));
+ EXPECT_EQ(home_page_url.spec(), prefs->GetString(prefs::kHomePage));
+}
+
+// Verify the security preference under privacy across restarts. Part1
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, PRE_TestPrivacySecurityPrefs) {
+ PrefService* prefs = browser()->profile()->GetPrefs();
+
+ static_assert(chrome_browser_net::NETWORK_PREDICTION_DEFAULT !=
+ chrome_browser_net::NETWORK_PREDICTION_NEVER,
+ "PrefsFunctionalTest.TestPrivacySecurityPrefs relies on "
+ "predictive network actions being enabled by default.");
+ EXPECT_EQ(chrome_browser_net::NETWORK_PREDICTION_DEFAULT,
+ prefs->GetInteger(prefs::kNetworkPredictionOptions));
+ prefs->SetInteger(prefs::kNetworkPredictionOptions,
+ chrome_browser_net::NETWORK_PREDICTION_NEVER);
+
+ EXPECT_TRUE(prefs->GetBoolean(prefs::kSafeBrowsingEnabled));
+ prefs->SetBoolean(prefs::kSafeBrowsingEnabled, false);
+
+ EXPECT_TRUE(prefs->GetBoolean(prefs::kAlternateErrorPagesEnabled));
+ prefs->SetBoolean(prefs::kAlternateErrorPagesEnabled, false);
+
+ EXPECT_TRUE(prefs->GetBoolean(prefs::kSearchSuggestEnabled));
+ prefs->SetBoolean(prefs::kSearchSuggestEnabled, false);
+}
+
+// Verify the security preference under privacy across restarts. Part2
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestPrivacySecurityPrefs) {
+ PrefService* prefs = browser()->profile()->GetPrefs();
+
+ EXPECT_EQ(chrome_browser_net::NETWORK_PREDICTION_NEVER,
+ prefs->GetInteger(prefs::kNetworkPredictionOptions));
+ EXPECT_FALSE(prefs->GetBoolean(prefs::kSafeBrowsingEnabled));
+ EXPECT_FALSE(prefs->GetBoolean(prefs::kAlternateErrorPagesEnabled));
+ EXPECT_FALSE(prefs->GetBoolean(prefs::kSearchSuggestEnabled));
+}
+
+// Verify that we have some Local State prefs.
+IN_PROC_BROWSER_TEST_F(PrefsFunctionalTest, TestHaveLocalStatePrefs) {
+ EXPECT_TRUE(g_browser_process->local_state()
+ ->GetPreferenceValues(PrefService::INCLUDE_DEFAULTS)
+ .get());
+}
diff --git a/chromium/chrome/browser/prefs/pref_metrics_service.cc b/chromium/chrome/browser/prefs/pref_metrics_service.cc
new file mode 100644
index 00000000000..f321fa5c75d
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_metrics_service.cc
@@ -0,0 +1,189 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/pref_metrics_service.h"
+
+#include <string>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/tabs/pinned_tab_codec.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/prefs/pref_service.h"
+#include "components/rappor/public/rappor_utils.h"
+#include "components/rappor/rappor_service_impl.h"
+#include "components/search_engines/template_url_prepopulate_data.h"
+#include "content/public/browser/browser_url_handler.h"
+#include "url/gurl.h"
+
+namespace {
+
+#if !defined(OS_ANDROID)
+// Record a sample for the Settings.NewTabPage rappor metric.
+void SampleNewTabPageURL(Profile* profile) {
+ GURL ntp_url(chrome::kChromeUINewTabURL);
+ bool reverse_on_redirect = false;
+ content::BrowserURLHandler::GetInstance()->RewriteURLIfNecessary(
+ &ntp_url,
+ profile,
+ &reverse_on_redirect);
+ if (ntp_url.is_valid()) {
+ rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
+ "Settings.NewTabPage", ntp_url);
+ }
+}
+#endif
+
+} // namespace
+
+PrefMetricsService::PrefMetricsService(Profile* profile)
+ : profile_(profile), prefs_(profile_->GetPrefs()) {
+ RecordLaunchPrefs();
+}
+
+PrefMetricsService::~PrefMetricsService() {
+}
+
+// static
+void PrefMetricsService::RecordHomePageLaunchMetrics(bool show_home_button,
+ bool homepage_is_ntp,
+ const GURL& homepage_url) {
+ UMA_HISTOGRAM_BOOLEAN("Settings.ShowHomeButton", show_home_button);
+ if (show_home_button) {
+ UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage",
+ homepage_is_ntp);
+ }
+
+ // For non-NTP homepages, see if the URL comes from the same TLD+1 as a known
+ // search engine. Note that this is only an approximation of search engine
+ // use, due to both false negatives (pages that come from unknown TLD+1 X but
+ // consist of a search box that sends to known TLD+1 Y) and false positives
+ // (pages that share a TLD+1 with a known engine but aren't actually search
+ // pages, e.g. plus.google.com). Additionally, record the TLD+1 of non-NTP
+ // homepages through the privacy-preserving Rappor service.
+ if (!homepage_is_ntp) {
+ if (homepage_url.is_valid()) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Settings.HomePageEngineType",
+ TemplateURLPrepopulateData::GetEngineType(homepage_url),
+ SEARCH_ENGINE_MAX);
+ rappor::SampleDomainAndRegistryFromGURL(
+ g_browser_process->rappor_service(), "Settings.HomePage2",
+ homepage_url);
+ }
+ }
+}
+
+void PrefMetricsService::RecordLaunchPrefs() {
+ // On Android, determining whether the homepage is enabled requires waiting
+ // for a response from a third party provider installed on the device. So,
+ // it will be logged later once all the dependent information is available.
+ // See DeferredStartupHandler.java.
+#if !defined(OS_ANDROID)
+ GURL homepage_url(prefs_->GetString(prefs::kHomePage));
+ RecordHomePageLaunchMetrics(prefs_->GetBoolean(prefs::kShowHomeButton),
+ prefs_->GetBoolean(prefs::kHomePageIsNewTabPage),
+ homepage_url);
+#endif
+
+ // Android does not support overriding the NTP URL.
+#if !defined(OS_ANDROID)
+ SampleNewTabPageURL(profile_);
+#endif
+
+ // Tab restoring is always done on Android, so these metrics are not
+ // applicable. Also, startup pages are not supported on Android
+#if !defined(OS_ANDROID)
+ int restore_on_startup = prefs_->GetInteger(prefs::kRestoreOnStartup);
+ UMA_HISTOGRAM_ENUMERATION(
+ "Settings.StartupPageLoadSettings", restore_on_startup,
+ static_cast<int>(SessionStartupPref::kPrefValueMax));
+ if (restore_on_startup == SessionStartupPref::kPrefValueURLs) {
+ const base::ListValue* url_list =
+ prefs_->GetList(prefs::kURLsToRestoreOnStartup);
+ // Similarly, check startup pages for known search engine TLD+1s.
+ std::string url_text;
+ for (size_t i = 0; i < url_list->GetSize(); ++i) {
+ if (url_list->GetString(i, &url_text)) {
+ GURL start_url(url_text);
+ if (start_url.is_valid()) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Settings.StartupPageEngineTypes",
+ TemplateURLPrepopulateData::GetEngineType(start_url),
+ SEARCH_ENGINE_MAX);
+ if (i == 0) {
+ rappor::SampleDomainAndRegistryFromGURL(
+ g_browser_process->rappor_service(),
+ "Settings.FirstStartupPage", start_url);
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ // Android does not support pinned tabs.
+#if !defined(OS_ANDROID)
+ StartupTabs startup_tabs = PinnedTabCodec::ReadPinnedTabs(profile_);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.PinnedTabs",
+ startup_tabs.size(), 1, 50, 20);
+ for (size_t i = 0; i < startup_tabs.size(); ++i) {
+ GURL start_url(startup_tabs.at(i).url);
+ if (start_url.is_valid()) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Settings.PinnedTabEngineTypes",
+ TemplateURLPrepopulateData::GetEngineType(start_url),
+ SEARCH_ENGINE_MAX);
+ }
+ }
+#endif
+}
+
+// static
+PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() {
+ return base::Singleton<PrefMetricsService::Factory>::get();
+}
+
+// static
+PrefMetricsService* PrefMetricsService::Factory::GetForProfile(
+ Profile* profile) {
+ return static_cast<PrefMetricsService*>(
+ GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+PrefMetricsService::Factory::Factory()
+ : BrowserContextKeyedServiceFactory(
+ "PrefMetricsService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(TemplateURLServiceFactory::GetInstance());
+}
+
+PrefMetricsService::Factory::~Factory() {
+}
+
+KeyedService* PrefMetricsService::Factory::BuildServiceInstanceFor(
+ content::BrowserContext* profile) const {
+ return new PrefMetricsService(static_cast<Profile*>(profile));
+}
+
+bool PrefMetricsService::Factory::ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const {
+ return false;
+}
+
+content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return chrome::GetBrowserContextRedirectedInIncognito(context);
+}
diff --git a/chromium/chrome/browser/prefs/pref_metrics_service.h b/chromium/chrome/browser/prefs/pref_metrics_service.h
new file mode 100644
index 00000000000..a1864def73f
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_metrics_service.h
@@ -0,0 +1,60 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_PREF_METRICS_SERVICE_H_
+#define CHROME_BROWSER_PREFS_PREF_METRICS_SERVICE_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/memory/weak_ptr.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+class PrefService;
+class Profile;
+
+// PrefMetricsService is responsible for recording prefs-related UMA stats.
+class PrefMetricsService : public KeyedService {
+ public:
+ explicit PrefMetricsService(Profile* profile);
+ ~PrefMetricsService() override;
+
+ // Records metrics about the state of the homepage on launch.
+ static void RecordHomePageLaunchMetrics(bool show_home_button,
+ bool homepage_is_ntp,
+ const GURL& homepage_url);
+
+ class Factory : public BrowserContextKeyedServiceFactory {
+ public:
+ static Factory* GetInstance();
+ static PrefMetricsService* GetForProfile(Profile* profile);
+ private:
+ friend struct base::DefaultSingletonTraits<Factory>;
+
+ Factory();
+ ~Factory() override;
+
+ // BrowserContextKeyedServiceFactory implementation
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ };
+
+ private:
+ // Record prefs state on browser context creation.
+ void RecordLaunchPrefs();
+
+ Profile* profile_;
+ PrefService* prefs_;
+
+ base::WeakPtrFactory<PrefMetricsService> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PrefMetricsService);
+};
+
+#endif // CHROME_BROWSER_PREFS_PREF_METRICS_SERVICE_H_
diff --git a/chromium/chrome/browser/prefs/pref_service_browsertest.cc b/chromium/chrome/browser/prefs/pref_service_browsertest.cc
new file mode 100644
index 00000000000..ccfd545b870
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_service_browsertest.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/path_service.h"
+#include "base/test/test_file_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/browser_window_state.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "ui/gfx/geometry/rect.h"
+
+typedef InProcessBrowserTest PreservedWindowPlacement;
+
+IN_PROC_BROWSER_TEST_F(PreservedWindowPlacement, PRE_Test) {
+ browser()->window()->SetBounds(gfx::Rect(20, 30, 600, 600));
+}
+
+// Fails on Chrome OS as the browser thinks it is restarting after a crash, see
+// http://crbug.com/168044
+#if defined(OS_CHROMEOS)
+#define MAYBE_Test DISABLED_Test
+#else
+#define MAYBE_Test Test
+#endif
+IN_PROC_BROWSER_TEST_F(PreservedWindowPlacement, MAYBE_Test) {
+ gfx::Rect bounds = browser()->window()->GetBounds();
+ gfx::Rect expected_bounds(gfx::Rect(20, 30, 600, 600));
+ ASSERT_EQ(expected_bounds.ToString(), bounds.ToString());
+}
+
+class PreferenceServiceTest : public InProcessBrowserTest {
+ public:
+ bool SetUpUserDataDirectory() override {
+ base::FilePath user_data_directory;
+ base::PathService::Get(chrome::DIR_USER_DATA, &user_data_directory);
+
+ original_pref_file_ = ui_test_utils::GetTestFilePath(
+ base::FilePath()
+ .AppendASCII("profiles")
+ .AppendASCII("window_placement")
+ .AppendASCII("Default"),
+ base::FilePath().Append(chrome::kPreferencesFilename));
+ tmp_pref_file_ =
+ user_data_directory.AppendASCII(TestingProfile::kTestUserProfileDir);
+ EXPECT_TRUE(base::CreateDirectory(tmp_pref_file_));
+ tmp_pref_file_ = tmp_pref_file_.Append(chrome::kPreferencesFilename);
+
+ EXPECT_TRUE(base::PathExists(original_pref_file_));
+ EXPECT_TRUE(base::CopyFile(original_pref_file_, tmp_pref_file_));
+
+#if defined(OS_WIN)
+ // Make the copy writable. On POSIX we assume the umask allows files
+ // we create to be writable.
+ EXPECT_TRUE(::SetFileAttributesW(tmp_pref_file_.value().c_str(),
+ FILE_ATTRIBUTE_NORMAL));
+#endif
+ return true;
+ }
+
+ protected:
+ base::FilePath original_pref_file_;
+ base::FilePath tmp_pref_file_;
+};
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+// This test verifies that the window position from the prefs file is restored
+// when the app restores. This doesn't really make sense on Linux, where
+// the window manager might fight with you over positioning. However, we
+// might be able to make this work on buildbots.
+// TODO(port): revisit this.
+
+IN_PROC_BROWSER_TEST_F(PreferenceServiceTest, Test) {
+ // The window should open with the new reference profile, with window
+ // placement values stored in the user data directory.
+ JSONFileValueDeserializer deserializer(original_pref_file_);
+ std::unique_ptr<base::Value> root;
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ root = deserializer.Deserialize(NULL, NULL);
+ }
+
+ ASSERT_TRUE(root.get());
+ ASSERT_TRUE(root->is_dict());
+
+ base::DictionaryValue* root_dict =
+ static_cast<base::DictionaryValue*>(root.get());
+
+ // Retrieve the screen rect for the launched window
+ gfx::Rect bounds = browser()->window()->GetRestoredBounds();
+
+ // Retrieve the expected rect values from "Preferences"
+ int bottom = 0;
+ std::string kBrowserWindowPlacement(prefs::kBrowserWindowPlacement);
+ EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".bottom",
+ &bottom));
+ EXPECT_EQ(bottom, bounds.y() + bounds.height());
+
+ int top = 0;
+ EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".top",
+ &top));
+ EXPECT_EQ(top, bounds.y());
+
+ int left = 0;
+ EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".left",
+ &left));
+ EXPECT_EQ(left, bounds.x());
+
+ int right = 0;
+ EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".right",
+ &right));
+ EXPECT_EQ(right, bounds.x() + bounds.width());
+
+ // Find if launched window is maximized.
+ bool is_window_maximized = browser()->window()->IsMaximized();
+ bool is_maximized = false;
+ EXPECT_TRUE(root_dict->GetBoolean(kBrowserWindowPlacement + ".maximized",
+ &is_maximized));
+ EXPECT_EQ(is_maximized, is_window_maximized);
+}
+#endif
diff --git a/chromium/chrome/browser/prefs/pref_service_incognito_whitelist.cc b/chromium/chrome/browser/prefs/pref_service_incognito_whitelist.cc
new file mode 100644
index 00000000000..0206ea78562
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_service_incognito_whitelist.cc
@@ -0,0 +1,229 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/pref_service_incognito_whitelist.h"
+
+#include <vector>
+
+#include "base/stl_util.h"
+#include "build/build_config.h"
+#include "chrome/common/pref_names.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/content_settings/core/common/pref_names.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/rappor/rappor_pref_names.h"
+#include "components/reading_list/core/reading_list_pref_names.h"
+#include "components/ukm/ukm_pref_names.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/accessibility/animation_policy_prefs.h"
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+#include "ash/public/cpp/ash_pref_names.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace {
+
+// List of keys that can be changed in the user prefs file by the incognito
+// profile.
+const char* const kPersistentPrefNames[] = {
+#if defined(OS_CHROMEOS)
+ // Accessibility preferences should be persisted if they are changed in
+ // incognito mode.
+ ash::prefs::kAccessibilityLargeCursorEnabled,
+ ash::prefs::kAccessibilityLargeCursorDipSize,
+ ash::prefs::kAccessibilityStickyKeysEnabled,
+ ash::prefs::kAccessibilitySpokenFeedbackEnabled,
+ ash::prefs::kAccessibilityHighContrastEnabled,
+ ash::prefs::kAccessibilityScreenMagnifierCenterFocus,
+ ash::prefs::kAccessibilityScreenMagnifierEnabled,
+ ash::prefs::kAccessibilityScreenMagnifierScale,
+ ash::prefs::kAccessibilityVirtualKeyboardEnabled,
+ ash::prefs::kAccessibilityMonoAudioEnabled,
+ ash::prefs::kAccessibilityAutoclickEnabled,
+ ash::prefs::kAccessibilityAutoclickDelayMs,
+ ash::prefs::kAccessibilityAutoclickEventType,
+ ash::prefs::kAccessibilityAutoclickRevertToLeftClick,
+ ash::prefs::kAccessibilityAutoclickStabilizePosition,
+ ash::prefs::kAccessibilityAutoclickMovementThreshold,
+ ash::prefs::kAccessibilityCaretHighlightEnabled,
+ ash::prefs::kAccessibilityCursorHighlightEnabled,
+ ash::prefs::kAccessibilityFocusHighlightEnabled,
+ ash::prefs::kAccessibilitySelectToSpeakEnabled,
+ ash::prefs::kAccessibilitySwitchAccessEnabled,
+ ash::prefs::kAccessibilitySwitchAccessSelectKeyCodes,
+ ash::prefs::kAccessibilitySwitchAccessSelectSetting,
+ ash::prefs::kAccessibilitySwitchAccessNextKeyCodes,
+ ash::prefs::kAccessibilitySwitchAccessNextSetting,
+ ash::prefs::kAccessibilitySwitchAccessPreviousKeyCodes,
+ ash::prefs::kAccessibilitySwitchAccessPreviousSetting,
+ ash::prefs::kAccessibilitySwitchAccessAutoScanEnabled,
+ ash::prefs::kAccessibilitySwitchAccessAutoScanSpeedMs,
+ ash::prefs::kAccessibilitySwitchAccessAutoScanKeyboardSpeedMs,
+ ash::prefs::kAccessibilityDictationEnabled,
+ ash::prefs::kDockedMagnifierEnabled,
+ ash::prefs::kDockedMagnifierScale,
+ ash::prefs::kDockedMagnifierAcceleratorDialogHasBeenAccepted,
+ ash::prefs::kHighContrastAcceleratorDialogHasBeenAccepted,
+ ash::prefs::kScreenMagnifierAcceleratorDialogHasBeenAccepted,
+ ash::prefs::kShouldAlwaysShowAccessibilityMenu,
+#endif // defined(OS_CHROMEOS)
+#if !defined(OS_ANDROID)
+ kAnimationPolicyAllowed,
+ kAnimationPolicyOnce,
+ kAnimationPolicyNone,
+#endif // !defined(OS_ANDROID)
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ prefs::kAnimationPolicy,
+#endif
+
+ // Bookmark preferences are common between incognito and regular mode.
+ bookmarks::prefs::kBookmarkEditorExpandedNodes,
+ bookmarks::prefs::kEditBookmarksEnabled,
+ bookmarks::prefs::kManagedBookmarks,
+ bookmarks::prefs::kManagedBookmarksFolderName,
+ bookmarks::prefs::kShowAppsShortcutInBookmarkBar,
+ bookmarks::prefs::kShowManagedBookmarksInBookmarkBar,
+ bookmarks::prefs::kShowBookmarkBar,
+#if defined(OS_ANDROID)
+ prefs::kPartnerBookmarkMappings,
+#endif // defined(OS_ANDROID)
+
+ // Metrics preferences are out of profile scope and are merged between
+ // incognito and regular modes.
+ metrics::prefs::kInstallDate,
+ metrics::prefs::kMetricsClientID,
+ metrics::prefs::kMetricsDefaultOptIn,
+ metrics::prefs::kMetricsInitialLogs,
+ metrics::prefs::kMetricsLowEntropySource,
+ metrics::prefs::kMetricsMachineId,
+ metrics::prefs::kMetricsOngoingLogs,
+ metrics::prefs::kMetricsResetIds,
+
+ metrics::prefs::kMetricsReportingEnabled,
+ metrics::prefs::kMetricsReportingEnabledTimestamp,
+ metrics::prefs::kMetricsSessionID,
+ metrics::prefs::kMetricsLastSeenPrefix,
+ metrics::prefs::kStabilityBreakpadRegistrationFail,
+ metrics::prefs::kStabilityBreakpadRegistrationSuccess,
+ metrics::prefs::kStabilityBrowserLastLiveTimeStamp,
+ metrics::prefs::kStabilityChildProcessCrashCount,
+ metrics::prefs::kStabilityCrashCount,
+ metrics::prefs::kStabilityCrashCountDueToGmsCoreUpdate,
+ metrics::prefs::kStabilityCrashCountWithoutGmsCoreUpdateObsolete,
+ metrics::prefs::kStabilityDebuggerNotPresent,
+ metrics::prefs::kStabilityDebuggerPresent,
+ metrics::prefs::kStabilityDeferredCount,
+ metrics::prefs::kStabilityDiscardCount,
+ metrics::prefs::kStabilityExecutionPhase,
+ metrics::prefs::kStabilityExitedCleanly,
+ metrics::prefs::kStabilityExtensionRendererCrashCount,
+ metrics::prefs::kStabilityExtensionRendererFailedLaunchCount,
+ metrics::prefs::kStabilityExtensionRendererLaunchCount,
+ metrics::prefs::kStabilityGmsCoreVersion,
+ metrics::prefs::kStabilityGpuCrashCount,
+ metrics::prefs::kStabilityIncompleteSessionEndCount,
+ metrics::prefs::kStabilityLaunchCount,
+ metrics::prefs::kStabilityPageLoadCount,
+ metrics::prefs::kStabilityRendererCrashCount,
+ metrics::prefs::kStabilityRendererFailedLaunchCount,
+ metrics::prefs::kStabilityRendererHangCount,
+ metrics::prefs::kStabilityRendererLaunchCount,
+ metrics::prefs::kStabilitySavedSystemProfile,
+ metrics::prefs::kStabilitySavedSystemProfileHash,
+ metrics::prefs::kStabilitySessionEndCompleted,
+ metrics::prefs::kStabilityStatsBuildTime,
+ metrics::prefs::kStabilityStatsVersion,
+ metrics::prefs::kStabilitySystemCrashCount,
+ metrics::prefs::kStabilityVersionMismatchCount,
+ metrics::prefs::kUninstallLaunchCount,
+ metrics::prefs::kUninstallMetricsPageLoadCount,
+ metrics::prefs::kUninstallMetricsUptimeSec,
+ metrics::prefs::kUkmCellDataUse,
+ metrics::prefs::kUmaCellDataUse,
+ metrics::prefs::kUserCellDataUse,
+
+#if defined(OS_ANDROID)
+ // Clipboard modification state is updated over all profiles.
+ prefs::kClipboardLastModifiedTime,
+#endif
+
+ // Default browser bar's status is aggregated between regular and incognito
+ // modes.
+ prefs::kBrowserSuppressDefaultBrowserPrompt,
+ prefs::kDefaultBrowserLastDeclined,
+ prefs::kDefaultBrowserSettingEnabled,
+ prefs::kResetCheckDefaultBrowser,
+
+ // Devtools preferences are stored cross profiles as they are not storing
+ // user data and just keep debugging environment settings.
+ prefs::kDevToolsAdbKey,
+ prefs::kDevToolsAvailability,
+ prefs::kDevToolsDiscoverUsbDevicesEnabled,
+ prefs::kDevToolsEditedFiles,
+ prefs::kDevToolsFileSystemPaths,
+ prefs::kDevToolsPortForwardingEnabled,
+ prefs::kDevToolsPortForwardingDefaultSet,
+ prefs::kDevToolsPortForwardingConfig,
+ prefs::kDevToolsPreferences,
+ prefs::kDevToolsDiscoverTCPTargetsEnabled,
+ prefs::kDevToolsTCPDiscoveryConfig,
+
+#if defined(OS_WIN)
+ // The total number of times that network profile warning is shown is
+ // aggregated between regular and incognito modes.
+ prefs::kNetworkProfileWarningsLeft,
+#endif
+
+ // Tab stats metrics are aggregated between regular and incognio mode.
+ prefs::kTabStatsTotalTabCountMax,
+ prefs::kTabStatsMaxTabsPerWindow,
+ prefs::kTabStatsWindowCountMax,
+ prefs::kTabStatsDailySample,
+
+#if defined(OS_MACOSX)
+ prefs::kShowFullscreenToolbar,
+#endif
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ // Toggleing custom frames affects all open windows in the profile, hence
+ // should be written to the regular profile when changed in incognito mode.
+ prefs::kUseCustomChromeFrame,
+#endif
+
+ // Rappor preferences are not used in incognito mode, but they are written
+ // in startup if they don't exist. So if the startup would be in incognito,
+ // they need to be persisted.
+ rappor::prefs::kRapporCohortSeed,
+ rappor::prefs::kRapporSecret,
+
+ // Reading list preferences are common between incognito and regular mode.
+ reading_list::prefs::kReadingListHasUnseenEntries,
+
+ // Although UKMs are not collected in incognito, theses preferences may be
+ // changed by UMA/Sync/Unity consent, and need to be the same between
+ // incognito and regular modes.
+ ukm::prefs::kUkmClientId,
+ ukm::prefs::kUkmUnsentLogStore,
+ ukm::prefs::kUkmSessionId,
+
+ // Cookie controls preference is, as in an initial release, surfaced only in
+ // the incognito mode and therefore should be persisted between incognito
+ // sessions.
+ prefs::kCookieControlsMode,
+};
+
+} // namespace
+
+namespace prefs {
+
+std::vector<const char*> GetIncognitoPersistentPrefsWhitelist() {
+ std::vector<const char*> whitelist;
+ whitelist.insert(whitelist.end(), kPersistentPrefNames,
+ kPersistentPrefNames + base::size(kPersistentPrefNames));
+ return whitelist;
+}
+
+} // namespace prefs
diff --git a/chromium/chrome/browser/prefs/pref_service_incognito_whitelist.h b/chromium/chrome/browser/prefs/pref_service_incognito_whitelist.h
new file mode 100644
index 00000000000..78123f6c04a
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_service_incognito_whitelist.h
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_PREF_SERVICE_INCOGNITO_WHITELIST_H_
+#define CHROME_BROWSER_PREFS_PREF_SERVICE_INCOGNITO_WHITELIST_H_
+
+#include <vector>
+
+namespace prefs {
+
+// Populate a list of all preferences that are stored in user profile in
+// incognito mode.
+// Please refer to the comments in .cc file.
+std::vector<const char*> GetIncognitoPersistentPrefsWhitelist();
+
+} // namespace prefs
+
+#endif // CHROME_BROWSER_PREFS_PREF_SERVICE_INCOGNITO_WHITELIST_H_
diff --git a/chromium/chrome/browser/prefs/pref_service_syncable_util.cc b/chromium/chrome/browser/prefs/pref_service_syncable_util.cc
new file mode 100644
index 00000000000..befeafcd1dc
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_service_syncable_util.cc
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/pref_service_syncable_util.h"
+
+#include <utility>
+
+#include "chrome/browser/prefs/pref_service_incognito_whitelist.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+
+
+sync_preferences::PrefServiceSyncable* PrefServiceSyncableFromProfile(
+ Profile* profile) {
+ return static_cast<sync_preferences::PrefServiceSyncable*>(
+ profile->GetPrefs());
+}
+
+std::unique_ptr<sync_preferences::PrefServiceSyncable>
+CreateIncognitoPrefServiceSyncable(
+ sync_preferences::PrefServiceSyncable* pref_service,
+ PrefStore* incognito_extension_pref_store,
+ std::unique_ptr<PrefValueStore::Delegate> delegate) {
+
+ return pref_service->CreateIncognitoPrefService(
+ incognito_extension_pref_store,
+ prefs::GetIncognitoPersistentPrefsWhitelist(), std::move(delegate));
+}
diff --git a/chromium/chrome/browser/prefs/pref_service_syncable_util.h b/chromium/chrome/browser/prefs/pref_service_syncable_util.h
new file mode 100644
index 00000000000..6c000ce461c
--- /dev/null
+++ b/chromium/chrome/browser/prefs/pref_service_syncable_util.h
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_PREF_SERVICE_SYNCABLE_UTIL_H_
+#define CHROME_BROWSER_PREFS_PREF_SERVICE_SYNCABLE_UTIL_H_
+
+#include <memory>
+
+#include "components/prefs/pref_value_store.h"
+
+class PrefStore;
+class Profile;
+
+namespace sync_preferences {
+class PrefServiceSyncable;
+}
+
+// sync_preferences::PrefServiceSyncable is a PrefService with added integration
+// for sync, and knowledge of how to create an incognito PrefService. For code
+// that does not need to know about the sync integration, you should use only
+// the plain PrefService type.
+//
+// For this reason, Profile does not expose an accessor for the
+// sync_preferences::PrefServiceSyncable type. Instead, you can use the
+// utilities below to retrieve the sync_preferences::PrefServiceSyncable from a
+// Profile.
+sync_preferences::PrefServiceSyncable* PrefServiceSyncableFromProfile(
+ Profile* profile);
+
+// Creates an incognito copy of |pref_service| that shares most prefs but uses
+// a fresh non-persistent overlay for the user pref store and an individual
+// extension pref store (to cache the effective extension prefs for incognito
+// windows).
+std::unique_ptr<sync_preferences::PrefServiceSyncable>
+CreateIncognitoPrefServiceSyncable(
+ sync_preferences::PrefServiceSyncable* pref_service,
+ PrefStore* incognito_extension_pref_store,
+ std::unique_ptr<PrefValueStore::Delegate> delegate);
+
+#endif // CHROME_BROWSER_PREFS_PREF_SERVICE_SYNCABLE_UTIL_H_
diff --git a/chromium/chrome/browser/prefs/profile_pref_store_manager.cc b/chromium/chrome/browser/prefs/profile_pref_store_manager.cc
new file mode 100644
index 00000000000..73245053297
--- /dev/null
+++ b/chromium/chrome/browser/prefs/profile_pref_store_manager.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/profile_pref_store_manager.h"
+
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "build/build_config.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_features.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "services/preferences/public/cpp/persistent_pref_store_client.h"
+#include "services/preferences/public/mojom/preferences.mojom.h"
+#include "services/preferences/tracked/pref_hash_filter.h"
+#include "services/preferences/tracked/tracked_persistent_pref_store_factory.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+#if defined(OS_WIN)
+#include "chrome/install_static/install_util.h"
+#endif
+
+namespace {
+
+#if defined(OS_WIN)
+// Forces a different registry key to be used for storing preference validation
+// MACs. See |SetPreferenceValidationRegistryPathForTesting|.
+const base::string16* g_preference_validation_registry_path_for_testing =
+ nullptr;
+#endif // OS_WIN
+
+} // namespace
+
+// Preference tracking and protection is not required on platforms where other
+// apps do not have access to chrome's persistent storage.
+const bool ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking =
+#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+ false;
+#else
+ true;
+#endif
+
+ProfilePrefStoreManager::ProfilePrefStoreManager(
+ const base::FilePath& profile_path,
+ const std::string& seed,
+ const std::string& legacy_device_id)
+ : profile_path_(profile_path),
+ seed_(seed),
+ legacy_device_id_(legacy_device_id) {}
+
+ProfilePrefStoreManager::~ProfilePrefStoreManager() {}
+
+// static
+void ProfilePrefStoreManager::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ PrefHashFilter::RegisterProfilePrefs(registry);
+}
+
+// static
+base::Time ProfilePrefStoreManager::GetResetTime(PrefService* pref_service) {
+ return PrefHashFilter::GetResetTime(pref_service);
+}
+
+// static
+void ProfilePrefStoreManager::ClearResetTime(PrefService* pref_service) {
+ PrefHashFilter::ClearResetTime(pref_service);
+}
+
+#if defined(OS_WIN)
+// static
+void ProfilePrefStoreManager::SetPreferenceValidationRegistryPathForTesting(
+ const base::string16* path) {
+ DCHECK(!path->empty());
+ g_preference_validation_registry_path_for_testing = path;
+}
+#endif // OS_WIN
+
+PersistentPrefStore* ProfilePrefStoreManager::CreateProfilePrefStore(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate) {
+ if (!kPlatformSupportsPreferenceTracking) {
+ return new JsonPrefStore(profile_path_.Append(chrome::kPreferencesFilename),
+ nullptr, io_task_runner);
+ }
+ return CreateTrackedPersistentPrefStore(
+ CreateTrackedPrefStoreConfiguration(
+ std::move(tracking_configuration), reporting_ids_count,
+ std::move(reset_on_load_observer), std::move(validation_delegate)),
+ io_task_runner);
+}
+
+bool ProfilePrefStoreManager::InitializePrefsFromMasterPrefs(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ std::unique_ptr<base::DictionaryValue> master_prefs) {
+ // Create the profile directory if it doesn't exist yet (very possible on
+ // first run).
+ if (!base::CreateDirectory(profile_path_))
+ return false;
+
+ if (kPlatformSupportsPreferenceTracking) {
+ InitializeMasterPrefsTracking(
+ CreateTrackedPrefStoreConfiguration(std::move(tracking_configuration),
+ reporting_ids_count, {},
+ mojo::NullRemote()),
+ master_prefs.get());
+ }
+
+ // This will write out to a single combined file which will be immediately
+ // migrated to two files on load.
+ JSONFileValueSerializer serializer(
+ profile_path_.Append(chrome::kPreferencesFilename));
+
+ // Call Serialize (which does IO) on the main thread, which would _normally_
+ // be verboten. In this case however, we require this IO to synchronously
+ // complete before Chrome can start (as master preferences seed the Local
+ // State and Preferences files). This won't trip ThreadIORestrictions as they
+ // won't have kicked in yet on the main thread.
+ bool success = serializer.Serialize(*master_prefs);
+
+ return success;
+}
+
+prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr
+ProfilePrefStoreManager::CreateTrackedPrefStoreConfiguration(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate) {
+ return prefs::mojom::TrackedPersistentPrefStoreConfiguration::New(
+ profile_path_.Append(chrome::kPreferencesFilename),
+ profile_path_.Append(chrome::kSecurePreferencesFilename),
+ std::move(tracking_configuration), reporting_ids_count, seed_,
+ legacy_device_id_, "ChromeRegistryHashStoreValidationSeed",
+#if defined(OS_WIN)
+ g_preference_validation_registry_path_for_testing
+ ? *g_preference_validation_registry_path_for_testing
+ : install_static::GetRegistryPath(),
+#else
+ base::string16(),
+#endif
+ std::move(validation_delegate), std::move(reset_on_load_observer));
+}
diff --git a/chromium/chrome/browser/prefs/profile_pref_store_manager.h b/chromium/chrome/browser/prefs/profile_pref_store_manager.h
new file mode 100644
index 00000000000..94dadd4953d
--- /dev/null
+++ b/chromium/chrome/browser/prefs/profile_pref_store_manager.h
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_PROFILE_PREF_STORE_MANAGER_H_
+#define CHROME_BROWSER_PREFS_PROFILE_PREF_STORE_MANAGER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "services/preferences/public/mojom/preferences.mojom.h"
+#include "services/preferences/public/mojom/tracked_preference_validation_delegate.mojom.h"
+
+class PersistentPrefStore;
+class PrefService;
+
+namespace base {
+class DictionaryValue;
+} // namespace base
+
+namespace service_manager {
+class Connector;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+} // namespace user_prefs
+
+// Provides a facade through which the user preference store may be accessed and
+// managed.
+class ProfilePrefStoreManager {
+ public:
+ // Instantiates a ProfilePrefStoreManager with the configuration required to
+ // manage the user preferences of the profile at |profile_path|.
+ // |seed| and |legacy_device_id| are used to track preference value changes
+ // and must be the same on each launch in order to verify loaded preference
+ // values.
+ ProfilePrefStoreManager(const base::FilePath& profile_path,
+ const std::string& seed,
+ const std::string& legacy_device_id);
+
+ ~ProfilePrefStoreManager();
+
+ static const bool kPlatformSupportsPreferenceTracking;
+
+ // Register user prefs used by the profile preferences system.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Retrieves the time of the last preference reset event, if any, for
+ // |pref_service|. Assumes that |pref_service| is backed by a PrefStore that
+ // was built by ProfilePrefStoreManager.
+ // If no reset has occurred, returns a null |Time|.
+ static base::Time GetResetTime(PrefService* pref_service);
+
+ // Clears the time of the last preference reset event, if any, for
+ // |pref_service|. Assumes that |pref_service| is backed by a PrefStore that
+ // was built by ProfilePrefStoreManager.
+ static void ClearResetTime(PrefService* pref_service);
+
+#if defined(OS_WIN)
+ // Call before startup tasks kick in to use a different registry path for
+ // storing and validating tracked preference MACs. Callers are responsible
+ // for ensuring that the key is deleted on shutdown. For testing only.
+ static void SetPreferenceValidationRegistryPathForTesting(
+ const base::string16* path);
+#endif
+
+ // Creates a PersistentPrefStore providing access to the user preferences of
+ // the managed profile. If |reset_on_load_observer| is provided, it will be
+ // notified if a reset occurs as a result of loading the profile's prefs. An
+ // optional |validation_delegate| will be notified of the status of each
+ // tracked preference as they are checked.
+ // |tracking_configuration| is used for preference tracking.
+ // |reporting_ids_count| is the count of all possible tracked preference IDs
+ // (possibly greater than |tracking_configuration.size()|).
+ PersistentPrefStore* CreateProfilePrefStore(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate);
+
+ // Initializes the preferences for the managed profile with the preference
+ // values in |master_prefs|. Acts synchronously, including blocking IO.
+ // Returns true on success.
+ bool InitializePrefsFromMasterPrefs(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ std::unique_ptr<base::DictionaryValue> master_prefs);
+
+ private:
+ // Connects to the pref service over mojo and configures it.
+ void ConfigurePrefService(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate,
+ service_manager::Connector* connector);
+
+ prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr
+ CreateTrackedPrefStoreConfiguration(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ tracking_configuration,
+ size_t reporting_ids_count,
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer,
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate);
+
+ const base::FilePath profile_path_;
+ const std::string seed_;
+ const std::string legacy_device_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfilePrefStoreManager);
+};
+
+#endif // CHROME_BROWSER_PREFS_PROFILE_PREF_STORE_MANAGER_H_
diff --git a/chromium/chrome/browser/prefs/profile_pref_store_manager_unittest.cc b/chromium/chrome/browser/prefs/profile_pref_store_manager_unittest.cc
new file mode 100644
index 00000000000..716061b4809
--- /dev/null
+++ b/chromium/chrome/browser/prefs/profile_pref_store_manager_unittest.cc
@@ -0,0 +1,562 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/profile_pref_store_manager.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "chrome/common/chrome_features.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_service_factory.h"
+#include "components/prefs/pref_store.h"
+#include "components/prefs/testing_pref_service.h"
+#include "content/public/common/service_names.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/preferences/public/cpp/pref_service_main.h"
+#include "services/preferences/public/cpp/tracked/configuration.h"
+#include "services/preferences/public/cpp/tracked/mock_validation_delegate.h"
+#include "services/preferences/public/cpp/tracked/pref_names.h"
+#include "services/preferences/public/mojom/preferences.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/cpp/constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using EnforcementLevel =
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel;
+using PrefTrackingStrategy =
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy;
+using ValueType = prefs::mojom::TrackedPreferenceMetadata::ValueType;
+
+class FirstEqualsPredicate {
+ public:
+ explicit FirstEqualsPredicate(const std::string& expected)
+ : expected_(expected) {}
+ bool operator()(const PrefValueMap::Map::value_type& pair) {
+ return pair.first == expected_;
+ }
+
+ private:
+ const std::string expected_;
+};
+
+// Observes changes to the PrefStore and verifies that only registered prefs are
+// written.
+class RegistryVerifier : public PrefStore::Observer {
+ public:
+ explicit RegistryVerifier(PrefRegistry* pref_registry)
+ : pref_registry_(pref_registry) {}
+
+ // PrefStore::Observer implementation
+ void OnPrefValueChanged(const std::string& key) override {
+ EXPECT_TRUE(pref_registry_->end() !=
+ std::find_if(pref_registry_->begin(),
+ pref_registry_->end(),
+ FirstEqualsPredicate(key)))
+ << "Unregistered key " << key << " was changed.";
+ }
+
+ void OnInitializationCompleted(bool succeeded) override {}
+
+ private:
+ scoped_refptr<PrefRegistry> pref_registry_;
+};
+
+class PrefStoreReadObserver : public PrefStore::Observer {
+ public:
+ explicit PrefStoreReadObserver(scoped_refptr<PersistentPrefStore> pref_store)
+ : pref_store_(std::move(pref_store)) {
+ pref_store_->AddObserver(this);
+ }
+
+ ~PrefStoreReadObserver() override { pref_store_->RemoveObserver(this); }
+
+ PersistentPrefStore::PrefReadError Read() {
+ base::RunLoop run_loop;
+ stop_waiting_ = run_loop.QuitClosure();
+ pref_store_->ReadPrefsAsync(nullptr);
+ run_loop.Run();
+ return pref_store_->GetReadError();
+ }
+
+ // PrefStore::Observer implementation
+ void OnPrefValueChanged(const std::string& key) override {}
+
+ void OnInitializationCompleted(bool succeeded) override {
+ if (!stop_waiting_.is_null()) {
+ std::move(stop_waiting_).Run();
+ }
+ }
+
+ private:
+ scoped_refptr<PersistentPrefStore> pref_store_;
+ base::Closure stop_waiting_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefStoreReadObserver);
+};
+
+const char kUnprotectedPref[] = "unprotected_pref";
+const char kTrackedAtomic[] = "tracked_atomic";
+const char kProtectedAtomic[] = "protected_atomic";
+
+const char kFoobar[] = "FOOBAR";
+const char kBarfoo[] = "BARFOO";
+const char kHelloWorld[] = "HELLOWORLD";
+const char kGoodbyeWorld[] = "GOODBYEWORLD";
+
+const prefs::TrackedPreferenceMetadata kConfiguration[] = {
+ {0u, kTrackedAtomic, EnforcementLevel::NO_ENFORCEMENT,
+ PrefTrackingStrategy::ATOMIC},
+ {1u, kProtectedAtomic, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC}};
+
+const size_t kExtraReportingId = 2u;
+const size_t kReportingIdCount = 3u;
+
+} // namespace
+
+class ProfilePrefStoreManagerTest : public testing::Test,
+ public prefs::mojom::ResetOnLoadObserver {
+ public:
+ ProfilePrefStoreManagerTest()
+ : configuration_(prefs::ConstructTrackedConfiguration(kConfiguration)),
+ profile_pref_registry_(new user_prefs::PrefRegistrySyncable),
+ registry_verifier_(profile_pref_registry_.get()),
+ seed_("seed"),
+ reset_recorded_(false) {}
+
+ void SetUp() override {
+ mock_validation_delegate_record_ = new MockValidationDelegateRecord;
+ mock_validation_delegate_ = std::make_unique<MockValidationDelegate>(
+ mock_validation_delegate_record_);
+
+ ProfilePrefStoreManager::RegisterProfilePrefs(profile_pref_registry_.get());
+ for (const prefs::TrackedPreferenceMetadata* it = kConfiguration;
+ it != kConfiguration + base::size(kConfiguration); ++it) {
+ if (it->strategy == PrefTrackingStrategy::ATOMIC) {
+ profile_pref_registry_->RegisterStringPref(it->name, std::string());
+ } else {
+ profile_pref_registry_->RegisterDictionaryPref(it->name);
+ }
+ }
+ profile_pref_registry_->RegisterStringPref(kUnprotectedPref, std::string());
+
+ // As in chrome_pref_service_factory.cc, kPreferencesResetTime needs to be
+ // declared as protected in order to be read from the proper store by the
+ // SegregatedPrefStore. Only declare it after configured prefs have been
+ // registered above for this test as kPreferenceResetTime is already
+ // registered in ProfilePrefStoreManager::RegisterProfilePrefs.
+ prefs::TrackedPreferenceMetadata pref_reset_time_config = {
+ (*configuration_.rbegin())->reporting_id + 1,
+ user_prefs::kPreferenceResetTime, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC};
+ configuration_.push_back(
+ prefs::ConstructTrackedMetadata(pref_reset_time_config));
+
+ ASSERT_TRUE(profile_dir_.CreateUniqueTempDir());
+ ReloadConfiguration();
+ }
+
+ void ReloadConfiguration() {
+ manager_.reset(new ProfilePrefStoreManager(profile_dir_.GetPath(), seed_,
+ "device_id"));
+ }
+
+ void TearDown() override {
+ DestroyPrefStore();
+ }
+
+ protected:
+ // Verifies whether a reset was reported via the OnResetOnLoad() hook. Also
+ // verifies that GetResetTime() was set (or not) accordingly.
+ void VerifyResetRecorded(bool reset_expected) {
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(reset_expected, reset_recorded_);
+
+ PrefServiceFactory pref_service_factory;
+ pref_service_factory.set_user_prefs(pref_store_);
+
+ std::unique_ptr<PrefService> pref_service(
+ pref_service_factory.Create(profile_pref_registry_.get()));
+
+ EXPECT_EQ(
+ reset_expected,
+ !ProfilePrefStoreManager::GetResetTime(pref_service.get()).is_null());
+ }
+
+ void ClearResetRecorded() {
+ reset_recorded_ = false;
+
+ PrefServiceFactory pref_service_factory;
+ pref_service_factory.set_user_prefs(pref_store_);
+
+ std::unique_ptr<PrefService> pref_service(
+ pref_service_factory.Create(profile_pref_registry_.get()));
+
+ ProfilePrefStoreManager::ClearResetTime(pref_service.get());
+ }
+
+ void InitializePrefs() {
+ // According to the implementation of ProfilePrefStoreManager, this is
+ // actually a SegregatedPrefStore backed by two underlying pref stores.
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver> observer;
+ reset_on_load_observer_receivers_.Add(
+ this, observer.InitWithNewPipeAndPassReceiver());
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate;
+ mock_validation_delegate_receivers_.Add(
+ mock_validation_delegate_.get(),
+ validation_delegate.InitWithNewPipeAndPassReceiver());
+ scoped_refptr<PersistentPrefStore> pref_store =
+ manager_->CreateProfilePrefStore(
+ prefs::CloneTrackedConfiguration(configuration_), kReportingIdCount,
+ base::ThreadTaskRunnerHandle::Get(), std::move(observer),
+ std::move(validation_delegate));
+ InitializePrefStore(pref_store.get());
+ pref_store = nullptr;
+ }
+
+ void DestroyPrefStore() {
+ if (pref_store_.get()) {
+ ClearResetRecorded();
+ // Force everything to be written to disk, triggering the PrefHashFilter
+ // while our RegistryVerifier is watching.
+ base::RunLoop run_loop;
+ pref_store_->CommitPendingWrite(run_loop.QuitClosure());
+ run_loop.Run();
+
+ pref_store_->RemoveObserver(&registry_verifier_);
+ pref_store_ = NULL;
+ // Nothing should have to happen on the background threads, but just in
+ // case...
+ base::RunLoop().RunUntilIdle();
+ }
+ }
+
+ void InitializePrefStore(PersistentPrefStore* pref_store) {
+ pref_store->AddObserver(&registry_verifier_);
+ PrefStoreReadObserver read_observer(pref_store);
+ PersistentPrefStore::PrefReadError error = read_observer.Read();
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, error);
+ pref_store->SetValue(kTrackedAtomic, std::make_unique<base::Value>(kFoobar),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ pref_store->SetValue(kProtectedAtomic,
+ std::make_unique<base::Value>(kHelloWorld),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ pref_store->SetValue(kUnprotectedPref,
+ std::make_unique<base::Value>(kFoobar),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ pref_store->RemoveObserver(&registry_verifier_);
+ base::RunLoop run_loop;
+ pref_store->CommitPendingWrite(run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ void LoadExistingPrefs() {
+ DestroyPrefStore();
+ mojo::PendingRemote<prefs::mojom::ResetOnLoadObserver> observer;
+ reset_on_load_observer_receivers_.Add(
+ this, observer.InitWithNewPipeAndPassReceiver());
+ mojo::PendingRemote<prefs::mojom::TrackedPreferenceValidationDelegate>
+ validation_delegate;
+ mock_validation_delegate_receivers_.Add(
+ mock_validation_delegate_.get(),
+ validation_delegate.InitWithNewPipeAndPassReceiver());
+ pref_store_ = manager_->CreateProfilePrefStore(
+ prefs::CloneTrackedConfiguration(configuration_), kReportingIdCount,
+ base::ThreadTaskRunnerHandle::Get(), std::move(observer),
+ std::move(validation_delegate));
+ pref_store_->AddObserver(&registry_verifier_);
+ PrefStoreReadObserver read_observer(pref_store_);
+ read_observer.Read();
+ }
+
+ void ReplaceStringInPrefs(const std::string& find,
+ const std::string& replace) {
+ base::FileEnumerator file_enum(profile_dir_.GetPath(), true,
+ base::FileEnumerator::FILES);
+
+ for (base::FilePath path = file_enum.Next(); !path.empty();
+ path = file_enum.Next()) {
+ // Tamper with the file's contents
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(path, &contents));
+ base::ReplaceSubstringsAfterOffset(&contents, 0u, find, replace);
+ EXPECT_EQ(static_cast<int>(contents.length()),
+ base::WriteFile(path, contents.c_str(), contents.length()));
+ }
+ }
+
+ void ExpectStringValueEquals(const std::string& name,
+ const std::string& expected) {
+ const base::Value* value = NULL;
+ std::string as_string;
+ if (!pref_store_->GetValue(name, &value)) {
+ ADD_FAILURE() << name << " is not a defined value.";
+ } else if (!value->GetAsString(&as_string)) {
+ ADD_FAILURE() << name << " could not be coerced to a string.";
+ } else {
+ EXPECT_EQ(expected, as_string);
+ }
+ }
+
+ void ExpectValidationObserved(const std::string& pref_path) {
+ // No validations are expected for platforms that do not support tracking.
+ if (!ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking)
+ return;
+ if (!mock_validation_delegate_record_->GetEventForPath(pref_path))
+ ADD_FAILURE() << "No validation observed for preference: " << pref_path;
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> configuration_;
+ base::ScopedTempDir profile_dir_;
+ scoped_refptr<user_prefs::PrefRegistrySyncable> profile_pref_registry_;
+ RegistryVerifier registry_verifier_;
+ scoped_refptr<MockValidationDelegateRecord> mock_validation_delegate_record_;
+ std::unique_ptr<MockValidationDelegate> mock_validation_delegate_;
+ mojo::ReceiverSet<prefs::mojom::TrackedPreferenceValidationDelegate>
+ mock_validation_delegate_receivers_;
+ std::unique_ptr<ProfilePrefStoreManager> manager_;
+ scoped_refptr<PersistentPrefStore> pref_store_;
+
+ std::string seed_;
+
+ private:
+ void OnResetOnLoad() override {
+ // As-is |reset_recorded_| is only designed to remember a single reset, make
+ // sure none was previously recorded (or that ClearResetRecorded() was
+ // called).
+ EXPECT_FALSE(reset_recorded_);
+ reset_recorded_ = true;
+ }
+
+ base::test::ScopedFeatureList feature_list_;
+ bool reset_recorded_;
+ service_manager::mojom::ConnectorRequest connector_request_;
+ mojo::ReceiverSet<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer_receivers_;
+};
+
+TEST_F(ProfilePrefStoreManagerTest, StoreValues) {
+ InitializePrefs();
+
+ LoadExistingPrefs();
+
+ ExpectStringValueEquals(kTrackedAtomic, kFoobar);
+ ExpectStringValueEquals(kProtectedAtomic, kHelloWorld);
+ VerifyResetRecorded(false);
+ ExpectValidationObserved(kTrackedAtomic);
+ ExpectValidationObserved(kProtectedAtomic);
+}
+
+TEST_F(ProfilePrefStoreManagerTest, ProtectValues) {
+ InitializePrefs();
+
+ ReplaceStringInPrefs(kFoobar, kBarfoo);
+ ReplaceStringInPrefs(kHelloWorld, kGoodbyeWorld);
+
+ LoadExistingPrefs();
+
+ // kTrackedAtomic is unprotected and thus will be loaded as it appears on
+ // disk.
+ ExpectStringValueEquals(kTrackedAtomic, kBarfoo);
+
+ // If preference tracking is supported, the tampered value of kProtectedAtomic
+ // will be discarded at load time, leaving this preference undefined.
+ EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking,
+ pref_store_->GetValue(kProtectedAtomic, NULL));
+ VerifyResetRecorded(
+ ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking);
+
+ ExpectValidationObserved(kTrackedAtomic);
+ ExpectValidationObserved(kProtectedAtomic);
+}
+
+TEST_F(ProfilePrefStoreManagerTest, InitializePrefsFromMasterPrefs) {
+ auto master_prefs = std::make_unique<base::DictionaryValue>();
+ master_prefs->Set(kTrackedAtomic, std::make_unique<base::Value>(kFoobar));
+ master_prefs->Set(kProtectedAtomic,
+ std::make_unique<base::Value>(kHelloWorld));
+ EXPECT_TRUE(manager_->InitializePrefsFromMasterPrefs(
+ prefs::CloneTrackedConfiguration(configuration_), kReportingIdCount,
+ std::move(master_prefs)));
+
+ LoadExistingPrefs();
+
+ // Verify that InitializePrefsFromMasterPrefs correctly applied the MACs
+ // necessary to authenticate these values.
+ ExpectStringValueEquals(kTrackedAtomic, kFoobar);
+ ExpectStringValueEquals(kProtectedAtomic, kHelloWorld);
+ VerifyResetRecorded(false);
+}
+
+TEST_F(ProfilePrefStoreManagerTest, UnprotectedToProtected) {
+ InitializePrefs();
+
+ ExpectValidationObserved(kTrackedAtomic);
+ ExpectValidationObserved(kProtectedAtomic);
+
+ LoadExistingPrefs();
+ ExpectStringValueEquals(kUnprotectedPref, kFoobar);
+
+ // Ensure everything is written out to disk.
+ DestroyPrefStore();
+
+ ReplaceStringInPrefs(kFoobar, kBarfoo);
+
+ // It's unprotected, so we can load the modified value.
+ LoadExistingPrefs();
+ ExpectStringValueEquals(kUnprotectedPref, kBarfoo);
+
+ // Now update the configuration to protect it.
+ prefs::TrackedPreferenceMetadata new_protected = {
+ kExtraReportingId, kUnprotectedPref, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC};
+ configuration_.push_back(prefs::ConstructTrackedMetadata(new_protected));
+ ReloadConfiguration();
+
+ // And try loading with the new configuration.
+ LoadExistingPrefs();
+
+ // Since there was a valid super MAC we were able to extend the existing trust
+ // to the newly protected preference.
+ ExpectStringValueEquals(kUnprotectedPref, kBarfoo);
+ VerifyResetRecorded(false);
+
+ // Ensure everything is written out to disk.
+ DestroyPrefStore();
+
+ // It's protected now, so (if the platform supports it) any tampering should
+ // lead to a reset.
+ ReplaceStringInPrefs(kBarfoo, kFoobar);
+ LoadExistingPrefs();
+ EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking,
+ pref_store_->GetValue(kUnprotectedPref, NULL));
+ VerifyResetRecorded(
+ ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking);
+}
+
+TEST_F(ProfilePrefStoreManagerTest, NewPrefWhenFirstProtecting) {
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ original_configuration = prefs::CloneTrackedConfiguration(configuration_);
+ for (const auto& metadata : configuration_) {
+ metadata->enforcement_level = EnforcementLevel::NO_ENFORCEMENT;
+ }
+ ReloadConfiguration();
+
+ InitializePrefs();
+
+ ExpectValidationObserved(kTrackedAtomic);
+ ExpectValidationObserved(kProtectedAtomic);
+
+ LoadExistingPrefs();
+ ExpectStringValueEquals(kUnprotectedPref, kFoobar);
+
+ // Ensure everything is written out to disk.
+ DestroyPrefStore();
+
+ // Now introduce protection, including the never-before tracked "new_pref".
+ configuration_ = std::move(original_configuration);
+ prefs::TrackedPreferenceMetadata new_protected = {
+ kExtraReportingId, kUnprotectedPref, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC};
+ configuration_.push_back(prefs::ConstructTrackedMetadata(new_protected));
+ ReloadConfiguration();
+
+ // And try loading with the new configuration.
+ LoadExistingPrefs();
+
+ // Since there was a valid super MAC we were able to extend the existing trust
+ // to the newly tracked & protected preference.
+ ExpectStringValueEquals(kUnprotectedPref, kFoobar);
+ VerifyResetRecorded(false);
+}
+
+TEST_F(ProfilePrefStoreManagerTest, UnprotectedToProtectedWithoutTrust) {
+ InitializePrefs();
+
+ ExpectValidationObserved(kTrackedAtomic);
+ ExpectValidationObserved(kProtectedAtomic);
+
+ // Now update the configuration to protect it.
+ prefs::TrackedPreferenceMetadata new_protected = {
+ kExtraReportingId, kUnprotectedPref, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC};
+ configuration_.push_back(prefs::ConstructTrackedMetadata(new_protected));
+ seed_ = "new-seed-to-break-trust";
+ ReloadConfiguration();
+
+ // And try loading with the new configuration.
+ LoadExistingPrefs();
+
+ // If preference tracking is supported, kUnprotectedPref will have been
+ // discarded because new values are not accepted without a valid super MAC.
+ EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking,
+ pref_store_->GetValue(kUnprotectedPref, NULL));
+ VerifyResetRecorded(
+ ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking);
+}
+
+// This test verifies that preference values are correctly maintained when a
+// preference's protection state changes from protected to unprotected.
+TEST_F(ProfilePrefStoreManagerTest, ProtectedToUnprotected) {
+ InitializePrefs();
+
+ ExpectValidationObserved(kTrackedAtomic);
+ ExpectValidationObserved(kProtectedAtomic);
+
+ DestroyPrefStore();
+
+ // Unconfigure protection for kProtectedAtomic
+ for (const auto& metadata : configuration_) {
+ if (metadata->name == kProtectedAtomic) {
+ metadata->enforcement_level = EnforcementLevel::NO_ENFORCEMENT;
+ break;
+ }
+ }
+
+ seed_ = "new-seed-to-break-trust";
+ ReloadConfiguration();
+ LoadExistingPrefs();
+
+ // Verify that the value was not reset.
+ ExpectStringValueEquals(kProtectedAtomic, kHelloWorld);
+ VerifyResetRecorded(false);
+
+ // Accessing the value of the previously protected pref didn't trigger its
+ // move to the unprotected preferences file, though the loading of the pref
+ // store should still have caused the MAC store to be recalculated.
+ LoadExistingPrefs();
+ ExpectStringValueEquals(kProtectedAtomic, kHelloWorld);
+
+ // Trigger the logic that migrates it back to the unprotected preferences
+ // file.
+ pref_store_->SetValue(kProtectedAtomic,
+ std::make_unique<base::Value>(kGoodbyeWorld),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ LoadExistingPrefs();
+ ExpectStringValueEquals(kProtectedAtomic, kGoodbyeWorld);
+ VerifyResetRecorded(false);
+}
diff --git a/chromium/chrome/browser/prefs/proxy_policy_unittest.cc b/chromium/chrome/browser/prefs/proxy_policy_unittest.cc
new file mode 100644
index 00000000000..efa4b31cf82
--- /dev/null
+++ b/chromium/chrome/browser/prefs/proxy_policy_unittest.cc
@@ -0,0 +1,254 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service_impl.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/proxy_config/proxy_config_dictionary.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "components/sync_preferences/pref_service_mock_factory.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/tpm/stub_install_attributes.h"
+#endif
+
+using ::testing::Return;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+void assertProxyMode(const ProxyConfigDictionary& dict,
+ ProxyPrefs::ProxyMode expected_mode) {
+ ProxyPrefs::ProxyMode actual_mode;
+ ASSERT_TRUE(dict.GetMode(&actual_mode));
+ EXPECT_EQ(expected_mode, actual_mode);
+}
+
+void assertProxyServer(const ProxyConfigDictionary& dict,
+ const std::string& expected) {
+ std::string actual;
+ if (!expected.empty()) {
+ ASSERT_TRUE(dict.GetProxyServer(&actual));
+ EXPECT_EQ(expected, actual);
+ } else {
+ EXPECT_FALSE(dict.GetProxyServer(&actual));
+ }
+}
+
+void assertPacUrl(const ProxyConfigDictionary& dict,
+ const std::string& expected) {
+ std::string actual;
+ if (!expected.empty()) {
+ ASSERT_TRUE(dict.GetPacUrl(&actual));
+ EXPECT_EQ(expected, actual);
+ } else {
+ EXPECT_FALSE(dict.GetPacUrl(&actual));
+ }
+}
+
+void assertBypassList(const ProxyConfigDictionary& dict,
+ const std::string& expected) {
+ std::string actual;
+ if (!expected.empty()) {
+ ASSERT_TRUE(dict.GetBypassList(&actual));
+ EXPECT_EQ(expected, actual);
+ } else {
+ EXPECT_FALSE(dict.GetBypassList(&actual));
+ }
+}
+
+void assertProxyModeWithoutParams(const ProxyConfigDictionary& dict,
+ ProxyPrefs::ProxyMode proxy_mode) {
+ assertProxyMode(dict, proxy_mode);
+ assertProxyServer(dict, std::string());
+ assertPacUrl(dict, std::string());
+ assertBypassList(dict, std::string());
+}
+
+} // namespace
+
+class ProxyPolicyTest : public testing::Test {
+ protected:
+ ProxyPolicyTest() : command_line_(base::CommandLine::NO_PROGRAM) {}
+
+ void SetUp() override {
+ EXPECT_CALL(provider_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider_);
+ policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers));
+ provider_.Init();
+ }
+
+ void TearDown() override { provider_.Shutdown(); }
+
+ std::unique_ptr<PrefService> CreatePrefService(bool with_managed_policies) {
+ sync_preferences::PrefServiceMockFactory factory;
+ factory.set_command_line_prefs(
+ new ChromeCommandLinePrefStore(&command_line_));
+ if (with_managed_policies) {
+ factory.SetManagedPolicies(policy_service_.get(),
+ g_browser_process->browser_policy_connector());
+ }
+
+ scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
+ new user_prefs::PrefRegistrySyncable);
+ std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs =
+ factory.CreateSyncable(registry.get());
+ RegisterUserProfilePrefs(registry.get());
+ return std::move(prefs);
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+ base::CommandLine command_line_;
+ MockConfigurationPolicyProvider provider_;
+ std::unique_ptr<PolicyServiceImpl> policy_service_;
+
+#if defined(OS_CHROMEOS)
+ chromeos::ScopedStubInstallAttributes test_install_attributes_;
+#endif
+};
+
+TEST_F(ProxyPolicyTest, OverridesCommandLineOptions) {
+ command_line_.AppendSwitchASCII(switches::kProxyBypassList, "123");
+ command_line_.AppendSwitchASCII(switches::kProxyServer, "789");
+ std::unique_ptr<base::Value> mode_name(
+ new base::Value(ProxyPrefs::kFixedServersProxyModeName));
+ PolicyMap policy;
+ policy.Set(key::kProxyMode, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(mode_name), nullptr);
+ policy.Set(key::kProxyBypassList, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("abc"),
+ nullptr);
+ policy.Set(key::kProxyServer, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("ghi"),
+ nullptr);
+ provider_.UpdateChromePolicy(policy);
+
+ // First verify that command-line options are set correctly when
+ // there is no policy in effect.
+ std::unique_ptr<PrefService> prefs(CreatePrefService(false));
+ ProxyConfigDictionary dict(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyMode(dict, ProxyPrefs::MODE_FIXED_SERVERS);
+ assertProxyServer(dict, "789");
+ assertPacUrl(dict, std::string());
+ assertBypassList(dict, "123");
+
+ // Try a second time time with the managed PrefStore in place, the
+ // manual proxy policy should have removed all traces of the command
+ // line and replaced them with the policy versions.
+ prefs = CreatePrefService(true);
+ ProxyConfigDictionary dict2(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyMode(dict2, ProxyPrefs::MODE_FIXED_SERVERS);
+ assertProxyServer(dict2, "ghi");
+ assertPacUrl(dict2, std::string());
+ assertBypassList(dict2, "abc");
+}
+
+TEST_F(ProxyPolicyTest, OverridesUnrelatedCommandLineOptions) {
+ command_line_.AppendSwitchASCII(switches::kProxyBypassList, "123");
+ command_line_.AppendSwitchASCII(switches::kProxyServer, "789");
+ std::unique_ptr<base::Value> mode_name(
+ new base::Value(ProxyPrefs::kAutoDetectProxyModeName));
+ PolicyMap policy;
+ policy.Set(key::kProxyMode, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(mode_name), nullptr);
+ provider_.UpdateChromePolicy(policy);
+
+ // First verify that command-line options are set correctly when
+ // there is no policy in effect.
+ std::unique_ptr<PrefService> prefs = CreatePrefService(false);
+ ProxyConfigDictionary dict(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyMode(dict, ProxyPrefs::MODE_FIXED_SERVERS);
+ assertProxyServer(dict, "789");
+ assertPacUrl(dict, std::string());
+ assertBypassList(dict, "123");
+
+ // Try a second time time with the managed PrefStore in place, the
+ // no proxy policy should have removed all traces of the command
+ // line proxy settings, even though they were not the specific one
+ // set in policy.
+ prefs = CreatePrefService(true);
+ ProxyConfigDictionary dict2(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyModeWithoutParams(dict2, ProxyPrefs::MODE_AUTO_DETECT);
+}
+
+TEST_F(ProxyPolicyTest, OverridesCommandLineNoProxy) {
+ command_line_.AppendSwitch(switches::kNoProxyServer);
+ std::unique_ptr<base::Value> mode_name(
+ new base::Value(ProxyPrefs::kAutoDetectProxyModeName));
+ PolicyMap policy;
+ policy.Set(key::kProxyMode, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(mode_name), nullptr);
+ provider_.UpdateChromePolicy(policy);
+
+ // First verify that command-line options are set correctly when
+ // there is no policy in effect.
+ std::unique_ptr<PrefService> prefs = CreatePrefService(false);
+ ProxyConfigDictionary dict(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyModeWithoutParams(dict, ProxyPrefs::MODE_DIRECT);
+
+ // Try a second time time with the managed PrefStore in place, the
+ // auto-detect should be overridden. The default pref store must be
+ // in place with the appropriate default value for this to work.
+ prefs = CreatePrefService(true);
+ ProxyConfigDictionary dict2(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyModeWithoutParams(dict2, ProxyPrefs::MODE_AUTO_DETECT);
+}
+
+TEST_F(ProxyPolicyTest, OverridesCommandLineAutoDetect) {
+ command_line_.AppendSwitch(switches::kProxyAutoDetect);
+ std::unique_ptr<base::Value> mode_name(
+ new base::Value(ProxyPrefs::kDirectProxyModeName));
+ PolicyMap policy;
+ policy.Set(key::kProxyMode, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(mode_name), nullptr);
+ provider_.UpdateChromePolicy(policy);
+
+ // First verify that the auto-detect is set if there is no managed
+ // PrefStore.
+ std::unique_ptr<PrefService> prefs = CreatePrefService(false);
+ ProxyConfigDictionary dict(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyModeWithoutParams(dict, ProxyPrefs::MODE_AUTO_DETECT);
+
+ // Try a second time time with the managed PrefStore in place, the
+ // auto-detect should be overridden. The default pref store must be
+ // in place with the appropriate default value for this to work.
+ prefs = CreatePrefService(true);
+ ProxyConfigDictionary dict2(
+ prefs->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+ assertProxyModeWithoutParams(dict2, ProxyPrefs::MODE_DIRECT);
+}
+
+} // namespace policy
diff --git a/chromium/chrome/browser/prefs/session_startup_pref.cc b/chromium/chrome/browser/prefs/session_startup_pref.cc
new file mode 100644
index 00000000000..216ea4a2a2e
--- /dev/null
+++ b/chromium/chrome/browser/prefs/session_startup_pref.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/session_startup_pref.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/url_formatter/url_fixer.h"
+
+namespace {
+
+// Converts a SessionStartupPref::Type to an integer written to prefs.
+int TypeToPrefValue(SessionStartupPref::Type type) {
+ switch (type) {
+ case SessionStartupPref::LAST: return SessionStartupPref::kPrefValueLast;
+ case SessionStartupPref::URLS: return SessionStartupPref::kPrefValueURLs;
+ default: return SessionStartupPref::kPrefValueNewTab;
+ }
+}
+
+void URLListToPref(const base::ListValue* url_list, SessionStartupPref* pref) {
+ pref->urls.clear();
+ for (size_t i = 0; i < url_list->GetSize(); ++i) {
+ std::string url_text;
+ if (url_list->GetString(i, &url_text)) {
+ GURL fixed_url = url_formatter::FixupURL(url_text, std::string());
+ pref->urls.push_back(fixed_url);
+ }
+ }
+}
+
+} // namespace
+
+// static
+void SessionStartupPref::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+#if defined(OS_ANDROID)
+ uint32_t flags = PrefRegistry::NO_REGISTRATION_FLAGS;
+#else
+ uint32_t flags = user_prefs::PrefRegistrySyncable::SYNCABLE_PREF;
+#endif
+ registry->RegisterIntegerPref(prefs::kRestoreOnStartup,
+ TypeToPrefValue(GetDefaultStartupType()),
+ flags);
+ registry->RegisterListPref(prefs::kURLsToRestoreOnStartup, flags);
+}
+
+// static
+SessionStartupPref::Type SessionStartupPref::GetDefaultStartupType() {
+#if defined(OS_CHROMEOS)
+ return SessionStartupPref::LAST;
+#else
+ return SessionStartupPref::DEFAULT;
+#endif
+}
+
+// static
+void SessionStartupPref::SetStartupPref(
+ Profile* profile,
+ const SessionStartupPref& pref) {
+ DCHECK(profile);
+ SetStartupPref(profile->GetPrefs(), pref);
+}
+
+// static
+void SessionStartupPref::SetStartupPref(PrefService* prefs,
+ const SessionStartupPref& pref) {
+ DCHECK(prefs);
+
+ if (!SessionStartupPref::TypeIsManaged(prefs))
+ prefs->SetInteger(prefs::kRestoreOnStartup, TypeToPrefValue(pref.type));
+
+ if (!SessionStartupPref::URLsAreManaged(prefs)) {
+ // Always save the URLs, that way the UI can remain consistent even if the
+ // user changes the startup type pref.
+ // Ownership of the ListValue retains with the pref service.
+ ListPrefUpdate update(prefs, prefs::kURLsToRestoreOnStartup);
+ base::ListValue* url_pref_list = update.Get();
+ DCHECK(url_pref_list);
+ url_pref_list->Clear();
+ for (size_t i = 0; i < pref.urls.size(); ++i) {
+ url_pref_list->Set(static_cast<int>(i),
+ std::make_unique<base::Value>(pref.urls[i].spec()));
+ }
+ }
+}
+
+// static
+SessionStartupPref SessionStartupPref::GetStartupPref(Profile* profile) {
+ DCHECK(profile);
+
+ // Guest sessions should not store any state, therefore they should never
+ // trigger a restore during startup.
+ return profile->IsGuestSession()
+ ? SessionStartupPref(SessionStartupPref::DEFAULT)
+ : GetStartupPref(profile->GetPrefs());
+}
+
+// static
+SessionStartupPref SessionStartupPref::GetStartupPref(PrefService* prefs) {
+ DCHECK(prefs);
+
+ SessionStartupPref pref(
+ PrefValueToType(prefs->GetInteger(prefs::kRestoreOnStartup)));
+
+ // Always load the urls, even if the pref type isn't URLS. This way the
+ // preferences panels can show the user their last choice.
+ const base::ListValue* url_list =
+ prefs->GetList(prefs::kURLsToRestoreOnStartup);
+ URLListToPref(url_list, &pref);
+
+ return pref;
+}
+
+// static
+bool SessionStartupPref::TypeIsManaged(PrefService* prefs) {
+ DCHECK(prefs);
+ const PrefService::Preference* pref_restore =
+ prefs->FindPreference(prefs::kRestoreOnStartup);
+ DCHECK(pref_restore);
+ return pref_restore->IsManaged();
+}
+
+// static
+bool SessionStartupPref::URLsAreManaged(PrefService* prefs) {
+ DCHECK(prefs);
+ const PrefService::Preference* pref_urls =
+ prefs->FindPreference(prefs::kURLsToRestoreOnStartup);
+ DCHECK(pref_urls);
+ return pref_urls->IsManaged();
+}
+
+// static
+bool SessionStartupPref::TypeHasRecommendedValue(PrefService* prefs) {
+ DCHECK(prefs);
+ const PrefService::Preference* pref_restore =
+ prefs->FindPreference(prefs::kRestoreOnStartup);
+ DCHECK(pref_restore);
+ return pref_restore->GetRecommendedValue() != nullptr;
+}
+
+// static
+bool SessionStartupPref::TypeIsDefault(PrefService* prefs) {
+ DCHECK(prefs);
+ const PrefService::Preference* pref_restore =
+ prefs->FindPreference(prefs::kRestoreOnStartup);
+ DCHECK(pref_restore);
+ return pref_restore->IsDefaultValue();
+}
+
+// static
+SessionStartupPref::Type SessionStartupPref::PrefValueToType(int pref_value) {
+ switch (pref_value) {
+ case kPrefValueLast: return SessionStartupPref::LAST;
+ case kPrefValueURLs: return SessionStartupPref::URLS;
+ default: return SessionStartupPref::DEFAULT;
+ }
+}
+
+SessionStartupPref::SessionStartupPref(Type type) : type(type) {}
+
+SessionStartupPref::SessionStartupPref(const SessionStartupPref& other) =
+ default;
+
+SessionStartupPref::~SessionStartupPref() {}
diff --git a/chromium/chrome/browser/prefs/session_startup_pref.h b/chromium/chrome/browser/prefs/session_startup_pref.h
new file mode 100644
index 00000000000..ebcb6e9850a
--- /dev/null
+++ b/chromium/chrome/browser/prefs/session_startup_pref.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFS_SESSION_STARTUP_PREF_H__
+#define CHROME_BROWSER_PREFS_SESSION_STARTUP_PREF_H__
+
+#include <vector>
+
+#include "url/gurl.h"
+
+class PrefService;
+class Profile;
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// StartupPref specifies what should happen at startup for a specified profile.
+// StartupPref is stored in the preferences for a particular profile.
+struct SessionStartupPref {
+ // Integer values should not be changed because reset reports depend on these.
+ enum Type {
+ // Indicates the user wants to open the New Tab page.
+ DEFAULT = 0,
+
+ // Indicates the user wants to restore the last session.
+ LAST = 2,
+
+ // Indicates the user wants to restore a specific set of URLs. The URLs
+ // are contained in urls.
+ URLS = 3,
+ };
+
+ // For historical reasons the enum and value registered in the prefs don't
+ // line up. These are the values registered in prefs.
+ // The values are also recorded in Settings.StartupPageLoadSettings histogram,
+ // so make sure to update histograms.xml if you change these.
+ enum PrefValue {
+ kPrefValueLast = 1,
+ kPrefValueURLs = 4,
+ kPrefValueNewTab = 5,
+ kPrefValueMax = 6,
+ };
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Returns the default value for |type|.
+ static Type GetDefaultStartupType();
+
+ // What should happen on startup for the specified profile.
+ static void SetStartupPref(Profile* profile, const SessionStartupPref& pref);
+ static void SetStartupPref(PrefService* prefs,
+ const SessionStartupPref& pref);
+ static SessionStartupPref GetStartupPref(Profile* profile);
+ static SessionStartupPref GetStartupPref(PrefService* prefs);
+
+ // Whether the startup type and URLs are managed via mandatory policy.
+ static bool TypeIsManaged(PrefService* prefs);
+ static bool URLsAreManaged(PrefService* prefs);
+
+ // Whether the startup type has a recommended value (regardless of whether or
+ // not that value is in use).
+ static bool TypeHasRecommendedValue(PrefService* prefs);
+
+ // Whether the startup type has not been overridden from its default.
+ static bool TypeIsDefault(PrefService* prefs);
+
+ // Converts an integer pref value to a SessionStartupPref::Type.
+ static SessionStartupPref::Type PrefValueToType(int pref_value);
+
+ explicit SessionStartupPref(Type type);
+
+ SessionStartupPref(const SessionStartupPref& other);
+
+ ~SessionStartupPref();
+
+ // What to do on startup.
+ Type type;
+
+ // The URLs to restore. Only used if type == URLS.
+ std::vector<GURL> urls;
+};
+
+#endif // CHROME_BROWSER_PREFS_SESSION_STARTUP_PREF_H__
diff --git a/chromium/chrome/browser/prefs/session_startup_pref_unittest.cc b/chromium/chrome/browser/prefs/session_startup_pref_unittest.cc
new file mode 100644
index 00000000000..69a77f997a5
--- /dev/null
+++ b/chromium/chrome/browser/prefs/session_startup_pref_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Unit tests for SessionStartupPref.
+class SessionStartupPrefTest : public testing::Test {
+ public:
+ void SetUp() override {
+ pref_service_.reset(new sync_preferences::TestingPrefServiceSyncable);
+ SessionStartupPref::RegisterProfilePrefs(registry());
+ registry()->RegisterBooleanPref(prefs::kHomePageIsNewTabPage, true);
+ }
+
+ user_prefs::PrefRegistrySyncable* registry() {
+ return pref_service_->registry();
+ }
+
+ std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_;
+};
+
+TEST_F(SessionStartupPrefTest, URLListIsFixedUp) {
+ auto url_pref_list = std::make_unique<base::ListValue>();
+ url_pref_list->Set(0, std::make_unique<base::Value>("google.com"));
+ url_pref_list->Set(1, std::make_unique<base::Value>("chromium.org"));
+ pref_service_->SetUserPref(prefs::kURLsToRestoreOnStartup,
+ std::move(url_pref_list));
+
+ SessionStartupPref result =
+ SessionStartupPref::GetStartupPref(pref_service_.get());
+ EXPECT_EQ(2u, result.urls.size());
+ EXPECT_EQ("http://google.com/", result.urls[0].spec());
+ EXPECT_EQ("http://chromium.org/", result.urls[1].spec());
+}
+
+TEST_F(SessionStartupPrefTest, URLListManagedOverridesUser) {
+ auto url_pref_list1 = std::make_unique<base::ListValue>();
+ url_pref_list1->Set(0, std::make_unique<base::Value>("chromium.org"));
+ pref_service_->SetUserPref(prefs::kURLsToRestoreOnStartup,
+ std::move(url_pref_list1));
+
+ auto url_pref_list2 = std::make_unique<base::ListValue>();
+ url_pref_list2->Set(0, std::make_unique<base::Value>("chromium.org"));
+ url_pref_list2->Set(1, std::make_unique<base::Value>("chromium.org"));
+ url_pref_list2->Set(2, std::make_unique<base::Value>("chromium.org"));
+ pref_service_->SetManagedPref(prefs::kURLsToRestoreOnStartup,
+ std::move(url_pref_list2));
+
+ SessionStartupPref result =
+ SessionStartupPref::GetStartupPref(pref_service_.get());
+ EXPECT_EQ(3u, result.urls.size());
+
+ SessionStartupPref override_test =
+ SessionStartupPref(SessionStartupPref::URLS);
+ override_test.urls.push_back(GURL("dev.chromium.org"));
+ SessionStartupPref::SetStartupPref(pref_service_.get(), override_test);
+
+ result = SessionStartupPref::GetStartupPref(pref_service_.get());
+ EXPECT_EQ(3u, result.urls.size());
+}
diff --git a/chromium/chrome/browser/prefs/synced_pref_change_registrar_browsertest.cc b/chromium/chrome/browser/prefs/synced_pref_change_registrar_browsertest.cc
new file mode 100644
index 00000000000..a0624c1c365
--- /dev/null
+++ b/chromium/chrome/browser/prefs/synced_pref_change_registrar_browsertest.cc
@@ -0,0 +1,205 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chrome/browser/prefs/pref_service_syncable_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/sync/model/fake_sync_change_processor.h"
+#include "components/sync/model/sync_change.h"
+#include "components/sync/model/sync_error_factory.h"
+#include "components/sync/model/sync_error_factory_mock.h"
+#include "components/sync/model/syncable_service.h"
+#include "components/sync/protocol/sync.pb.h"
+#include "components/sync_preferences/synced_pref_change_registrar.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/test_utils.h"
+
+namespace {
+
+using testing::Return;
+using testing::_;
+
+class SyncedPrefChangeRegistrarTest : public InProcessBrowserTest {
+ public:
+ SyncedPrefChangeRegistrarTest() : next_sync_data_id_(0) {}
+ ~SyncedPrefChangeRegistrarTest() override {}
+
+ void UpdateChromePolicy(const policy::PolicyMap& policies) {
+ policy_provider_.UpdateChromePolicy(policies);
+ DCHECK(base::MessageLoopCurrent::Get());
+ base::RunLoop loop;
+ loop.RunUntilIdle();
+ }
+
+ void SetBooleanPrefValueFromSync(const std::string& name, bool value) {
+ std::string serialized_value;
+ JSONStringValueSerializer json(&serialized_value);
+ json.Serialize(base::Value(value));
+
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::PreferenceSpecifics* pref_specifics =
+ specifics.mutable_preference();
+ pref_specifics->set_name(name);
+ pref_specifics->set_value(serialized_value);
+
+ syncer::SyncData change_data =
+ syncer::SyncData::CreateRemoteData(++next_sync_data_id_, specifics);
+ syncer::SyncChange change(
+ FROM_HERE, syncer::SyncChange::ACTION_UPDATE, change_data);
+
+ syncer::SyncChangeList change_list;
+ change_list.push_back(change);
+
+ syncer_->ProcessSyncChanges(FROM_HERE, change_list);
+ }
+
+ void SetBooleanPrefValueFromLocal(const std::string& name, bool value) {
+ prefs_->SetBoolean(name.c_str(), value);
+ }
+
+ bool GetBooleanPrefValue(const std::string& name) {
+ return prefs_->GetBoolean(name.c_str());
+ }
+
+ sync_preferences::PrefServiceSyncable* prefs() const { return prefs_; }
+
+ sync_preferences::SyncedPrefChangeRegistrar* registrar() const {
+ return registrar_.get();
+ }
+
+ private:
+ void SetUpInProcessBrowserTestFixture() override {
+ EXPECT_CALL(policy_provider_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+ &policy_provider_);
+ }
+
+ void SetUpOnMainThread() override {
+ prefs_ = PrefServiceSyncableFromProfile(browser()->profile());
+ syncer_ = prefs_->GetSyncableService(syncer::PREFERENCES);
+ syncer_->MergeDataAndStartSyncing(
+ syncer::PREFERENCES, syncer::SyncDataList(),
+ std::unique_ptr<syncer::SyncChangeProcessor>(
+ new syncer::FakeSyncChangeProcessor),
+ std::unique_ptr<syncer::SyncErrorFactory>(
+ new syncer::SyncErrorFactoryMock));
+ registrar_.reset(new sync_preferences::SyncedPrefChangeRegistrar(prefs_));
+ }
+
+ void TearDownOnMainThread() override { registrar_.reset(); }
+
+ sync_preferences::PrefServiceSyncable* prefs_;
+ syncer::SyncableService* syncer_;
+ int next_sync_data_id_;
+
+ std::unique_ptr<sync_preferences::SyncedPrefChangeRegistrar> registrar_;
+ policy::MockConfigurationPolicyProvider policy_provider_;
+};
+
+struct TestSyncedPrefObserver {
+ bool last_seen_value;
+ bool last_update_is_from_sync;
+ bool has_been_notified;
+};
+
+void TestPrefChangeCallback(PrefService* prefs,
+ TestSyncedPrefObserver* observer,
+ const std::string& path,
+ bool from_sync) {
+ observer->last_seen_value = prefs->GetBoolean(path.c_str());
+ observer->last_update_is_from_sync = from_sync;
+ observer->has_been_notified = true;
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(SyncedPrefChangeRegistrarTest,
+ DifferentiateRemoteAndLocalChanges) {
+ TestSyncedPrefObserver observer = {};
+ registrar()->Add(prefs::kShowHomeButton,
+ base::Bind(&TestPrefChangeCallback, prefs(), &observer));
+
+ EXPECT_FALSE(observer.has_been_notified);
+
+ SetBooleanPrefValueFromSync(prefs::kShowHomeButton, true);
+ EXPECT_TRUE(observer.has_been_notified);
+ EXPECT_TRUE(GetBooleanPrefValue(prefs::kShowHomeButton));
+ EXPECT_TRUE(observer.last_update_is_from_sync);
+ EXPECT_TRUE(observer.last_seen_value);
+
+ observer.has_been_notified = false;
+ SetBooleanPrefValueFromLocal(prefs::kShowHomeButton, false);
+ EXPECT_TRUE(observer.has_been_notified);
+ EXPECT_FALSE(GetBooleanPrefValue(prefs::kShowHomeButton));
+ EXPECT_FALSE(observer.last_update_is_from_sync);
+ EXPECT_FALSE(observer.last_seen_value);
+
+ observer.has_been_notified = false;
+ SetBooleanPrefValueFromLocal(prefs::kShowHomeButton, true);
+ EXPECT_TRUE(observer.has_been_notified);
+ EXPECT_TRUE(GetBooleanPrefValue(prefs::kShowHomeButton));
+ EXPECT_FALSE(observer.last_update_is_from_sync);
+ EXPECT_TRUE(observer.last_seen_value);
+
+ observer.has_been_notified = false;
+ SetBooleanPrefValueFromSync(prefs::kShowHomeButton, false);
+ EXPECT_TRUE(observer.has_been_notified);
+ EXPECT_FALSE(GetBooleanPrefValue(prefs::kShowHomeButton));
+ EXPECT_TRUE(observer.last_update_is_from_sync);
+ EXPECT_FALSE(observer.last_seen_value);
+}
+
+IN_PROC_BROWSER_TEST_F(SyncedPrefChangeRegistrarTest,
+ IgnoreLocalChangesToManagedPrefs) {
+ TestSyncedPrefObserver observer = {};
+ registrar()->Add(prefs::kShowHomeButton,
+ base::Bind(&TestPrefChangeCallback, prefs(), &observer));
+
+ policy::PolicyMap policies;
+ policies.Set(policy::key::kShowHomeButton, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(true), nullptr);
+ UpdateChromePolicy(policies);
+
+ EXPECT_TRUE(prefs()->IsManagedPreference(prefs::kShowHomeButton));
+
+ SetBooleanPrefValueFromLocal(prefs::kShowHomeButton, false);
+ EXPECT_FALSE(observer.has_been_notified);
+ EXPECT_TRUE(GetBooleanPrefValue(prefs::kShowHomeButton));
+}
+
+IN_PROC_BROWSER_TEST_F(SyncedPrefChangeRegistrarTest,
+ IgnoreSyncChangesToManagedPrefs) {
+ TestSyncedPrefObserver observer = {};
+ registrar()->Add(prefs::kShowHomeButton,
+ base::Bind(&TestPrefChangeCallback, prefs(), &observer));
+
+ policy::PolicyMap policies;
+ policies.Set(policy::key::kShowHomeButton, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(true), nullptr);
+ UpdateChromePolicy(policies);
+
+ EXPECT_TRUE(prefs()->IsManagedPreference(prefs::kShowHomeButton));
+ SetBooleanPrefValueFromSync(prefs::kShowHomeButton, false);
+ EXPECT_FALSE(observer.has_been_notified);
+ EXPECT_TRUE(GetBooleanPrefValue(prefs::kShowHomeButton));
+}
diff --git a/chromium/chrome/browser/prefs/tracked/pref_hash_browsertest.cc b/chromium/chrome/browser/prefs/tracked/pref_hash_browsertest.cc
new file mode 100644
index 00000000000..67913e7e49d
--- /dev/null
+++ b/chromium/chrome/browser/prefs/tracked/pref_hash_browsertest.cc
@@ -0,0 +1,1284 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_reader.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/prefs/chrome_pref_service_factory.h"
+#include "chrome/browser/prefs/profile_pref_store_manager.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/search_engines/default_search_manager.h"
+#include "components/search_engines/template_url_data.h"
+#include "content/public/test/test_launcher.h"
+#include "extensions/browser/pref_names.h"
+#include "extensions/common/extension.h"
+#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/constants/chromeos_switches.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/registry.h"
+#include "chrome/install_static/install_util.h"
+#endif
+
+namespace {
+
+// Extension ID of chrome/test/data/extensions/good.crx
+const char kGoodCrxId[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
+
+// Explicit expectations from the caller of GetTrackedPrefHistogramCount(). This
+// enables detailed reporting of the culprit on failure.
+enum AllowedBuckets {
+ // Allow no samples in any buckets.
+ ALLOW_NONE = -1,
+ // Any integer between BEGIN_ALLOW_SINGLE_BUCKET and END_ALLOW_SINGLE_BUCKET
+ // indicates that only this specific bucket is allowed to have a sample.
+ BEGIN_ALLOW_SINGLE_BUCKET = 0,
+ END_ALLOW_SINGLE_BUCKET = 100,
+ // Allow any buckets (no extra verifications performed).
+ ALLOW_ANY
+};
+
+#if defined(OS_WIN)
+base::string16 GetRegistryPathForTestProfile() {
+ // Cleanup follow-up to http://crbug.com/721245 for the previous location of
+ // this test key which had similar problems (to a lesser extent). It's
+ // redundant but harmless to have multiple callers hit this on the same
+ // machine. TODO(gab): remove this mid-june 2017.
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Chromium\\PrefHashBrowserTest",
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ LONG result = key.DeleteKey(L"");
+ EXPECT_TRUE(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+ }
+
+ base::FilePath profile_dir;
+ EXPECT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &profile_dir));
+
+ // Use a location under the real PreferenceMACs path so that the backup
+ // cleanup logic in ChromeTestLauncherDelegate::PreSharding() for interrupted
+ // tests covers this test key as well.
+ return install_static::GetRegistryPath() +
+ L"\\PreferenceMACs\\PrefHashBrowserTest\\" +
+ profile_dir.BaseName().value();
+}
+#endif
+
+// Returns the number of times |histogram_name| was reported so far; adding the
+// results of the first 100 buckets (there are only ~19 reporting IDs as of this
+// writing; varies depending on the platform). |allowed_buckets| hints at extra
+// requirements verified in this method (see AllowedBuckets for details).
+int GetTrackedPrefHistogramCount(const char* histogram_name,
+ const char* histogram_suffix,
+ int allowed_buckets) {
+ std::string full_histogram_name(histogram_name);
+ if (*histogram_suffix)
+ full_histogram_name.append(".").append(histogram_suffix);
+ const base::HistogramBase* histogram =
+ base::StatisticsRecorder::FindHistogram(full_histogram_name);
+ if (!histogram)
+ return 0;
+
+ std::unique_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples());
+ int sum = 0;
+ for (int i = 0; i < 100; ++i) {
+ int count_for_id = samples->GetCount(i);
+ EXPECT_GE(count_for_id, 0);
+ sum += count_for_id;
+
+ if (allowed_buckets == ALLOW_NONE ||
+ (allowed_buckets != ALLOW_ANY && i != allowed_buckets)) {
+ EXPECT_EQ(0, count_for_id) << "Unexpected reporting_id: " << i;
+ }
+ }
+ return sum;
+}
+
+// Helper function to call GetTrackedPrefHistogramCount with no external
+// validation suffix.
+int GetTrackedPrefHistogramCount(const char* histogram_name,
+ int allowed_buckets) {
+ return GetTrackedPrefHistogramCount(histogram_name, "", allowed_buckets);
+}
+
+std::unique_ptr<base::DictionaryValue> ReadPrefsDictionary(
+ const base::FilePath& pref_file) {
+ JSONFileValueDeserializer deserializer(pref_file);
+ int error_code = JSONFileValueDeserializer::JSON_NO_ERROR;
+ std::string error_str;
+ std::unique_ptr<base::Value> prefs =
+ deserializer.Deserialize(&error_code, &error_str);
+ if (!prefs || error_code != JSONFileValueDeserializer::JSON_NO_ERROR) {
+ ADD_FAILURE() << "Error #" << error_code << ": " << error_str;
+ return std::unique_ptr<base::DictionaryValue>();
+ }
+ if (!prefs->is_dict()) {
+ ADD_FAILURE();
+ return std::unique_ptr<base::DictionaryValue>();
+ }
+ return std::unique_ptr<base::DictionaryValue>(
+ static_cast<base::DictionaryValue*>(prefs.release()));
+}
+
+// Returns whether external validation is supported on the platform through
+// storing MACs in the registry.
+bool SupportsRegistryValidation() {
+#if defined(OS_WIN)
+ return true;
+#else
+ return false;
+#endif
+}
+
+#define PREF_HASH_BROWSER_TEST(fixture, test_name) \
+ IN_PROC_BROWSER_TEST_P(fixture, PRE_##test_name) { SetupPreferences(); } \
+ IN_PROC_BROWSER_TEST_P(fixture, test_name) { VerifyReactionToPrefAttack(); } \
+ INSTANTIATE_TEST_SUITE_P( \
+ fixture##Instance, fixture, \
+ testing::Values( \
+ chrome_prefs::internals::kSettingsEnforcementGroupNoEnforcement, \
+ chrome_prefs::internals::kSettingsEnforcementGroupEnforceAlways, \
+ chrome_prefs::internals:: \
+ kSettingsEnforcementGroupEnforceAlwaysWithDSE, \
+ chrome_prefs::internals:: \
+ kSettingsEnforcementGroupEnforceAlwaysWithExtensionsAndDSE))
+
+// A base fixture designed such that implementations do two things:
+// 1) Override all three pure-virtual methods below to setup, attack, and
+// verify preferences throughout the tests provided by this fixture.
+// 2) Instantiate their test via the PREF_HASH_BROWSER_TEST macro above.
+// Based on top of ExtensionBrowserTest to allow easy interaction with the
+// ExtensionRegistry.
+class PrefHashBrowserTestBase
+ : public extensions::ExtensionBrowserTest,
+ public testing::WithParamInterface<std::string> {
+ public:
+ // List of potential protection levels for this test in strict increasing
+ // order of protection levels.
+ enum SettingsProtectionLevel {
+ PROTECTION_DISABLED_ON_PLATFORM,
+ PROTECTION_DISABLED_FOR_GROUP,
+ PROTECTION_ENABLED_BASIC,
+ PROTECTION_ENABLED_DSE,
+ PROTECTION_ENABLED_EXTENSIONS,
+ // Represents the strongest level (i.e. always equivalent to the last one in
+ // terms of protection), leave this one last when adding new levels.
+ PROTECTION_ENABLED_ALL
+ };
+
+ PrefHashBrowserTestBase()
+ : protection_level_(GetProtectionLevelFromTrialGroup(GetParam())) {
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
+ EXPECT_FALSE(command_line->HasSwitch(switches::kForceFieldTrials));
+ command_line->AppendSwitchASCII(
+ switches::kForceFieldTrials,
+ std::string(chrome_prefs::internals::kSettingsEnforcementTrialName) +
+ "/" + GetParam() + "/");
+#if defined(OS_CHROMEOS)
+ command_line->AppendSwitch(
+ chromeos::switches::kIgnoreUserProfileMappingForTests);
+#endif
+ }
+
+ bool SetUpUserDataDirectory() override {
+ // Do the normal setup in the PRE test and attack preferences in the main
+ // test.
+ if (content::IsPreTest())
+ return extensions::ExtensionBrowserTest::SetUpUserDataDirectory();
+
+#if defined(OS_CHROMEOS)
+ // For some reason, the Preferences file does not exist in the location
+ // below on Chrome OS. Since protection is disabled on Chrome OS, it's okay
+ // to simply not attack preferences at all (and still assert that no
+ // hardening related histogram kicked in in VerifyReactionToPrefAttack()).
+ // TODO(gab): Figure out why there is no Preferences file in this location
+ // on Chrome OS (and re-enable the section disabled for OS_CHROMEOS further
+ // below).
+ EXPECT_EQ(PROTECTION_DISABLED_ON_PLATFORM, protection_level_);
+ return true;
+#endif
+
+ base::FilePath profile_dir;
+ EXPECT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &profile_dir));
+ profile_dir = profile_dir.AppendASCII(TestingProfile::kTestUserProfileDir);
+
+ // Sanity check that old protected pref file is never present in modern
+ // Chromes.
+ EXPECT_FALSE(base::PathExists(
+ profile_dir.Append(FILE_PATH_LITERAL("Protected Preferences"))));
+
+ // Read the preferences from disk.
+
+ const base::FilePath unprotected_pref_file =
+ profile_dir.Append(chrome::kPreferencesFilename);
+ EXPECT_TRUE(base::PathExists(unprotected_pref_file));
+
+ const base::FilePath protected_pref_file =
+ profile_dir.Append(chrome::kSecurePreferencesFilename);
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM,
+ base::PathExists(protected_pref_file));
+
+ std::unique_ptr<base::DictionaryValue> unprotected_preferences(
+ ReadPrefsDictionary(unprotected_pref_file));
+ if (!unprotected_preferences)
+ return false;
+
+ std::unique_ptr<base::DictionaryValue> protected_preferences;
+ if (protection_level_ > PROTECTION_DISABLED_ON_PLATFORM) {
+ protected_preferences = ReadPrefsDictionary(protected_pref_file);
+ if (!protected_preferences)
+ return false;
+ }
+
+ // Let the underlying test modify the preferences.
+ AttackPreferencesOnDisk(unprotected_preferences.get(),
+ protected_preferences.get());
+
+ // Write the modified preferences back to disk.
+
+ JSONFileValueSerializer unprotected_prefs_serializer(unprotected_pref_file);
+ EXPECT_TRUE(
+ unprotected_prefs_serializer.Serialize(*unprotected_preferences));
+
+ if (protected_preferences) {
+ JSONFileValueSerializer protected_prefs_serializer(protected_pref_file);
+ EXPECT_TRUE(protected_prefs_serializer.Serialize(*protected_preferences));
+ }
+
+ return true;
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ extensions::ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
+
+ // Bots are on a domain, turn off the domain check for settings hardening in
+ // order to be able to test all SettingsEnforcement groups.
+ chrome_prefs::DisableDomainCheckForTesting();
+
+#if defined(OS_WIN)
+ // Avoid polluting prefs for the user and the bots by writing to a specific
+ // testing registry path.
+ registry_key_for_external_validation_ = GetRegistryPathForTestProfile();
+ ProfilePrefStoreManager::SetPreferenceValidationRegistryPathForTesting(
+ &registry_key_for_external_validation_);
+
+ // Keys should be unique, but to avoid flakes in the long run make sure an
+ // identical test key wasn't left behind by a previous test.
+ if (content::IsPreTest()) {
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER,
+ registry_key_for_external_validation_.c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ LONG result = key.DeleteKey(L"");
+ ASSERT_TRUE(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+ }
+ }
+#endif
+ }
+
+ void TearDown() override {
+#if defined(OS_WIN)
+ // When done, delete the Registry key to avoid polluting the registry.
+ if (!content::IsPreTest()) {
+ base::string16 registry_key = GetRegistryPathForTestProfile();
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER, registry_key.c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ LONG result = key.DeleteKey(L"");
+ ASSERT_TRUE(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+ }
+ }
+#endif
+ extensions::ExtensionBrowserTest::TearDown();
+ }
+
+ // In the PRE_ test, find the number of tracked preferences that were
+ // initialized and save it to a file to be read back in the main test and used
+ // as the total number of tracked preferences.
+ void SetUpOnMainThread() override {
+ extensions::ExtensionBrowserTest::SetUpOnMainThread();
+
+ // File in which the PRE_ test will save the number of tracked preferences
+ // on this platform.
+ const char kNumTrackedPrefFilename[] = "NumTrackedPrefs";
+
+ base::FilePath num_tracked_prefs_file;
+ ASSERT_TRUE(
+ base::PathService::Get(chrome::DIR_USER_DATA, &num_tracked_prefs_file));
+ num_tracked_prefs_file =
+ num_tracked_prefs_file.AppendASCII(kNumTrackedPrefFilename);
+
+ if (content::IsPreTest()) {
+ num_tracked_prefs_ = GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized, ALLOW_ANY);
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM,
+ num_tracked_prefs_ > 0);
+
+ // Split tracked prefs are reported as Unchanged not as NullInitialized
+ // when an empty dictionary is encountered on first run (this should only
+ // hit for pref #5 in the current design).
+ int num_split_tracked_prefs = GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged,
+ BEGIN_ALLOW_SINGLE_BUCKET + 5);
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ num_split_tracked_prefs);
+
+ if (SupportsRegistryValidation()) {
+ // Same checks as above, but for the registry.
+ num_tracked_prefs_ = GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ ALLOW_ANY);
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM,
+ num_tracked_prefs_ > 0);
+
+ int num_split_tracked_prefs = GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 5);
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ num_split_tracked_prefs);
+ }
+
+ num_tracked_prefs_ += num_split_tracked_prefs;
+
+ std::string num_tracked_prefs_str =
+ base::NumberToString(num_tracked_prefs_);
+ EXPECT_EQ(static_cast<int>(num_tracked_prefs_str.size()),
+ base::WriteFile(num_tracked_prefs_file,
+ num_tracked_prefs_str.c_str(),
+ num_tracked_prefs_str.size()));
+ } else {
+ std::string num_tracked_prefs_str;
+ EXPECT_TRUE(base::ReadFileToString(num_tracked_prefs_file,
+ &num_tracked_prefs_str));
+ EXPECT_TRUE(
+ base::StringToInt(num_tracked_prefs_str, &num_tracked_prefs_));
+ }
+ }
+
+ protected:
+ // Called from the PRE_ test's body. Overrides should use it to setup
+ // preferences through Chrome.
+ virtual void SetupPreferences() = 0;
+
+ // Called prior to the main test launching its browser. Overrides should use
+ // it to attack preferences. |(un)protected_preferences| represent the state
+ // on disk prior to launching the main test, they can be modified by this
+ // method and modifications will be flushed back to disk before launching the
+ // main test. |unprotected_preferences| is never NULL, |protected_preferences|
+ // may be NULL if in PROTECTION_DISABLED_ON_PLATFORM mode.
+ virtual void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) = 0;
+
+ // Called from the body of the main test. Overrides should use it to verify
+ // that the browser had the desired reaction when faced when the attack
+ // orchestrated in AttackPreferencesOnDisk().
+ virtual void VerifyReactionToPrefAttack() = 0;
+
+ int num_tracked_prefs() const { return num_tracked_prefs_; }
+
+ const SettingsProtectionLevel protection_level_;
+
+ private:
+ SettingsProtectionLevel GetProtectionLevelFromTrialGroup(
+ const std::string& trial_group) {
+ if (!ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking)
+ return PROTECTION_DISABLED_ON_PLATFORM;
+
+// Protection levels can't be adjusted via --force-fieldtrials in official
+// builds.
+#if defined(OFFICIAL_BUILD)
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ // The strongest mode is enforced on Windows and MacOS in the absence of a
+ // field trial.
+ return PROTECTION_ENABLED_ALL;
+#else
+ return PROTECTION_DISABLED_FOR_GROUP;
+#endif // defined(OS_WIN) || defined(OS_MACOSX)
+
+#else // defined(OFFICIAL_BUILD)
+
+ namespace internals = chrome_prefs::internals;
+ if (trial_group == internals::kSettingsEnforcementGroupNoEnforcement)
+ return PROTECTION_DISABLED_FOR_GROUP;
+ if (trial_group == internals::kSettingsEnforcementGroupEnforceAlways)
+ return PROTECTION_ENABLED_BASIC;
+ if (trial_group == internals::kSettingsEnforcementGroupEnforceAlwaysWithDSE)
+ return PROTECTION_ENABLED_DSE;
+ if (trial_group ==
+ internals::kSettingsEnforcementGroupEnforceAlwaysWithExtensionsAndDSE) {
+ return PROTECTION_ENABLED_EXTENSIONS;
+ }
+ ADD_FAILURE();
+ return static_cast<SettingsProtectionLevel>(-1);
+#endif // defined(OFFICIAL_BUILD)
+ }
+
+ int num_tracked_prefs_;
+
+#if defined(OS_WIN)
+ base::string16 registry_key_for_external_validation_;
+#endif
+};
+
+} // namespace
+
+// Verifies that nothing is reset when nothing is tampered with.
+// Also sanity checks that the expected preferences files are in place.
+class PrefHashBrowserTestUnchangedDefault : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ // Default Chrome setup.
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ // No attack.
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Expect all prefs to be reported as Unchanged with no resets.
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs()
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset, ALLOW_NONE));
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged, ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared, ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ // Expect all prefs to be reported as Unchanged.
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs()
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ ALLOW_ANY));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUnchangedDefault, UnchangedDefault);
+
+// Augments PrefHashBrowserTestUnchangedDefault to confirm that nothing is reset
+// when nothing is tampered with, even if Chrome itself wrote custom prefs in
+// its last run.
+class PrefHashBrowserTestUnchangedCustom
+ : public PrefHashBrowserTestUnchangedDefault {
+ public:
+ void SetupPreferences() override {
+ profile()->GetPrefs()->SetString(prefs::kHomePage, "http://example.com");
+
+ InstallExtensionWithUIAutoConfirm(
+ test_data_dir_.AppendASCII("good.crx"), 1, browser());
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Make sure the settings written in the last run stuck.
+ EXPECT_EQ("http://example.com",
+ profile()->GetPrefs()->GetString(prefs::kHomePage));
+
+ EXPECT_TRUE(extension_registry()->enabled_extensions().GetByID(kGoodCrxId));
+
+ // Reaction should be identical to unattacked default prefs.
+ PrefHashBrowserTestUnchangedDefault::VerifyReactionToPrefAttack();
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUnchangedCustom, UnchangedCustom);
+
+// Verifies that cleared prefs are reported.
+class PrefHashBrowserTestClearedAtomic : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ profile()->GetPrefs()->SetString(prefs::kHomePage, "http://example.com");
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ base::DictionaryValue* selected_prefs =
+ protection_level_ >= PROTECTION_ENABLED_BASIC ? protected_preferences
+ : unprotected_preferences;
+ // |selected_prefs| should never be NULL under the protection level picking
+ // it.
+ EXPECT_TRUE(selected_prefs);
+ EXPECT_TRUE(selected_prefs->Remove(prefs::kHomePage, NULL));
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // The clearance of homepage should have been noticed (as pref #2 being
+ // cleared), but shouldn't have triggered a reset (as there is nothing we
+ // can do when the pref is already gone).
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs() - 1
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset, ALLOW_NONE));
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged, ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ // Expect homepage clearance to have been noticed by registry validation.
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestClearedAtomic, ClearedAtomic);
+
+// Verifies that clearing the MACs results in untrusted Initialized pings for
+// non-null protected prefs.
+class PrefHashBrowserTestUntrustedInitialized : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ // Explicitly set the DSE (it's otherwise NULL by default, preventing
+ // thorough testing of the PROTECTION_ENABLED_DSE level).
+ DefaultSearchManager default_search_manager(
+ profile()->GetPrefs(), DefaultSearchManager::ObserverCallback());
+ DefaultSearchManager::Source dse_source =
+ static_cast<DefaultSearchManager::Source>(-1);
+
+ const TemplateURLData* default_template_url_data =
+ default_search_manager.GetDefaultSearchEngine(&dse_source);
+ EXPECT_EQ(DefaultSearchManager::FROM_FALLBACK, dse_source);
+
+ default_search_manager.SetUserSelectedDefaultSearchEngine(
+ *default_template_url_data);
+
+ default_search_manager.GetDefaultSearchEngine(&dse_source);
+ EXPECT_EQ(DefaultSearchManager::FROM_USER, dse_source);
+
+ // Also explicitly set an atomic pref that falls under
+ // PROTECTION_ENABLED_BASIC.
+ profile()->GetPrefs()->SetInteger(prefs::kRestoreOnStartup,
+ SessionStartupPref::URLS);
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ unprotected_preferences->Remove("protection.macs", NULL);
+ if (protected_preferences)
+ protected_preferences->Remove("protection.macs", NULL);
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Preferences that are NULL by default will be NullInitialized.
+ int num_null_values = GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized, ALLOW_ANY);
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM,
+ num_null_values > 0);
+ if (num_null_values > 0) {
+ // This test requires that at least 3 prefs be non-null (extensions, DSE,
+ // and 1 atomic pref explictly set for this test above).
+ EXPECT_GE(num_tracked_prefs() - num_null_values, 3);
+ }
+
+ // Expect all non-null prefs to be reported as Initialized (with
+ // accompanying resets or wanted resets based on the current protection
+ // level).
+ EXPECT_EQ(
+ num_tracked_prefs() - num_null_values,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized, ALLOW_ANY));
+
+ int num_protected_prefs = 0;
+ // A switch statement falling through each protection level in decreasing
+ // levels of protection to add expectations for each level which augments
+ // the previous one.
+ switch (protection_level_) {
+ case PROTECTION_ENABLED_ALL:
+ case PROTECTION_ENABLED_EXTENSIONS:
+ ++num_protected_prefs;
+ FALLTHROUGH;
+ case PROTECTION_ENABLED_DSE:
+ ++num_protected_prefs;
+ FALLTHROUGH;
+ case PROTECTION_ENABLED_BASIC:
+ num_protected_prefs += num_tracked_prefs() - num_null_values - 2;
+ FALLTHROUGH;
+ case PROTECTION_DISABLED_FOR_GROUP:
+ case PROTECTION_DISABLED_ON_PLATFORM:
+ // No protection.
+ break;
+ }
+
+ EXPECT_EQ(
+ num_tracked_prefs() - num_null_values - num_protected_prefs,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset, ALLOW_ANY));
+ EXPECT_EQ(num_protected_prefs,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset, ALLOW_ANY));
+
+ // Explicitly verify the result of reported resets.
+
+ DefaultSearchManager default_search_manager(
+ profile()->GetPrefs(), DefaultSearchManager::ObserverCallback());
+ DefaultSearchManager::Source dse_source =
+ static_cast<DefaultSearchManager::Source>(-1);
+ default_search_manager.GetDefaultSearchEngine(&dse_source);
+ EXPECT_EQ(protection_level_ < PROTECTION_ENABLED_DSE
+ ? DefaultSearchManager::FROM_USER
+ : DefaultSearchManager::FROM_FALLBACK,
+ dse_source);
+
+ EXPECT_EQ(protection_level_ < PROTECTION_ENABLED_BASIC,
+ profile()->GetPrefs()->GetInteger(prefs::kRestoreOnStartup) ==
+ SessionStartupPref::URLS);
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged, ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared, ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ // The MACs have been cleared but the preferences have not been tampered.
+ // The registry should report all prefs as unchanged.
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs()
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ ALLOW_ANY));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUntrustedInitialized,
+ UntrustedInitialized);
+
+// Verifies that changing an atomic pref results in it being reported (and reset
+// if the protection level allows it).
+class PrefHashBrowserTestChangedAtomic : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ profile()->GetPrefs()->SetInteger(prefs::kRestoreOnStartup,
+ SessionStartupPref::URLS);
+
+ ListPrefUpdate update(profile()->GetPrefs(),
+ prefs::kURLsToRestoreOnStartup);
+ update->AppendString("http://example.com");
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ base::DictionaryValue* selected_prefs =
+ protection_level_ >= PROTECTION_ENABLED_BASIC ? protected_preferences
+ : unprotected_preferences;
+ // |selected_prefs| should never be NULL under the protection level picking
+ // it.
+ EXPECT_TRUE(selected_prefs);
+ base::ListValue* startup_urls;
+ EXPECT_TRUE(
+ selected_prefs->GetList(prefs::kURLsToRestoreOnStartup, &startup_urls));
+ EXPECT_TRUE(startup_urls);
+ EXPECT_EQ(1U, startup_urls->GetSize());
+ startup_urls->AppendString("http://example.org");
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Expect a single Changed event for tracked pref #4 (startup URLs).
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ BEGIN_ALLOW_SINGLE_BUCKET + 4));
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs() - 1
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+
+ EXPECT_EQ((protection_level_ > PROTECTION_DISABLED_ON_PLATFORM &&
+ protection_level_ < PROTECTION_ENABLED_BASIC)
+ ? 1
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset,
+ BEGIN_ALLOW_SINGLE_BUCKET + 4));
+ EXPECT_EQ(protection_level_ >= PROTECTION_ENABLED_BASIC ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset,
+ BEGIN_ALLOW_SINGLE_BUCKET + 4));
+
+// TODO(gab): This doesn't work on OS_CHROMEOS because we fail to attack
+// Preferences.
+#if !defined(OS_CHROMEOS)
+ // Explicitly verify the result of reported resets.
+ EXPECT_EQ(protection_level_ >= PROTECTION_ENABLED_BASIC ? 0U : 2U,
+ profile()
+ ->GetPrefs()
+ ->GetList(prefs::kURLsToRestoreOnStartup)
+ ->GetSize());
+#endif
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared, ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ // Expect a single Changed event for tracked pref #4 (startup URLs).
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 4));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestChangedAtomic, ChangedAtomic);
+
+// Verifies that changing or adding an entry in a split pref results in both
+// items being reported (and remove if the protection level allows it).
+class PrefHashBrowserTestChangedSplitPref : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ InstallExtensionWithUIAutoConfirm(
+ test_data_dir_.AppendASCII("good.crx"), 1, browser());
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ base::DictionaryValue* selected_prefs =
+ protection_level_ >= PROTECTION_ENABLED_EXTENSIONS
+ ? protected_preferences
+ : unprotected_preferences;
+ // |selected_prefs| should never be NULL under the protection level picking
+ // it.
+ EXPECT_TRUE(selected_prefs);
+ base::DictionaryValue* extensions_dict;
+ EXPECT_TRUE(selected_prefs->GetDictionary(
+ extensions::pref_names::kExtensions, &extensions_dict));
+ EXPECT_TRUE(extensions_dict);
+
+ // Tamper with any installed setting for good.crx
+ base::DictionaryValue* good_crx_dict;
+ EXPECT_TRUE(extensions_dict->GetDictionary(kGoodCrxId, &good_crx_dict));
+ int good_crx_state;
+ EXPECT_TRUE(good_crx_dict->GetInteger("state", &good_crx_state));
+ EXPECT_EQ(extensions::Extension::ENABLED, good_crx_state);
+ good_crx_dict->SetInteger("state", extensions::Extension::DISABLED);
+
+ // Drop a fake extension (for the purpose of this test, dropped settings
+ // don't need to be valid extension settings).
+ auto fake_extension = std::make_unique<base::DictionaryValue>();
+ fake_extension->SetString("name", "foo");
+ extensions_dict->Set(std::string(32, 'a'), std::move(fake_extension));
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Expect a single split pref changed report with a count of 2 for tracked
+ // pref #5 (extensions).
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ BEGIN_ALLOW_SINGLE_BUCKET + 5));
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ "Settings.TrackedSplitPreferenceChanged.extensions.settings",
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+
+ // Everything else should have remained unchanged.
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs() - 1
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+
+ EXPECT_EQ((protection_level_ > PROTECTION_DISABLED_ON_PLATFORM &&
+ protection_level_ < PROTECTION_ENABLED_EXTENSIONS)
+ ? 1
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset,
+ BEGIN_ALLOW_SINGLE_BUCKET + 5));
+ EXPECT_EQ(protection_level_ >= PROTECTION_ENABLED_EXTENSIONS ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset,
+ BEGIN_ALLOW_SINGLE_BUCKET + 5));
+
+ EXPECT_EQ(
+ protection_level_ < PROTECTION_ENABLED_EXTENSIONS,
+ extension_registry()->GetExtensionById(
+ kGoodCrxId, extensions::ExtensionRegistry::EVERYTHING) != nullptr);
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared, ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ // Expect that the registry validation caught the invalid MAC in split
+ // pref #5 (extensions).
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 5));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestChangedSplitPref, ChangedSplitPref);
+
+// Verifies that adding a value to unprotected preferences for a key which is
+// still using the default (i.e. has no value stored in protected preferences)
+// doesn't allow that value to slip in with no valid MAC (regression test for
+// http://crbug.com/414554)
+class PrefHashBrowserTestUntrustedAdditionToPrefs
+ : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ // Ensure there is no user-selected value for kRestoreOnStartup.
+ EXPECT_FALSE(
+ profile()->GetPrefs()->GetUserPrefValue(prefs::kRestoreOnStartup));
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ unprotected_preferences->SetInteger(prefs::kRestoreOnStartup,
+ SessionStartupPref::LAST);
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Expect a single Changed event for tracked pref #3 (kRestoreOnStartup) if
+ // not protecting; if protection is enabled the change should be a no-op.
+ int changed_expected =
+ protection_level_ == PROTECTION_DISABLED_FOR_GROUP ? 1 : 0;
+ EXPECT_EQ((protection_level_ > PROTECTION_DISABLED_ON_PLATFORM &&
+ protection_level_ < PROTECTION_ENABLED_BASIC)
+ ? changed_expected
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ BEGIN_ALLOW_SINGLE_BUCKET + 3));
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs() - changed_expected
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+
+ EXPECT_EQ((protection_level_ > PROTECTION_DISABLED_ON_PLATFORM &&
+ protection_level_ < PROTECTION_ENABLED_BASIC)
+ ? 1
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset,
+ BEGIN_ALLOW_SINGLE_BUCKET + 3));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset, ALLOW_NONE));
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared, ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ EXPECT_EQ((protection_level_ > PROTECTION_DISABLED_ON_PLATFORM &&
+ protection_level_ < PROTECTION_ENABLED_BASIC)
+ ? changed_expected
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 3));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUntrustedAdditionToPrefs,
+ UntrustedAdditionToPrefs);
+
+// Verifies that adding a value to unprotected preferences while wiping a
+// user-selected value from protected preferences doesn't allow that value to
+// slip in with no valid MAC (regression test for http://crbug.com/414554).
+class PrefHashBrowserTestUntrustedAdditionToPrefsAfterWipe
+ : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ profile()->GetPrefs()->SetString(prefs::kHomePage, "http://example.com");
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ // Set or change the value in Preferences to the attacker's choice.
+ unprotected_preferences->SetString(prefs::kHomePage, "http://example.net");
+ // Clear the value in Secure Preferences, if any.
+ if (protected_preferences)
+ protected_preferences->Remove(prefs::kHomePage, NULL);
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ // Expect a single Changed event for tracked pref #2 (kHomePage) if
+ // not protecting; if protection is enabled the change should be a Cleared.
+ int changed_expected =
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM &&
+ protection_level_ < PROTECTION_ENABLED_BASIC
+ ? 1 : 0;
+ int cleared_expected =
+ protection_level_ >= PROTECTION_ENABLED_BASIC
+ ? 1 : 0;
+ EXPECT_EQ(changed_expected,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ EXPECT_EQ(cleared_expected,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs() - changed_expected - cleared_expected
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+
+ EXPECT_EQ(changed_expected,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramReset, ALLOW_NONE));
+
+ // Nothing else should have triggered.
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized,
+ ALLOW_NONE));
+ EXPECT_EQ(
+ 0, GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId,
+ ALLOW_NONE));
+
+ if (SupportsRegistryValidation()) {
+ EXPECT_EQ(changed_expected,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ EXPECT_EQ(cleared_expected,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramCleared,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUntrustedAdditionToPrefsAfterWipe,
+ UntrustedAdditionToPrefsAfterWipe);
+
+#if defined(OS_WIN)
+class PrefHashBrowserTestRegistryValidationFailure
+ : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ profile()->GetPrefs()->SetString(prefs::kHomePage, "http://example.com");
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ base::string16 registry_key =
+ GetRegistryPathForTestProfile() + L"\\PreferenceMACs\\Default";
+ base::win::RegKey key;
+ ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, registry_key.c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY));
+ // An incorrect hash should still have the correct size.
+ ASSERT_EQ(ERROR_SUCCESS,
+ key.WriteValue(L"homepage", base::string16(64, 'A').c_str()));
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ EXPECT_EQ(
+ protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
+ ? num_tracked_prefs()
+ : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));
+
+ if (SupportsRegistryValidation()) {
+ // Expect that the registry validation caught the invalid MAC for pref #2
+ // (homepage).
+ EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
+ GetTrackedPrefHistogramCount(
+ user_prefs::tracked::kTrackedPrefHistogramChanged,
+ user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
+ BEGIN_ALLOW_SINGLE_BUCKET + 2));
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestRegistryValidationFailure,
+ RegistryValidationFailure);
+#endif
+
+// Verifies that all preferences related to choice of default search engine are
+// protected.
+class PrefHashBrowserTestDefaultSearch : public PrefHashBrowserTestBase {
+ public:
+ void SetupPreferences() override {
+ // Set user selected default search engine.
+ DefaultSearchManager default_search_manager(
+ profile()->GetPrefs(), DefaultSearchManager::ObserverCallback());
+ DefaultSearchManager::Source dse_source =
+ static_cast<DefaultSearchManager::Source>(-1);
+
+ TemplateURLData user_dse;
+ user_dse.SetKeyword(base::UTF8ToUTF16("userkeyword"));
+ user_dse.SetShortName(base::UTF8ToUTF16("username"));
+ user_dse.SetURL("http://user_default_engine/search?q=good_user_query");
+ default_search_manager.SetUserSelectedDefaultSearchEngine(user_dse);
+
+ const TemplateURLData* current_dse =
+ default_search_manager.GetDefaultSearchEngine(&dse_source);
+ EXPECT_EQ(DefaultSearchManager::FROM_USER, dse_source);
+ EXPECT_EQ(current_dse->keyword(), base::UTF8ToUTF16("userkeyword"));
+ EXPECT_EQ(current_dse->short_name(), base::UTF8ToUTF16("username"));
+ EXPECT_EQ(current_dse->url(),
+ "http://user_default_engine/search?q=good_user_query");
+ }
+
+ void AttackPreferencesOnDisk(
+ base::DictionaryValue* unprotected_preferences,
+ base::DictionaryValue* protected_preferences) override {
+ static constexpr char default_search_provider_data[] = R"(
+ {
+ "default_search_provider_data" : {
+ "template_url_data" : {
+ "keyword" : "badkeyword",
+ "short_name" : "badname",
+ "url" : "http://bad_default_engine/search?q=dirty_user_query"
+ }
+ }
+ })";
+ static constexpr char search_provider_overrides[] = R"(
+ {
+ "search_provider_overrides" : [
+ {
+ "keyword" : "badkeyword",
+ "name" : "badname",
+ "search_url" : "http://bad_default_engine/search?q=dirty_user_query",
+ "encoding" : "utf-8",
+ "id" : 1
+ }, {
+ "keyword" : "badkeyword2",
+ "name" : "badname2",
+ "search_url" : "http://bad_default_engine2/search?q=dirty_user_query",
+ "encoding" : "utf-8",
+ "id" : 2
+ }
+ ]
+ })";
+
+ // Try to override default search in all three of available preferences.
+ auto attack1 = base::DictionaryValue::From(
+ base::JSONReader::ReadDeprecated(default_search_provider_data));
+ auto attack2 = base::DictionaryValue::From(
+ base::JSONReader::ReadDeprecated(search_provider_overrides));
+ unprotected_preferences->MergeDictionary(attack1.get());
+ unprotected_preferences->MergeDictionary(attack2.get());
+ if (protected_preferences) {
+ // Override here, too.
+ protected_preferences->MergeDictionary(attack1.get());
+ protected_preferences->MergeDictionary(attack2.get());
+ }
+ }
+
+ void VerifyReactionToPrefAttack() override {
+ DefaultSearchManager default_search_manager(
+ profile()->GetPrefs(), DefaultSearchManager::ObserverCallback());
+ DefaultSearchManager::Source dse_source =
+ static_cast<DefaultSearchManager::Source>(-1);
+
+ const TemplateURLData* current_dse =
+ default_search_manager.GetDefaultSearchEngine(&dse_source);
+
+ if (protection_level_ < PROTECTION_ENABLED_DSE) {
+// This doesn't work on OS_CHROMEOS because we fail to attack Preferences.
+#if !defined(OS_CHROMEOS)
+ // Attack is successful.
+ EXPECT_EQ(DefaultSearchManager::FROM_USER, dse_source);
+ EXPECT_EQ(current_dse->keyword(), base::UTF8ToUTF16("badkeyword"));
+ EXPECT_EQ(current_dse->short_name(), base::UTF8ToUTF16("badname"));
+ EXPECT_EQ(current_dse->url(),
+ "http://bad_default_engine/search?q=dirty_user_query");
+#endif
+ } else {
+ // Attack fails.
+ EXPECT_EQ(DefaultSearchManager::FROM_FALLBACK, dse_source);
+ EXPECT_NE(current_dse->keyword(), base::UTF8ToUTF16("badkeyword"));
+ EXPECT_NE(current_dse->short_name(), base::UTF8ToUTF16("badname"));
+ EXPECT_NE(current_dse->url(),
+ "http://bad_default_engine/search?q=dirty_user_query");
+ }
+ }
+};
+
+PREF_HASH_BROWSER_TEST(PrefHashBrowserTestDefaultSearch, SearchProtected);
diff --git a/chromium/chrome/browser/renderer_host/DEPS b/chromium/chrome/browser/renderer_host/DEPS
new file mode 100644
index 00000000000..01daa08dfcb
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ # renderer_host is intended to support the content layer's renderers. No
+ # reference to the tab contents is allowed.
+ "-chrome/browser/tab_contents",
+ # Allow tab_util.h for GetWebContentsByID.
+ "+chrome/browser/tab_contents/tab_util.h",
+ "+components/page_load_metrics/browser",
+ "+third_party/ocmock/OCMock/OCMock.h",
+ "+third_party/ocmock/ocmock_extensions.h",
+]
+
diff --git a/chromium/chrome/browser/renderer_host/OWNERS b/chromium/chrome/browser/renderer_host/OWNERS
new file mode 100644
index 00000000000..a6d4ea44b12
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/OWNERS
@@ -0,0 +1,19 @@
+per-file chrome_extension_message_filter.*=file://extensions/OWNERS
+
+# Mac files
+per-file *.mm=avi@chromium.org
+per-file *.mm=ccameron@chromium.org
+per-file *.mm=erikchen@chromium.org
+per-file *.mm=mark@chromium.org
+per-file *.mm=rsesek@chromium.org
+per-file *.mm=thakis@chromium.org
+
+per-file *_mac.h=avi@chromium.org
+per-file *_mac.h=ccameron@chromium.org
+per-file *_mac.h=erikchen@chromium.org
+per-file *_mac.h=mark@chromium.org
+per-file *_mac.h=rsesek@chromium.org
+per-file *_mac.h=thakis@chromium.org
+
+
+# COMPONENT: Content>Core
diff --git a/chromium/chrome/browser/renderer_host/chrome_extension_message_filter.cc b/chromium/chrome/browser/renderer_host/chrome_extension_message_filter.cc
new file mode 100644
index 00000000000..e2d004551ca
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_extension_message_filter.cc
@@ -0,0 +1,255 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/renderer_host/chrome_extension_message_filter.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
+#include "chrome/browser/extensions/activity_log/activity_actions.h"
+#include "chrome/browser/extensions/activity_log/activity_log.h"
+#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/message_bundle.h"
+
+using content::BrowserThread;
+
+namespace {
+
+const uint32_t kExtensionFilteredMessageClasses[] = {
+ ExtensionMsgStart,
+};
+
+// Logs an action to the extension activity log for the specified profile.
+void AddActionToExtensionActivityLog(Profile* profile,
+ extensions::ActivityLog* activity_log,
+ scoped_refptr<extensions::Action> action) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // If the action included a URL, check whether it is for an incognito
+ // profile. The check is performed here so that it can safely be done from
+ // the UI thread.
+ if (action->page_url().is_valid() || !action->page_title().empty())
+ action->set_page_incognito(profile->IsOffTheRecord());
+ activity_log->LogAction(action);
+}
+
+} // namespace
+
+ChromeExtensionMessageFilter::ChromeExtensionMessageFilter(
+ int render_process_id,
+ Profile* profile)
+ : BrowserMessageFilter(kExtensionFilteredMessageClasses,
+ base::size(kExtensionFilteredMessageClasses)),
+ render_process_id_(render_process_id),
+ profile_(profile),
+ activity_log_(extensions::ActivityLog::GetInstance(profile)),
+ extension_info_map_(
+ extensions::ExtensionSystem::Get(profile)->info_map()) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_PROFILE_DESTROYED,
+ content::Source<Profile>(profile));
+}
+
+ChromeExtensionMessageFilter::~ChromeExtensionMessageFilter() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+bool ChromeExtensionMessageFilter::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ChromeExtensionMessageFilter, message)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ExtensionHostMsg_GetMessageBundle,
+ OnGetExtMessageBundle)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddAPIActionToActivityLog,
+ OnAddAPIActionToExtensionActivityLog);
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddDOMActionToActivityLog,
+ OnAddDOMActionToExtensionActivityLog);
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddEventToActivityLog,
+ OnAddEventToExtensionActivityLog);
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+
+void ChromeExtensionMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) {
+ switch (message.type()) {
+ case ExtensionHostMsg_GetMessageBundle::ID:
+ case ExtensionHostMsg_AddAPIActionToActivityLog::ID:
+ case ExtensionHostMsg_AddDOMActionToActivityLog::ID:
+ case ExtensionHostMsg_AddEventToActivityLog::ID:
+ *thread = BrowserThread::UI;
+ break;
+ default:
+ break;
+ }
+}
+
+void ChromeExtensionMessageFilter::OnDestruct() const {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ delete this;
+ } else {
+ BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
+ }
+}
+
+void ChromeExtensionMessageFilter::OnGetExtMessageBundle(
+ const std::string& extension_id, IPC::Message* reply_msg) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const extensions::ExtensionSet& extension_set =
+ extensions::ExtensionRegistry::Get(profile_)->enabled_extensions();
+ const extensions::Extension* extension = extension_set.GetByID(extension_id);
+
+ if (!extension) { // The extension has gone.
+ ExtensionHostMsg_GetMessageBundle::WriteReplyParams(
+ reply_msg, extensions::MessageBundle::SubstitutionMap());
+ Send(reply_msg);
+ return;
+ }
+
+ const std::string& default_locale =
+ extensions::LocaleInfo::GetDefaultLocale(extension);
+ if (default_locale.empty()) {
+ // A little optimization: send the answer here to avoid an extra thread hop.
+ std::unique_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
+ extensions::file_util::LoadNonLocalizedMessageBundleSubstitutionMap(
+ extension_id));
+ ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
+ *dictionary_map);
+ Send(reply_msg);
+ return;
+ }
+
+ std::vector<base::FilePath> paths_to_load;
+ paths_to_load.push_back(extension->path());
+
+ auto imports = extensions::SharedModuleInfo::GetImports(extension);
+ // Iterate through the imports in reverse. This will allow later imported
+ // modules to override earlier imported modules, as the list order is
+ // maintained from the definition in manifest.json of the imports.
+ for (auto it = imports.rbegin(); it != imports.rend(); ++it) {
+ const extensions::Extension* imported_extension =
+ extension_set.GetByID(it->extension_id);
+ if (!imported_extension) {
+ NOTREACHED() << "Missing shared module " << it->extension_id;
+ continue;
+ }
+ paths_to_load.push_back(imported_extension->path());
+ }
+
+ // This blocks tab loading. Priority is inherited from the calling context.
+ base::PostTask(
+ FROM_HERE, {base::ThreadPool(), base::MayBlock()},
+ base::BindOnce(&ChromeExtensionMessageFilter::OnGetExtMessageBundleAsync,
+ this, paths_to_load, extension_id, default_locale,
+ reply_msg));
+}
+
+void ChromeExtensionMessageFilter::OnGetExtMessageBundleAsync(
+ const std::vector<base::FilePath>& extension_paths,
+ const std::string& main_extension_id,
+ const std::string& default_locale,
+ IPC::Message* reply_msg) {
+ std::unique_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
+ extensions::file_util::LoadMessageBundleSubstitutionMapFromPaths(
+ extension_paths, main_extension_id, default_locale));
+
+ ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
+ *dictionary_map);
+ Send(reply_msg);
+}
+
+void ChromeExtensionMessageFilter::OnAddAPIActionToExtensionActivityLog(
+ const std::string& extension_id,
+ const ExtensionHostMsg_APIActionOrEvent_Params& params) {
+ if (!ShouldLogExtensionAction(extension_id))
+ return;
+
+ scoped_refptr<extensions::Action> action = new extensions::Action(
+ extension_id, base::Time::Now(), extensions::Action::ACTION_API_CALL,
+ params.api_call);
+ action->set_args(base::WrapUnique(params.arguments.DeepCopy()));
+ if (!params.extra.empty()) {
+ action->mutable_other()->SetString(
+ activity_log_constants::kActionExtra, params.extra);
+ }
+ AddActionToExtensionActivityLog(profile_, activity_log_, action);
+}
+
+void ChromeExtensionMessageFilter::OnAddDOMActionToExtensionActivityLog(
+ const std::string& extension_id,
+ const ExtensionHostMsg_DOMAction_Params& params) {
+ if (!ShouldLogExtensionAction(extension_id))
+ return;
+
+ scoped_refptr<extensions::Action> action = new extensions::Action(
+ extension_id, base::Time::Now(), extensions::Action::ACTION_DOM_ACCESS,
+ params.api_call);
+ action->set_args(base::WrapUnique(params.arguments.DeepCopy()));
+ action->set_page_url(params.url);
+ action->set_page_title(base::UTF16ToUTF8(params.url_title));
+ action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
+ params.call_type);
+ AddActionToExtensionActivityLog(profile_, activity_log_, action);
+}
+
+void ChromeExtensionMessageFilter::OnAddEventToExtensionActivityLog(
+ const std::string& extension_id,
+ const ExtensionHostMsg_APIActionOrEvent_Params& params) {
+ if (!ShouldLogExtensionAction(extension_id))
+ return;
+
+ scoped_refptr<extensions::Action> action = new extensions::Action(
+ extension_id, base::Time::Now(), extensions::Action::ACTION_API_EVENT,
+ params.api_call);
+ action->set_args(base::WrapUnique(params.arguments.DeepCopy()));
+ if (!params.extra.empty()) {
+ action->mutable_other()->SetString(activity_log_constants::kActionExtra,
+ params.extra);
+ }
+ AddActionToExtensionActivityLog(profile_, activity_log_, action);
+}
+
+void ChromeExtensionMessageFilter::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
+ profile_ = NULL;
+ activity_log_ = nullptr;
+}
+
+bool ChromeExtensionMessageFilter::ShouldLogExtensionAction(
+ const std::string& extension_id) const {
+ // We only send these IPCs if activity logging is enabled, but due to race
+ // conditions (e.g. logging gets disabled but the renderer sends the message
+ // before it gets updated), we still need this check here.
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return profile_ &&
+ g_browser_process->profile_manager()->IsValidProfile(profile_) &&
+ activity_log_ && activity_log_->ShouldLog(extension_id);
+}
diff --git a/chromium/chrome/browser/renderer_host/chrome_extension_message_filter.h b/chromium/chrome/browser/renderer_host/chrome_extension_message_filter.h
new file mode 100644
index 00000000000..9f0003bc2d8
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_extension_message_filter.h
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_RENDERER_HOST_CHROME_EXTENSION_MESSAGE_FILTER_H_
+#define CHROME_BROWSER_RENDERER_HOST_CHROME_EXTENSION_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class Profile;
+struct ExtensionHostMsg_APIActionOrEvent_Params;
+struct ExtensionHostMsg_DOMAction_Params;
+
+namespace extensions {
+class ActivityLog;
+class InfoMap;
+struct Message;
+}
+
+// This class filters out incoming Chrome-specific IPC messages from the
+// extension process on the IPC thread.
+class ChromeExtensionMessageFilter : public content::BrowserMessageFilter,
+ public content::NotificationObserver {
+ public:
+ ChromeExtensionMessageFilter(int render_process_id, Profile* profile);
+
+ // content::BrowserMessageFilter methods:
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void OverrideThreadForMessage(const IPC::Message& message,
+ content::BrowserThread::ID* thread) override;
+ void OnDestruct() const override;
+
+ private:
+ friend class content::BrowserThread;
+ friend class base::DeleteHelper<ChromeExtensionMessageFilter>;
+
+ ~ChromeExtensionMessageFilter() override;
+
+ // TODO(jamescook): Move these functions into the extensions module. Ideally
+ // this would be in extensions::ExtensionMessageFilter but that will require
+ // resolving the ActivityLog dependencies on src/chrome.
+ // http://crbug.com/339637
+ void OnGetExtMessageBundle(const std::string& extension_id,
+ IPC::Message* reply_msg);
+ void OnGetExtMessageBundleAsync(
+ const std::vector<base::FilePath>& extension_paths,
+ const std::string& main_extension_id,
+ const std::string& default_locale,
+ IPC::Message* reply_msg);
+ void OnAddAPIActionToExtensionActivityLog(
+ const std::string& extension_id,
+ const ExtensionHostMsg_APIActionOrEvent_Params& params);
+ void OnAddBlockedCallToExtensionActivityLog(
+ const std::string& extension_id,
+ const std::string& function_name);
+ void OnAddDOMActionToExtensionActivityLog(
+ const std::string& extension_id,
+ const ExtensionHostMsg_DOMAction_Params& params);
+ void OnAddEventToExtensionActivityLog(
+ const std::string& extension_id,
+ const ExtensionHostMsg_APIActionOrEvent_Params& params);
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ // Returns true if an action should be logged for the given extension.
+ bool ShouldLogExtensionAction(const std::string& extension_id) const;
+
+ const int render_process_id_;
+
+ // The Profile associated with our renderer process. This should only be
+ // accessed on the UI thread! Furthermore since this class is refcounted it
+ // may outlive |profile_|, so make sure to NULL check if in doubt; async
+ // calls and the like.
+ Profile* profile_;
+
+ // The ActivityLog associated with the given profile. Also only safe to
+ // access on the UI thread, and may be null.
+ extensions::ActivityLog* activity_log_;
+
+ scoped_refptr<extensions::InfoMap> extension_info_map_;
+
+ content::NotificationRegistrar notification_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeExtensionMessageFilter);
+};
+
+#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_EXTENSION_MESSAGE_FILTER_H_
diff --git a/chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.cc b/chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.cc
new file mode 100644
index 00000000000..b2bd3d47ed8
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.cc
@@ -0,0 +1,116 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/renderer_host/chrome_navigation_ui_data.h"
+
+#include "chrome/browser/prerender/prerender_contents.h"
+#include "chrome/browser/prerender/prerender_histograms.h"
+#include "chrome/browser/previews/previews_lite_page_redirect_decider.h"
+#include "content/public/browser/navigation_handle.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ui/base/window_open_disposition.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/constants.h"
+#endif
+
+ChromeNavigationUIData::ChromeNavigationUIData()
+ : disposition_(WindowOpenDisposition::CURRENT_TAB) {}
+
+ChromeNavigationUIData::ChromeNavigationUIData(
+ content::NavigationHandle* navigation_handle)
+ : disposition_(WindowOpenDisposition::CURRENT_TAB) {
+ auto* web_contents = navigation_handle->GetWebContents();
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ int tab_id = extension_misc::kUnknownTabId;
+ int window_id = extension_misc::kUnknownWindowId;
+ // The browser client can be null in unittests.
+ if (extensions::ExtensionsBrowserClient::Get()) {
+ extensions::ExtensionsBrowserClient::Get()->GetTabAndWindowIdForWebContents(
+ web_contents, &tab_id, &window_id);
+ }
+ extension_data_ = std::make_unique<extensions::ExtensionNavigationUIData>(
+ navigation_handle, tab_id, window_id);
+#endif
+
+ auto* prerender_contents =
+ prerender::PrerenderContents::FromWebContents(web_contents);
+ if (prerender_contents) {
+ prerender_mode_ = prerender_contents->prerender_mode();
+ prerender_histogram_prefix_ =
+ prerender::PrerenderHistograms::GetHistogramPrefix(
+ prerender_contents->origin());
+ }
+ data_reduction_proxy_page_id_ =
+ PreviewsLitePageRedirectDecider::GeneratePageIdForWebContents(
+ web_contents);
+}
+
+ChromeNavigationUIData::~ChromeNavigationUIData() {}
+
+// static
+std::unique_ptr<ChromeNavigationUIData>
+ChromeNavigationUIData::CreateForMainFrameNavigation(
+ content::WebContents* web_contents,
+ WindowOpenDisposition disposition,
+ int64_t data_reduction_proxy_page_id) {
+ auto navigation_ui_data = std::make_unique<ChromeNavigationUIData>();
+ navigation_ui_data->disposition_ = disposition;
+ navigation_ui_data->data_reduction_proxy_page_id_ =
+ data_reduction_proxy_page_id;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ int tab_id = extension_misc::kUnknownTabId;
+ int window_id = extension_misc::kUnknownWindowId;
+ // The browser client can be null in unittests.
+ if (extensions::ExtensionsBrowserClient::Get()) {
+ extensions::ExtensionsBrowserClient::Get()->GetTabAndWindowIdForWebContents(
+ web_contents, &tab_id, &window_id);
+ }
+
+ navigation_ui_data->extension_data_ =
+ extensions::ExtensionNavigationUIData::CreateForMainFrameNavigation(
+ web_contents, tab_id, window_id);
+#endif
+
+ return navigation_ui_data;
+}
+
+std::unique_ptr<content::NavigationUIData> ChromeNavigationUIData::Clone() {
+ auto copy = std::make_unique<ChromeNavigationUIData>();
+
+ copy->disposition_ = disposition_;
+ copy->data_reduction_proxy_page_id_ = data_reduction_proxy_page_id_;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (extension_data_)
+ copy->SetExtensionNavigationUIData(extension_data_->DeepCopy());
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ if (offline_page_data_)
+ copy->SetOfflinePageNavigationUIData(offline_page_data_->DeepCopy());
+#endif
+
+ copy->prerender_mode_ = prerender_mode_;
+ copy->prerender_histogram_prefix_ = prerender_histogram_prefix_;
+
+ return std::move(copy);
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+void ChromeNavigationUIData::SetExtensionNavigationUIData(
+ std::unique_ptr<extensions::ExtensionNavigationUIData> extension_data) {
+ extension_data_ = std::move(extension_data);
+}
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+void ChromeNavigationUIData::SetOfflinePageNavigationUIData(
+ std::unique_ptr<offline_pages::OfflinePageNavigationUIData>
+ offline_page_data) {
+ offline_page_data_ = std::move(offline_page_data);
+}
+#endif
diff --git a/chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.h b/chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.h
new file mode 100644
index 00000000000..81b9b8df052
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_navigation_ui_data.h
@@ -0,0 +1,92 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_RENDERER_HOST_CHROME_NAVIGATION_UI_DATA_H_
+#define CHROME_BROWSER_RENDERER_HOST_CHROME_NAVIGATION_UI_DATA_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/common/prerender_types.h"
+#include "components/offline_pages/buildflags/buildflags.h"
+#include "components/offline_pages/core/request_header/offline_page_navigation_ui_data.h"
+#include "content/public/browser/navigation_ui_data.h"
+#include "extensions/browser/extension_navigation_ui_data.h"
+#include "extensions/buildflags/buildflags.h"
+
+namespace content {
+class NavigationHandle;
+}
+
+enum class WindowOpenDisposition;
+
+// Contains data that is passed from the UI thread to the IO thread at the
+// beginning of each navigation. The class is instantiated on the UI thread,
+// then a copy created using Clone is passed to the content::ResourceRequestInfo
+// on the IO thread.
+class ChromeNavigationUIData : public content::NavigationUIData {
+ public:
+ ChromeNavigationUIData();
+ explicit ChromeNavigationUIData(content::NavigationHandle* navigation_handle);
+ ~ChromeNavigationUIData() override;
+
+ static std::unique_ptr<ChromeNavigationUIData> CreateForMainFrameNavigation(
+ content::WebContents* web_contents,
+ WindowOpenDisposition disposition,
+ int64_t data_reduction_proxy_page_id);
+
+ // Creates a new ChromeNavigationUIData that is a deep copy of the original.
+ // Any changes to the original after the clone is created will not be
+ // reflected in the clone. All owned data members are deep copied.
+ std::unique_ptr<content::NavigationUIData> Clone() override;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ void SetExtensionNavigationUIData(
+ std::unique_ptr<extensions::ExtensionNavigationUIData> extension_data);
+
+ extensions::ExtensionNavigationUIData* GetExtensionNavigationUIData() const {
+ return extension_data_.get();
+ }
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ void SetOfflinePageNavigationUIData(
+ std::unique_ptr<offline_pages::OfflinePageNavigationUIData>
+ offline_page_data);
+
+ offline_pages::OfflinePageNavigationUIData* GetOfflinePageNavigationUIData()
+ const {
+ return offline_page_data_.get();
+ }
+#endif
+ WindowOpenDisposition window_open_disposition() const { return disposition_; }
+ prerender::PrerenderMode prerender_mode() const { return prerender_mode_; }
+ const std::string& prerender_histogram_prefix() {
+ return prerender_histogram_prefix_;
+ }
+ uint64_t data_reduction_proxy_page_id() const {
+ return data_reduction_proxy_page_id_;
+ }
+
+ private:
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // Manages the lifetime of optional ExtensionNavigationUIData information.
+ std::unique_ptr<extensions::ExtensionNavigationUIData> extension_data_;
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ // Manages the lifetime of optional OfflinePageNavigationUIData information.
+ std::unique_ptr<offline_pages::OfflinePageNavigationUIData>
+ offline_page_data_;
+#endif
+
+ WindowOpenDisposition disposition_;
+ prerender::PrerenderMode prerender_mode_ = prerender::NO_PRERENDER;
+ std::string prerender_histogram_prefix_;
+ uint64_t data_reduction_proxy_page_id_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeNavigationUIData);
+};
+
+#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_NAVIGATION_UI_DATA_H_
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_message_filter.cc b/chromium/chrome/browser/renderer_host/chrome_render_message_filter.cc
new file mode 100644
index 00000000000..547fbecd203
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_message_filter.cc
@@ -0,0 +1,379 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
+#include "chrome/browser/predictors/loading_predictor.h"
+#include "chrome/browser/predictors/loading_predictor_factory.h"
+#include "chrome/browser/predictors/preconnect_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/render_messages.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "components/network_hints/common/network_hints_common.h"
+#include "components/network_hints/common/network_hints_messages.h"
+#include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
+#include "components/web_cache/browser/web_cache_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/buildflags/buildflags.h"
+#include "net/base/network_isolation_key.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "url/origin.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#endif
+
+using content::BrowserThread;
+
+namespace {
+
+void OnDomStorageAccessedUI(int process_id,
+ int routing_id,
+ const GURL& origin_url,
+ const GURL& top_origin_url,
+ bool local,
+ bool blocked_by_policy) {
+ content::RenderFrameHost* frame =
+ content::RenderFrameHost::FromID(process_id, routing_id);
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(frame);
+
+ if (!web_contents)
+ return;
+
+ TabSpecificContentSettings* tab_settings =
+ TabSpecificContentSettings::FromWebContents(web_contents);
+ if (tab_settings)
+ tab_settings->OnDomStorageAccessed(origin_url, local, blocked_by_policy);
+
+ page_load_metrics::MetricsWebContentsObserver* metrics_observer =
+ page_load_metrics::MetricsWebContentsObserver::FromWebContents(
+ web_contents);
+ if (metrics_observer)
+ metrics_observer->OnDomStorageAccessed(origin_url, top_origin_url, local,
+ blocked_by_policy);
+}
+
+const uint32_t kRenderFilteredMessageClasses[] = {
+ ChromeMsgStart, NetworkHintsMsgStart,
+};
+
+void StartPreconnect(
+ base::WeakPtr<predictors::PreconnectManager> preconnect_manager,
+ int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ bool allow_credentials) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!preconnect_manager)
+ return;
+
+ content::RenderFrameHost* render_frame_host =
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+ if (!render_frame_host)
+ return;
+
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(render_frame_host);
+ if (!web_contents)
+ return;
+
+ net::NetworkIsolationKey network_isolation_key(
+ web_contents->GetMainFrame()->GetLastCommittedOrigin(),
+ render_frame_host->GetLastCommittedOrigin());
+ preconnect_manager->StartPreconnectUrl(url, allow_credentials,
+ network_isolation_key);
+}
+
+} // namespace
+
+ChromeRenderMessageFilter::ChromeRenderMessageFilter(int render_process_id,
+ Profile* profile)
+ : BrowserMessageFilter(kRenderFilteredMessageClasses,
+ base::size(kRenderFilteredMessageClasses)),
+ render_process_id_(render_process_id),
+ preconnect_manager_initialized_(false),
+ cookie_settings_(CookieSettingsFactory::GetForProfile(profile)) {
+ auto* loading_predictor =
+ predictors::LoadingPredictorFactory::GetForProfile(profile);
+ if (loading_predictor && loading_predictor->preconnect_manager()) {
+ preconnect_manager_ = loading_predictor->preconnect_manager()->GetWeakPtr();
+ preconnect_manager_initialized_ = true;
+ }
+}
+
+ChromeRenderMessageFilter::~ChromeRenderMessageFilter() {
+}
+
+bool ChromeRenderMessageFilter::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ChromeRenderMessageFilter, message)
+ IPC_MESSAGE_HANDLER(NetworkHintsMsg_DNSPrefetch, OnDnsPrefetch)
+ IPC_MESSAGE_HANDLER(NetworkHintsMsg_Preconnect, OnPreconnect)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AllowDatabase, OnAllowDatabase)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AllowDOMStorage, OnAllowDOMStorage)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(
+ ChromeViewHostMsg_RequestFileSystemAccessSync,
+ OnRequestFileSystemAccessSync)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RequestFileSystemAccessAsync,
+ OnRequestFileSystemAccessAsync)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AllowIndexedDB, OnAllowIndexedDB)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AllowCacheStorage,
+ OnAllowCacheStorage)
+#if BUILDFLAG(ENABLE_PLUGINS)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_IsCrashReportingEnabled,
+ OnIsCrashReportingEnabled)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+
+void ChromeRenderMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) {
+#if BUILDFLAG(ENABLE_PLUGINS)
+ switch (message.type()) {
+ case ChromeViewHostMsg_IsCrashReportingEnabled::ID:
+ *thread = BrowserThread::UI;
+ break;
+ default:
+ break;
+ }
+#endif
+}
+
+void ChromeRenderMessageFilter::OnDnsPrefetch(
+ const network_hints::LookupRequest& request) {
+ if (preconnect_manager_initialized_) {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&predictors::PreconnectManager::StartPreresolveHosts,
+ preconnect_manager_, request.hostname_list));
+ }
+}
+
+void ChromeRenderMessageFilter::OnPreconnect(int render_frame_id,
+ const GURL& url,
+ bool allow_credentials,
+ int count) {
+ if (count < 1) {
+ LOG(WARNING) << "NetworkHintsMsg_Preconnect IPC with invalid count: "
+ << count;
+ return;
+ }
+
+ if (!url.is_valid() || !url.has_host() || !url.has_scheme() ||
+ !url.SchemeIsHTTPOrHTTPS()) {
+ return;
+ }
+
+ if (!preconnect_manager_initialized_)
+ return;
+
+ // TODO(mmenke): Think about enabling cross-site preconnects, though that
+ // will result in at least some cross-site information leakage.
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&StartPreconnect, preconnect_manager_, render_process_id_,
+ render_frame_id, url, allow_credentials));
+}
+
+void ChromeRenderMessageFilter::OnAllowDatabase(
+ int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool* allowed) {
+ *allowed = cookie_settings_->IsCookieAccessAllowed(
+ origin.GetURL(), site_for_cookies, top_frame_origin);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&TabSpecificContentSettings::WebDatabaseAccessed,
+ render_process_id_, render_frame_id, origin.GetURL(),
+ !*allowed));
+}
+
+void ChromeRenderMessageFilter::OnAllowDOMStorage(
+ int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool local,
+ bool* allowed) {
+ GURL url = origin.GetURL();
+ *allowed = cookie_settings_->IsCookieAccessAllowed(url, site_for_cookies,
+ top_frame_origin);
+ // Record access to DOM storage for potential display in UI.
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&OnDomStorageAccessedUI, render_process_id_,
+ render_frame_id, url, top_frame_origin.GetURL(),
+ local, !*allowed));
+}
+
+void ChromeRenderMessageFilter::OnRequestFileSystemAccessSync(
+ int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ IPC::Message* reply_msg) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::Callback<void(bool)> callback = base::Bind(
+ &ChromeRenderMessageFilter::OnRequestFileSystemAccessSyncResponse,
+ base::WrapRefCounted(this), reply_msg);
+ OnRequestFileSystemAccess(render_frame_id, origin, site_for_cookies,
+ top_frame_origin, callback);
+}
+
+void ChromeRenderMessageFilter::OnRequestFileSystemAccessSyncResponse(
+ IPC::Message* reply_msg,
+ bool allowed) {
+ ChromeViewHostMsg_RequestFileSystemAccessSync::WriteReplyParams(reply_msg,
+ allowed);
+ Send(reply_msg);
+}
+
+void ChromeRenderMessageFilter::OnRequestFileSystemAccessAsync(
+ int render_frame_id,
+ int request_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::Callback<void(bool)> callback = base::Bind(
+ &ChromeRenderMessageFilter::OnRequestFileSystemAccessAsyncResponse,
+ base::WrapRefCounted(this), render_frame_id, request_id);
+ OnRequestFileSystemAccess(render_frame_id, origin, site_for_cookies,
+ top_frame_origin, callback);
+}
+
+void ChromeRenderMessageFilter::OnRequestFileSystemAccessAsyncResponse(
+ int render_frame_id,
+ int request_id,
+ bool allowed) {
+ Send(new ChromeViewMsg_RequestFileSystemAccessAsyncResponse(
+ render_frame_id, request_id, allowed));
+}
+
+void ChromeRenderMessageFilter::OnRequestFileSystemAccess(
+ int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ base::Callback<void(bool)> callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ bool allowed = cookie_settings_->IsCookieAccessAllowed(
+ origin.GetURL(), site_for_cookies, top_frame_origin);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ bool is_web_view_guest = extensions::WebViewRendererState::GetInstance()
+ ->IsGuest(render_process_id_);
+ if (is_web_view_guest) {
+ // Record access to file system for potential display in UI.
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&ChromeRenderMessageFilter::FileSystemAccessedOnUIThread,
+ render_process_id_, render_frame_id, origin.GetURL(),
+ allowed, callback));
+ return;
+ }
+#endif
+ callback.Run(allowed);
+ // Record access to file system for potential display in UI.
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&TabSpecificContentSettings::FileSystemAccessed,
+ render_process_id_, render_frame_id,
+ origin.GetURL(), !allowed));
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+void ChromeRenderMessageFilter::FileSystemAccessedOnUIThread(
+ int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ bool allowed,
+ base::Callback<void(bool)> callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ extensions::WebViewPermissionHelper* web_view_permission_helper =
+ extensions::WebViewPermissionHelper::FromFrameID(
+ render_process_id, render_frame_id);
+ // Between the time the permission request is made and the time it is handled
+ // by the UI thread, the extensions::WebViewPermissionHelper might be gone.
+ if (!web_view_permission_helper)
+ return;
+ web_view_permission_helper->RequestFileSystemPermission(
+ url, allowed,
+ base::BindOnce(&ChromeRenderMessageFilter::FileSystemAccessedResponse,
+ render_process_id, render_frame_id, url, callback));
+}
+
+void ChromeRenderMessageFilter::FileSystemAccessedResponse(
+ int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ base::Callback<void(bool)> callback,
+ bool allowed) {
+ TabSpecificContentSettings::FileSystemAccessed(
+ render_process_id, render_frame_id, url, !allowed);
+ callback.Run(allowed);
+}
+#endif
+
+void ChromeRenderMessageFilter::OnAllowIndexedDB(
+ int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool* allowed) {
+ *allowed = cookie_settings_->IsCookieAccessAllowed(
+ origin.GetURL(), site_for_cookies, top_frame_origin);
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&TabSpecificContentSettings::IndexedDBAccessed,
+ render_process_id_, render_frame_id,
+ origin.GetURL(), !*allowed));
+}
+
+void ChromeRenderMessageFilter::OnAllowCacheStorage(
+ int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool* allowed) {
+ GURL url = origin.GetURL();
+ *allowed = cookie_settings_->IsCookieAccessAllowed(url, site_for_cookies,
+ top_frame_origin);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&TabSpecificContentSettings::CacheStorageAccessed,
+ render_process_id_, render_frame_id, url, !*allowed));
+}
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+void ChromeRenderMessageFilter::OnIsCrashReportingEnabled(bool* enabled) {
+ *enabled = ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled();
+}
+#endif
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_message_filter.h b/chromium/chrome/browser/renderer_host/chrome_render_message_filter.h
new file mode 100644
index 00000000000..bc8a2928282
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_message_filter.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_MESSAGE_FILTER_H_
+#define CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+
+class GURL;
+class Profile;
+
+namespace url {
+class Origin;
+}
+
+namespace predictors {
+class PreconnectManager;
+}
+
+namespace content_settings {
+class CookieSettings;
+}
+
+namespace network_hints {
+struct LookupRequest;
+}
+
+// This class filters out incoming Chrome-specific IPC messages for the renderer
+// process on the IPC thread.
+class ChromeRenderMessageFilter : public content::BrowserMessageFilter {
+ public:
+ ChromeRenderMessageFilter(int render_process_id, Profile* profile);
+
+ // content::BrowserMessageFilter methods:
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void OverrideThreadForMessage(const IPC::Message& message,
+ content::BrowserThread::ID* thread) override;
+
+ private:
+ friend class content::BrowserThread;
+ friend class base::DeleteHelper<ChromeRenderMessageFilter>;
+
+ ~ChromeRenderMessageFilter() override;
+
+ void OnDnsPrefetch(const network_hints::LookupRequest& request);
+ void OnPreconnect(int render_frame_id,
+ const GURL& url,
+ bool allow_credentials,
+ int count);
+
+ void OnAllowDatabase(int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool* allowed);
+ void OnAllowDOMStorage(int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool local,
+ bool* allowed);
+ void OnRequestFileSystemAccessSync(int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ IPC::Message* message);
+ void OnRequestFileSystemAccessAsync(int render_frame_id,
+ int request_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin);
+ void OnRequestFileSystemAccessSyncResponse(IPC::Message* reply_msg,
+ bool allowed);
+ void OnRequestFileSystemAccessAsyncResponse(int render_frame_id,
+ int request_id,
+ bool allowed);
+ void OnRequestFileSystemAccess(int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ base::Callback<void(bool)> callback);
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ static void FileSystemAccessedOnUIThread(int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ bool allowed,
+ base::Callback<void(bool)> callback);
+ static void FileSystemAccessedResponse(int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ base::Callback<void(bool)> callback,
+ bool allowed);
+#endif
+ void OnAllowIndexedDB(int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool* allowed);
+ void OnAllowCacheStorage(int render_frame_id,
+ const url::Origin& origin,
+ const GURL& site_for_cookies,
+ const url::Origin& top_frame_origin,
+ bool* allowed);
+#if BUILDFLAG(ENABLE_PLUGINS)
+ void OnIsCrashReportingEnabled(bool* enabled);
+#endif
+
+ const int render_process_id_;
+
+ // The PreconnectManager for the associated Profile. This must only be
+ // accessed on the UI thread.
+ base::WeakPtr<predictors::PreconnectManager> preconnect_manager_;
+ // Allows to check on the IO thread whether the PreconnectManager was
+ // initialized.
+ bool preconnect_manager_initialized_;
+
+ // Used to look up permissions at database creation time.
+ scoped_refptr<content_settings::CookieSettings> cookie_settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderMessageFilter);
+};
+
+#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_MESSAGE_FILTER_H_
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.h b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.h
new file mode 100644
index 00000000000..86bdd502ce2
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_DELEGATE_H_
+#define CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_DELEGATE_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include <memory>
+
+#include "base/mac/scoped_nsobject.h"
+#import "content/public/browser/render_widget_host_view_mac_delegate.h"
+
+namespace content {
+class RenderWidgetHost;
+}
+
+@class HistorySwiper;
+@interface ChromeRenderWidgetHostViewMacDelegate
+ : NSObject<RenderWidgetHostViewMacDelegate> {
+ @private
+ content::RenderWidgetHost* renderWidgetHost_; // weak
+
+ // Responsible for 2-finger swipes history navigation.
+ base::scoped_nsobject<HistorySwiper> historySwiper_;
+}
+
+- (id)initWithRenderWidgetHost:(content::RenderWidgetHost*)renderWidgetHost;
+
+- (BOOL)handleEvent:(NSEvent*)event;
+- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
+ isValidItem:(BOOL*)valid;
+@end
+
+#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_DELEGATE_H_
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm
new file mode 100644
index 00000000000..d64cd0beecc
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.mm
@@ -0,0 +1,307 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.h"
+
+#include <cmath>
+
+#include "base/auto_reset.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/browser/devtools/devtools_window.h"
+#include "chrome/browser/profiles/profile.h"
+#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/common/url_constants.h"
+#include "components/prefs/pref_service.h"
+#include "components/spellcheck/browser/pref_names.h"
+#include "components/spellcheck/browser/spellcheck_platform.h"
+#include "components/spellcheck/common/spellcheck_panel.mojom.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+
+using content::RenderViewHost;
+
+@interface ChromeRenderWidgetHostViewMacDelegate () <HistorySwiperDelegate>
+@end
+
+@implementation ChromeRenderWidgetHostViewMacDelegate {
+ BOOL resigningFirstResponder_;
+}
+
+- (id)initWithRenderWidgetHost:(content::RenderWidgetHost*)renderWidgetHost {
+ self = [super init];
+ if (self) {
+ renderWidgetHost_ = renderWidgetHost;
+ historySwiper_.reset([[HistorySwiper alloc] initWithDelegate:self]);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [historySwiper_ setDelegate:nil];
+ [super dealloc];
+}
+
+// Handle an event. All incoming key and mouse events flow through this
+// delegate method if implemented. Return YES if the event is fully handled, or
+// NO if normal processing should take place.
+- (BOOL)handleEvent:(NSEvent*)event {
+ return [historySwiper_ handleEvent:event];
+}
+
+// NSWindow events.
+
+- (void)beginGestureWithEvent:(NSEvent*)event {
+ [historySwiper_ beginGestureWithEvent:event];
+}
+
+- (void)endGestureWithEvent:(NSEvent*)event {
+ [historySwiper_ endGestureWithEvent:event];
+}
+
+// This is a low level API which provides touches associated with an event.
+// It is used in conjunction with gestures to determine finger placement
+// on the trackpad.
+- (void)touchesMovedWithEvent:(NSEvent*)event {
+ [historySwiper_ touchesMovedWithEvent:event];
+}
+
+- (void)touchesBeganWithEvent:(NSEvent*)event {
+ [historySwiper_ touchesBeganWithEvent:event];
+}
+
+- (void)touchesCancelledWithEvent:(NSEvent*)event {
+ [historySwiper_ touchesCancelledWithEvent:event];
+}
+
+- (void)touchesEndedWithEvent:(NSEvent*)event {
+ [historySwiper_ touchesEndedWithEvent:event];
+}
+
+// HistorySwiperDelegate methods
+
+- (BOOL)shouldAllowHistorySwiping {
+ if (!renderWidgetHost_)
+ return NO;
+ RenderViewHost* renderViewHost = RenderViewHost::From(renderWidgetHost_);
+ if (!renderViewHost)
+ return NO;
+ content::WebContents* webContents =
+ content::WebContents::FromRenderViewHost(renderViewHost);
+ if (webContents && DevToolsWindow::IsDevToolsWindow(webContents)) {
+ return NO;
+ }
+
+ return YES;
+}
+
+- (NSView*)viewThatWantsHistoryOverlay {
+ return renderWidgetHost_->GetView()->GetNativeView().GetNativeNSView();
+}
+
+- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
+ isValidItem:(BOOL*)valid {
+ SEL action = [item action];
+
+ Profile* profile = Profile::FromBrowserContext(
+ renderWidgetHost_->GetProcess()->GetBrowserContext());
+ DCHECK(profile);
+ PrefService* pref = profile->GetPrefs();
+ const PrefService::Preference* spellCheckEnablePreference =
+ pref->FindPreference(spellcheck::prefs::kSpellCheckEnable);
+ DCHECK(spellCheckEnablePreference);
+ const bool spellCheckUserModifiable =
+ spellCheckEnablePreference->IsUserModifiable();
+
+ // For now, this action is always enabled for render view;
+ // this is sub-optimal.
+ // TODO(suzhe): Plumb the "can*" methods up from WebCore.
+ if (action == @selector(checkSpelling:)) {
+ *valid = spellCheckUserModifiable &&
+ (RenderViewHost::From(renderWidgetHost_) != nullptr);
+ return YES;
+ }
+
+ // TODO(groby): Clarify who sends this and if toggleContinuousSpellChecking:
+ // is still necessary.
+ if (action == @selector(toggleContinuousSpellChecking:)) {
+ if ([(id)item respondsToSelector:@selector(setState:)]) {
+ NSCellStateValue checkedState =
+ pref->GetBoolean(spellcheck::prefs::kSpellCheckEnable) ? NSOnState
+ : NSOffState;
+ [(id)item setState:checkedState];
+ }
+ *valid = spellCheckUserModifiable;
+ return YES;
+ }
+
+ if (action == @selector(showGuessPanel:) ||
+ action == @selector(toggleGrammarChecking:)) {
+ *valid = spellCheckUserModifiable;
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
+ consumed:(BOOL)consumed {
+ [historySwiper_ rendererHandledWheelEvent:event consumed:consumed];
+}
+
+- (void)rendererHandledGestureScrollEvent:(const blink::WebGestureEvent&)event
+ consumed:(BOOL)consumed {
+ [historySwiper_ rendererHandledGestureScrollEvent:event consumed:consumed];
+}
+
+- (void)rendererHandledOverscrollEvent:(const ui::DidOverscrollParams&)params {
+ [historySwiper_ onOverscrolled:params];
+}
+
+// Spellchecking methods
+// The next five methods are implemented here since this class is the first
+// responder for anything in the browser.
+
+// This message is sent whenever the user specifies that a word should be
+// changed from the spellChecker.
+- (void)changeSpelling:(id)sender {
+ // Grab the currently selected word from the spell panel, as this is the word
+ // that we want to replace the selected word in the text with.
+ NSString* newWord = [[sender selectedCell] stringValue];
+ if (newWord != nil) {
+ content::WebContents* webContents =
+ content::WebContents::FromRenderViewHost(
+ RenderViewHost::From(renderWidgetHost_));
+ webContents->ReplaceMisspelling(base::SysNSStringToUTF16(newWord));
+ }
+}
+
+// This message is sent by NSSpellChecker whenever the next word should be
+// advanced to, either after a correction or clicking the "Find Next" button.
+// This isn't documented anywhere useful, like in NSSpellProtocol.h with the
+// other spelling panel methods. This is probably because Apple assumes that the
+// the spelling panel will be used with an NSText, which will automatically
+// catch this and advance to the next word for you. Thanks Apple.
+// This is also called from the Edit -> Spelling -> Check Spelling menu item.
+- (void)checkSpelling:(id)sender {
+ content::WebContents* webContents = content::WebContents::FromRenderViewHost(
+ RenderViewHost::From(renderWidgetHost_));
+ DCHECK(webContents && webContents->GetFocusedFrame());
+
+ mojo::Remote<spellcheck::mojom::SpellCheckPanel>
+ focused_spell_check_panel_client;
+ webContents->GetFocusedFrame()->GetRemoteInterfaces()->GetInterface(
+ focused_spell_check_panel_client.BindNewPipeAndPassReceiver());
+ focused_spell_check_panel_client->AdvanceToNextMisspelling();
+}
+
+// This message is sent by the spelling panel whenever a word is ignored.
+- (void)ignoreSpelling:(id)sender {
+ // Ideally, we would ask the current RenderView for its tag, but that would
+ // mean making a blocking IPC call from the browser. Instead,
+ // spellcheck_platform::CheckSpelling remembers the last tag and
+ // spellcheck_platform::IgnoreWord assumes that is the correct tag.
+ NSString* wordToIgnore = [sender stringValue];
+ if (wordToIgnore != nil)
+ spellcheck_platform::IgnoreWord(base::SysNSStringToUTF16(wordToIgnore));
+}
+
+- (void)showGuessPanel:(id)sender {
+ const bool visible = spellcheck_platform::SpellingPanelVisible();
+
+ content::WebContents* webContents = content::WebContents::FromRenderViewHost(
+ RenderViewHost::From(renderWidgetHost_));
+ DCHECK(webContents && webContents->GetFocusedFrame());
+
+ mojo::Remote<spellcheck::mojom::SpellCheckPanel>
+ focused_spell_check_panel_client;
+ webContents->GetFocusedFrame()->GetRemoteInterfaces()->GetInterface(
+ focused_spell_check_panel_client.BindNewPipeAndPassReceiver());
+ focused_spell_check_panel_client->ToggleSpellPanel(visible);
+}
+
+- (void)toggleContinuousSpellChecking:(id)sender {
+ content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
+ Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
+ DCHECK(profile);
+ PrefService* pref = profile->GetPrefs();
+ pref->SetBoolean(spellcheck::prefs::kSpellCheckEnable,
+ !pref->GetBoolean(spellcheck::prefs::kSpellCheckEnable));
+}
+
+// END Spellchecking methods
+
+// If a dialog is visible, make its window key. See becomeFirstResponder.
+- (void)makeAnyDialogKey {
+ if (const auto* contents = content::WebContents::FromRenderViewHost(
+ RenderViewHost::From(renderWidgetHost_))) {
+ if (const auto* manager =
+ web_modal::WebContentsModalDialogManager::FromWebContents(
+ contents)) {
+ // IsDialogActive() returns true if a dialog exists.
+ if (manager->IsDialogActive()) {
+ manager->FocusTopmostDialog();
+ }
+ }
+ }
+}
+
+// If the RenderWidgetHostView becomes first responder while it has a dialog
+// (say, if the user was interacting with the omnibox and then tabs back into
+// the web contents), then make the dialog window key.
+- (void)becomeFirstResponder {
+ [self makeAnyDialogKey];
+}
+
+// If the RenderWidgetHostView is asked to resign first responder while a child
+// window is key, then the user performed some action which targets the browser
+// window, like clicking the omnibox or typing cmd+L. In that case, the browser
+// window should become key.
+- (void)resignFirstResponder {
+ NSWindow* browserWindow =
+ [renderWidgetHost_->GetView()->GetNativeView().GetNativeNSView() window];
+ DCHECK(browserWindow);
+
+ // If the browser window is already key, there's nothing to do.
+ if (browserWindow.isKeyWindow)
+ return;
+
+ // Otherwise, look for it in the key window's chain of parents.
+ NSWindow* keyWindowOrParent = NSApp.keyWindow;
+ while (keyWindowOrParent && keyWindowOrParent != browserWindow)
+ keyWindowOrParent = keyWindowOrParent.parentWindow;
+
+ // If the browser window isn't among the parents, there's nothing to do.
+ if (keyWindowOrParent != browserWindow)
+ return;
+
+ // Otherwise, temporarily set an ivar so that -windowDidBecomeKey, below,
+ // doesn't immediately make the dialog key.
+ base::AutoReset<BOOL> scoped(&resigningFirstResponder_, YES);
+
+ // …then make the browser window key.
+ [browserWindow makeKeyWindow];
+}
+
+// If the browser window becomes key while the RenderWidgetHostView is first
+// responder, make the dialog key (if there is one).
+- (void)windowDidBecomeKey {
+ if (resigningFirstResponder_)
+ return;
+ NSView* view =
+ renderWidgetHost_->GetView()->GetNativeView().GetNativeNSView();
+ if (view.window.firstResponder == view)
+ [self makeAnyDialogKey];
+}
+
+@end
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h
new file mode 100644
index 00000000000..f925c6109d8
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h
@@ -0,0 +1,235 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_H_
+#define CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_H_
+
+#import <Cocoa/Cocoa.h>
+
+namespace blink {
+class WebGestureEvent;
+class WebMouseWheelEvent;
+}
+
+namespace ui {
+struct DidOverscrollParams;
+}
+
+@class HistorySwiper;
+@protocol HistorySwiperDelegate
+// Return NO from this method if the view/render_widget_host should not
+// allow history swiping.
+- (BOOL)shouldAllowHistorySwiping;
+// The history overlay is added to the view returned from this method.
+- (NSView*)viewThatWantsHistoryOverlay;
+@end
+
+namespace history_swiper {
+enum NavigationDirection {
+ kBackwards = 0,
+ kForwards,
+};
+
+// The states of a state machine that recognizes the history swipe gesture.
+// When a gesture first begins, the state is reset to kPending. The state
+// machine only applies to trackpad gestures. Magic Mouse gestures use a
+// different mechanism.
+enum RecognitionState {
+ // Waiting to see whether the renderer will handle the event with phase
+ // NSEventPhaseBegan. The state machine will also stay in this state if
+ // external conditions prohibit the initialization of history swiping. New
+ // gestures always start in this state.
+ // Events are forwarded to the renderer.
+ kPending,
+ // The gesture looks like the beginning of a history swipe.
+ // Events are forwarded to the renderer.
+ // The history overlay is visible.
+ kPotential,
+ // The gesture is definitely a history swipe.
+ // Events are not forwarded to the renderer.
+ // The history overlay is visible.
+ kTracking,
+ // The history swipe gesture has finished.
+ // Events are not forwarded to the renderer.
+ kCompleted,
+ // The history swipe gesture was cancelled.
+ // Events are forwarded to the renderer.
+ kCancelled,
+};
+} // history_swiper
+
+// History swiping is the feature wherein a horizontal 2-finger swipe of of a
+// trackpad causes the browser to navigate forwards or backwards.
+// Unfortunately, the act of 2-finger swiping is overloaded, and has 3 possible
+// effects. In descending order of priority, the swipe should:
+// 1. Scroll the content on the web page.
+// 2. Perform a history swipe.
+// 3. Rubberband/overscroll the content past the edge of the window.
+// Effects (1) and (3) are managed by the renderer, whereas effect (2) is
+// managed by this class.
+//
+// Touches on the trackpad enter the run loop as NSEvents, grouped into
+// gestures. The phases of NSEvents within a gesture follow a well defined
+// order.
+// 1. NSEventPhaseMayBegin. (exactly 1 event with this phase)
+// 2. NSEventPhaseBegan. (exactly 1 event with this phase)
+// 3. NSEventPhaseMoved. (many events with this phase)
+// 4. NSEventPhaseEnded. (exactly 1 event with this phase)
+// Events with the phase NSEventPhaseCancelled may come in at any time, and
+// generally mean that an entity within the Cocoa framework has consumed the
+// gesture, and wants to "cancel" previous NSEvents that have been passed to
+// this class.
+//
+// The event handling stack in Chrome passes all events to this class, which is
+// given the opportunity to process and consume the event. If the event is not
+// consumed, it is passed to the renderer via IPC. The renderer returns an IPC
+// indicating whether the event was consumed. To prevent spamming the renderer
+// with IPCs, the browser waits for an ACK from the renderer from the previous
+// event before sending the next one. While waiting for an ACK, the browser
+// coalesces NSEvents with the same phase. It is common for dozens of events
+// with the phase NSEventPhaseMoved to be coalesced.
+//
+// It is difficult to determine from the initial events in a gesture whether
+// the gesture was intended to be a history swipe. The loss of information from
+// the coalescing of events with phase NSEventPhaseMoved before they are passed
+// to the renderer is also problematic. The general approach is as follows:
+// 1. Wait for the renderer to return an ACK for the event with phase
+// NSEventPhaseBegan. If that event was not consumed, change the state to
+// kPotential. If the renderer is not certain about whether the event should
+// be consumed, it tries to not consume the event.
+// 2. In the state kPotential, this class will process events and update its
+// internal state machine, but it will also continue to pass events to the
+// renderer. This class tries to aggressively cancel history swiping to make
+// up for the fact that the renderer errs on the side of allowing history
+// swiping to occur.
+// 3. As more events come in, if the gesture continues to appear horizontal,
+// then this class will transition to the state kTracking. Events are
+// consumed, and not passed to the renderer.
+//
+// There are multiple callbacks that provide information about gestures on the
+// trackpad. This class uses two different sets of callbacks.
+// 1. The -[NSView touches*WithEvent:] callbacks provide detailed information
+// about the touches within a gesture. The callbacks happen with more
+// frequency, and have higher accuracy. These callbacks are used to
+// transition between all states except for kPending -> kPotential.
+// 2. The -[NSView scrollWheel:] callback provides less information, but the
+// events are passed to the renderer. This class must process these events so
+// that it can decide whether to consume the events and prevent them from
+// being passed to the renderer. This API is used to transition from kPending
+// -> kPotential.
+//
+// This class is also responsible for handling gestures from a Magic Mouse.
+// Magic Mouse gestures do not generate -touches*WithEvent: callbacks, so this
+// class must use the API -[NSEvent trackSwipeEventWithOptions:...] to track
+// the Magic Mouse gesture. Due to an AppKit bug, once this API is invoked,
+// views no longer reliable receive -touches*WithEvent: callbacks. As such,
+// once this class invokes the -[NSEvent trackSwipeEventWithOptions:...] API,
+// it must continue to use that API, since it no longer receives touch events.
+//
+// TODO(erikchen): Even for users that do not have a Magic Mouse, this class
+// will sometime transition into Magic Mouse mode. This is very undesirable.
+// See http://crbug.com/317161 for more details.
+@class HistoryOverlayController;
+@interface HistorySwiper : NSObject {
+ @private
+ // This controller will exist if and only if the UI is in history swipe mode.
+ HistoryOverlayController* historyOverlay_;
+ // The location of the fingers when the gesture started.
+ NSPoint gestureStartPoint_;
+ // The current location of the fingers in the gesture.
+ NSPoint gestureCurrentPoint_;
+ // The total Y distance moved since the beginning of the gesture.
+ CGFloat gestureTotalY_;
+ // A flag that indicates that there is an ongoing gesture. Only used to
+ // determine whether swipe events are coming from a Magic Mouse.
+ BOOL inGesture_;
+ // A flag that indicates that Chrome is receiving a series of touch events.
+ BOOL receivingTouches_;
+ // Each time a new gesture begins, we must get a new start point.
+ // This ivar determines whether the start point is valid.
+ int gestureStartPointValid_;
+
+ // The user's intended direction with the history swipe. Set during the
+ // transition from kPending -> kPotential.
+ history_swiper::NavigationDirection historySwipeDirection_;
+
+ // Whether the history swipe gesture has its direction inverted. Set during
+ // the transition from kPending -> kPotential.
+ BOOL historySwipeDirectionInverted_;
+
+ // Whether:
+ // 1) When wheel gestures are disabled if the wheel event with phase
+ // NSEventPhaseBegan was consumed by the renderer.
+ // 2) When wheel gestures are enabled and if the first gesture
+ // scroll was not consumed by the renderer.
+ // This variables defaults to NO for new gestures.
+ BOOL firstScrollUnconsumed_;
+
+ // Whether the overscroll has been triggered by renderer and is not disabled
+ // by CSSOverscrollBehavior.
+ BOOL overscrollTriggeredByRenderer_;
+
+ // Whether we have received a gesture scroll begin and are awiting on the
+ // first gesture scroll update to deteremine of the event was consumed by
+ // the renderer.
+ BOOL waitingForFirstGestureScroll_;
+
+ history_swiper::RecognitionState recognitionState_;
+
+ id<HistorySwiperDelegate> delegate_;
+
+ // Cumulative scroll delta since scroll gesture start. Only valid during
+ // scroll gesture handling. Only used to trigger Magic Mouse history swiping.
+ NSSize mouseScrollDelta_;
+}
+
+// Many event types are passed in, but the only one we care about is
+// NSScrollWheel. We look at the phase to determine whether to trigger history
+// swiping
+- (BOOL)handleEvent:(NSEvent*)event;
+- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
+ consumed:(BOOL)consumed;
+- (void)rendererHandledGestureScrollEvent:(const blink::WebGestureEvent&)event
+ consumed:(BOOL)consumed;
+
+// This is called whenever an overscroll event is generated on the renderer
+// side. This is called before InputEventAck. For an overscroll event, the
+// ack_result of "unconsumed" will trigger the swipe navigation. However, the
+// renderer can plumb the value of overscroll_behavior by DidOverscroll,
+// to prevent the swipe navigation before the ack_result arrives.
+// This code makes the assumption that the DidOverscroll() event arrives
+// before InputEventAcks of GestureScrollUpdate/GestureScrollEnd
+// (GestureScrollBegin does not trigger history_swiper) are returned from the
+// renderer. As such, it's safe to just set a flag and prevent history swipe
+// from starting.
+// If this assumption ever becomes false, we will need to update the logic of
+// this method to cancel any ongoing history swipes.
+- (void)onOverscrolled:(const ui::DidOverscrollParams&)params;
+
+// The event passed in is a gesture event, and has touch data associated with
+// the trackpad.
+// Once the method -[NSEvent trackSwipeEventWithOptions:...] is invoked, the
+// methods -touches*WithEvent: are no longer guaranteed to be called for
+// subsequent gestures. http://crbug.com/317161
+- (void)touchesBeganWithEvent:(NSEvent*)event;
+- (void)touchesMovedWithEvent:(NSEvent*)event;
+- (void)touchesCancelledWithEvent:(NSEvent*)event;
+- (void)touchesEndedWithEvent:(NSEvent*)event;
+
+- (void)beginGestureWithEvent:(NSEvent*)event;
+- (void)endGestureWithEvent:(NSEvent*)event;
+
+// Designated initializer.
+- (id)initWithDelegate:(id<HistorySwiperDelegate>)delegate;
+
+@property (nonatomic, assign) id<HistorySwiperDelegate> delegate;
+
+@end
+
+// Exposed only for unit testing, do not call directly.
+@interface HistorySwiper (PrivateExposedForTesting)
++ (void)resetMagicMouseState;
+@end
+
+#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_H_
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.mm b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.mm
new file mode 100644
index 00000000000..acea6635c05
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.mm
@@ -0,0 +1,614 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h"
+
+#import "base/mac/sdk_forward_declarations.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_finder.h"
+#import "chrome/browser/ui/cocoa/history_overlay_controller.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "third_party/blink/public/platform/web_gesture_event.h"
+#include "third_party/blink/public/platform/web_mouse_wheel_event.h"
+#include "ui/events/blink/did_overscroll_params.h"
+
+namespace {
+// The horizontal distance required to cause the browser to perform a history
+// navigation.
+const CGFloat kHistorySwipeThreshold = 0.08;
+
+// The horizontal distance required for this class to start consuming events,
+// which stops the events from reaching the renderer.
+const CGFloat kConsumeEventThreshold = 0.01;
+
+// If there has been sufficient vertical motion, the gesture can't be intended
+// for history swiping.
+const CGFloat kCancelEventVerticalThreshold = 0.24;
+
+// If there has been sufficient vertical motion, and more vertical than
+// horizontal motion, the gesture can't be intended for history swiping.
+const CGFloat kCancelEventVerticalLowerThreshold = 0.01;
+
+// Once we call `[NSEvent trackSwipeEventWithOptions:]`, we cannot reliably
+// expect NSTouch callbacks. We set this variable to YES and ignore NSTouch
+// callbacks.
+BOOL forceMagicMouse = NO;
+} // namespace
+
+@interface HistorySwiper ()
+// Given a touch event, returns the average touch position.
+- (NSPoint)averagePositionInEvent:(NSEvent*)event;
+
+// Updates internal state with the location information from the touch event.
+- (void)updateGestureCurrentPointFromEvent:(NSEvent*)event;
+
+// Updates the state machine with the given touch event.
+// Returns NO if no further processing of the event should happen.
+- (BOOL)processTouchEventForHistorySwiping:(NSEvent*)event;
+
+// Returns whether the wheel event should be consumed, and not passed to the
+// renderer.
+- (BOOL)shouldConsumeWheelEvent:(NSEvent*)event;
+
+// Shows the history swiper overlay.
+- (void)showHistoryOverlay:(history_swiper::NavigationDirection)direction;
+
+// Removes the history swiper overlay.
+- (void)removeHistoryOverlay;
+
+// Returns YES if the event was consumed or NO if it should be passed on to the
+// renderer. If |event| was generated by a Magic Mouse, this method forwards to
+// handleMagicMouseWheelEvent. Otherwise, this method attempts to transition
+// the state machine from kPending -> kPotential. If it performs the
+// transition, it also shows the history overlay. In order for a history swipe
+// gesture to be recognized, the transition must occur.
+//
+// There are 4 types of scroll wheel events:
+// 1. Magic mouse swipe events.
+// These are identical to magic trackpad events, except that there are no
+// -[NSView touches*WithEvent:] callbacks. The only way to accurately
+// track these events is with the `trackSwipeEventWithOptions:` API.
+// scrollingDelta{X,Y} is not accurate over long distances (it is computed
+// using the speed of the swipe, rather than just the distance moved by
+// the fingers).
+// 2. Magic trackpad swipe events.
+// These are the most common history swipe events. The logic of this
+// method is predominantly designed to handle this use case.
+// 3. Traditional mouse scrollwheel events.
+// These should not initiate scrolling. They can be distinguished by the
+// fact that `phase` and `momentumPhase` both return NSEventPhaseNone.
+// 4. Momentum swipe events.
+// After a user finishes a swipe, the system continues to generate
+// artificial callbacks. `phase` returns NSEventPhaseNone, but
+// `momentumPhase` does not. Unfortunately, the callbacks don't work
+// properly (OSX 10.9). Sometimes, the system start sending momentum swipe
+// events instead of trackpad swipe events while the user is still
+// 2-finger swiping.
+- (BOOL)handleScrollWheelEvent:(NSEvent*)event;
+
+// Returns YES if the event was consumed or NO if it should be passed on to the
+// renderer. Attempts to initiate history swiping for Magic Mouse events.
+- (BOOL)handleMagicMouseWheelEvent:(NSEvent*)theEvent;
+@end
+
+@implementation HistorySwiper
+@synthesize delegate = delegate_;
+
+- (id)initWithDelegate:(id<HistorySwiperDelegate>)delegate {
+ self = [super init];
+ if (self) {
+ delegate_ = delegate;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self removeHistoryOverlay];
+ [super dealloc];
+}
+
+- (BOOL)handleEvent:(NSEvent*)event {
+ if ([event type] != NSScrollWheel)
+ return NO;
+
+ return [self handleScrollWheelEvent:event];
+}
+
+- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
+ consumed:(BOOL)consumed {
+ if (event.phase != NSEventPhaseBegan)
+ return;
+ firstScrollUnconsumed_ = !consumed;
+}
+
+- (void)rendererHandledGestureScrollEvent:(const blink::WebGestureEvent&)event
+ consumed:(BOOL)consumed {
+ switch (event.GetType()) {
+ case blink::WebInputEvent::kGestureScrollBegin:
+ if (event.data.scroll_begin.synthetic ||
+ event.data.scroll_begin.inertial_phase ==
+ blink::WebGestureEvent::InertialPhaseState::kMomentum) {
+ return;
+ }
+ // GestureScrollBegin and GestureScrollEnd events are created to wrap
+ // individual resent GestureScrollUpdates from a plugin. Hence these
+ // should not be used to indicate the beginning/end of the swipe gesture.
+ // TODO(mcnee): When we remove BrowserPlugin, delete this code.
+ // See crbug.com/533069
+ if (event.resending_plugin_id != -1) {
+ return;
+ }
+ waitingForFirstGestureScroll_ = YES;
+ break;
+ case blink::WebInputEvent::kGestureScrollUpdate:
+ if (waitingForFirstGestureScroll_)
+ firstScrollUnconsumed_ = !consumed;
+ waitingForFirstGestureScroll_ = NO;
+ break;
+ default:
+ break;
+ }
+}
+
+- (void)onOverscrolled:(const ui::DidOverscrollParams&)params {
+ overscrollTriggeredByRenderer_ =
+ params.overscroll_behavior.x ==
+ cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto;
+}
+
+- (void)beginGestureWithEvent:(NSEvent*)event {
+ inGesture_ = YES;
+
+ // Reset state pertaining to Magic Mouse swipe gestures.
+ mouseScrollDelta_ = NSZeroSize;
+}
+
+- (void)endGestureWithEvent:(NSEvent*)event {
+ inGesture_ = NO;
+}
+
+// This method assumes that there is at least 1 touch in the event.
+// The event must correpond to a valid gesture, or else
+// [NSEvent touchesMatchingPhase:inView:] will fail.
+- (NSPoint)averagePositionInEvent:(NSEvent*)event {
+ NSPoint position = NSMakePoint(0,0);
+ int pointCount = 0;
+ for (NSTouch* touch in
+ [event touchesMatchingPhase:NSTouchPhaseAny inView:nil]) {
+ position.x += touch.normalizedPosition.x;
+ position.y += touch.normalizedPosition.y;
+ ++pointCount;
+ }
+
+ if (pointCount > 1) {
+ position.x /= pointCount;
+ position.y /= pointCount;
+ }
+
+ return position;
+}
+
+- (void)updateGestureCurrentPointFromEvent:(NSEvent*)event {
+ NSPoint averagePosition = [self averagePositionInEvent:event];
+
+ // If the start point is valid, then so is the current point.
+ if (gestureStartPointValid_)
+ gestureTotalY_ += fabs(averagePosition.y - gestureCurrentPoint_.y);
+
+ // Update the current point of the gesture.
+ gestureCurrentPoint_ = averagePosition;
+
+ // If the gesture doesn't have a start point, set one.
+ if (!gestureStartPointValid_) {
+ gestureStartPointValid_ = YES;
+ gestureStartPoint_ = gestureCurrentPoint_;
+ }
+}
+
+// Ideally, we'd set the gestureStartPoint_ here, but this method only gets
+// called before the gesture begins, and the touches in an event are only
+// available after the gesture begins.
+- (void)touchesBeganWithEvent:(NSEvent*)event {
+ receivingTouches_ = YES;
+
+ // Reset state pertaining to previous trackpad gestures.
+ gestureStartPointValid_ = NO;
+ gestureTotalY_ = 0;
+ firstScrollUnconsumed_ = NO;
+ overscrollTriggeredByRenderer_ = NO;
+ waitingForFirstGestureScroll_ = NO;
+ recognitionState_ = history_swiper::kPending;
+}
+
+- (void)touchesMovedWithEvent:(NSEvent*)event {
+ [self processTouchEventForHistorySwiping:event];
+}
+
+- (void)touchesCancelledWithEvent:(NSEvent*)event {
+ receivingTouches_ = NO;
+
+ if (![self processTouchEventForHistorySwiping:event])
+ return;
+
+ [self cancelHistorySwipe];
+}
+
+- (void)touchesEndedWithEvent:(NSEvent*)event {
+ receivingTouches_ = NO;
+ if (![self processTouchEventForHistorySwiping:event])
+ return;
+
+ if (historyOverlay_) {
+ BOOL finished = [self updateProgressBar];
+
+ // If the gesture was completed, perform a navigation.
+ if (finished)
+ [self navigateBrowserInDirection:historySwipeDirection_];
+
+ [self removeHistoryOverlay];
+
+ // The gesture was completed.
+ recognitionState_ = history_swiper::kCompleted;
+ }
+}
+
+- (BOOL)processTouchEventForHistorySwiping:(NSEvent*)event {
+ NSEventType type = [event type];
+ if (type != NSEventTypeBeginGesture && type != NSEventTypeEndGesture &&
+ type != NSEventTypeGesture) {
+ return NO;
+ }
+
+ switch (recognitionState_) {
+ case history_swiper::kCancelled:
+ case history_swiper::kCompleted:
+ return NO;
+ case history_swiper::kPending:
+ case history_swiper::kPotential:
+ case history_swiper::kTracking:
+ break;
+ }
+
+ [self updateGestureCurrentPointFromEvent:event];
+
+ // Consider cancelling the history swipe gesture.
+ if ([self shouldCancelHorizontalSwipeWithCurrentPoint:gestureCurrentPoint_
+ startPoint:gestureStartPoint_]) {
+ [self cancelHistorySwipe];
+ return NO;
+ }
+
+ // Don't do any more processing if the state machine is in the pending state.
+ if (recognitionState_ == history_swiper::kPending)
+ return NO;
+
+ if (recognitionState_ == history_swiper::kPotential) {
+ // The user is in the process of doing history swiping. If the history
+ // swipe has progressed sufficiently far, stop sending events to the
+ // renderer.
+ BOOL sufficientlyFar = fabs(gestureCurrentPoint_.x - gestureStartPoint_.x) >
+ kConsumeEventThreshold;
+ if (sufficientlyFar)
+ recognitionState_ = history_swiper::kTracking;
+ }
+
+ if (historyOverlay_)
+ [self updateProgressBar];
+ return YES;
+}
+
+// Consider cancelling the horizontal swipe if the user was intending a
+// vertical swipe.
+- (BOOL)shouldCancelHorizontalSwipeWithCurrentPoint:(NSPoint)currentPoint
+ startPoint:(NSPoint)startPoint {
+ CGFloat yDelta = gestureTotalY_;
+ CGFloat xDelta = fabs(currentPoint.x - startPoint.x);
+
+ // The gesture is pretty clearly more vertical than horizontal.
+ if (yDelta > 2 * xDelta)
+ return YES;
+
+ // There's been more vertical distance than horizontal distance.
+ if (yDelta * 1.3 > xDelta && yDelta > kCancelEventVerticalLowerThreshold)
+ return YES;
+
+ // There's been a lot of vertical distance.
+ if (yDelta > kCancelEventVerticalThreshold)
+ return YES;
+
+ return NO;
+}
+
+- (void)cancelHistorySwipe {
+ [self removeHistoryOverlay];
+ recognitionState_ = history_swiper::kCancelled;
+}
+
+- (void)removeHistoryOverlay {
+ [historyOverlay_ dismiss];
+ [historyOverlay_ release];
+ historyOverlay_ = nil;
+}
+
+// Returns whether the progress bar has been 100% filled.
+- (BOOL)updateProgressBar {
+ NSPoint currentPoint = gestureCurrentPoint_;
+ NSPoint startPoint = gestureStartPoint_;
+
+ float progress = 0;
+ BOOL finished = NO;
+
+ progress = (currentPoint.x - startPoint.x) / kHistorySwipeThreshold;
+ // If the swipe is a backwards gesture, we need to invert progress.
+ if (historySwipeDirection_ == history_swiper::kBackwards)
+ progress *= -1;
+
+ // If the user has directions reversed, we need to invert progress.
+ if (historySwipeDirectionInverted_)
+ progress *= -1;
+
+ if (progress >= 1.0)
+ finished = YES;
+
+ // Progress can't be less than 0 or greater than 1.
+ progress = MAX(0.0, progress);
+ progress = MIN(1.0, progress);
+
+ [historyOverlay_ setProgress:progress finished:finished];
+
+ return finished;
+}
+
+- (BOOL)isEventDirectionInverted:(NSEvent*)event {
+ if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)])
+ return [event isDirectionInvertedFromDevice];
+ return NO;
+}
+
+- (void)showHistoryOverlay:(history_swiper::NavigationDirection)direction {
+ // We cannot make any assumptions about the current state of the
+ // historyOverlay_, since users may attempt to use multiple gesture input
+ // devices simultaneously, which confuses Cocoa.
+ [self removeHistoryOverlay];
+
+ HistoryOverlayController* historyOverlay = [[HistoryOverlayController alloc]
+ initForMode:(direction == history_swiper::kForwards)
+ ? kHistoryOverlayModeForward
+ : kHistoryOverlayModeBack];
+ [historyOverlay showPanelForView:[delegate_ viewThatWantsHistoryOverlay]];
+ historyOverlay_ = historyOverlay;
+}
+
+- (BOOL)systemSettingsAllowHistorySwiping:(NSEvent*)event {
+ if ([NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return [NSEvent isSwipeTrackingFromScrollEventsEnabled];
+ return NO;
+}
+
+- (void)navigateBrowserInDirection:
+ (history_swiper::NavigationDirection)direction {
+ Browser* browser = chrome::FindBrowserWithWindow(
+ historyOverlay_.view.window);
+ if (browser) {
+ if (direction == history_swiper::kForwards)
+ chrome::GoForward(browser, WindowOpenDisposition::CURRENT_TAB);
+ else
+ chrome::GoBack(browser, WindowOpenDisposition::CURRENT_TAB);
+ }
+}
+
+- (BOOL)browserCanNavigateInDirection:
+ (history_swiper::NavigationDirection)direction
+ event:(NSEvent*)event {
+ Browser* browser = chrome::FindBrowserWithWindow([event window]);
+ if (!browser)
+ return NO;
+
+ if (direction == history_swiper::kForwards) {
+ return chrome::CanGoForward(browser);
+ } else {
+ return chrome::CanGoBack(browser);
+ }
+}
+
+- (BOOL)handleMagicMouseWheelEvent:(NSEvent*)theEvent {
+ // The 'trackSwipeEventWithOptions:' api doesn't handle momentum events.
+ if ([theEvent phase] == NSEventPhaseNone)
+ return NO;
+
+ mouseScrollDelta_.width += [theEvent scrollingDeltaX];
+ mouseScrollDelta_.height += [theEvent scrollingDeltaY];
+
+ BOOL isHorizontalGesture =
+ std::abs(mouseScrollDelta_.width) > std::abs(mouseScrollDelta_.height);
+ if (!isHorizontalGesture)
+ return NO;
+
+ BOOL isRightScroll = [theEvent scrollingDeltaX] < 0;
+ history_swiper::NavigationDirection direction =
+ isRightScroll ? history_swiper::kForwards : history_swiper::kBackwards;
+ BOOL browserCanMove =
+ [self browserCanNavigateInDirection:direction event:theEvent];
+ if (!browserCanMove)
+ return NO;
+
+ [self initiateMagicMouseHistorySwipe:isRightScroll event:theEvent];
+ return YES;
+}
+
+- (void)initiateMagicMouseHistorySwipe:(BOOL)isRightScroll
+ event:(NSEvent*)event {
+ // Released by the tracking handler once the gesture is complete.
+ __block HistoryOverlayController* historyOverlay =
+ [[HistoryOverlayController alloc]
+ initForMode:isRightScroll ? kHistoryOverlayModeForward
+ : kHistoryOverlayModeBack];
+
+ // The way this API works: gestureAmount is between -1 and 1 (float). If
+ // the user does the gesture for more than about 30% (i.e. < -0.3 or >
+ // 0.3) and then lets go, it is accepted, we get a NSEventPhaseEnded,
+ // and after that the block is called with amounts animating towards 1
+ // (or -1, depending on the direction). If the user lets go below that
+ // threshold, we get NSEventPhaseCancelled, and the amount animates
+ // toward 0. When gestureAmount has reaches its final value, i.e. the
+ // track animation is done, the handler is called with |isComplete| set
+ // to |YES|.
+ // When starting a backwards navigation gesture (swipe from left to right,
+ // gestureAmount will go from 0 to 1), if the user swipes from left to
+ // right and then quickly back to the left, this call can send
+ // NSEventPhaseEnded and then animate to gestureAmount of -1. For a
+ // picture viewer, that makes sense, but for back/forward navigation users
+ // find it confusing. There are two ways to prevent this:
+ // 1. Set Options to NSEventSwipeTrackingLockDirection. This way,
+ // gestureAmount will always stay > 0.
+ // 2. Pass min:0 max:1 (instead of min:-1 max:1). This way, gestureAmount
+ // will become less than 0, but on the quick swipe back to the left,
+ // NSEventPhaseCancelled is sent instead.
+ // The current UI looks nicer with (1) so that swiping the opposite
+ // direction after the initial swipe doesn't cause the shield to move
+ // in the wrong direction.
+ forceMagicMouse = YES;
+ [event trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection
+ dampenAmountThresholdMin:-1
+ max:1
+ usingHandler:^(CGFloat gestureAmount,
+ NSEventPhase phase,
+ BOOL isComplete,
+ BOOL* stop) {
+ if (phase == NSEventPhaseBegan) {
+ [historyOverlay
+ showPanelForView:[delegate_ viewThatWantsHistoryOverlay]];
+ return;
+ }
+
+ BOOL ended = phase == NSEventPhaseEnded;
+
+ // Dismiss the panel before navigation for immediate visual feedback.
+ CGFloat progress = std::abs(gestureAmount) / 0.3;
+ BOOL finished = progress >= 1.0;
+ progress = MAX(0.0, progress);
+ progress = MIN(1.0, progress);
+ [historyOverlay setProgress:progress finished:finished];
+
+ // |gestureAmount| obeys -[NSEvent isDirectionInvertedFromDevice]
+ // automatically.
+ Browser* browser =
+ chrome::FindBrowserWithWindow(historyOverlay.view.window);
+ if (ended && browser) {
+ if (isRightScroll)
+ chrome::GoForward(browser, WindowOpenDisposition::CURRENT_TAB);
+ else
+ chrome::GoBack(browser, WindowOpenDisposition::CURRENT_TAB);
+ }
+
+ if (ended || isComplete) {
+ [historyOverlay dismiss];
+ [historyOverlay release];
+ historyOverlay = nil;
+ }
+ }];
+}
+
+- (BOOL)handleScrollWheelEvent:(NSEvent*)theEvent {
+ if (![theEvent respondsToSelector:@selector(phase)])
+ return NO;
+
+ // The only events that this class consumes have type NSEventPhaseChanged.
+ // This simultaneously weeds our regular mouse wheel scroll events, and
+ // gesture events with incorrect phase.
+ if ([theEvent phase] != NSEventPhaseChanged &&
+ [theEvent momentumPhase] != NSEventPhaseChanged) {
+ return NO;
+ }
+
+ // We've already processed this gesture.
+ if (recognitionState_ != history_swiper::kPending) {
+ return [self shouldConsumeWheelEvent:theEvent];
+ }
+
+ // Don't allow momentum events to start history swiping.
+ if ([theEvent momentumPhase] != NSEventPhaseNone)
+ return NO;
+
+ BOOL systemSettingsValid = [self systemSettingsAllowHistorySwiping:theEvent];
+ if (!systemSettingsValid)
+ return NO;
+
+ if (![delegate_ shouldAllowHistorySwiping])
+ return NO;
+
+ // Don't enable history swiping until the renderer has decided to not consume
+ // the event with phase NSEventPhaseBegan.
+ if (!firstScrollUnconsumed_)
+ return NO;
+
+ // History swiping should be prevented if the renderer hasn't triggered it.
+ if (!overscrollTriggeredByRenderer_)
+ return NO;
+
+ // Magic mouse and touchpad swipe events are identical except magic mouse
+ // events do not generate NSTouch callbacks. Since we rely on NSTouch
+ // callbacks to perform history swiping, magic mouse swipe events use an
+ // entirely different set of logic.
+ if ((inGesture_ && !receivingTouches_) || forceMagicMouse)
+ return [self handleMagicMouseWheelEvent:theEvent];
+
+ // The scrollWheel: callback is only relevant if it happens while the user is
+ // still actively using the touchpad.
+ if (!receivingTouches_)
+ return NO;
+
+ // TODO(erikchen): Ideally, the direction of history swiping should not be
+ // determined this early in a gesture, when it's unclear what the user is
+ // intending to do. Since it is determined this early, make sure that there
+ // is at least a minimal amount of horizontal motion.
+ CGFloat xDelta = gestureCurrentPoint_.x - gestureStartPoint_.x;
+ if (fabs(xDelta) < 0.001)
+ return NO;
+
+ BOOL isRightScroll = xDelta > 0;
+ BOOL inverted = [self isEventDirectionInverted:theEvent];
+ if (inverted)
+ isRightScroll = !isRightScroll;
+
+ history_swiper::NavigationDirection direction =
+ isRightScroll ? history_swiper::kForwards : history_swiper::kBackwards;
+ BOOL browserCanMove =
+ [self browserCanNavigateInDirection:direction event:theEvent];
+ if (!browserCanMove)
+ return NO;
+
+ historySwipeDirection_ = direction;
+ historySwipeDirectionInverted_ = [self isEventDirectionInverted:theEvent];
+ recognitionState_ = history_swiper::kPotential;
+ [self showHistoryOverlay:direction];
+ return [self shouldConsumeWheelEvent:theEvent];
+}
+
+- (BOOL)shouldConsumeWheelEvent:(NSEvent*)event {
+ switch (recognitionState_) {
+ case history_swiper::kPending:
+ case history_swiper::kCancelled:
+ return NO;
+ case history_swiper::kTracking:
+ case history_swiper::kCompleted:
+ return YES;
+ case history_swiper::kPotential:
+ // It is unclear whether the user is attempting to perform history
+ // swiping. If the event has a vertical component, send it on to the
+ // renderer.
+ return [event scrollingDeltaY] == 0;
+ }
+}
+
+@end
+
+@implementation HistorySwiper (PrivateExposedForTesting)
++ (void)resetMagicMouseState {
+ forceMagicMouse = NO;
+}
+@end
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
new file mode 100644
index 00000000000..6b92c17c894
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
@@ -0,0 +1,821 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <Cocoa/Cocoa.h>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#import "base/mac/sdk_forward_declarations.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "third_party/blink/public/platform/web_mouse_wheel_event.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/ocmock_extensions.h"
+#include "ui/events/base_event_utils.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Refers to how the event is going to be sent to the NSView. There are 3
+// relevant sets of APIs. The current code relies on all three sets of APIs.
+// There is significant information duplication between the three sets of APIs,
+// but the timing of the callbacks of the three APIs differ significantly.
+enum Deployment {
+ // -[NSView touchesBeganWithEvent:]
+ DEPLOYMENT_TOUCHES_BEGAN,
+ // -[NSView touchesMovedWithEvent:]
+ DEPLOYMENT_TOUCHES_MOVED,
+ // -[NSView touchesEndedWithEvent:]
+ DEPLOYMENT_TOUCHES_ENDED,
+ // -[NSView scrollWheel:]
+ DEPLOYMENT_SCROLL_WHEEL,
+ // -[NSView beginGestureWithEvent:]
+ DEPLOYMENT_GESTURE_BEGIN,
+ // -[NSView endGestureWithEvent:]
+ DEPLOYMENT_GESTURE_END,
+};
+
+} // namespace
+
+// A wrapper object for events queued for replay.
+@interface QueuedEvent : NSObject {
+ BOOL _runMessageLoop;
+ Deployment _deployment;
+ NSEvent* _event;
+}
+// Whether the message loop should be run after this event has been replayed.
+@property(nonatomic, assign) BOOL runMessageLoop;
+// How this event should be replayed.
+@property(nonatomic, assign) Deployment deployment;
+// The event to be replayed.
+@property(nonatomic, retain) NSEvent* event;
+@end
+
+@implementation QueuedEvent
+@synthesize deployment = _deployment;
+@synthesize event = _event;
+@synthesize runMessageLoop = _runMessageLoop;
+- (void)dealloc {
+ [_event release];
+ [super dealloc];
+}
+@end
+
+class ChromeRenderWidgetHostViewMacHistorySwiperTest
+ : public InProcessBrowserTest {
+ public:
+ ChromeRenderWidgetHostViewMacHistorySwiperTest()
+ : event_queue_(), touch_(CGPointMake(0, 0)) {
+ const base::FilePath base_path(FILE_PATH_LITERAL("scroll"));
+ url1_ = ui_test_utils::GetTestUrl(
+ base_path, base::FilePath(FILE_PATH_LITERAL("text.html")));
+ url2_ = ui_test_utils::GetTestUrl(
+ base_path, base::FilePath(FILE_PATH_LITERAL("blank.html")));
+ url_iframe_ = ui_test_utils::GetTestUrl(
+ base_path, base::FilePath(FILE_PATH_LITERAL("iframe.html")));
+ }
+
+ void SetUpOnMainThread() override {
+ event_queue_.reset([[NSMutableArray alloc] init]);
+ touch_ = CGPointMake(0.5, 0.5);
+
+ // Ensure that the navigation stack is not empty.
+ ui_test_utils::NavigateToURL(browser(), url1_);
+ ASSERT_EQ(url1_, GetWebContents()->GetURL());
+ ui_test_utils::NavigateToURL(browser(), url2_);
+ ASSERT_EQ(url2_, GetWebContents()->GetURL());
+
+ mock_clock_.Advance(base::TimeDelta::FromMilliseconds(100));
+ ui::SetEventTickClockForTesting(&mock_clock_);
+ }
+
+ void TearDownOnMainThread() override {
+ ui::SetEventTickClockForTesting(nullptr);
+ event_queue_.reset();
+ }
+
+ protected:
+ // Returns the active web contents.
+ content::WebContents* GetWebContents() {
+ return browser()->tab_strip_model()->GetActiveWebContents();
+ }
+
+ // Returns the value of |query| from Javascript as an int.
+ int GetScriptIntValue(const std::string& query) {
+ int value = 0;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+ GetWebContents(),
+ "domAutomationController.send(" + query + ")",
+ &value));
+ return value;
+ }
+
+ // Returns the vertical scroll offset of the current page.
+ int GetScrollTop() {
+ return GetScriptIntValue("document.body.scrollTop");
+ }
+
+ bool IsHistorySwipingSupported() {
+ // These tests require 10.7+ APIs.
+ return [NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)];
+ }
+
+ // Create mock events --------------------------------------------------------
+
+ // Create a gesture event with no useful data. Used to create Begin and End
+ // events.
+ id MockGestureEvent(NSEventType type) {
+ id event = [OCMockObject mockForClass:[NSEvent class]];
+ NSPoint locationInWindow = NSMakePoint(0, 0);
+ CGFloat deltaX = 0;
+ CGFloat deltaY = 0;
+ NSTimeInterval timestamp = 0;
+ NSUInteger modifierFlags = 0;
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
+ return event;
+ }
+
+ // Creates a mock scroll wheel event that is backed by a real CGEvent.
+ id MockScrollWheelEvent(NSPoint delta, NSEventType type) {
+ CGEventRef cg_event =
+ CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 2, 0, 0);
+ CGEventSetIntegerValueField(cg_event, kCGScrollWheelEventIsContinuous, 1);
+ CGEventSetIntegerValueField(
+ cg_event, kCGScrollWheelEventPointDeltaAxis2, delta.x);
+ CGEventSetIntegerValueField(
+ cg_event, kCGScrollWheelEventPointDeltaAxis1, delta.y);
+ NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
+ CFRelease(cg_event);
+
+ id mock_event = [OCMockObject partialMockForObject:event];
+ [[[mock_event stub] andReturnBool:NO] isDirectionInvertedFromDevice];
+ NSTimeInterval timestamp = 0;
+ [(NSEvent*)[[mock_event stub]
+ andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
+ [(NSEvent*)[[mock_event stub] andReturnValue:OCMOCK_VALUE(type)] type];
+
+ // We need to assign a locationInWindow for the event so that the wheel
+ // event happens inside the page.
+ NSPoint locationInWindow = NSMakePoint(400, 400);
+ [(NSEvent*)[[mock_event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)]
+ locationInWindow];
+
+ return mock_event;
+ }
+
+ // Returns a scroll wheel event with the given parameters.
+ id ScrollWheelEventWithPhase(NSEventPhase phase,
+ NSEventPhase momentum_phase,
+ CGFloat scrolling_delta_x,
+ CGFloat scrolling_delta_y) {
+ id event = MockScrollWheelEvent(
+ NSMakePoint(scrolling_delta_x, scrolling_delta_y), NSScrollWheel);
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(momentum_phase)] momentumPhase];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(scrolling_delta_x)] scrollingDeltaX];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(scrolling_delta_y)] scrollingDeltaY];
+ NSUInteger modifierFlags = 0;
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
+ NSTimeInterval timestamp = 0;
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
+
+ NSView* view = GetWebContents()
+ ->GetRenderViewHost()
+ ->GetWidget()
+ ->GetView()
+ ->GetNativeView()
+ .GetNativeNSView();
+ NSWindow* window = [view window];
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(window)] window];
+
+ return event;
+ }
+
+ // Queue events for playback -------------------------------------------------
+
+ void QueueEvent(id event, Deployment deployment, BOOL run_message_loop) {
+ QueuedEvent* queued_event = [[[QueuedEvent alloc] init] autorelease];
+ queued_event.event = event;
+ queued_event.deployment = deployment;
+ queued_event.runMessageLoop = run_message_loop;
+ [event_queue_ addObject:queued_event];
+ }
+
+ // Queues a trackpad scroll event (e.g. [NSView scrollWheel:])
+ void QueueTrackpadScroll(int dx,
+ int dy,
+ NSEventPhase phase,
+ BOOL run_message_loop) {
+ id event = ScrollWheelEventWithPhase(phase, NSEventPhaseNone, dx, dy);
+ QueueEvent(event, DEPLOYMENT_SCROLL_WHEEL, run_message_loop);
+ }
+
+ // Queues a gesture begin event (e.g. [NSView gestureDidBegin:])
+ void QueueGestureBegin() {
+ QueueEvent(MockGestureEvent(NSEventTypeBeginGesture),
+ DEPLOYMENT_GESTURE_BEGIN, NO);
+ }
+
+ // Queues a gesture end event (e.g. [NSView gestureDidEnd:])
+ void QueueGestureEnd() {
+ QueueEvent(MockGestureEvent(NSEventTypeEndGesture),
+ DEPLOYMENT_GESTURE_BEGIN, NO);
+ }
+
+ // Queues a touch event with absolute coordinates |x| and |y|.
+ void QueueTouch(CGFloat x,
+ CGFloat y,
+ Deployment deployment,
+ NSEventType type,
+ short subtype,
+ BOOL run_message_loop) {
+ id event = [OCMockObject mockForClass:[NSEvent class]];
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(subtype)] subtype];
+
+ id mock_touch = [OCMockObject mockForClass:[NSTouch class]];
+ [[[mock_touch stub] andReturnNSPoint:NSMakePoint(x, y)] normalizedPosition];
+ NSArray* touches = @[ mock_touch ];
+ [[[event stub] andReturn:touches] touchesMatchingPhase:NSTouchPhaseAny
+ inView:[OCMArg any]];
+ [[[event stub] andReturnBool:NO] isDirectionInvertedFromDevice];
+ NSTimeInterval timestamp = 0;
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
+
+ QueueEvent(event, deployment, run_message_loop);
+ }
+
+ // Convenience methods for event queuing -------------------------------------
+
+ // Trackpad scroll events are roughly related to touch events. Given a
+ // trackpad scroll delta, approximate the change to the touch event.
+ void UpdateTouchLocationFromTrackpadScroll(int dx, int dy) {
+ touch_.x -= dx * 0.001;
+ touch_.y -= dy * 0.001;
+ }
+
+ // Queue the typical events at the beginning of a new swipe gesture. The
+ // ordering and values were determined by recording real swipe events.
+ void QueueBeginningEvents(int dx, int dy) {
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_BEGAN, NSEventTypeGesture, NSMouseEventSubtype, NO);
+ QueueTrackpadScroll(0, 0, NSEventPhaseMayBegin, YES);
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture, NSMouseEventSubtype, NO);
+
+ QueueTrackpadScroll(dx, dy, NSEventPhaseBegan, NO);
+ QueueGestureBegin();
+ QueueTouch(DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeBeginGesture,
+ NSTouchEventSubtype,
+ NO);
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture, NSTouchEventSubtype, YES);
+ UpdateTouchLocationFromTrackpadScroll(dx, dy);
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture, NSTouchEventSubtype, NO);
+ }
+
+ // Queue the typical events at the end of a new swipe gesture. The ordering
+ // and values were determined by recording real swipe events.
+ void QueueEndEvents() {
+ QueueTouch(DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeEndGesture,
+ NSMouseEventSubtype,
+ NO);
+ QueueTouch(DEPLOYMENT_TOUCHES_ENDED,
+ NSEventTypeEndGesture,
+ NSMouseEventSubtype,
+ NO);
+ QueueGestureEnd();
+ QueueTrackpadScroll(0, 0, NSEventPhaseEnded, YES);
+ }
+
+ // Queues a trackpad scroll movement and a touch movement event.
+ void QueueScrollAndTouchMoved(int dx, int dy) {
+ QueueTrackpadScroll(dx, dy, NSEventPhaseChanged, NO);
+ UpdateTouchLocationFromTrackpadScroll(dx, dy);
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture, NSTouchEventSubtype, YES);
+ }
+
+ // Queues a touch event with the stored touch coordinates.
+ void QueueTouch(Deployment deployment,
+ NSEventType type,
+ short subtype,
+ BOOL run_message_loop) {
+ QueueTouch(touch_.x, touch_.y, deployment, type, subtype, run_message_loop);
+ }
+
+ // Replays the events from the queue.
+ void RunQueuedEvents() {
+ while ([event_queue_ count] > 0) {
+ QueuedEvent* queued_event = [event_queue_ objectAtIndex:0];
+ NSEvent* event = queued_event.event;
+ NSView* view = GetWebContents()
+ ->GetRenderViewHost()
+ ->GetWidget()
+ ->GetView()
+ ->GetNativeView()
+ .GetNativeNSView();
+ BOOL run_loop = queued_event.runMessageLoop;
+ switch (queued_event.deployment) {
+ case DEPLOYMENT_GESTURE_BEGIN:
+ [view beginGestureWithEvent:event];
+ break;
+ case DEPLOYMENT_GESTURE_END:
+ [view endGestureWithEvent:event];
+ break;
+ case DEPLOYMENT_SCROLL_WHEEL:
+ [view scrollWheel:event];
+ break;
+ case DEPLOYMENT_TOUCHES_BEGAN:
+ [view touchesBeganWithEvent:event];
+ break;
+ case DEPLOYMENT_TOUCHES_ENDED:
+ [view touchesEndedWithEvent:event];
+ break;
+ case DEPLOYMENT_TOUCHES_MOVED:
+ [view touchesMovedWithEvent:event];
+ break;
+ }
+
+ [event_queue_ removeObjectAtIndex:0];
+
+ if (!run_loop)
+ continue;
+ // Give time for the IPC to make it to the renderer process. If the IPC
+ // doesn't have time to make it to the renderer process, that's okay,
+ // since that simulates realistic conditions.
+ [[NSRunLoop currentRunLoop]
+ runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
+ // The renderer process returns an IPC, which needs to be handled.
+ base::RunLoop().RunUntilIdle();
+ }
+ }
+
+ void ExpectUrlAndOffset(const GURL& url, int offset) {
+ content::WaitForLoadStop(GetWebContents());
+ EXPECT_EQ(url, GetWebContents()->GetURL());
+
+ const int scroll_offset = GetScrollTop();
+ EXPECT_EQ(offset, scroll_offset);
+ }
+
+ base::SimpleTestTickClock mock_clock_;
+
+ GURL url1_;
+ GURL url2_;
+ GURL url_iframe_;
+ base::scoped_nsobject<NSMutableArray> event_queue_;
+ // The current location of the user's fingers on the track pad.
+ CGPoint touch_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderWidgetHostViewMacHistorySwiperTest);
+};
+
+// The ordering, timing, and parameters of the events was determined by
+// recording a real swipe.
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ DISABLED_TestBackwardsHistoryNavigationRealData) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ QueueTouch(0.510681,
+ 0.444672,
+ DEPLOYMENT_TOUCHES_BEGAN,
+ NSEventTypeGesture,
+ NSMouseEventSubtype,
+ NO);
+ QueueTrackpadScroll(0, 0, NSEventPhaseMayBegin, YES);
+ QueueTouch(0.510681,
+ 0.444672,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSMouseEventSubtype,
+ NO);
+
+ QueueTrackpadScroll(1, 0, NSEventPhaseBegan, NO);
+ QueueGestureBegin();
+ QueueTouch(0.510681,
+ 0.444672,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeBeginGesture,
+ NSTouchEventSubtype,
+ NO);
+ QueueTouch(0.510681,
+ 0.444672,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTouch(0.507019,
+ 0.444092,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ NO);
+ QueueTrackpadScroll(3, 0, NSEventPhaseChanged, YES);
+
+ QueueTrackpadScroll(3, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.502861,
+ 0.443512,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.497002,
+ 0.44294,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(5, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.487236,
+ 0.44149,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(8, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.480392,
+ 0.440628,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ NO);
+ QueueTouch(0.475266,
+ 0.440338,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
+ QueueTrackpadScroll(10, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.467934,
+ 0.439758,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.462807,
+ 0.439186,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(12, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.454018,
+ 0.438316,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.449623,
+ 0.438026,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(9, 0, NSEventPhaseChanged, NO);
+ QueueTouch(0.443275,
+ 0.437744,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTouch(0.437164,
+ 0.437164,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(9, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.431305,
+ 0.436874,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(8, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.425926,
+ 0.436295,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(7, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.420311,
+ 0.43573,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(7, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.415184,
+ 0.43544,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.410057,
+ 0.43457,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTouch(0.40493,
+ 0.43399,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(7, -1, NSEventPhaseChanged, YES);
+ QueueTrackpadScroll(3, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.402489,
+ 0.433701,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(5, 0, NSEventPhaseChanged, NO);
+ QueueTouch(0.398094,
+ 0.433418,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ QueueTrackpadScroll(4, -1, NSEventPhaseChanged, NO);
+ QueueTouch(0.394669,
+ 0.433128,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTouch(0.391006,
+ 0.432549,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTrackpadScroll(4, -1, NSEventPhaseChanged, NO);
+ QueueTrackpadScroll(5, 0, NSEventPhaseChanged, YES);
+ QueueTouch(0.386848,
+ 0.432259,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+ QueueTouch(0.38343,
+ 0.432259,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeGesture,
+ NSTouchEventSubtype,
+ YES);
+
+ // Skipped a bunch of events. The data on the gesture end events are fudged.
+
+ QueueTouch(0.38343,
+ 0.432259,
+ DEPLOYMENT_TOUCHES_MOVED,
+ NSEventTypeEndGesture,
+ NSMouseEventSubtype,
+ NO);
+ QueueTouch(0.38343,
+ 0.432259,
+ DEPLOYMENT_TOUCHES_ENDED,
+ NSEventTypeEndGesture,
+ NSMouseEventSubtype,
+ NO);
+ QueueGestureEnd();
+ QueueTrackpadScroll(0, 0, NSEventPhaseEnded, YES);
+
+ RunQueuedEvents();
+ ExpectUrlAndOffset(url1_, 0);
+}
+
+// Each movement event that has non-zero parameters has both horizontal and
+// vertical motion. This should not trigger history navigation.
+// http://crbug.com/396328
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ DISABLED_TestAllDiagonalSwipes) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ QueueBeginningEvents(1, -1);
+ for (int i = 0; i < 150; ++i)
+ QueueScrollAndTouchMoved(1, -1);
+
+ QueueEndEvents();
+ RunQueuedEvents();
+ ExpectUrlAndOffset(url2_, 150);
+}
+
+// Disabled for flakiness. crbug.com/378158
+//
+// The movements are equal part diagonal, horizontal, and vertical. This should
+// not trigger history navigation.
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ DISABLED_TestStaggeredDiagonalSwipe) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ QueueBeginningEvents(1, 0);
+ for (int i = 0; i < 150; ++i) {
+ switch (i % 3) {
+ case 0:
+ QueueScrollAndTouchMoved(1, -1);
+ break;
+ case 1:
+ QueueScrollAndTouchMoved(0, -1);
+ break;
+ case 2:
+ QueueScrollAndTouchMoved(1, 0);
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ QueueEndEvents();
+ RunQueuedEvents();
+
+ content::WaitForLoadStop(GetWebContents());
+ EXPECT_EQ(url2_, GetWebContents()->GetURL());
+
+ // Depending on the timing of the IPCs, some of the initial events might be
+ // recognized as part of the history swipe, and not forwarded to the renderer,
+ // resulting in a non-deterministic scroll offset. This is bad, as some
+ // vertical motion is lost. Once the history swiper logic is fixed, this
+ // should become a direct comparison between 'scroll_offset' and 100.
+ // crbug.com/375514
+ const int scroll_offset = GetScrollTop();
+ // TODO(erikchen): Depending on the timing of the IPCs between Chrome and the
+ // renderer, more than 15% of the vertical motion can be lost. This assertion
+ // should eventually become an equality comparison against 100.
+ // crbug.com/378158
+ EXPECT_GT(scroll_offset, 1);
+}
+
+// The movement events are mostly in the horizontal direction, which should
+// trigger a history swipe. This should trigger history navigation.
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ DISABLED_TestMostlyHorizontal) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ QueueBeginningEvents(1, 1);
+ for (int i = 0; i < 150; ++i) {
+ if (i % 10 == 0) {
+ QueueScrollAndTouchMoved(0, -1);
+ } else if (i % 5 == 0) {
+ QueueScrollAndTouchMoved(1, -1);
+ } else {
+ QueueScrollAndTouchMoved(1, 0);
+ }
+ }
+
+ QueueEndEvents();
+ RunQueuedEvents();
+ ExpectUrlAndOffset(url1_, 0);
+}
+
+// Each movement event is horizontal, except the first two. This should trigger
+// history navigation. This test is DISABLED because it has never worked. Once
+// the flaw in the history swiper logic has been corrected, this test should be
+// enabled.
+// crbug.com/375512
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ DISABLED_TestAllHorizontalButFirst) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ QueueBeginningEvents(0, -1);
+ QueueScrollAndTouchMoved(0, -1);
+ for (int i = 0; i < 149; ++i)
+ QueueScrollAndTouchMoved(1, 0);
+
+ QueueEndEvents();
+ RunQueuedEvents();
+ ExpectUrlAndOffset(url1_, 0);
+}
+
+// Initial movements are vertical, and scroll the iframe. Subsequent movements
+// are horizontal, and should not trigger history swiping.
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ TestIframeHistorySwiping) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ ui_test_utils::NavigateToURL(browser(), url_iframe_);
+ ASSERT_EQ(url_iframe_, GetWebContents()->GetURL());
+
+ content::InputEventAckWaiter wheel_end_ack_waiter(
+ GetWebContents()->GetRenderViewHost()->GetWidget(),
+ base::BindRepeating([](content::InputEventAckSource,
+ content::InputEventAckState,
+ const blink::WebInputEvent& event) {
+ return event.GetType() == blink::WebInputEvent::kMouseWheel &&
+ static_cast<const blink::WebMouseWheelEvent&>(event).phase ==
+ blink::WebMouseWheelEvent::kPhaseEnded;
+ }));
+
+ QueueBeginningEvents(0, -1);
+ for (int i = 0; i < 10; ++i)
+ QueueScrollAndTouchMoved(0, -1);
+ for (int i = 0; i < 149; ++i)
+ QueueScrollAndTouchMoved(1, 0);
+
+ QueueEndEvents();
+ RunQueuedEvents();
+
+ // Wait for the scroll to end.
+ wheel_end_ack_waiter.Wait();
+
+ content::WaitForLoadStop(GetWebContents());
+ EXPECT_EQ(url_iframe_, GetWebContents()->GetURL());
+}
+
+// The gesture ends before the touchesEndedWithEvent: method gets called.
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ DISABLED_TestGestureEndTiming) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ QueueBeginningEvents(1, 0);
+ for (int i = 0; i < 150; ++i)
+ QueueScrollAndTouchMoved(1, 0);
+
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_MOVED, NSEventTypeEndGesture, NSMouseEventSubtype, NO);
+ QueueGestureEnd();
+ QueueTouch(
+ DEPLOYMENT_TOUCHES_ENDED, NSEventTypeEndGesture, NSMouseEventSubtype, NO);
+ QueueTrackpadScroll(0, 0, NSEventPhaseEnded, YES);
+
+ RunQueuedEvents();
+ ExpectUrlAndOffset(url1_, 0);
+}
+
+IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
+ InnerScrollersOverscrollBehaviorPreventsNavigation) {
+ if (!IsHistorySwipingSupported())
+ return;
+
+ const base::FilePath base_path(FILE_PATH_LITERAL("scroll"));
+ GURL url_overscroll_behavior = ui_test_utils::GetTestUrl(
+ base_path, base::FilePath(FILE_PATH_LITERAL("overscroll_behavior.html")));
+ ui_test_utils::NavigateToURL(browser(), url_overscroll_behavior);
+ ASSERT_EQ(url_overscroll_behavior, GetWebContents()->GetURL());
+
+ QueueBeginningEvents(1, 0);
+ for (int i = 0; i < 10; ++i) {
+ QueueScrollAndTouchMoved(10, 0);
+ }
+
+ QueueEndEvents();
+ RunQueuedEvents();
+ // If navigation was to occur, the URL would be url2_.
+ ExpectUrlAndOffset(url_overscroll_behavior, 0);
+}
diff --git a/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm
new file mode 100644
index 00000000000..1af449d13a9
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm
@@ -0,0 +1,656 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h"
+
+#include "base/mac/scoped_nsobject.h"
+#import "base/mac/sdk_forward_declarations.h"
+#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_input_event.h"
+#include "third_party/blink/public/platform/web_mouse_wheel_event.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/ocmock_extensions.h"
+#include "ui/events/blink/did_overscroll_params.h"
+
+@interface HistorySwiper (MacHistorySwiperTest)
+- (BOOL)systemSettingsAllowHistorySwiping:(NSEvent*)event;
+- (BOOL)browserCanNavigateInDirection:
+ (history_swiper::NavigationDirection)forward
+ event:(NSEvent*)event;
+- (void)removeHistoryOverlay;
+- (void)showHistoryOverlay:(history_swiper::NavigationDirection)direction;
+- (void)navigateBrowserInDirection:(history_swiper::NavigationDirection)forward;
+- (void)initiateMagicMouseHistorySwipe:(BOOL)isRightScroll
+ event:(NSEvent*)event;
+@end
+
+class MacHistorySwiperTest : public CocoaTest {
+ public:
+ void SetUp() override {
+ CocoaTest::SetUp();
+
+ [HistorySwiper resetMagicMouseState];
+
+ view_ = [[NSView alloc] init];
+ id mockDelegate =
+ [OCMockObject mockForProtocol:@protocol(HistorySwiperDelegate)];
+ [[[mockDelegate stub] andReturn:view_] viewThatWantsHistoryOverlay];
+ [[[mockDelegate stub] andReturnBool:YES] shouldAllowHistorySwiping];
+
+ base::scoped_nsobject<HistorySwiper> historySwiper(
+ [[HistorySwiper alloc] initWithDelegate:mockDelegate]);
+ id mockHistorySwiper = [OCMockObject partialMockForObject:historySwiper];
+ [[[mockHistorySwiper stub] andReturnBool:YES]
+ systemSettingsAllowHistorySwiping:[OCMArg any]];
+ [[[mockHistorySwiper stub] andReturnBool:YES]
+ browserCanNavigateInDirection:history_swiper::kForwards
+ event:[OCMArg any]];
+ [[[mockHistorySwiper stub] andReturnBool:YES]
+ browserCanNavigateInDirection:history_swiper::kBackwards
+ event:[OCMArg any]];
+ [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ ++begin_count_;
+ // showHistoryOverlay: calls removeHistoryOverlay internally.
+ --end_count_;
+ }] andForwardToRealObject] showHistoryOverlay:history_swiper::kForwards];
+ [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ ++begin_count_;
+ // showHistoryOverlay: calls removeHistoryOverlay internally.
+ --end_count_;
+ }] andForwardToRealObject] showHistoryOverlay:history_swiper::kBackwards];
+ [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ ++end_count_;
+ }] andForwardToRealObject] removeHistoryOverlay];
+ [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ navigated_right_ = true;
+ }] navigateBrowserInDirection:history_swiper::kForwards];
+ [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ navigated_left_ = true;
+ }] navigateBrowserInDirection:history_swiper::kBackwards];
+
+ [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ magic_mouse_history_swipe_ = true;
+ }] initiateMagicMouseHistorySwipe:YES event:[OCMArg any]];
+ [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) {
+ magic_mouse_history_swipe_ = true;
+ }] initiateMagicMouseHistorySwipe:NO event:[OCMArg any]];
+
+ historySwiper_ = [mockHistorySwiper retain];
+
+ begin_count_ = 0;
+ end_count_ = 0;
+ navigated_right_ = false;
+ navigated_left_ = false;
+ magic_mouse_history_swipe_ = false;
+ }
+
+ void TearDown() override {
+ [view_ release];
+ [historySwiper_ release];
+ CocoaTest::TearDown();
+ }
+
+ // These methods send all 3 types of events: gesture, scroll, and touch.
+ void startGestureInMiddle();
+ void moveGestureInMiddle();
+ void moveGestureAtPoint(NSPoint point);
+ void momentumMoveGestureAtPoint(NSPoint point);
+ void endGestureAtPoint(NSPoint point);
+ void rendererACKForBeganEvent();
+ void onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType);
+
+ // These methods send a single type of event.
+ void sendBeginGestureEventInMiddle();
+ void sendEndGestureEventAtPoint(NSPoint point);
+
+ HistorySwiper* historySwiper_;
+ NSView* view_;
+ int begin_count_;
+ int end_count_;
+ bool navigated_right_;
+ bool navigated_left_;
+ bool magic_mouse_history_swipe_;
+};
+
+NSPoint makePoint(CGFloat x, CGFloat y) {
+ NSPoint point;
+ point.x = x;
+ point.y = y;
+ return point;
+}
+
+id mockEventWithPoint(NSPoint point, NSEventType type) {
+ id mockEvent = [OCMockObject mockForClass:[NSEvent class]];
+ id mockTouch = [OCMockObject mockForClass:[NSTouch class]];
+ [[[mockTouch stub] andReturnNSPoint:point] normalizedPosition];
+ NSArray* touches = @[mockTouch];
+ [[[mockEvent stub] andReturn:touches] touchesMatchingPhase:NSTouchPhaseAny
+ inView:[OCMArg any]];
+ [[[mockEvent stub] andReturnBool:NO] isDirectionInvertedFromDevice];
+ [(NSEvent*)[[mockEvent stub] andReturnValue:OCMOCK_VALUE(type)] type];
+
+ return mockEvent;
+}
+
+id scrollWheelEventWithPhase(NSEventPhase phase,
+ NSEventPhase momentumPhase,
+ CGFloat scrollingDeltaX,
+ CGFloat scrollingDeltaY) {
+ // The point isn't used, so we pass in bogus data.
+ id event = mockEventWithPoint(makePoint(0,0), NSScrollWheel);
+ [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
+ [(NSEvent*)
+ [[event stub] andReturnValue:OCMOCK_VALUE(momentumPhase)] momentumPhase];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(scrollingDeltaX)] scrollingDeltaX];
+ [(NSEvent*)[[event stub]
+ andReturnValue:OCMOCK_VALUE(scrollingDeltaY)] scrollingDeltaY];
+ return event;
+}
+
+id scrollWheelEventWithPhase(NSEventPhase phase,
+ NSEventPhase momentumPhase) {
+ return scrollWheelEventWithPhase(phase, momentumPhase, 0, 0);
+}
+
+id scrollWheelEventWithPhase(NSEventPhase phase) {
+ return scrollWheelEventWithPhase(phase, NSEventPhaseNone);
+}
+
+void MacHistorySwiperTest::startGestureInMiddle() {
+ NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture);
+ [historySwiper_ touchesBeganWithEvent:event];
+ [historySwiper_ beginGestureWithEvent:event];
+ NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan);
+ [historySwiper_ handleEvent:scrollEvent];
+}
+
+void MacHistorySwiperTest::moveGestureInMiddle() {
+ moveGestureAtPoint(makePoint(0.5, 0.5));
+
+ // Callbacks from blink to set the relevant state for history swiping.
+ rendererACKForBeganEvent();
+}
+
+void MacHistorySwiperTest::moveGestureAtPoint(NSPoint point) {
+ NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
+ [historySwiper_ touchesMovedWithEvent:event];
+
+ NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged);
+ [historySwiper_ handleEvent:scrollEvent];
+}
+
+void MacHistorySwiperTest::momentumMoveGestureAtPoint(NSPoint point) {
+ NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
+ [historySwiper_ touchesMovedWithEvent:event];
+
+ NSEvent* scrollEvent =
+ scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged);
+ [historySwiper_ handleEvent:scrollEvent];
+}
+
+void MacHistorySwiperTest::endGestureAtPoint(NSPoint point) {
+ NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
+ [historySwiper_ touchesEndedWithEvent:event];
+
+ NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseEnded);
+ [historySwiper_ handleEvent:scrollEvent];
+
+ sendEndGestureEventAtPoint(point);
+}
+
+void MacHistorySwiperTest::onOverscrolled(
+ cc::OverscrollBehavior::OverscrollBehaviorType behavior) {
+ ui::DidOverscrollParams params;
+ params.overscroll_behavior.x = behavior;
+ [historySwiper_ onOverscrolled:params];
+}
+
+void MacHistorySwiperTest::rendererACKForBeganEvent() {
+ blink::WebMouseWheelEvent event;
+ event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
+ [historySwiper_ rendererHandledWheelEvent:event consumed:NO];
+}
+
+void MacHistorySwiperTest::sendBeginGestureEventInMiddle() {
+ NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture);
+ [historySwiper_ beginGestureWithEvent:event];
+}
+
+void MacHistorySwiperTest::sendEndGestureEventAtPoint(NSPoint point) {
+ NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
+ [historySwiper_ endGestureWithEvent:event];
+}
+
+// Test that a simple left-swipe causes navigation.
+TEST_F(MacHistorySwiperTest, SwipeLeft) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ moveGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+
+ endGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_TRUE(navigated_left_);
+}
+
+// Test that a simple right-swipe causes navigation.
+TEST_F(MacHistorySwiperTest, SwipeRight) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ moveGestureAtPoint(makePoint(0.8, 0.5));
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+
+ endGestureAtPoint(makePoint(0.8, 0.5));
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_TRUE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// If the user doesn't swipe enough, the history swiper should begin, but the
+// browser should not navigate.
+TEST_F(MacHistorySwiperTest, SwipeLeftSmallAmount) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ moveGestureAtPoint(makePoint(0.45, 0.5));
+ endGestureAtPoint(makePoint(0.45, 0.5));
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// Diagonal swipes with a slight horizontal bias should not start the history
+// swiper.
+TEST_F(MacHistorySwiperTest, SwipeDiagonal) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ moveGestureInMiddle();
+ moveGestureAtPoint(makePoint(0.6, 0.59));
+ endGestureAtPoint(makePoint(0.6, 0.59));
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// Swiping left and then down should cancel the history swiper without
+// navigating.
+TEST_F(MacHistorySwiperTest, SwipeLeftThenDown) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ moveGestureAtPoint(makePoint(0.4, 0.5));
+ moveGestureAtPoint(makePoint(0.4, 0.3));
+ endGestureAtPoint(makePoint(0.2, 0.2));
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// Sometimes Cocoa gets confused and sends us a momentum swipe event instead of
+// a swipe gesture event. Momentum events should not cause history swiping.
+TEST_F(MacHistorySwiperTest, MomentumSwipeLeft) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+
+ // Send a momentum move gesture.
+ momentumMoveGestureAtPoint(makePoint(0.5, 0.5));
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ // Callbacks from blink to set the relevant state for history swiping.
+ rendererACKForBeganEvent();
+
+ momentumMoveGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ endGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+}
+
+// Momentum scroll events for magic mouse should not attempt to trigger the
+// `trackSwipeEventWithOptions:` api, as that throws an exception.
+TEST_F(MacHistorySwiperTest, MagicMouseMomentumSwipe) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ // Magic mouse events don't generate 'touches*' callbacks.
+ NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture);
+ [historySwiper_ beginGestureWithEvent:event];
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan);
+ [historySwiper_ handleEvent:scrollEvent];
+
+ // Callbacks from blink to set the relevant state for history swiping.
+ rendererACKForBeganEvent();
+
+ // Send a momentum move gesture.
+ scrollEvent =
+ scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged, 5.0, 0);
+ [historySwiper_ handleEvent:scrollEvent];
+
+ EXPECT_FALSE(magic_mouse_history_swipe_);
+}
+
+// User starts a swipe but doesn't move.
+TEST_F(MacHistorySwiperTest, NoSwipe) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+
+ // Starts the gesture.
+ moveGestureAtPoint(makePoint(0.44, 0.44));
+
+ // No movement.
+ endGestureAtPoint(makePoint(0.44, 0.44));
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// After a gesture is successfully recognized, momentum events should be
+// swallowed, but new events should pass through.
+TEST_F(MacHistorySwiperTest, TouchEventAfterGestureFinishes) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ // Successfully pass through a gesture.
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ moveGestureAtPoint(makePoint(0.8, 0.5));
+ endGestureAtPoint(makePoint(0.8, 0.5));
+ EXPECT_TRUE(navigated_right_);
+
+ // Momentum events should be swallowed.
+ NSEvent* momentumEvent = scrollWheelEventWithPhase(NSEventPhaseNone,
+ NSEventPhaseChanged);
+ EXPECT_TRUE([historySwiper_ handleEvent:momentumEvent]);
+
+ // New events should not be swallowed.
+ NSEvent* beganEvent = scrollWheelEventWithPhase(NSEventPhaseBegan);
+ EXPECT_FALSE([historySwiper_ handleEvent:beganEvent]);
+}
+
+// The history swipe logic should be resilient against the timing of the
+// different callbacks that result from scrolling.
+TEST_F(MacHistorySwiperTest, SwipeRightEventOrdering) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ // Touches began.
+ NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan);
+ NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture);
+ [historySwiper_ touchesBeganWithEvent:event];
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+ [historySwiper_ handleEvent:scrollEvent];
+ rendererACKForBeganEvent();
+
+ // Touches moved.
+ moveGestureAtPoint(makePoint(0.5, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ // Touches moved.
+ moveGestureAtPoint(makePoint(0.52, 0.5));
+
+ // Begin gesture callback is delayed.
+ [historySwiper_ beginGestureWithEvent:event];
+
+ // Touches moved.
+ moveGestureAtPoint(makePoint(0.52, 0.5));
+
+ // Complete the rest of the gesture.
+ moveGestureAtPoint(makePoint(0.60, 0.5));
+ scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged);
+ [historySwiper_ handleEvent:scrollEvent];
+ endGestureAtPoint(makePoint(0.70, 0.5));
+
+ EXPECT_EQ(begin_count_, 1);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_TRUE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// Substantial vertical scrolling followed by horizontal scrolling should not
+// result in navigation.
+TEST_F(MacHistorySwiperTest, SubstantialVerticalThenHorizontal) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+
+ // Move up, then move down.
+ for (CGFloat y = 0.51; y < 0.6; y += 0.01)
+ moveGestureAtPoint(makePoint(0.5, y));
+ for (CGFloat y = 0.59; y > 0.5; y -= 0.01)
+ moveGestureAtPoint(makePoint(0.5, y));
+
+ // Large movement to the right.
+ moveGestureAtPoint(makePoint(0.6, 0.51));
+ endGestureAtPoint(makePoint(0.6, 0.51));
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 1);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// Magic Mouse gestures don't send -touches*WithEvent: callbacks. The history
+// swiper should still handle this gracefully. It should not turn vertical
+// motion into history swipes.
+TEST_F(MacHistorySwiperTest, MagicMouseStateResetsCorrectly) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ // Magic mouse events don't generate '-touches*WithEvent:' callbacks.
+ // Send the following events:
+ // - beginGesture
+ // - scrollWheel: (phase=Began)
+ // - scrollWheel: (phase=Changed), significant horizontal motion.
+ // - scrollWheel: (phase=Ended)
+ // - endGesture
+ sendBeginGestureEventInMiddle();
+ [historySwiper_ handleEvent:scrollWheelEventWithPhase(NSEventPhaseBegan)];
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeAuto);
+
+ // Callback from Blink to set the relevant state for history swiping.
+ rendererACKForBeganEvent();
+
+ NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged,
+ NSEventPhaseNone, 200.0, 0);
+ [historySwiper_ handleEvent:scrollEvent];
+ [historySwiper_ handleEvent:scrollWheelEventWithPhase(NSEventPhaseEnded)];
+ sendEndGestureEventAtPoint(makePoint(0.7, 0.5));
+
+ // Expect this sequence of events to trigger a magic mouse history swipe.
+ EXPECT_TRUE(magic_mouse_history_swipe_);
+
+ // Reset state.
+ magic_mouse_history_swipe_ = false;
+
+ // Send the following events:
+ // - beginGesture
+ // - scrollWheel: (phase=Began)
+ // - scrollWheel: (phase=Changed), significant vertical motion.
+ // - scrollWheel: (phase=Ended)
+ // - endGesture
+ sendBeginGestureEventInMiddle();
+ [historySwiper_ handleEvent:scrollWheelEventWithPhase(NSEventPhaseBegan)];
+
+ // Callback from Blink to set the relevant state for history swiping.
+ rendererACKForBeganEvent();
+
+ scrollEvent =
+ scrollWheelEventWithPhase(NSEventPhaseChanged, NSEventPhaseNone, 0, 20);
+ [historySwiper_ handleEvent:scrollEvent];
+ [historySwiper_ handleEvent:scrollWheelEventWithPhase(NSEventPhaseEnded)];
+ sendEndGestureEventAtPoint(makePoint(0.5, 0.7));
+
+ // Vertical motion should never trigger a history swipe!
+ EXPECT_FALSE(magic_mouse_history_swipe_);
+}
+
+// With overscroll-behavior value as contain, the page should not navigate,
+// nor should the history overlay appear.
+TEST_F(MacHistorySwiperTest, OverscrollBehaviorContainPreventsNavigation) {
+ // These tests require 10.7+ APIs.
+ if (![NSEvent
+ respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
+ return;
+
+ startGestureInMiddle();
+ moveGestureInMiddle();
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeContain);
+ moveGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+
+ endGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// With overscroll-behavior value as none, the page should not navigate,
+// nor should the history overlay appear.
+TEST_F(MacHistorySwiperTest, OverscrollBehaviorNonePreventsNavigation) {
+ startGestureInMiddle();
+ moveGestureInMiddle();
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ onOverscrolled(cc::OverscrollBehavior::OverscrollBehaviorType::
+ kOverscrollBehaviorTypeNone);
+ moveGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+
+ endGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
+
+// Without overscroll msg from renderer, the page should not navigate, nor
+// should the history overlay appear.
+TEST_F(MacHistorySwiperTest, NoNavigationWithoutOverscrollMsg) {
+ startGestureInMiddle();
+ moveGestureInMiddle();
+
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+
+ moveGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+
+ endGestureAtPoint(makePoint(0.2, 0.5));
+ EXPECT_EQ(begin_count_, 0);
+ EXPECT_EQ(end_count_, 0);
+ EXPECT_FALSE(navigated_right_);
+ EXPECT_FALSE(navigated_left_);
+}
diff --git a/chromium/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc b/chromium/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
new file mode 100644
index 00000000000..d58b837f47a
--- /dev/null
+++ b/chromium/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
@@ -0,0 +1,786 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base_switches.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/process/process.h"
+#include "base/run_loop.h"
+#include "base/test/test_timeouts.h"
+#include "build/build_config.h"
+#include "chrome/browser/devtools/devtools_window.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/search/search.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/singleton_tabs.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/child_process_launcher_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_iterator.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/base/filename_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#if defined(OS_MACOSX)
+#include "content/public/browser/browser_child_process_host.h"
+#endif // defined(OS_MACOSX)
+
+using content::RenderViewHost;
+using content::RenderWidgetHost;
+using content::WebContents;
+
+namespace {
+
+int RenderProcessHostCount() {
+ return content::RenderProcessHost::GetCurrentRenderProcessCountForTesting();
+}
+
+WebContents* FindFirstDevToolsContents() {
+ std::unique_ptr<content::RenderWidgetHostIterator> widgets(
+ RenderWidgetHost::GetRenderWidgetHosts());
+ while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
+ if (!widget->GetProcess()->IsInitializedAndNotDead())
+ continue;
+ RenderViewHost* view_host = RenderViewHost::From(widget);
+ if (!view_host)
+ continue;
+ WebContents* contents = WebContents::FromRenderViewHost(view_host);
+ GURL url = contents->GetURL();
+ if (url.SchemeIs(content::kChromeDevToolsScheme))
+ return contents;
+ }
+ return NULL;
+}
+
+// TODO(rvargas) crbug.com/417532: Remove this code.
+base::Process ProcessFromHandle(base::ProcessHandle handle) {
+#if defined(OS_WIN)
+ if (handle == GetCurrentProcess())
+ return base::Process::Current();
+
+ base::ProcessHandle out_handle;
+ if (!::DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(),
+ &out_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+ return base::Process();
+ }
+ handle = out_handle;
+#endif // defined(OS_WIN)
+ return base::Process(handle);
+}
+
+} // namespace
+
+class ChromeRenderProcessHostTest : public extensions::ExtensionBrowserTest {
+ public:
+ ChromeRenderProcessHostTest() {}
+
+ // Show a tab, activating the current one if there is one, and wait for
+ // the renderer process to be created or foregrounded, returning the
+ // WebContents associated with the tab.
+ WebContents* ShowSingletonTab(const GURL& page) {
+ ::ShowSingletonTab(browser(), page);
+ WebContents* wc = browser()->tab_strip_model()->GetActiveWebContents();
+ CHECK(wc->GetURL() == page);
+
+ WaitForLauncherThread();
+ WaitForMessageProcessing(wc);
+ return wc;
+ }
+
+ // Loads the given url in a new background tab and returns the WebContents
+ // associated with the tab.
+ WebContents* OpenBackgroundTab(const GURL& page) {
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), page, WindowOpenDisposition::NEW_BACKGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+
+ TabStripModel* tab_strip = browser()->tab_strip_model();
+ WebContents* wc =
+ tab_strip->GetWebContentsAt(tab_strip->active_index() + 1);
+ CHECK(wc->GetVisibleURL() == page);
+
+ WaitForLauncherThread();
+ WaitForMessageProcessing(wc);
+ return wc;
+ }
+
+ // Ensures that the backgrounding / foregrounding gets a chance to run.
+ void WaitForLauncherThread() {
+ base::RunLoop run_loop;
+ content::GetProcessLauncherTaskRunner()->PostTaskAndReply(
+ FROM_HERE, base::DoNothing(), run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ }
+
+ // Implicitly waits for the renderer process associated with the specified
+ // WebContents to process outstanding IPC messages by running some JavaScript
+ // and waiting for the result.
+ void WaitForMessageProcessing(WebContents* wc) {
+ bool result = false;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+ wc, "window.domAutomationController.send(true);", &result));
+ ASSERT_TRUE(result);
+ }
+
+ // When we hit the max number of renderers, verify that the way we do process
+ // sharing behaves correctly. In particular, this test is verifying that even
+ // when we hit the max process limit, that renderers of each type will wind up
+ // in a process of that type, even if that means creating a new process.
+ void TestProcessOverflow() {
+ int tab_count = 1;
+ int host_count = 1;
+ WebContents* tab1 = NULL;
+ WebContents* tab2 = NULL;
+ content::RenderProcessHost* rph1 = NULL;
+ content::RenderProcessHost* rph2 = NULL;
+ content::RenderProcessHost* rph3 = NULL;
+
+ const extensions::Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("options_page"));
+
+ content::RenderFrameDeletedObserver before_webui_obs(
+ content::ConvertToRenderFrameHost(
+ browser()->tab_strip_model()->GetActiveWebContents()));
+
+ // Change the first tab to be the omnibox page (WebUI).
+ GURL omnibox(chrome::kChromeUIOmniboxURL);
+ ui_test_utils::NavigateToURL(browser(), omnibox);
+
+ // The host objects from the page before the WebUI navigation stick around
+ // until the old renderer cleans up and ACKs, which may happen later than
+ // the navigation in the WebUI's renderer. So wait for that.
+ before_webui_obs.WaitUntilDeleted();
+
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
+ rph1 = tab1->GetMainFrame()->GetProcess();
+ EXPECT_EQ(omnibox, tab1->GetURL());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // Create a new normal tab with a data URL. It should be in its own process.
+ GURL page1("data:text/html,hello world1");
+
+ ui_test_utils::TabAddedWaiter add_tab1(browser());
+ ::ShowSingletonTab(browser(), page1);
+ add_tab1.Wait();
+
+ tab_count++;
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
+ rph2 = tab1->GetMainFrame()->GetProcess();
+ EXPECT_EQ(tab1->GetURL(), page1);
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+ EXPECT_NE(rph1, rph2);
+
+ // Create another data URL tab. With Site Isolation, this will require its
+ // own process, but without Site Isolation, it can share the previous
+ // process.
+ GURL page2("data:text/html,hello world2");
+ ui_test_utils::TabAddedWaiter add_tab2(browser());
+ ::ShowSingletonTab(browser(), page2);
+ add_tab2.Wait();
+ tab_count++;
+ if (content::AreAllSitesIsolatedForTesting())
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
+ EXPECT_EQ(tab2->GetURL(), page2);
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+ if (content::AreAllSitesIsolatedForTesting())
+ EXPECT_NE(tab2->GetMainFrame()->GetProcess(), rph2);
+ else
+ EXPECT_EQ(tab2->GetMainFrame()->GetProcess(), rph2);
+
+ // Create another WebUI tab. Each WebUI tab should get a separate process
+ // because of origin locking.
+ // Note: intentionally create this tab after the normal tabs to exercise bug
+ // 43448 where extension and WebUI tabs could get combined into normal
+ // renderers.
+ GURL history(chrome::kChromeUIHistoryURL);
+ ui_test_utils::TabAddedWaiter add_tab3(browser());
+ ::ShowSingletonTab(browser(), history);
+ add_tab3.Wait();
+ tab_count++;
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
+ EXPECT_EQ(tab2->GetURL(), GURL(history));
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+ EXPECT_NE(tab2->GetMainFrame()->GetProcess(), rph1);
+
+ // Create an extension tab. It should be in its own process.
+ GURL extension_url("chrome-extension://" + extension->id());
+ ui_test_utils::TabAddedWaiter add_tab4(browser());
+ ::ShowSingletonTab(browser(), extension_url);
+
+ add_tab4.Wait();
+ tab_count++;
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
+ rph3 = tab1->GetMainFrame()->GetProcess();
+ EXPECT_EQ(tab1->GetURL(), extension_url);
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+ EXPECT_NE(rph1, rph3);
+ EXPECT_NE(rph2, rph3);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderProcessHostTest);
+};
+
+class ChromeRenderProcessHostTestWithCommandLine
+ : public ChromeRenderProcessHostTest {
+ public:
+ ChromeRenderProcessHostTestWithCommandLine() = default;
+ ~ChromeRenderProcessHostTestWithCommandLine() override = default;
+
+ protected:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ChromeRenderProcessHostTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderProcessHostTestWithCommandLine);
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) {
+ // Set max renderers to 1 to force running out of processes.
+ content::RenderProcessHost::SetMaxRendererProcessCount(1);
+
+ base::CommandLine& parsed_command_line =
+ *base::CommandLine::ForCurrentProcess();
+ parsed_command_line.AppendSwitch(switches::kProcessPerTab);
+
+ int tab_count = 1;
+ int host_count = 1;
+
+ content::RenderFrameDeletedObserver before_webui_obs(
+ content::ConvertToRenderFrameHost(
+ browser()->tab_strip_model()->GetActiveWebContents()));
+
+ // Change the first tab to be a WebUI page.
+ GURL omnibox(chrome::kChromeUIOmniboxURL);
+ ui_test_utils::NavigateToURL(browser(), omnibox);
+
+ // The host objects from the page before the WebUI navigation stick around
+ // until the old renderer cleans up and ACKs, which may happen later than the
+ // navigation in the WebUI's renderer. So wait for that.
+ before_webui_obs.WaitUntilDeleted();
+
+ // Expect just the WebUI tab's process to be around.
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // Create a new normal tab with a data URL. It should be in its own process.
+ GURL page1("data:text/html,hello world1");
+ ui_test_utils::TabAddedWaiter add_tab1(browser());
+ ::ShowSingletonTab(browser(), page1);
+ add_tab1.Wait();
+ tab_count++;
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // Create another data URL tab. With Site Isolation, this will require its
+ // own process, but without Site Isolation, it can share the previous process.
+ GURL page2("data:text/html,hello world2");
+ ui_test_utils::TabAddedWaiter add_tab2(browser());
+ ::ShowSingletonTab(browser(), page2);
+ add_tab2.Wait();
+ tab_count++;
+ if (content::AreAllSitesIsolatedForTesting())
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // Create another omnibox tab. It should share the process with the other
+ // WebUI.
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), omnibox, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+ tab_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // Create another omnibox tab. It should share the process with the other
+ // WebUI.
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), omnibox, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+ tab_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+}
+
+class ChromeRenderProcessHostBackgroundingTest
+ : public ChromeRenderProcessHostTest {
+ public:
+ ChromeRenderProcessHostBackgroundingTest() = default;
+ ~ChromeRenderProcessHostBackgroundingTest() override = default;
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ChromeRenderProcessHostTest::SetUpCommandLine(command_line);
+
+ command_line->AppendSwitch(switches::kProcessPerTab);
+ }
+
+ void VerifyProcessIsBackgrounded(WebContents* web_contents) {
+ constexpr bool kExpectedIsBackground = true;
+ VerifyProcessPriority(web_contents->GetMainFrame()->GetProcess(),
+ kExpectedIsBackground);
+ }
+
+ void VerifyProcessIsForegrounded(WebContents* web_contents) {
+ constexpr bool kExpectedIsBackground = false;
+ VerifyProcessPriority(web_contents->GetMainFrame()->GetProcess(),
+ kExpectedIsBackground);
+ }
+
+ void SetUpOnMainThread() override {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ private:
+ void VerifyProcessPriority(content::RenderProcessHost* process,
+ bool expected_is_backgrounded) {
+ EXPECT_TRUE(process->IsInitializedAndNotDead());
+ EXPECT_EQ(expected_is_backgrounded, process->IsProcessBackgrounded());
+
+ if (base::Process::CanBackgroundProcesses()) {
+ base::Process p = ProcessFromHandle(process->GetProcess().Handle());
+ ASSERT_TRUE(p.IsValid());
+#if defined(OS_MACOSX)
+ base::PortProvider* port_provider =
+ content::BrowserChildProcessHost::GetPortProvider();
+ EXPECT_EQ(expected_is_backgrounded,
+ p.IsProcessBackgrounded(port_provider));
+#else
+ EXPECT_EQ(expected_is_backgrounded, p.IsProcessBackgrounded());
+#endif
+ }
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderProcessHostBackgroundingTest);
+};
+
+#define EXPECT_PROCESS_IS_BACKGROUNDED(process_or_tab) \
+ do { \
+ SCOPED_TRACE("Verifying that |" #process_or_tab "| is backgrounded..."); \
+ VerifyProcessIsBackgrounded(process_or_tab); \
+ } while (0);
+
+#define EXPECT_PROCESS_IS_FOREGROUNDED(process_or_tab) \
+ do { \
+ SCOPED_TRACE("Verifying that |" #process_or_tab "| is foregrounded..."); \
+ VerifyProcessIsForegrounded(process_or_tab); \
+ } while (0);
+
+// Flaky on Mac: https://crbug.com/888308
+#if defined(OS_MACOSX)
+#define MAYBE_MultipleTabs DISABLED_MultipleTabs
+#else
+#define MAYBE_MultipleTabs MultipleTabs
+#endif
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTest,
+ MAYBE_MultipleTabs) {
+ // Change the first tab to be the omnibox page (TYPE_WEBUI).
+ GURL omnibox(chrome::kChromeUIOmniboxURL);
+ ui_test_utils::NavigateToURL(browser(), omnibox);
+
+ // Create a new tab. It should be foreground.
+ GURL page1("data:text/html,hello world1");
+ WebContents* tab1 = ShowSingletonTab(page1);
+ {
+ SCOPED_TRACE("TEST STEP: Single tab");
+ EXPECT_PROCESS_IS_FOREGROUNDED(tab1);
+ }
+
+ // Create another tab. It should be foreground, and the first tab should
+ // now be background.
+ GURL page2("data:text/html,hello world2");
+ WebContents* tab2 = ShowSingletonTab(page2);
+ {
+ SCOPED_TRACE("TEST STEP: 2nd tab opened in foreground");
+ EXPECT_NE(tab1->GetMainFrame()->GetProcess(),
+ tab2->GetMainFrame()->GetProcess());
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab1);
+ EXPECT_PROCESS_IS_FOREGROUNDED(tab2);
+ }
+
+ // Load another tab in background. The renderer of the new tab should be
+ // backgrounded, while visibility of the other renderers should not change.
+ GURL page3("data:text/html,hello world3");
+ WebContents* tab3 = OpenBackgroundTab(page3);
+ {
+ SCOPED_TRACE("TEST STEP: 3rd tab opened in background");
+ EXPECT_NE(tab1->GetMainFrame()->GetProcess(),
+ tab3->GetMainFrame()->GetProcess());
+ EXPECT_NE(tab2->GetMainFrame()->GetProcess(),
+ tab3->GetMainFrame()->GetProcess());
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab1);
+ EXPECT_PROCESS_IS_FOREGROUNDED(tab2);
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab3);
+ }
+
+ // Navigate back to the first page. Its renderer should be in foreground
+ // again while the other renderers should be backgrounded.
+ ShowSingletonTab(page1);
+ {
+ SCOPED_TRACE("TEST STEP: First tab activated again");
+ EXPECT_PROCESS_IS_FOREGROUNDED(tab1);
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab2);
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab3);
+ }
+
+ // Confirm that |tab3| remains backgrounded after being shown/hidden.
+ ShowSingletonTab(page3);
+ ShowSingletonTab(page1);
+ {
+ SCOPED_TRACE("TEST STEP: Third tab activated and deactivated");
+ EXPECT_PROCESS_IS_FOREGROUNDED(tab1);
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab2);
+ EXPECT_PROCESS_IS_BACKGROUNDED(tab3);
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessOverflow) {
+ // Set max renderers to 1 to force running out of processes.
+ content::RenderProcessHost::SetMaxRendererProcessCount(1);
+ TestProcessOverflow();
+}
+
+// Variation of the ProcessOverflow test, which is driven through command line
+// parameter instead of direct function call into the class.
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
+ ProcessOverflowCommandLine) {
+ TestProcessOverflow();
+}
+
+// Ensure that DevTools opened to debug DevTools is launched in a separate
+// process when --process-per-tab is set. See crbug.com/69873.
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
+ DevToolsOnSelfInOwnProcessPPT) {
+ base::CommandLine& parsed_command_line =
+ *base::CommandLine::ForCurrentProcess();
+ parsed_command_line.AppendSwitch(switches::kProcessPerTab);
+
+ int tab_count = 1;
+ int host_count = 1;
+
+ GURL page1("data:text/html,hello world1");
+ ui_test_utils::TabAddedWaiter add_tab(browser());
+ ::ShowSingletonTab(browser(), page1);
+ add_tab.Wait();
+ tab_count++;
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // DevTools start in docked mode (no new tab), in a separate process.
+ chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ WebContents* devtools = FindFirstDevToolsContents();
+ DCHECK(devtools);
+
+ // DevTools start in a separate process.
+ DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // close docked devtools
+ content::WindowedNotificationObserver close_observer(
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<WebContents>(devtools));
+
+ chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
+ close_observer.Wait();
+}
+
+// Ensure that DevTools opened to debug DevTools is launched in a separate
+// process. See crbug.com/69873.
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
+ DevToolsOnSelfInOwnProcess) {
+ int tab_count = 1;
+ int host_count = 1;
+
+ GURL page1("data:text/html,hello world1");
+ ui_test_utils::TabAddedWaiter add_tab1(browser());
+ ::ShowSingletonTab(browser(), page1);
+ add_tab1.Wait();
+ tab_count++;
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // DevTools start in docked mode (no new tab), in a separate process.
+ chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ WebContents* devtools = FindFirstDevToolsContents();
+ DCHECK(devtools);
+
+ // DevTools start in a separate process.
+ DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
+ host_count++;
+ EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
+ EXPECT_EQ(host_count, RenderProcessHostCount());
+
+ // close docked devtools
+ content::WindowedNotificationObserver close_observer(
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<content::WebContents>(devtools));
+ chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
+ close_observer.Wait();
+}
+
+// This class's goal is to close the browser window when a renderer process has
+// crashed. It does so by monitoring WebContents for RenderProcessGone event and
+// closing the passed in TabStripModel. This is used in the following test case.
+class WindowDestroyer : public content::WebContentsObserver {
+ public:
+ WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
+ : content::WebContentsObserver(web_contents), tab_strip_model_(model) {}
+
+ // Wait for the browser window to be destroyed.
+ void Wait() { ui_test_utils::WaitForBrowserToClose(); }
+
+ void RenderProcessGone(base::TerminationStatus status) override {
+ tab_strip_model_->CloseAllTabs();
+ }
+
+ private:
+ TabStripModel* tab_strip_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
+};
+
+// Test to ensure that while iterating through all listeners in
+// RenderProcessHost and invalidating them, we remove them properly and don't
+// access already freed objects. See http://crbug.com/255524.
+// Crashes on Win/Linux only. http://crbug.com/606485.
+#if defined(OS_WIN) || defined(OS_LINUX)
+#define MAYBE_CloseAllTabsDuringProcessDied \
+ DISABLED_CloseAllTabsDuringProcessDied
+#else
+#define MAYBE_CloseAllTabsDuringProcessDied CloseAllTabsDuringProcessDied
+#endif
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
+ MAYBE_CloseAllTabsDuringProcessDied) {
+ GURL url(chrome::kChromeUIOmniboxURL);
+
+ ui_test_utils::NavigateToURL(browser(), url);
+ WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);
+
+ content::WebContentsAddedObserver wc2_observer;
+ content::ExecuteScriptAsync(
+ wc1, content::JsReplace("window.open($1, '_blank')", url));
+ WebContents* wc2 = wc2_observer.GetWebContents();
+ content::TestNavigationObserver nav_observer(wc2, 1);
+ nav_observer.Wait();
+
+ EXPECT_EQ(2, browser()->tab_strip_model()->count());
+ EXPECT_EQ(wc1->GetMainFrame()->GetLastCommittedURL(),
+ wc2->GetMainFrame()->GetLastCommittedURL());
+ EXPECT_EQ(wc1->GetMainFrame()->GetProcess(),
+ wc2->GetMainFrame()->GetProcess());
+
+ // Create an object that will close the window on a process crash.
+ WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
+
+ // Kill the renderer process, simulating a crash. This should the ProcessDied
+ // method to be called. Alternatively, RenderProcessHost::OnChannelError can
+ // be called to directly force a call to ProcessDied.
+ content::ScopedAllowRendererCrashes allow_renderer_crashes(wc1);
+ wc1->GetMainFrame()->GetProcess()->Shutdown(-1);
+
+ destroyer.Wait();
+}
+
+// Sets up the browser in order to start the tests with two tabs open: one
+// called "no audio" in foreground and another called "audio" in background with
+// audio in playing state. Also sets up the variables containing the process
+// associated with each tab, the urls of the two pages and the WebContents of
+// the "audio" page.
+class ChromeRenderProcessHostBackgroundingTestWithAudio
+ : public ChromeRenderProcessHostTest {
+ public:
+ ChromeRenderProcessHostBackgroundingTestWithAudio() {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ChromeRenderProcessHostTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kProcessPerTab);
+
+ command_line->AppendSwitchASCII(
+ switches::kAutoplayPolicy,
+ switches::autoplay::kNoUserGestureRequiredPolicy);
+ }
+
+ void SetUpOnMainThread() override {
+ ChromeRenderProcessHostTest::SetUpOnMainThread();
+ ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+
+ // Set up the server and get the test pages.
+ base::FilePath test_data_dir;
+ ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
+ embedded_test_server()->ServeFilesFromDirectory(
+ test_data_dir.AppendASCII("chrome/test/data/"));
+ audio_url_ = embedded_test_server()->GetURL("/extensions/loop_audio.html");
+ no_audio_url_ = embedded_test_server()->GetURL("/title1.html");
+
+ embedded_test_server()->StartAcceptingConnections();
+
+ // Open a browser, navigate to the audio page and get its WebContents.
+ ui_test_utils::NavigateToURL(browser(), audio_url_);
+ audio_tab_web_contents_ =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Create a new tab for the no audio page and confirm that the process of
+ // each tab is different and that both are valid.
+ audio_process_ = ProcessFromHandle(audio_tab_web_contents_->GetMainFrame()
+ ->GetProcess()
+ ->GetProcess()
+ .Handle());
+ WebContents* wc = ShowSingletonTab(no_audio_url_);
+ no_audio_process_ = ProcessFromHandle(
+ wc->GetMainFrame()->GetProcess()->GetProcess().Handle());
+ ASSERT_NE(audio_process_.Pid(), no_audio_process_.Pid());
+ ASSERT_TRUE(no_audio_process_.IsValid());
+ ASSERT_TRUE(audio_process_.IsValid());
+#if defined(OS_MACOSX)
+ port_provider_ = content::BrowserChildProcessHost::GetPortProvider();
+#endif // defined(OS_MACOSX)
+ }
+
+ protected:
+ void WaitUntilBackgrounded(const base::Process& lhs,
+ bool lhs_backgrounded,
+ const base::Process& rhs,
+ bool rhs_backgrounded) {
+ while (IsProcessBackgrounded(lhs) != lhs_backgrounded ||
+ IsProcessBackgrounded(rhs) != rhs_backgrounded) {
+ base::RunLoop().RunUntilIdle();
+ base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ }
+ }
+
+ GURL audio_url_;
+ GURL no_audio_url_;
+
+ base::Process audio_process_;
+ base::Process no_audio_process_;
+
+ content::WebContents* audio_tab_web_contents_;
+
+ private:
+ bool IsProcessBackgrounded(const base::Process& process) {
+#if defined(OS_MACOSX)
+ return process.IsProcessBackgrounded(port_provider_);
+#else
+ return process.IsProcessBackgrounded();
+#endif
+ }
+
+#if defined(OS_MACOSX)
+ base::PortProvider* port_provider_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderProcessHostBackgroundingTestWithAudio);
+};
+
+// Test to make sure that a process is backgrounded when the audio stops playing
+// from the active tab and there is an immediate tab switch.
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTestWithAudio,
+ ProcessPriorityAfterStoppedAudio) {
+ // This test is invalid on platforms that can't background.
+ if (!base::Process::CanBackgroundProcesses())
+ return;
+
+ ShowSingletonTab(audio_url_);
+
+ // Wait until the no audio page is backgrounded and the audio page is not
+ // backgrounded.
+ WaitUntilBackgrounded(no_audio_process_, true, audio_process_, false);
+ // Pause the audio and immediately switch to the no audio tab.
+ ASSERT_TRUE(content::ExecuteScript(
+ audio_tab_web_contents_,
+ "document.getElementById('audioPlayer').pause();"));
+ ShowSingletonTab(no_audio_url_);
+
+ // Wait until the no audio page is not backgrounded and the audio page is
+ // backgrounded.
+ WaitUntilBackgrounded(no_audio_process_, false, audio_process_, true);
+}
+
+// Test to make sure that a process is backgrounded automatically when audio
+// stops playing from a hidden tab.
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTestWithAudio,
+ ProcessPriorityAfterAudioStopsOnNotVisibleTab) {
+ // This test is invalid on platforms that can't background.
+ if (!base::Process::CanBackgroundProcesses())
+ return;
+
+ // Wait until the two pages are not backgrounded.
+ WaitUntilBackgrounded(audio_process_, false, no_audio_process_, false);
+ // Stop the audio.
+ ASSERT_TRUE(content::ExecuteScript(
+ audio_tab_web_contents_,
+ "document.getElementById('audioPlayer').pause();"));
+
+ // Wait until the no audio page is not backgrounded and the audio page is
+ // backgrounded.
+ WaitUntilBackgrounded(no_audio_process_, false, audio_process_, true);
+}
+
+// Test to make sure that a process is un-backgrounded automatically when
+// audio
+// starts playing from a backgrounded tab.
+IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTestWithAudio,
+ ProcessPriorityAfterAudioStartsFromBackgroundTab) {
+ // This test is invalid on platforms that can't background.
+ if (!base::Process::CanBackgroundProcesses())
+ return;
+
+ // Stop the audio.
+ ASSERT_TRUE(content::ExecuteScript(
+ audio_tab_web_contents_,
+ "document.getElementById('audioPlayer').pause();"));
+
+ WaitUntilBackgrounded(no_audio_process_, false, audio_process_, true);
+
+ // Start the audio from the backgrounded tab.
+ ASSERT_TRUE(
+ content::ExecuteScript(audio_tab_web_contents_,
+ "document.getElementById('audioPlayer').play();"));
+
+ // Wait until the two pages are not backgrounded.
+ WaitUntilBackgrounded(no_audio_process_, false, audio_process_, false);
+}
diff --git a/chromium/chrome/browser/resources/chromeos/arc_support/manifest.json b/chromium/chrome/browser/resources/chromeos/arc_support/manifest.json
deleted file mode 100644
index 6f58ee840f7..00000000000
--- a/chromium/chrome/browser/resources/chromeos/arc_support/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "app": {
- "background": {
- "scripts": [ "background.js" ]
- },
- "content_security_policy": "default-src 'self'; style-src 'self' chrome://resources/ 'unsafe-inline'; font-src 'self' chrome://resources/; img-src 'self' data: chrome://theme/; script-src chrome://resources/ 'self'"
- },
- "description": "Play Store",
- "display_in_launcher": false,
- // cnbgggchhmkkdmeppjobngjoejnihlei
- "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnYsWobpgYLBvlN3PIFXyDwcCqRKcL+1swMf4f1Dnqn1+TYqbGW51ZNQbRo8xgE7y+0D8cz8fE0kZsQXhE7gzDKEyUgePNU75GQ3I9xdqsoNYWHGPegf2p6azj0kzJKCE+aaO4Ys7tSvvIniHfVGFuaqaS1m1JYP0Mlhv+pkwbrbVV3Ymdzb4tYP2y8jrISmyhrzlX9E2p9RU2ObSgZCUgZofMkwWZd1tyZN089sjxcPlN/RGHsB0NtpgFFhSAs9tSrRcvUUcoUTDQYeDB4qBioCJ0QUtjRj21xqsotjUQM4GFOkAxmNbijghvKoMwSulvEbgrb0x3dQl+y+ecr0ybwIDAQAB",
- "manifest_version": 2,
- "name": "Play Store",
- "icons": {
- "32": "icon/32.png",
- "192": "icon/192.png"
- },
- "permissions": [ "webview",
- "browser",
- "nativeMessaging",
- "chrome://resources/" ],
- "version": "0.2.0.0"
-}
diff --git a/chromium/chrome/browser/resources/chromeos/braille_ime/manifest.json b/chromium/chrome/browser/resources/chromeos/braille_ime/manifest.json
deleted file mode 100644
index 3f0f9e1c898..00000000000
--- a/chromium/chrome/browser/resources/chromeos/braille_ime/manifest.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "name": "Braille IME",
- "description": "Braille Input Method Extension.",
- "version": "1.0",
- "background": {
- "scripts": [ "braille_ime.js", "main.js" ],
- "persistent": true
- },
- // chrome-extension://jddehjeebkoimngcbdkaahpobgicbffp
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvDjqqYESDQe3OcI65JctUYLSlQ7RAd902VUw+RO/70fJ7SSkg8+2y+5paD6+g8f6wgFsgVsbTX2UM+tsmGKWR23bgSQxYhfZUZgP7qFdk72hGRUnKnXA+JOJ5maI4v+w18WPTWYOFJt2NOvat+GKKF0CAFQG+z2Ucn/sRZVfnrQIDAQAB",
- "manifest_version": 2,
- "permissions": [
- "input"
- ],
- "ime_path": "chromeos/braille_ime",
- "input_components": [
- {
- "name": "Braille Keyboard",
- "type": "ime",
- "id": "braille",
- "indicator": "\u2803\u2817\u2807", // Unicode of 'brl' in English (and many other) braille codes.
- "language": ["None"],
- "description": "Braille hardware keyboard"
- }
- ]
-}
diff --git a/chromium/chrome/browser/resources/chromeos/camera/manifest.json b/chromium/chrome/browser/resources/chromeos/camera/manifest.json
deleted file mode 100644
index 894b240e3bb..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "Camera",
- "display": "standalone",
- "icons": [
- {
- "src": "src/images/camera_app_icons_128.png",
- "sizes": "128x128",
- "type": "image/png"
- },
- {
- "src": "src/images/camera_app_icons_48.png",
- "sizes": "48x48",
- "type": "image/png"
- }
- ],
- "start_url": "src/views/main.html",
- "theme_color": "#000000"
-}
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/am/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/am/messages.json
deleted file mode 100644
index d7f2d53b890..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/am/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "ካሜራ",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "በካሜራዎ áŽá‰¶á‹Žá‰½áŠ• ያንሱ እና ቪዲዮዎችን ይቅረጹá¢",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "የá‹á‹­áˆ ሥርዓት ስህተቶችá¢",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "የእርስዎ ካሜራ አáˆáŠ• ላይ ሊገአአይችáˆáˆá¢\nካሜራዠበአáŒá‰£á‰¡ መገናኘቱን እባክዎ á‹­áˆá‰µáˆ¹á¢",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ን ወደ á‹áŒ­ መላክ አáˆá‰°á‰»áˆˆáˆ",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "á‹á‹­áˆ‰áŠ• ማስቀመጥ አáˆá‰°áˆ³áŠ«áˆ",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "áŽá‰¶áŠ• ማንሳት አáˆá‰°á‰»áˆˆáˆ",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "መቅረጽ መጀመር አáˆá‰°á‰»áˆˆáˆ",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "áˆáŠ•áˆ አáˆá‰°á‰€áˆ¨áŒ¸áˆ",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ቀረጻ ቆሟáˆ",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ገቢር",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "ወደ ማዕከለ-ሥዕላት ሂድ",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "ሰርá‹",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ወደ ዲስክ ላክ",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "አትáˆ",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "ተመለስ",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "የማዕከለ-ሥዕላት áˆáˆµáˆŽá‰½",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "ቅንብሮች",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "የáርáŒáˆ­áŒ á‹“á‹­áŠá‰µ",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "የጊዜ ቆጣሪ ቆይታ",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "áŒá‰¥áˆ¨áˆ˜áˆáˆµ ላክ",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "እገዛ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 በ3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 በ4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "ወርቃማ á‹á‹µáˆ­",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 ሰከንዶች",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 ሰከንዶች",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "áŽá‰¶ ያንሱ",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "áŽá‰¶áŠ• ማንሳት አá‰áˆ",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "መቅረጽ ጀáˆáˆ­",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "መቅረጽ አá‰áˆ",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "ወደ ቀጣዩ ካሜራ ቀይር",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "ቪዲዮ ለመቅረጽ ቀይር",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "áŽá‰¶ ለማንሳት ይቀይሩ",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "የሰዓት ቆጣሪ",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "ማስተላለá",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "áርáŒáˆ­áŒ",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "ማይክሮáŽáŠ•",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "እሺ",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "ይቅር",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "እርáŒáŒ áŠ› áŠá‹Žá‰µ $file$ን ማስወገድ á‹­áˆáˆáŒ‹áˆ‰?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "እርáŒáŒ áŠ› áŠá‹Žá‰µ $count$ ንጥሎችን ማስወገድ á‹­áˆáˆáŒ‹áˆ‰?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "በካሜራዠየተáŠáˆ± áŽá‰¶á‹Žá‰½ ወይሠየተቀረጹ ቪዲዮዎች ወደ የá‹áˆ­á‹¶á‰½ አቃáŠá‹ ይወሰዳሉᢠበá‹á‹­áˆŽá‰½ á‹áˆµáŒ¥ ሊደርሱባቸዠይችላሉá¢\n\nየማከማቻ áˆá‰ƒá‹¶á‰½ ያላቸዠመተáŒá‰ áˆªá‹«á‹Žá‰½ የእርስዎን áŽá‰¶á‹Žá‰½ እና ቪዲዮዎች መዳረሻ ይኖራቸዋáˆá¢",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ar/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ar/messages.json
deleted file mode 100644
index 9c2cc276975..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ar/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "الكاميرا",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "يمكنك التقاط الصور وتسجيل الÙيديوهات باستخدام الكاميرا.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "أخطاء نظام الملÙات.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "الكاميرا غير متاحة حاليًا.\nÙŠÙرجى التحقّÙÙ‚ من اتصال الكاميرا بشكل٠صحيح.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "يتعذَّر تصدير $file$.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "يتعذَّر Ø­Ùظ الملÙ.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "يتعذّر التقاط الصورة.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "يتعذّر بدء التسجيل.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "لم يتمّ تسجيل أيّ بيانات.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "تمّ إيقا٠التسجيل.",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "الكاميرا $camera$ نشÙطة.",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "الانتقال إلى المعرض",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "حذÙ",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "تصدير إلى القرص",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "طباعة",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "رجوع",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "صور المعرض",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "إعدادات",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "نوع الشبكة",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "مدة المؤقّÙت",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "إرسال تعليقات",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "مساعدة",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "وضع 3 × 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "وضع 3 ÙÙŠ 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "وضع 4 × 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "وضع 4 ÙÙŠ 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "وضع النسبة الذهبية",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 ثوانÙ",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "Û±Û° ثوانÙ",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "التقاط صورة",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "إيقا٠التقاط الصورة",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "بدء التسجيل",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "إيقا٠التسجيل",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "التبديل إلى الكاميرا التالية",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "تبديل لوضع تسجيل الÙيديو",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "تبديل لوضع التقاط الصور",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "الموقّÙت",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "النسخ المطابق",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "الشبكة",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "الميكروÙون",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "مواÙÙ‚",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "إلغاء",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "هل تريد Ùعلاً إزالة $file$ØŸ",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "هل تريد Ùعلاً إزالة $count$ عنصر؟",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "سيتمّ نقل الصور والÙيديوهات الملتقَطة بالكاميرا إلى مجلد \"التنزيلات\" ويمكنك الوصول إليها من خلال تطبيق \"ملÙات\".\n\nستتمكَّن التطبيقات من الوصول إلى صورك ÙˆÙيديوهاتك ÙÙŠ حال تزويدها بأذونات الاطّلاع على سعة التخزين.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/bg/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/bg/messages.json
deleted file mode 100644
index c7ae7bd4d9d..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/bg/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Камера",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Правете Ñнимки и запиÑвайте видеоклипове Ñ ÐºÐ°Ð¼ÐµÑ€Ð°Ñ‚Ð° Ñи.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Грешки в ÑиÑтемата за Ñъхранение на файлове.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "ПонаÑтоÑщем нÑма доÑтъп до камерата ви.\nМолÑ, проверете дали е Ñвързана правилно.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ не може да Ñе екÑпортира",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Файлът не може да бъде запазен",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Ðе може да Ñе направи Ñнимка",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "ЗапиÑването не може да Ñе Ñтартира",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Ðищо не е запиÑано",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ЗапиÑването ÑпрÑ",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ е активна",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Към галериÑта",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Изтриване",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ЕкÑпортиране в диÑка",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Печат",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Ðазад",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Ð˜Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² галериÑта",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "ÐаÑтройки",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Тип решетка",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ПродължителноÑÑ‚ на таймера",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Изпращане на отзиви",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Помощ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 на 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 на 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Златното Ñечение",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 Ñекунди",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 Ñекунди",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Правене на Ñнимка",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Спиране на правенето на Ñнимка",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Започване на запиÑ",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Спиране на запиÑа",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Превключване към Ñледващата камера",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Превключване към режима за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° видеоклипове",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Превключване към режима за правене на Ñнимки",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Таймер",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð·Ð° огледално обръщане",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Решетка",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Микрофон",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Отказ",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "ÐаиÑтина ли иÑкате да премахнете $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "ÐаиÑтина ли иÑкате да премахнете $count$ елемента?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Ðаправените Ñ ÐºÐ°Ð¼ÐµÑ€Ð°Ñ‚Ð° Ñнимки и видеоклипове ще бъдат премеÑтени в папката „ИзтеглÑниÑ“. Можете да оÑъщеÑтвите доÑтъп до Ñ‚ÑÑ… от „Файлове“.\n\nПриложениÑта Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð·Ð° хранилището ще имат доÑтъп до Ñнимките и видеоклиповете ви.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/bn/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/bn/messages.json
deleted file mode 100644
index d3055e094ce..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/bn/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "আপনার কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾ বà§à¦¯à¦¬à¦¹à¦¾à¦° করে ছবি তà§à¦²à§à¦¨ à¦à¦¬à¦‚ ভিডিও রেকরà§à¦¡ করà§à¦¨à¥¤",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ফাইল সিসà§à¦Ÿà§‡à¦®à§‡ সমসà§à¦¯à¦¾à¥¤",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "আপনার কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾à¦Ÿà¦¿ বরà§à¦¤à¦®à¦¾à¦¨à§‡ উপলভà§à¦¯ নয়।\nকà§à¦¯à¦¾à¦®à§‡à¦°à¦¾à¦Ÿà¦¿ সঠিকভাবে কানেকà§à¦Ÿ করা আছে কিনা দেখে নিন।",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$টি à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ করা যায়নি",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ফাইলটি সেভ করা যায়নি",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ফটো তোলা যায়নি",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "রেকরà§à¦¡à¦¿à¦‚ শà§à¦°à§ করা যায়নি",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "কিছà§à¦‡ রেকরà§à¦¡ করা হয়নি",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "রেকরà§à¦¡à¦¿à¦‚ বনà§à¦§ করা হয়েছে",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ চালৠআছে",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "গà§à¦¯à¦¾à¦²à¦¾à¦°à¦¿à¦¤à§‡ যান",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "মà§à¦›à§à¦¨",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ডিসà§à¦•à§‡ à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "পà§à¦°à¦¿à¦¨à§à¦Ÿ করà§à¦¨",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "ফিরে যান",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "গà§à¦¯à¦¾à¦²à¦¾à¦°à¦¿à¦° ছবিগà§à¦²à¦¿",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "সেটিংস",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "গà§à¦°à¦¿à¦¡à§‡à¦° ধরন",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "টাইমার সময়কাল",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "মতামত দিন",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "সহায়তা",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "৩ x ৩",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "৩ বাই ৩",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "৪ x ৪",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "৪ বাই ৪",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "গোলà§à¦¡à§‡à¦¨ রেশিও",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "৩ সেকেনà§à¦¡",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "১০ সেকেনà§à¦¡",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "ফটো তà§à¦²à§à¦¨",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ফটো তোলা বনà§à¦§ করà§à¦¨",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "রেকরà§à¦¡à¦¿à¦‚ শà§à¦°à§ করà§à¦¨",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "রেকরà§à¦¡à¦¿à¦‚ বনà§à¦§ করà§à¦¨",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "পরবরà§à¦¤à§€ কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾à¦¤à§‡ যান",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "ভিডিও রেকরà§à¦¡ করতে পরিবরà§à¦¤à¦¨ করà§à¦¨",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "ফটো তà§à¦²à¦¤à§‡ পরিবরà§à¦¤à¦¨ করà§à¦¨",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "টাইমার",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "মিররিং",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "গà§à¦°à¦¿à¦¡",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "মাইকà§à¦°à§‹à¦«à§‹à¦¨",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ঠিক আছে",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "বাতিল করà§à¦¨",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "আপনি কি সতà§à¦¯à¦¿à¦‡ $file$ সরিয়ে ফেলতে চান?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "আপনি কি সতà§à¦¯à¦¿à¦‡ $count$টি আইটেম সরিয়ে ফেলতে চান?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾à§Ÿ তোলা ফটো à¦à¦¬à¦‚ ভিডিও ডাউনলোড ফোলà§à¦¡à¦¾à¦°à§‡ সরিয়ে দেওয়া হবে। আপনি সেগà§à¦²à¦¿ ফাইল বিকলà§à¦ª থেকে অà§à¦¯à¦¾à¦•à§à¦¸à§‡à¦¸ করতে পারবেন।\n\nযে অà§à¦¯à¦¾à¦ªà¦—à§à¦²à¦¿à¦•à§‡ সà§à¦Ÿà§‹à¦°à§‡à¦œ বà§à¦¯à¦¬à¦¹à¦¾à¦°à§‡à¦° অনà§à¦®à¦¤à¦¿ দেওয়া আছে সেগà§à¦²à¦¿ আপনার ফটো à¦à¦¬à¦‚ ভিডিও অà§à¦¯à¦¾à¦•à§à¦¸à§‡à¦¸ করতে পারবে।",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ca/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ca/messages.json
deleted file mode 100644
index 3e72fd0d06c..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ca/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Càmera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Fes fotos i grava vídeos amb la càmera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Errors del sistema de fitxers.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "La càmera no està disponible en aquest moment.\nComprova que la càmera estigui ben connectada.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "No es pot exportar el fitxer $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "No es pot desar el fitxer",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "No es pot fer la foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "No es pot iniciar la gravació",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "No s'ha gravat res",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "S'ha aturat la gravació",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Càmera $camera$ activada",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ves a la galeria",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Suprimeix",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exporta al disc",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Imprimeix",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Torna",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imatges de la galeria",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Configuració",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipus de quadrícula",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Durada del temporitzador",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Envia suggeriments",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ajuda",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 per 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 per 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Proporció àuria",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 segons",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 segons",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Fes una foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "No facis la foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Comença a gravar",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Atura la gravació",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Canvia a la càmera següent",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Canvia al mode per gravar vídeo",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Canvia al mode per fer fotos",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Temporitzador",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Rèplica",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Quadrícula",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Micròfon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "D'acord",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancel·la",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Confirmes que vols suprimir el fitxer $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Confirmes que vols suprimir $count$ elements?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Les fotos i els vídeos que es facin amb la càmera es mouran a la carpeta Baixades. Pots accedir-hi des de Fitxers.\n\nLes aplicacions amb permisos d'emmagatzematge tindran accés a les fotos i als vídeos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/cs/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/cs/messages.json
deleted file mode 100644
index cc863090688..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/cs/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Fotoaparát",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Foťte a nahrávejte videa pomocí fotoaparátu.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Chyby systému souborů.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Fotoaparát momentálně není dostupný.\nZkontrolujte, zda je fotoaparát správně připojen.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Soubor $file$ se nepodařilo exportovat",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Soubor se nepodařilo uložit",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Fotku se nepodařilo pořídit",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nahrávání se nepodařilo zahájit",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nic nebylo zaznamenáno",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Nahrávání bylo zastaveno",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ je aktivní",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Přejít do galerie",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Smazat",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportovat na disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Tisknout",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Zpět",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Obrázky v galerii",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Nastavení",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Typ mřížky",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Trvání ÄasovaÄe",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Odeslat zpětnou vazbu",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Nápověda",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 × 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 krát 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 × 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 krát 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Zlatý řez",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekundy",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekund",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Vyfotit",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Zastavit focení",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Zahájit nahrávání",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Zastavit nahrávání",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Přepnout na další fotoaparát",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Přepnout na záznam videa",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Přepnout na focení",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ÄŒasovaÄ",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Zrcadlení",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Mřížka",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Zrušit",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Opravdu chcete soubor $file$ odstranit?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Opravdu chcete tyto položky ($count$) odstranit?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Fotky a videa pořízená fotoaparátem se přesunou do složky stažených souborů. Najdete je v aplikaci Soubory.\n\nK vašim fotkám a videím budou mít přístup aplikace s oprávněním k úložišti.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/da/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/da/messages.json
deleted file mode 100644
index 8917dcd0ca4..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/da/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Tag billeder, og optag videoer med dit kamera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Der er opstået fejl i filsystemet.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Dit kamera er i øjeblikket utilgængeligt.\nTjek, om kameraet er tilsluttet korrekt.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ kan ikke eksporteres",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Filen kan ikke gemmes",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Det er ikke muligt at tage billeder",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Det er ikke muligt at starte en optagelse",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Der er ingen optagelser",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Optagelsen er stoppet",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ er aktivt",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "GÃ¥ til galleri",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Slet",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Eksportér til disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Udskriv",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "GÃ¥ tilbage",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galleribilleder",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Indstillinger",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Gittertype",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Varighed for timer",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Giv feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Hjælp",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3x3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 gange 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4x4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 gange 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Det gyldne snit",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekunder",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekunder",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Tag billede",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Stop med at tage billedet",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Start optagelse",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Stop optagelse",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Skift til næste kamera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Skift til videooptagelse",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Skift til billedtilstand",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Spejling",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Gitter",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Annuller",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Er du sikker på, at du vil fjerne $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Er du sikker på, at du vil fjerne $count$ elementer?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Billeder og videoer, der er taget med kameraet, flyttes til mappen Downloads. Du kan finde dem under Filer.\n\nApps med lagertilladelser vil have adgang til dine billeder og videoer.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/de/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/de/messages.json
deleted file mode 100644
index ca71f64b8b6..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/de/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Nehmen Sie Fotos und Videos mit Ihrer Kamera auf.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Fehler im Dateisystem.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Ihre Kamera ist derzeit nicht verfügbar.\nÜberprüfen Sie bitte, ob die Kamera richtig verbunden ist.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ konnte nicht exportiert werden",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Datei konnte nicht gespeichert werden",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Foto konnte nicht aufgenommen werden",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Videoaufnahme konnte nicht gestartet werden",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Keine Aufzeichnung",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Aufzeichnung wurde beendet",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ aktiv",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Zur Galerie",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Löschen",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Auf Festplatte exportieren",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Drucken",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Zurück",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galeriebilder",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Einstellungen",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Rastertyp",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Timerdauer",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Feedback geben",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Hilfe",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 mal 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 mal 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Goldener Schnitt",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 Sekunden",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 Sekunden",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Foto aufnehmen",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Fotoaufnahme beenden",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Video aufnehmen",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Videoaufnahme beenden",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Zur nächsten Kamera wechseln",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Zu Videomodus wechseln",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Zu Fotomodus wechseln",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Spiegeln",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Raster",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Abbrechen",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Möchten Sie \"$file$\" wirklich entfernen?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Möchten Sie $count$ Elemente wirklich entfernen?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Mit der Kamera aufgenommene Fotos und Videos werden in den Ordner \"Downloads\" verschoben. Sie können sie in der App \"Dateien\" ansehen.\n\nApps mit Speicherberechtigungen haben Zugriff auf Ihre Fotos und Videos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/el/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/el/messages.json
deleted file mode 100644
index fb0c3d6edba..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/el/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "ΚάμεÏα",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "ΤÏαβήξτε φωτογÏαφίες και βίντεο με την κάμεÏά σας.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ΑÏχειοθετήστε σφάλματα συστήματος.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Η κάμεÏά σας δεν είναι διαθέσιμη Ï€Ïος το παÏόν.\nΕλέγξτε αν η κάμεÏα έχει συνδεθεί σωστά.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Δεν είναι δυνατή η εξαγωγή του αÏχείου $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Δεν είναι δυνατή η αποθήκευση του αÏχείου",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Δεν είναι δυνατή η λήψη φωτογÏαφίας",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Δεν είναι δυνατή η έναÏξη της εγγÏαφής",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Καμία εγγÏαφή",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Η εγγÏαφή διακόπηκε",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ενεÏγή",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Μετάβαση στο gallery",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "ΔιαγÏαφή",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Εξαγωγή σε δίσκο",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "ΕκτÏπωση",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Πίσω",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Εικόνες gallery",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Ρυθμίσεις",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "ΤÏπος πλέγματος",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ΔιάÏκεια χÏονομέτÏου",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Αποστολή σχολίων",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Βοήθεια",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Ιδανικός λόγος διαστάσεων",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 δευτεÏόλεπτα",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 δευτεÏόλεπτα",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Λήψη φωτογÏαφίας",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Διακοπή λήψης φωτογÏαφίας",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "ΈναÏξη εγγÏαφής",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Διακοπή εγγÏαφής",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Μετάβαση στην επόμενη κάμεÏα",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Μετάβαση στη λειτουÏγία εγγÏαφής βίντεο",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Μετάβαση στη λειτουÏγία λήψης φωτογÏαφίας",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ΧÏονόμετÏο",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "ΚατοπτÏισμός",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Πλέγμα",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "ΜικÏόφωνο",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ΟΚ",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "ΑκÏÏωση",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Είστε βέβαιοι ότι θέλετε να καταÏγήσετε το αÏχείο $file$;",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Είστε βέβαιοι ότι θέλετε να καταÏγήσετε $count$ στοιχεία;",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Οι φωτογÏαφίες και τα βίντεο που λήφθηκαν με την κάμεÏα θα μετακινηθοÏν στον φάκελο \"Λήψεις\". ΜποÏείτε να τα δείτε στα ΑÏχεία.\n\nΟι εφαÏμογές με άδειες Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου θα έχουν Ï€Ïόσβαση στις φωτογÏαφίες και τα βίντεό σας.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json
deleted file mode 100644
index 3a824bee776..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json
+++ /dev/null
@@ -1,356 +0,0 @@
-{
- "NAME": {
- "message": "Camera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Take photos and record videos with your camera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "File system errors.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Your camera is currently unavailable.\nPlease check if the camera is properly connected.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Unable to export $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Unable to save the file",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Unable to take photo",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_TAKE_PORTRAIT_PHOTO_FAILED": {
- "message": "Unable to take portrait photo",
- "description": "Error message shown when failing to take portrait photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Unable to start recording",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nothing recorded",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "ERROR_MSG_EXPERT_MODE_NOT_SUPPORTED": {
- "message": "Expert mode is not supported on this device",
- "description": "Error message when the device does not support expert mode but tries to enable it."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Recording stopped",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ active",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Go to gallery",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Delete",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Export to disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Print",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Go back",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Gallery images",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Settings",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Grid type",
- "description": "Label for the button of grid-type options."
- },
- "LABEL_30FPS": {
- "message": "30 FPS",
- "description": "Label showing current state of 30 FPS on tooltip of toggle 60 FPS recording checkbox."
- },
- "LABEL_60FPS": {
- "message": "60 FPS",
- "description": "Label showing current state of 60 FPS on tooltip of toggle 60 FPS recording checkbox."
- },
- "TOGGLE_60FPS_BUTTON": {
- "message": "60 FPS",
- "description": "Label for the checkbox to toggle 60 FPS recording."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Timer duration",
- "description": "Label for the button of timer-duration options."
- },
- "CAMERA_RESOLUTION_BUTTON": {
- "message": "Camera resolution",
- "description": "Label for the button of camera resolution options."
- },
- "EXPERT_MODE_BUTTON": {
- "message": "Expert mode",
- "description": "Label for the button of expert mode options."
- },
- "EXPERT_PREVIEW_METADATA": {
- "message": "Preview metadata",
- "description": "Label for expert mode option: preview metadata."
- },
- "EXPERT_SAVE_METADATA": {
- "message": "Save metadata",
- "description": "Label for expert mode option: save metadata."
- },
- "FEEDBACK_BUTTON": {
- "message": "Send feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Help",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 by 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 by 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Golden ratio",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 seconds",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 seconds",
- "description": "Label for timer-duration: 10 seconds."
- },
- "LABEL_PHOTO_RESOLUTION": {
- "message": "($aspect_ratio_width$:$aspect_ratio_height$) $megapixel$ mega pixel",
- "description": "Label for photo resolution options of specific resolution.",
- "placeholders": {
- "aspect_ratio_width": {
- "content": "$1",
- "example": "16"
- },
- "aspect_ratio_height": {
- "content": "$2",
- "example": "9"
- },
- "megapixel": {
- "content": "$3",
- "example": "0.9"
- }
- }
- },
- "LABEL_DETAIL_PHOTO_RESOLUTION": {
- "message": "($aspect_ratio_width$:$aspect_ratio_height$ - $width$x$height$) $megapixel$ mega pixel",
- "description": "Label for detail photo resolution options of specific resolution.",
- "placeholders": {
- "aspect_ratio_width": {
- "content": "$1",
- "example": "16"
- },
- "aspect_ratio_height": {
- "content": "$2",
- "example": "9"
- },
- "width": {
- "content": "$3",
- "example": "1920"
- },
- "height": {
- "content": "$4",
- "example": "1080"
- },
- "megapixel": {
- "content": "$5",
- "example": "0.9"
- }
- }
- },
- "LABEL_VIDEO_RESOLUTION": {
- "message": "HD $height$p ($width$:$height$)",
- "description": "Label for video resolution options of specific resolution.",
- "placeholders": {
- "height": {
- "content": "$1",
- "example": "720"
- },
- "width": {
- "content": "$2",
- "example": "1280"
- }
- }
- },
- "LABEL_FRONT_CAMERA": {
- "message": "Front camera",
- "description": "Label for front camera."
- },
- "LABEL_BACK_CAMERA": {
- "message": "Back camera",
- "description": "Label for back camera."
- },
- "LABEL_EXTERNAL_CAMERA": {
- "message": "External camera",
- "description": "Label for external camera."
- },
- "PHOTO_RESOLUTION_BUTTON": {
- "message": "Photo resolution",
- "description": "Label for the button of photo-resolution options."
- },
- "VIDEO_RESOLUTION_BUTTON": {
- "message": "Video resolution",
- "description": "Label for the button of video-resolution options."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Take photo",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Stop taking photo",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Start recording",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Stop recording",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Switch to next camera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Switch to record video",
- "description": "Label for spoken feedback to read out switch to record video mode button."
- },
- "LABEL_SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Switch to take photo",
- "description": "Label for spoken feedback to read out switch to take photo mode button."
- },
- "LABEL_SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Photo",
- "description": "Label for the button to switch to take photo mode."
- },
- "SWITCH_TAKE_SQUARE_PHOTO_BUTTON": {
- "message": "Switch to take square photo",
- "description": "Label for spoken feedback to read out switch to take square photo mode button."
- },
- "LABEL_SWITCH_TAKE_SQUARE_PHOTO_BUTTON": {
- "message": "Square",
- "description": "Label for the button to switch to take square photo mode."
- },
- "SWITCH_TAKE_PORTRAIT_PHOTO_BUTTON": {
- "message": "Switch to take portrait photo",
- "description": "Label for spoken feedback to read out switch to take portrait photo mode button."
- },
- "LABEL_SWITCH_TAKE_PORTRAIT_PHOTO_BUTTON": {
- "message": "Portrait",
- "description": "Label for the button to switch to take portrait photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Mirroring",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grid",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microphone",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancel",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Do you really want to remove $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Do you really want to remove $count$ items?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Photos and videos taken with the camera will be moved to the Downloads folder. You can access them in Files.\n\nApps with storage permissions will have access to your photos and videos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- },
- "CONFIRM_REVIEW_BUTTON": {
- "message": "Confirm",
- "description": "Label for the confirm button to confirm with the reviewed photo or video."
- },
- "CANCEL_REVIEW_BUTTON": {
- "message": "Cancel",
- "description": "Label for the cancel button to cancel with the reviewed photo or video."
- },
- "PLAY_RESULT_VIDEO_BUTTON": {
- "message": "Play video",
- "description": "Label for the button to play video."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/en_GB/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/en_GB/messages.json
deleted file mode 100644
index 4e9f00a5587..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/en_GB/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Camera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Take photos and record videos with your camera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "File system errors.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Your camera is currently unavailable.\nPlease check if the camera is properly connected.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Unable to export $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Unable to save the file",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Unable to take photo",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Unable to start recording",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nothing recorded",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Recording stopped",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ active",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Go to gallery",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Delete",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Export to disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Print",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Go back",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Gallery images",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Settings",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Grid type",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Timer duration",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Send feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Help",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 by 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 by 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Golden ratio",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 seconds",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 seconds",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Take photo",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Stop taking photo",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Start recording",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Stop recording",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Switch to next camera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Switch to record video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Switch to take photo",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Mirroring",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grid",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microphone",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancel",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Do you really want to remove $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Do you really want to remove $count$ items?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Photos and videos taken with the camera will be moved to the Downloads folder. You can access them in Files.\n\nApps with storage permissions will have access to your photos and videos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/es/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/es/messages.json
deleted file mode 100644
index 5c5c4b48cd2..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/es/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Cámara",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Haz fotos y graba vídeos con la cámara.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Se han producido errores en el sistema de archivos.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "La cámara no está disponible en este momento.\nComprueba si la cámara está conectada correctamente.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "No se ha podido exportar $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "No se ha podido guardar el archivo",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "No se ha podido hacer la foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "No se ha podido iniciar la grabación",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "No se ha grabado nada",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Se ha detenido la grabación",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ activa",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ir a la galería",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Eliminar",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportar a disco",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Imprimir",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Atrás",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imágenes de la galería",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Ajustes",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipo de cuadrícula",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Duración del temporizador",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Enviar comentarios",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ayuda",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3x3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "Tres por tres",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4x4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "Cuatro por cuatro",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Proporción áurea",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 segundos",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 segundos",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Hacer foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Detener foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Iniciar grabación",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Detener grabación",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Cambiar a la siguiente cámara",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Cambiar al modo de cámara de vídeo",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Cambiar al modo de cámara de fotos",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Temporizador",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Efecto espejo",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Cuadrícula",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Micrófono",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Aceptar",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancelar",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "¿Seguro que quieres eliminar $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "¿Seguro que quieres quitar estos $count$ elementos?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Las fotos y los vídeos hechos con la cámara se moverán a la carpeta Descargas. Podrás acceder a ellos con la aplicación Archivos.\n\nLas aplicaciones con permiso de almacenamiento podrán acceder a tus fotos y vídeos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/es_419/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/es_419/messages.json
deleted file mode 100644
index 8b9e030f6a4..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/es_419/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Cámara",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Toma fotos y graba videos con la cámara.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Errores en el sistema de archivos",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "La cámara no está disponible en este momento. Revisa si la conectaste correctamente.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "No se puede exportar $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "No se puede guardar el archivo",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "No se puede tomar la foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "No es posible comenzar a grabar",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "No se grabaron datos",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Se detuvo la grabación",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ activada",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ir a la galería",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Borrar",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportar al disco",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Imprimir",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Volver",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imágenes de la galería",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Configuración",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipo de cuadrícula",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Duración del temporizador",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Enviar comentarios",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ayuda",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 por 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 por 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Proporción áurea",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 segundos",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 segundos",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Tomar una foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Detener foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Iniciar grabación",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Detener grabación",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Cambiar a la siguiente cámara",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Cambiar al modo para grabar video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Cambiar al modo para tomar fotos",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Temporizador",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Duplicación",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Cuadrícula",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Micrófono",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Aceptar",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancelar",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "¿Confirmas que deseas quitar $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "¿Confirmas que deseas quitar $count$ elementos?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Las fotos y los videos que tomes con la cámara se moverán a la carpeta Descargas, que puedes abrir con la app de Archivos.\n\nLas apps que tengan permiso de almacenamiento podrán acceder a tus fotos y videos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/et/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/et/messages.json
deleted file mode 100644
index 337fdb1c17f..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/et/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kaamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Jäädvustage oma kaameraga fotosid ja salvestage videoid.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Failisüsteemi vead.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kaamera pole praegu saadaval.\nKontrollige, kas kaamera on korralikult ühendatud.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Faili $file$ eksportimine ebaõnnestus",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Faili salvestamine ebaõnnestus",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Foto jäädvustamine ebaõnnestus",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Salvestamise alustamine ebaõnnestus",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Midagi pole salvestatud",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Salvestamine on peatatud",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ on aktiivne",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ava galerii",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Kustuta",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Ekspordi kettale",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Prindi",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Mine tagasi",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galerii kujutised",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Seaded",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Ruudustiku tüüp",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Taimeri kestus",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Tagasiside saatmine",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Abi",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "kolm korda kolm",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "neli korda neli",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Kuldlõige",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekundit",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekundit",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Jäädvusta foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Peata foto jäädvustamine",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Alusta salvestamist",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Peata salvestamine",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Vaheta järgmisele kaamerale",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Vaheta video salvestamise režiimile",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Vaheta foto jäädvustamise režiimile",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Taimer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Peegeldamine",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Ruudustik",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Tühista",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Kas soovite kindlasti eemaldada faili $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Kas soovite kindlasti eemaldada need $count$ üksust?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Kaameraga tehtud fotod ja videod teisaldatakse kausta Allalaadimised. Pääsete neile juurde rakenduses Failid.\n\nSalvestusruumi kasutuse lubadega rakendustel on juurdepääs teie fotodele ja videotele.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fa/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fa/messages.json
deleted file mode 100644
index 1fa790394f2..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fa/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "دوربین",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "با دوربینتان عکس بگیرید Ùˆ Ùیلم‌برداری کنید.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "خطاهایی در سیستم Ùایل وجود دارد.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "درحال‌حاضر دوربینتان دردسترس نیست.\nلطÙاً بررسی کنید Ú©Ù‡ دوربین به‌درستی متصل شده است یا نه.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "صادر کردن $file$ امکان‌پذیر نیست",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ذخیره کردن Ùایل امکان‌پذیر نیست",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "عکس‌برداری امکان‌پذیر نیست",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "شروع ضبط امکان‌پذیر نیست",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "چیزی ضبط نشد",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ضبط متوق٠شد",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ Ùعال است",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "رÙتن به گالری",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "حذÙ",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "صادر کردن به دیسک",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "چاپ",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "برگشت",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "تصاویر گالری",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "تنظیمات",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "نوع شبکه",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "مدت تایمر",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ارسال بازخورد",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "راهنما",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "۳ × ۳",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "۳ در ۳",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "۴ × ۴",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "۴ در ۴",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "نسبت طلایی",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "۳ ثانیه",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "۱۰ ثانیه",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "عکس گرÙتن",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "توق٠عکس‌برداری",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "شروع ضبط",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "توق٠ضبط",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "جابه‌جایی به دوربین بعدی",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "رÙتن به حالت Ùیلم‌برداری",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "رÙتن به حالت عکس‌برداری",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "تایمر",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "نمایش صÙحه‌نمایش روی دستگاه دیگر",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "شطرنجی",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "میکروÙون",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "تأیید",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "لغو",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "واقعاً می‌خواهید $file$ حذ٠شود؟",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "واقعاً می‌خواهید $count$ مورد حذ٠شود؟",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "عکس‌ها Ùˆ ویدیوهایی Ú©Ù‡ با دوربین گرÙته شده است، به پوشه «بارگیری‌ها» منتقل خواهد شد. می‌توانید در Files به آن‌ها دسترسی داشته باشید.\n\nبرنامه‌هایی Ú©Ù‡ مجوز Ùضای ذخیره‌سازی دارند، به عکس‌ها Ùˆ ویدیوهای شما دسترسی خواهند داشت.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fi/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fi/messages.json
deleted file mode 100644
index 34ec1550414..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fi/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Ota kuvia ja videoita kamerallasi.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Tiedostojärjestelmän virheitä",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kamerasi ei ole tällä hetkellä käytettävissä.\nTarkista, onko kamera liitetty oikein.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Vieminen epäonnistui: $file$.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Tiedoston tallennus epäonnistui.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Kuvan ottaminen epäonnistui.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Tallennuksen aloittaminen epäonnistui.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Mitään ei kuvattu.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Tallennus keskeytettiin.",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ on aktiivinen.",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Siirry galleriaan",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Poista",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Vie paikalliselle levylle",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Tulosta",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Takaisin",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Gallerian kuvat",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Asetukset",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Ruudukkotyyppi",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Ajastimen kesto",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Lähetä palautetta",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ohje",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3x3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3x3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4x4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4x4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Kultainen leikkaus",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekuntia",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekuntia",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Ota valokuva",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Lopeta kuvan ottaminen",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Aloita kuvaaminen",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Lopeta kuvaaminen",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Vaihda seuraavaan kameraan",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Vaihda videon kuvaamiseen",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Vaihda kuvan ottamiseen",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Ajastin",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Peilaus",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Ruudukko",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofoni",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Peruuta",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Haluatko varmasti poistaa tiedoston $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Haluatko varmasti poistaa $count$ kohdetta?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Kameralla otetut kuvat ja videot siirretään Lataukset-kansioon. Voit käyttää niitä avaamalla Tiedostot.\n\nJos sovelluksella on tallennustilan käyttöoikeus, se voi käyttää kuvia ja videoita.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fil/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fil/messages.json
deleted file mode 100644
index e9fd879b98e..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fil/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Camera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Kumuha ng mga larawan at mag-record ng mga video gamit ang iyong camera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Mga error sa file system.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kasalukuyang hindi available ang iyong camera.\nPakitingnan kung nakakonekta nang maayos ang camera.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Hindi na-export ang $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Hindi na-save ang file",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Hindi nakakuha ng larawan",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Hindi nasimulan ang pag-record",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Walang na-record",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Huminto ang pag-record",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Aktibo ang $camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Pumunta sa gallery",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "I-delete",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "I-export sa disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "I-print",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Bumalik",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Mga larawan sa gallery",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Mga Setting",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Uri ng grid",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Tagal ng timer",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Magpadala ng feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Tulong",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 by 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 by 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Golden ratio",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 segundo",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 segundo",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Kumuha ng larawan",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Ihinto ang pagkuha ng larawan",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Magsimulang mag-record",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Ihinto ang pag-record",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Lumipat sa susunod na camera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Lumipat sa pag-record ng video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Lumipat sa pagkuha ng larawan",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Pag-mirror",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grid",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikropono",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Kanselahin",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Gusto mo ba talagang alisin ang $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Gusto mo ba talagang alisin ang $count$ (na) item?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Ang mga larawan at video na nakunan gamit ang camera ay ililipat sa folder na Mga Download. Maa-access mo ang mga ito sa Mga File.\n\nAng mga app na may mga pahintulot sa storage ay magkakaroon ng access sa iyong mga larawan at video.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fr/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fr/messages.json
deleted file mode 100644
index d5864cfb1be..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/fr/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Appareil photo",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Prenez des photos et enregistrez des vidéos avec votre appareil photo.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Erreurs du système de fichiers.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Votre caméra est actuellement indisponible.\nVérifiez qu'elle est bien connectée.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Impossible d'exporter $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Impossible d'enregistrer le fichier",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Impossible de prendre la photo",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Impossible de démarrer l'enregistrement",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Aucune donnée enregistrée",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Enregistrement interrompu",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ active",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Accéder à la galerie",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Supprimer",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exporter vers le disque",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Imprimer",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Retour",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Images de la galerie",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Paramètres",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Type de grille",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Durée du minuteur",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Envoyer des commentaires",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Aide",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 par 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 par 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Rectangle d'or",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 secondes",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 secondes",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Prendre une photo",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Arrêter de prendre la photo",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Commencer l'enregistrement",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Arrêter l'enregistrement",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Passer à la caméra suivante",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Passer à l'enregistrement de vidéo",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Passer à la prise de photo",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Minuteur",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Duplication d'écran",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grille",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Micro",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Annuler",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Voulez-vous vraiment supprimer $file$ ?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Voulez-vous vraiment supprimer $count$ éléments ?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Les photos et vidéos prises avec l'appareil photo seront déplacées vers le dossier \"Téléchargements\". Vous pouvez y accéder via l'application Fichiers.\n\nLes applications disposant d'autorisations d'accès à l'espace de stockage pourront accéder à vos photos et vidéos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/gu/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/gu/messages.json
deleted file mode 100644
index 1c34c476982..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/gu/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "કૅમેરા",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "તમારા કૅમેરા વડે ફોટા લો અને વીડિઓ રેકોરà«àª¡ કરો.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ફાઇલ સિસà«àªŸàª®àª¨à«€ ભૂલો.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "તમારો કૅમેરા હાલમાં અનà«àªªàª²àª¬à«àª§ છે.\nકૃપા કરીને ચેક કરો કે કૅમેરા યોગà«àª¯ રીતે કનેકà«àªŸ થયેલો છે.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ને નિકાસ કરવામાં નિષà«àª«àª³ રહà«àª¯àª¾àª‚",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ફાઇલ સાચવવામાં નિષà«àª«àª³ રહà«àª¯àª¾àª‚",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ફોટો લેવામાં નિષà«àª«àª³ રહà«àª¯àª¾àª‚",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "રેકોરà«àª¡àª¿àª‚ગ શરૂ કરવામાં નિષà«àª«àª³ રહà«àª¯àª¾àª‚",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "કંઈ રેકોરà«àª¡ થયà«àª‚ નથી",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "રેકોરà«àª¡àª¿àª‚ગ બંધ કરà«àª¯à«àª‚",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ સકà«àª°àª¿àª¯",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "ગૅલેરી પર જાઓ",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "ડિલીટ કરો",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ડિસà«àª•àª®àª¾àª‚ નિકાસ કરો",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "પà«àª°àª¿àª¨à«àªŸ કરો",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "પાછા જાઓ",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "ગૅલેરી છબીઓ",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "સેટિંગ",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "ગà«àª°àª¿àª¡àª¨à«‹ પà«àª°àª•àª¾àª°",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ટાઇમરનો અવધિ",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "પà«àª°àª¤àª¿àª¸àª¾àª¦ મોકલો",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "સહાય",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 બાય 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 બાય 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "ગોલà«àª¡àª¨ રેશિયો",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 સેકનà«àª¡",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 સેકનà«àª¡",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "ફોટો લો",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ફોટા લેવાનà«àª‚ બંધ કરો",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "રેકોરà«àª¡àª¿àª‚ગ શરૂ કરો",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "રેકોરà«àª¡àª¿àª‚ગ રોકો",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "આગલા કૅમેરા પર સà«àªµàª¿àªš કરો",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "વીડિયો રેકોરà«àª¡ કરવા માટે સà«àªµàª¿àªš કરો",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "ફોટો લેવા માટે સà«àªµàª¿àªš કરો",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ટાઇમર",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "મીરરીંગ",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "ગà«àª°àª¿àª¡",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "માઇકà«àª°à«‹àª«à«‹àª¨",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ઓકે",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "રદ કરો",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "શà«àª‚ તમે ખરેખર $file$ને કાઢી નાખવા માગો છો?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "શà«àª‚ તમે ખરેખર $count$ આઇટમ કાઢી નાખવા માગો છો?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "કૅમેરા વડે લીધેલા ફોટા અને વીડિઓ ડાઉનલોડ ફોલà«àª¡àª°àª®àª¾àª‚ ખસેડવામાં આવશે. તમે તેઓને ફાઇલોમાં àªàª•à«àª¸à«‡àª¸ કરી શકશો.\n\nસà«àªŸà«‹àª°à«‡àªœ માટે પરવાનગી ધરાવતી àªàªª તમારા ફોટા તથા વીડિઓને àªàª•à«àª¸à«‡àª¸ કરી શકશે.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/he/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/he/messages.json
deleted file mode 100644
index 954d5ae4afa..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/he/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "מצלמה",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "×¦×™×œ×•× ×ª×ž×•× ×•×ª ×•×¡×¨×˜×•× ×™× ×‘×מצעות המצלמה.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "שגי×ות במערכת הקבצי×.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "המצלמה ×œ× ×–×ž×™× ×” כרגע.\nיש לבדוק ×× ×”×™× ×ž×—×•×‘×¨×ª כר×וי.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "×œ× × ×™×ª×Ÿ ×œ×™×™×¦× ×ת $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "×œ× × ×™×ª×Ÿ לשמור ×ת הקובץ",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "×œ× × ×™×ª×Ÿ ×œ×¦×œ× ×ת התמונה",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "×œ× × ×™×ª×Ÿ להתחיל ×‘×¦×™×œ×•× ×¡×¨×˜×•×Ÿ",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "×œ× ×¦×•×œ× ×¡×¨×˜×•×Ÿ",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "×¦×™×œ×•× ×”×¡×¨×˜×•×Ÿ הופסק",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ פעילה",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "מעבר ×ל הגלריה",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "מחיקה",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "×™×™×¦×•× ×œ×“×™×¡×§",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "הדפסה",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "חזרה",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "תמונות בגלריה",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "הגדרות",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "סוג הרשת",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "משך זמן בטיימר",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "שליחת משוב",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "עזרה",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "‎3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 על 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "‎4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 על 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "יחס הזהב",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 שניות",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 שניות",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "×¦×™×œ×•× ×”×ª×ž×•× ×”",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "הפסקת הצילו×",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "התחלת ×¦×™×œ×•× ×¡×¨×˜×•×Ÿ",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "הפסקת ×¦×™×œ×•× ×”×¡×¨×˜×•×Ÿ",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "מעבר למצלמה הב××”",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "מעבר ×œ×¦×™×œ×•× ×¡×¨×˜×•×Ÿ",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "מעבר ×œ×¦×™×œ×•× ×ª×ž×•× ×”",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "טיימר",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "שיקוף",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "רשת",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "מיקרופון",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "×ישור",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "ביטול",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "להסיר ×ת $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "להסיר ×ת $count$ הפריטי×?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "תמונות ×•×¡×¨×˜×•× ×™× ×©×¦×•×œ×ž×• ב×מצעות המצלמה יועברו ×ל התיקייה 'הורדות'. ×פשר לגשת ××œ×™×”× ×“×¨×š 'קבצי×'.\n\n×פליקציות ×¢× ×”×¨×©×ות ×חסון יקבלו גישה ×ל התמונות והסרטוני×.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hi/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hi/messages.json
deleted file mode 100644
index d0d1ba2d4ca..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hi/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "कैमरा",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "अपने कैमरे से फ़ोटो खींचें और वीडियो रिकॉरà¥à¤¡ करें.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "फ़ाइल सिसà¥à¤Ÿà¤® की गड़बड़ियां.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "आपका कैमरा फ़िलहाल उपलबà¥à¤§ नहीं है.\nकृपया देखें कि कैमरा ठीक तरह से कनेकà¥à¤Ÿ है या नहीं.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ à¤à¤•à¥à¤¸à¤ªà¥‹à¤°à¥à¤Ÿ नहीं की जा सकती",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "फ़ाइल सेव नहीं की जा सकी",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "फ़ोटो नहीं खींची जा सकी",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "रिकॉरà¥à¤¡à¤¿à¤‚ग शà¥à¤°à¥‚ नहीं की जा सकी",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "कà¥à¤› भी रिकॉरà¥à¤¡ नहीं हà¥à¤†",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "रिकॉरà¥à¤¡à¤¿à¤‚ग रूक गई है",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ चालू है",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "गैलरी में जाà¤à¤‚",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "मिटाà¤à¤‚",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "डिसà¥à¤• में ले जाà¤à¤‚",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "पà¥à¤°à¤¿à¤‚ट करें",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "वापस जाà¤à¤‚",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "गैलरी की इमेज",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "सेटिंग",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "गà¥à¤°à¤¿à¤¡ पà¥à¤°à¤•à¤¾à¤°",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "टाइमर का कà¥à¤² समय",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "फ़ीडबैक भेजें",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "सहायता",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 गà¥à¤£à¤¾ 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 गà¥à¤£à¤¾ 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "सà¥à¤µà¤°à¥à¤£ अनà¥à¤ªà¤¾à¤¤",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "तीन सेकंड",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 सेकंड",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "फ़ोटो लें",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "फ़ोटो न खींचें",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "रिकॉरà¥à¤¡ करना शà¥à¤°à¥‚ करें",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "रिकॉरà¥à¤¡ करना बंद करें",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "अगले कैमरे का इसà¥à¤¤à¥‡à¤®à¤¾à¤² करें",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "वीडियो रिकॉरà¥à¤¡à¤¿à¤‚ग मोड पर जाà¤à¤‚",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "फ़ोटो खींचने वाले मोड पर जाà¤à¤‚",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "टाइमर",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "मिररिंग",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "गà¥à¤°à¤¿à¤¡",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "माइकà¥à¤°à¥‹à¤«à¤¼à¥‹à¤¨",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ठीक",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "अभी नहीं",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "कà¥à¤¯à¤¾ आप वाकई $file$ को हटाना चाहते हैं?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "कà¥à¤¯à¤¾ आप वाकई $count$ आइटम हटाना चाहते हैं?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "कैमरे से ली गईं फ़ोटो और वीडियो 'डाउनलोड' फ़ोलà¥à¤¡à¤° में ले जाठजाà¤à¤‚गे. आप 'फ़ाइल' में जाकर उनà¥à¤¹à¥‡à¤‚ à¤à¤•à¥à¤¸à¥‡à¤¸ कर सकते हैं.\n\nमेमोरी की अनà¥à¤®à¤¤à¤¿à¤¯à¥‹à¤‚ वाले à¤à¤ªà¥à¤²à¤¿à¤•à¥‡à¤¶à¤¨ आपकी फ़ोटो और वीडियो à¤à¤•à¥à¤¸à¥‡à¤¸ कर पाà¤à¤‚गे.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hr/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hr/messages.json
deleted file mode 100644
index e1f5f47a090..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hr/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Fotoaparat",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Snimajte fotografije i videozapise svojim fotoaparatom.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "PogreÅ¡ke datoteÄnog sustava.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Fotoaparat trenutaÄno nije dostupan.\nProvjerite je li fotoaparat pravilno povezan.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Izvoz datoteke $file$ nije uspio",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Spremanje datoteke nije uspjelo",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Snimanje fotografije nije uspjelo",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Pokretanje snimanja nije uspjelo",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nije snimljeno ništa",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Snimanje je zaustavljeno",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Aktivno: $camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Otvori galeriju",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Izbriši",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Izvezi na disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Ispiši",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Natrag",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Slike iz galerije",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Postavke",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Vrsta rešetke",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Trajanje odbrojavanja",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Pošaljite povratne informacije",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Pomoć",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "Tri puta tri",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "ÄŒetiri puta Äetiri",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Zlatni rez",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekunde",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekundi",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Snimi fotografiju",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Zaustavi snimanje fotografije",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "ZapoÄni snimanje",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Zaustavi snimanje",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Prijeđi na sljedeći fotoaparat",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Prijeđi na snimanje videozapisa",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Prijeđi na snimanje fotografije",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Odbrojavanje",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Zrcaljenje",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Rešetka",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "U redu",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Odustani",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Želite li doista ukloniti datoteku $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Želite li doista ukloniti te stavke (ukupno $count$)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Fotografije i videozapisi snimljeni fotoaparatom premjestit će se u mapu Preuzimanja. Možete im pristupiti u Datotekama.\n\nAplikacije s dopuštenjima za pohranu imat će pristup vašim fotografijama i videozapisima.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hu/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hu/messages.json
deleted file mode 100644
index f93da742bb9..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/hu/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Fotókat és videókat készíthet a kamerával.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Fájlrendszerbeli hibák.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "A kamera jelenleg nem áll rendelkezésre.\nEllenőrizze, hogy megfelelően van-e csatlakoztatva.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Nem sikerült exportálni a fájlt ($file$).",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Nem sikerült menteni a fájlt.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Nem sikerült elkészíteni a fotót.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nem sikerült elindítani a rögzítést.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "A rendszer semmit sem rögzített.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "A rögzítés leállt",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ aktív",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ugrás a galériába",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Törlés",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportálás lemezre",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Nyomtatás",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Vissza",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galériaképek",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Beállítások",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Rács típusa",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Időzítés hossza",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Visszajelzés küldése",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Súgó",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 × 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "Háromszor hármas",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 × 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "Négyszer négyes",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Aranymetszés",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 másodperc",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 másodperc",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Fotókészítés",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Fotókészítés leállítása",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Rögzítés megkezdése",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Rögzítés leállítása",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Váltás a következő kamerára",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Váltás videórögzítésre",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Váltás fotókészítésre",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Időzítő",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Tükrözés",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Rács",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Mégse",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Biztosan eltávolítja a következő fájlt: $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Biztosan eltávolít $count$ elemet?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "A kamerával készített fotók és videók a Letöltések mappába kerülnek. A Fájlok alkalmazásban érheti el őket.\n\nA tárhelyengedéllyel rendelkező alkalmazások hozzáférnek fotóihoz és videóihoz.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/id/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/id/messages.json
deleted file mode 100644
index 4d4c2a6c267..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/id/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Ambil foto dan rekam video dengan kamera Anda.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Error sistem file.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kamera Anda saat ini tidak tersedia.\nHarap periksa apakah kamera terhubung dengan benar.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Tidak dapat mengekspor $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Tidak dapat menyimpan file",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Tidak dapat mengambil foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Tidak dapat memulai rekaman",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Tidak ada yang direkam",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Rekaman dihentikan",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ aktif",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Buka galeri",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Hapus",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Ekspor ke disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Cetak",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Kembali",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Foto galeri",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Setelan",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Jenis petak",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Durasi timer",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Kirim masukan",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Bantuan",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 kali 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 kali 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Rasio emas",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 detik",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 detik",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Ambil foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Berhenti mengambil foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Mulai merekam",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Hentikan rekaman",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Beralih ke kamera berikutnya",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Beralih ke mode rekam video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Beralih ke mode ambil foto",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Pencerminan",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Petak",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Oke",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Batal",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Yakin ingin menghapus $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Yakin ingin menghapus $count$?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Foto dan video yang diambil dengan kamera akan dipindahkan ke folder Download. Anda dapat mengaksesnya di File.\n\nAplikasi yang memiliki izin penyimpanan dapat mengakses foto dan video.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/it/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/it/messages.json
deleted file mode 100644
index 7b4c910cc87..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/it/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Fotocamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Realizza foto e video con la tua fotocamera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Errori del file system.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "La fotocamera non è al momento disponibile.\nControlla se è collegata correttamente.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Impossibile esportare $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Impossibile salvare il file",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Impossibile scattare una foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Impossibile avviare la registrazione",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nessun dato registrato",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Registrazione interrotta",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ attiva",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Vai alla galleria",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Elimina",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Esporta su disco",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Stampa",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Indietro",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Immagini della galleria",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Impostazioni",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipo di griglia",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Durata del timer",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Invia feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Guida",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Sezione aurea",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 secondi",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 secondi",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Scatta una foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Interrompi acquisizione foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Avvia registrazione",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Interrompi la registrazione",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Passa alla fotocamera successiva",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Passa alla modalità video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Passa alla modalità foto",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Mirroring",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Griglia",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microfono",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Annulla",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Vuoi rimuovere il file $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Vuoi rimuovere $count$ elementi?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Le foto e i video acquisiti con la fotocamera verranno spostati nella cartella Download e potrai accedervi dall'app File.\n\nLe app con autorizzazioni di accesso allo spazio di archiviazione potranno accedere alle foto e ai video.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ja/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ja/messages.json
deleted file mode 100644
index 2c6eda22f00..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ja/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "カメラ",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "カメラを使ã£ã¦å†™çœŸã‚„動画を撮影ã§ãã¾ã™ã€‚",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ファイル システム エラー。",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "カメラã¯ç¾åœ¨ã”利用ã„ãŸã ã‘ã¾ã›ã‚“。\næ­£ã—ã接続ã•ã‚Œã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„。",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ をエクスãƒãƒ¼ãƒˆã§ãã¾ã›ã‚“",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ファイルをä¿å­˜ã§ãã¾ã›ã‚“",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "写真を撮影ã§ãã¾ã›ã‚“",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "録画を開始ã§ãã¾ã›ã‚“",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "何も記録ã•ã‚Œã¦ã„ã¾ã›ã‚“",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "録画をåœæ­¢ã—ã¾ã—ãŸ",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ã«åˆ‡ã‚Šæ›¿ãˆã¾ã—ãŸ",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "ギャラリーã«ç§»å‹•",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "削除",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ディスクã«ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "å°åˆ·",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "戻る",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "ギャラリーã®ç”»åƒ",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "設定",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "グリッドã®ç¨®é¡ž",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "タイマーã®æ™‚é–“",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "フィードãƒãƒƒã‚¯ã‚’é€ä¿¡",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "ヘルプ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "黄金比",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 秒",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 秒",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "写真を撮影",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "写真ã®æ’®å½±ã‚’åœæ­¢",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "録画を開始",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "録画をåœæ­¢",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "次ã®ã‚«ãƒ¡ãƒ©ã«åˆ‡ã‚Šæ›¿ãˆã‚‹",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "å‹•ç”»ã®æ’®å½±ã«åˆ‡ã‚Šæ›¿ãˆã‚‹",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "写真ã®æ’®å½±ã«åˆ‡ã‚Šæ›¿ãˆã‚‹",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "タイマー",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "ミラーリング",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "グリッド",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "マイク",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "キャンセル",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "$file$ を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "$count$ 個ã®ã‚¢ã‚¤ãƒ†ãƒ ã‚’削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "カメラã§æ’®å½±ã—ãŸå†™çœŸã¨å‹•ç”»ã‚’ [ダウンロード] フォルダã«ç§»å‹•ã—ã¾ã™ã€‚ã“れらã®å†™çœŸã‚„å‹•ç”»ã«ã¯ã€[ファイル] ã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚\n\nã¾ãŸã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ãŒè¨±å¯ã•ã‚Œã¦ã„るアプリã‹ã‚‰ã‚‚ã€ã“れらã®å†™çœŸã‚„å‹•ç”»ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/kn/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/kn/messages.json
deleted file mode 100644
index f547c91c6e3..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/kn/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "ಕà³à²¯à²¾à²®à²°à²¾",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "ನಿಮà³à²® ಕà³à²¯à²¾à²®à²°à²¾à²¦ ಮೂಲಕ ಚಿತà³à²°à²—ಳನà³à²¨à³ ತೆಗೆಯಿರಿ ಮತà³à²¤à³ ವೀಡಿಯೊಗಳನà³à²¨à³ ರೆಕಾರà³à²¡à³ ಮಾಡಿ.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ಫೈಲೠಸಿಸà³à²Ÿà²®à³ ದೋಷಗಳà³.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "ನಿಮà³à²® ಕà³à²¯à²¾à²®à²°à²¾ ಪà³à²°à²¸à³à²¤à³à²¤ ಲಭà³à²¯à²µà²¿à²²à³à²².\nದಯವಿಟà³à²Ÿà³ ಕà³à²¯à²¾à²®à²°à²¾ ಸರಿಯಾಗಿ ಸಂಪರà³à²•à²—ೊಂಡಿದೆಯೇ ಎಂಬà³à²¦à²¨à³à²¨à³ ಪರೀಕà³à²·à²¿à²¸à²¿.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ ಅನà³à²¨à³ ರಫà³à²¤à³ ಮಾಡಲೠಸಾಧà³à²¯à²µà²¾à²—à³à²¤à³à²¤à²¿à²²à³à²²",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ಫೈಲೠಉಳಿಸಲೠಸಾಧà³à²¯à²µà²¾à²—à³à²¤à³à²¤à²¿à²²à³à²²",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ಫೋಟೋವನà³à²¨à³ ತೆಗೆದà³à²•à³†à³‚ಳà³à²³à²²à³ ಸಾಧà³à²¯à²µà²¾à²—à³à²¤à³à²¤à²¿à²²à³à²²",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "ರೆಕಾರà³à²¡à³ ಮಾಡà³à²µà³à²¦à²¨à³à²¨à³ ಪà³à²°à²¾à²°à²‚ಭಿಸಲೠಸಾಧà³à²¯à²µà²¾à²—à³à²¤à³à²¤à²¿à²²à³à²²",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "à²à²¨à²¨à³à²¨à³‚ ರೆಕಾರà³à²¡à³ ಮಾಡಲಾಗಿಲà³à²²",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ರೆಕಾರà³à²¡à³ ಮಾಡà³à²µà³à²¦à²¨à³à²¨à³ ನಿಲà³à²²à²¿à²¸à²²à²¾à²—ಿದೆ",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ಸಕà³à²°à²¿à²¯à²µà²¾à²—ಿದೆ",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "ಗà³à²¯à²¾à²²à²°à²¿à²—ೆ ಹೋಗಿ",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "ಅಳಿಸಿ",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ಡಿಸà³à²•à³â€Œà²—ೆ ರಫà³à²¤à³ ಮಾಡಿ",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "ಮà³à²¦à³à²°à²¿à²¸à²¿",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "ಹಿಂದಿರà³à²—ಿ",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "ಗà³à²¯à²¾à²²à²°à²¿ ಚಿತà³à²°à²—ಳà³",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳà³",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "ಗà³à²°à²¿à²¡à³ ಪà³à²°à²•à²¾à²°",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ಟೈಮರೠಅವಧಿ",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ಪà³à²°à²¤à²¿à²•à³à²°à²¿à²¯à³† ಕಳà³à²¹à²¿à²¸à²¿",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "ಸಹಾಯ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "ಗೋಲà³à²¡à²¨à³ ಅನà³à²ªà²¾à²¤",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 ಸೆಕೆಂಡà³à²—ಳà³",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 ಸೆಕೆಂಡà³à²—ಳà³",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "ಫೋಟೋ ತೆಗೆಯಿರಿ",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ಫೋಟೋ ತೆಗೆಯà³à²µà³à²¦à²¨à³à²¨à³ ನಿಲà³à²²à²¿à²¸à²¿",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "ರೆಕಾರà³à²¡à³â€Œ ಮಾಡಲೠಪà³à²°à²¾à²°à²‚ಭಿಸಿ",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "ರೆಕಾರà³à²¡à³ ಮಾಡà³à²µà³à²¦à²¨à³à²¨à³ ನಿಲà³à²²à²¿à²¸à²¿",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "ಮà³à²‚ದಿನ ಸಂಪರà³à²•à²—ೊಂಡಿರà³à²µ ಕà³à²¯à²¾à²®à³†à²°à²¾à²—ೆ ಬದಲಿಸಿ",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "ವೀಡಿಯೊ ರೆಕಾರà³à²¡à³ ಮಾಡಿಗೆ ಬದಲಿಸಿ",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "ಫೋಟೋ ತೆಗೆದà³à²•à³Šà²³à³à²³à²¿à²—ೆ ಬದಲಿಸಿ",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ಟೈಮರà³",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "ಪà³à²°à²¤à²¿à²¬à²¿à²‚ಬಿಸà³à²µà²¿à²•à³†",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "ಗà³à²°à²¿à²¡à³",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "ಮೈಕà³à²°à³†à³‚ಫೋನà³",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ಸರಿ",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "ರದà³à²¦à³à²®à²¾à²¡à²¿",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "ನೀವೠನಿಜವಾಗಿಯೂ $file$ ಅನà³à²¨à³ ತೆಗೆದà³à²¹à²¾à²•à²²à³ ಬಯಸà³à²¤à³à²¤à³€à²°à²¾?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "ನೀವೠನಿಜವಾಗಿಯೂ $count$ à²à²Ÿà²‚ಗಳನà³à²¨à³ ತೆಗೆದà³à²¹à²¾à²•à²²à³ ಬಯಸà³à²¤à³à²¤à²¿à³•à²°à²¾?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "ಕà³à²¯à²¾à²®à²°à²¾à²¦à²¿à²‚ದ ಸೆರೆಹಿಡಿದ ಫೋಟೋಗಳೠಮತà³à²¤à³ ವೀಡಿಯೊಗಳನà³à²¨à³ ಡೌನà³â€Œà²²à³‹à²¡à³â€Œà²—ಳ ಫೋಲà³à²¡à²°à³â€Œà²—ೆ ವರà³à²—ಾಯಿಸಲಾಗà³à²¤à³à²¤à²¦à³†. ನೀವೠಅವà³à²—ಳನà³à²¨à³ ಫೈಲà³â€Œà²—ಳಲà³à²²à²¿ ಪà³à²°à²µà³‡à²¶à²¿à²¸à²¬à²¹à³à²¦à³.\n\nಸಂಗà³à²°à²¹à²£à³† ಅನà³à²®à²¤à²¿à²—ಳನà³à²¨à³ ಹೊಂದಿರà³à²µ ಆà³à²¯à²ªà³â€Œà²—ಳೠನಿಮà³à²® ಫೋಟೋಗಳೠಮತà³à²¤à³ ವೀಡಿಯೊಗಳಿಗೆ ಪà³à²°à²µà³‡à²¶à²µà²¨à³à²¨à³ ಹೊಂದಿರà³à²¤à³à²¤à²µà³†.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ko/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ko/messages.json
deleted file mode 100644
index ec4f52b0d1c..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ko/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "ì¹´ë©”ë¼",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "ì¹´ë©”ë¼ë¡œ ì‚¬ì§„ì„ ì°ê³  ë™ì˜ìƒì„ 녹화합니다.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "íŒŒì¼ ì‹œìŠ¤í…œ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "현재 ì¹´ë©”ë¼ë¥¼ 사용할 수 없습니다.\nì¹´ë©”ë¼ê°€ 제대로 ì—°ê²°ë˜ì–´ 있는지 확ì¸í•˜ì„¸ìš”.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ 파ì¼ì„ 내보낼 수 없습니다.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "파ì¼ì„ 저장할 수 없습니다.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ì‚¬ì§„ì„ ì°ì„ 수 없습니다.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "녹화를 시작할 수 없습니다.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "ë…¹í™”ëœ ë™ì˜ìƒì´ 없습니다.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "녹화가 중지ë˜ì—ˆìŠµë‹ˆë‹¤.",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ 활성화",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "갤러리로 ì´ë™",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "삭제",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "디스í¬ë¡œ 내보내기",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "ì¸ì‡„",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "뒤로",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "갤러리 ì´ë¯¸ì§€",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "설정",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "그리드 유형",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "타ì´ë¨¸ 시간",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ì˜ê²¬ 보내기",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "ë„움ë§",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3x3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3x3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4x4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4x4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "황금 비율",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3ì´ˆ",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10ì´ˆ",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "사진 ì´¬ì˜",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "사진 ì´¬ì˜ ì¤‘ì§€",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "녹화 시작",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "녹화 중지",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "ë‹¤ìŒ ì¹´ë©”ë¼ë¡œ 전환",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "ë™ì˜ìƒ 녹화 모드로 전환",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "사진 ì´¬ì˜ ëª¨ë“œë¡œ 전환",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "타ì´ë¨¸",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "미러ë§",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "그리드",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "마ì´í¬",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "확ì¸",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "취소",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "$file$ 파ì¼ì„ 삭제하시겠습니까?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "항목 $count$개를 삭제하시겠습니까?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "ì¹´ë©”ë¼ë¡œ ì´¬ì˜ëœ 사진 ë° ë™ì˜ìƒì€ 다운로드 í´ë”ë¡œ ì´ë™ë˜ë©°, íŒŒì¼ ì•±ì—ì„œ 액세스할 수 있습니다.\n\n저장용량 사용 ê¶Œí•œì´ ìžˆëŠ” ì•±ì€ ì‚¬ì§„ ë° ë™ì˜ìƒì— 액세스할 수 있습니다.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/lt/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/lt/messages.json
deleted file mode 100644
index e8653312d44..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/lt/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Žiniatinklio kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Fotografuokite ir filmuokite naudodami žiniatinklio kamerą.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Failų sistemos klaidos.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Jūsų kamera šiuo metu nepasiekiama.\nPatikrinkite, ar kamera tinkamai prijungta.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Nepavyko eksportuoti failo $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Nepavyko išsaugoti failo",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Nepavyko nufotografuoti",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nepavyko pradėti įrašymo",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nieko neįrašyta",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Įrašymas sustabdytas",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ veikia",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Eiti į galeriją",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "IÅ¡trinti",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Eksportuoti į diską",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Spausdinti",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Grįžti atgal",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galerijos vaizdai",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Nustatymai",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tinklelio tipas",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "LaikmaÄio trukmÄ—",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Siųsti atsiliepimą",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Pagalba",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 ir 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 iš 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 ir 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 iš 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Geriausias įvertinimas",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekundÄ—s",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekundžių",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Fotografuoti",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Sustabdyti fotografavimÄ…",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Pradėti įrašymą",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Sustabdyti įrašymą",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Perjungti į kitą kamerą",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Perjungti į vaizdo įrašymo režimą",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Perjungti į fotografavimo režimą",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Laikmatis",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "AtspindÄ—jimas",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Tinklelis",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofonas",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Gerai",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Atšaukti",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Ar tikrai norite pašalinti failą $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Ar tikrai norite pašalinti elementus ($count$)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Nuotraukos ir vaizdo įrašai, sukurti naudojant fotoaparatą, bus perkelti į aplanką „Atsisiuntimai“. Juos galite pasiekti Failų programoje.\n\nProgramos su saugyklos leidimais turės prieigą prie nuotraukų ir vaizdo įrašų.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/lv/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/lv/messages.json
deleted file mode 100644
index a59e899c36f..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/lv/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Uzņemiet fotoattēlus un ierakstiet videoklipus, izmantojot savu kameru.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Failu sistēmas kļūdas.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "JÅ«su kamera paÅ¡laik nav pieejama.\nPÄrbaudiet, vai ir pareizi izveidots savienojums ar kameru.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Nevar eksportēt failu $file$.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Nevar saglabÄt failu.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Nevar uzņemt fotoattēlu.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nevar sÄkt ierakstÄ«Å¡anu.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nekas nav ierakstīts.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Ierakstīšana apturēta",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ aktīva",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Doties uz galeriju",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Dzēst",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "EksportÄ“t diskÄ",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "DrukÄt",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Atpakaļ",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galerijas attēli",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Iestatījumi",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Režģa veids",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Taimera ilgums",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Sūtīt atsauksmes",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Palīdzība",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 reiz 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 reiz 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Zelta griezums",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekundes",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekundes",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Uzņemt fotoattēlu",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Apturēt fotoattēla uzņemšanu",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "SÄkt ierakstÄ«Å¡anu",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Apturēt ierakstīšanu",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "PÄrslÄ“gties uz nÄkamo kameru",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "PÄrslÄ“gties uz videoklipa ierakstÄ«Å¡anu",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "PÄrslÄ“gties uz fotoattÄ“la uzņemÅ¡anu",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Taimeris",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Spoguļošana",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Režģis",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofons",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Labi",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Atcelt",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Vai tieÅ¡Äm vÄ“laties noņemt failu $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Vai tieÅ¡Äm vÄ“laties noņemt $count$ failu(-us)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Ar kameru uzņemtie fotoattÄ“li un videoklipi tiks ievietoti mapÄ“ LejupielÄdes. Varat tiem piekļūt lietotnÄ“ Faili.\n\nJÅ«su fotoattÄ“liem un videoklipiem varÄ“s piekļūt lietotnes, kam ir krÄtuves atļaujas.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ml/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ml/messages.json
deleted file mode 100644
index 454e774454f..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ml/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "à´•àµà´¯à´¾à´®à´±",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "നിങàµà´™à´³àµà´Ÿàµ† à´•àµà´¯à´¾à´®à´± ഉപയോഗിചàµà´šàµ à´šà´¿à´¤àµà´°à´™àµà´™àµ¾ à´Žà´Ÿàµà´•àµà´•àµà´•, വീഡിയോകൾ റെകàµà´•àµ‹àµ¼à´¡àµ ചെയàµà´¯àµà´•.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ഫയൽ സിസàµâ€Œà´±àµà´±à´‚ പിശകàµà´•àµ¾.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "à´•àµà´¯à´¾à´®à´± നിലവിൽ ലഭàµà´¯à´®à´²àµà´².\nà´•àµà´¯à´¾à´®à´± ശരിയായി കണകàµà´±àµà´±àµ ചെയàµâ€Œà´¤à´¿à´Ÿàµà´Ÿàµà´£àµà´Ÿàµ‹ à´Žà´¨àµà´¨àµ പരിശോധികàµà´•àµà´•.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ à´Žà´•àµâ€Œà´¸àµâ€Œà´ªàµ‹àµ¼à´Ÿàµà´Ÿàµ ചെയàµà´¯à´¾à´¨à´¾à´¯à´¿à´²àµà´²",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ഫയൽ സംരകàµà´·à´¿à´•àµà´•à´¾à´¨à´¾à´¯à´¿à´²àµà´²",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ഫോടàµà´Ÿàµ‹ à´Žà´Ÿàµà´•àµà´•à´¾à´¨à´¾à´¯à´¿à´²àµà´²",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "റെകàµà´•àµ‹àµ¼à´¡à´¿à´‚ഗൠആരംഭികàµà´•à´¾à´¨à´¾à´¯à´¿à´²àµà´²",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "à´’à´¨àµà´¨àµà´‚ റെകàµà´•àµ‹àµ¼à´¡àµ ചെയàµâ€Œà´¤à´¿à´²àµà´²",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "റെകàµà´•àµ‹àµ¼à´¡àµ ചെയàµà´¯àµ½ നിർതàµà´¤à´¿",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ സജീവമാണàµ",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "ഗാലറിയിലേകàµà´•àµ പോവàµà´•",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "ഇലàµà´²à´¾à´¤à´¾à´•àµà´•àµà´•",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "à´¡à´¿à´¸àµâ€Œà´•àµà´•à´¿à´²àµ‡à´•àµà´•àµ à´Žà´•àµâ€Œà´¸àµâ€Œà´ªàµ‹àµ¼à´Ÿàµà´Ÿàµ ചെയàµà´¯àµà´•",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "à´ªàµà´°à´¿à´¨àµâ€à´±àµ ചെയàµà´¯àµà´•",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "തിരികെ പോവàµà´•",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "ഗാലറി à´šà´¿à´¤àµà´°à´™àµà´™àµ¾",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "à´•àµà´°à´®àµ€à´•à´°à´£à´‚",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "à´—àµà´°à´¿à´¡àµ തരം",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ടൈമർ ദൈർഘàµà´¯à´‚",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ഫീഡàµâ€à´¬à´¾à´•àµà´•àµ അയയàµà´•àµà´•àµà´•",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "സഹായം",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 ബൈ 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 ബൈ 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "ഗോൾഡൻ റേഷàµà´¯àµ‹",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 സെകàµà´•àµ»à´¡àµ",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 സെകàµà´•àµ»à´¡àµ",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "ഫോടàµà´Ÿàµ‹ à´Žà´Ÿàµà´•àµà´•àµà´•",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ഫോടàµà´Ÿàµ‹ à´Žà´Ÿàµà´•àµà´•àµà´¨àµà´¨à´¤àµ നിർതàµà´¤àµà´•",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "റെകàµà´•àµ‹à´°àµâ€à´¡à´¿à´‚ഗൠആരംഭികàµà´•àµà´•",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "റെകàµà´•àµ‹àµ¼à´¡à´¿à´‚ഗൠനിർതàµà´¤àµà´•",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "à´…à´Ÿàµà´¤àµà´¤ à´•àµà´¯à´¾à´®à´±à´¯à´¿à´²àµ‡à´•àµà´•àµ മാറàµà´•",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "വീഡിയോ റെകàµà´•àµ‹àµ¼à´¡àµ ചെയàµà´¯àµà´¨àµà´¨à´¤à´¿à´²àµ‡à´•àµà´•àµ മാറàµà´•",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "ഫോടàµà´Ÿàµ‹ à´Žà´Ÿàµà´•àµà´•àµà´¨àµà´¨à´¤à´¿à´²àµ‡à´•àµà´•àµ മാറàµà´•",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ടൈമർ",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "മിററിംഗàµ",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "à´—àµà´°à´¿à´¡àµ",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "മൈകàµà´°àµ‹à´«àµ‹àµº",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ശരി",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "റദàµà´¦à´¾à´•àµà´•àµà´•",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "നിങàµà´™àµ¾à´•àµà´•àµ ശരികàµà´•àµà´‚ $file$ നീകàµà´•à´‚ ചെയàµà´¯à´£àµ‹?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "നിങàµà´™àµ¾à´•àµà´•àµ ശരികàµà´•àµà´‚ $count$ ഇനങàµà´™àµ¾ നീകàµà´•à´‚ ചെയàµà´¯à´£àµ‹?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "à´•àµà´¯à´¾à´®à´± ഉപയോഗിചàµà´šàµ†à´Ÿàµà´¤àµà´¤ ഫോടàµà´Ÿàµ‹à´•à´³àµà´‚ വീഡിയോകളàµà´‚ 'ഡൗൺലോഡàµà´•àµ¾' ഫോൾഡറിലേകàµà´•àµ നീകàµà´•àµà´‚. നിങàµà´™àµ¾à´•àµà´•àµ à´…à´µ ഫയലàµà´•à´³à´¿àµ½ ആകàµâ€Œà´¸à´¸àµ ചെയàµà´¯à´¾à´¨à´¾à´µàµà´‚.\n\nà´¸àµâ€Œà´±àµà´±àµ‹à´±àµ‡à´œàµ à´…à´¨àµà´®à´¤à´¿à´•à´³àµà´³àµà´³ ആപàµà´ªàµà´•àµ¾à´•àµà´•àµ നിങàµà´™à´³àµà´Ÿàµ† ഫോടàµà´Ÿàµ‹à´•à´³à´¿à´²àµ‡à´•àµà´•àµà´‚ വീഡിയോകളിലേകàµà´•àµà´‚ ആകàµâ€Œà´¸à´¸àµ ഉണàµà´Ÿà´¾à´¯à´¿à´°à´¿à´•àµà´•àµà´‚.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/mr/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/mr/messages.json
deleted file mode 100644
index a1de935ba83..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/mr/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "कॅमेरा",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "तà¥à¤®à¤šà¥â€à¤¯à¤¾ कॅमेरà¥â€à¤¯à¤¾à¤¨à¥‡ फोटो काढा आणि वà¥à¤¹à¤¿à¤¡à¤¿à¤“ रेकॉरà¥à¤¡ करा.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "फाइल सिसà¥à¤Ÿà¤® à¤à¤°à¤°.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "तà¥à¤®à¤šà¤¾ कॅमेरा सधà¥à¤¯à¤¾ उपलबà¥à¤§ नाही.\nकृपया कॅमेरा योगà¥à¤¯à¤°à¥€à¤¤à¥â€à¤¯à¤¾ कनेकà¥à¤Ÿ केला असलà¥à¤¯à¤¾à¤šà¥‡ तपासा.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ à¤à¤•à¥à¤¸à¤ªà¥‹à¤°à¥à¤Ÿ करता आली नाही",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "फाइल सेवà¥â€à¤¹ करता आली नाही",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "फोटो काढता आला नाही",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "रेकॉरà¥à¤¡à¤¿à¤‚ग सà¥à¤°à¥‚ करता आले नाही",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "काहीही रेकॉरà¥à¤¡ केले नाही",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "रेकॉरà¥à¤¡à¤¿à¤‚ग थांबले आहे",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ अâ€à¥…कà¥à¤Ÿà¤¿à¤µà¥à¤¹ आहे",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "गॅलरीवर जा",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "हटवा",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "डिसà¥à¤•à¤µà¤° निरà¥à¤¯à¤¾à¤¤ करा",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "पà¥à¤°à¤¿à¤‚ट करा",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "परत जा",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "गॅलरीमधील इमेज",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "सेटिंगà¥à¤œ",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "गà¥à¤°à¤¿à¤¡à¤šà¤¾ पà¥à¤°à¤•à¤¾à¤°",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "टायमरचा कालावधी",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "फीडबॅक पाठवा",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "मदत",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "३ x ३",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "३ बाय ३",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "४ x ४",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "४ बाय ४",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "गोलà¥à¤¡à¤¨ रेशो",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "तीन सेकंद",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "१० सेकंद",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "फोटो काढा",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "फोटो काढणे थांबवा",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "रेकॉरà¥à¤¡à¤¿à¤‚ग सà¥à¤°à¥‚ करा",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "रेकॉरà¥à¤¡à¤¿à¤‚ग थांबवा",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "पà¥à¤¢à¥€à¤² कॅमेरà¥â€à¤¯à¤¾à¤µà¤° सà¥à¤µà¤¿à¤š करा",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "वà¥à¤¹à¤¿à¤¡à¤¿à¤“ रेकॉरà¥à¤¡ करणà¥â€à¤¯à¤¾à¤¸à¤¾à¤ à¥€ सà¥à¤µà¤¿à¤š करा",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "फोटो काढणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ सà¥à¤µà¤¿à¤š करा",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "टायमर",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "मिररिंग",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "गà¥à¤°à¤¿à¤¡",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "मायकà¥à¤°à¥‹à¤«à¥‹à¤¨",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ओके",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "रदà¥à¤¦ करा",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "तà¥à¤®à¥â€à¤¹à¤¾à¤²à¤¾ $file$ खरोखर काढून टाकायची आहे?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "तà¥à¤®à¥â€à¤¹à¤¾à¤²à¤¾ $count$ आयटम खरोखर काढून टाकायचे आहेत?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "कॅमेरà¥â€à¤¯à¤¾à¤¨à¥‡ काढलेले फोटो आणि वà¥à¤¹à¤¿à¤¡à¤¿à¤“ डाउनलोड फोलà¥à¤¡à¤°à¤µà¤° हलवले जातील. तà¥à¤®à¥à¤¹à¥€ ते फायलींमधà¥à¤¯à¥‡ अâ€à¥…कà¥à¤¸à¥‡à¤¸ करू शकता.\n\nसà¥à¤Ÿà¥‹à¤°à¥‡à¤œ परवानगà¥à¤¯à¤¾ असलेलà¥à¤¯à¤¾ अâ€à¥…पà¥à¤¸à¤¨à¤¾ तà¥à¤®à¤šà¥à¤¯à¤¾ फोटोचा आणि वà¥à¤¹à¤¿à¤¡à¤¿à¤“चा अâ€à¥…कà¥à¤¸à¥‡à¤¸ असेल.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ms/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ms/messages.json
deleted file mode 100644
index edad0aa816f..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ms/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Ambil foto dan rakam video dengan kamera anda.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Ralat sistem fail.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kamera anda tidak tersedia pada masa ini.\nSila periksa sama ada kamera disambungkan dengan betul.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Tidak dapat mengeksport $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Tidak dapat menyimpan fail",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Tidak dapat mengambil foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Tidak dapat memulakan rakaman",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Tiada apa-apa yang dirakamkan",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Rakaman dihentikan",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ aktif",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Pergi ke galeri",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Padam",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Eksport ke cakera",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Cetak",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Kembali",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imej galeri",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Tetapan",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Jenis grid",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Tempoh pemasa",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Hantar maklum balas",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Bantuan",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 kali 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 kali 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Nisbah keemasan",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 saat",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 saat",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Ambil foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Berhenti mengambil foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Mulakan rakaman",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Hentikan rakaman",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Beralih kepada kamera seterusnya",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Beralih kepada merakam video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Beralih kepada mengambil foto",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Pemasa",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Pencerminan",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grid",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Batal",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Adakah anda benar-benar mahu mengalih keluar $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Adakah anda benar-benar mahu mengalih keluar $count$ item?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Foto dan video yang diambil dengan kamera akan dialihkan ke folder Muat turun. Anda boleh mengakses item ini dalam Fail.\n\nApl dengan kebenaran storan akan boleh mengakses foto dan video anda.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/nl/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/nl/messages.json
deleted file mode 100644
index 54059af7e0d..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/nl/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Camera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Maak foto's en neem video's op met je camera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Bestandssysteemfouten.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Je camera is momenteel niet beschikbaar.\nControleer of de camera correct is aangesloten.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Kan $file$ niet exporteren",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Kan het bestand niet opslaan",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Kan geen foto maken",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Kan opname niet starten",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Niets opgenomen",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Opname gestopt",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ actief",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ga naar galerij",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Verwijderen",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exporteren naar schijf",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Afdrukken",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Terug",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galerij-afbeeldingen",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Instellingen",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Rastertype",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Timerduur",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Feedback verzenden",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Help",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 bij 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 bij 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Gulden snede",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 seconden",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 seconden",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Foto maken",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Foto maken stoppen",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Opname starten",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Opname stoppen",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Naar volgende camera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Naar video opnemen",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Naar foto maken",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Mirroring",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Raster",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microfoon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Annuleren",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Weet je zeker dat je $file$ wilt verwijderen?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Weet je zeker dat je $count$ items wilt verwijderen?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Foto's en video's die met de camera zijn gemaakt, worden verplaatst naar de map Downloads. Je kunt ze vinden via Bestanden.\n\nApps met opslagmachtiging hebben toegang tot je foto's en video's.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/no/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/no/messages.json
deleted file mode 100644
index 142e138c068..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/no/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Ta bilder og ta opp video med kameraet ditt.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Filsystemfeil.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kameraet ditt er utilgjengelig for øyeblikket.\nSjekk om kameraet er koblet til riktig.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Kan ikke eksportere $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Kan ikke lagre filen",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Kan ikke ta bilde",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Kan ikke starte opptak",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Ingenting er spilt inn",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Opptaket er stoppet",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ er aktivt",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "GÃ¥ til galleriet",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Slett",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Eksportér til disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Skriv ut",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "GÃ¥ tilbake",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galleribilder",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Innstillinger",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Rutenettype",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Nedtellervarighet",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Send tilbakemelding",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Hjelp",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 ganger 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 ganger 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Det gylne snitt",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekunder",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekunder",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Ta bilde",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Slutt å ta bilde",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Start opptak",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Stopp opptak",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Bytt til neste kamera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Bytt til video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Bytt til foto",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Nedtelling",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Speiling",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Rutenett",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Avbryt",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Vil du fjerne $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Vil du fjerne $count$ elementer?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Bilder og videoer som er tatt med kameraet, flyttes til mappen Nedlastinger. Du har tilgang til dem i Filer.\n\nApper med lagringstillatelser har tilgang til bildene og videoene dine.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pl/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pl/messages.json
deleted file mode 100644
index 97e6fc9aa70..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pl/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Aparat",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Aparatem możesz robić zdjęcia i nagrywać filmy.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Błędy systemu plików.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kamera jest teraz niedostępna.\nSprawdź, czy jest prawidłowo podłączona.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Nie udało się wyeksportować pliku $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Nie udało się zapisać pliku",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Nie udało się zrobić zdjęcia",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nie udało się rozpocząć nagrywania",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nic nie zostało nagrane",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Nagrywanie zatrzymane",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Aktywna kamera: $camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Otwórz galerię",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Usuń",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Eksportuj na dysk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Drukuj",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Wróć",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Zdjęcia z galerii",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Ustawienia",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Typ siatki",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Czas samowyzwalacza",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Prześlij opinię",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Pomoc",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 na 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 na 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "ZÅ‚ote proporcje",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekundy",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekund",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Zrób zdjęcie",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Nie rób zdjęcia",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Rozpocznij nagrywanie",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Zatrzymaj nagrywanie",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Przełącz na następną kamerę",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Przełącz, by nagrać film",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Przełącz, by zrobić zdjęcie",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Samowyzwalacz",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Odbicie lustrzane",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Siatka",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Anuluj",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Czy na pewno chcesz usunąć plik $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Czy na pewno chcesz usunąć te elementy ($count$)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Zdjęcia i filmy pochodzące z aparatu zostaną przeniesione do folderu Pobrane pliki. Będziesz mieć do nich dostęp w aplikacji Pliki.\n\nZ Twoich zdjęć i filmów będą mogły korzystać aplikacje z uprawnieniami dostępu do pamięci.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_BR/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_BR/messages.json
deleted file mode 100644
index 3a2e4d8327a..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_BR/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Câmera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Tire fotos e grave vídeos com sua câmera.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Erros no sistema de arquivos.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Sua câmera está indisponível no momento.\nVerifique se a câmera está conectada corretamente.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Não foi possível exportar $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Não foi possível salvar o arquivo",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Não foi possível tirar a foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Não foi possível começar a gravação",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nenhum dado gravado",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "A gravação foi interrompida",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ativa",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ir para a galeria",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Excluir",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportar para o disco",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Imprimir",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Voltar",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imagens da galeria",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Configurações",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipo de grade",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Duração do timer",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Enviar feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ajuda",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 por 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 por 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Proporção de ouro",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 segundos",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 segundos",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Tirar foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Parar de tirar foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Começar a gravar",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Interromper gravação",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Alternar para a próxima câmera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Alternar para gravar vídeo",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Alternar para tirar foto",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Espelhamento",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grade",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microfone",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancelar",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Tem certeza de que quer remover o arquivo $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Tem certeza de que quer remover $count$ itens?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "As fotos e os vídeos capturados com a câmera serão movidos para a pasta \"Downloads\". Você pode acessá-los no app Arquivos.\n\nOs apps com permissões de armazenamento terão acesso às suas fotos e aos seus vídeos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_PT/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_PT/messages.json
deleted file mode 100644
index 61bf7a6e2db..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/pt_PT/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Câmara",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Tire fotos e grave vídeos com a sua câmara.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Erros do sistema de ficheiros.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "De momento, a câmara não está disponível.\nVerifique se a câmara está ligada corretamente.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Não foi possível exportar o ficheiro $file$.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Não foi possível guardar o ficheiro.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Não é possível tirar a foto.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Não é possível iniciar a gravação.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nada foi gravado.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Gravação interrompida",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ativa",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Ir para a galeria",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Eliminar",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportar para o disco",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Imprimir",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Voltar",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imagens da galeria",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Definições",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipo de grelha",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Duração do temporizador",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Enviar comentários",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ajuda",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 por 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 por 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Número de Ouro",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 segundos",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 segundos",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Tirar foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Parar de tirar foto",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Começar a gravar",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Parar de gravar",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Mudar para a câmara seguinte",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Mudar para gravar vídeo",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Mudar para tirar foto",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Temporizador",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Espelhamento",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grelha",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microfone",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Cancelar",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Pretende realmente remover o ficheiro $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Pretende realmente remover $count$ itens?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "As fotos e os vídeos registados com a câmara serão transferidos para a pasta Transferências. Pode aceder aos mesmos em Ficheiros.\n\nAs aplicações com autorizações de armazenamento terão acesso aos seus vídeos e fotos.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ro/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ro/messages.json
deleted file mode 100644
index a954f1a03e4..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ro/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Cameră foto",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Fotografiați și înregistrați videoclipuri cu camera foto.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Erori în sistemul de fișiere.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Camera dvs. foto este indisponibilă momentan.\nVerificați dacă este conectată corespunzător.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Nu se poate exporta $file$.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Fișierul nu poate fi salvat.",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Nu se pot face fotografii.",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nu se poate porni înregistrarea.",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Nu s-a înregistrat nimic.",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ÃŽnregistrarea s-a oprit.",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ activă",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Accesați galeria",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Ștergeți",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportați pe disc",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Printați",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "ÃŽnapoi",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Imagini din galerie",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Setări",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Tipul grilei",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Durata temporizatorului",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Trimiteți feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Ajutor",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 pe 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Raportul de aur",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 secunde",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 secunde",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Fotografiați",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Nu mai fotografiați",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Porniți înregistrarea",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Opriți înregistrarea",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Comutați la următoarea cameră foto",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Comutați pentru a înregistra un videoclip",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Comutați pentru a fotografia",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Temporizator",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Oglindire",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Grilă",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Microfon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Anulați",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Sigur doriți să eliminați $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Sigur doriți să eliminați $count$ elemente?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Fotografiile și videoclipurile făcute cu camera foto vor fi mutate în dosarul Descărcări. Le puteți accesa în Fișiere.\n\nAplicațiile cu permisiuni de stocare vor avea acces la fotografiile și videoclipurile dvs.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ru/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ru/messages.json
deleted file mode 100644
index f73711b1e75..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ru/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Камера",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Съемка фото и видео при помощи камеры.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Ошибки файловой ÑиÑтемы",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Камера недоÑтупна.\nПроверьте, правильно ли она подключена.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Ðе удалоÑÑŒ ÑкÑпортировать файл $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Ðе удалоÑÑŒ Ñохранить файл",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Ðе удалоÑÑŒ Ñделать Ñнимок",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Ðе удалоÑÑŒ начать запиÑÑŒ",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Ðичего не запиÑано",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ЗапиÑÑŒ прекращена",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Выбрана $camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Перейти в галерею",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Удалить",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ЭкÑпортировать на диÑк",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Печать",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Ðазад",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Ð˜Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² галерее",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "ÐаÑтройки",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Тип Ñетки",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ДлительноÑÑ‚ÑŒ таймера",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ОÑтавить отзыв",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Справка",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 на 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 на 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Золотое Ñечение",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 Ñекунды",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 Ñекунд",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Сделать Ñнимок",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ОÑтановить Ñъемку",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Ðачать запиÑÑŒ",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "ОÑтановить запиÑÑŒ",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "ПереключитьÑÑ Ð½Ð° Ñледующую камеру",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "ПереключитьÑÑ Ð½Ð° видеоÑъемку",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "ПереключитьÑÑ Ð½Ð° фотоÑъемку",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Таймер",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Зеркалирование",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Сетка",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Микрофон",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ОК",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Отмена",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Удалить файл $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Удалить файлы ($count$)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Фотографии и видео, полученные Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹, будут перемещены в папку \"Загрузки\". Ð’Ñ‹ также Ñможете найти их в папке \"Файлы\".\n\nПриложениÑ, у которых еÑÑ‚ÑŒ доÑтуп к хранилищу, Ñмогут работать Ñ Ð²Ð°ÑˆÐ¸Ð¼Ð¸ фотографиÑми и видео.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sk/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sk/messages.json
deleted file mode 100644
index 3934bb502cd..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sk/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Fotoaparát",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Foťte a nahrávajte videá pomocou fotoaparátu.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Chyby systému súborov.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Váš fotoaparát je momentálne nedostupný.\nSkontrolujte, Äi je fotoaparát správne pripojený.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Súbor $file$ sa nepodarilo exportovať",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Súbor sa nepodarilo uložiť",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Fotku sa nepodarilo nasnímať",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Nahrávanie sa nepodarilo spustiť",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "NiÄ sa nenahralo",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Nahrávanie bolo zastavené",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Kamera $camera$ je aktívna",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Prejsť do galérie",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Odstrániť",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportovať na disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "TlaÄiÅ¥",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Späť",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Obrázky galérie",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Nastavenia",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Typ mriežky",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Trvanie ÄasovaÄa",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Spätná väzba",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Pomocník",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 na 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 na 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Zlatý pomer",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekundy",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekúnd",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Odfotiť",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Zastaviť fotenie",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Spustiť nahrávanie",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Zastaviť nahrávanie",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Prepnúť na Äalší fotoaparát",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Prepnúť na nahrávanie videa",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Prepnúť na fotenie",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ÄŒasovaÄ",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Zrkadlenie",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Mriežka",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofón",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Zrušiť",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Naozaj chcete súbor $file$ odstrániť?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Naozaj chcete tieto položky ($count$) odstrániť?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Fotky a videá nasnímané fotoaparátom budú presunuté do prieÄinka stiahnutých súborov. Prístup k nim získate v prieÄinku Súbory.\nK fotkám a videám budú maÅ¥ prístup aplikácie s povoleniami pre úložisko.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sl/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sl/messages.json
deleted file mode 100644
index 81ac42e3c58..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sl/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Fotoaparat",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Fotografirajte in snemajte videoposnetke s fotoaparatom.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Napake datoteÄnega sistema.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Fotoaparat trenutno ni na voljo.\nPreverite, ali je fotoaparat ustrezno povezan.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Datoteke $file$ ni mogoÄe izvoziti",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Datoteke ni mogoÄe shraniti",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Fotografije ni mogoÄe posneti",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Snemanja ni mogoÄe zaÄeti",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "NiÄ ni posneto",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Snemanje je ustavljeno",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Fotoaparat $camera$ je aktiven",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "V galerijo",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Izbris",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Izvoz na disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Tiskanje",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Nazaj",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Slike v galeriji",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Nastavitve",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Vrsta mreže",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Trajanje Äasovnika",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Pošlji povratne informacije",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "PomoÄ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Zlato razmerje",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 sekunde",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 sekund",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Fotografiranje",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Ustavitev fotografiranja",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "ZaÄetek snemanja",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Ustavitev snemanja",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Preklop na naslednji fotoaparat",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Preklop na snemanje videa",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Preklop na fotografiranje",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ÄŒasovnik",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Zrcaljenje",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Mreža",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "V redu",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "PrekliÄi",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Ali res želite odstraniti datoteko $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Ali res želite odstraniti toliko elementov: $count$?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Fotografije in videoposnetki, ki ste jih posneli s fotoaparatom, bodo premaknjeni v mapo Prenosi. Do njih lahko dostopate v aplikaciji Datoteke.\n\nAplikacije z dovoljenji za shrambo bodo imele dostop do fotografij in videoposnetkov.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sr/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sr/messages.json
deleted file mode 100644
index 74b3974d46c..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sr/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Камера",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Снимајте Ñлике и видео Ñнимке помоћу камере.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Грешке ÑиÑтема датотека.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Камера тренутно није доÑтупна.\nПроверите да ли је камера иÑправно повезана.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Извоз датотеке $file$ није уÑпео",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Чување датотеке није уÑпело",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Снимање Ñлике није уÑпело",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "ÐиÑмо уÑпели да започнемо Ñнимање",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Ðишта није Ñнимљено",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Снимање је зауÑтављено",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Ðктивна је камера $camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Иди у галерију",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Избриши",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Извези на диÑк",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Штампај",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Ðазад",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Слике у галерији",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Подешавања",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Тип мреже",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Трајање тајмера",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Пошаљи повратне информације",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Помоћ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3×3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 Ñа 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4×4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 Ñа 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Златни преÑек",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 Ñекунде",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 Ñекунди",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Сликај",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ЗауÑтави Ñнимање Ñлике",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Започни Ñнимање",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "ЗауÑтави Ñнимање",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Пређи на Ñледећу камеру",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Пређи на режим за Ñнимање видеа",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Пређи на режим за Ñнимање Ñлика",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Тајмер",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "ПреÑликавање",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Мрежа",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Микрофон",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Потврди",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Откажи",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Желите ли Ñтварно да уклоните датотеку $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Желите ли Ñтварно да уклоните ове Ñтавке ($count$)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Слике и видео Ñнимци који Ñу Ñнимљени помоћу камере ће Ñе премеÑтити у директоријум Преузимања. Можете да им приÑтупите у Датотекама.\n\nÐпликације Ñа дозволама за приÑтуп меморијÑком проÑтору ће имати приÑтуп Ñликама и видео Ñнимцима.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sv/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sv/messages.json
deleted file mode 100644
index 430a3977dfb..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sv/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Ta foton och spela in video med kameran.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Fel i filsystemet.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kameran är inte tillgänglig.\nKontrollera kameraanslutningen.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Det gick inte att exportera $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Det gick inte att spara filen",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Det gick inte att ta ett foto",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Det gick inte att starta inspelningen",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Inget har spelats in",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Inspelningen stoppad",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ är aktiv",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Besök galleriet",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Radera",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Exportera till disk",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Skriv ut",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Föregående",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Bilder i galleriet",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Inställningar",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Typ av rutnät",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Timerns längd",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Skicka feedback",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Hjälp",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 × 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "Tre gånger tre",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 × 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "Fyra gånger fyra",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Gyllene snittet",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "Tre sekunder",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "Tio sekunder",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Ta foto",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Sluta ta foton",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Börja spela in",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Sluta spela in",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Byt till nästa kamera",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Byt till videoinspelning",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Byt till fotoläge",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Timer",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Spegling",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Rutnät",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Avbryt",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Vill du ta bort $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Vill du ta bort $count$ objekt?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Videor och foton du tagit med kameran flyttas till mappen Nedladdningar. Du kan öppna dem i Filer.\n\nFotona och videorna blir tillgängliga för appar som har åtkomstbehörighet till lagringsutrymmet.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sw/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sw/messages.json
deleted file mode 100644
index 79e750b4c7b..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/sw/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Piga picha na urekodi video ukitumia kamera yako.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Hitilafu za mfumo wa faili.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kamera yako haipatikani kwa sasa.\nTafadhali angalia ikiwa umeunganisha kamera vizuri.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Imeshindwa kutuma $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Imeshindwa kuhifadhi faili",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Imeshindwa kupiga picha",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Imeshindwa kuanza kurekodi",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Haijarekodi chochote",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Imeacha kurekodi",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ inatumika",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Nenda kwenye matunzio",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Futa",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Hamishia kwenye diski",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Chapisha",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Rudi nyuma",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Picha za matunzio",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Mipangilio",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Aina ya gridi",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Urefu wa kipima muda",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Tuma maoni",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Usaidizi",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 kwa 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 kwa 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Uwiano mkuu",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "Sekunde 3",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "Sekunde 10",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Piga picha",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Acha kupiga picha",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Anza kurekodi",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Acha kurekodi",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Tumia kamera inayofuata",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Rekodi video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Piga picha",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Kipima muda",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Uakisi",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Gridi",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Maikrofoni",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Sawa",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Ghairi",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Una uhakika ungependa kuondoa $file$ kwenye matunzio?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Una uhakika ungependa kuondoa vipengee $count$?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Picha na video zinazopigwa kwa kamera zitahamishiwa kwenye folda ya Vipakuliwa. Unaweza kuzifikia kwenye Faili.\n\nProgramu zilizo na ruhusa za hifadhi zitaweza kufikia picha na video zako.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ta/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ta/messages.json
deleted file mode 100644
index 8c5f1613407..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/ta/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "கேமரா",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "உஙà¯à®•à®³à¯ கேமராவைப௠பயனà¯à®ªà®Ÿà¯à®¤à¯à®¤à®¿, படஙà¯à®•à®³à¯ˆ எடà¯à®•à¯à®•à®²à®¾à®®à¯, வீடியோகà¯à®•à®³à¯ˆ ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®¯à®²à®¾à®®à¯.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "கோபà¯à®ªà¯ அமைபà¯à®ªà¯à®ªà¯ பிழைகளà¯.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "உஙà¯à®•à®³à¯ கேமரா தறà¯à®ªà¯‹à®¤à¯ கிடைகà¯à®•à®µà®¿à®²à¯à®²à¯ˆ.\nஅத௠சரியாக இணைகà¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à®¾ எனச௠சரிபாரà¯à®•à¯à®•à®µà¯à®®à¯.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$஠இடமாறà¯à®± à®®à¯à®Ÿà®¿à®¯à®µà®¿à®²à¯à®²à¯ˆ",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "கோபà¯à®ªà¯ˆà®šà¯ சேமிகà¯à®• à®®à¯à®Ÿà®¿à®¯à®µà®¿à®²à¯à®²à¯ˆ",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "படம௠எடà¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à®µà®¿à®²à¯à®²à¯ˆ",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®¯ à®®à¯à®Ÿà®¿à®¯à®µà®¿à®²à¯à®²à¯ˆ",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "எதà¯à®µà¯à®®à¯ ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®¯à®ªà¯à®ªà®Ÿà®µà®¿à®²à¯à®²à¯ˆ",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®µà®¤à¯ நிறà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà¯à®Ÿà®¤à¯",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ செயலிலà¯à®³à¯à®³à®¤à¯",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "கேலரிகà¯à®•à¯à®šà¯ செலà¯",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "நீகà¯à®•à¯",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "வடà¯à®Ÿà¯à®•à¯à®•à¯ இடமாறà¯à®±à¯",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "அசà¯à®šà®¿à®Ÿà¯",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "பினà¯à®šà¯†à®²à¯",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "கேலரியிலà¯à®³à¯à®³ படஙà¯à®•à®³à¯",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "அமைபà¯à®ªà¯à®•à®³à¯",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "கடà¯à®Ÿ வகை",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "டைமர௠காலஅளவà¯",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "கரà¯à®¤à¯à®¤à¯ அனà¯à®ªà¯à®ªà¯à®•",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "உதவி",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "கோலà¯à®Ÿà®©à¯ ரேஷியோ",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 வினாடிகளà¯",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 வினாடிகளà¯",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "படமெடà¯",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "படமெடà¯à®ªà¯à®ªà®¤à¯ˆ நிறà¯à®¤à¯à®¤à¯",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®¯à®¤à¯ தொடஙà¯à®•à¯",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®µà®¤à¯ˆ நிறà¯à®¤à¯à®¤à¯",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "அடà¯à®¤à¯à®¤ கேமராவà¯à®•à¯à®•à¯ மாறà¯",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "வீடியோவை ரெகà¯à®•à®¾à®°à¯à®Ÿà¯ செயà¯à®¯à¯à®®à¯ பயனà¯à®®à¯à®±à¯ˆà®•à¯à®•à¯ மாறà¯",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "படமெடà¯à®•à¯à®•à¯à®®à¯ பயனà¯à®®à¯à®±à¯ˆà®•à¯à®•à¯ மாறà¯",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "டைமரà¯",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "பிரதிபலிதà¯à®¤à®²à¯",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "கடà¯à®Ÿà®®à¯",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "மைகà¯à®°à¯‹à®ƒà®ªà¯‹à®©à¯",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "சரி",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "ரதà¯à®¤à¯à®šà¯†à®¯à¯",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "$file$஠நிசà¯à®šà®¯à®®à®¾à®• அகறà¯à®± விரà¯à®®à¯à®ªà¯à®•à®¿à®±à¯€à®°à¯à®•à®³à®¾?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "$count$ உரà¯à®ªà¯à®ªà®Ÿà®¿à®•à®³à¯ˆ நிசà¯à®šà®¯à®®à®¾à®• அகறà¯à®± விரà¯à®®à¯à®ªà¯à®•à®¿à®±à¯€à®°à¯à®•à®³à®¾?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "கேமராவில௠எடà¯à®•à¯à®•à®ªà¯à®ªà®Ÿà¯à®®à¯ படஙà¯à®•à®³à¯à®®à¯ வீடியோகà¯à®•à®³à¯à®®à¯ ‘பதிவிறகà¯à®•à®™à¯à®•à®³à¯â€™ கோபà¯à®ªà¯à®±à¯ˆà®•à¯à®•à¯ நகரà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà¯à®®à¯. அவறà¯à®±à¯ˆ ‘Files’ எனà¯à®ªà®¤à®¿à®²à¯ பாரà¯à®•à¯à®•à®²à®¾à®®à¯.\n\nசேமிபà¯à®ªà®• அனà¯à®®à®¤à®¿à®•à®³à¯ உளà¯à®³ ஆபà¯à®¸à®¾à®²à¯ உஙà¯à®•à®³à¯ படஙà¯à®•à®³à¯ˆà®¯à¯à®®à¯ வீடியோகà¯à®•à®³à¯ˆà®¯à¯à®®à¯ அணà¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/te/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/te/messages.json
deleted file mode 100644
index 8f780f9aed6..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/te/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "కెమెరా",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "మీ కెమెరాతో ఫోటోలనౠతీయండి, వీడియోలనౠరికారà±à°¡à± చేయండి.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ఫైలౠసిసà±à°Ÿà°®à± à°Žà°°à±à°°à°°à±â€Œà°²à±.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "మీ కెమెరా à°ªà±à°°à°¸à±à°¤à±à°¤à°‚ à°…à°‚à°¦à±à°¬à°¾à°Ÿà±à°²à±‹ లేదà±.\nకెమెరా సరిగà±à°—à°¾ కనెకà±à°Ÿà± చేయబడిందో లేదో తనిఖీ చేయండి.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$నౠఎగà±à°®à°¤à°¿ చేయడం సాధà±à°¯à°‚ కాలేదà±",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "ఫైలà±â€Œà°¨à± సేవౠచేయడం సాధà±à°¯à°‚ కాలేదà±",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ఫోటోనౠతీయడం సాధà±à°¯à°‚ కాలేదà±",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "రికారà±à°¡à°¿à°‚à°—à±â€Œà°¨à± à°ªà±à°°à°¾à°°à°‚à°­à°¿à°‚à°šà°¡à°‚ సాధà±à°¯à°‚ కాలేదà±",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "à°à°¦à±€ రికారà±à°¡à± చేయబడలేదà±",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "రికారà±à°¡à°¿à°‚గౠఆపివేయబడింది",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ యాకà±à°Ÿà°¿à°µà±â€Œà°—à°¾ ఉంది",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "à°—à±à°¯à°¾à°²à°°à±€à°•à°¿ వెళà±à°²à±",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "తొలగించà±",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "à°¡à°¿à°¸à±à°•à±â€Œà°•à°¿ à°Žà°—à±à°®à°¤à°¿ చేయి",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "à°®à±à°¦à±à°°à°¿à°‚à°šà±",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "వెనà±à°•à°•à± వెళà±à°²à±",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "à°—à±à°¯à°¾à°²à°°à±€ à°šà°¿à°¤à±à°°à°¾à°²à±",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à±",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "à°—à±à°°à°¿à°¡à± à°°à°•à°‚",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "టైమరౠవà±à°¯à°µà°§à°¿",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "à°…à°­à°¿à°ªà±à°°à°¾à°¯à°¾à°¨à±à°¨à°¿ పంపà±",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "సహాయం",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "గోలà±à°¡à±†à°¨à± రేషియో‌",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 సెకనà±à°²à±",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 సెకనà±à°²à±",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "ఫోటోనౠతీయి",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "ఫోటోనౠతీయడం ఆపివేయి",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "రికారà±à°¡à°¿à°‚à°—à±â€Œà°¨à± à°ªà±à°°à°¾à°°à°‚à°­à°¿à°‚à°šà±",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "రికారà±à°¡à± చేయడం ఆపివేయి",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "తరà±à°µà°¾à°¤à°¿ కెమెరాకౠమారండి",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "వీడియో రికారà±à°¡à°¿à°‚గౠమోడà±â€Œà°•à± మారండి",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "ఫోటో మోడà±â€Œà°•à± మారండి",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "టైమరà±",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "మిరà±à°°à°°à°¿à°‚à°—à±",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "à°—à±à°°à°¿à°¡à±",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "మైకà±à°°à±‹à°«à±‹à°¨à±",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "సరే",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "à°°à°¦à±à°¦à± చేయి",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "మీరౠ$file$ని నిజంగా తీసివేయాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à°¾?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "మీరౠ$count$ అంశాలనౠనిజంగా తీసివేయాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à°¾?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "కెమెరాతో తీసిన ఫోటోలౠమరియౠవీడియోలà±, డౌనà±â€Œà°²à±‹à°¡à±â€Œà°²à± ఫోలà±à°¡à°°à±â€Œà°•à± తరలించబడతాయి. మీరౠవాటిని ఫైలà±à°¸à±â€Œà°²à±‹ యాకà±à°¸à±†à°¸à± చేయవచà±à°šà±. నిలà±à°µ à°…à°¨à±à°®à°¤à±à°²à± కలిగిన యాపà±â€Œà°²à± మీ ఫోటోలౠమరియౠవీడియోలనౠయాకà±à°¸à±†à°¸à± చేయగలà±à°—à±à°¤à°¾à°¯à°¿.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/th/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/th/messages.json
deleted file mode 100644
index 72e6885f629..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/th/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "à¸à¸¥à¹‰à¸­à¸‡",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "ถ่ายภาพà¹à¸¥à¸°à¸šà¸±à¸™à¸—ึà¸à¸§à¸´à¸”ีโอด้วยà¸à¸¥à¹‰à¸­à¸‡",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "ข้อผิดพลาดเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸£à¸°à¸šà¸šà¹„ฟล์",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "ขณะนี้à¸à¸¥à¹‰à¸­à¸‡à¹„ม่พร้อมใช้งาน\nโปรดตรวจสอบว่าà¸à¸¥à¹‰à¸­à¸‡à¹€à¸Šà¸·à¹ˆà¸­à¸¡à¸•à¹ˆà¸­à¸­à¸¢à¹ˆà¸²à¸‡à¸–ูà¸à¸•à¹‰à¸­à¸‡",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "ส่งออภ$file$ ไม่ได้",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "บันทึà¸à¹„ฟล์ไม่ได้",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "ถ่ายภาพไม่ได้",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "เริ่มบันทึà¸à¸§à¸´à¸”ีโอไม่ได้",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "ไม่ได้บันทึà¸à¸‚้อมูลใด",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "หยุดบันทึà¸à¸§à¸´à¸”ีโอà¹à¸¥à¹‰à¸§",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ ทำงาน",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "ไปที่à¹à¸à¸¥à¹€à¸¥à¸­à¸£à¸µ",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "ลบ",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ส่งออà¸à¹„ปยังดิสà¸à¹Œ",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "พิมพ์",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "à¸à¸¥à¸±à¸š",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "รูปภาพในà¹à¸à¸¥à¹€à¸¥à¸­à¸£à¸µ",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่า",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "ประเภทตารางà¸à¸£à¸´à¸”",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ระยะเวลาของตัวจับเวลา",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ส่งความคิดเห็น",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "ความช่วยเหลือ",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "อัตราส่วนทองคำ",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 วินาที",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 วินาที",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "ถ่ายภาพ",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "หยุดถ่ายภาพ",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "เริ่มบันทึà¸",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "หยุดบันทึà¸",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "เปลี่ยนไปใช้à¸à¸¥à¹‰à¸­à¸‡à¸–ัดไป",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "เปลี่ยนเป็นโหมดบันทึà¸à¸§à¸´à¸”ีโอ",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "เปลี่ยนเป็นโหมดถ่ายภาพ",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "ตัวจับเวลา",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "à¸à¸²à¸£à¸¡à¸´à¹€à¸£à¸­à¸£à¹Œ",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "ตารางà¸à¸£à¸´à¸”",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "ไมโครโฟน",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "ตà¸à¸¥à¸‡",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "ยà¸à¹€à¸¥à¸´à¸",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "คุณต้องà¸à¸²à¸£à¸™à¸³ $file$ ออà¸à¸ˆà¸£à¸´à¸‡à¹† ใช่ไหม",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "คุณต้องà¸à¸²à¸£à¸™à¸³ $count$ รายà¸à¸²à¸£à¸­à¸­à¸à¸ˆà¸£à¸´à¸‡à¹† ใช่ไหม",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "รูปภาพà¹à¸¥à¸°à¸§à¸´à¸”ีโอที่ถ่ายจาà¸à¸à¸¥à¹‰à¸­à¸‡à¸ˆà¸°à¸¢à¹‰à¸²à¸¢à¹„ปยังโฟลเดอร์ \"ดาวน์โหลด\" คุณจะเข้าถึงรายà¸à¸²à¸£à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¹„ด้ในà¹à¸­à¸› Files\n\nà¹à¸­à¸›à¸—ี่มีสิทธิ์ในพื้นที่เà¸à¹‡à¸šà¸‚้อมูลจะเข้าถึงรูปภาพà¹à¸¥à¸°à¸§à¸´à¸”ีโอของคุณได้",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/tr/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/tr/messages.json
deleted file mode 100644
index 352ab165f75..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/tr/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Kamera",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Kameranızla fotoğraf çekin ve video kaydedin.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Dosya sistemi hataları.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Kameranız şu anda kullanılamıyor.\nLütfen kameranın bağlı olup olmadığını kontrol edin.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "$file$ dışa aktarılamıyor.",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Dosya kaydedilemiyor",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Fotoğraf çekilemiyor",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Kayıt başlatılamıyor",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Hiçbir şey kaydedilmedi",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Kayıt durduruldu",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ etkin",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Galeriye git",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Sil",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Diske aktar",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Yazdır",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Geri dön",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Galeri resimleri",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Ayarlar",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Kılavuz türü",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Zamanlayıcı süresi",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Geri bildirim gönder",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Yardım",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3'e 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4'e 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Altın oran",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 saniye",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 saniye",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Fotoğraf çek",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Fotoğraf çekmeyi durdur",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Kaydı başlat",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Kaydı durdur",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Sonraki kameraya geç",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Video kaydetmeye geç",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Fotoğraf çekmeye geç",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Zamanlayıcı",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Yansıtma",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Kılavuz",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Mikrofon",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "Tamam",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Ä°ptal",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "$file$ adlı dosyayı gerçekten kaldırmak istiyor musunuz?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "$count$ öğeyi gerçekten kaldırmak istiyor musunuz?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Kamerayla çekilen fotoğraflar ve videolar, İndirilenler klasörüne taşınacaktır. Bunlara Dosyalar uygulamasından erişebilirsiniz.\n\nDepolama iznine sahip uygulamalar fotoğraflarınıza ve videolarınıza erişebilecektir.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/uk/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/uk/messages.json
deleted file mode 100644
index 0e10110a6ab..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/uk/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Камера",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Фотографуйте й запиÑуйте відео камерою.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Помилки файлової ÑиÑтеми.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Камера недоÑтупна.\nПеревірте, чи Ñ—Ñ— правильно під’єднано.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Ðе вдалоÑÑŒ екÑпортувати файл $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ файл",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Ðе вдалоÑÑ Ñфотографувати",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Ðе вдалоÑÑ Ð¿Ð¾Ñ‡Ð°Ñ‚Ð¸ запиÑ",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Ðічого не запиÑано",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Ð—Ð°Ð¿Ð¸Ñ Ð¿Ñ€Ð¸Ð¿Ð¸Ð½ÐµÐ½Ð¾",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "Камера $camera$ активна",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Перейти в галерею",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Видалити",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "ЕкÑпортувати на диÑк",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "Друкувати",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Ðазад",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð² галереї",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "ÐалаштуваннÑ",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Тип Ñітки",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "ТриваліÑÑ‚ÑŒ таймера",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "ÐадіÑлати відгук",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Довідка",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 на 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 на 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Золотий перетин",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 Ñекунди",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 Ñекунд",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Сфотографувати",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Припинити фотографувати",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Почати запиÑ",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Припинити запиÑ",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "ПеремкнутиÑÑ Ð½Ð° наÑтупну камеру",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Перейти в режим запиÑу відео",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Перейти в режим фотозйомки",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Таймер",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Дзеркальне відображеннÑ",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "Сітка",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Мікрофон",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "СкаÑувати",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Видалити файл $file$?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Видалити елементи ($count$)?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Фото й відео з камери перенеÑутьÑÑ Ð² папку \"ЗавантаженнÑ\", Ñ– ви зможете відкрити Ñ—Ñ… у Файлах.\n\nТакож до них матимуть доÑтуп додатки, Ñким дозволено викориÑтовувати пам’ÑÑ‚ÑŒ приÑтрою.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/vi/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/vi/messages.json
deleted file mode 100644
index b1066fceea3..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/vi/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "Máy ảnh",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "Chụp ảnh và quay video bằng máy ảnh của bạn.",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "Lỗi hệ thống tệp.",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "Máy ảnh của bạn hiện không sử dụng được.\nVui lòng kiểm tra xem máy ảnh có được kết nối đúng cách không.",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "Không thể xuất $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "Không thể lưu tệp",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "Không thể chụp ảnh",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "Không thể bắt đầu quay video",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "Chưa ghi dữ liệu nào",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "Äã dừng quay video",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "$camera$ đang hoạt động",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "Chuyển đến thư viện",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "Xóa",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "Xuất ra đĩa",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "In",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "Quay lại",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "Ảnh trong thư viện",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "Cài đặt",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "Loại lưới",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "Thá»i lượng bá»™ hẹn giá»",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "Gửi phản hồi",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "Trợ giúp",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "Tỷ lệ vàng",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 giây",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 giây",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "Chụp ảnh",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "Dừng chụp ảnh",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "Bắt đầu quay video",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "Dừng quay video",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "Chuyển sang máy ảnh tiếp theo",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "Chuyển sang quay video",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "Chuyển sang chụp ảnh",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "Bá»™ hẹn giá»",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "Phản chiếu",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "LÆ°á»›i",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "Micrô",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "OK",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "Hủy",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "Bạn có thực sự muốn xóa $file$ không?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "Bạn có thực sự muốn xóa $count$ mục không?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "Ảnh và video mà bạn chụp và quay bằng máy ảnh sẽ được chuyển đến thÆ° mục Tài nguyên đã tải xuống. Bạn có thể truy cập vào những ná»™i dung này trong ứng dụng Files.\n\nỨng dụng có quyá»n truy cập vào bá»™ nhá»› sẽ có quyá»n truy cập vào ảnh và video của bạn.",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_CN/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_CN/messages.json
deleted file mode 100644
index 4508a1a6d58..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_CN/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "相机",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "使用相机æ‹ç…§å’Œå½•åˆ¶è§†é¢‘。",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "文件系统出错。",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "您的相机目å‰ä¸å¯ç”¨ã€‚\n请检查此相机是å¦è¿žæŽ¥æ­£ç¡®ã€‚",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "无法导出 $file$",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "无法ä¿å­˜æ–‡ä»¶",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "无法æ‹ç…§",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "无法开始录制",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "未记录任何数æ®",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "å·²åœæ­¢å½•åˆ¶",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "已切æ¢åˆ°$camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "打开图库",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "删除",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "导出到ç£ç›˜",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "打å°",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "返回",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "图库图片",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "设置",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "网格类型",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "定时器时长",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "å‘é€å馈",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "帮助",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "黄金比例",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 秒",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 秒",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "æ‹ç…§",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "åœæ­¢æ‹ç…§",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "开始录制",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "åœæ­¢å½•åˆ¶",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "切æ¢åˆ°ä¸‹ä¸€ä¸ªç›¸æœº",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "切æ¢åˆ°å½•åˆ¶è§†é¢‘模å¼",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "切æ¢åˆ°æ‹ç…§æ¨¡å¼",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "定时器",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "é•œåƒ",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "网格",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "麦克风",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "确定",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "å–消",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "确定è¦ç§»é™¤ $file$ å—?",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "确定è¦ç§»é™¤è¿™ $count$ 个文件å—?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "使用此相机æ‹æ‘„的照片和视频将被移至“下载内容â€æ–‡ä»¶å¤¹ã€‚您å¯åœ¨â€œæ–‡ä»¶â€ä¸­è®¿é—®å®ƒä»¬ã€‚\n\n具有存储æƒé™çš„应用将有æƒè®¿é—®æ‚¨çš„照片和视频。",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_TW/messages.json b/chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_TW/messages.json
deleted file mode 100644
index dd4006d588d..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/_locales/zh_TW/messages.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "NAME": {
- "message": "相機",
- "description": "Name of the Camera App."
- },
- "DESCRIPTION": {
- "message": "使用相機æ‹æ”相片åŠéŒ„製影片。",
- "description": "Short description of the Camera App."
- },
- "ERROR_MSG_FILE_SYSTEM_FAILED": {
- "message": "檔案系統發生錯誤。",
- "description": "Error message shown when failing to read or write the file system."
- },
- "ERROR_MSG_NO_CAMERA": {
- "message": "ç›®å‰ç„¡æ³•ä½¿ç”¨ç›¸æ©Ÿã€‚\n請檢查相機是å¦å·²æ­£ç¢ºé€£æŽ¥ã€‚",
- "description": "Error message shown when it was impossible to connect to the camera due to unavailability."
- },
- "ERROR_MSG_GALLERY_EXPORT_FAILED": {
- "message": "無法匯出「$file$ã€",
- "description": "Error message shown when exporting to an external directory failed. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg"
- }
- }
- },
- "ERROR_MSG_SAVE_FILE_FAILED": {
- "message": "無法儲存檔案",
- "description": "Error message shown when saving/adding a image or video to the file system failed."
- },
- "ERROR_MSG_TAKE_PHOTO_FAILED": {
- "message": "無法æ‹ç…§",
- "description": "Error message shown when failing to take photo."
- },
- "ERROR_MSG_RECORD_START_FAILED": {
- "message": "無法開始錄影",
- "description": "Error message shown when failing to start recording video."
- },
- "ERROR_MSG_EMPTY_RECORDING": {
- "message": "未錄製任何內容",
- "description": "Error message shown when no data is recorded for a recording and it won't be added to the gallery."
- },
- "STATUS_MSG_RECORDING_STOPPED": {
- "message": "å·²åœæ­¢éŒ„å½±",
- "description": "Status message for spoken feedback when video recording has been stopped."
- },
- "STATUS_MSG_CAMERA_SWITCHED": {
- "message": "已切æ›è‡³ $camera$",
- "description": "Status message for spoken feedback when switching over to another camera.",
- "placeholders": {
- "camera": {
- "content": "$1",
- "example": "USB WebCam (12ab:5678) User-facing"
- }
- }
- },
- "GALLERY_BUTTON": {
- "message": "å‰å¾€åœ–片庫",
- "description": "Label for the gallery button."
- },
- "DELETE_BUTTON": {
- "message": "刪除",
- "description": "Label for the delete button."
- },
- "EXPORT_BUTTON": {
- "message": "匯出至ç£ç¢Ÿ",
- "description": "Label for the exporting button."
- },
- "PRINT_BUTTON": {
- "message": "列å°",
- "description": "Label for the printing button."
- },
- "BACK_BUTTON": {
- "message": "返回",
- "description": "Label for the back button."
- },
- "GALLERY_IMAGES": {
- "message": "圖片庫圖片",
- "description": "Text to speech label for the gallery list."
- },
- "SETTINGS_BUTTON": {
- "message": "設定",
- "description": "Label for the settings button."
- },
- "GRID_TYPE_BUTTON": {
- "message": "格線類型",
- "description": "Label for the button of grid-type options."
- },
- "TIMER_DURATION_BUTTON": {
- "message": "計時器倒數時間",
- "description": "Label for the button of timer-duration options."
- },
- "FEEDBACK_BUTTON": {
- "message": "æä¾›æ„見",
- "description": "Label for the feedback button."
- },
- "HELP_BUTTON": {
- "message": "說明",
- "description": "Label for the help button."
- },
- "LABEL_GRID_3X3": {
- "message": "3 x 3",
- "description": "Label for for grid-type: 3x3."
- },
- "ARIA_GRID_3X3": {
- "message": "3 乘 3",
- "description": "Label for spoken feedback to read out grid-type: 3x3."
- },
- "LABEL_GRID_4X4": {
- "message": "4 x 4",
- "description": "Label for for grid-type: 4x4."
- },
- "ARIA_GRID_4X4": {
- "message": "4 乘 4",
- "description": "Label for spoken feedback to read out grid-type: 4x4."
- },
- "LABEL_GRID_GOLDEN": {
- "message": "黃金比例",
- "description": "Label for grid-type: golden ratio."
- },
- "LABEL_TIMER_3S": {
- "message": "3 秒",
- "description": "Label for timer-duration: 3 seconds."
- },
- "LABEL_TIMER_10S": {
- "message": "10 秒",
- "description": "Label for timer-duration: 10 seconds."
- },
- "TAKE_PHOTO_BUTTON": {
- "message": "æ‹ç…§",
- "description": "Label for the shutter button to take photo."
- },
- "TAKE_PHOTO_CANCEL_BUTTON": {
- "message": "åœæ­¢æ‹ç…§",
- "description": "Label for the shutter button to cancel countdown timer and stop taking photo."
- },
- "RECORD_VIDEO_START_BUTTON": {
- "message": "開始錄製",
- "description": "Label for the shutter button to start recording."
- },
- "RECORD_VIDEO_STOP_BUTTON": {
- "message": "åœæ­¢éŒ„å½±",
- "description": "Label for the shutter button to stop recording."
- },
- "SWITCH_CAMERA_BUTTON": {
- "message": "切æ›è‡³ä¸‹ä¸€å€‹ç›¸æ©Ÿ",
- "description": "Label for the button to switch to next connected camera. Eg. switching from front facing to back facing, or a camera connected via USB. The button is shown only when more than one camera is connected."
- },
- "SWITCH_RECORD_VIDEO_BUTTON": {
- "message": "切æ›ç‚ºéŒ„影模å¼",
- "description": "Label for the button to switch to record video mode."
- },
- "SWITCH_TAKE_PHOTO_BUTTON": {
- "message": "切æ›ç‚ºæ‹ç…§æ¨¡å¼",
- "description": "Label for the button to switch to take photo mode."
- },
- "TOGGLE_TIMER_BUTTON": {
- "message": "計時器",
- "description": "Label for the checkbox to toggle the countdown timer."
- },
- "TOGGLE_MIRROR_BUTTON": {
- "message": "é¡åƒåŠŸèƒ½",
- "description": "Label for the checkbox to toggle the preview mirroring. Eg. if mirroring is toggled on, preview will be flipped horizontally."
- },
- "TOGGLE_GRID_BUTTON": {
- "message": "格線",
- "description": "Label for the checkbox to toggle the grid shown on preview."
- },
- "TOGGLE_MIC_BUTTON": {
- "message": "麥克風",
- "description": "Label for the checkbox to toggle the microphone for recording video."
- },
- "DIALOG_OK_BUTTON": {
- "message": "確定",
- "description": "Label for the accepting button in the dialog."
- },
- "DIALOG_CANCEL_BUTTON": {
- "message": "å–消",
- "description": "Label for the dismissing button in the dialog."
- },
- "DELETE_CONFIRMATION_MSG": {
- "message": "確定è¦ç§»é™¤ã€Œ$file$ã€å—Žï¼Ÿ",
- "description": "Confirmation message before removing a selected item from the gallery. Expects a file name to be passed in.",
- "placeholders": {
- "file": {
- "content": "$1",
- "example": "IMG_20160520_000000.jpg."
- }
- }
- },
- "DELETE_MULTI_CONFIRMATION_MSG": {
- "message": "確定è¦ç§»é™¤é€™ $count$ 個項目嗎?",
- "description": "Confirmation message before removing multiple selected items from the gallery. Expects a file count to be passed in.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "20."
- }
- }
- },
- "MIGRATE_PICTURES_MSG": {
- "message": "使用相機æ‹æ”的相片和影片將移至「下載內容ã€è³‡æ–™å¤¾ï¼Œä½ å¯ä»¥åœ¨ã€Œæª”案ã€æ‡‰ç”¨ç¨‹å¼ä¸­æ‰¾åˆ°é€™äº›å…§å®¹ã€‚\n\n具備儲存空間權é™çš„應用程å¼å°‡å¯å­˜å–你的相片和影片。",
- "description": "Message shown before moving all photos and videos stored in the Camera App to the Downloads folder."
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/chromeos/camera/src/manifest.json b/chromium/chrome/browser/resources/chromeos/camera/src/manifest.json
deleted file mode 100644
index f0c5c272c5b..00000000000
--- a/chromium/chrome/browser/resources/chromeos/camera/src/manifest.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3oA1Ox8PiRORjuFDgJeWcwnzZDPHUzuH6fymTrx8KPybhfd6XIYFXvilSsRnsGKPslN+BXke3HWC4/MoB2HM2pNGmsQ+jNyTh2dgk4ISdZYRLfqjxr846/245dkznCJLYAZr72Lk+vRZUyYBcLNNox8jRV5ZF16+8uPPUsUiqbQIDAQAB",
- "manifest_version": 2,
- "name": "__MSG_name__",
- "description": "__MSG_description__",
- "version": "6.1.0",
- "default_locale": "en",
- "minimum_chrome_version": "60.0.0.0",
- "icons": {
- "48": "images/camera_app_icons_48.png",
- "128": "images/camera_app_icons_128.png"
- },
- "permissions": [
- "videoCapture",
- "audioCapture",
- "storage",
- "unlimitedStorage",
- "idle",
- "chromeosInfoPrivate",
- "metricsPrivate",
- "fileManagerPrivate",
- "fileSystem.requestDownloads",
- {"fileSystem": ["write", "directory"]},
- "https://www.google-analytics.com/"
- ],
- "app": {
- "background": {
- "scripts": [
- "js/mojo/mojo_bindings_lite.js",
- "js/mojo/camera_intent.mojom-lite.js",
- "js/mojo/camera_app_helper.mojom-lite.js",
- "js/mojo/chrome_helper.js",
- "js/intent.js",
- "js/background.js"
- ]
- }
- }
-}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/classic_keymap.json b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/classic_keymap.json
deleted file mode 100644
index 9386c6b8fc2..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/classic_keymap.json
+++ /dev/null
@@ -1,558 +0,0 @@
-{ "bindings": [
- {"command": "handleTabPrev",
- "sequence": {"keys": {"keyCode": [9],
- "shiftKey": [true]}
- }},
-
- {"command": "handleTab",
- "sequence": {"keys": {"keyCode": [9]}
- }},
-
- {"command": "stopSpeech",
- "sequence": {"keys": {"ctrlKey": [true],
- "keyCode": [17]}
- }},
-
- {"command": "toggleKeyPrefix",
- "sequence": {"keys": {"ctrlKey": [true],
- "keyCode": [186]}
- }},
-
- {"command": "toggleStickyMode",
- "sequence": {"keys": {"keyCode": [45]},
- "doubleTap": true,
- "platformFilter": 5
- }},
-
- {"command": "toggleStickyMode",
- "sequence": {"keys": {"keyCode": [91]},
- "doubleTap": true,
- "platformFilter": 10
- }},
-
- {"command": "passThroughMode",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [27]}
- }},
-
- {"command": "performDefaultAction",
- "sequence": {
- "keys": {"keyCode": [13]}
- }},
-
- {"command": "forceClickOnCurrentItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [32]}
- }},
-
- {"command": "forceDoubleClickOnCurrentItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [32]},
- "doubleTap": true
- }},
-
- {"command": "left",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [37]}
- }},
-
- {"command": "backward",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [38]}
- }},
-
- {"command": "right",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [39]}
- }},
-
- {"command": "forward",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [40]}
- }},
-
- {"command": "fullyDescribe",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 75]}
- }},
-
- {"command": "speakTimeAndDate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 68]}
- }},
-
- {"command": "readFromHere",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [82]}
- }},
-
- {"command": "toggleSelection",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [83]}
- }},
-
- {"command": "jumpToTop",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [36]},
- "platformFilter": 7
- }},
-
- {"command": "jumpToTop",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [65]},
- "platformFilter": 8
- }},
-
- {"command": "jumpToBottom",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [35]},
- "platformFilter": 7
- }},
-
- {"command": "jumpToBottom",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [90]},
- "platformFilter": 8
- }},
-
- {"command": "nextGranularity",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [187]}
- }},
-
- {"command": "previousGranularity",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [189]}
- }},
-
- {"command": "toggleKeyboardHelp",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [190]}
- }},
-
- {"command": "toggleSearchWidget",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [191]}
- }},
-
- {"command": "contextMenu",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [188]}
- }},
-
- {"command": "decreaseTtsRate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [219]}
- }},
-
- {"command": "increaseTtsRate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [221]}
- }},
-
- {"command": "decreaseTtsPitch",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [186]}
- }},
-
- {"command": "increaseTtsPitch",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222]}
- }},
-
- {"command": "cyclePunctuationEcho",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [69, 80]}
- }},
-
- {"command": "cycleTypingEcho",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [69, 84]}
- }},
-
- {"command": "toggleEarcons",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [69, 69]}
- }},
-
- {"command": "enterShifter",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [220]}
- }},
-
- {"command": "exitShifter",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [8]}
- }},
-
- {"command": "toggleChromeVox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [65, 65]}
- }},
-
- {"command": "toggleChromeVoxVersion",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [81, 81]}
- }},
-
- {"command": "readCurrentTitle",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 84]}
- }},
-
- {"command": "readCurrentURL",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 85]}
- }},
-
- {"command": "readLinkURL",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 76]}
- }},
-
- {"command": "openLongDesc",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 68]}
- }},
-
- {"command": "showFormsList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 70]}
- }},
-
- {"command": "showHeadingsList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 72]}
- }},
-
- {"command": "showLandmarksList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 186]}
- }},
-
- {"command": "showLinksList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 76]}
- }},
-
- {"command": "showTablesList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 84]}
- }},
-
- {"command": "nextHeading1",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 49]}
- }},
-
- {"command": "nextHeading2",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 50]}
- }},
-
- {"command": "nextHeading3",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 51]}
- }},
-
- {"command": "nextHeading4",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 52]}
- }},
-
- {"command": "nextHeading5",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 53]}
- }},
-
- {"command": "nextHeading6",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 54]}
- }},
-
- {"command": "nextArticle",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 65]}
- }},
-
- {"command": "nextButton",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 66]}
- }},
-
- {"command": "nextComboBox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 67]}
- }},
-
- {"command": "nextEditText",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 69]}
- }},
-
- {"command": "nextFormField",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 70]}
- }},
-
- {"command": "nextGraphic",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 71]}
- }},
-
- {"command": "nextHeading",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 72]}
- }},
-
- {"command": "nextListItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 73]}
- }},
-
- {"command": "nextLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 76]}
- }},
-
- {"command": "nextList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 79]}
- }},
-
- {"command": "nextMath",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 77]}
- }},
-
- {"command": "nextMedia",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 87]}
- }},
-
- {"command": "nextRadio",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 82]}
- }},
-
- {"command": "nextTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 84]}
- }},
-
- {"command": "nextVisitedLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 86]}
- }},
-
- {"command": "nextCheckbox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 88]}
- }},
-
- {"command": "showKbExplorerPage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 75]}
- }},
-
- {"command": "showOptionsPage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 79]}
- }},
-
- {"command": "showNextUpdatePage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 78]}
- }},
-
- {"command": "help",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 84]}
- }},
-
- {"command": "previousHeading1",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 49]}
- }},
-
- {"command": "previousHeading2",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 50]}
- }},
-
- {"command": "previousHeading3",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 51]}
- }},
-
- {"command": "previousHeading4",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 52]}
- }},
-
- {"command": "previousHeading5",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 53]}
- }},
-
- {"command": "previousHeading6",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 54]}
- }},
-
- {"command": "previousArticle",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 65]}
- }},
-
- {"command": "previousButton",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 66]}
- }},
-
- {"command": "previousComboBox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 67]}
- }},
-
- {"command": "previousEditText",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 69]}
- }},
-
- {"command": "previousFormField",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 70]}
- }},
-
- {"command": "previousGraphic",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 71]}
- }},
-
- {"command": "previousHeading",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 72]}
- }},
-
- {"command": "previousListItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 73]}
- }},
-
- {"command": "previousLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 76]}
- }},
-
- {"command": "previousList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 79]}
- }},
-
- {"command": "previousMath",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 77]}
- }},
-
- {"command": "previousMedia",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 87]}
- }},
-
- {"command": "previousRadio",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 82]}
- }},
-
- {"command": "previousTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 84]}
- }},
-
- {"command": "previousVisitedLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 86]}
- }},
-
- {"command": "previousCheckbox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 88]}
- }},
-
- {"command": "announceHeaders",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 72]}
- }},
-
- {"command": "speakTableLocation",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 76]}
- }},
-
- {"command": "exitShifterContent",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 69]}
- }},
-
- {"command": "nextLandmark",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 186]}
- }},
-
- {"command": "previousLandmark",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 186]}
- }},
-
- {"command": "goToRowFirstCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 186]}
- }},
-
- {"command": "goToColFirstCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 188]}
- }},
-
- {"command": "goToColLastCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 190]}
- }},
-
- {"command": "goToFirstCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 219]}
- }},
-
- {"command": "goToLastCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 221]}
- }},
-
- {"command": "goToRowLastCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 222]}
- }},
-
- {"command": "pauseAllMedia",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [88]}
- }},
-
- {"command": "enableConsoleTts",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [68, 67]}
- }},
-
- {"command": "toggleBrailleCaptions",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [68, 66]}
- }},
-
- {"command": "toggleSemantics",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [77, 83]}
- }}
- ]
-}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/experimental.json b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/experimental.json
deleted file mode 100644
index 37090b7e17a..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/experimental.json
+++ /dev/null
@@ -1,431 +0,0 @@
-{ "bindings": [
- {"command": "handleTabPrev",
- "sequence": {"keys": {"keyCode": [9],
- "shiftKey": [true]}
- }},
-
- {"command": "handleTab",
- "sequence": {"keys": {"keyCode": [9]}
- }},
-
- {"command": "stopSpeech",
- "sequence": {"keys": {"ctrlKey": [true],
- "keyCode": [17]}
- }},
-
- {"command": "toggleKeyPrefix",
- "sequence": {"keys": {"ctrlKey": [true],
- "keyCode": [90]}
- }},
-
- {"command": "forceClickOnCurrentItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [32]}
- }},
-
- {"command": "left",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [37]}
- }},
-
- {"command": "backward",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [38]}
- }},
-
- {"command": "right",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [39]}
- }},
-
- {"command": "forward",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [40]}
- }},
-
- {"command": "help",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [72]}
- }},
-
- {"command": "fullyDescribe",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [75]}
- }},
-
- {"command": "readFromHere",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [82]}
- }},
-
- {"command": "toggleSelection",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [83]}
- }},
-
- {"command": "readLinkURL",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [85]}
- }},
-
- {"command": "skipBackward",
- "sequence": {"cvoxModifier": false,
- "keys": {"keyCode": [37]}
- }},
-
- {"command": "skipForward",
- "sequence": {"cvoxModifier": false,
- "keys": {"keyCode": [39]}
- }},
-
- {"command": "decreaseTtsPitch",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [186]}
- }},
-
- {"command": "nextGranularity",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [187]}
- }},
-
- {"command": "previousGranularity",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [189]}
- }},
-
- {"command": "showPowerKey",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [190]}
- }},
-
- {"command": "toggleSearchWidget",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [191]}
- }},
-
- {"command": "decreaseTtsRate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [219]}
- }},
-
- {"command": "increaseTtsRate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [221]}
- }},
-
- {"command": "increaseTtsPitch",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222]}
- }},
-
- {"command": "cyclePunctuationLevel",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [81]}
- }},
-
- {"command": "toggleChromeVox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [65, 65]}
- }},
-
- {"command": "enableConsoleTts",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [66, 67]}
- }},
-
- {"command": "readCurrentTitle",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 84]}
- }},
-
- {"command": "readCurrentURL",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67, 85]}
- }},
-
- {"command": "showFormsList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 70]}
- }},
-
- {"command": "showHeadingsList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 72]}
- }},
-
- {"command": "showLinksList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 76]}
- }},
-
- {"command": "showTablesList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 84]}
- }},
-
- {"command": "nextHeading1",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 49]}
- }},
-
- {"command": "nextHeading2",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 50]}
- }},
-
- {"command": "nextHeading3",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 51]}
- }},
-
- {"command": "nextHeading4",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 52]}
- }},
-
- {"command": "nextHeading5",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 53]}
- }},
-
- {"command": "nextHeading6",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 54]}
- }},
-
- {"command": "nextButton",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 66]}
- }},
-
- {"command": "nextComboBox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 67]}
- }},
-
- {"command": "nextEditText",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 69]}
- }},
-
- {"command": "nextFormField",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 70]}
- }},
-
- {"command": "nextGraphic",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 71]}
- }},
-
- {"command": "nextHeading",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 72]}
- }},
-
- {"command": "nextListItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 73]}
- }},
-
- {"command": "nextLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 76]}
- }},
-
- {"command": "nextList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 79]}
- }},
-
- {"command": "nextRadio",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 82]}
- }},
-
- {"command": "nextTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 84]}
- }},
-
- {"command": "nextCheckbox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 88]}
- }},
-
- {"command": "showKbExplorerPage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 75]}
- }},
-
- {"command": "showOptionsPage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 87]}
- }},
-
- {"command": "previousHeading1",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 49]}
- }},
-
- {"command": "previousHeading2",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 50]}
- }},
-
- {"command": "previousHeading3",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 51]}
- }},
-
- {"command": "previousHeading4",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 52]}
- }},
-
- {"command": "previousHeading5",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 53]}
- }},
-
- {"command": "previousHeading6",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 54]}
- }},
-
- {"command": "previousButton",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 66]}
- }},
-
- {"command": "previousComboBox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 67]}
- }},
-
- {"command": "previousEditText",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 69]}
- }},
-
- {"command": "previousFormField",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 70]}
- }},
-
- {"command": "previousGraphic",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 71]}
- }},
-
- {"command": "previousHeading",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 72]}
- }},
-
- {"command": "previousListItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 73]}
- }},
-
- {"command": "previousLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 76]}
- }},
-
- {"command": "previousList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 79]}
- }},
-
- {"command": "previousRadio",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 82]}
- }},
-
- {"command": "previousTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 84]}
- }},
-
- {"command": "previousCheckbox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 88]}
- }},
-
- {"command": "toggleTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 69]}
- }},
-
- {"command": "announceHeaders",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 72]}
- }},
-
- {"command": "speakTableLocation",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 76]}
- }},
-
- {"command": "showLandmarksList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76, 186]}
- }},
-
- {"command": "nextLandmark",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [78, 186]}
- }},
-
- {"command": "previousLandmark",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [80, 186]}
- }},
-
- {"command": "skipToRowBeginning",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 186]}
- }},
-
- {"command": "skipToColBeginning",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 188]}
- }},
-
- {"command": "skipToColEnd",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 190]}
- }},
-
- {"command": "skipToBeginning",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 219]}
- }},
-
- {"command": "skipToEnd",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 221]}
- }},
-
- {"command": "skipToRowEnd",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84, 222]}
- }},
-
- {"command": "cycleDomain",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [77, 68]}
- }},
-
- {"command": "cycleTraversalMode",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [77, 84]}
- }},
-
- {"command": "toggleExplore",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [77, 69]}
- }}
- ]
-}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/flat_keymap.json b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/flat_keymap.json
deleted file mode 100644
index bde46804d9a..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/flat_keymap.json
+++ /dev/null
@@ -1,621 +0,0 @@
-{ "bindings": [
- {"command": "handleTabPrev",
- "sequence": {"keys": {"keyCode": [9],
- "shiftKey": [true]}
- }},
-
- {"command": "handleTab",
- "sequence": {"keys": {"keyCode": [9]}
- }},
-
- {"command": "stopSpeech",
- "sequence": {"keys": {"ctrlKey": [true],
- "keyCode": [17]}
- }},
-
- {"command": "enterShifter",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [220]}
- }},
-
- {"command": "exitShifter",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [8]}
- }},
-
- {"command": "nextGranularity",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [187]}
- }},
-
- {"command": "previousGranularity",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [189]}
- }},
-
- {"command": "toggleKeyPrefix",
- "sequence": {"keys": {"ctrlKey": [true],
- "keyCode": [186]}
- }},
-
- {"command": "toggleStickyMode",
- "sequence": {"keys": {"keyCode": [45]},
- "doubleTap": true,
- "platformFilter": 5
- }},
-
- {"command": "toggleStickyMode",
- "sequence": {"keys": {"keyCode": [91]},
- "doubleTap": true,
- "platformFilter": 10
- }},
-
- {"command": "passThroughMode",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [27]}
- }},
-
- {"command": "performDefaultAction",
- "sequence": {
- "keys": {"keyCode": [13]}
- }},
-
- {"command": "forceClickOnCurrentItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [32]}
- }},
-
- {"command": "forceDoubleClickOnCurrentItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [32]},
- "doubleTap": true
- }},
-
- {"command": "previousCharacter",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [37]}
- }},
-
- {"command": "previousWord",
- "sequence": {
- "keys": {"keyCode": [37],
- "ctrlKey": [true]}
- }},
-
-
- {"command": "previousSentence",
- "sequence": {
- "keys": {"keyCode": [38],
- "altKey": [true]}
- }},
-
- {"command": "previousLine",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [38]}
- }},
-
- {"command": "previousGroup",
- "sequence": {
- "keys": {"keyCode": [38],
- "ctrlKey": [true]}
- }},
-
- {"command": "previousRow",
- "sequence": {"keys": {"keyCode": [38],
- "altKey": [true],
- "shiftKey": [true]}
- }},
-
- {"command": "previousCol",
- "sequence": {"keys": {"keyCode": [37],
- "altKey": [true],
- "shiftKey": [true]}
- }},
-
- {"command": "nextCharacter",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [39]}
- }},
-
- {"command": "nextWord",
- "sequence": {
- "keys": {"keyCode": [39],
- "ctrlKey": [true]}
- }},
-
- {"command": "nextSentence",
- "sequence": {
- "keys": {"keyCode": [40],
- "altKey": [true]}
- }},
-
- {"command": "nextLine",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [40]}
- }},
-
- {"command": "nextGroup",
- "sequence": {
- "keys": {"keyCode": [40],
- "ctrlKey": [true]}
- }},
-
- {"command": "nextRow",
- "sequence": {"keys": {"keyCode": [40],
- "altKey": [true],
- "shiftKey": [true]}
- }},
-
- {"command": "nextCol",
- "sequence": {"keys": {"keyCode": [39],
- "altKey": [true],
- "shiftKey": [true]}
- }},
-
- {"command": "left",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [37],
- "shiftKey": [true]}
- }},
-
- {"command": "backward",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [38],
- "shiftKey": [true]}
- }},
-
- {"command": "forward",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [40],
- "shiftKey": [true]}
- }},
-
- {"command": "right",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [39],
- "shiftKey": [true]}
- }},
-
- {"command": "jumpToTop",
- "sequence": {
- "keys": {"keyCode": [36],
- "ctrlKey": [true]}
- }},
-
- {"command": "jumpToBottom",
- "sequence": {
- "keys": {"keyCode": [35],
- "ctrlKey": [true]}
- }},
-
- {"command": "moveToStartOfLine",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [36]}
- }},
-
- {"command": "moveToEndOfLine",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [35]}
- }},
-
- {"command": "fullyDescribe",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [75]}
- }},
-
- {"command": "speakTimeAndDate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [123]}
- }},
-
- {"command": "readFromHere",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [82]}
- }},
-
- {"command": "toggleSelection",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [83]}
- }},
-
- {"command": "contextMenu",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [188]}
- }},
-
- {"command": "toggleKeyboardHelp",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [190]}
- }},
-
- {"command": "toggleSearchWidget",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [191]}
- }},
-
- {"command": "decreaseTtsRate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [116]}
- }},
-
- {"command": "increaseTtsRate",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [117]}
- }},
-
- {"command": "decreaseTtsPitch",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [118]}
- }},
-
- {"command": "increaseTtsPitch",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [119]}
- }},
-
- {"command": "decreaseTtsVolume",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [120]}
- }},
-
- {"command": "increaseTtsVolume",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [121]}
- }},
-
- {"command": "cyclePunctuationEcho",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [65, 80]}
- }},
-
- {"command": "cycleTypingEcho",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [65, 84]}
- }},
-
- {"command": "toggleEarcons",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [65, 69]}
- }},
-
- {"command": "showFormsList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [87, 70]}
- }},
-
- {"command": "showHeadingsList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [87, 72]}
- }},
-
- {"command": "showLandmarksList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [87, 186]}
- }},
-
- {"command": "showLinksList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [87, 76]}
- }},
-
- {"command": "showTablesList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [87, 84]}
- }},
-
- {"command": "nextHeading1",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [49]}
- }},
-
- {"command": "nextHeading2",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [50]}
- }},
-
- {"command": "nextHeading3",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [51]}
- }},
-
- {"command": "nextHeading4",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [52]}
- }},
-
- {"command": "nextHeading5",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [53]}
- }},
-
- {"command": "nextHeading6",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [54]}
- }},
-
- {"command": "nextButton",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [66]}
- }},
-
- {"command": "nextComboBox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67]}
- }},
-
- {"command": "nextEditText",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [69]}
- }},
-
- {"command": "nextFormField",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [70]}
- }},
-
- {"command": "nextGraphic",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [71]}
- }},
-
- {"command": "nextHeading",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [72]}
- }},
-
- {"command": "nextListItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [73]}
- }},
-
- {"command": "nextLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76]}
- }},
-
- {"command": "nextList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [85]}
- }},
-
- {"command": "nextMath",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [77]}
- }},
-
- {"command": "nextRadio",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [82]}
- }},
-
- {"command": "nextTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84]}
- }},
-
- {"command": "nextVisitedLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [86]}
- }},
-
- {"command": "nextCheckbox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [88]}
- }},
-
- {"command": "showKbExplorerPage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 75]}
- }},
-
- {"command": "showOptionsPage",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [79, 79]}
- }},
-
- {"command": "previousHeading1",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [49],
- "shiftKey": [true]}
- }},
-
- {"command": "previousHeading2",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [50],
- "shiftKey": [true]}
- }},
-
- {"command": "previousHeading3",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [51],
- "shiftKey": [true]}
- }},
-
- {"command": "previousHeading4",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [52],
- "shiftKey": [true]}
- }},
-
- {"command": "previousHeading5",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [53],
- "shiftKey": [true]}
- }},
-
- {"command": "previousHeading6",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [54],
- "shiftKey": [true]}
- }},
-
- {"command": "previousButton",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [66],
- "shiftKey": [true]}
- }},
-
- {"command": "previousComboBox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [67],
- "shiftKey": [true]}
- }},
-
- {"command": "previousEditText",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [69],
- "shiftKey": [true]}
- }},
-
- {"command": "previousFormField",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [70],
- "shiftKey": [true]}
- }},
-
- {"command": "previousGraphic",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [71],
- "shiftKey": [true]}
- }},
-
- {"command": "previousHeading",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [72],
- "shiftKey": [true]}
- }},
-
- {"command": "previousListItem",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [73],
- "shiftKey": [true]}
- }},
-
- {"command": "previousLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [76],
- "shiftKey": [true]}
- }},
-
- {"command": "previousList",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [85],
- "shiftKey": [true]}
- }},
-
- {"command": "previousMath",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [77],
- "shiftKey": [true]}
- }},
-
- {"command": "previousRadio",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [82],
- "shiftKey": [true]}
- }},
-
- {"command": "previousTable",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [84],
- "shiftKey": [true]}
- }},
-
- {"command": "previousVisitedLink",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [86],
- "shiftKey": [true]}
- }},
-
- {"command": "previousCheckbox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [88],
- "shiftKey": [true]}
- }},
-
- {"command": "announceHeaders",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 72]}
- }},
-
- {"command": "speakTableLocation",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 76]}
- }},
-
- {"command": "nextLandmark",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [186]}
- }},
-
- {"command": "previousLandmark",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [186],
- "shiftKey": [true]}
- }},
-
- {"command": "goToRowFirstCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 186]}
- }},
-
- {"command": "goToColFirstCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 188]}
- }},
-
- {"command": "goToColLastCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 190]}
- }},
-
- {"command": "goToFirstCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 219]}
- }},
-
- {"command": "goToLastCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 221]}
- }},
-
- {"command": "goToRowLastCell",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [222, 222]}
- }},
-
- {"command": "help",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [112]}
- }},
-
- {"command": "readCurrentTitle",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [113]}
- }},
-
- {"command": "readCurrentURL",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [114]}
- }},
-
- {"command": "toggleChromeVox",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [115]}
- }},
-
- {"command": "toggleChromeVoxVersion",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [81, 81]}
- }},
-
- {"command": "enableConsoleTts",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [68, 67]}
- }},
- {"command": "toggleBrailleCaptions",
- "sequence": {"cvoxModifier": true,
- "keys": {"keyCode": [68, 66]}
- }}
- ]
-}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json
deleted file mode 100644
index f049fa2f5b9..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json
+++ /dev/null
@@ -1,1047 +0,0 @@
-{
- "bindings": [
- {
- "command": "previousObject",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37]
- }
- }
- },
- {
- "command": "previousLine",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [38]
- }
- }
- },
- {
- "command": "nextObject",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39]
- }
- }
- },
- {
- "command": "nextLine",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [40]
- }
- }
- },
- {
- "command": "nextCharacter",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousCharacter",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextWord",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39],
- "ctrlKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousWord",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37],
- "ctrlKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextButton",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [66]
- }
- }
- },
- {
- "command": "previousButton",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [66],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextCheckbox",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [88]
- }
- }
- },
- {
- "command": "previousCheckbox",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [88],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextComboBox",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [67]
- }
- }
- },
- {
- "command": "previousComboBox",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [67],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextEditText",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [69]
- }
- }
- },
- {
- "command": "previousEditText",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [69],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextFormField",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [70]
- }
- }
- },
- {
- "command": "previousFormField",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [70],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousGraphic",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [71],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextGraphic",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [71]
- }
- }
- },
- {
- "command": "nextHeading",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [72]
- }
- }
- },
- {
- "command": "nextHeading1",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [49]
- }
- }
- },
- {
- "command": "nextHeading2",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [50]
- }
- }
- },
- {
- "command": "nextHeading3",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [51]
- }
- }
- },
- {
- "command": "nextHeading4",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [52]
- }
- }
- },
- {
- "command": "nextHeading5",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [53]
- }
- }
- },
- {
- "command": "nextHeading6",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [54]
- }
- }
- },
- {
- "command": "previousHeading",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [72],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousHeading1",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [49],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousHeading2",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [50],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousHeading3",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [51],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousHeading4",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [52],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousHeading5",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [53],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousHeading6",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [54],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextLink",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [76]
- }
- }
- },
- {
- "command": "previousLink",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [76],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextTable",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [84]
- }
- }
- },
- {
- "command": "previousTable",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [84],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextVisitedLink",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [86]
- }
- }
- },
- {
- "command": "previousVisitedLink",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [86],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextLandmark",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [186]
- }
- }
- },
- {
- "command": "previousLandmark",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [186],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "jumpToBottom",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "jumpToTop",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "forceClickOnCurrentItem",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [32]
- }
- }
- },
- {
- "command": "contextMenu",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [77]
- }
- }
- },
- {
- "command": "readFromHere",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [82]
- }
- }
- },
- {
- "command": "toggleStickyMode",
- "sequence": {
- "doubleTap": true,
- "keys": {
- "keyCode": [
- 45
- ]
- },
- "platformFilter": 5
- }
- },
- {
- "command": "toggleStickyMode",
- "sequence": {
- "skipStripping": false,
- "doubleTap": true,
- "keys": {
- "keyCode": [
- 91
- ]
- },
- "platformFilter": 10
- }
- },
- {
- "command": "passThroughMode",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [27],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "toggleKeyboardHelp",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [190]
- }
- }
- },
- {
- "command": "stopSpeech",
- "sequence": {
- "cvoxModifier": false,
- "keys": {
- "ctrlKey": [true],
- "keyCode": [17]
- }
- }
- },
- {
- "command": "decreaseTtsRate",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [219],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "increaseTtsRate",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [219]
- }
- }
- },
- {
- "command": "decreaseTtsPitch",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [221],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "increaseTtsPitch",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [221]
- }
- }
- },
- {
- "command": "stopSpeech",
- "sequence": {
- "keys": {
- "ctrlKey": [true],
- "keyCode": [17]
- }
- }
- },
- {
- "command": "cyclePunctuationEcho",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 80]
- }
- }
- },
- {
- "command": "showKbExplorerPage",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 75]
- }
- }
- },
- {
- "command": "cycleTypingEcho",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 84]
- }
- }
- },
- {
- "command": "showOptionsPage",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 79]
- }
- }
- },
- {
- "command": "showLogPage",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 87]
- }
- }
- },
- {
- "command": "enableLogging",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 69]
- }
- }
- },
- {
- "command": "disableLogging",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 68]
- }
- }
- },
- {
- "command": "dumpTree",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [68, 84],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "help",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 84]
- }
- }
- },
- {
- "command": "showNextUpdatePage",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 78]
- }
- }
- },
- {
- "command": "toggleEarcons",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 69]
- }
- }
- },
- {
- "command": "speakTimeAndDate",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 68]}
- }
- },
- {
- "command": "readCurrentTitle",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 87]
- }
- }
- },
- {
- "command": "readCurrentURL",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 85]
- }
- }
- },
- {
- "command": "reportIssue",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 73]
- }
- }
- },
- {
- "command": "toggleSearchWidget",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [191]
- }
- }
- },
- {
- "command": "showHeadingsList",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [72],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "showFormsList",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [70],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "showLandmarksList",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [186],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "showLinksList",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [76],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "showTablesList",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [84],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "toggleBrailleCaptions",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 66]
- }
- }
- },
- {
- "command": "toggleBrailleTable",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 71]
- }
- }
- },
- {
- "command": "viewGraphicAsBraille",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [71],
- "altKey": [true]
- }
- }
- },
- {
- "command": "toggleSelection",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [83]
- }
- }
- },
- {
- "command": "fullyDescribe",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [75]
- }
- }
- },
- {
- "command": "previousRow",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [38],
- "ctrlKey": [true],
- "altKey": [true]
- }
- }
- },
- {
- "command": "nextRow",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [40],
- "ctrlKey": [true],
- "altKey": [true]
- }
- }
- },
- {
- "command": "nextCol",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39],
- "ctrlKey": [true],
- "altKey": [true]
- }
- }
- },
- {
- "command": "previousCol",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37],
- "ctrlKey": [true],
- "altKey": [true]
- }
- }
- },
- {
- "command": "goToRowFirstCell",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37],
- "ctrlKey": [true],
- "altKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "goToColFirstCell",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [38],
- "ctrlKey": [true],
- "altKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "goToColLastCell",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [40],
- "ctrlKey": [true],
- "altKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "goToFirstCell",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [37],
- "altKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "goToLastCell",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39],
- "altKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "goToRowLastCell",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [39],
- "ctrlKey": [true],
- "altKey": [true],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "previousGroup",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [38],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "nextGroup",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [40],
- "ctrlKey": [true]
- }
- }
- },
- {
- "command": "previousSimilarItem",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [73],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "nextSimilarItem",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [73]
- }
- }
- },
- {
- "command": "jumpToDetails",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65,74]
- }
- }
- },
- {
- "command": "darkenScreen",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [118],
- "shiftKey": [true]
- }
- }
- },
- {
- "command": "undarkenScreen",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [118]
- }
- }
- },
- {
- "command": "toggleSpeechOnOrOff",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [119]
- }
- }
- },
- {
- "command": "enableChromeVoxArcSupportForCurrentApp",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 219]
- }
- }
- },
- {
- "command": "disableChromeVoxArcSupportForCurrentApp",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 221]
- }
- }
- },
- {
- "command": "forceClickOnCurrentItem",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [32]
- },
- "doubleTap": true
- }
- },
- {
- "command": "showTtsSettings",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 83]
- }
- }
- },
- {
- "command": "announceBatteryDescription",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [79, 66]
- }
- }
- },
- {
- "command": "announceRichTextDescription",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 70]
- }
- }
- },
- {
- "command": "readPhoneticPronunciation",
- "sequence": {
- "cvoxModifier": true,
- "keys": {
- "keyCode": [65, 67]
- }
- }
- }
- ]
-}
diff --git a/chromium/chrome/browser/resources/chromeos/connectivity_diagnostics/manifest.json b/chromium/chrome/browser/resources/chromeos/connectivity_diagnostics/manifest.json
deleted file mode 100644
index 65355f34f15..00000000000
--- a/chromium/chrome/browser/resources/chromeos/connectivity_diagnostics/manifest.json
+++ /dev/null
@@ -1,53 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGJWLUb83WKoDeODlrPIZu60M8bzbvrkg3Jf/5aO3ux2FL+G+/BO4Vyt/J0t8lXBtnTOvHo6KPpA042PyE9xMdlufFnJqEkvPXNzRlBWeVQqFHbMWE6o/x8diG69dNmHyFYcUjjFFDk2X2GOLQXNUGJQ6MIikbdzoWdLGttmhAIwIDAQAB",
- "manifest_version": 2,
- "incognito" : "split",
- "name": "Connectivity Diagnostics",
- "version": "1.1.0",
- "minimum_chrome_version": "29",
- "offline_enabled": true,
- "default_locale": "en",
- "icons": {
- "128": "img/icon_128.png",
- "16": "img/icon_16.png"
- },
- "permissions" : [
- { "socket" : [
- "tcp-connect:*:443",
- "tcp-connect:*:80",
- "tcp-connect:*:25",
- "tcp-connect:*:19302",
- "tcp-connect:*:19303",
- "tcp-connect:*:19304",
- "tcp-connect:*:19305",
- "tcp-connect:*:19306",
- "tcp-connect:*:19307",
- "tcp-connect:*:19308",
- "tcp-connect:*:19309",
- "udp-bind:*",
- "udp-send-to:*:19302",
- "udp-send-to:*:19303",
- "udp-send-to:*:19304",
- "udp-send-to:*:19305",
- "udp-send-to:*:19306",
- "udp-send-to:*:19307",
- "udp-send-to:*:19308",
- "udp-send-to:*:19309"
- ]},
- "clipboardWrite",
- "experimental",
- "dns",
- "diagnostics",
- "metricsPrivate",
- "networkingPrivate",
- "http://*.google.com/*",
- "https://*.google.com/*"
- ],
- "app": {
- "background": {
- "scripts": ["background.js"]
- }
- },
- "display_in_launcher": false,
- "display_in_new_tab_page": false
-}
diff --git a/chromium/chrome/browser/resources/chromeos/connectivity_diagnostics_launcher/manifest.json b/chromium/chrome/browser/resources/chromeos/connectivity_diagnostics_launcher/manifest.json
deleted file mode 100644
index 04b9d4027de..00000000000
--- a/chromium/chrome/browser/resources/chromeos/connectivity_diagnostics_launcher/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOg+/GcwTYomV06ymtKpD08+M7qjbINqbvPUT/+RFdGm30vVIWivqIrFuiXr+h8d77yTUHcpkRa4LEMulsG2Q44SWfWhBZOVnohsu/OUdHKZ+nw82Q0G+4jjkVOz3/AvUBCBwBaL90Jn05hockJriLAvNUWKLheirw58BVzF9z9wIDAQAB",
- "manifest_version": 2,
- "incognito" : "split",
- "name": "NCDLauncher",
- "version": "1",
- "minimum_chrome_version": "27",
- "offline_enabled": true,
- "incognito": "split",
- "web_accessible_resources": ["index.html"]
-}
-
diff --git a/chromium/chrome/browser/resources/chromeos/crosh_builtin/manifest.json b/chromium/chrome/browser/resources/chromeos/crosh_builtin/manifest.json
deleted file mode 100644
index 759bd77b8ee..00000000000
--- a/chromium/chrome/browser/resources/chromeos/crosh_builtin/manifest.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "key": "AAAAB3NzaC1yc2EAAAADAQABAAAAgQDtKfIKWzC4HnQbyAeddk5h78K7LSyrIEnLKDsQCNxhfsavJ+otV9JprDSz3nF7EHZokXiC72SHxdMndt2IlId/aRfhbU4UGidrmFBKg6v1Fs2zey6niB+xLOhJQMe9XtwVNWDpiGXHLmwNhG/FLhj0bBBf1HZDBV18Xp47ymLiuQ==",
- "name": "Terminal",
- "manifest_version": 2,
- "icons": {
- "192": "images/dev/crostini-192.png"
- },
- "content_security_policy": "script-src 'self' blob: filesystem:; object-src 'self' blob: filesystem:",
- "version": "0.8.36",
- "default_locale": "en",
- "description": "Built-in terminal for crosh.",
- "offline_enabled": true,
- "options_page": "html/nassh_preferences_editor.html",
- "incognito": "split",
- "permissions": [
- "clipboardRead",
- "clipboardWrite",
- "notifications",
- "storage",
- "terminalPrivate",
- "unlimitedStorage",
- "accessibilityFeatures.read"
- ]
-}
diff --git a/chromium/chrome/browser/resources/chromeos/demo_app/manifest.json b/chromium/chrome/browser/resources/chromeos/demo_app/manifest.json
deleted file mode 100644
index 39cec111571..00000000000
--- a/chromium/chrome/browser/resources/chromeos/demo_app/manifest.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- // chrome-extension://klimoghijjogocdbaikffefjfcfheiel
- "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTqJNPnnhAarSbA5aPvPU/h6BxvHO+PWQLVab5RyJUxl1eVnZFx9sIGoXctUgKG+DXpSflYhsvK7opMd1jPqdeSjeSvIDOlsSNX45G+FhAqPQ7OHDdKboQb86TbE53n4qXYamQcx09VIJhi19mPBdqod6myCrTYjPukKiNwAlIEz6eAQL/++uZnIyRzvkht3aMMSfA2PP1qg0ie9aHE/bTwPec9I1kUIe4dPz4NaI5hKxKoDVk2TC9MNNEUx1YznuZNP6Vr21ZRutaOIiptnz1/QJXVr37EMqKvEwNJ/zGFhk1nNju7hYRTbf55CID94yWY14SevnO1nprEGv8+zAwIDAQAB",
- "name": "Demo App",
- "version": "1.0",
- "manifest_version": 2,
- "description": "ChromeOS Demo App",
- "permissions": [
- "chromeosInfoPrivate",
- "fullscreen",
- "idle",
- "power",
- "videoCapture"
- ],
- "default_locale": "en",
- "app": {
- "background": {
- "scripts": ["js/background.js"]
- }
- }
-}
diff --git a/chromium/chrome/browser/resources/chromeos/echo/manifest.json b/chromium/chrome/browser/resources/chromeos/echo/manifest.json
deleted file mode 100644
index 9643ccc02af..00000000000
--- a/chromium/chrome/browser/resources/chromeos/echo/manifest.json
+++ /dev/null
@@ -1,60 +0,0 @@
-{
- // chrome-extension://kddnkjkcjddckihglkfcickdhbmaodcn/
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvO/P4dVdpUZlr+oT5s3ccPIOIxZl6fOch+sqcQWsROKNf+mvKZURGnVts8ttHH4oRt2+LW3I2M5PrkW9Q6ZPkOX5ZQFFuD7ujPSaUn8+Br5lC5TtlXsNzp1r+962Qaa1d8zCocwF/IZ+9M5bt61LUJ6Obe8lw+NRnC6CS6DuiewIDAQAB",
- "name": "Chrome Goodies",
- "version": "1.0.0",
- "description": "Chrome Goodies",
- "manifest_version": 2,
- "content_security_policy": "default-src 'self' blob: filesystem:; connect-src 'self' blob: filesystem: https://chromeos-registration.googleapis.com https://www.google-analytics.com",
- "permissions": [
- "alarms",
- "cookies",
- "chromeosInfoPrivate",
- "echoPrivate",
- "metricsPrivate",
- "notifications",
- "storage",
- "https://*/*"
- ],
- "icons": {
- "16": "chrome16.png",
- "32": "chrome32.png",
- "128": "chrome128.png"
- },
- "default_locale": "en",
- "web_accessible_resources": [
- "broker.html",
- "broker.js",
- "not-eligible.html",
- "not-eligible.js"
- ],
- "background": {
- "scripts": [
- "constants.js",
- "lib/google-analytics-bundle.js",
- "analytics.js",
- "main.js"
- ],
- "persistent": false
- },
- "externally_connectable": {
- "ids": ["*"],
- "matches": [
- "*://www.google.com/*chromebook/*",
- "*://www.google.com.au/*chromebook/*",
- "*://www.google.ca/*chromebook/*",
- "*://www.google.co.jp/*chromebook/*",
- "*://www.google.co.uk/*chromebook/*",
- "*://www.google.de/*chromebook/*",
- "*://www.google.dk/*chromebook/*",
- "*://www.google.fi/*chromebook/*",
- "*://www.google.fr/*chromebook/*",
- "*://www.google.ie/*chromebook/*",
- "*://www.google.nl/*chromebook/*",
- "*://www.google.no/*chromebook/*",
- "*://www.google.co.nz/*chromebook/*",
- "*://www.google.se/*chromebook/*",
- "*://chromebook-dot-googwebreview.appspot.com/*chromebook/*"
- ]
- }
-}
diff --git a/chromium/chrome/browser/resources/chromeos/first_run/app/manifest.json b/chromium/chrome/browser/resources/chromeos/first_run/app/manifest.json
deleted file mode 100644
index b30774838a3..00000000000
--- a/chromium/chrome/browser/resources/chromeos/first_run/app/manifest.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- // chrome-extension://jdgcneonijmofocbhmijhacgchbihela/
- "key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvlKjREBX57nx8lzBezbhiWwYg7mqHinPeP8qTV8d1AqusVAimI1BiZSDTKqFQGFvZAadYhHK3AIXDpEJ4MwHIPv/UIbqEwqPyKlDELNzsxaxrKVHv2BIuvWwabN2W5QLwbIFH/aQEm1+IN2QiNRl6Xjp2aG1RKz15cSif6qWKTMhsHDfF2lx875PJ9t504eacbMfJ8cyZHTZ8VzYRUCZuQANyPDtrGtspe9jwReJZ581UCuZBMpaWARcPkVY7PvFd8SEYysGHJ/NqdsyOyBtaO2wvGTInoDXWay5S0r9BnT6WX9DMiv2QyMF7FmgcO7IQk2j0mrUQbrtc7oc9yySmQIDAQAB",
- "name": "Welcome",
- "version": "0.1",
- "manifest_version": 2,
- "icons": {
- "16": "icon/16.png",
- "32": "icon/32.png",
- "48": "icon/48.png",
- "64": "icon/64.png",
- "96": "icon/96.png",
- "128": "icon/128.png",
- "256": "icon/256.png"
- },
- "app": {
- "background": {
- "scripts": ["background.js"]
- },
- "content_security_policy": "default-src 'none'; script-src 'self' blob: filesystem: chrome://resources; style-src 'self' blob: filesystem: 'unsafe-inline' chrome://resources; img-src 'self' blob: filesystem: chrome://theme chrome://resources"
- },
- "permissions": [
- "accessibilityFeatures.read",
- "firstRunPrivate",
- "chrome://theme/",
- "chrome://resources/"
- ],
- "display_in_launcher": false
-}
diff --git a/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json b/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json
deleted file mode 100644
index c79cf9eda03..00000000000
--- a/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json
+++ /dev/null
@@ -1,70 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNAtiHknM7LHSwE9YKiFFvYvsp0TkfIriWfrjz53odJD0jEbOtyNhVXqdBOdtcenbSraxt7oJ9GlIfUkvBMP5iOpcrJkYfuNJv8ANHTKC79TjGXRg76TjiqpNI+R2NiVav1pXlzYZpu0ToaiaOcr3IgoGsvIUPcC6pd1U5AjdrCQIDAQAB",
- "manifest_version": 2,
- "name": "__MSG_app_name__",
- "description": "__MSG_app_description__",
- "version": "0.4",
- "minimum_chrome_version": "28",
- "default_locale": "en",
- "incognito" : "split",
- "icons": {
- "16": "icon/16.png",
- "32": "icon/32.png",
- "48": "icon/48.png",
- "64": "icon/64.png",
- "96": "icon/96.png",
- "128": "icon/128.png",
- "256": "icon/256.png"
- },
- "app": {
- "background": {
- "persistent": false,
- "page": "background.html"
- }
- },
- "permissions": [
- "identity",
- "identity.email",
- "chromeosInfoPrivate",
- "fileSystem",
- "firstRunPrivate",
- "management",
- "metricsPrivate",
- "webview",
- "storage",
- "unlimitedStorage",
- "https://*.ytimg.com/*",
- "https://www.google.com/*",
- "https://support.google.com/*",
- "https://scone-pa.clients6.google.com/*",
- "https://commondatastorage.googleapis.com/*",
- "https://storage.googleapis.com/*",
- "https://www.google-analytics.com/"
- ],
- "oauth2": {
- "client_id": "929143421683.apps.googleusercontent.com",
- "scopes": [
- "https://www.googleapis.com/auth/supportcontent",
- "https://www.googleapis.com/auth/cases",
- "https://www.googleapis.com/auth/cases.readonly",
- "https://www.googleapis.com/auth/pixelbook.email.preferences",
- "https://www.google.com/accounts/OAuthLogin"
- ]
- },
- "display_in_new_tab_page": false,
- "url_handlers": {
- "view_answer": {
- "matches": [
- "https://support.google.com/chromeos-gethelp/answer/*"
- ],
- "title": "Open Help Article"
- },
- "home": {
- "matches": [
- "https://support.google.com/chromeos-gethelp",
- "https://support.google.com/chromeos-gethelp/"
- ],
- "title": "Open Help"
- }
- }
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json
deleted file mode 100644
index d1dee84090e..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIgqcph7Si8b8iQL1Bfg0L2uXeez55ctSYwZRWBpYu7pBDOxyQvcUotQiUkVrJJRZMp0/0H7ekaGSSkiHMaeOJGQmg3eTmYwJFyyNffEdQTn+41zQBEbfK0Kr1NpGLYDniX8ZI5vzsqSwBdpau9PAow3Njb6afN6v6GmJPoAdEQIDAQAB",
- "name": "Chromium OS IME (Cangjie)",
- "version": "1.0.0.0",
- "description": "Traditional Chinese Cangjie IME for Chromium OS.",
- "default_locale": "en",
- "permissions": [
- "input"
- ],
- "background": {
- "page": "backgroundpage.html"
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/cangjie",
- "input_components": [
- {
- "name": "__MSG_inputmethod_cangjie__",
- "type": "ime",
- "id": "zh-hant-t-i0-cangjie-1987",
- "indicator": "\u5009",
- "description": "Cangjie",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ]
- }
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
deleted file mode 100644
index 5aae7aca66d..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
+++ /dev/null
@@ -1,1945 +0,0 @@
-{
- "name": "Chrome OS XKB",
- "version": "1.0.5.0",
- "description": "Chrome OS XKB",
- "default_locale": "en",
- "incognito": "split",
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7C0oB6YTnf69uhWnVTZl5TB/psHrJXgIPLYchFb0whlVCG8fqMo9lW/oxBmZXZ3N8T7zZrdYI/SUjoc9I5R/dMVVD2q4iKox+x7xlTbqSdVeOb6b9ZVJ24pLbO1L7feSNSBgR0t61jrC2eY/gf78h7w58UEQBPFT2mUxhhwodyQIDAQAB",
- "permissions": [
- "accessibilityFeatures.read",
- "app.window.alpha",
- "app.window.alwaysOnTop",
- "app.window.ime",
- "audioCapture",
- "https://clients4.google.com/",
- "https://dl.google.com/",
- "https://www.googleapis.com/",
- "input",
- "inputMethodPrivate",
- "metricsPrivate",
- "system.display",
- "tabs",
- "tts",
- "unlimitedStorage",
- "virtualKeyboardPrivate"
- ],
- "background": {
- "page": "background.html",
- "persistent": false
- },
- "content_scripts": [
- {
- "matches": [
- "https://www.googleapis.com/auth/imesync*"
- ],
- "js": [
- "chos_inject-debug.js"
- ]
- }
- ],
- "ime_path": "/usr/share/chromeos-assets/input_methods/input_tools",
- "input_components": [
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::eng",
- "description": "",
- "language": [
- "en",
- "en-US",
- "en-AU",
- "en-NZ"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us.compact.qwerty&language=en-US&passwordLayout=us.compact.qwerty&name=keyboard_us",
- "options_page": "hmm_options.html?code=xkb:us::eng"
- },
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::ind",
- "description": "",
- "language": [
- "id"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us-ltr&language=id&passwordLayout=us-ltr&name=keyboard_us",
- "options_page": "hmm_options.html?code=xkb:us::ind"
- },
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::fil",
- "description": "",
- "language": [
- "fil"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us-ltr&language=fil&passwordLayout=us-ltr&name=keyboard_us",
- "options_page": "hmm_options.html?code=xkb:us::fil"
- },
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::msa",
- "description": "",
- "language": [
- "ms"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us-ltr&language=ms&passwordLayout=us-ltr&name=keyboard_us",
- "options_page": "hmm_options.html?code=xkb:us::msa"
- },
- {
- "name": "__MSG_keyboard_us_international__",
- "type": "ime",
- "id": "xkb:us:intl:eng",
- "indicator": "INTL",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(intl)"
- ],
- "input_view": "inputview.html#id=us-intl&language=en-US&passwordLayout=us-intl&name=keyboard_us_international",
- "options_page": "hmm_options.html?code=xkb:us:intl:eng"
- },
- {
- "name": "__MSG_keyboard_us_international_pc__",
- "type": "ime",
- "id": "xkb:us:intl_pc:eng",
- "indicator": "INTL",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(intl_pc)"
- ],
- "input_view": "inputview.html#id=us-intl&language=en-US&passwordLayout=us-intl&name=keyboard_us_international_pc",
- "options_page": "hmm_options.html?code=xkb:us:intl_pc:eng"
- },
- {
- "name": "__MSG_keyboard_netherlands__",
- "type": "ime",
- "id": "xkb:us:intl:nld",
- "indicator": "NLD",
- "description": "",
- "language": [
- "nl"
- ],
- "layouts": [
- "us(intl)"
- ],
- "input_view": "inputview.html#id=nl.compact.qwerty&language=nl&passwordLayout=nl.compact.qwerty&name=keyboard_netherlands",
- "options_page": "hmm_options.html?code=xkb:us:intl:nld"
- },
- {
- "name": "__MSG_keyboard_us_international_pc__",
- "type": "ime",
- "id": "xkb:us:intl_pc:nld",
- "indicator": "NLD",
- "description": "",
- "language": [
- "nl"
- ],
- "layouts": [
- "us(intl_pc)"
- ],
- "input_view": "inputview.html#id=us-intl&language=nl&passwordLayout=us-intl&name=keyboard_us_international_pc",
- "options_page": "hmm_options.html?code=xkb:us:intl_pc:nld"
- },
- {
- "name": "__MSG_keyboard_us_extended__",
- "type": "ime",
- "id": "xkb:us:altgr-intl:eng",
- "indicator": "EXTD",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(altgr-intl)"
- ],
- "input_view": "inputview.html#id=us-altgr-intl&language=en-US&passwordLayout=us-altgr-intl&name=keyboard_us_extended",
- "options_page": "hmm_options.html?code=xkb:us:altgr-intl:eng"
- },
- {
- "name": "__MSG_keyboard_us_dvorak__",
- "type": "ime",
- "id": "xkb:us:dvorak:eng",
- "indicator": "DV",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(dvorak)"
- ],
- "input_view": "inputview.html#id=us-dvorak&language=en-US&passwordLayout=us-dvorak&name=keyboard_us_dvorak",
- "options_page": "hmm_options.html?code=xkb:us:dvorak:eng"
- },
- {
- "name": "__MSG_keyboard_us_dvp__",
- "type": "ime",
- "id": "xkb:us:dvp:eng",
- "indicator": "DVP",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(dvp)"
- ],
- "input_view": "inputview.html#id=us-dvp&language=en-US&passwordLayout=us-dvp&name=keyboard_us_dvp",
- "options_page": "hmm_options.html?code=xkb:us:dvp:eng"
- },
- {
- "name": "__MSG_keyboard_us_colemak__",
- "type": "ime",
- "id": "xkb:us:colemak:eng",
- "indicator": "CO",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(colemak)"
- ],
- "input_view": "inputview.html#id=us-colemak&language=en-US&passwordLayout=us-colemak&name=keyboard_us_colemak",
- "options_page": "hmm_options.html?code=xkb:us:colemak:eng"
- },
- {
- "name": "__MSG_keyboard_us_workman__",
- "type": "ime",
- "id": "xkb:us:workman:eng",
- "indicator": "WM",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(workman)"
- ],
- "input_view": "inputview.html#id=us-workman&language=en-US&passwordLayout=us-workman&name=keyboard_us_workman",
- "options_page": "hmm_options.html?code=xkb:us:workman:eng"
- },
- {
- "name": "__MSG_keyboard_us_workman_international__",
- "type": "ime",
- "id": "xkb:us:workman-intl:eng",
- "indicator": "WMI",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(workman-intl)"
- ],
- "input_view": "inputview.html#id=us-workman-intl&language=en-US&passwordLayout=us-workman-intl&name=keyboard_us_workman_international",
- "options_page": "hmm_options.html?code=xkb:us:workman-intl:eng"
- },
- {
- "name": "__MSG_keyboard_belgian__",
- "type": "ime",
- "id": "xkb:be::nld",
- "description": "",
- "language": [
- "nl"
- ],
- "layouts": [
- "be"
- ],
- "input_view": "inputview.html#id=be&language=nl&passwordLayout=be&name=keyboard_belgian",
- "options_page": "hmm_options.html?code=xkb:be::nld"
- },
- {
- "name": "__MSG_keyboard_french__",
- "type": "ime",
- "id": "xkb:fr::fra",
- "description": "",
- "language": [
- "fr",
- "fr-FR"
- ],
- "layouts": [
- "fr(oss)"
- ],
- "input_view": "inputview.html#id=fr.compact.qwerty&language=fr&passwordLayout=fr.compact.qwerty&name=keyboard_french",
- "options_page": "hmm_options.html?code=xkb:fr::fra"
- },
- {
- "name": "__MSG_keyboard_french_bepo__",
- "type": "ime",
- "id": "xkb:fr:bepo:fra",
- "description": "",
- "language": [
- "fr",
- "fr-FR"
- ],
- "layouts": [
- "fr(bepo)"
- ],
- "input_view": "inputview.html#id=fr-bepo&language=fr&passwordLayout=fr-bepo&name=keyboard_french_bepo",
- "options_page": "hmm_options.html?code=xkb:fr:bepo:fra"
- },
- {
- "name": "__MSG_keyboard_belgian__",
- "type": "ime",
- "id": "xkb:be::fra",
- "description": "",
- "language": [
- "fr"
- ],
- "layouts": [
- "be"
- ],
- "input_view": "inputview.html#id=be&language=fr&passwordLayout=be&name=keyboard_belgian",
- "options_page": "hmm_options.html?code=xkb:be::fra"
- },
- {
- "name": "__MSG_keyboard_faroese__",
- "type": "ime",
- "id": "xkb:fo::fao",
- "description": "",
- "language": [
- "fo"
- ],
- "layouts": [
- "fo"
- ],
- "input_view": "inputview.html#id=fo&language=fo&passwordLayout=fo&name=keyboard_faroese",
- "options_page": "hmm_options.html?code=xkb:fo::fao"
- },
- {
- "name": "__MSG_keyboard_canadian_french__",
- "type": "ime",
- "id": "xkb:ca::fra",
- "description": "",
- "language": [
- "fr",
- "fr-CA"
- ],
- "layouts": [
- "ca"
- ],
- "input_view": "inputview.html#id=ca.compact.qwerty&language=fr&passwordLayout=ca.compact.qwerty&name=keyboard_canadian_french",
- "options_page": "hmm_options.html?code=xkb:ca::fra"
- },
- {
- "name": "__MSG_keyboard_swiss_french__",
- "type": "ime",
- "id": "xkb:ch:fr:fra",
- "description": "",
- "language": [
- "fr",
- "fr-CH"
- ],
- "layouts": [
- "ch(fr)"
- ],
- "input_view": "inputview.html#id=ch-fr&language=fr&passwordLayout=ch-fr&name=keyboard_swiss_french",
- "options_page": "hmm_options.html?code=xkb:ch:fr:fra"
- },
- {
- "name": "__MSG_keyboard_canadian_multilingual__",
- "type": "ime",
- "id": "xkb:ca:multix:fra",
- "description": "",
- "language": [
- "fr",
- "fr-CA"
- ],
- "layouts": [
- "ca(multix)"
- ],
- "input_view": "inputview.html#id=ca-multix&language=fr&passwordLayout=ca-multix&name=keyboard_canadian_multilingual",
- "options_page": "hmm_options.html?code=xkb:ca:multix:fra"
- },
- {
- "name": "__MSG_keyboard_german__",
- "type": "ime",
- "id": "xkb:de::ger",
- "description": "",
- "language": [
- "de",
- "de-AT",
- "de-DE",
- "de-LI"
- ],
- "layouts": [
- "de"
- ],
- "input_view": "inputview.html#id=de.compact.qwerty&language=de&passwordLayout=de.compact.qwerty&name=keyboard_german",
- "options_page": "hmm_options.html?code=xkb:de::ger"
- },
- {
- "name": "__MSG_keyboard_german_neo_2__",
- "type": "ime",
- "id": "xkb:de:neo:ger",
- "indicator": "NEO",
- "description": "",
- "language": [
- "de",
- "de-AT",
- "de-DE",
- "de-LI"
- ],
- "layouts": [
- "de(neo)"
- ],
- "input_view": "inputview.html#id=de-neo&language=de&passwordLayout=de-neo&name=keyboard_german_neo_2",
- "options_page": "hmm_options.html?code=xkb:de:neo:ger"
- },
- {
- "name": "__MSG_keyboard_belgian__",
- "type": "ime",
- "id": "xkb:be::ger",
- "description": "",
- "language": [
- "de"
- ],
- "layouts": [
- "be"
- ],
- "input_view": "inputview.html#id=be&language=de&passwordLayout=be&name=keyboard_belgian",
- "options_page": "hmm_options.html?code=xkb:be::ger"
- },
- {
- "name": "__MSG_keyboard_swiss__",
- "type": "ime",
- "id": "xkb:ch::ger",
- "description": "",
- "language": [
- "de",
- "de-CH"
- ],
- "layouts": [
- "ch"
- ],
- "input_view": "inputview.html#id=ch&language=de&passwordLayout=ch&name=keyboard_swiss",
- "options_page": "hmm_options.html?code=xkb:ch::ger"
- },
- {
- "name": "__MSG_keyboard_japanese__",
- "type": "ime",
- "id": "xkb:jp::jpn",
- "indicator": "JA",
- "description": "",
- "language": [
- "ja"
- ],
- "layouts": [
- "jp"
- ],
- "input_view": "inputview.html#id=jp&language=ja&passwordLayout=jp&name=keyboard_japanese",
- "options_page": "hmm_options.html?code=xkb:jp::jpn"
- },
- {
- "name": "__MSG_keyboard_russian__",
- "type": "ime",
- "id": "xkb:ru::rus",
- "description": "",
- "language": [
- "ru"
- ],
- "layouts": [
- "ru"
- ],
- "input_view": "inputview.html#id=ru&language=ru&passwordLayout=us-ltr&name=keyboard_russian",
- "options_page": "hmm_options.html?code=xkb:ru::rus"
- },
- {
- "name": "__MSG_keyboard_russian_phonetic__",
- "type": "ime",
- "id": "xkb:ru:phonetic:rus",
- "description": "",
- "language": [
- "ru"
- ],
- "layouts": [
- "ru(phonetic)"
- ],
- "input_view": "inputview.html#id=ru-phonetic&language=ru&passwordLayout=us-ltr&name=keyboard_russian_phonetic",
- "options_page": "hmm_options.html?code=xkb:ru:phonetic:rus"
- },
- {
- "name": "__MSG_keyboard_brazilian__",
- "type": "ime",
- "id": "xkb:br::por",
- "description": "",
- "language": [
- "pt-BR",
- "pt"
- ],
- "layouts": [
- "br"
- ],
- "input_view": "inputview.html#id=br&language=pt-BR&passwordLayout=br&name=keyboard_brazilian",
- "options_page": "hmm_options.html?code=xkb:br::por"
- },
- {
- "name": "__MSG_keyboard_us_international__",
- "type": "ime",
- "id": "xkb:us:intl:por",
- "indicator": "INTL",
- "description": "",
- "language": [
- "pt-BR"
- ],
- "layouts": [
- "us(intl)"
- ],
- "input_view": "inputview.html#id=us-intl&language=pt-BR&passwordLayout=us-intl&name=keyboard_us_international",
- "options_page": "hmm_options.html?code=xkb:us:intl:por"
- },
- {
- "name": "__MSG_keyboard_us_international_pc__",
- "type": "ime",
- "id": "xkb:us:intl_pc:por",
- "indicator": "INTL",
- "description": "",
- "language": [
- "pt-BR"
- ],
- "layouts": [
- "us(intl_pc)"
- ],
- "input_view": "inputview.html#id=us-intl&language=pt-BR&passwordLayout=us-intl&name=keyboard_us_international_pc",
- "options_page": "hmm_options.html?code=xkb:us:intl_pc:por"
- },
- {
- "name": "__MSG_keyboard_bulgarian__",
- "type": "ime",
- "id": "xkb:bg::bul",
- "description": "",
- "language": [
- "bg"
- ],
- "layouts": [
- "bg"
- ],
- "input_view": "inputview.html#id=bg&language=bg&passwordLayout=us-ltr&name=keyboard_bulgarian",
- "options_page": "hmm_options.html?code=xkb:bg::bul"
- },
- {
- "name": "__MSG_keyboard_bulgarian_phonetic__",
- "type": "ime",
- "id": "xkb:bg:phonetic:bul",
- "description": "",
- "language": [
- "bg"
- ],
- "layouts": [
- "bg(phonetic)"
- ],
- "input_view": "inputview.html#id=bg-phonetic&language=bg&passwordLayout=us-ltr&name=keyboard_bulgarian_phonetic",
- "options_page": "hmm_options.html?code=xkb:bg:phonetic:bul"
- },
- {
- "name": "__MSG_keyboard_canadian_english__",
- "type": "ime",
- "id": "xkb:ca:eng:eng",
- "description": "",
- "language": [
- "en",
- "en-CA"
- ],
- "layouts": [
- "ca(eng)"
- ],
- "input_view": "inputview.html#id=ca-eng.compact.qwerty&language=en-CA&passwordLayout=ca-eng.compact.qwerty&name=keyboard_canadian_english",
- "options_page": "hmm_options.html?code=xkb:ca:eng:eng"
- },
- {
- "name": "__MSG_keyboard_czech__",
- "type": "ime",
- "id": "xkb:cz::cze",
- "description": "",
- "language": [
- "cs"
- ],
- "layouts": [
- "cz"
- ],
- "input_view": "inputview.html#id=cz&language=cs&passwordLayout=cz&name=keyboard_czech",
- "options_page": "hmm_options.html?code=xkb:cz::cze"
- },
- {
- "name": "__MSG_keyboard_czech_qwerty__",
- "type": "ime",
- "id": "xkb:cz:qwerty:cze",
- "indicator": "CS",
- "description": "",
- "language": [
- "cs"
- ],
- "layouts": [
- "cz(qwerty)"
- ],
- "input_view": "inputview.html#id=cz-qwerty&language=cs&passwordLayout=cz-qwerty&name=keyboard_czech_qwerty",
- "options_page": "hmm_options.html?code=xkb:cz:qwerty:cze"
- },
- {
- "name": "__MSG_keyboard_estonian__",
- "type": "ime",
- "id": "xkb:ee::est",
- "description": "",
- "language": [
- "et"
- ],
- "layouts": [
- "ee"
- ],
- "input_view": "inputview.html#id=ee&language=et&passwordLayout=ee&name=keyboard_estonian",
- "options_page": "hmm_options.html?code=xkb:ee::est"
- },
- {
- "name": "__MSG_keyboard_spanish__",
- "type": "ime",
- "id": "xkb:es::spa",
- "description": "",
- "language": [
- "es",
- "es-ES"
- ],
- "layouts": [
- "es"
- ],
- "input_view": "inputview.html#id=es&language=es&passwordLayout=es&name=keyboard_spanish",
- "options_page": "hmm_options.html?code=xkb:es::spa"
- },
- {
- "name": "__MSG_keyboard_catalan__",
- "type": "ime",
- "id": "xkb:es:cat:cat",
- "indicator": "CAT",
- "description": "",
- "language": [
- "ca"
- ],
- "layouts": [
- "es(cat)"
- ],
- "input_view": "inputview.html#id=es-cat&language=ca&passwordLayout=es-cat&name=keyboard_catalan",
- "options_page": "hmm_options.html?code=xkb:es:cat:cat"
- },
- {
- "name": "__MSG_keyboard_danish__",
- "type": "ime",
- "id": "xkb:dk::dan",
- "description": "",
- "language": [
- "da"
- ],
- "layouts": [
- "dk"
- ],
- "input_view": "inputview.html#id=dk.compact.qwerty&language=da&passwordLayout=dk.compact.qwerty&name=keyboard_danish",
- "options_page": "hmm_options.html?code=xkb:dk::dan"
- },
- {
- "name": "__MSG_keyboard_greek__",
- "type": "ime",
- "id": "xkb:gr::gre",
- "description": "",
- "language": [
- "el"
- ],
- "layouts": [
- "gr"
- ],
- "input_view": "inputview.html#id=gr&language=el&passwordLayout=us-ltr&name=keyboard_greek",
- "options_page": "hmm_options.html?code=xkb:gr::gre"
- },
- {
- "name": "__MSG_keyboard_hebrew__",
- "type": "ime",
- "id": "xkb:il::heb",
- "description": "",
- "language": [
- "he"
- ],
- "layouts": [
- "il"
- ],
- "input_view": "inputview.html#id=il&language=he&passwordLayout=us-rtl&name=keyboard_hebrew",
- "options_page": "hmm_options.html?code=xkb:il::heb"
- },
- {
- "name": "__MSG_keyboard_latin_american__",
- "type": "ime",
- "id": "xkb:latam::spa",
- "indicator": "LA",
- "description": "",
- "language": [
- "es",
- "es-419"
- ],
- "layouts": [
- "latam"
- ],
- "input_view": "inputview.html#id=latam&language=es&passwordLayout=latam&name=keyboard_latin_american",
- "options_page": "hmm_options.html?code=xkb:latam::spa"
- },
- {
- "name": "__MSG_keyboard_lithuanian__",
- "type": "ime",
- "id": "xkb:lt::lit",
- "description": "",
- "language": [
- "lt"
- ],
- "layouts": [
- "lt"
- ],
- "input_view": "inputview.html#id=lt&language=lt&passwordLayout=lt&name=keyboard_lithuanian",
- "options_page": "hmm_options.html?code=xkb:lt::lit"
- },
- {
- "name": "__MSG_keyboard_latvian__",
- "type": "ime",
- "id": "xkb:lv:apostrophe:lav",
- "description": "",
- "language": [
- "lv"
- ],
- "layouts": [
- "lv(apostrophe)"
- ],
- "input_view": "inputview.html#id=lv-apostrophe&language=lv&passwordLayout=lv-apostrophe&name=keyboard_latvian",
- "options_page": "hmm_options.html?code=xkb:lv:apostrophe:lav"
- },
- {
- "name": "__MSG_keyboard_croatian__",
- "type": "ime",
- "id": "xkb:hr::scr",
- "description": "",
- "language": [
- "hr"
- ],
- "layouts": [
- "hr"
- ],
- "input_view": "inputview.html#id=hr&language=hr&passwordLayout=hr&name=keyboard_croatian",
- "options_page": "hmm_options.html?code=xkb:hr::scr"
- },
- {
- "name": "__MSG_keyboard_uk__",
- "type": "ime",
- "id": "xkb:gb:extd:eng",
- "description": "",
- "language": [
- "en",
- "en-GB"
- ],
- "layouts": [
- "gb(extd)"
- ],
- "input_view": "inputview.html#id=gb-extd.compact.qwerty&language=en-GB&passwordLayout=gb-extd.compact.qwerty&name=keyboard_uk",
- "options_page": "hmm_options.html?code=xkb:gb:extd:eng"
- },
- {
- "name": "__MSG_keyboard_uk_dvorak__",
- "type": "ime",
- "id": "xkb:gb:dvorak:eng",
- "indicator": "DV",
- "description": "",
- "language": [
- "en",
- "en-GB"
- ],
- "layouts": [
- "gb(dvorak)"
- ],
- "input_view": "inputview.html#id=gb-dvorak&language=en-GB&passwordLayout=gb-dvorak&name=keyboard_uk_dvorak",
- "options_page": "hmm_options.html?code=xkb:gb:dvorak:eng"
- },
- {
- "name": "__MSG_keyboard_finnish__",
- "type": "ime",
- "id": "xkb:fi::fin",
- "description": "",
- "language": [
- "fi"
- ],
- "layouts": [
- "fi"
- ],
- "input_view": "inputview.html#id=fi.compact.qwerty&language=fi&passwordLayout=fi.compact.qwerty&name=keyboard_finnish",
- "options_page": "hmm_options.html?code=xkb:fi::fin"
- },
- {
- "name": "__MSG_keyboard_hungarian__",
- "type": "ime",
- "id": "xkb:hu::hun",
- "description": "",
- "language": [
- "hu"
- ],
- "layouts": [
- "hu"
- ],
- "input_view": "inputview.html#id=hu&language=hu&passwordLayout=hu&name=keyboard_hungarian",
- "options_page": "hmm_options.html?code=xkb:hu::hun"
- },
- {
- "name": "__MSG_keyboard_hungarian_qwerty__",
- "type": "ime",
- "id": "xkb:hu:qwerty:hun",
- "description": "",
- "language": [
- "hu"
- ],
- "layouts": [
- "hu(qwerty)"
- ],
- "input_view": "inputview.html#id=hu-qwerty&language=hu&passwordLayout=hu-qwerty&name=keyboard_hungarian_qwerty",
- "options_page": "hmm_options.html?code=xkb:hu:qwerty:hun"
- },
- {
- "name": "__MSG_keyboard_italian__",
- "type": "ime",
- "id": "xkb:it::ita",
- "description": "",
- "language": [
- "it",
- "it-IT"
- ],
- "layouts": [
- "it"
- ],
- "input_view": "inputview.html#id=it&language=it&passwordLayout=it&name=keyboard_italian",
- "options_page": "hmm_options.html?code=xkb:it::ita"
- },
- {
- "name": "__MSG_keyboard_icelandic__",
- "type": "ime",
- "id": "xkb:is::ice",
- "description": "",
- "language": [
- "is"
- ],
- "layouts": [
- "is"
- ],
- "input_view": "inputview.html#id=is.compact.qwerty&language=is&passwordLayout=is.compact.qwerty&name=keyboard_icelandic",
- "options_page": "hmm_options.html?code=xkb:is::ice"
- },
- {
- "name": "__MSG_keyboard_norwegian__",
- "type": "ime",
- "id": "xkb:no::nob",
- "description": "",
- "language": [
- "nb",
- "nn",
- "no"
- ],
- "layouts": [
- "no"
- ],
- "input_view": "inputview.html#id=no.compact.qwerty&language=no&passwordLayout=no.compact.qwerty&name=keyboard_norwegian",
- "options_page": "hmm_options.html?code=xkb:no::nob"
- },
- {
- "name": "__MSG_keyboard_polish__",
- "type": "ime",
- "id": "xkb:pl::pol",
- "description": "",
- "language": [
- "pl"
- ],
- "layouts": [
- "pl"
- ],
- "input_view": "inputview.html#id=pl&language=pl&passwordLayout=pl&name=keyboard_polish",
- "options_page": "hmm_options.html?code=xkb:pl::pol"
- },
- {
- "name": "__MSG_keyboard_portuguese__",
- "type": "ime",
- "id": "xkb:pt::por",
- "description": "",
- "language": [
- "pt-PT",
- "pt"
- ],
- "layouts": [
- "pt"
- ],
- "input_view": "inputview.html#id=pt&language=pt-PT&passwordLayout=pt&name=keyboard_portuguese",
- "options_page": "hmm_options.html?code=xkb:pt::por"
- },
- {
- "name": "__MSG_keyboard_romanian__",
- "type": "ime",
- "id": "xkb:ro::rum",
- "description": "",
- "language": [
- "ro"
- ],
- "layouts": [
- "ro"
- ],
- "input_view": "inputview.html#id=ro&language=ro&passwordLayout=ro&name=keyboard_romanian",
- "options_page": "hmm_options.html?code=xkb:ro::rum"
- },
- {
- "name": "__MSG_keyboard_romanian_standard__",
- "type": "ime",
- "id": "xkb:ro:std:rum",
- "description": "",
- "language": [
- "ro"
- ],
- "layouts": [
- "ro(std)"
- ],
- "input_view": "inputview.html#id=ro-std&language=ro&passwordLayout=ro-std&name=keyboard_romanian_standard",
- "options_page": "hmm_options.html?code=xkb:ro:std:rum"
- },
- {
- "name": "__MSG_keyboard_swedish__",
- "type": "ime",
- "id": "xkb:se::swe",
- "description": "",
- "language": [
- "sv"
- ],
- "layouts": [
- "se"
- ],
- "input_view": "inputview.html#id=se.compact.qwerty&language=sv&passwordLayout=se.compact.qwerty&name=keyboard_swedish",
- "options_page": "hmm_options.html?code=xkb:se::swe"
- },
- {
- "name": "__MSG_keyboard_slovak__",
- "type": "ime",
- "id": "xkb:sk::slo",
- "description": "",
- "language": [
- "sk"
- ],
- "layouts": [
- "sk"
- ],
- "input_view": "inputview.html#id=sk&language=sk&passwordLayout=us-ltr&name=keyboard_slovakian",
- "options_page": "hmm_options.html?code=xkb:sk::slo"
- },
- {
- "name": "__MSG_keyboard_slovenian__",
- "type": "ime",
- "id": "xkb:si::slv",
- "description": "",
- "language": [
- "sl"
- ],
- "layouts": [
- "si"
- ],
- "input_view": "inputview.html#id=si&language=sl&passwordLayout=si&name=keyboard_slovenian",
- "options_page": "hmm_options.html?code=xkb:si::slv"
- },
- {
- "name": "__MSG_keyboard_serbian__",
- "type": "ime",
- "id": "xkb:rs::srp",
- "description": "",
- "language": [
- "sr"
- ],
- "layouts": [
- "rs"
- ],
- "input_view": "inputview.html#id=rs&language=sr&passwordLayout=us-ltr&name=keyboard_serbian",
- "options_page": "hmm_options.html?code=xkb:rs::srp"
- },
- {
- "name": "__MSG_keyboard_turkish__",
- "type": "ime",
- "id": "xkb:tr::tur",
- "description": "",
- "language": [
- "tr"
- ],
- "layouts": [
- "tr"
- ],
- "input_view": "inputview.html#id=tr&language=tr&passwordLayout=tr&name=keyboard_turkish",
- "options_page": "hmm_options.html?code=xkb:tr::tur"
- },
- {
- "name": "__MSG_keyboard_turkish_f__",
- "type": "ime",
- "id": "xkb:tr:f:tur",
- "description": "",
- "language": [
- "tr"
- ],
- "layouts": [
- "tr(f)"
- ],
- "input_view": "inputview.html#id=tr-f&language=tr&passwordLayout=tr-f&name=keyboard_turkish_f",
- "options_page": "hmm_options.html?code=xkb:tr:f:tur"
- },
- {
- "name": "__MSG_keyboard_ukrainian__",
- "type": "ime",
- "id": "xkb:ua::ukr",
- "description": "",
- "language": [
- "uk"
- ],
- "layouts": [
- "ua"
- ],
- "input_view": "inputview.html#id=ua&language=uk&passwordLayout=us-ltr&name=keyboard_ukrainian",
- "options_page": "hmm_options.html?code=xkb:ua::ukr"
- },
- {
- "name": "__MSG_keyboard_belarusian__",
- "type": "ime",
- "id": "xkb:by::bel",
- "description": "",
- "language": [
- "be"
- ],
- "layouts": [
- "by"
- ],
- "input_view": "inputview.html#id=by&language=be&passwordLayout=us-ltr&name=keyboard_belarusian",
- "options_page": "hmm_options.html?code=xkb:by::bel"
- },
- {
- "name": "__MSG_keyboard_armenian_phonetic__",
- "type": "ime",
- "id": "xkb:am:phonetic:arm",
- "description": "",
- "language": [
- "hy"
- ],
- "layouts": [
- "am(phonetic)"
- ],
- "input_view": "inputview.html#id=am-phonetic&language=hy&passwordLayout=us-ltr&name=keyboard_armenian_phonetic",
- "options_page": "hmm_options.html?code=xkb:am:phonetic:arm"
- },
- {
- "name": "__MSG_keyboard_georgian__",
- "type": "ime",
- "id": "xkb:ge::geo",
- "description": "",
- "language": [
- "ka"
- ],
- "layouts": [
- "ge"
- ],
- "input_view": "inputview.html#id=ge&language=ka&passwordLayout=us-ltr&name=keyboard_georgian",
- "options_page": "hmm_options.html?code=xkb:ge::geo"
- },
- {
- "name": "__MSG_keyboard_mongolian__",
- "type": "ime",
- "id": "xkb:mn::mon",
- "description": "",
- "language": [
- "mn"
- ],
- "layouts": [
- "mn"
- ],
- "input_view": "inputview.html#id=mn&language=mn&passwordLayout=us-ltr&name=keyboard_mongolian",
- "options_page": "hmm_options.html?code=xkb:mn::mon"
- },
- {
- "name": "__MSG_keyboard_irish__",
- "type": "ime",
- "id": "xkb:ie::ga",
- "description": "",
- "language": [
- "ga"
- ],
- "layouts": [
- "ie"
- ],
- "input_view": "inputview.html#id=ie.compact.qwerty&language=ga&passwordLayout=ie.compact.qwerty&name=keyboard_irish",
- "options_page": "hmm_options.html?code=xkb:ie::ga"
- },
- {
- "name": "__MSG_keyboard_maltese__",
- "type": "ime",
- "id": "xkb:mt::mlt",
- "description": "",
- "language": [
- "mt"
- ],
- "layouts": [
- "mt"
- ],
- "input_view": "inputview.html#id=mt&language=mt&passwordLayout=mt&name=keyboard_maltese",
- "options_page": "hmm_options.html?code=xkb:mt::mlt"
- },
- {
- "name": "__MSG_keyboard_macedonian__",
- "type": "ime",
- "id": "xkb:mk::mkd",
- "description": "",
- "language": [
- "mk"
- ],
- "layouts": [
- "mk"
- ],
- "input_view": "inputview.html#id=mk&language=mk&passwordLayout=us-ltr&name=keyboard_macedonian",
- "options_page": "hmm_options.html?code=xkb:mk::mkd"
- },
- {
- "name": "__MSG_keyboard_kazakh__",
- "type": "ime",
- "id": "xkb:kz::kaz",
- "description": "",
- "language": [
- "kk"
- ],
- "layouts": [
- "kz"
- ],
- "input_view": "inputview.html#id=kz&language=kk&passwordLayout=us-ltr&name=keyboard_kazakh",
- "options_page": "hmm_options.html?code=xkb:kz::kaz"
- },
- {
- "name": "__MSG_inputmethod_pinyin__",
- "type": "ime",
- "id": "zh-t-i0-pinyin",
- "indicator": "\u62fc",
- "description": "Pinyin",
- "language": [
- "zh-CN",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=pinyin-zh-CN.compact.qwerty&language=zh-CN&passwordLayout=pinyin-zh-CN.en.compact.qwerty&name=inputmethod_pinyin",
- "options_page": "hmm_options.html?code=zh-t-i0-pinyin"
- },
- {
- "name": "__MSG_inputmethod_traditional_pinyin__",
- "type": "ime",
- "id": "zh-hant-t-i0-pinyin",
- "indicator": "\u62fc",
- "description": "Pinyin for Tranditional Chinese",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=pinyin-zh-TW&language=zh-TW&passwordLayout=pinyin-zh-TW&name=inputmethod_traditional_pinyin",
- "options_page": "hmm_options.html?code=zh-hant-t-i0-pinyin"
- },
- {
- "name": "__MSG_inputmethod_cangjie__",
- "type": "ime",
- "id": "zh-hant-t-i0-cangjie-1987",
- "indicator": "\u5009",
- "description": "Cangjie",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=cangjie&language=zh-TW&passwordLayout=cangjie&name=inputmethod_cangjie"
- },
- {
- "name": "__MSG_inputmethod_quick__",
- "type": "ime",
- "id": "zh-hant-t-i0-cangjie-1987-x-m0-simplified",
- "indicator": "\u901f",
- "description": "Quick",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=quick&language=zh-TW&passwordLayout=quick&name=inputmethod_quick"
- },
- {
- "name": "__MSG_inputmethod_cantonese__",
- "type": "ime",
- "id": "yue-hant-t-i0-und",
- "indicator": "\u7CA4",
- "description": "Cantonese",
- "language": [
- "zh-TW"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us&language=zh-CN&passwordLayout=us&name=inputmethod_cantonese"
- },
- {
- "name": "__MSG_inputmethod_wubi__",
- "type": "ime",
- "id": "zh-t-i0-wubi-1986",
- "indicator": "\u4e94",
- "description": "Wubi",
- "language": [
- "zh-CN",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=wubi&language=zh-CN&passwordLayout=wubi&name=inputmethod_wubi"
- },
- {
- "name": "__MSG_inputmethod_array__",
- "type": "ime",
- "id": "zh-hant-t-i0-array-1992",
- "indicator": "\u884c\u5217",
- "description": "Array",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=array&language=zh-TW&passwordLayout=array&name=inputmethod_array"
- },
- {
- "name": "__MSG_inputmethod_dayi__",
- "type": "ime",
- "id": "zh-hant-t-i0-dayi-1988",
- "indicator": "\u5927\u6613",
- "description": "Dayi",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=dayi&language=zh-TW&passwordLayout=dayi&name=inputmethod_dayi"
- },
- {
- "name": "__MSG_inputmethod_zhuyin__",
- "type": "ime",
- "id": "zh-hant-t-i0-und",
- "indicator": "\u6CE8",
- "description": "Zhuyin",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=zhuyin.compact.qwerty&language=zh-TW&passwordLayout=zhuyin.en.compact.qwerty&name=inputmethod_zhuyin",
- "options_page": "hmm_options.html?code=zh-hant-t-i0-und"
- },
- {
- "name": "__MSG_transliteration_am__",
- "type": "ime",
- "id": "am-t-i0-und",
- "description": "Amharic",
- "language": "am",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=am&passwordLayout=t13n&name=transliteration_am"
- },
- {
- "name": "__MSG_transliteration_ar__",
- "type": "ime",
- "id": "ar-t-i0-und",
- "description": "Arabic",
- "language": "ar",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n-rtl&language=ar&passwordLayout=t13n-rtl&name=transliteration_ar"
- },
- {
- "name": "__MSG_transliteration_bn__",
- "type": "ime",
- "id": "bn-t-i0-und",
- "description": "Bengali",
- "language": "bn",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=bn&passwordLayout=t13n&name=transliteration_bn"
- },
- {
- "name": "__MSG_transliteration_el__",
- "type": "ime",
- "id": "el-t-i0-und",
- "description": "Greek",
- "language": "el",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=el&passwordLayout=t13n&name=transliteration_el"
- },
- {
- "name": "__MSG_transliteration_fa__",
- "type": "ime",
- "id": "fa-t-i0-und",
- "description": "Persian",
- "language": "fa",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n-rtl&language=fa&passwordLayout=t13n-rtl&name=transliteration_fa"
- },
- {
- "name": "__MSG_transliteration_gu__",
- "type": "ime",
- "id": "gu-t-i0-und",
- "description": "Gujarati",
- "language": "gu",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=gu&passwordLayout=t13n&name=transliteration_gu"
- },
- {
- "name": "__MSG_transliteration_he__",
- "type": "ime",
- "id": "he-t-i0-und",
- "description": "Hebrew",
- "language": "he",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n-rtl&language=he&passwordLayout=t13n-rtl&name=transliteration_he"
- },
- {
- "name": "__MSG_transliteration_hi__",
- "type": "ime",
- "id": "hi-t-i0-und",
- "description": "Hindi",
- "language": "hi",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=hi&passwordLayout=t13n&name=transliteration_hi"
- },
- {
- "name": "__MSG_transliteration_kn__",
- "type": "ime",
- "id": "kn-t-i0-und",
- "description": "Kannada",
- "language": "kn",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=kn&passwordLayout=t13n&name=transliteration_kn"
- },
- {
- "name": "__MSG_transliteration_ml__",
- "type": "ime",
- "id": "ml-t-i0-und",
- "description": "Malayalam",
- "language": "ml",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=ml&passwordLayout=t13n&name=transliteration_ml"
- },
- {
- "name": "__MSG_transliteration_mr__",
- "type": "ime",
- "id": "mr-t-i0-und",
- "description": "Marathi",
- "language": "mr",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=mr&passwordLayout=t13n&name=transliteration_mr"
- },
- {
- "name": "__MSG_transliteration_ne__",
- "type": "ime",
- "id": "ne-t-i0-und",
- "description": "Nepali",
- "language": "ne",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=ne&passwordLayout=t13n&name=transliteration_ne"
- },
- {
- "name": "__MSG_transliteration_or__",
- "type": "ime",
- "id": "or-t-i0-und",
- "description": "Oriya",
- "language": "or",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=or&passwordLayout=t13n&name=transliteration_or"
- },
- {
- "name": "__MSG_transliteration_pa__",
- "type": "ime",
- "id": "pa-t-i0-und",
- "description": "Punjabi",
- "language": "pa",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=pa&passwordLayout=t13n&name=transliteration_pa"
- },
- {
- "name": "__MSG_transliteration_sa__",
- "type": "ime",
- "id": "sa-t-i0-und",
- "description": "Sanskrit",
- "language": "sa",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=sa&passwordLayout=t13n&name=transliteration_sa"
- },
- {
- "name": "__MSG_transliteration_sr__",
- "type": "ime",
- "id": "sr-t-i0-und",
- "description": "Serbian",
- "language": "sr",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=sr&passwordLayout=t13n&name=transliteration_sr"
- },
- {
- "name": "__MSG_transliteration_ta__",
- "type": "ime",
- "id": "ta-t-i0-und",
- "description": "Tamil",
- "language": "ta",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=ta&passwordLayout=t13n&name=transliteration_ta"
- },
- {
- "name": "__MSG_transliteration_te__",
- "type": "ime",
- "id": "te-t-i0-und",
- "description": "Telugu",
- "language": "te",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=te&passwordLayout=t13n&name=transliteration_te"
- },
- {
- "name": "__MSG_transliteration_ti__",
- "type": "ime",
- "id": "ti-t-i0-und",
- "description": "Tigrinya",
- "language": "ti",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n&language=ti&passwordLayout=t13n&name=transliteration_ti"
- },
- {
- "name": "__MSG_transliteration_ur__",
- "type": "ime",
- "id": "ur-t-i0-und",
- "description": "Urdu",
- "language": "ur",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=t13n-rtl&language=ur&passwordLayout=t13n-rtl&name=transliteration_ur"
- },
- {
- "name": "__MSG_inputmethod_hangul__",
- "type": "ime",
- "id": "ko-t-i0-und",
- "indicator": "\ud55c",
- "description": "Korean input method.",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=ko&language=ko&passwordLayout=us-ltr&name=inputmethod_hangul",
- "options_page": "hmm_options.html?code=ko-t-i0-und"
- },
- {
- "name": "__MSG_inputmethod_mozc_us__",
- "type": "ime",
- "id": "nacl_mozc_us",
- "indicator": "\u3042",
- "description": "Japanese input method.",
- "language": "ja",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=jp_us&language=ja&passwordLayout=us-ltr&name=appNameUSKeyboard",
- "options_page": "mozc_option.html"
- },
- {
- "name": "__MSG_inputmethod_mozc_jp__",
- "type": "ime",
- "id": "nacl_mozc_jp",
- "indicator": "\u3042",
- "description": "Japanese input method.",
- "language": "ja",
- "layouts": [
- "jp"
- ],
- "input_view": "inputview.html#id=jp&language=ja&passwordLayout=us-ltr&name=appNameJPKeyboard",
- "options_page": "mozc_option.html"
- },
- {
- "name": "__MSG_keyboard_bengali_phonetic__",
- "type": "ime",
- "id": "vkd_bn_phone",
- "description": "",
- "language": [
- "bn"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:bn_phone&language=bn&passwordLayout=us-ltr&name=keyboard_bengali_phonetic"
- },
- {
- "name": "__MSG_keyboard_gujarati_phonetic__",
- "type": "ime",
- "id": "vkd_gu_phone",
- "description": "",
- "language": [
- "gu"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:gu_phone&language=gu&passwordLayout=us-ltr&name=keyboard_gujarati_phonetic"
- },
- {
- "name": "__MSG_keyboard_devanagari_phonetic__",
- "type": "ime",
- "id": "vkd_deva_phone",
- "description": "",
- "language": [
- "hi",
- "mr"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:deva_phone&language=hi&passwordLayout=us-ltr&name=keyboard_devanagari_phonetic"
- },
- {
- "name": "__MSG_keyboard_kannada_phonetic__",
- "type": "ime",
- "id": "vkd_kn_phone",
- "description": "",
- "language": [
- "kn"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:kn_phone&language=kn&passwordLayout=us-ltr&name=keyboard_kannada_phonetic"
- },
- {
- "name": "__MSG_keyboard_malayalam_phonetic__",
- "type": "ime",
- "id": "vkd_ml_phone",
- "description": "",
- "language": [
- "ml"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ml_phone&language=ml&passwordLayout=us-ltr&name=keyboard_malayalam_phonetic"
- },
- {
- "name": "__MSG_keyboard_tamil_inscript__",
- "type": "ime",
- "id": "vkd_ta_inscript",
- "description": "",
- "language": [
- "ta"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ta_inscript&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_inscript"
- },
- {
- "name": "__MSG_keyboard_tamil_phonetic__",
- "type": "ime",
- "id": "vkd_ta_phone",
- "description": "",
- "language": [
- "ta"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ta_phone&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_phonetic"
- },
- {
- "name": "__MSG_keyboard_tamil_tamil99__",
- "type": "ime",
- "id": "vkd_ta_tamil99",
- "description": "",
- "language": [
- "ta"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ta_tamil99&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_tamil99"
- },
- {
- "name": "__MSG_keyboard_tamil_typewriter__",
- "type": "ime",
- "id": "vkd_ta_typewriter",
- "description": "",
- "language": [
- "ta"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ta_typewriter&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_typewriter"
- },
- {
- "name": "__MSG_keyboard_tamil_itrans__",
- "type": "ime",
- "id": "vkd_ta_itrans",
- "description": "",
- "language": [
- "ta"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ta_itrans&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_itrans"
- },
- {
- "name": "__MSG_keyboard_telugu_phonetic__",
- "type": "ime",
- "id": "vkd_te_phone",
- "description": "",
- "language": [
- "te"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:te_phone&language=te&passwordLayout=us-ltr&name=keyboard_telugu_phonetic"
- },
- {
- "name": "__MSG_keyboard_ethiopic__",
- "type": "ime",
- "id": "vkd_ethi",
- "description": "",
- "language": [
- "am"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ethi&language=am&passwordLayout=us-ltr&name=keyboard_ethiopic"
- },
- {
- "name": "__MSG_keyboard_thai_kedmanee__",
- "type": "ime",
- "id": "vkd_th",
- "description": "",
- "language": [
- "th"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:th&language=th&passwordLayout=us-ltr&name=keyboard_thai_kedmanee"
- },
- {
- "name": "__MSG_keyboard_thai_pattachote__",
- "type": "ime",
- "id": "vkd_th_pattajoti",
- "description": "",
- "language": [
- "th"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:th_pattajoti&language=th&passwordLayout=us-ltr&name=keyboard_thai_pattachote"
- },
- {
- "name": "__MSG_keyboard_thai_tis__",
- "type": "ime",
- "id": "vkd_th_tis",
- "description": "",
- "language": [
- "th"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:th_tis&language=th&passwordLayout=us-ltr&name=keyboard_thai_tis"
- },
- {
- "name": "__MSG_keyboard_persian__",
- "type": "ime",
- "id": "vkd_fa",
- "description": "",
- "language": [
- "fa"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:fa&language=fa&passwordLayout=us-rtl&name=keyboard_persian"
- },
- {
- "name": "__MSG_keyboard_vietnamese_tcvn__",
- "type": "ime",
- "id": "vkd_vi_tcvn",
- "description": "",
- "language": [
- "vi"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:vi_tcvn&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_tcvn"
- },
- {
- "name": "__MSG_keyboard_vietnamese_telex__",
- "type": "ime",
- "id": "vkd_vi_telex",
- "description": "",
- "language": [
- "vi"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:vi_telex&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_telex"
- },
- {
- "name": "__MSG_keyboard_vietnamese_viqr__",
- "type": "ime",
- "id": "vkd_vi_viqr",
- "description": "",
- "language": [
- "vi"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:vi_viqr&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_viqr"
- },
- {
- "name": "__MSG_keyboard_vietnamese_vni__",
- "type": "ime",
- "id": "vkd_vi_vni",
- "description": "",
- "language": [
- "vi"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:vi_vni&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_vni"
- },
- {
- "name": "__MSG_keyboard_arabic__",
- "type": "ime",
- "id": "vkd_ar",
- "description": "",
- "language": [
- "ar"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ar&language=ar&passwordLayout=us-rtl&name=keyboard_arabic"
- },
- {
- "name": "__MSG_keyboard_lao__",
- "type": "ime",
- "id": "vkd_lo",
- "description": "",
- "language": [
- "lo"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:lo&language=lo&passwordLayout=us-ltr&name=keyboard_laothian"
- },
- {
- "name": "__MSG_keyboard_nepali_inscript__",
- "type": "ime",
- "id": "vkd_ne_inscript",
- "description": "",
- "language": [
- "ne"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ne_inscript&language=ne&passwordLayout=us-ltr&name=keyboard_nepali_inscript"
- },
- {
- "name": "__MSG_keyboard_nepali_phonetic__",
- "type": "ime",
- "id": "vkd_ne_phone",
- "description": "",
- "language": [
- "ne"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ne_phone&language=ne&passwordLayout=us-ltr&name=keyboard_nepali_phonetic"
- },
- {
- "name": "__MSG_keyboard_khmer__",
- "type": "ime",
- "id": "vkd_km",
- "description": "",
- "language": [
- "km"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:km&language=km&passwordLayout=us-ltr&name=keyboard_khmer"
- },
- {
- "name": "__MSG_keyboard_myanmar__",
- "type": "ime",
- "id": "vkd_my",
- "description": "",
- "language": [
- "my"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:my&language=my&passwordLayout=us-ltr&name=keyboard_myanmar"
- },
- {
- "name": "__MSG_keyboard_sinhala__",
- "type": "ime",
- "id": "vkd_si",
- "description": "",
- "language": [
- "si"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:si&language=si&passwordLayout=us-ltr&name=keyboard_sinhala"
- },
- {
- "name": "__MSG_keyboard_soranikurdish_en__",
- "type": "ime",
- "id": "vkd_ckb_en",
- "description": "Sorani Kurdish - English-based",
- "language": [
- "ckb"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ckb_en&language=ckb&passwordLayout=us-rtl&name=keyboard_soranikurdish_en"
- },
- {
- "name": "__MSG_keyboard_soranikurdish_ar__",
- "type": "ime",
- "id": "vkd_ckb_ar",
- "description": "Sorani Kurdish - Arabic-based",
- "language": [
- "ckb"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ckb_ar&language=ckb&passwordLayout=us-rtl&name=keyboard_soranikurdish_ar"
- },
- {
- "name": "__MSG_keyboard_myanmar_myansan__",
- "type": "ime",
- "id": "vkd_my_myansan",
- "description": "",
- "language": [
- "my"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:my_myansan&language=my&passwordLayout=us-ltr&name=keyboard_myanmar_myansan"
- },
- {
- "name": "__MSG_keyboard_russian_phonetic_aatseel__",
- "type": "ime",
- "id": "vkd_ru_phone_aatseel",
- "description": "",
- "language": [
- "ru"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ru_phone_aatseel&language=ru&passwordLayout=us-ltr&name=keyboard_russian_phonetic_aatseel"
- },
- {
- "name": "__MSG_keyboard_russian_phonetic_yazhert__",
- "type": "ime",
- "id": "vkd_ru_phone_yazhert",
- "description": "",
- "language": [
- "ru"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ru_phone_yazhert&language=ru&passwordLayout=us-ltr&name=keyboard_russian_phonetic_yazhert"
- }
- ],
- "manifest_version": 2,
- "content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'"
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json
deleted file mode 100644
index f6216def4c5..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json
+++ /dev/null
@@ -1,98 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcH+iGhcthZAC7BdquejweZ0ww0UFcKGrzou8qTBSOZcyql2groTUcpTEciMD2O0g2rc/DiS3T3r9TNvtDS9u8paaZKiT0xZC7IT9GB9cVDVNgdJpob9oUYTol/NFOdnqOyFDlNlH6SH+Z3BhOevtaKUARVK8OWrcoEhMaJEuWdwIDAQAB",
- "name": "Chrome OS Hangul IME",
- "version": "1.0.0.0",
- "description": "Korean input method on Chrome OS",
- "permissions": [
- "accessibilityFeatures.read",
- "input",
- "inputMethodPrivate",
- "metricsPrivate",
- "unlimitedStorage",
- "virtualKeyboardPrivate"
- ],
- "manifest_version": 2,
- "minimum_chrome_version": "22",
- "default_locale": "en",
- "background": {
- "page": "backgroundpage.html"
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/hangul",
- "input_components": [
- {
- "name": "__MSG_inputmethod_Hangul_2_Set__",
- "type": "ime",
- "id": "hangul_2set",
- "indicator": "\ud55c",
- "description": "",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=m17n:ko_2set&language=ko&passwordLayout=us&name=Hangul_2_Set&isFakeEventUsed=true"
- },
- {
- "name": "__MSG_inputmethod_Hangul_3_Set_390__",
- "type": "ime",
- "id": "hangul_3set390",
- "indicator": "\ud55c",
- "description": "",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_390&isFakeEventUsed=true"
- },
- {
- "name": "__MSG_inputmethod_Hangul_3_Set_Final__",
- "type": "ime",
- "id": "hangul_3setfinal",
- "indicator": "\ud55c",
- "description": "",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_Final&isFakeEventUsed=true"
- },
- {
- "name": "__MSG_inputmethod_Hangul_3_Set_No_Shift__",
- "type": "ime",
- "id": "hangul_3setnoshift",
- "indicator": "\ud55c",
- "description": "",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_No_Shift&isFakeEventUsed=true"
- },
- {
- "name": "__MSG_inputmethod_Hangul_Romaja__",
- "type": "ime",
- "id": "hangul_romaja",
- "indicator": "\ud55c",
- "description": "",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_Romaja&isFakeEventUsed=true"
- },
- {
- "name": "__MSG_inputmethod_Hangul_Ahnmatae__",
- "type": "ime",
- "id": "hangul_ahnmatae",
- "indicator": "\ud55c",
- "description": "",
- "language": "ko",
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_Ahnmatae&isFakeEventUsed=true"
- }
- ],
- "icons": {
- "128": "icon128.png"
- }
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json
deleted file mode 100644
index 145235ffc26..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json
+++ /dev/null
@@ -1,232 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWnOIJ+1glt7xg0p1c3pZTBppnhxzHVHvQBBCv86dciJQr9/B9AKcbPrGtYfK6qfBJ7QQQWdBcttZdsc1vuYNlOsnBqLhMbrJmVip0ZLfbxII0/2xozIuzQ1vIvRdBXHroRliXPk6eiJh7yrGluqNJXpDOpB/8CSbmDtCP9CzRPwIDAQAB",
- "name": "Chrome OS Keyboard",
- "version": "1.1.4.0",
- "description": "Chrome OS Keyboard",
- "default_locale": "en",
- "permissions": [
- "input"
- ],
- "background": {
- "scripts": [
- "cros_vk_background.js"
- ]
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/keyboard_layouts",
- "input_components": [
- {
- "name": "__MSG_keyboard_bengali_phonetic__",
- "type": "ime",
- "id": "vkd_bn_phone",
- "description": "",
- "language": "bn",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_gujarati_phonetic__",
- "type": "ime",
- "id": "vkd_gu_phone",
- "description": "",
- "language": "gu",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_devanagari_phonetic__",
- "type": "ime",
- "id": "vkd_deva_phone",
- "description": "",
- "language": [
- "hi",
- "mr"
- ],
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_kannada_phonetic__",
- "type": "ime",
- "id": "vkd_kn_phone",
- "description": "",
- "language": "kn",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_malayalam_phonetic__",
- "type": "ime",
- "id": "vkd_ml_phone",
- "description": "",
- "language": "ml",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_tamil_inscript__",
- "type": "ime",
- "id": "vkd_ta_inscript",
- "description": "",
- "language": "ta",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_tamil_phonetic__",
- "type": "ime",
- "id": "vkd_ta_phone",
- "description": "",
- "language": "ta",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_tamil_tamil99__",
- "type": "ime",
- "id": "vkd_ta_tamil99",
- "description": "",
- "language": "ta",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_tamil_typewriter__",
- "type": "ime",
- "id": "vkd_ta_typewriter",
- "description": "",
- "language": "ta",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_tamil_itrans__",
- "type": "ime",
- "id": "vkd_ta_itrans",
- "description": "",
- "language": "ta",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_telugu_phonetic__",
- "type": "ime",
- "id": "vkd_te_phone",
- "description": "",
- "language": "te",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_ethiopic__",
- "type": "ime",
- "id": "vkd_ethi",
- "description": "",
- "language": "am",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_thai_kedmanee__",
- "type": "ime",
- "id": "vkd_th",
- "description": "",
- "language": "th",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_thai_pattachote__",
- "type": "ime",
- "id": "vkd_th_pattajoti",
- "description": "",
- "language": "th",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_thai_tis__",
- "type": "ime",
- "id": "vkd_th_tis",
- "description": "",
- "language": "th",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_persian__",
- "type": "ime",
- "id": "vkd_fa",
- "description": "",
- "language": "fa",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_vietnamese_tcvn__",
- "type": "ime",
- "id": "vkd_vi_tcvn",
- "description": "",
- "language": "vi",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_vietnamese_telex__",
- "type": "ime",
- "id": "vkd_vi_telex",
- "description": "",
- "language": "vi",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_vietnamese_viqr__",
- "type": "ime",
- "id": "vkd_vi_viqr",
- "description": "",
- "language": "vi",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_vietnamese_vni__",
- "type": "ime",
- "id": "vkd_vi_vni",
- "description": "",
- "language": "vi",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_keyboard_arabic__",
- "type": "ime",
- "id": "vkd_ar",
- "description": "",
- "language": "ar",
- "layouts": [
- "us"
- ]
- }
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json
deleted file mode 100644
index dc29e902802..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCG2Ia88F4+jAbMHaNVVDO2a7bLmWjG9URAzLsByWAoH0lZVVsorcRGVTOhiCNFe7Wkxc9GIDVQy286VNPYwvKJ8sfAejOLEcMskMF9tL0W9D39S04Q9XY60rcjDzIUc7JI2KV/mY4Wx5lyrap54BhRBaNp6lYUuxoRBWgZbtm+hwIDAQAB",
- "name": "Mozc IME extension",
- "version": "2.0.0.0",
- "description": "__MSG_appDesc__",
- "permissions": [
- "input",
- "unlimitedStorage"
- ],
- "manifest_version": 2,
- "minimum_chrome_version": "22",
- "default_locale": "en",
- "background": {
- "page": "nacl_mozc.html"
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/nacl_mozc",
- "input_components": [
- {
- "name": "__MSG_inputmethod_mozc_us__",
- "type": "ime",
- "id": "nacl_mozc_us",
- "indicator": "\u3042",
- "description": "Japanese input method.",
- "language": "ja",
- "layouts": [
- "us"
- ]
- },
- {
- "name": "__MSG_inputmethod_mozc_jp__",
- "type": "ime",
- "id": "nacl_mozc_jp",
- "indicator": "\u3042",
- "description": "Japanese input method.",
- "language": "ja",
- "layouts": [
- "jp"
- ]
- }
- ],
- "options_page": "options.html",
- "icons": {
- "128": "product_icon_32bpp-128.png"
- }
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json
deleted file mode 100644
index f6fc4c0f799..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSRMbBa14C/G45ow3CZvJlP7Nx3imrrj59+gWo7NzNdbtkQcne9HjTcmh3ALIHWtcDZ6fBiENBir9746MsgwC0h9yVg9BsgJJelLd7NV3hb6VXcPh9YFCKt6maLviENv1oApmMpY86wHifMiGr7WxR72T21V1RQQzetNTcQC3tvwIDAQAB",
- "name": "Chromium OS IME (Pinyin)",
- "version": "1.0.0.0",
- "description": "Simplified Chinese Pinyin IME for Chromium OS.",
- "default_locale": "en",
- "permissions": [
- "input",
- "unlimitedStorage"
- ],
- "background": {
- "page": "backgroundpage.html"
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/pinyin",
- "input_components": [
- {
- "name": "__MSG_inputmethod_pinyin__",
- "type": "ime",
- "id": "zh-t-i0-pinyin",
- "indicator": "\u62fc",
- "description": "Pinyin",
- "language": [
- "zh-CN",
- "zh"
- ],
- "layouts": [
- "us"
- ]
- }
- ],
- "options_page": "option.html",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
deleted file mode 100644
index ca18e770961..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
+++ /dev/null
@@ -1,883 +0,0 @@
-{
- "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsVpj8qC5VhObWORqQ3odZm+z5Vf58jWtqG2dmWkG87gsS1ZuYm4jA7mFZItOxgMENpYYWla8HCs21W6hEGoydDiIEAEVaWMTzCw19/x0m/LMeNIBtuaZ/IJQvFJuo1KHvmHX0lWxsHZgUfwpeDQFAqytDHjI0/50bI9P31eZKlZwkAWsRI3RoDuOv5YMVXIo3Y/CeTnG1OuxZOE9dr9Z7URfJ4vvkBytfKlmJIk9Z5ovGow23aWf3Z6iYJaxJrqVJOhjNuWY1Axp2jvkngcmz+AfusEC9zbmeg0moVLLykPD5x76dEH0BwFTFQk6fOcJy+vGotR2bKNVf0xhFlKueQIDAQAB",
- "name": "Chrome OS XKB",
- "version": "1.0.1.0",
- "description": "Chrome OS XKB",
- "default_locale": "en",
- "permissions": [
- "app.window.ime",
- "app.window.alpha",
- "input",
- "metricsPrivate",
- "accessibilityFeatures.read"
- ],
- "background": {
- "page": "background.html",
- "persistent": false
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/xkb",
- "input_components": [
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::eng",
- "description": "",
- "language": [
- "en",
- "en-US",
- "en-AU",
- "en-NZ"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html?id=us.compact.qwerty&language=en-US&passwordLayout=us.compact.qwerty&name=keyboard_us"
- },
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::ind",
- "description": "",
- "language": [
- "id"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html?id=us&language=id&passwordLayout=us&name=keyboard_us"
- },
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::fil",
- "description": "",
- "language": [
- "fil"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html?id=us&language=fil&passwordLayout=us&name=keyboard_us"
- },
- {
- "name": "__MSG_keyboard_us__",
- "type": "ime",
- "id": "xkb:us::msa",
- "description": "",
- "language": [
- "ms"
- ],
- "layouts": [
- "us"
- ],
- "input_view": "inputview.html?id=us&language=ms&passwordLayout=us&name=keyboard_us"
- },
- {
- "name": "__MSG_keyboard_us_international__",
- "type": "ime",
- "id": "xkb:us:intl:eng",
- "indicator": "INTL",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(intl)"
- ],
- "input_view": "inputview.html?id=us-intl&language=en-US&passwordLayout=us-intl&name=keyboard_us_international"
- },
- {
- "name": "__MSG_keyboard_netherlands__",
- "type": "ime",
- "id": "xkb:us:intl:nld",
- "indicator": "NLD",
- "description": "",
- "language": [
- "nl"
- ],
- "layouts": [
- "us(intl)"
- ],
- "input_view": "inputview.html?id=nl.compact.qwerty&language=nl&passwordLayout=nl.compact.qwerty&name=keyboard_netherlands"
- },
- {
- "name": "__MSG_keyboard_us_extended__",
- "type": "ime",
- "id": "xkb:us:altgr-intl:eng",
- "indicator": "EXTD",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(altgr-intl)"
- ],
- "input_view": "inputview.html?id=us-altgr-intl&language=en-US&passwordLayout=us-altgr-intl&name=keyboard_us_extended"
- },
- {
- "name": "__MSG_keyboard_us_dvorak__",
- "type": "ime",
- "id": "xkb:us:dvorak:eng",
- "indicator": "DV",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(dvorak)"
- ],
- "input_view": "inputview.html?id=us-dvorak&language=en-US&passwordLayout=us-dvorak&name=keyboard_us_dvorak"
- },
- {
- "name": "__MSG_keyboard_us_dvp__",
- "type": "ime",
- "id": "xkb:us:dvp:eng",
- "indicator": "DVP",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(dvp)"
- ],
- "input_view": "inputview.html?id=us-dvp&language=en-US&passwordLayout=us-dvp&name=keyboard_us_dvp"
- },
- {
- "name": "__MSG_keyboard_us_colemak__",
- "type": "ime",
- "id": "xkb:us:colemak:eng",
- "indicator": "CO",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(colemak)"
- ],
- "input_view": "inputview.html?id=us-colemak&language=en-US&passwordLayout=us-colemak&name=keyboard_us_colemak"
- },
- {
- "name": "__MSG_keyboard_us_workman__",
- "type": "ime",
- "id": "xkb:us:workman:eng",
- "indicator": "WM",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(workman)"
- ],
- "input_view": "inputview.html?id=us-workman&language=en-US&passwordLayout=us-workman&name=keyboard_us_workman"
- },
- {
- "name": "__MSG_keyboard_us_workman_international__",
- "type": "ime",
- "id": "xkb:us:workman-intl:eng",
- "indicator": "WMI",
- "description": "",
- "language": [
- "en",
- "en-US"
- ],
- "layouts": [
- "us(workman-intl)"
- ],
- "input_view": "inputview.html?id=us-workman-intl&language=en-US&passwordLayout=us-workman-intl&name=keyboard_us_workman_international"
- },
- {
- "name": "__MSG_keyboard_belgian__",
- "type": "ime",
- "id": "xkb:be::nld",
- "description": "",
- "language": [
- "nl"
- ],
- "layouts": [
- "be"
- ],
- "input_view": "inputview.html?id=be&language=nl&passwordLayout=be&name=keyboard_belgian"
- },
- {
- "name": "__MSG_keyboard_french__",
- "type": "ime",
- "id": "xkb:fr::fra",
- "description": "",
- "language": [
- "fr",
- "fr-FR"
- ],
- "layouts": [
- "fr(oss)"
- ],
- "input_view": "inputview.html?id=fr.compact.qwerty&language=fr&passwordLayout=fr.compact.qwerty&name=keyboard_french"
- },
- {
- "name": "__MSG_keyboard_belgian__",
- "type": "ime",
- "id": "xkb:be::fra",
- "description": "",
- "language": [
- "fr"
- ],
- "layouts": [
- "be"
- ],
- "input_view": "inputview.html?id=be&language=fr&passwordLayout=be&name=keyboard_belgian"
- },
- {
- "name": "__MSG_keyboard_canadian_french__",
- "type": "ime",
- "id": "xkb:ca::fra",
- "description": "",
- "language": [
- "fr",
- "fr-CA"
- ],
- "layouts": [
- "ca"
- ],
- "input_view": "inputview.html?id=ca.compact.qwerty&language=fr&passwordLayout=ca.compact.qwerty&name=keyboard_canadian_french"
- },
- {
- "name": "__MSG_keyboard_swiss_french__",
- "type": "ime",
- "id": "xkb:ch:fr:fra",
- "description": "",
- "language": [
- "fr",
- "fr-CH"
- ],
- "layouts": [
- "ch(fr)"
- ],
- "input_view": "inputview.html?id=ch-fr&language=fr&passwordLayout=ch-fr&name=keyboard_swiss_french"
- },
- {
- "name": "__MSG_keyboard_canadian_multilingual__",
- "type": "ime",
- "id": "xkb:ca:multix:fra",
- "description": "",
- "language": [
- "fr",
- "fr-CA"
- ],
- "layouts": [
- "ca(multix)"
- ],
- "input_view": "inputview.html?id=ca-multix&language=fr&passwordLayout=ca-multix&name=keyboard_canadian_multilingual"
- },
- {
- "name": "__MSG_keyboard_german__",
- "type": "ime",
- "id": "xkb:de::ger",
- "description": "",
- "language": [
- "de",
- "de-DE"
- ],
- "layouts": [
- "de"
- ],
- "input_view": "inputview.html?id=de.compact.qwerty&language=de&passwordLayout=de.compact.qwerty&name=keyboard_german"
- },
- {
- "name": "__MSG_keyboard_german_neo_2__",
- "type": "ime",
- "id": "xkb:de:neo:ger",
- "indicator": "NEO",
- "description": "",
- "language": [
- "de",
- "de-DE"
- ],
- "layouts": [
- "de(neo)"
- ],
- "input_view": "inputview.html?id=de-neo&language=de&passwordLayout=de-neo&name=keyboard_german_neo_2"
- },
- {
- "name": "__MSG_keyboard_belgian__",
- "type": "ime",
- "id": "xkb:be::ger",
- "description": "",
- "language": [
- "de"
- ],
- "layouts": [
- "be"
- ],
- "input_view": "inputview.html?id=be&language=de&passwordLayout=be&name=keyboard_belgian"
- },
- {
- "name": "__MSG_keyboard_swiss__",
- "type": "ime",
- "id": "xkb:ch::ger",
- "description": "",
- "language": [
- "de",
- "de-CH"
- ],
- "layouts": [
- "ch"
- ],
- "input_view": "inputview.html?id=ch&language=de&passwordLayout=ch&name=keyboard_swiss"
- },
- {
- "name": "__MSG_keyboard_japanese__",
- "type": "ime",
- "id": "xkb:jp::jpn",
- "indicator": "JA",
- "description": "",
- "language": [
- "ja"
- ],
- "layouts": [
- "jp"
- ],
- "input_view": "inputview.html?id=jp&language=ja&passwordLayout=jp&name=keyboard_japanese"
- },
- {
- "name": "__MSG_keyboard_russian__",
- "type": "ime",
- "id": "xkb:ru::rus",
- "description": "",
- "language": [
- "ru"
- ],
- "layouts": [
- "ru"
- ],
- "input_view": "inputview.html?id=ru&language=ru&passwordLayout=us&name=keyboard_russian"
- },
- {
- "name": "__MSG_keyboard_russian_phonetic__",
- "type": "ime",
- "id": "xkb:ru:phonetic:rus",
- "description": "",
- "language": [
- "ru"
- ],
- "layouts": [
- "ru(phonetic)"
- ],
- "input_view": "inputview.html?id=ru-phonetic&language=ru&passwordLayout=us&name=keyboard_russian_phonetic"
- },
- {
- "name": "__MSG_keyboard_brazilian__",
- "type": "ime",
- "id": "xkb:br::por",
- "description": "",
- "language": [
- "pt-BR",
- "pt"
- ],
- "layouts": [
- "br"
- ],
- "input_view": "inputview.html?id=br&language=pt-BR&passwordLayout=br&name=keyboard_brazilian"
- },
- {
- "name": "__MSG_keyboard_us_international__",
- "type": "ime",
- "id": "xkb:us:intl:por",
- "indicator": "INTL",
- "description": "",
- "language": [
- "pt-BR"
- ],
- "layouts": [
- "us(intl)"
- ],
- "input_view": "inputview.html?id=us-intl&language=pt-BR&passwordLayout=us-intl&name=keyboard_us_international"
- },
- {
- "name": "__MSG_keyboard_bulgarian__",
- "type": "ime",
- "id": "xkb:bg::bul",
- "description": "",
- "language": [
- "bg"
- ],
- "layouts": [
- "bg"
- ],
- "input_view": "inputview.html?id=bg&language=bg&passwordLayout=us&name=keyboard_bulgarian"
- },
- {
- "name": "__MSG_keyboard_bulgarian_phonetic__",
- "type": "ime",
- "id": "xkb:bg:phonetic:bul",
- "description": "",
- "language": [
- "bg"
- ],
- "layouts": [
- "bg(phonetic)"
- ],
- "input_view": "inputview.html?id=bg-phonetic&language=bg&passwordLayout=us&name=keyboard_bulgarian_phonetic"
- },
- {
- "name": "__MSG_keyboard_canadian_english__",
- "type": "ime",
- "id": "xkb:ca:eng:eng",
- "description": "",
- "language": [
- "en",
- "en-CA"
- ],
- "layouts": [
- "ca(eng)"
- ],
- "input_view": "inputview.html?id=ca-eng.compact.qwerty&language=en-CA&passwordLayout=ca-eng.compact.qwerty&name=keyboard_canadian_english"
- },
- {
- "name": "__MSG_keyboard_czech__",
- "type": "ime",
- "id": "xkb:cz::cze",
- "description": "",
- "language": [
- "cs"
- ],
- "layouts": [
- "cz"
- ],
- "input_view": "inputview.html?id=cz&language=cs&passwordLayout=cz&name=keyboard_czech"
- },
- {
- "name": "__MSG_keyboard_czech_qwerty__",
- "type": "ime",
- "id": "xkb:cz:qwerty:cze",
- "indicator": "CS",
- "description": "",
- "language": [
- "cs"
- ],
- "layouts": [
- "cz(qwerty)"
- ],
- "input_view": "inputview.html?id=cz-qwerty&language=cs&passwordLayout=cz-qwerty&name=keyboard_czech_qwerty"
- },
- {
- "name": "__MSG_keyboard_estonian__",
- "type": "ime",
- "id": "xkb:ee::est",
- "description": "",
- "language": [
- "et"
- ],
- "layouts": [
- "ee"
- ],
- "input_view": "inputview.html?id=ee&language=et&passwordLayout=ee&name=keyboard_estonian"
- },
- {
- "name": "__MSG_keyboard_spanish__",
- "type": "ime",
- "id": "xkb:es::spa",
- "description": "",
- "language": [
- "es",
- "es-ES"
- ],
- "layouts": [
- "es"
- ],
- "input_view": "inputview.html?id=es&language=es&passwordLayout=es&name=keyboard_spanish"
- },
- {
- "name": "__MSG_keyboard_catalan__",
- "type": "ime",
- "id": "xkb:es:cat:cat",
- "indicator": "CAT",
- "description": "",
- "language": [
- "ca"
- ],
- "layouts": [
- "es(cat)"
- ],
- "input_view": "inputview.html?id=es-cat&language=ca&passwordLayout=es-cat&name=keyboard_catalan"
- },
- {
- "name": "__MSG_keyboard_danish__",
- "type": "ime",
- "id": "xkb:dk::dan",
- "description": "",
- "language": [
- "da"
- ],
- "layouts": [
- "dk"
- ],
- "input_view": "inputview.html?id=dk.compact.qwerty&language=da&passwordLayout=dk.compact.qwerty&name=keyboard_danish"
- },
- {
- "name": "__MSG_keyboard_greek__",
- "type": "ime",
- "id": "xkb:gr::gre",
- "description": "",
- "language": [
- "el"
- ],
- "layouts": [
- "gr"
- ],
- "input_view": "inputview.html?id=gr&language=el&passwordLayout=us&name=keyboard_greek"
- },
- {
- "name": "__MSG_keyboard_hebrew__",
- "type": "ime",
- "id": "xkb:il::heb",
- "description": "",
- "language": [
- "he"
- ],
- "layouts": [
- "il"
- ],
- "input_view": "inputview.html?id=il&language=he&passwordLayout=us&name=keyboard_hebrew"
- },
- {
- "name": "__MSG_keyboard_latin_american__",
- "type": "ime",
- "id": "xkb:latam::spa",
- "indicator": "LA",
- "description": "",
- "language": [
- "es",
- "es-419"
- ],
- "layouts": [
- "latam"
- ],
- "input_view": "inputview.html?id=latam&language=es&passwordLayout=latam&name=keyboard_latin_american"
- },
- {
- "name": "__MSG_keyboard_lithuanian__",
- "type": "ime",
- "id": "xkb:lt::lit",
- "description": "",
- "language": [
- "lt"
- ],
- "layouts": [
- "lt"
- ],
- "input_view": "inputview.html?id=lt&language=lt&passwordLayout=lt&name=keyboard_lithuanian"
- },
- {
- "name": "__MSG_keyboard_latvian__",
- "type": "ime",
- "id": "xkb:lv:apostrophe:lav",
- "description": "",
- "language": [
- "lv"
- ],
- "layouts": [
- "lv(apostrophe)"
- ],
- "input_view": "inputview.html?id=lv-apostrophe&language=lv&passwordLayout=lv-apostrophe&name=keyboard_latvian"
- },
- {
- "name": "__MSG_keyboard_croatian__",
- "type": "ime",
- "id": "xkb:hr::scr",
- "description": "",
- "language": [
- "hr"
- ],
- "layouts": [
- "hr"
- ],
- "input_view": "inputview.html?id=hr&language=hr&passwordLayout=hr&name=keyboard_croatian"
- },
- {
- "name": "__MSG_keyboard_uk__",
- "type": "ime",
- "id": "xkb:gb:extd:eng",
- "description": "",
- "language": [
- "en",
- "en-GB"
- ],
- "layouts": [
- "gb(extd)"
- ],
- "input_view": "inputview.html?id=gb-extd.compact.qwerty&language=en-GB&passwordLayout=gb-extd.compact.qwerty&name=keyboard_uk"
- },
- {
- "name": "__MSG_keyboard_uk_dvorak__",
- "type": "ime",
- "id": "xkb:gb:dvorak:eng",
- "indicator": "DV",
- "description": "",
- "language": [
- "en",
- "en-GB"
- ],
- "layouts": [
- "gb(dvorak)"
- ],
- "input_view": "inputview.html?id=gb-dvorak&language=en-GB&passwordLayout=gb-dvorak&name=keyboard_uk_dvorak"
- },
- {
- "name": "__MSG_keyboard_finnish__",
- "type": "ime",
- "id": "xkb:fi::fin",
- "description": "",
- "language": [
- "fi"
- ],
- "layouts": [
- "fi"
- ],
- "input_view": "inputview.html?id=fi.compact.qwerty&language=fi&passwordLayout=fi.compact.qwerty&name=keyboard_finnish"
- },
- {
- "name": "__MSG_keyboard_hungarian__",
- "type": "ime",
- "id": "xkb:hu::hun",
- "description": "",
- "language": [
- "hu"
- ],
- "layouts": [
- "hu"
- ],
- "input_view": "inputview.html?id=hu&language=hu&passwordLayout=hu&name=keyboard_hungarian"
- },
- {
- "name": "__MSG_keyboard_italian__",
- "type": "ime",
- "id": "xkb:it::ita",
- "description": "",
- "language": [
- "it",
- "it-IT"
- ],
- "layouts": [
- "it"
- ],
- "input_view": "inputview.html?id=it&language=it&passwordLayout=it&name=keyboard_italian"
- },
- {
- "name": "__MSG_keyboard_icelandic__",
- "type": "ime",
- "id": "xkb:is::ice",
- "description": "",
- "language": [
- "is"
- ],
- "layouts": [
- "is"
- ],
- "input_view": "inputview.html?id=is.compact.qwerty&language=is&passwordLayout=is.compact.qwerty&name=keyboard_icelandic"
- },
- {
- "name": "__MSG_keyboard_norwegian__",
- "type": "ime",
- "id": "xkb:no::nob",
- "description": "",
- "language": [
- "nb",
- "nn",
- "no"
- ],
- "layouts": [
- "no"
- ],
- "input_view": "inputview.html?id=no.compact.qwerty&language=no&passwordLayout=no.compact.qwerty&name=keyboard_norwegian"
- },
- {
- "name": "__MSG_keyboard_polish__",
- "type": "ime",
- "id": "xkb:pl::pol",
- "description": "",
- "language": [
- "pl"
- ],
- "layouts": [
- "pl"
- ],
- "input_view": "inputview.html?id=pl&language=pl&passwordLayout=pl&name=keyboard_polish"
- },
- {
- "name": "__MSG_keyboard_portuguese__",
- "type": "ime",
- "id": "xkb:pt::por",
- "description": "",
- "language": [
- "pt-PT",
- "pt"
- ],
- "layouts": [
- "pt"
- ],
- "input_view": "inputview.html?id=pt&language=pt-PT&passwordLayout=pt&name=keyboard_portuguese"
- },
- {
- "name": "__MSG_keyboard_romanian__",
- "type": "ime",
- "id": "xkb:ro::rum",
- "description": "",
- "language": [
- "ro"
- ],
- "layouts": [
- "ro"
- ],
- "input_view": "inputview.html?id=ro&language=ro&passwordLayout=ro&name=keyboard_romanian"
- },
- {
- "name": "__MSG_keyboard_swedish__",
- "type": "ime",
- "id": "xkb:se::swe",
- "description": "",
- "language": [
- "sv"
- ],
- "layouts": [
- "se"
- ],
- "input_view": "inputview.html?id=se.compact.qwerty&language=sv&passwordLayout=se.compact.qwerty&name=keyboard_swedish"
- },
- {
- "name": "__MSG_keyboard_slovak__",
- "type": "ime",
- "id": "xkb:sk::slo",
- "description": "",
- "language": [
- "sk"
- ],
- "layouts": [
- "sk"
- ],
- "input_view": "inputview.html?id=sk&language=sk&passwordLayout=us&name=keyboard_slovakian"
- },
- {
- "name": "__MSG_keyboard_slovenian__",
- "type": "ime",
- "id": "xkb:si::slv",
- "description": "",
- "language": [
- "sl"
- ],
- "layouts": [
- "si"
- ],
- "input_view": "inputview.html?id=si&language=sl&passwordLayout=si&name=keyboard_slovenian"
- },
- {
- "name": "__MSG_keyboard_serbian__",
- "type": "ime",
- "id": "xkb:rs::srp",
- "description": "",
- "language": [
- "sr"
- ],
- "layouts": [
- "rs"
- ],
- "input_view": "inputview.html?id=rs&language=sr&passwordLayout=us&name=keyboard_serbian"
- },
- {
- "name": "__MSG_keyboard_turkish__",
- "type": "ime",
- "id": "xkb:tr::tur",
- "description": "",
- "language": [
- "tr"
- ],
- "layouts": [
- "tr"
- ],
- "input_view": "inputview.html?id=tr&language=tr&passwordLayout=tr&name=keyboard_turkish"
- },
- {
- "name": "__MSG_keyboard_ukrainian__",
- "type": "ime",
- "id": "xkb:ua::ukr",
- "description": "",
- "language": [
- "uk"
- ],
- "layouts": [
- "ua"
- ],
- "input_view": "inputview.html?id=ua&language=uk&passwordLayout=us&name=keyboard_ukrainian"
- },
- {
- "name": "__MSG_keyboard_belarusian__",
- "type": "ime",
- "id": "xkb:by::bel",
- "description": "",
- "language": [
- "be"
- ],
- "layouts": [
- "by"
- ],
- "input_view": "inputview.html?id=by&language=be&passwordLayout=us&name=keyboard_belarusian"
- },
- {
- "name": "__MSG_keyboard_armenian_phonetic__",
- "type": "ime",
- "id": "xkb:am:phonetic:arm",
- "description": "",
- "language": [
- "hy"
- ],
- "layouts": [
- "am"
- ],
- "input_view": "inputview.html?id=am&language=hy&passwordLayout=us&name=keyboard_armenian_phonetic"
- },
- {
- "name": "__MSG_keyboard_georgian__",
- "type": "ime",
- "id": "xkb:ge::geo",
- "description": "",
- "language": [
- "ka"
- ],
- "layouts": [
- "ge"
- ],
- "input_view": "inputview.html?id=ge&language=ka&passwordLayout=us&name=keyboard_georgian"
- },
- {
- "name": "__MSG_keyboard_mongolian__",
- "type": "ime",
- "id": "xkb:mn::mon",
- "description": "",
- "language": [
- "mn"
- ],
- "layouts": [
- "mn"
- ],
- "input_view": "inputview.html?id=mn&language=mn&passwordLayout=us&name=keyboard_mongolian"
- },
- {
- "name": "__MSG_keyboard_irish__",
- "type": "ime",
- "id": "xkb:ie::ga",
- "description": "",
- "language": [
- "ga"
- ],
- "layouts": [
- "ie"
- ],
- "input_view": "inputview.html?id=ie.compact.qwerty&language=ga&passwordLayout=ie.compact.qwerty&name=keyboard_irish"
- }
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json
deleted file mode 100644
index ebbde26523a..00000000000
--- a/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/5U4avuxjO6MQcDI9PYsgdSm59Q7lhlBaNxCw8LLhx48HJdOph5hwin5MZ56PMckqrLkisyTFu6r9WDErZNWyZQtUUXgwAlFg48aH+G/K49gRSw/0HKrs5iY2zoTFpLIO1+ru4rzJOkuk2Z4VPp/m/ZVgjmNT++35zkZn2qZEOQIDAQAB",
- "name": "Chromium OS IME (Zhuyin)",
- "version": "1.0.0.0",
- "description": "Traditional Chinese Zhuyin IME for Chromium OS.",
- "default_locale": "en",
- "permissions": [
- "input",
- "unlimitedStorage"
- ],
- "background": {
- "page": "backgroundpage.html"
- },
- "ime_path": "/usr/share/chromeos-assets/input_methods/zhuyin",
- "input_components": [
- {
- "name": "__MSG_inputmethod_zhuyin__",
- "type": "ime",
- "id": "zh-hant-t-i0-und",
- "indicator": "\u6CE8",
- "description": "Zhuyin",
- "language": [
- "zh-TW",
- "zh"
- ],
- "layouts": [
- "us"
- ]
- }
- ],
- "options_page": "option.html",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/browser/resources/chromeos/login/discover/manifest.json b/chromium/chrome/browser/resources/chromeos/login/discover/manifest.json
deleted file mode 100644
index 689fc1cf5e2..00000000000
--- a/chromium/chrome/browser/resources/chromeos/login/discover/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "short_name": "Discover",
- "display": "standalone",
- "icons": [
- {
- "src": "chrome://oobe/logo.png",
- "sizes": "192x192",
- "type": "image/png"
- }
- ],
- "start_url": "/discover",
- "theme_color": "#FFFFFF"
-}
diff --git a/chromium/chrome/browser/resources/chromeos/mobile_app/manifest.json b/chromium/chrome/browser/resources/chromeos/mobile_app/manifest.json
deleted file mode 100644
index 5a4a822e9d2..00000000000
--- a/chromium/chrome/browser/resources/chromeos/mobile_app/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "key": "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDITTAJ8zmgRjcmCRIAUbzlrSSzoWPBRY/kaBP3mxZ8nlCMljhQLDDKClnyGK6+krYq8/LayF4r0PR7g0Q27lwvw+asg1BFN+AL+suJqe4jJzxBK3GaMBRsBCCcwpAQw4F82zI9Hk0e6EqfrDq4ePNgv+PsI2MDbOuk3b1vS5oBjQIBIw==",
- "name": "Mobile Activation",
- "version": "1.0",
- "description": "Chrome OS Mobile Activation Resources",
- "default_locale": "en",
- "incognito": "split",
- "permissions": [],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json
deleted file mode 100644
index 15681ebd858..00000000000
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- // chrome-extension://obklkkbkpaoaejdabbfldmcfplpdgolj/
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6czTauf4/ISarT2JOcjcanIq+kcL/2UOF56QqXNT6PFgEwBG1i9Tfw/dTE59qWdGuxWt4yQlChewOFaRP3nb9AygPFXO31pNXaCY9xyDsjE5RhVvVB0GJa3VHFITIROpBSJHXNOqZBm706A8SFCTauasdWPFSE6Y7sA13t1P0MwIDAQAB",
- "name": "Wallpaper Picker",
- // Updates version to 0.2 for v2 version wallpaper picker. Otherwise the event
- // listener can not register appropriately. See crbug/157717.
- "version": "0.2",
- "manifest_version": 2,
- "description": "An experimental wallpaper picker UI",
- "icons": {
- "48": "images/icon48.png",
- "192": "images/icon192.png"
- },
- "permissions": [
- "alarms",
- "app.window.alpha",
- "chrome://resources/",
- "commandLinePrivate",
- "experimental",
- "storage",
- "unlimitedStorage",
- {"fileSystem": ["write"]},
- "wallpaperPrivate",
- "https://storage.googleapis.com/",
- "syncFileSystem"
- ],
- "app": {
- "background": {
- "scripts": [
- "js/constants.js",
- "js/util.js",
- "js/event_page.js"
- ]
- },
- "content_security_policy": "img-src 'self' blob: filesystem: data: chrome://resources;"
- },
- "display_in_launcher": false
-}
diff --git a/chromium/chrome/browser/resources/chromeos/zip_archiver/manifest.json b/chromium/chrome/browser/resources/chromeos/zip_archiver/manifest.json
deleted file mode 100644
index bf14acab791..00000000000
--- a/chromium/chrome/browser/resources/chromeos/zip_archiver/manifest.json
+++ /dev/null
@@ -1,73 +0,0 @@
-{
- // chrome-extension://dmboannefpncccogfdikhmhpmdnddgoe
- "key": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxGxJCOLUzHIYc812NFoBC1eV8PhOTuF6he3gSuqzxckUyrDLdl5++DAd1AkQkv6i8SSMWFvDKLg2b+zfCOwk6P7uu3tqNavXXy61Okaq5HKF3xhciNDl4zF6ZlegvE9AhJOTo2eCHVIMS0+YuK5hyno/+xMwN4byvsrOYXQnhcJeOHxkFb9TfVUb3SOBgl4pBZ7+EIMNntEvzY7mxjBzOgnCjBePvwnoMRyAqljCJarz2WSbUOLP3yoCuH9vPKj+0D6hF1woXmd6qBr0ln/7tHdbr1cYmkosfFuJO2y6d00FAJY/G5L6o8JAEBbWG5D0qELt+aBjkG0uos5gcR4ZPAgMBAAECggEBAK3aIjFJU25J6MjQiRvvY5a4O56bnUIb8SDZgAP6pbwZ7R2R9hiaN6AqVMOiptvgHDZAISYU/OerD4b3s0OCCkvYtlcxwh6iSZQ9BvIighFWrpZRqPHVjDktfQuNIS/dZiiy+9Yr0oFmD4jS45idCPgy+K0h6CEUX9GlPTEq24ElECDwQHVyB9LHdenleCdvldIEDxf6/D+zkc/PmCPlZPfwdppK6wgH2GvgqbxV+OoSnNp0XhNinjCN37P5yAo4xEi0UGOxOwkNGkJn0V5bYjH6/JHzmdVH74D40N4/Fcy0bC79oFGeiP0ZzW8AAArfIxbxStodWlBOCsTVtvi4RMECgYEA2pyZRThGx4OH8uXCC94LkdpVjKvLvbUlIVd2zk3UEFpWujgmAI+erkAaE1JSlcJpFNSlfonTX1vQuMgTOfnK7soy4677P1CMQH++GxjMWRIAQsMyx7vLtKOISr5/vQQKAyuFmxzt9xbMOmPzqWxwkuuiF74GtPgE5VXslhvsoyECgYEAz2U7L6YS4u2aMRK4DMDxcf/hZ3BxwHmUl5euknRNcaFJSdv6392y8w3t9Lz7sp8CK56GADXL1bmLrDgg2tlL82f60rtPd6IOoJk10uMmCnyjbZh7aJzuw1CTSs+dwi6qpGUB4YbJn8R2AN79SHxUb4dwVOh4lHeNa415Wka+a28CgYA3Vf5iDB22cO/fpxLYSCtrjvWqtu3KpmiwqOAU1pSAUy2y03WjHLeQ6f7vtx3adKx+rlj5z89mSuppa5OaUEVy7lG1WlyUqUHnLa6kU0GepjTUsW5QKpQktGRSbygMY1JZfRHDsq31ppqpiRVrZFyWg/iyw9IUytcKahaJ5KWgoQKBgFbgY/ugyNaQi3+1BK4rALktZAGNo8jp5SnfWzx0RaCs3GN5J80xNG4GTsCvjYwUebdF74IVBu7fi7e3x2OFlQBAdVxjJHXLx+7UXyyZBG1uKpOVRVTcMFRW42x6Le6S196HhVMwwDMR/BB/WIBNvJz/kjmvLBudPPtpxwTfD5M3AoGBALrrXX4QwqBiq4q09SPKoeOwlV35QETUhQaAKKag9aSrNMONcf77TXUBZ0d9Z+tabHLTGGa6E7q2BL82NdZSZvVeVWA+KaE4ezW2t5KyZqg14Cc0uY9Xys9VkFcVgMqsvtkUzDvAVJcmNAgcrMIEiapUR6LPrneLLXH1ikOt+hM8",
- "name": "Zip Archiver",
- "version": "1.1",
- "manifest_version": 2,
- "minimum_chrome_version": "44.0.2400.0",
- "description": "Zip Archiver - Open and pack ZIP files in Files app.",
- "default_locale": "en",
- "display_in_launcher": false,
- "incognito": "split",
- "permissions": [
- "alwaysOnTopWindows",
- "chrome://resources/",
- "fileSystemProvider",
- "fileManagerPrivate",
- {
- "fileSystem": [
- "requestFileSystem",
- "retainEntries",
- "write",
- "directory"
- ]
- },
- "notifications",
- "unlimitedStorage"
- ],
- "file_system_provider_capabilities": {
- "multipleMounts": true,
- "source": "file"
- },
- "file_handlers": {
- "open": {
- "types":["application/zip"],
- "extensions": ["zip"],
- "verb": "open_with"
- },
- "pack": {
- "types": ["*"],
- "include_directories": true,
- "verb": "pack_with"
- },
- "pack_using_tmp": {
- "types": ["*"],
- "include_directories": true,
- "verb": "pack_with"
- }
- },
- "icons": {
- "16": "icons/icon16.png",
- "32": "icons/icon32.png",
- "64": "icons/icon64.png",
- "96": "icons/icon96.png",
- "128": "icons/icon128.png"
- },
- "app": {
- "background": {
- "scripts": [
- "js/unpacker.js",
- "js/app.js",
- "js/background.js",
- "js/file_operation_utils.js",
- "js/compressor.js",
- "js/decompressor.js",
- "js/passphrase-manager.js",
- "js/request.js",
- "js/types.js",
- "js/volume.js"
- ]
- },
- "content_security_policy": "default-src 'none'; script-src 'self' chrome://resources; style-src 'unsafe-inline' chrome://resources;"
- }
-}
diff --git a/chromium/chrome/browser/resources/settings/OWNERS b/chromium/chrome/browser/resources/settings/OWNERS
deleted file mode 100644
index de56e9e6644..00000000000
--- a/chromium/chrome/browser/resources/settings/OWNERS
+++ /dev/null
@@ -1,12 +0,0 @@
-dbeam@chromium.org
-dpapad@chromium.org
-dschuyler@chromium.org
-hcarmona@chromium.org
-michaelpg@chromium.org
-stevenjb@chromium.org
-tommycli@chromium.org
-
-# Chrome OS Settings
-per-file *os_settings*=file://chrome/browser/resources/settings/chromeos/OWNERS
-
-# COMPONENT: UI>Settings
diff --git a/chromium/chrome/browser/resources/settings/PRESUBMIT.py b/chromium/chrome/browser/resources/settings/PRESUBMIT.py
deleted file mode 100644
index dcc145b7f91..00000000000
--- a/chromium/chrome/browser/resources/settings/PRESUBMIT.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-def _CheckChangeOnUploadOrCommit(input_api, output_api):
- import sys
- old_sys_path, cwd = sys.path[:], input_api.PresubmitLocalPath()
- src_root = input_api.os_path.join(cwd, '..', '..', '..', '..')
- try:
- sys.path += [input_api.os_path.join(src_root, 'tools', 'web_dev_style')]
- import web_dev_style.presubmit_support
- finally:
- sys.path = old_sys_path
- return web_dev_style.presubmit_support.DisallowIncludes(input_api, output_api,
- '<include> does not work in settings; use HTML imports instead')
-
-
-def CheckChangeOnUpload(input_api, output_api):
- return _CheckChangeOnUploadOrCommit(input_api, output_api)
-
-
-def CheckChangeOnCommit(input_api, output_api):
- return _CheckChangeOnUploadOrCommit(input_api, output_api)
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html
deleted file mode 100644
index 86bd1584a0c..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html
+++ /dev/null
@@ -1,123 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="captions_browser_proxy.html">
-<link rel="import" href="captions_subpage.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<if expr="chromeos">
-<link rel="import" href="manage_a11y_page.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="switch_access_subpage.html">
-<link rel="import" href="tts_subpage.html">
-</if>
-
-<dom-module id="settings-a11y-page">
- <template>
- <style include="settings-shared"></style>
- <settings-animated-pages id="pages" current-route="{{currentRoute}}"
- section="a11y" focus-config="[[focusConfig_]]">
- <div route-path="default">
-<if expr="chromeos">
- <cr-link-row class="hr" id="subpage-trigger"
- label="$i18n{manageAccessibilityFeatures}"
- on-click="onManageSystemAccessibilityFeaturesTap_"
- sub-label="$i18n{moreFeaturesLinkDescription}"
- hidden="[[showOsSettings_]]" external>
- </cr-link-row>
-</if>
-<if expr="not chromeos">
- <cr-link-row class="hr" id="captions" label="$i18n{captionsTitle}"
- on-click="onCaptionsClick_"
- external$="[[captionSettingsOpensExternally_]]">
- </cr-link-row>
- <settings-toggle-button
- id="a11yImageLabels"
- hidden$="[[!showAccessibilityLabelsSetting_]]"
- pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
- on-change="onToggleAccessibilityImageLabels_"
- label="$i18n{accessibleImageLabelsTitle}"
- sub-label="$i18n{accessibleImageLabelsSubtitle}">
- </settings-toggle-button>
-</if>
-<if expr="chromeos">
- <template is="dom-if" if="[[showOsSettings_]]">
- <settings-toggle-button
- id="a11yImageLabels"
- hidden$="[[!showAccessibilityLabelsSetting_]]"
- pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
- on-change="onToggleAccessibilityImageLabels_"
- label="$i18n{accessibleImageLabelsTitle}"
- sub-label="$i18n{accessibleImageLabelsSubtitle}">
- </settings-toggle-button>
- <settings-toggle-button id="optionsInMenuToggle"
- label="$i18n{optionsInMenuLabel}"
- pref="{{prefs.settings.a11y.enable_menu}}">
- </settings-toggle-button>
- <cr-link-row class="hr" id="subpage-trigger"
- label="$i18n{manageAccessibilityFeatures}"
- on-click="onManageAccessibilityFeaturesTap_"
- sub-label="$i18n{moreFeaturesLinkDescription}">
- </cr-link-row>
- </template>
-</if>
- <cr-link-row class="hr" label="$i18n{moreFeaturesLink}"
- on-click="onMoreFeaturesLinkClick_" sub-label="$i18n{a11yWebStore}"
- hidden="[[showOsSettings_]]" external>
- </cr-link-row>
- </div>
-<if expr="not is_macosx">
- <template is="dom-if" if="[[showCaptionSettings_]]">
- <template is="dom-if" route-path="/captions">
- <settings-subpage
- associated-control="[[$$('#captions')]]"
- page-title="$i18n{captionsTitle}">
- <settings-captions prefs="{{prefs}}"></settings-captions>
- </settings-subpage>
- </template>
- </template>
-</if>
-<if expr="chromeos">
- <template is="dom-if" if="[[showCaptionSettings_]]">
- <template is="dom-if" route-path="/manageAccessibility/captions">
- <settings-subpage
- associated-control="[[$$('#subpage-trigger')]]"
- page-title="$i18n{captionsTitle}">
- <settings-captions prefs="{{prefs}}">
- </settings-captions>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" if="[[showOsSettings_]]">
- <template is="dom-if" route-path="/manageAccessibility">
- <settings-subpage
- associated-control="[[$$('#subpage-trigger')]]"
- page-title="$i18n{manageAccessibilityFeatures}">
- <settings-manage-a11y-page prefs="{{prefs}}">
- </settings-manage-a11y-page>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/manageAccessibility/tts">
- <settings-subpage
- associated-control="[[$$('#subpage-trigger')]]"
- page-title="$i18n{manageTtsSettings}">
- <settings-tts-subpage prefs="{{prefs}}"></settings-tts-subpage>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/manageAccessibility/switchAccess">
- <settings-subpage associated-control="[[$$('#subpage-trigger')]]"
- page-title="$i18n{manageSwitchAccessSettings}">
- <settings-switch-access-subpage prefs="{{prefs.settings.a11y}}">
- </settings-switch-access-subpage>
- </settings-subpage>
- </template>
- </template>
-</if>
- </settings-animated-pages>
- </template>
- <script src="a11y_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js
deleted file mode 100644
index 6b24c8fa880..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-a11y-page' is the small section of advanced settings with
- * a link to the web store accessibility page on most platforms, and
- * a subpage with lots of other settings on Chrome OS.
- */
-Polymer({
- is: 'settings-a11y-page',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /**
- * The current active route.
- */
- currentRoute: {
- type: Object,
- notify: true,
- },
-
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Whether to show accessibility labels settings.
- */
- showAccessibilityLabelsSetting_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.CAPTIONS) {
- map.set(settings.routes.CAPTIONS.path, '#captions');
- }
- // <if expr="chromeos">
- if (settings.routes.MANAGE_ACCESSIBILITY) {
- map.set(
- settings.routes.MANAGE_ACCESSIBILITY.path, '#subpage-trigger');
- }
- // </if>
- return map;
- },
- },
-
- /**
- * Whether to show the link to caption settings.
- * @private {boolean}
- */
- showCaptionSettings_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableCaptionSettings');
- },
- },
-
- /**
- * Whether to show OS settings.
- * @private {boolean}
- */
- showOsSettings_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('showOSSettings');
- },
- },
-
- /**
- * Whether the caption settings link opens externally.
- * @private {boolean}
- */
- captionSettingsOpensExternally_: {
- type: Boolean,
- value: function() {
- let opensExternally = false;
- // <if expr="is_macosx">
- opensExternally = true;
- // </if>
-
- // <if expr="is_win">
- opensExternally = loadTimeData.getBoolean('isWindows10OrNewer');
- // </if>
-
- return opensExternally;
- },
- },
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'screen-reader-state-changed',
- this.onScreenReaderStateChanged_.bind(this));
- chrome.send('getScreenReaderState');
- },
-
- /**
- * @private
- * @param {boolean} hasScreenReader Whether a screen reader is enabled.
- */
- onScreenReaderStateChanged_: function(hasScreenReader) {
- // TODO(katie): Remove showExperimentalA11yLabels flag before launch.
- this.showAccessibilityLabelsSetting_ = hasScreenReader &&
- loadTimeData.getBoolean('showExperimentalA11yLabels');
- },
-
- /** @private */
- onToggleAccessibilityImageLabels_: function() {
- const a11yImageLabelsOn = this.$.a11yImageLabels.checked;
- if (a11yImageLabelsOn) {
- chrome.send('confirmA11yImageLabels');
- }
- chrome.metricsPrivate.recordBoolean(
- 'Accessibility.ImageLabels.FromSettings.ToggleSetting',
- a11yImageLabelsOn);
- },
-
- // <if expr="chromeos">
- /** @private */
- onManageAccessibilityFeaturesTap_: function() {
- settings.navigateTo(settings.routes.MANAGE_ACCESSIBILITY);
- },
-
- /** @private */
- onManageSystemAccessibilityFeaturesTap_: function() {
- window.location.href = 'chrome://os-settings/manageAccessibility';
- },
- // </if>
-
- /** private */
- onMoreFeaturesLinkClick_: function() {
- window.open(
- 'https://chrome.google.com/webstore/category/collection/accessibility');
- },
-
- /** @private */
- onCaptionsClick_: function() {
- // Open the system captions dialog for Mac.
- // <if expr="is_macosx">
- settings.CaptionsBrowserProxyImpl.getInstance().openSystemCaptionsDialog();
- // </if>
-
- // Open the system captions dialog for Windows 10+ or navigate to the
- // caption settings page for older versions of Windows
- // <if expr="is_win">
- if (loadTimeData.getBoolean('isWindows10OrNewer')) {
- settings.CaptionsBrowserProxyImpl.getInstance()
- .openSystemCaptionsDialog();
- } else {
- settings.navigateTo(settings.routes.CAPTIONS);
- }
- // </if>
-
- // Navigate to the caption settings page for ChromeOS and Linux as they
- // do not have system caption settings.
- // <if expr="chromeos or is_linux">
- settings.navigateTo(settings.routes.CAPTIONS);
- // </if>
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html b/chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html
deleted file mode 100644
index 92b4cd16176..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="captions_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js b/chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js
deleted file mode 100644
index bf86a7e8665..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the Chrome captions section to
- * interact with the browser. Used on operating system that is not Chrome OS.
- */
-
-cr.define('settings', function() {
- /** @interface */
- class CaptionsBrowserProxy {
- /**
- * Open the native captions system dialog.
- */
- openSystemCaptionsDialog() {}
- }
-
- /**
- * @implements {settings.CaptionsBrowserProxy}
- */
- class CaptionsBrowserProxyImpl {
- /** @override */
- openSystemCaptionsDialog() {
- chrome.send('openSystemCaptionsDialog');
- }
- }
-
- cr.addSingletonGetter(CaptionsBrowserProxyImpl);
-
- return {
- CaptionsBrowserProxy: CaptionsBrowserProxy,
- CaptionsBrowserProxyImpl: CaptionsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.html b/chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.html
deleted file mode 100644
index a894a919576..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/polymer.html">
-<link rel="import" href="../appearance_page/fonts_browser_proxy.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-captions">
- <template>
- <style include="settings-shared">
- .preview-box {
- align-items: center;
- background-image:
- url(chrome://theme/IDR_ACCESSIBILITY_CAPTIONS_PREVIEW_BACKGROUND);
- background-position: center;
- background-size: cover;
- display: flex;
- height: 112px;
- justify-content: center;
- margin: 0 var(--cr-section-padding) var(--cr-section-padding);
- text-align: center;
- }
- </style>
- <div class="settings-box first">
- <h2 class="start">$i18n{captionsPreview}</h2>
- </div>
- <div class="preview-box">
- <span style="
- font-size:[[prefs.accessibility.captions.text_size.value]];
- font-family:[[prefs.accessibility.captions.text_font.value]];
- background-color: [[computeBackgroundColor_(
- prefs.accessibility.captions.background_opacity.value,
- prefs.accessibility.captions.background_color.value)]];
- color: [[computeTextColor_(
- prefs.accessibility.captions.text_opacity.value,
- prefs.accessibility.captions.text_color.value)]];
- text-shadow: [[prefs.accessibility.captions.text_shadow.value]];
- padding: [[computePadding_(
- prefs.accessibility.captions.text_size.value)]]">
- $i18n{quickBrownFox}
- </span>
- </div>
- <div class="settings-box continuation">
- <h2 class="start">$i18n{captionsSettings}</h2>
- </div>
- <div class="list-frame">
- <div class="list-item underbar first">
- <div class="start settings-box-text">$i18n{captionsTextSize}</div>
- <settings-dropdown-menu id="captionsTextSize"
- label="$i18n{captionsTextSize}"
- pref="{{prefs.accessibility.captions.text_size}}"
- menu-options="[[textSizeOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item underbar">
- <div class="start settings-box-text">$i18n{captionsTextFont}</div>
- <settings-dropdown-menu id="captionsTextFont"
- label="$i18n{captionsTextFont}"
- pref="{{prefs.accessibility.captions.text_font}}"
- menu-options="[[textFontOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item underbar">
- <div class="start settings-box-text">$i18n{captionsTextColor}</div>
- <settings-dropdown-menu id="captionsTextColor"
- label="$i18n{captionsTextColor}"
- pref="{{prefs.accessibility.captions.text_color}}"
- menu-options="[[colorOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item underbar">
- <div class="start settings-box-text">$i18n{captionsTextOpacity}</div>
- <settings-dropdown-menu id="captionsTextOpacity"
- label="$i18n{captionsTextOpacity}"
- pref="{{prefs.accessibility.captions.text_opacity}}"
- menu-options="[[textOpacityOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item underbar">
- <div class="start settings-box-text">$i18n{captionsTextShadow}</div>
- <settings-dropdown-menu id="captionsTextShadow"
- label="$i18n{captionsTextShadow}"
- pref="{{prefs.accessibility.captions.text_shadow}}"
- menu-options="[[textShadowOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item underbar">
- <div class="start settings-box-text">
- $i18n{captionsBackgroundColor}
- </div>
- <settings-dropdown-menu id="captionsBackgroundColor"
- label="$i18n{captionsBackgroundColor}"
- pref="{{prefs.accessibility.captions.background_color}}"
- menu-options="[[colorOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item">
- <div class="start settings-box-text">
- $i18n{captionsBackgroundOpacity}
- </div>
- <settings-dropdown-menu id="captionsBackgroundOpacity"
- label="$i18n{captionsBackgroundOpacity}"
- pref="{{prefs.accessibility.captions.background_opacity}}"
- menu-options="[[backgroundOpacityOptions_]]">
- </settings-dropdown-menu>
- </div>
- </div>
- </template>
- <script src="captions_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.js b/chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.js
deleted file mode 100644
index ca625a3be4f..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/captions_subpage.js
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-captions' is a component for showing captions
- * settings subpage (chrome://settings/captions).
- */
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-captions',
-
- behaviors: [I18nBehavior, WebUIListenerBehavior],
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * List of options for the background opacity drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- backgroundOpacityOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {
- value: 100, // Default
- name: loadTimeData.getString('captionsOpacityOpaque')
- },
- {
- value: 50,
- name: loadTimeData.getString('captionsOpacitySemiTransparent')
- },
- {
- value: 0,
- name: loadTimeData.getString('captionsOpacityTransparent')
- },
- ];
- },
- },
-
- /**
- * List of options for the color drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- colorOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {
- value: '',
- name: loadTimeData.getString('captionsDefaultSetting')
- },
- {
- value: '0,0,0',
- name: loadTimeData.getString('captionsColorBlack')
- },
- {
- value: '255,255,255',
- name: loadTimeData.getString('captionsColorWhite')
- },
- {
- value: '255,0,0',
- name: loadTimeData.getString('captionsColorRed')
- },
- {
- value: '0,255,0',
- name: loadTimeData.getString('captionsColorGreen')
- },
- {
- value: '0,0,255',
- name: loadTimeData.getString('captionsColorBlue')
- },
- {
- value: '255,255,0',
- name: loadTimeData.getString('captionsColorYellow')
- },
- {
- value: '0,255,255',
- name: loadTimeData.getString('captionsColorCyan')
- },
- {
- value: '255,0,255',
- name: loadTimeData.getString('captionsColorMagenta')
- },
- ];
- },
- },
-
- /**
- * List of fonts populated by the fonts browser proxy.
- * @private {!DropdownMenuOptionList} */
- textFontOptions_: Object,
-
- /**
- * List of options for the text opacity drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- textOpacityOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {
- value: 100, // Default
- name: loadTimeData.getString('captionsOpacityOpaque')
- },
- {
- value: 50,
- name: loadTimeData.getString('captionsOpacitySemiTransparent')
- },
- {
- value: 10,
- name: loadTimeData.getString('captionsOpacityTransparent')
- },
- ];
- },
- },
-
- /**
- * List of options for the text shadow drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- textShadowOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {value: '', name: loadTimeData.getString('captionsTextShadowNone')},
- {
- value: '-2px -2px 4px rgba(0, 0, 0, 0.5)',
- name: loadTimeData.getString('captionsTextShadowRaised')
- },
- {
- value: '2px 2px 4px rgba(0, 0, 0, 0.5)',
- name: loadTimeData.getString('captionsTextShadowDepressed')
- },
- {
- value: '-1px 0px 0px black, ' +
- '0px -1px 0px black, 1px 0px 0px black, 0px 1px 0px black',
- name: loadTimeData.getString('captionsTextShadowUniform')
- },
- {
- value: '0px 0px 2px rgba(0, 0, 0, 0.5), 2px 2px 2px black',
- name: loadTimeData.getString('captionsTextShadowDropShadow')
- },
- ];
- },
- },
-
- /**
- * List of options for the text size drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- textSizeOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {value: '25%', name: loadTimeData.getString('verySmall')},
- {value: '50%', name: loadTimeData.getString('small')},
- {value: '', name: loadTimeData.getString('medium')}, // Default = 100%
- {value: '150%', name: loadTimeData.getString('large')},
- {value: '200%', name: loadTimeData.getString('veryLarge')},
- ];
- },
- },
- },
-
- /** @private {?settings.FontsBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.FontsBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_.observeAdvancedFontExtensionAvailable();
-
- this.browserProxy_.fetchFontsData().then(this.setFontsData_.bind(this));
- },
-
- /**
- * @param {!FontsData} response A list of fonts.
- * @private
- */
- setFontsData_: function(response) {
- const fontMenuOptions =
- [{value: '', name: loadTimeData.getString('captionsDefaultSetting')}];
- for (const fontData of response.fontList) {
- fontMenuOptions.push({value: fontData[0], name: fontData[1]});
- }
- this.textFontOptions_ = fontMenuOptions;
- },
-
- /**
- * Get the background color as a RGBA string.
- * @return {string}
- * @private
- */
- computeBackgroundColor_: function() {
- return this.formatRGAString_(
- 'prefs.accessibility.captions.background_color.value',
- 'prefs.accessibility.captions.background_opacity.value');
- },
-
- /**
- * Get the text color as a RGBA string.
- * @return {string}
- * @private
- */
- computeTextColor_: function() {
- return this.formatRGAString_(
- 'prefs.accessibility.captions.text_color.value',
- 'prefs.accessibility.captions.text_opacity.value');
- },
-
- /**
- * Formats the color as an RGBA string.
- * @param {string} colorPreference The name of the preference containing the
- * RGB values as a comma-separated string.
- * @param {string} opacityPreference The name of the preference containing
- * the opacity value as a percentage.
- * @return {string} The formatted RGBA string.
- * @private
- */
- formatRGAString_: function(colorPreference, opacityPreference) {
- return 'rgba(' + this.get(colorPreference) + ',' +
- parseInt(this.get(opacityPreference), 10) / 100.0 + ')';
- },
-
- /**
- * @param {string} size The font size of the captions text as a percentage.
- * @return {string} The padding around the captions text as a percentage.
- * @private
- */
- computePadding_: function(size) {
- if (size == '') {
- return '1%';
- }
-
- return `${+size.slice(0, -1) / 100}%`;
- }
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/externs.js b/chromium/chrome/browser/resources/settings/a11y_page/externs.js
deleted file mode 100644
index 499139f2e37..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/externs.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Stub methods to allow the closure compiler to compile
- * successfully for external dependencies.
- */
-
-/**
- * Represents a voice as sent from the TTS Handler class. |languageCode| is
- * the language, not the locale, i.e. 'en' rather than 'en-us'. |name| is the
- * user-facing voice name, and |id| is the unique ID for that voice name (which
- * is generated in tts_subpage.js and not passed from tts_handler.cc).
- * |displayLanguage| is the user-facing display string, i.e. 'English'.
- * |fullLanguageCode| is the code with locale, i.e. 'en-us' or 'en-gb'.
- * |languageScore| is a relative measure of how closely the voice's language
- * matches the app language, and can be used to set a default voice.
- * @typedef {{languageCode: string, name: string, displayLanguage: string,
- * extensionId: string, id: string, fullLanguageCode: string,
- * languageScore: number}}
- */
-let TtsHandlerVoice;
-
-/**
- * @typedef {{name: string, extensionId: string, optionsPage: string}}
- */
-let TtsHandlerExtension;
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html b/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html
deleted file mode 100644
index ccc488cb561..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html
+++ /dev/null
@@ -1,255 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-manage-a11y-page">
- <template>
- <style include="settings-shared">
- h2 {
- padding-inline-start: var(--cr-section-padding);
- }
-
- .sub-item {
- margin-inline-start: var(--cr-section-indent-width);
- }
-
- h2 ~ .settings-box,
- h2 ~ settings-toggle-button,
- h2 ~ cr-link-row,
- iron-collapse .settings-box,
- iron-collapse settings-toggle-button,
- iron-collapse cr-link-row {
- margin-inline-end: var(--cr-section-padding);
- margin-inline-start: var(--cr-section-indent-padding);
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
- </style>
- <div class="settings-box row first">
- <span>
- $i18n{a11yExplanation}
- <a href="$i18nRaw{a11yLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </span>
- </div>
-
- <h2>$i18n{textToSpeechHeading}</h2>
- <settings-toggle-button class="first"
- pref="{{prefs.settings.accessibility}}"
- label="$i18n{chromeVoxLabel}">
- </settings-toggle-button>
- <iron-collapse opened="[[prefs.settings.accessibility.value]]">
- <cr-link-row class="settings-box" on-click="onChromeVoxSettingsTap_"
- label="$i18n{chromeVoxOptionsLabel}" external></cr-link-row>
- </iron-collapse>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.select_to_speak}}"
- label="$i18n{selectToSpeakTitle}"
- sub-label="[[getSelectToSpeakDescription_(
- prefs.settings.a11y.select_to_speak.value, hasKeyboard_,
- '$i18nPolymer{selectToSpeakDisabledDescription}',
- '$i18nPolymer{selectToSpeakDescription}',
- '$i18nPolymer{selectToSpeakDescriptionWithoutKeyboard}')]]">
- </settings-toggle-button>
- <iron-collapse opened="[[prefs.settings.a11y.select_to_speak.value]]">
- <cr-link-row class="settings-box" on-click="onSelectToSpeakSettingsTap_"
- label="$i18n{selectToSpeakOptionsLabel}" external></cr-link-row>
- </iron-collapse>
-
- <cr-link-row class="hr" label="$i18n{manageTtsSettings}"
- on-click="onManageTtsSettingsTap_"
- sub-label="$i18n{ttsSettingsLinkDescription}" embedded></cr-link-row>
-
- <h2>$i18n{displayHeading}</h2>
- <settings-toggle-button class="first"
- pref="{{prefs.settings.a11y.high_contrast_enabled}}"
- label="$i18n{highContrastLabel}">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.screen_magnifier}}"
- label="$i18n{screenMagnifierLabel}"
- disabled="[[prefs.ash.docked_magnifier.enabled.value]]">
- </settings-toggle-button>
- <template is="dom-if" if="[[prefs.settings.a11y.screen_magnifier.value]]">
- <div class="settings-box continuation">
- <div class="start sub-item settings-box-text">
- $i18n{screenMagnifierZoomLabel}
- </div>
- <settings-dropdown-menu label="$i18n{screenMagnifierZoomLabel}"
- pref="{{prefs.settings.a11y.screen_magnifier_scale}}"
- menu-options="[[screenMagnifierZoomOptions_]]"
- disabled="[[!prefs.settings.a11y.screen_magnifier.value]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <settings-toggle-button
- pref="{{prefs.ash.docked_magnifier.enabled}}"
- label="$i18n{dockedMagnifierLabel}"
- disabled="[[prefs.settings.a11y.screen_magnifier.value]]">
- </settings-toggle-button>
- <template is="dom-if" if="[[prefs.ash.docked_magnifier.enabled.value]]">
- <div class="settings-box continuation">
- <div class="start sub-item settings-box-text">
- $i18n{dockedMagnifierZoomLabel}
- </div>
- <settings-dropdown-menu label="$i18n{dockedMagnifierZoomLabel}"
- pref="{{prefs.ash.docked_magnifier.scale}}"
- menu-options="[[screenMagnifierZoomOptions_]]"
- disabled="[[!prefs.ash.docked_magnifier.enabled.value]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <cr-link-row class="hr" label="$i18n{displaySettingsTitle}"
- on-click="onDisplayTap_" sub-label="$i18n{displaySettingsDescription}"
- embedded></cr-link-row>
- <cr-link-row class="hr" label="$i18n{appearanceSettingsTitle}"
- on-click="onAppearanceTap_"
- sub-label="$i18n{appearanceSettingsDescription}"
- external="[[isOSSettings_]]" embedded></cr-link-row>
-
- <h2>$i18n{keyboardAndTextInputHeading}</h2>
- <settings-toggle-button class="first"
- pref="{{prefs.settings.a11y.sticky_keys_enabled}}"
- label="$i18n{stickyKeysLabel}">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.virtual_keyboard}}"
- label="$i18n{onScreenKeyboardLabel}">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.dictation}}"
- label="$i18n{dictationLabel}"
- sub-label="$i18n{dictationDescription}">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.focus_highlight}}"
- label="$i18n{focusHighlightLabel}">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.caret_highlight}}"
- label="$i18n{caretHighlightLabel}">
- </settings-toggle-button>
- <template is="dom-if" if="[[showExperimentalSwitchAccess_]]">
- <settings-toggle-button
- pref="{{prefs.settings.a11y.switch_access.enabled}}"
- label="$i18n{switchAccessLabel}">
- </settings-toggle-button>
- <iron-collapse
- opened="[[prefs.settings.a11y.switch_access.enabled.value]]">
- <cr-link-row label="$i18n{switchAccessOptionsLabel}"
- on-click="onSwitchAccessSettingsTap_" embedded>
- </cr-link-row>
- </iron-collapse>
- </template>
- <cr-link-row class="hr" label="$i18n{keyboardSettingsTitle}"
- on-click="onKeyboardTap_" sub-label="$i18n{keyboardSettingsDescription}"
- embedded></cr-link-row>
-
- <h2>$i18n{mouseAndTouchpadHeading}</h2>
- <settings-toggle-button class="first"
- pref="{{prefs.settings.a11y.autoclick}}"
- label="$i18n{clickOnStopLabel}">
- </settings-toggle-button>
- <div class="settings-box continuation"
- hidden$="[[!prefs.settings.a11y.autoclick.value]]">
- <div class="start sub-item settings-box-text">
- $i18n{delayBeforeClickLabel}
- </div>
- <settings-dropdown-menu label="$i18n{delayBeforeClickLabel}"
- pref="{{prefs.settings.a11y.autoclick_delay_ms}}"
- menu-options="[[autoClickDelayOptions_]]"
- disabled="[[!prefs.settings.a11y.autoclick.value]]">
- </settings-dropdown-menu>
- </div>
- <div class="sub-item">
- <settings-toggle-button class="continuation sub-item"
- hidden$="[[!prefs.settings.a11y.autoclick.value]]"
- pref="{{prefs.settings.a11y.autoclick_stabilize_position}}"
- label="$i18n{autoclickStabilizeCursorPosition}">
- </settings-toggle-button>
- </div>
- <div class="sub-item">
- <settings-toggle-button class="continuation sub-item"
- hidden$="[[!prefs.settings.a11y.autoclick.value]]"
- pref="{{prefs.settings.a11y.autoclick_revert_to_left_click}}"
- label="$i18n{autoclickRevertToLeftClick}">
- </settings-toggle-button>
- </div>
- <div class="settings-box continuation"
- hidden$="[[!prefs.settings.a11y.autoclick.value]]">
- <div class="start sub-item settings-box-text"
- id="autoclickMovementThresholdLabel">
- $i18n{autoclickMovementThresholdLabel}
- </div>
- <settings-dropdown-menu
- aria-labelledby="autoclickMovementThresholdLabel"
- pref="{{prefs.settings.a11y.autoclick_movement_threshold}}"
- menu-options="[[autoClickMovementThresholdOptions_]]"
- disabled="[[!prefs.settings.a11y.autoclick.value]]">
- </settings-dropdown-menu>
- </div>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.large_cursor_enabled}}"
- label="$i18n{largeMouseCursorLabel}">
- </settings-toggle-button>
- <div class="settings-box continuation"
- hidden$="[[!prefs.settings.a11y.large_cursor_enabled.value]]">
- <div class="start sub-item settings-box-text">
- $i18n{largeMouseCursorSizeLabel}
- </div>
- <settings-slider
- pref="{{prefs.settings.a11y.large_cursor_dip_size}}"
- min="25" max="64"
- label-min="$i18n{largeMouseCursorSizeDefaultLabel}"
- label-max="$i18n{largeMouseCursorSizeLargeLabel}">
- </settings-slider>
- </div>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.cursor_highlight}}"
- label="$i18n{cursorHighlightLabel}">
- </settings-toggle-button>
- <cr-link-row class="hr" label="$i18n{mouseSettingsTitle}"
- on-click="onMouseTap_" sub-label="$i18n{mouseSettingsDescription}"
- embedded></cr-link-row>
-
- <h2>$i18n{audioAndCaptionsHeading}</h2>
- <cr-link-row class="first" label="$i18n{captionsTitle}"
- on-click="onCaptionsClick_"></cr-link-row>
- <settings-toggle-button
- pref="{{prefs.settings.a11y.mono_audio}}"
- label="$i18n{monoAudioLabel}">
- </settings-toggle-button>
- <settings-toggle-button id="startupSoundEnabled"
- pref=" "
- on-change="toggleStartupSoundEnabled_"
- label="$i18n{startupSoundLabel}">
- </settings-toggle-button>
-
- <template is="dom-if" if="[[!isGuest_]]">
- <a class="settings-box inherit-color no-outline" tabindex="-1"
- target="_blank"
- href="https://chrome.google.com/webstore/category/collection/accessibility">
- <div class="start settings-box-text">
- $i18n{additionalFeaturesTitle}
- <div class="secondary" id="moreFeaturesSecondary">
- $i18n{a11yWebStore}
- </div>
- </div>
- <cr-icon-button actionable class="icon-external"
- aria-label="$i18n{additionalFeaturesTitle}"
- aria-describedby="moreFeaturesSecondary"></cr-icon-button>
- </a>
- </template>
- </template>
- <script src="manage_a11y_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js b/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js
deleted file mode 100644
index 8828b4fee1a..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-manage-a11y-page' is the subpage with the accessibility
- * settings.
- */
-Polymer({
- is: 'settings-manage-a11y-page',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- screenMagnifierZoomOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- // These values correspond to the i18n values in settings_strings.grdp.
- // If these values get changed then those strings need to be changed as
- // well.
- return [
- {value: 2, name: loadTimeData.getString('screenMagnifierZoom2x')},
- {value: 4, name: loadTimeData.getString('screenMagnifierZoom4x')},
- {value: 6, name: loadTimeData.getString('screenMagnifierZoom6x')},
- {value: 8, name: loadTimeData.getString('screenMagnifierZoom8x')},
- {value: 10, name: loadTimeData.getString('screenMagnifierZoom10x')},
- {value: 12, name: loadTimeData.getString('screenMagnifierZoom12x')},
- {value: 14, name: loadTimeData.getString('screenMagnifierZoom14x')},
- {value: 16, name: loadTimeData.getString('screenMagnifierZoom16x')},
- {value: 18, name: loadTimeData.getString('screenMagnifierZoom18x')},
- {value: 20, name: loadTimeData.getString('screenMagnifierZoom20x')},
- ];
- },
- },
-
- autoClickDelayOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- // These values correspond to the i18n values in settings_strings.grdp.
- // If these values get changed then those strings need to be changed as
- // well.
- return [
- {
- value: 600,
- name: loadTimeData.getString('delayBeforeClickExtremelyShort')
- },
- {
- value: 800,
- name: loadTimeData.getString('delayBeforeClickVeryShort')
- },
- {value: 1000, name: loadTimeData.getString('delayBeforeClickShort')},
- {value: 2000, name: loadTimeData.getString('delayBeforeClickLong')},
- {
- value: 4000,
- name: loadTimeData.getString('delayBeforeClickVeryLong')
- },
- ];
- },
- },
-
- autoClickMovementThresholdOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {
- value: 5,
- name: loadTimeData.getString('autoclickMovementThresholdExtraSmall')
- },
- {
- value: 10,
- name: loadTimeData.getString('autoclickMovementThresholdSmall')
- },
- {
- value: 20,
- name: loadTimeData.getString('autoclickMovementThresholdDefault')
- },
- {
- value: 30,
- name: loadTimeData.getString('autoclickMovementThresholdLarge')
- },
- {
- value: 40,
- name: loadTimeData.getString('autoclickMovementThresholdExtraLarge')
- },
- ];
- },
- },
-
- showExperimentalSwitchAccess_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean(
- 'showExperimentalAccessibilitySwitchAccess');
- },
- },
-
- /** @private */
- isGuest_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isGuest');
- }
- },
-
- /**
- * Whether this page shown as part of OS settings.
- * TODO(crbug.com/986596): Remove this when SplitSettings is the default.
- * @private
- */
- isOSSettings_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isOSSettings');
- },
- },
-
- /**
- * |hasKeyboard_|starts undefined so observers don't trigger
- * until it has been populated.
- * @private
- */
- hasKeyboard_: Boolean,
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'has-hardware-keyboard', this.set.bind(this, 'hasKeyboard_'));
- chrome.send('initializeKeyboardWatcher');
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'startup-sound-enabled-updated',
- this.updateStartupSoundEnabled_.bind(this));
- chrome.send('getStartupSoundEnabled');
- },
-
- /**
- * Updates the Select-to-Speak description text based on:
- * 1. Whether Select-to-Speak is enabled.
- * 2. If it is enabled, whether a physical keyboard is present.
- * @param {boolean} enabled
- * @param {boolean} hasKeyboard
- * @param {string} disabledString String to show when Select-to-Speak is
- * disabled.
- * @param {string} keyboardString String to show when there is a physical
- * keyboard
- * @param {string} noKeyboardString String to show when there is no keyboard
- * @private
- */
- getSelectToSpeakDescription_: function(
- enabled, hasKeyboard, disabledString, keyboardString, noKeyboardString) {
- return !enabled ? disabledString :
- hasKeyboard ? keyboardString : noKeyboardString;
- },
-
- /**
- * @param {!CustomEvent<boolean>} e
- * @private
- */
- toggleStartupSoundEnabled_: function(e) {
- chrome.send('setStartupSoundEnabled', [e.detail]);
- },
-
- /**
- * @param {boolean} enabled
- * @private
- */
- updateStartupSoundEnabled_: function(enabled) {
- this.$.startupSoundEnabled.checked = enabled;
- },
-
- /** @private */
- onManageTtsSettingsTap_: function() {
- settings.navigateTo(settings.routes.MANAGE_TTS_SETTINGS);
- },
-
- /** @private */
- onChromeVoxSettingsTap_: function() {
- chrome.send('showChromeVoxSettings');
- },
-
- /** @private */
- onCaptionsClick_: function() {
- settings.navigateTo(settings.routes.MANAGE_CAPTION_SETTINGS);
- },
-
- /** @private */
- onSelectToSpeakSettingsTap_: function() {
- chrome.send('showSelectToSpeakSettings');
- },
-
- /** @private */
- onSwitchAccessSettingsTap_: function() {
- settings.navigateTo(settings.routes.MANAGE_SWITCH_ACCESS_SETTINGS);
- },
-
- /** @private */
- onDisplayTap_: function() {
- settings.navigateTo(
- settings.routes.DISPLAY,
- /* dynamicParams */ null, /* removeSearch */ true);
- },
-
- /** @private */
- onAppearanceTap_: function() {
- if (loadTimeData.getBoolean('isOSSettings')) {
- // Open browser appearance section in a new browser tab.
- window.open('chrome://settings/appearance');
- } else {
- // Open browser appearance in this settings window.
- // TODO(crbug.com/986596): Remove this when SplitSettings is the default.
- settings.navigateTo(
- settings.routes.APPEARANCE,
- /* dynamicParams */ null, /* removeSearch */ true);
- }
- },
-
- /** @private */
- onKeyboardTap_: function() {
- settings.navigateTo(
- settings.routes.KEYBOARD,
- /* dynamicParams */ null, /* removeSearch */ true);
- },
-
- /** @private */
- onMouseTap_: function() {
- settings.navigateTo(
- settings.routes.POINTERS,
- /* dynamicParams */ null, /* removeSearch */ true);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.html b/chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.html
deleted file mode 100644
index 6134a743a50..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-switch-access-subpage">
- <template>
- <style include="settings-shared md-select">
- h2 {
- padding-inline-start: var(--cr-section-padding);
- }
- </style>
- <h2>$i18n{switchAssignmentHeading}</h2>
- <div class="list-frame">
- <div class="settings-box continuation list-item">
- <div class="start sub-item settings-box-text">
- $i18n{assignSelectSwitchLabel}
- </div>
- <settings-dropdown-menu label="$i18n{assignSelectSwitchLabel}"
- pref="{{prefs.switch_access.select.setting}}"
- menu-options="[[switchAssignOptions_]]"
- on-settings-control-change="onSelectAssigned_">
- </settings-dropdown-menu>
- </div>
- <div class="settings-box continuation list-item">
- <div class="start sub-item settings-box-text">
- $i18n{assignNextSwitchLabel}
- </div>
- <settings-dropdown-menu label="$i18n{assignNextSwitchLabel}"
- pref="{{prefs.switch_access.next.setting}}"
- menu-options="[[switchAssignOptions_]]"
- on-settings-control-change="onNextAssigned_">
- </settings-dropdown-menu>
- </div>
- <div class="settings-box continuation list-item">
- <div class="start sub-item settings-box-text">
- $i18n{assignPreviousSwitchLabel}
- </div>
- <settings-dropdown-menu label="$i18n{assignPreviousSwitchLabel}"
- pref="{{prefs.switch_access.previous.setting}}"
- menu-options="[[switchAssignOptions_]]"
- on-settings-control-change="onPreviousAssigned_">
- </settings-dropdown-menu>
- </div>
- </div>
- <h2>$i18n{switchAccessAutoScanHeading}</h2>
- <div class="list-frame">
- <settings-toggle-button class="continuation list-item"
- pref="{{prefs.switch_access.auto_scan.enabled}}"
- label="$i18n{switchAccessAutoScanLabel}">
- </settings-toggle-button>
- <div class="settings-box continuation list-item"
- hidden$="[[!prefs.switch_access.auto_scan.enabled.value]]">
- <div class="start sub-item settings-box-text" id="scanSpeed">
- [[getLabelForSpeedSlider_(prefs.switch_access.auto_scan.speed_ms.*)]]
- </div>
- <settings-slider id="scanSpeedSlider" aria-describedby="scanSpeed"
- pref="{{prefs.switch_access.auto_scan.speed_ms}}"
- ticks="[[autoScanSpeedRangeMs_]]"
- min="[[minScanSpeedMs_]]"
- max="[[maxScanSpeedMs_]]"
- label-min="[[minScanSpeedLabelSec_]]"
- label-max="[[maxScanSpeedLabelSec_]]">
- </settings-dropdown-menu>
- </div>
- <div class="settings-box continuation list-item"
- hidden$="[[!showKeyboardScanSettings_(
- prefs.switch_access.auto_scan.enabled.value)]]">
- <div class="start sub-item settings-box-text" id="keyboardScanSpeed">
- $i18n{switchAccessAutoScanKeyboardSpeedLabel}
- </div>
- <settings-slider id="keyboardScanSpeedSlider"
- aria-describedby="keyboardScanSpeed"
- pref="{{prefs.switch_access.auto_scan.keyboard.speed_ms}}"
- ticks="[[autoScanSpeedRangeMs_]]"
- min="[[minScanSpeedMs_]]"
- max="[[maxScanSpeedMs_]]"
- label-min="[[minScanSpeedLabelSec_]]"
- label-max="[[maxScanSpeedLabelSec_]]">
- </settings-dropdown-menu>
- </div>
- </div>
- </template>
- <script src="switch_access_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.js b/chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.js
deleted file mode 100644
index cd0026e9cdd..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/switch_access_subpage.js
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'switch-access-subpage' is the collapsible section containing
- * Switch Access settings.
- */
-
-(function() {
-
-/**
- * Available switch assignment values.
- * @enum {number}
- * @const
- */
-const SwitchAccessAssignmentValue = {
- NONE: 0,
- SPACE: 1,
- ENTER: 2,
-};
-
-/** @type {!Array<number>} */
-const AUTO_SCAN_SPEED_RANGE_MS = [
- 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600,
- 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800,
- 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000
-];
-
-/**
- * @param {!Array<number>} ticksInMs
- * @return {!Array<!cr_slider.SliderTick>}
- */
-function ticksWithLabelsInSec(ticksInMs) {
- // Dividing by 1000 to convert milliseconds to seconds for the label.
- return ticksInMs.map(x => ({label: `${x / 1000}`, value: x}));
-}
-
-Polymer({
- is: 'settings-switch-access-subpage',
-
- behaviors: [
- I18nBehavior,
- PrefsBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private {Array<number>} */
- autoScanSpeedRangeMs_: {
- readOnly: true,
- type: Array,
- value: ticksWithLabelsInSec(AUTO_SCAN_SPEED_RANGE_MS),
- },
-
- /** @private {Object} */
- formatter_: {
- type: Object,
- value: function() {
- // navigator.language actually returns a locale, not just a language.
- const locale = window.navigator.language;
- const options = {minimumFractionDigits: 1, maximumFractionDigits: 1};
- return new Intl.NumberFormat(locale, options);
- },
- },
-
- /** @private {number} */
- maxScanSpeedMs_: {readOnly: true, type: Number, value: 4000},
-
- /** @private {string} */
- maxScanSpeedLabelSec_: {
- readOnly: true,
- type: String,
- value: function() {
- return this.scanSpeedStringInSec_(this.maxScanSpeedMs_);
- },
- },
-
- /** @private {number} */
- minScanSpeedMs_: {readOnly: true, type: Number, value: 500},
-
- /** @private {string} */
- minScanSpeedLabelSec_: {
- readOnly: true,
- type: String,
- value: function() {
- return this.scanSpeedStringInSec_(this.minScanSpeedMs_);
- },
- },
-
- /** @private {Array<Object>} */
- switchAssignOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {
- value: SwitchAccessAssignmentValue.NONE,
- name: this.i18n('switchAssignOptionNone')
- },
- {
- value: SwitchAccessAssignmentValue.SPACE,
- name: this.i18n('switchAssignOptionSpace')
- },
- {
- value: SwitchAccessAssignmentValue.ENTER,
- name: this.i18n('switchAssignOptionEnter')
- },
- ];
- },
- },
- },
-
- /**
- * @return {string}
- * @private
- */
- currentSpeed_: function() {
- const speed = this.get('prefs.switch_access.auto_scan.speed_ms.value');
- if (typeof speed != 'number') {
- return '';
- }
- return this.scanSpeedStringInSec_(speed);
- },
-
- /**
- * @return {string} label for the speed slider.
- * @private
- */
- getLabelForSpeedSlider_: function() {
- const speedString = this.currentSpeed_();
- return this.i18n('switchAccessAutoScanSpeedLabel', speedString);
- },
-
- /**
- * @return {boolean} Whether to show settings for auto-scan within the
- * keyboard.
- * @private
- */
- showKeyboardScanSettings_: function() {
- const improvedTextInputEnabled = loadTimeData.getBoolean(
- 'showExperimentalAccessibilitySwitchAccessImprovedTextInput');
- const autoScanEnabled = /** @type {boolean} */
- (this.getPref('switch_access.auto_scan.enabled').value);
- return improvedTextInputEnabled && autoScanEnabled;
- },
-
- /**
- * @param {string} command
- */
- onSwitchAssigned_: function(command) {
- const pref = 'prefs.switch_access.' + command;
- const keyCodeSuffix = '.key_codes.value';
- const settingSuffix = '.setting.value';
-
- switch (this.get(pref + settingSuffix)) {
- case SwitchAccessAssignmentValue.NONE:
- this.set(pref + keyCodeSuffix, []);
- break;
- case SwitchAccessAssignmentValue.SPACE:
- this.set(pref + keyCodeSuffix, [32]);
- break;
- case SwitchAccessAssignmentValue.ENTER:
- this.set(pref + keyCodeSuffix, [13]);
- break;
- }
- },
-
- onNextAssigned_: function() {
- this.onSwitchAssigned_('next');
- },
-
- onPreviousAssigned_: function() {
- this.onSwitchAssigned_('previous');
- },
-
- onSelectAssigned_: function() {
- this.onSwitchAssigned_('select');
- },
-
- /**
- * @param {number} scanSpeedValueMs
- * @return {string} a string representing the scan speed in seconds.
- * @private
- */
- scanSpeedStringInSec_: function(scanSpeedValueMs) {
- const scanSpeedValueSec = scanSpeedValueMs / 1000;
- return this.i18n(
- 'durationInSeconds', this.formatter_.format(scanSpeedValueSec));
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.html b/chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.html
deleted file mode 100644
index 6d66dfe0934..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.html
+++ /dev/null
@@ -1,168 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../languages_page/languages_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-tts-subpage">
- <template>
- <style include="settings-shared md-select">
- h2 {
- padding-inline-start: var(--cr-section-padding);
- }
-
- .settings-box {
- margin-inline-end: var(--cr-section-padding);
- margin-inline-start: var(--cr-section-indent-padding);
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
-
- #previewInput {
- width: 100%;
- --cr-input-error-display: none;
- }
-
- #previewInput cr-button {
- margin-inline-start: 8px;
- }
- </style>
-
- <h2>$i18n{textToSpeechProperties}</h2>
- <div class="settings-box first">
- <div class="start settings-box-text" id="rate">
- $i18n{textToSpeechRate}
- </div>
- <settings-slider show-markers
- pref="{{prefs.settings.tts.speech_rate}}"
- ticks="[[speechRateTicks_()]]"
- label-min="$i18n{textToSpeechRateMinimumLabel}"
- label-max="$i18n{textToSpeechRateMaximumLabel}"
- aria-describedby="rate">
- </settings-slider>
- </div>
- <div class="settings-box continuation">
- <div class="start settings-box-text" id="pitch">
- $i18n{textToSpeechPitch}
- </div>
- <settings-slider show-markers
- pref="{{prefs.settings.tts.speech_pitch}}"
- ticks="[[speechPitchTicks_()]]"
- label-min="$i18n{textToSpeechPitchMinimumLabel}"
- label-max="$i18n{textToSpeechPitchMaximumLabel}"
- aria-describedby="pitch">
- </settings-slider>
- </div>
- <div class="settings-box continuation">
- <div class="start settings-box-text" id="volume">
- $i18n{textToSpeechVolume}
- </div>
- <settings-slider show-markers
- pref="{{prefs.settings.tts.speech_volume}}"
- ticks="[[speechVolumeTicks_()]]"
- label-min="$i18n{textToSpeechVolumeMinimumLabel}"
- label-max="$i18n{textToSpeechVolumeMaximumLabel}"
- aria-describedby="volume">
- </settings-slider>
- </div>
-
- <h2>$i18n{textToSpeechPreviewHeading}</h2>
- <div class="settings-box first">
- <div class="start settings-box-text">
- $i18n{textToSpeechPreviewVoice}
- </div>
- <select id="previewVoice" class="md-select"
- label="$i18n{textToSpeechPreviewVoice}"
- value="{{defaultPreviewVoice}}"
- disabled="[[!hasVoices]]">
- <template is="dom-repeat" id="previewVoiceOptions"
- items="[[allVoices]]" as="voice">
- <!-- TODO: Use a combobox with a search field instead. -->
- <option value="[[voice.id]]">
- [[voice.displayLanguage]] - [[voice.name]]
- </option>
- </template>
- </select>
- </div>
- <div class="settings-box continuation">
- <cr-input id="previewInput" label="$i18n{textToSpeechPreviewInputLabel}"
- value="{{previewText_}}"
- disabled="[[isPreviewing_]]">
- <cr-button on-click="onPreviewTtsClick_"
- disabled$="[[!enablePreviewButton_(allVoices, isPreviewing_,
- previewText_)]]"
- slot="suffix">
- $i18n{textToSpeechPreviewPlay}
- </cr-button>
- </cr-input>
- </div>
-
- <h2>$i18n{textToSpeechVoices}</h2>
- <template is="dom-repeat" items="[[languagesToVoices]]" as="lang"
- filter="isPrimaryLanguage_"
- sort="alphabeticalSort_">
- <div class="settings-box first">
- <div class="start settings-box-text">
- [[lang.language]]
- </div>
- <settings-dropdown-menu label="[[lang.language]]"
- pref="{{prefs.settings.tts.lang_to_voice_name}}"
- pref-key="[[lang.code]]"
- menu-options="[[menuOptionsForLang_(lang)]]"
- disabled="[[hasOneLanguage_(lang)]]"
- on-settings-control-change="onDefaultTtsVoicePicked_">
- </settings-dropdown-menu>
- </div>
- </template>
- <cr-expand-button
- alt="$i18n{textToSpeechMoreLanguages}"
- class="settings-box continuation"
- expanded="{{languagesOpened}}"
- hidden="[[!hasVoices]]">
- $i18n{textToSpeechMoreLanguages}
- </cr-expand-button>
- <iron-collapse id="moreLanguages" opened="[[languagesOpened]]">
- <template is="dom-repeat" items="[[languagesToVoices]]"
- as="lang" sort="alphabeticalSort_" filter="isSecondaryLanguage_">
- <div class="settings-box continuation">
- <div class="start settings-box-text">
- [[lang.language]]
- </div>
- <settings-dropdown-menu label="[[lang.language]]"
- pref="{{prefs.settings.tts.lang_to_voice_name}}"
- pref-key="[[lang.code]]"
- menu-options="[[menuOptionsForLang_(lang)]]"
- disabled="[[hasOneLanguage_(lang)]]"
- on-settings-control-change="onDefaultTtsVoicePicked_">
- </settings-dropdown-menu>
- </div>
- </template>
- </iron-collapse>
- <div class="settings-box first settings-box-text" hidden$="[[hasVoices]]">
- $i18n{textToSpeechNoVoicesMessage}
- </div>
-
- <h2>$i18n{textToSpeechEngines}</h2>
- <template is="dom-repeat" items="[[extensions]]" as="extension">
- <div class="settings-box continuation">
- <div id="extension_name_[[index]]" class="start settings-box-text">
- [[extension.name]]
- </div>
- <cr-button on-click="onEngineSettingsTap_"
- aria-describedby$="extension_name_[[index]]"
- hidden$="[[!extension.optionsPage]]">
- $i18n{settings}
- </cr-button>
- </div>
- </template>
- </template>
- <script src="tts_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.js b/chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.js
deleted file mode 100644
index 51619b41d16..00000000000
--- a/chromium/chrome/browser/resources/settings/a11y_page/tts_subpage.js
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'tts-subpage' is the collapsible section containing
- * text-to-speech settings.
- */
-
-Polymer({
- is: 'settings-tts-subpage',
-
- behaviors: [WebUIListenerBehavior, I18nBehavior],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Available languages.
- * @type {Array<{language: string, code: string, preferred: boolean,
- * voice: TtsHandlerVoice}>}
- */
- languagesToVoices: {
- type: Array,
- notify: true,
- },
-
- /**
- * All voices.
- * @type {Array<TtsHandlerVoice>}
- */
- allVoices: {
- type: Array,
- value: [],
- notify: true,
- },
-
- /**
- * Default preview voice.
- */
- defaultPreviewVoice: {
- type: String,
- notify: true,
- },
-
- /**
- * Whether preview is currently speaking.
- * @private
- */
- isPreviewing_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- previewText_: {
- type: String,
- value: '',
- },
-
- /** Whether any voices are loaded. */
- hasVoices: {
- type: Boolean,
- computed: 'hasVoices_(allVoices)',
- },
-
- /** Whether the additional languages section has been opened. */
- languagesOpened: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @override */
- ready: function() {
- // Populate the preview text with textToSpeechPreviewInput. Users can change
- // this to their own value later.
- this.previewText_ = this.i18n('textToSpeechPreviewInput');
- this.addWebUIListener(
- 'all-voice-data-updated', this.populateVoiceList_.bind(this));
- chrome.send('getAllTtsVoiceData');
- this.addWebUIListener(
- 'tts-extensions-updated', this.populateExtensionList_.bind(this));
- this.addWebUIListener(
- 'tts-preview-state-changed', this.onTtsPreviewStateChanged_.bind(this));
- chrome.send('getTtsExtensions');
- },
-
- /**
- * Ticks for the Speech Rate slider. Non-linear as we expect people
- * to want more control near 1.0.
- * @return Array<cr_slider.SliderTick>
- * @private
- */
- speechRateTicks_: function() {
- return Array.from(Array(16).keys()).map(x => {
- return x <= 4 ?
- // Linear from rates 0.6 to 1.0
- this.initTick_(x / 10 + .6) :
- // Power function above 1.0 gives more control at lower values.
- this.initTick_(Math.pow(x - 3, 2) / 20 + 1);
- });
- },
-
- /**
- * Ticks for the Speech Pitch slider. Valid pitches are between 0 and 2,
- * exclusive of 0.
- * @return Array<cr_slider.SliderTick>
- * @private
- */
- speechPitchTicks_: function() {
- return Array.from(Array(10).keys()).map(x => {
- return this.initTick_(x * .2 + .2);
- });
- },
-
- /**
- * Ticks for the Speech Volume slider. Valid volumes are between 0 and
- * 1 (100%), but volumes lower than .2 are excluded as being too quiet.
- * The values are linear between .2 and 1.0.
- * @return Array<cr_slider.SliderTick>
- * @private
- */
- speechVolumeTicks_: function() {
- return Array.from(Array(9).keys()).map(x => {
- return this.initTick_(x * .1 + .2);
- });
- },
-
- /**
- * Initializes i18n labels for ticks arrays.
- * @param {number} tick The value to make a tick for.
- * @return {cr_slider.SliderTick}
- * @private
- */
- initTick_: function(tick) {
- const value = Math.round(100 * tick);
- const strValue = value.toFixed(0);
- const label = strValue === '100' ?
- this.i18n('defaultPercentage', strValue) :
- this.i18n('percentage', strValue);
- return {label: label, value: tick, ariaValue: value};
- },
-
- /**
- * Returns true if any voices are loaded.
- * @param {!Array<TtsHandlerVoice>} voices
- * @return {boolean}
- * @private
- */
- hasVoices_: function(voices) {
- return voices.length > 0;
- },
-
- /**
- * Returns true if voices are loaded and preview is not currently speaking and
- * there is text to preview.
- * @param {!Array<TtsHandlerVoice>} voices
- * @param {boolean} isPreviewing
- * @param {boolean} previewText
- * @return {boolean}
- * @private
- */
- enablePreviewButton_: function(voices, isPreviewing, previewText) {
- const nonWhitespaceRe = /\S+/;
- const hasPreviewText = nonWhitespaceRe.exec(previewText) != null;
- return this.hasVoices_(voices) && !isPreviewing && hasPreviewText;
- },
-
- /**
- * Populates the list of languages and voices for the UI to use in display.
- * @param {Array<TtsHandlerVoice>} voices
- * @private
- */
- populateVoiceList_: function(voices) {
- // Build a map of language code to human-readable language and voice.
- const result = {};
- const languageCodeMap = {};
- const pref = this.prefs.settings['language']['preferred_languages'];
- const preferredLangs = pref.value.split(',');
- voices.forEach(voice => {
- if (!result[voice.languageCode]) {
- result[voice.languageCode] = {
- language: voice.displayLanguage,
- code: voice.languageCode,
- preferred: false,
- voices: []
- };
- }
- // Each voice gets a unique ID from its name and extension.
- voice.id =
- JSON.stringify({name: voice.name, extension: voice.extensionId});
- // TODO(katie): Make voices a map rather than an array to enforce
- // uniqueness, then convert back to an array for polymer repeat.
- result[voice.languageCode].voices.push(voice);
-
- // A language is "preferred" if it has a voice that uses the default
- // locale of the device.
- result[voice.languageCode].preferred =
- result[voice.languageCode].preferred ||
- preferredLangs.indexOf(voice.fullLanguageCode) != -1;
- languageCodeMap[voice.fullLanguageCode] = voice.languageCode;
- });
- this.updateLangToVoicePrefs_(result);
- this.set('languagesToVoices', Object.values(result));
- this.set('allVoices', voices);
- this.setDefaultPreviewVoiceForLocale_(voices, languageCodeMap);
- },
-
- /**
- * Returns true if the language is a primary language and should be shown by
- * default, false if it should be hidden by default.
- * @param {{language: string, code: string, preferred: boolean,
- * voice: TtsHandlerVoice}} language
- * @return {boolean} true if it's a primary language.
- */
- isPrimaryLanguage_: function(language) {
- return language.preferred;
- },
-
- /**
- * Returns true if the language is a secondary language and should be hidden
- * by default, true if it should be shown by default.
- * @param {{language: string, code: string, preferred: boolean,
- * voice: TtsHandlerVoice}} language
- * @return {boolean} true if it's a secondary language.
- */
- isSecondaryLanguage_: function(language) {
- return !language.preferred;
- },
-
- /**
- * Sets the list of Text-to-Speech extensions for the UI.
- * @param {Array<TtsHandlerExtension>} extensions
- * @private
- */
- populateExtensionList_: function(extensions) {
- this.extensions = extensions;
- },
-
- /**
- * Called when the TTS voice preview state changes between speaking and not
- * speaking.
- * @param {boolean} isSpeaking
- * @private
- */
- onTtsPreviewStateChanged_: function(isSpeaking) {
- this.isPreviewing_ = isSpeaking;
- },
-
- /**
- * A function used for sorting languages alphabetically.
- * @param {Object} first A languageToVoices array item.
- * @param {Object} second A languageToVoices array item.
- * @return {number} The result of the comparison.
- * @private
- */
- alphabeticalSort_: function(first, second) {
- return first.language.localeCompare(second.language);
- },
-
- /**
- * Tests whether a language has just once voice.
- * @param {Object} lang A languageToVoices array item.
- * @return {boolean} True if the item has only one voice.
- * @private
- */
- hasOneLanguage_: function(lang) {
- return lang['voices'].length == 1;
- },
-
- /**
- * Returns a list of objects that can be used as drop-down menu options for a
- * language. This is a list of voices in that language.
- * @param {Object} lang A languageToVoices array item.
- * @return {Array<Object>} An array of menu options with a value and name.
- * @private
- */
- menuOptionsForLang_: function(lang) {
- return lang.voices.map(voice => {
- return {value: voice.id, name: voice.name};
- });
- },
-
- /**
- * Updates the preferences given the current list of voices.
- * @param {Object<string, {language: string, code: string, preferred: boolean,
- * voices: Array<TtsHandlerVoice>}>} langToVoices
- * @private
- */
- updateLangToVoicePrefs_: function(langToVoices) {
- if (langToVoices.length == 0) {
- return;
- }
- const allCodes = new Set(
- Object.keys(this.prefs.settings['tts']['lang_to_voice_name'].value));
- for (const code in langToVoices) {
- // Remove from allCodes, to track what we've found a default for.
- allCodes.delete(code);
- const voices = langToVoices[code].voices;
- const defaultVoiceForLang =
- this.prefs.settings['tts']['lang_to_voice_name'].value[code];
- if (!defaultVoiceForLang || defaultVoiceForLang === '') {
- // Initialize prefs that have no value
- this.set(
- 'prefs.settings.tts.lang_to_voice_name.value.' + code,
- this.getBestVoiceForLocale_(voices));
- continue;
- }
- // See if the set voice ID is in the voices list, in which case we are
- // done checking this language.
- if (voices.some(voice => voice.id === defaultVoiceForLang)) {
- continue;
- }
- // Change prefs that point to voices that no longer exist.
- this.set(
- 'prefs.settings.tts.lang_to_voice_name.value.' + code,
- this.getBestVoiceForLocale_(voices));
- }
- // If there are any items left in allCodes, they are for languages that are
- // no longer covered by the UI. We could now delete them from the
- // lang_to_voice_name pref.
- for (const code of allCodes) {
- this.set('prefs.settings.tts.lang_to_voice_name.value.' + code, '');
- }
- },
-
- /**
- * Sets the voice to show in the preview drop-down as default, based on the
- * current locale and voice preferences.
- * @param {Array<TtsHandlerVoice>} allVoices
- * @param {Object<string, string>} languageCodeMap Mapping from language code
- * to simple language code without locale.
- * @private
- */
- setDefaultPreviewVoiceForLocale_: function(allVoices, languageCodeMap) {
- if (!allVoices || allVoices.length == 0) {
- return;
- }
-
- // Force a synchronous render so that we can set the default.
- this.$.previewVoiceOptions.render();
-
- // Set something if nothing exists. This useful for new users where
- // sometimes browserProxy.getProspectiveUILanguage() does not complete the
- // callback.
- if (!this.defaultPreviewVoice) {
- this.set('defaultPreviewVoice', this.getBestVoiceForLocale_(allVoices));
- }
-
- const browserProxy = settings.LanguagesBrowserProxyImpl.getInstance();
- browserProxy.getProspectiveUILanguage().then(prospectiveUILanguage => {
- let result;
- if (prospectiveUILanguage && prospectiveUILanguage != '' &&
- languageCodeMap[prospectiveUILanguage]) {
- const code = languageCodeMap[prospectiveUILanguage];
- // First try the pref value.
- result = this.prefs.settings['tts']['lang_to_voice_name'].value[code];
- }
- if (!result) {
- // If it's not a pref value yet, or the prospectiveUILanguage was
- // missing, try using the voice score.
- result = this.getBestVoiceForLocale_(allVoices);
- }
- this.set('defaultPreviewVoice', result);
- });
- },
-
- /**
- * Gets the best voice for the app locale.
- * @param {Array<TtsHandlerVoice>} voices Voices to search through.
- * @return {string} The ID of the best matching voice in the array.
- * @private
- */
- getBestVoiceForLocale_: function(voices) {
- let bestScore = -1;
- let bestVoice = '';
- voices.forEach((voice) => {
- if (voice.languageScore > bestScore) {
- bestScore = voice.languageScore;
- bestVoice = voice.id;
- }
- });
- return bestVoice;
- },
-
- /** @private */
- onPreviewTtsClick_: function() {
- chrome.send(
- 'previewTtsVoice', [this.previewText_, this.$.previewVoice.value]);
- chrome.metricsPrivate.recordSparseHashable(
- 'TextToSpeech.Settings.PreviewVoiceClicked', this.$.previewVoice.value);
- },
-
- /** @private */
- onDefaultTtsVoicePicked_: function(event) {
- // Log the default voice the user selected. Each voice has at most one
- // language, so there's no need to log language as well.
- // The event target is the settings-dropdown-menu.
- const target = /** @type {{prefStringValue_: function():string}} */
- (event.target);
- const newDefault = target.prefStringValue_();
- chrome.metricsPrivate.recordSparseHashable(
- 'TextToSpeech.Settings.DefaultVoicePicked', newDefault);
- },
-
- /**
- * @param {{model:Object}} event
- * @private
- */
- onEngineSettingsTap_: function(event) {
- chrome.send('wakeTtsEngine');
- window.open(event.model.extension.optionsPage);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/about_page/about_page.html b/chromium/chrome/browser/resources/settings/about_page/about_page.html
deleted file mode 100644
index 99c0a1c19a1..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/about_page.html
+++ /dev/null
@@ -1,315 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="about_page_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../lifetime_browser_proxy.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/main_page_behavior.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_section.html">
-<link rel="import" href="../settings_page_css.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-
-<if expr="chromeos">
-<link rel="import" href="detailed_build_info.html">
-<link rel="import" href="update_warning_dialog.html">
-<link rel="import" href="../chromeos/os_icons.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../reset_page/powerwash_dialog.html">
-</if>
-
-<dom-module id="settings-about-page">
- <template>
- <style include="settings-shared settings-page-styles">
- :host {
- --about-page-image-space: 10px;
- }
-
- .info-section {
- margin-bottom: 12px;
- }
-
- .padded {
- padding-bottom: 10px;
- padding-top: 10px;
- }
-
- .product-title {
- font-size: 153.85%; /* 20px / 13px */
- font-weight: 400;
- margin-bottom: auto;
- margin-top: auto;
- }
-
- img {
- margin-inline-end: var(--about-page-image-space);
- }
-
- .icon-container {
- margin-inline-end: var(--about-page-image-space);
- min-width: 32px; /* The width of the product-logo img. */
- text-align: center;
- }
-
- iron-icon[icon='settings:check-circle'] {
- fill: var(--cr-checked-color);
- }
-
- iron-icon[icon='cr:error'] {
- fill: var(--settings-error-color);
- }
-
- .settings-box .start {
- overflow-x: auto;
- }
-
- cr-button {
- white-space: nowrap;
- }
-
- #regulatoryInfo img {
- width: 330px;
- }
-
-<if expr="_google_chrome and is_macosx">
- #promoteUpdater[disabled] {
- @apply --cr-secondary-text;
- }
-</if>
- </style>
- <settings-section page-title="$i18n{aboutPageTitle}" section="about">
- <settings-animated-pages id="pages" section="about"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <div class="settings-box two-line first">
- <img id="product-logo" on-click="onProductLogoTap_"
- srcset="chrome://theme/current-channel-logo@1x 1x,
- chrome://theme/current-channel-logo@2x 2x"
- alt="$i18n{aboutProductLogoAlt}">
- <h1 class="product-title">$i18n{aboutProductTitle}</h1>
- </div>
- <div class="settings-box two-line">
- <!-- TODO(dpapad): Investigate why vulcanize does not handle well
- a new line after "getThrobberSrcIfUpdating_(", causes incorrect
- src URL -->
- <!-- Set the icon from the iconset (when it's obsolete/EOL and
- when update is done) or set the src (when it's updating). -->
-<if expr="not chromeos">
- <div class="icon-container"
- hidden="[[!shouldShowIcons_(showUpdateStatus_)]]">
- <iron-icon
- icon$="[[getUpdateStatusIcon_(
- obsoleteSystemInfo_, currentUpdateStatusEvent_)]]"
- src="[[getThrobberSrcIfUpdating_(obsoleteSystemInfo_, currentUpdateStatusEvent_)]]">
-</if>
-<if expr="chromeos">
- <div class="icon-container"
- hidden="[[!shouldShowIcons_(showUpdateStatus_,
- showOsSettings_)]]">
- <iron-icon
- icon$="[[getUpdateStatusIcon_(
- hasEndOfLife_, currentUpdateStatusEvent_)]]"
- src="[[getThrobberSrcIfUpdating_(hasEndOfLife_, currentUpdateStatusEvent_)]]">
-</if>
- </iron-icon>
- </div>
- <div class="start padded">
- <div id="updateStatusMessage" hidden="[[!showUpdateStatus_]]">
- <div
-<if expr="not chromeos">
- inner-h-t-m-l="[[getUpdateStatusMessage_(
- currentUpdateStatusEvent_)]]">
-</if>
-<if expr="chromeos">
- inner-h-t-m-l="[[getUpdateStatusMessage_(
- currentUpdateStatusEvent_, targetChannel_)]]">
-</if>
- </div>
- <a hidden$="[[!shouldShowLearnMoreLink_(
- currentUpdateStatusEvent_)]]" target="_blank"
- href="https://support.google.com/chrome?p=update_error">
- $i18n{learnMore}
- </a>
- </div>
-<if expr="not chromeos">
- <span id="deprecationWarning"
- hidden="[[!obsoleteSystemInfo_.obsolete]]">
- $i18n{aboutObsoleteSystem}
- <a href="$i18n{aboutObsoleteSystemURL}" target="_blank">
- $i18n{learnMore}
- </a>
- </span>
-</if>
-<if expr="chromeos">
- <div id="endOfLifeMessageContainer" hidden="[[!hasEndOfLife_]]">
- $i18n{endOfLifeMessage}
- <a href="$i18n{endOfLifeLearnMoreURL}" target="_blank">
- $i18n{learnMore}
- </a>
- </div>
-</if>
- <div class="secondary">$i18n{aboutBrowserVersion}</div>
-<if expr="chromeos">
- <div class="secondary"
- inner-h-t-m-l="[[getUpdateOsSettingsLink_()]]">
- </div>
-</if>
- </div>
- <div class="separator" hidden="[[!showButtonContainer_]]"></div>
- <span id="buttonContainer" hidden="[[!showButtonContainer_]]">
- <cr-button id="relaunch" hidden="[[!showRelaunch_]]"
- on-click="onRelaunchTap_">
- $i18n{aboutRelaunch}
- </cr-button>
-<if expr="chromeos">
- <cr-button id="relaunchAndPowerwash"
- hidden="[[!showRelaunchAndPowerwash_]]"
- on-click="onRelaunchAndPowerwashTap_">
- $i18n{aboutRelaunchAndPowerwash}
- </cr-button>
- <cr-button id="checkForUpdates" hidden="[[!showCheckUpdates_]]"
- on-click="onCheckUpdatesTap_">
- $i18n{aboutCheckForUpdates}
- </cr-button>
-</if>
- </span>
- </div>
-<if expr="chromeos">
- <cr-link-row
- id="aboutTPMFirmwareUpdate"
- class="hr"
- hidden$="[[!showTPMFirmwareUpdateLineItem_]]"
- label="$i18n{aboutTPMFirmwareUpdateTitle}"
- on-click="onTPMFirmwareUpdateTap_">
- <div slot="sub-label">
- $i18n{aboutTPMFirmwareUpdateDescription}
- <a href="$i18n{aboutTPMFirmwareUpdateLearnMoreURL}"
- target="_blank" on-click="onLearnMoreTap_">
- $i18n{learnMore}
- </a>
- </div>
- </cr-link-row>
-</if>
-<if expr="_google_chrome and is_macosx">
- <template is="dom-if" if="[[!promoteUpdaterStatus_.hidden]]">
- <div id="promoteUpdater" class="settings-box"
- disabled$="[[promoteUpdaterStatus_.disabled]]"
- actionable$="[[promoteUpdaterStatus_.actionable]]"
- on-click="onPromoteUpdaterTap_">
- <div class="start">
- [[promoteUpdaterStatus_.text]]
- <a href="https://support.google.com/chrome/answer/95414"
- target="_blank" id="updaterLearnMore"
- on-click="onLearnMoreTap_">
- $i18n{learnMore}
- </a>
- </div>
- <cr-icon-button class="subpage-arrow"
- hidden$="[[!promoteUpdaterStatus_.actionable]]"
- disabled="[[promoteUpdaterStatus_.disabled]]"
- aria-label$="[[promoteUpdaterStatus_.text]]"></cr-icon-button>
- </div>
- </template>
-</if>
-<if expr="chromeos">
- <template is="dom-if" if="[[hasReleaseNotes_]]">
- <cr-link-row class="hr" id="releaseNotesOnline"
- hidden="[[!hasInternetConnection_]]"
- on-click="onReleaseNotesTap_"
- label="$i18n{aboutShowReleaseNotes}" external></cr-link-row>
- <cr-link-row class="hr" id="releaseNotesOffline"
- hidden="[[hasInternetConnection_]]"
- on-click="onReleaseNotesTap_"
- label="$i18n{aboutShowReleaseNotes}"
- title="$i18n{aboutReleaseNotesOffline}" external></cr-link-row>
- </template>
-</if>
- <cr-link-row class="hr" id="help" on-click="onHelpTap_"
- label="$i18n{aboutGetHelpUsingChrome}" external></cr-link-row>
-<if expr="_google_chrome">
- <cr-link-row class="hr" id="reportIssue" on-click="onReportIssueTap_"
- hidden="[[!prefs.feedback_allowed.value]]"
- label="$i18n{aboutReportAnIssue}" external></cr-link-row>
-</if>
-<if expr="chromeos">
- <cr-link-row class="hr" id="detailed-build-info-trigger"
- on-click="onDetailedBuildInfoTap_"
- label="$i18n{aboutDetailedBuildInfo}"
- hidden$="[[!showOsSettings_]]">
- </cr-link-row>
-</if>
- <cr-link-row class="hr" on-click="onManagementPageTap_"
- start-icon="cr:domain" label="$i18n{managementPage}"
- hidden$="[[!isManaged_]]"></cr-link-row>
- </div>
-<if expr="chromeos">
- <template is="dom-if" route-path="/help/details">
- <settings-subpage page-title="$i18n{aboutDetailedBuildInfo}">
- <settings-detailed-build-info></settings-detailed-build-info>
- </settings-subpage>
- </template>
-</if>
- </settings-animated-pages>
- </settings-section>
-
- <settings-section>
- <div class="settings-box padded block first">
- <div class="info-section">
- <div class="secondary">$i18n{aboutProductTitle}</div>
- <div class="secondary">$i18n{aboutProductCopyright}</div>
- </div>
-
- <div class="info-section">
- <div class="secondary">$i18nRaw{aboutProductLicense}</div>
-<if expr="chromeos">
- <div id="crostiniLicense" class="secondary"
- inner-h-t-m-l="[[getAboutProductOsLicense_(
- showCrostiniLicense_)]]" hidden$="[[!showOsSettings_]]"></div>
-</if>
- </div>
-<if expr="_google_chrome">
- <div class="secondary">$i18nRaw{aboutProductTos}</div>
-</if>
- </div>
-<if expr="chromeos">
- <div class="settings-box padded block" id="regulatoryInfo"
- hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_,
- showOsSettings_)]]">
-<if expr="_google_chrome">
- <div class="secondary" hidden$="[[!shouldShowSafetyInfo_()]]">
- <a target="_blank" href="$i18n{aboutProductSafetyURL}">
- $i18nRaw{aboutProductSafety}
- </a>
- </div>
-</if>
- <img src="[[regulatoryInfo_.url]]" alt="[[regulatoryInfo_.text]]"
- hidden$="[[!shouldShowRegulatoryInfo_(regulatoryInfo_)]]">
- </div>
-</if>
- </settings-section>
-<if expr="chromeos">
- <template is="dom-if" if="[[showUpdateWarningDialog_]]" restamp>
- <settings-update-warning-dialog update-info="[[updateInfo_]]"
- on-close="onUpdateWarningDialogClose_">
- </settings-update-warning-dialog>
- </template>
- <template is="dom-if" if="[[showTPMFirmwareUpdateDialog_]]"
- restamp>
- <settings-powerwash-dialog request-tpm-firmware-update
- on-close="onPowerwashDialogClose_">
- </settings-powerwash-dialog>
- </template>
-</if>
- </template>
- <script src="about_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/about_page/about_page.js b/chromium/chrome/browser/resources/settings/about_page/about_page.js
deleted file mode 100644
index cdb845116be..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/about_page.js
+++ /dev/null
@@ -1,745 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-about-page' contains version and OS related
- * information.
- */
-
-Polymer({
- is: 'settings-about-page',
-
- behaviors: [
- WebUIListenerBehavior,
- settings.MainPageBehavior,
- settings.RouteObserverBehavior,
- I18nBehavior,
- ],
-
- properties: {
- /** @private {?UpdateStatusChangedEvent} */
- currentUpdateStatusEvent_: {
- type: Object,
- value: {
- message: '',
- progress: 0,
- rollback: false,
- status: UpdateStatus.DISABLED
- },
- },
-
- /**
- * Whether the browser/ChromeOS is managed by their organization
- * through enterprise policies.
- * @private
- */
- isManaged_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isManaged');
- },
- },
-
- // <if expr="chromeos">
- /** @private */
- hasCheckedForUpdates_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {!BrowserChannel} */
- currentChannel_: String,
-
- /** @private {!BrowserChannel} */
- targetChannel_: String,
-
- /** @private {?RegulatoryInfo} */
- regulatoryInfo_: Object,
-
- /** @private */
- hasEndOfLife_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- hasReleaseNotes_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showCrostini: Boolean,
-
- /**
- * When the SplitSettings feature is disabled, the about page shows the OS-
- * specific parts. When SplitSettings is enabled, the OS-specific parts
- * will only show up in chrome://os-settings/help.
- * TODO(aee): remove after SplitSettings feature flag is removed.
- * @private
- */
- showOsSettings_: {
- type: Boolean,
- value: () => loadTimeData.getBoolean('showOSSettings'),
- },
-
- /** @private */
- showCrostiniLicense_: {
- type: Boolean,
- value: false,
- },
- // </if>
-
- /** @private */
- hasInternetConnection_: {
- type: Boolean,
- value: false,
- },
-
- // <if expr="_google_chrome and is_macosx">
- /** @private {!PromoteUpdaterStatus} */
- promoteUpdaterStatus_: Object,
- // </if>
-
- // <if expr="not chromeos">
- /** @private {!{obsolete: boolean, endOfLine: boolean}} */
- obsoleteSystemInfo_: {
- type: Object,
- value: function() {
- return {
- obsolete: loadTimeData.getBoolean('aboutObsoleteNowOrSoon'),
- endOfLine: loadTimeData.getBoolean('aboutObsoleteEndOfTheLine'),
- };
- },
- },
- // </if>
-
- /** @private */
- showUpdateStatus_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showButtonContainer_: Boolean,
-
- /** @private */
- showRelaunch_: {
- type: Boolean,
- value: false,
- },
-
- // <if expr="chromeos">
- /** @private */
- showRelaunchAndPowerwash_: {
- type: Boolean,
- value: false,
- computed: 'computeShowRelaunchAndPowerwash_(' +
- 'currentUpdateStatusEvent_, targetChannel_, currentChannel_)',
- },
-
- /** @private */
- showCheckUpdates_: {
- type: Boolean,
- computed: 'computeShowCheckUpdates_(' +
- 'currentUpdateStatusEvent_, hasCheckedForUpdates_, hasEndOfLife_)',
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.DETAILED_BUILD_INFO) {
- map.set(
- settings.routes.DETAILED_BUILD_INFO.path,
- '#detailed-build-info-trigger');
- }
- return map;
- },
- },
-
- /** @private */
- showUpdateWarningDialog_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showTPMFirmwareUpdateLineItem_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showTPMFirmwareUpdateDialog_: Boolean,
-
- /** @private {!AboutPageUpdateInfo|undefined} */
- updateInfo_: Object,
- // </if>
- },
-
- observers: [
- // <if expr="not chromeos">
- 'updateShowUpdateStatus_(' +
- 'obsoleteSystemInfo_, currentUpdateStatusEvent_)',
- 'updateShowRelaunch_(currentUpdateStatusEvent_)',
- 'updateShowButtonContainer_(showRelaunch_)',
- // </if>
-
- // <if expr="chromeos">
- 'updateShowUpdateStatus_(' +
- 'hasEndOfLife_, currentUpdateStatusEvent_,' +
- 'hasCheckedForUpdates_)',
- 'updateShowRelaunch_(currentUpdateStatusEvent_, targetChannel_,' +
- 'currentChannel_)',
- 'updateShowButtonContainer_(' +
- 'showRelaunch_, showRelaunchAndPowerwash_, showCheckUpdates_)',
- 'handleCrostiniEnabledChanged_(prefs.crostini.enabled.value)',
- // </if>
- ],
-
- /** @private {?settings.AboutPageBrowserProxy} */
- aboutBrowserProxy_: null,
-
- /** @private {?settings.LifetimeBrowserProxy} */
- lifetimeBrowserProxy_: null,
-
- /** @override */
- attached: function() {
- this.aboutBrowserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance();
- this.aboutBrowserProxy_.pageReady();
-
- this.lifetimeBrowserProxy_ =
- settings.LifetimeBrowserProxyImpl.getInstance();
-
- // <if expr="chromeos">
- if (!this.showOsSettings_) {
- return;
- }
-
- this.addEventListener('target-channel-changed', e => {
- this.targetChannel_ = e.detail;
- });
-
- this.aboutBrowserProxy_.getChannelInfo().then(info => {
- this.currentChannel_ = info.currentChannel;
- this.targetChannel_ = info.targetChannel;
- this.startListening_();
- });
-
- this.aboutBrowserProxy_.getRegulatoryInfo().then(info => {
- this.regulatoryInfo_ = info;
- });
-
- this.aboutBrowserProxy_.getEndOfLifeInfo().then(result => {
- this.hasEndOfLife_ = !!result.hasEndOfLife;
- });
-
- this.aboutBrowserProxy_.getEnabledReleaseNotes().then(result => {
- this.hasReleaseNotes_ = result;
- });
-
- this.aboutBrowserProxy_.checkInternetConnection().then(result => {
- this.hasInternetConnection_ = result;
- });
-
- // </if>
- // <if expr="not chromeos">
- this.startListening_();
- // </if>
- if (settings.getQueryParameters().get('checkForUpdate') == 'true') {
- this.onCheckUpdatesTap_();
- }
- },
-
- /**
- * @param {!settings.Route} newRoute
- * @param {settings.Route} oldRoute
- */
- currentRouteChanged: function(newRoute, oldRoute) {
- settings.MainPageBehavior.currentRouteChanged.call(
- this, newRoute, oldRoute);
- },
-
- // Override settings.MainPageBehavior method.
- containsRoute: function(route) {
- return !route || settings.routes.ABOUT.contains(route);
- },
-
- /** @private */
- startListening_: function() {
- this.addWebUIListener(
- 'update-status-changed', this.onUpdateStatusChanged_.bind(this));
- // <if expr="_google_chrome and is_macosx">
- this.addWebUIListener(
- 'promotion-state-changed',
- this.onPromoteUpdaterStatusChanged_.bind(this));
- // </if>
- this.aboutBrowserProxy_.refreshUpdateStatus();
- // <if expr="chromeos">
- this.addWebUIListener(
- 'tpm-firmware-update-status-changed',
- this.onTPMFirmwareUpdateStatusChanged_.bind(this));
- this.aboutBrowserProxy_.refreshTPMFirmwareUpdateStatus();
- // </if>
- },
-
- /**
- * @param {!UpdateStatusChangedEvent} event
- * @private
- */
- onUpdateStatusChanged_: function(event) {
- // <if expr="chromeos">
- if (event.status == UpdateStatus.CHECKING) {
- this.hasCheckedForUpdates_ = true;
- } else if (event.status == UpdateStatus.NEED_PERMISSION_TO_UPDATE) {
- this.showUpdateWarningDialog_ = true;
- this.updateInfo_ = {version: event.version, size: event.size};
- }
- // </if>
- this.currentUpdateStatusEvent_ = event;
- },
-
- // <if expr="_google_chrome and is_macosx">
- /**
- * @param {!PromoteUpdaterStatus} status
- * @private
- */
- onPromoteUpdaterStatusChanged_: function(status) {
- this.promoteUpdaterStatus_ = status;
- },
-
- /**
- * If #promoteUpdater isn't disabled, trigger update promotion.
- * @private
- */
- onPromoteUpdaterTap_: function() {
- // This is necessary because #promoteUpdater is not a button, so by default
- // disable doesn't do anything.
- if (this.promoteUpdaterStatus_.disabled) {
- return;
- }
- this.aboutBrowserProxy_.promoteUpdater();
- },
- // </if>
-
- /**
- * @param {!Event} event
- * @private
- */
- onLearnMoreTap_: function(event) {
- // Stop the propagation of events, so that clicking on links inside
- // actionable items won't trigger action.
- event.stopPropagation();
- },
-
- /** @private */
- onReleaseNotesTap_: function() {
- this.aboutBrowserProxy_.launchReleaseNotes();
- },
-
- /** @private */
- onHelpTap_: function() {
- this.aboutBrowserProxy_.openHelpPage();
- },
-
- /** @private */
- onRelaunchTap_: function() {
- this.lifetimeBrowserProxy_.relaunch();
- },
-
- /** @private */
- updateShowUpdateStatus_: function() {
- // <if expr="chromeos">
- // Do not show the "updated" status if we haven't checked yet or the update
- // warning dialog is shown to user.
- if (this.currentUpdateStatusEvent_.status == UpdateStatus.UPDATED &&
- (!this.hasCheckedForUpdates_ || this.showUpdateWarningDialog_)) {
- this.showUpdateStatus_ = false;
- return;
- }
-
- // Do not show "updated" status if the device is end of life.
- if (this.hasEndOfLife_) {
- this.showUpdateStatus_ = false;
- return;
- }
- // </if>
-
- // <if expr="not chromeos">
- if (this.obsoleteSystemInfo_.endOfLine) {
- this.showUpdateStatus_ = false;
- return;
- }
- // </if>
- this.showUpdateStatus_ =
- this.currentUpdateStatusEvent_.status != UpdateStatus.DISABLED;
- },
-
- /**
- * Hide the button container if all buttons are hidden, otherwise the
- * container displays an unwanted border (see separator class).
- * @private
- */
- updateShowButtonContainer_: function() {
- // <if expr="not chromeos">
- this.showButtonContainer_ = this.showRelaunch_;
- // </if>
- // <if expr="chromeos">
- this.showButtonContainer_ = this.showRelaunch_ ||
- this.showRelaunchAndPowerwash_ || this.showCheckUpdates_;
- // </if>
- },
-
- /** @private */
- updateShowRelaunch_: function() {
- // <if expr="not chromeos">
- this.showRelaunch_ = this.checkStatus_(UpdateStatus.NEARLY_UPDATED);
- // </if>
- // <if expr="chromeos">
- this.showRelaunch_ =
- this.checkStatus_(UpdateStatus.NEARLY_UPDATED) && !this.isRollback_();
- // </if>
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowLearnMoreLink_: function() {
- return this.currentUpdateStatusEvent_.status == UpdateStatus.FAILED;
- },
-
- /**
- * @return {string}
- * @private
- */
- getUpdateStatusMessage_: function() {
- switch (this.currentUpdateStatusEvent_.status) {
- case UpdateStatus.CHECKING:
- case UpdateStatus.NEED_PERMISSION_TO_UPDATE:
- return this.i18nAdvanced('aboutUpgradeCheckStarted');
- case UpdateStatus.NEARLY_UPDATED:
- // <if expr="chromeos">
- if (this.currentChannel_ != this.targetChannel_) {
- return this.i18nAdvanced('aboutUpgradeSuccessChannelSwitch');
- }
- if (this.currentUpdateStatusEvent_.rollback) {
- return this.i18nAdvanced('aboutRollbackSuccess');
- }
- // </if>
- return this.i18nAdvanced('aboutUpgradeRelaunch');
- case UpdateStatus.UPDATED:
- return this.i18nAdvanced('aboutUpgradeUpToDate');
- case UpdateStatus.UPDATING:
- assert(typeof this.currentUpdateStatusEvent_.progress == 'number');
- const progressPercent = this.currentUpdateStatusEvent_.progress + '%';
-
- // <if expr="chromeos">
- if (this.currentChannel_ != this.targetChannel_) {
- return this.i18nAdvanced('aboutUpgradeUpdatingChannelSwitch', {
- substitutions: [
- this.i18nAdvanced(
- settings.browserChannelToI18nId(this.targetChannel_)),
- progressPercent
- ]
- });
- }
- if (this.currentUpdateStatusEvent_.rollback) {
- return this.i18nAdvanced('aboutRollbackInProgress', {
- substitutions: [progressPercent],
- });
- }
- // </if>
- if (this.currentUpdateStatusEvent_.progress > 0) {
- // NOTE(dbeam): some platforms (i.e. Mac) always send 0% while
- // updating (they don't support incremental upgrade progress). Though
- // it's certainly quite possible to validly end up here with 0% on
- // platforms that support incremental progress, nobody really likes
- // seeing that they're 0% done with something.
- return this.i18nAdvanced('aboutUpgradeUpdatingPercent', {
- substitutions: [progressPercent],
- });
- }
- return this.i18nAdvanced('aboutUpgradeUpdating');
- default:
- function formatMessage(msg) {
- return parseHtmlSubset('<b>' + msg + '</b>', ['br', 'pre'])
- .firstChild.innerHTML;
- }
- let result = '';
- const message = this.currentUpdateStatusEvent_.message;
- if (message) {
- result += formatMessage(message);
- }
- const connectMessage = this.currentUpdateStatusEvent_.connectionTypes;
- if (connectMessage) {
- result += '<div>' + formatMessage(connectMessage) + '</div>';
- }
- return result;
- }
- },
-
- /**
- * @return {?string}
- * @private
- */
- getUpdateStatusIcon_: function() {
- // <if expr="chromeos">
- // If Chrome OS has reached end of life, display a special icon and
- // ignore UpdateStatus.
- if (this.hasEndOfLife_) {
- return 'os-settings:end-of-life';
- }
- // </if>
-
- // <if expr="not chromeos">
- // If this platform has reached the end of the line, display an error icon
- // and ignore UpdateStatus.
- if (this.obsoleteSystemInfo_.endOfLine) {
- return 'cr:error';
- }
- // </if>
-
- switch (this.currentUpdateStatusEvent_.status) {
- case UpdateStatus.DISABLED_BY_ADMIN:
- return 'cr20:domain';
- case UpdateStatus.FAILED:
- return 'cr:error';
- case UpdateStatus.UPDATED:
- case UpdateStatus.NEARLY_UPDATED:
- return 'settings:check-circle';
- default:
- return null;
- }
- },
-
- /**
- * @return {?string}
- * @private
- */
- getThrobberSrcIfUpdating_: function() {
- // <if expr="chromeos">
- if (this.hasEndOfLife_) {
- return null;
- }
- // </if>
-
- // <if expr="not chromeos">
- if (this.obsoleteSystemInfo_.endOfLine) {
- return null;
- }
- // </if>
-
- switch (this.currentUpdateStatusEvent_.status) {
- case UpdateStatus.CHECKING:
- case UpdateStatus.UPDATING:
- return 'chrome://resources/images/throbber_small.svg';
- default:
- return null;
- }
- },
-
- /**
- * @param {!UpdateStatus} status
- * @return {boolean}
- * @private
- */
- checkStatus_: function(status) {
- return this.currentUpdateStatusEvent_.status == status;
- },
-
- /** @private */
- onManagementPageTap_: function() {
- window.location.href = 'chrome://management';
- },
-
- // <if expr="chromeos">
- /**
- * @return {boolean}
- * @private
- */
- isRollback_: function() {
- assert(this.currentChannel_.length > 0);
- assert(this.targetChannel_.length > 0);
- if (this.currentUpdateStatusEvent_.rollback) {
- return true;
- }
- // Channel switch to a more stable channel is also a rollback
- return settings.isTargetChannelMoreStable(
- this.currentChannel_, this.targetChannel_);
- },
-
- /** @private */
- onDetailedBuildInfoTap_: function() {
- settings.navigateTo(settings.routes.DETAILED_BUILD_INFO);
- },
-
- /** @private */
- onRelaunchAndPowerwashTap_: function() {
- if (this.currentUpdateStatusEvent_.rollback) {
- // Wipe already initiated, simply relaunch.
- this.lifetimeBrowserProxy_.relaunch();
- } else {
- this.lifetimeBrowserProxy_.factoryReset(
- /* requestTpmFirmwareUpdate= */ false);
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeShowRelaunchAndPowerwash_: function() {
- return this.checkStatus_(UpdateStatus.NEARLY_UPDATED) && this.isRollback_();
- },
-
- /** @private */
- onCheckUpdatesTap_: function() {
- this.onUpdateStatusChanged_({status: UpdateStatus.CHECKING});
- this.aboutBrowserProxy_.requestUpdate();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeShowCheckUpdates_: function() {
- // Disable update button if the device is end of life.
- if (this.hasEndOfLife_) {
- return false;
- }
-
- // Enable the update button if we are in a stale 'updated' status or
- // update has failed. Disable it otherwise.
- const staleUpdatedStatus =
- !this.hasCheckedForUpdates_ && this.checkStatus_(UpdateStatus.UPDATED);
-
- return staleUpdatedStatus || this.checkStatus_(UpdateStatus.FAILED);
- },
-
- /**
- * @param {boolean} showCrostiniLicense True if Crostini is enabled and
- * Crostini UI is allowed.
- * @return {string}
- * @private
- */
- getAboutProductOsLicense_: function(showCrostiniLicense) {
- return showCrostiniLicense ?
- this.i18nAdvanced('aboutProductOsWithLinuxLicense') :
- this.i18nAdvanced('aboutProductOsLicense');
- },
-
- // <if expr="chromeos">
- /**
- * @return {string}
- * @private
- */
- getUpdateOsSettingsLink_: function() {
- // Note: This string contains raw HTML and thus requires i18nAdvanced().
- // Since the i18n template syntax (e.g., $i18n{}) does not include an
- // "advanced" version, it's not possible to inline this link directly in the
- // HTML.
- return this.i18nAdvanced('aboutUpdateOsSettingsLink');
- },
- // </if>
-
- /**
- * @param {boolean} enabled True if Crostini is enabled.
- * @private
- */
- handleCrostiniEnabledChanged_: function(enabled) {
- this.showCrostiniLicense_ = enabled && this.showCrostini;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowSafetyInfo_: function() {
- return loadTimeData.getBoolean('shouldShowSafetyInfo');
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowRegulatoryInfo_: function() {
- return this.regulatoryInfo_ !== null;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowRegulatoryOrSafetyInfo_: function() {
- return this.showOsSettings_ &&
- (this.shouldShowSafetyInfo_() || this.shouldShowRegulatoryInfo_());
- },
-
- /** @private */
- onUpdateWarningDialogClose_: function() {
- this.showUpdateWarningDialog_ = false;
- // Shows 'check for updates' button in case that the user cancels the
- // dialog and then intends to check for update again.
- this.hasCheckedForUpdates_ = false;
- },
-
- /**
- * @param {!TPMFirmwareUpdateStatusChangedEvent} event
- * @private
- */
- onTPMFirmwareUpdateStatusChanged_: function(event) {
- this.showTPMFirmwareUpdateLineItem_ = event.updateAvailable;
- },
-
- /** @private */
- onTPMFirmwareUpdateTap_: function() {
- this.showTPMFirmwareUpdateDialog_ = true;
- },
-
- /** @private */
- onPowerwashDialogClose_: function() {
- this.showTPMFirmwareUpdateDialog_ = false;
- },
- // </if>
-
- /** @private */
- onProductLogoTap_: function() {
- this.$['product-logo'].animate(
- {
- transform: ['none', 'rotate(-10turn)'],
- },
- {
- duration: 500,
- easing: 'cubic-bezier(1, 0, 0, 1)',
- });
- },
-
- // <if expr="_google_chrome">
- /** @private */
- onReportIssueTap_: function() {
- this.aboutBrowserProxy_.openFeedbackDialog();
- },
- // </if>
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowIcons_: function() {
- // <if expr="chromeos">
- if (this.hasEndOfLife_) {
- return true;
- }
- // </if>
- // <if expr="not chromeos">
- if (this.obsoleteSystemInfo_.endOfLine) {
- return true;
- }
- // </if>
- return this.showUpdateStatus_;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.html b/chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.html
deleted file mode 100644
index d5412c5d332..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="about_page_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.js b/chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.js
deleted file mode 100644
index 1d5eeb7273d..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/about_page_browser_proxy.js
+++ /dev/null
@@ -1,342 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "About" section to interact with
- * the browser.
- */
-
-// <if expr="chromeos">
-/**
- * @typedef {{
- * text: string,
- * url: string,
- * }}
- */
-let RegulatoryInfo;
-
-/**
- * @typedef {{
- * currentChannel: BrowserChannel,
- * targetChannel: BrowserChannel,
- * canChangeChannel: boolean,
- * }}
- */
-let ChannelInfo;
-
-/**
- * @typedef {{
- * version: (string|undefined),
- * size: (string|undefined),
- * }}
- */
-let AboutPageUpdateInfo;
-
-/**
- * @typedef {{
- * hasEndOfLife: (boolean|undefined),
- * eolMessageWithMonthAndYear: (string|undefined),
- * }}
- */
-let EndOfLifeInfo;
-
-/**
- * Enumeration of all possible browser channels.
- * @enum {string}
- */
-const BrowserChannel = {
- BETA: 'beta-channel',
- CANARY: 'canary-channel',
- DEV: 'dev-channel',
- STABLE: 'stable-channel',
-};
-
-/**
- * @typedef {{
- * updateAvailable: boolean,
- * }}
- */
-let TPMFirmwareUpdateStatusChangedEvent;
-// </if>
-
-/**
- * Enumeration of all possible update statuses. The string literals must match
- * the ones defined at |AboutHandler::UpdateStatusToString|.
- * @enum {string}
- */
-const UpdateStatus = {
- CHECKING: 'checking',
- UPDATING: 'updating',
- NEARLY_UPDATED: 'nearly_updated',
- UPDATED: 'updated',
- FAILED: 'failed',
- DISABLED: 'disabled',
- DISABLED_BY_ADMIN: 'disabled_by_admin',
- NEED_PERMISSION_TO_UPDATE: 'need_permission_to_update',
-};
-
-// <if expr="_google_chrome and is_macosx">
-/**
- * @typedef {{
- * hidden: boolean,
- * disabled: boolean,
- * actionable: boolean,
- * text: (string|undefined)
- * }}
- */
-let PromoteUpdaterStatus;
-// </if>
-
-/**
- * @typedef {{
- * status: !UpdateStatus,
- * progress: (number|undefined),
- * message: (string|undefined),
- * connectionTypes: (string|undefined),
- * version: (string|undefined),
- * size: (string|undefined),
- * }}
- */
-let UpdateStatusChangedEvent;
-
-cr.define('settings', function() {
- /**
- * @param {!BrowserChannel} channel
- * @return {string}
- */
- function browserChannelToI18nId(channel) {
- switch (channel) {
- case BrowserChannel.BETA:
- return 'aboutChannelBeta';
- case BrowserChannel.CANARY:
- return 'aboutChannelCanary';
- case BrowserChannel.DEV:
- return 'aboutChannelDev';
- case BrowserChannel.STABLE:
- return 'aboutChannelStable';
- }
-
- assertNotReached();
- }
-
- /**
- * @param {!BrowserChannel} currentChannel
- * @param {!BrowserChannel} targetChannel
- * @return {boolean} Whether the target channel is more stable than the
- * current channel.
- */
- function isTargetChannelMoreStable(currentChannel, targetChannel) {
- // List of channels in increasing stability order.
- const channelList = [
- BrowserChannel.CANARY,
- BrowserChannel.DEV,
- BrowserChannel.BETA,
- BrowserChannel.STABLE,
- ];
- const currentIndex = channelList.indexOf(currentChannel);
- const targetIndex = channelList.indexOf(targetChannel);
- return currentIndex < targetIndex;
- }
-
- /** @interface */
- class AboutPageBrowserProxy {
- /**
- * Indicates to the browser that the page is ready.
- */
- pageReady() {}
-
- /**
- * Request update status from the browser. It results in one or more
- * 'update-status-changed' WebUI events.
- */
- refreshUpdateStatus() {}
-
- // <if expr="chromeos">
- /** Opens the release notes app. */
- launchReleaseNotes() {}
- // </if>
-
-
- /** Opens the help page. */
- openHelpPage() {}
-
- // <if expr="_google_chrome">
- /**
- * Opens the feedback dialog.
- */
- openFeedbackDialog() {}
-
- // </if>
-
- // <if expr="chromeos">
- /** Opens the OS help page. */
- openOsHelpPage() {}
-
- /**
- * Checks for available update and applies if it exists.
- */
- requestUpdate() {}
-
- /**
- * Checks for the update with specified version and size and applies over
- * cellular. The target version and size are the same as were received from
- * 'update-status-changed' WebUI event. We send this back all the way to
- * update engine for it to double check with update server in case there's a
- * new update available. This prevents downloading the new update that user
- * didn't agree to.
- * @param {string} target_version
- * @param {string} target_size
- */
- requestUpdateOverCellular(target_version, target_size) {}
-
- /**
- * @param {!BrowserChannel} channel
- * @param {boolean} isPowerwashAllowed
- */
- setChannel(channel, isPowerwashAllowed) {}
-
- /** @return {!Promise<!ChannelInfo>} */
- getChannelInfo() {}
-
- /** @return {!Promise<?RegulatoryInfo>} */
- getRegulatoryInfo() {}
-
- /**
- * Checks if the device has reached end-of-life status and will no longer
- * receive updates.
- * @return {!Promise<!EndOfLifeInfo>}
- */
- getEndOfLifeInfo() {}
-
- /**
- * Request TPM firmware update status from the browser. It results in one or
- * more 'tpm-firmware-update-status-changed' WebUI events.
- */
- refreshTPMFirmwareUpdateStatus() {}
- // </if>
-
- // <if expr="_google_chrome and is_macosx">
- /**
- * Triggers setting up auto-updates for all users.
- */
- promoteUpdater() {}
- // </if>
-
- // <if expr="chromeos">
- /**
- * Checks if the device has release notes enabled.
- * @return {!Promise<boolean>}
- */
- getEnabledReleaseNotes() {}
-
- /**
- * Checks if the device is connected to the internet.
- * @return {!Promise<boolean>}
- */
- checkInternetConnection() {}
- // </if>
- }
-
- /**
- * @implements {settings.AboutPageBrowserProxy}
- */
- class AboutPageBrowserProxyImpl {
- /** @override */
- pageReady() {
- chrome.send('aboutPageReady');
- }
-
- /** @override */
- refreshUpdateStatus() {
- chrome.send('refreshUpdateStatus');
- }
-
- // <if expr="_google_chrome and is_macosx">
- /** @override */
- promoteUpdater() {
- chrome.send('promoteUpdater');
- }
-
- // </if>
-
- /** @override */
- launchReleaseNotes() {
- chrome.send('launchReleaseNotes');
- }
-
- /** @override */
- openHelpPage() {
- chrome.send('openHelpPage');
- }
-
- // <if expr="_google_chrome">
- /** @override */
- openFeedbackDialog() {
- chrome.send('openFeedbackDialog');
- }
-
- // </if>
-
- // <if expr="chromeos">
- /** @override */
- openOsHelpPage() {
- chrome.send('openOsHelpPage');
- }
-
- /** @override */
- requestUpdate() {
- chrome.send('requestUpdate');
- }
-
- /** @override */
- requestUpdateOverCellular(target_version, target_size) {
- chrome.send('requestUpdateOverCellular', [target_version, target_size]);
- }
-
- /** @override */
- setChannel(channel, isPowerwashAllowed) {
- chrome.send('setChannel', [channel, isPowerwashAllowed]);
- }
-
- /** @override */
- getChannelInfo() {
- return cr.sendWithPromise('getChannelInfo');
- }
-
- /** @override */
- getRegulatoryInfo() {
- return cr.sendWithPromise('getRegulatoryInfo');
- }
-
- /** @override */
- getEndOfLifeInfo() {
- return cr.sendWithPromise('getEndOfLifeInfo');
- }
-
- /** @override */
- getEnabledReleaseNotes() {
- return cr.sendWithPromise('getEnabledReleaseNotes');
- }
-
- /** @override */
- checkInternetConnection() {
- return cr.sendWithPromise('checkInternetConnection');
- }
-
- /** @override */
- refreshTPMFirmwareUpdateStatus() {
- chrome.send('refreshTPMFirmwareUpdateStatus');
- }
- // </if>
- }
-
- cr.addSingletonGetter(AboutPageBrowserProxyImpl);
-
- return {
- AboutPageBrowserProxy: AboutPageBrowserProxy,
- AboutPageBrowserProxyImpl: AboutPageBrowserProxyImpl,
- browserChannelToI18nId: browserChannelToI18nId,
- isTargetChannelMoreStable: isTargetChannelMoreStable,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.html b/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.html
deleted file mode 100644
index 78ac464caad..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html">
-<link rel="import" href="about_page_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-channel-switcher-dialog">
- <template>
- <style include="settings-shared">
- #warningSelector > :not(.iron-selected) {
- display: none;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{aboutChangeChannel}</div>
- <div slot="body">
- <!-- TODO(dbeam): this can be policy-controlled. Show this in the UI.
- https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ChromeOsReleaseChannel
- -->
- <cr-radio-group on-selected-changed="onChannelSelectionChanged_">
- <cr-radio-button name="[[browserChannelEnum_.STABLE]]">
- $i18n{aboutChannelDialogStable}
- </cr-radio-button>
- <cr-radio-button name="[[browserChannelEnum_.BETA]]">
- $i18n{aboutChannelDialogBeta}
- </cr-radio-button>
- <cr-radio-button name="[[browserChannelEnum_.DEV]]">
- $i18n{aboutChannelDialogDev}
- </cr-radio-button>
- </cr-radio-group>
- <iron-selector id="warningSelector">
- <div>
- <h2>$i18n{aboutDelayedWarningTitle}</h2>
- <div>[[substituteString_(
- '$i18nPolymer{aboutDelayedWarningMessage}',
- '$i18nPolymer{aboutOsProductTitle}')]]</div>
- </div>
- <div>
- <h2>$i18n{aboutPowerwashWarningTitle}</h2>
- <div>$i18n{aboutPowerwashWarningMessage}</div>
- </div>
- <div>
- <h2>$i18n{aboutUnstableWarningTitle}</h2>
- <div>[[substituteString_(
- '$i18nPolymer{aboutUnstableWarningMessage}',
- '$i18nPolymer{aboutOsProductTitle}')]]</div>
- </div>
- </iron-selector>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_"
- id="cancel">$i18n{cancel}</cr-button>
- <cr-button id="changeChannel" class="action-button"
- on-click="onChangeChannelTap_"
- hidden="[[!shouldShowButtons_.changeChannel]]">
- $i18n{aboutChangeChannel}
- </cr-button>
- <cr-button id="changeChannelAndPowerwash" class="action-button"
- on-click="onChangeChannelAndPowerwashTap_"
- hidden="[[!shouldShowButtons_.changeChannelAndPowerwash]]">
- $i18n{aboutChangeChannelAndPowerwash}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="channel_switcher_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.js b/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.js
deleted file mode 100644
index 29336e7629d..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.js
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-/**
- */
-const WarningMessage = {
- NONE: -1,
- ENTERPRISE_MANAGED: 0,
- POWERWASH: 1,
- UNSTABLE: 2,
-};
-
-/**
- * @fileoverview 'settings-channel-switcher-dialog' is a component allowing the
- * user to switch between release channels (dev, beta, stable). A
- * |target-channel-changed| event is fired if the user does select a different
- * release channel to notify parents of this dialog.
- */
-Polymer({
- is: 'settings-channel-switcher-dialog',
-
- properties: {
- /** @private */
- browserChannelEnum_: {
- type: Object,
- value: BrowserChannel,
- },
-
- /** @private {!BrowserChannel} */
- currentChannel_: String,
-
- /** @private {!BrowserChannel} */
- targetChannel_: String,
-
- /**
- * Controls which of the two action buttons is visible.
- * @private {?{changeChannel: boolean, changeChannelAndPowerwash: boolean}}
- */
- shouldShowButtons_: {
- type: Object,
- value: null,
- },
- },
-
- /** @private {?settings.AboutPageBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance();
- this.browserProxy_.getChannelInfo().then(info => {
- this.currentChannel_ = info.currentChannel;
- this.targetChannel_ = info.targetChannel;
- // Pre-populate radio group with target channel.
- const radioGroup = this.$$('cr-radio-group');
- radioGroup.selected = this.targetChannel_;
- radioGroup.focus();
- });
- },
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onChangeChannelTap_: function() {
- const selectedChannel = this.$$('cr-radio-group').selected;
- this.browserProxy_.setChannel(selectedChannel, false);
- this.$.dialog.close();
- this.fire('target-channel-changed', selectedChannel);
- },
-
- /** @private */
- onChangeChannelAndPowerwashTap_: function() {
- const selectedChannel = this.$$('cr-radio-group').selected;
- this.browserProxy_.setChannel(selectedChannel, true);
- this.$.dialog.close();
- this.fire('target-channel-changed', selectedChannel);
- },
-
- /**
- * @param {boolean} changeChannel Whether the changeChannel button should be
- * visible.
- * @param {boolean} changeChannelAndPowerwash Whether the
- * changeChannelAndPowerwash button should be visible.
- * @private
- */
- updateButtons_: function(changeChannel, changeChannelAndPowerwash) {
- if (changeChannel || changeChannelAndPowerwash) {
- // Ensure that at most one button is visible at any given time.
- assert(changeChannel != changeChannelAndPowerwash);
- }
-
- this.shouldShowButtons_ = {
- changeChannel: changeChannel,
- changeChannelAndPowerwash: changeChannelAndPowerwash,
- };
- },
-
- /** @private */
- onChannelSelectionChanged_: function() {
- const selectedChannel = this.$$('cr-radio-group').selected;
-
- // Selected channel is the same as the target channel so only show 'cancel'.
- if (selectedChannel == this.targetChannel_) {
- this.shouldShowButtons_ = null;
- this.$.warningSelector.select(WarningMessage.NONE);
- return;
- }
-
- // Selected channel is the same as the current channel, allow the user to
- // change without warnings.
- if (selectedChannel == this.currentChannel_) {
- this.updateButtons_(true, false);
- this.$.warningSelector.select(WarningMessage.NONE);
- return;
- }
-
- if (settings.isTargetChannelMoreStable(
- this.currentChannel_, selectedChannel)) {
- // More stable channel selected. For non managed devices, notify the user
- // about powerwash.
- if (loadTimeData.getBoolean('aboutEnterpriseManaged')) {
- this.$.warningSelector.select(WarningMessage.ENTERPRISE_MANAGED);
- this.updateButtons_(true, false);
- } else {
- this.$.warningSelector.select(WarningMessage.POWERWASH);
- this.updateButtons_(false, true);
- }
- } else {
- if (selectedChannel == BrowserChannel.DEV) {
- // Dev channel selected, warn the user.
- this.$.warningSelector.select(WarningMessage.UNSTABLE);
- } else {
- this.$.warningSelector.select(WarningMessage.NONE);
- }
- this.updateButtons_(true, false);
- }
- },
-
- /**
- * @param {string} format
- * @param {string} replacement
- * @return {string}
- * @private
- */
- substituteString_: function(format, replacement) {
- return loadTimeData.substituteString(format, replacement);
- },
-});
-})(); \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.html b/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.html
deleted file mode 100644
index fa1ae5f812e..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="about_page_browser_proxy.html">
-<link rel="import" href="channel_switcher_dialog.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-
-<dom-module id="settings-detailed-build-info">
- <template>
- <style include="settings-shared">
- cr-policy-indicator {
- margin-inline-start: var(--cr-controlled-by-spacing);
- }
-
- /* The command line string can contain very long substrings that
- * don't have any spaces, need to force a line break in such cases. */
- #command-line {
- overflow-wrap: break-word;
- width: 100%;
- }
- </style>
- <div class="settings-box two-line">
- <div class="start">
- <div>$i18n{aboutChannelLabel}</div>
- <div class="secondary">[[currentlyOnChannelText_]]</div>
- </div>
- <div class="separator"></div>
- <cr-button on-click="onChangeChannelTap_"
- disabled="[[!canChangeChannel_]]">
- $i18n{aboutChangeChannel}
- </cr-button>
- <template is="dom-if" if="[[!canChangeChannel_]]">
- <cr-policy-indicator
- indicator-source-name="[[getChangeChannelIndicatorSourceName_(
- canChangeChannel_)]]"
- indicator-type="[[getChangeChannelIndicatorType_(
- canChangeChannel_)]]">
- </cr-policy-indicator>
- </template>
- <template is="dom-if" if="[[showChannelSwitcherDialog_]]" restamp>
- <settings-channel-switcher-dialog
- on-close="onChannelSwitcherDialogClosed_">
- </settings-channel-switcher-dialog>
- </template>
- </div>
- <div id="endOfLifeSectionContainer"
- class="settings-box two-line single-column"
- hidden="[[!eolMessageWithMonthAndYear]]">
- <div>$i18n{aboutEndOfLifeTitle}</div>
- <div class="secondary" inner-h-t-m-l="[[eolMessageWithMonthAndYear]]">
- </div>
- </div>
- <cr-link-row class="hr" label="$i18n{aboutBuildDetailsTitle}"
- on-click="onBuildDetailsTap_" external>
- </cr-link-row>
- <div class="hr"></div>
- </template>
- <script src="detailed_build_info.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.js b/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.js
deleted file mode 100644
index ba47cf969b4..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.js
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-detailed-build-info' contains detailed build
- * information for ChromeOS.
- */
-
-Polymer({
- is: 'settings-detailed-build-info',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /** @private */
- currentlyOnChannelText_: String,
-
- /** @private */
- showChannelSwitcherDialog_: Boolean,
-
- /** @private */
- canChangeChannel_: Boolean,
-
- eolMessageWithMonthAndYear: {
- type: String,
- value: '',
- },
- },
-
- /** @override */
- ready: function() {
- const browserProxy = settings.AboutPageBrowserProxyImpl.getInstance();
- browserProxy.pageReady();
- this.updateChannelInfo_();
- },
-
- /** @private */
- updateChannelInfo_: function() {
- const browserProxy = settings.AboutPageBrowserProxyImpl.getInstance();
- browserProxy.getChannelInfo().then(info => {
- // Display the target channel for the 'Currently on' message.
- this.currentlyOnChannelText_ = this.i18n(
- 'aboutCurrentlyOnChannel',
- this.i18n(settings.browserChannelToI18nId(info.targetChannel)));
- this.canChangeChannel_ = info.canChangeChannel;
- });
- },
-
- /**
- * @param {boolean} canChangeChannel
- * @return {string}
- * @private
- */
- getChangeChannelIndicatorSourceName_: function(canChangeChannel) {
- return loadTimeData.getBoolean('aboutEnterpriseManaged') ?
- '' :
- loadTimeData.getString('ownerEmail');
- },
-
- /**
- * @param {boolean} canChangeChannel
- * @return {CrPolicyIndicatorType}
- * @private
- */
- getChangeChannelIndicatorType_: function(canChangeChannel) {
- if (canChangeChannel) {
- return CrPolicyIndicatorType.NONE;
- }
- return loadTimeData.getBoolean('aboutEnterpriseManaged') ?
- CrPolicyIndicatorType.DEVICE_POLICY :
- CrPolicyIndicatorType.OWNER;
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onChangeChannelTap_: function(e) {
- e.preventDefault();
- this.showChannelSwitcherDialog_ = true;
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onBuildDetailsTap_: function(e) {
- e.preventDefault();
- window.open('chrome://version');
- },
-
- /** @private */
- onChannelSwitcherDialogClosed_: function() {
- this.showChannelSwitcherDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$$('cr-button')));
- this.updateChannelInfo_();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.html b/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.html
deleted file mode 100644
index b6fe9f3a612..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="about_page_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-update-warning-dialog">
- <template>
- <style include="settings-shared"></style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{aboutUpdateWarningTitle}</div>
- <div slot="body">
- <div id="update-warning-message"></div>
- </div>
- <div slot="button-container">
- <cr-button id="cancel" class="cancel-button"
- on-click="onCancelTap_">$i18n{cancel}</cr-button>
- <cr-button id="continue" class="action-button"
- on-click="onContinueTap_">
- $i18n{continue}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="update_warning_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.js b/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.js
deleted file mode 100644
index 4e62fc88900..00000000000
--- a/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-update-warning-dialog' is a component warning the
- * user about update over mobile data. By clicking 'Continue', the user
- * agrees to download update using mobile data.
- */
-Polymer({
- is: 'settings-update-warning-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /** @type {!AboutPageUpdateInfo|undefined} */
- updateInfo: {
- type: Object,
- observer: 'updateInfoChanged_',
- },
- },
-
- /** @private {?settings.AboutPageBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onContinueTap_: function() {
- this.browserProxy_.requestUpdateOverCellular(
- this.updateInfo.version, this.updateInfo.size);
- this.$.dialog.close();
- },
-
- /** @private */
- updateInfoChanged_: function() {
- this.$$('#update-warning-message').innerHTML = this.i18n(
- 'aboutUpdateWarningMessage',
- // Convert bytes to megabytes
- Math.floor(Number(this.updateInfo.size) / (1024 * 1024)));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.html b/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.html
deleted file mode 100644
index a2d5ed626f6..00000000000
--- a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="android_apps_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.js b/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.js
deleted file mode 100644
index 82651604fd3..00000000000
--- a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_browser_proxy.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used by the "Google Play Store" (ARC) section
- * to retrieve information about android apps.
- */
-
-/**
- * Type definition of AndroidAppsInfo entry. |playStoreEnabled| indicates that
- * Play Store is enabled. |settingsAppAvailable| indicates that Android settings
- * app is registered in the system.
- * @typedef {{
- * playStoreEnabled: boolean,
- * settingsAppAvailable: boolean,
- * }}
- * @see chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc
- */
-let AndroidAppsInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class AndroidAppsBrowserProxy {
- requestAndroidAppsInfo() {}
-
- /**
- * @param {boolean} keyboardAction True if the app was opened using a
- * keyboard action.
- */
- showAndroidAppsSettings(keyboardAction) {}
- }
-
- /**
- * @implements {settings.AndroidAppsBrowserProxy}
- */
- class AndroidAppsBrowserProxyImpl {
- /** @override */
- requestAndroidAppsInfo() {
- chrome.send('requestAndroidAppsInfo');
- }
-
- /** @override */
- showAndroidAppsSettings(keyboardAction) {
- chrome.send('showAndroidAppsSettings', [keyboardAction]);
- }
- }
-
- // The singleton instance_ can be replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(AndroidAppsBrowserProxyImpl);
-
- return {
- AndroidAppsBrowserProxy: AndroidAppsBrowserProxy,
- AndroidAppsBrowserProxyImpl: AndroidAppsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.html b/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.html
deleted file mode 100644
index 91ab92092e9..00000000000
--- a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="android_apps_browser_proxy.html">
-<link rel="import" href="android_apps_subpage.html">
-
-<!-- Changes to this file should be reflected in
- ../chromeos/os_apps_page/os_apps_page.html.
- TODO(crbug.com/1006152): This file should be deleted once the split-settings
- flag has been removed. -->
-
-<dom-module id="settings-android-apps-page">
- <template>
- <style include="settings-shared"></style>
-
- <settings-animated-pages id="pages" section="androidApps"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <template is="dom-if" if="[[havePlayStoreApp]]" restamp>
- <div id="android-apps" class="settings-box first"
- actionable$="[[androidAppsInfo.playStoreEnabled]]"
- on-click="onSubpageTap_">
- <div class="start settings-box-text">
- $i18n{androidAppsPageLabel}
- <div class="secondary" id="secondaryText"
- inner-h-t-m-l="[[i18nAdvanced('androidAppsSubtext')]]">
- </div>
- </div>
- <cr-policy-pref-indicator pref="[[prefs.arc.enabled]]"
- icon-aria-label="$i18n{androidAppsPageTitle}">
- </cr-policy-pref-indicator>
- <template is="dom-if" if="[[androidAppsInfo.playStoreEnabled]]">
- <cr-icon-button class="subpage-arrow"
- aria-label="$i18n{androidAppsPageTitle}"
- aria-describedby="secondaryText"></cr-icon-button>
- </template>
- <template is="dom-if" if="[[!androidAppsInfo.playStoreEnabled]]">
- <div class="separator"></div>
- <cr-button id="enable"
- disabled="[[isEnforced_(prefs.arc.enabled)]]"
- on-click="onEnableTap_"
- aria-label="$i18n{androidAppsPageTitle}"
- aria-describedby="secondaryText">
- $i18n{androidAppsEnable}
- </cr-button>
- </template>
- </div>
- </template>
- <template is="dom-if" if="[[!havePlayStoreApp]]" restamp>
- <cr-link-row id="manageApps" label="$i18n{androidAppsManageApps}"
- on-click="onManageAndroidAppsTap_" external>
- </cr-link-row>
- </template>
- </div>
-
- <template is="dom-if" route-path="/androidApps/details">
- <settings-subpage
- associated-control="[[$$('#android-apps')]]"
- page-title="$i18n{androidAppsPageLabel}">
- <settings-android-apps-subpage
- android-apps-info="[[androidAppsInfo]]" prefs="{{prefs}}">
- </settings-android-apps-subpage>
- </settings-subpage>
- </template>
- </settings-animated-pages>
-
- </template>
- <script src="android_apps_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.js b/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.js
deleted file mode 100644
index a79ba938237..00000000000
--- a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_page.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'android-apps-page' is the settings page for enabling android apps.
- */
-
-Polymer({
- is: 'settings-android-apps-page',
-
- behaviors: [I18nBehavior, PrefsBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- havePlayStoreApp: Boolean,
-
- androidAppsInfo: Object,
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.ANDROID_APPS_DETAILS) {
- map.set(
- settings.routes.ANDROID_APPS_DETAILS.path,
- '#android-apps .subpage-arrow');
- }
- return map;
- },
- },
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onEnableTap_: function(event) {
- this.setPrefValue('arc.enabled', true);
- event.stopPropagation();
- },
-
- /** @return {boolean} */
- isEnforced_: function(pref) {
- return pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED;
- },
-
- /** @private */
- onSubpageTap_: function(event) {
- if (event.target && event.target.tagName == 'A') {
- // Filter out events coming from 'Learn more' link
- return;
- }
- if (this.androidAppsInfo.playStoreEnabled) {
- settings.navigateTo(settings.routes.ANDROID_APPS_DETAILS);
- }
- },
-
- /**
- * @param {!MouseEvent} event
- * @private
- */
- onManageAndroidAppsTap_: function(event) {
- // |event.detail| is the click count. Keyboard events will have 0 clicks.
- const isKeyboardAction = event.detail == 0;
- settings.AndroidAppsBrowserProxyImpl.getInstance().showAndroidAppsSettings(
- isKeyboardAction);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.html b/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.html
deleted file mode 100644
index 92340b7c23c..00000000000
--- a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="android_apps_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-android-apps-subpage">
- <template>
- <style include="settings-shared"></style>
-
- <template is="dom-if" if="[[androidAppsInfo.settingsAppAvailable]]" restamp>
- <cr-link-row id="manageApps" icon-class="icon-external"
- label="$i18n{androidAppsManageApps}"
- on-click="onManageAndroidAppsTap_" external>
- </cr-link-row>
- </template>
-
- <!-- Use 'restamp' so tests can check if the row exists. -->
- <template is="dom-if" if="[[allowRemove_(prefs.arc.enabled.*)]]" restamp>
- <div id="remove" class="settings-box">
- <div id="androidRemoveLabel" class="start">
- $i18n{androidAppsRemove}
- </div>
- <cr-button on-click="onRemoveTap_"
- aria-labelledby="androidRemoveLabel">
- $i18n{androidAppsRemoveButton}
- </cr-button>
- </div>
- </template>
-
- <!-- Confirm disable android apps dialog -->
- <cr-dialog id="confirmDisableDialog" close-text="$i18n{close}"
- on-cancel="onConfirmDisableDialogCancel_"
- on-close="onConfirmDisableDialogClose_">
- <div slot="title">$i18n{androidAppsDisableDialogTitle}</div>
- <div slot="body" inner-h-t-m-l="[[dialogBody_]]"></div>
- <div slot="button-container">
- <cr-button class="cancel-button"
- on-click="onConfirmDisableDialogCancel_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button"
- on-click="onConfirmDisableDialogConfirm_">
- $i18n{androidAppsDisableDialogRemove}
- </cr-button>
- </div>
- </cr-dialog>
-
- </template>
- <script src="android_apps_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.js b/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.js
deleted file mode 100644
index 046682bc850..00000000000
--- a/chromium/chrome/browser/resources/settings/android_apps_page/android_apps_subpage.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'android-apps-subpage' is the settings subpage for managing android apps.
- */
-
-Polymer({
- is: 'settings-android-apps-subpage',
-
- behaviors: [I18nBehavior, PrefsBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: Object,
-
- /** @private {!AndroidAppsInfo|undefined} */
- androidAppsInfo: {
- type: Object,
- },
-
- /** @private */
- playStoreEnabled_: {
- type: Boolean,
- computed: 'computePlayStoreEnabled_(androidAppsInfo)',
- observer: 'onPlayStoreEnabledChanged_'
- },
-
- /** @private */
- dialogBody_: {
- type: String,
- value: function() {
- return this.i18nAdvanced(
- 'androidAppsDisableDialogMessage',
- {substitutions: [], tags: ['br']});
- }
- }
- },
-
- /** @private */
- onPlayStoreEnabledChanged_: function(enabled) {
- if (!enabled &&
- settings.getCurrentRoute() == settings.routes.ANDROID_APPS_DETAILS) {
- settings.navigateToPreviousRoute();
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computePlayStoreEnabled_: function() {
- return this.androidAppsInfo.playStoreEnabled;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- allowRemove_: function() {
- return this.prefs.arc.enabled.enforcement !=
- chrome.settingsPrivate.Enforcement.ENFORCED;
- },
-
- /**
- * Shows a confirmation dialog when disabling android apps.
- * @param {!Event} event
- * @private
- */
- onRemoveTap_: function(event) {
- this.$.confirmDisableDialog.showModal();
- },
-
- /**
- * Handles the shared proxy confirmation dialog 'Confirm' button.
- * @private
- */
- onConfirmDisableDialogConfirm_: function() {
- this.setPrefValue('arc.enabled', false);
- this.$.confirmDisableDialog.close();
- // Sub-page will be closed in onAndroidAppsInfoUpdate_ call.
- },
-
- /**
- * Handles the shared proxy confirmation dialog 'Cancel' button or a cancel
- * event.
- * @private
- */
- onConfirmDisableDialogCancel_: function() {
- this.$.confirmDisableDialog.close();
- },
-
- /** @private */
- onConfirmDisableDialogClose_: function() {
- cr.ui.focusWithoutInk(assert(this.$$('#remove')));
- },
-
- /**
- * @param {!MouseEvent} event
- * @private
- */
- onManageAndroidAppsTap_: function(event) {
- // |event.detail| is the click count. Keyboard events will have 0 clicks.
- const isKeyboardAction = event.detail == 0;
- settings.AndroidAppsBrowserProxyImpl.getInstance().showAndroidAppsSettings(
- isKeyboardAction);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html b/chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html
deleted file mode 100644
index 9f8635bc54f..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="href" src="chrome://resources/html/cr.html">
-<link rel="href" src="chrome://resources/html/load_time_data.html">
-<script src="appearance_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js b/chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js
deleted file mode 100644
index 7e8bd802fc3..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class AppearanceBrowserProxy {
- /** @return {!Promise<number>} */
- getDefaultZoom() {}
-
- /**
- * @param {string} themeId
- * @return {!Promise<!chrome.management.ExtensionInfo>} Theme info.
- */
- getThemeInfo(themeId) {}
-
- /** @return {boolean} Whether the current profile is supervised. */
- isSupervised() {}
-
- useDefaultTheme() {}
-
- // <if expr="is_linux and not chromeos">
- useSystemTheme() {}
-
- // </if>
-
- /**
- * @param {string} url The url of which to check validity.
- * @return {!Promise<boolean>}
- */
- validateStartupPage(url) {}
- }
-
- /**
- * @implements {settings.AppearanceBrowserProxy}
- */
- class AppearanceBrowserProxyImpl {
- /** @override */
- getDefaultZoom() {
- return new Promise(function(resolve) {
- chrome.settingsPrivate.getDefaultZoom(resolve);
- });
- }
-
- /** @override */
- getThemeInfo(themeId) {
- return new Promise(function(resolve) {
- chrome.management.get(themeId, resolve);
- });
- }
-
- /** @override */
- isSupervised() {
- return loadTimeData.getBoolean('isSupervised');
- }
-
- /** @override */
- useDefaultTheme() {
- chrome.send('useDefaultTheme');
- }
-
- // <if expr="is_linux and not chromeos">
- /** @override */
- useSystemTheme() {
- chrome.send('useSystemTheme');
- }
-
- // </if>
-
- /** @override */
- validateStartupPage(url) {
- return cr.sendWithPromise('validateStartupPage', url);
- }
- }
-
- cr.addSingletonGetter(AppearanceBrowserProxyImpl);
-
- return {
- AppearanceBrowserProxy: AppearanceBrowserProxy,
- AppearanceBrowserProxyImpl: AppearanceBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html b/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
deleted file mode 100644
index c79e375102d..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
+++ /dev/null
@@ -1,142 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="fonts_browser_proxy.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-appearance-fonts-page">
- <template>
- <style include="settings-shared">
- #minimumSize {
- align-items: flex-end;
- display: flex;
- flex-direction: column;
- }
-
- #minimumSizeSample {
- text-align: end;
- }
- </style>
- <div class="settings-box first">
- <div class="start settings-box-text">$i18n{fontSize}</div>
- <settings-slider id="sizeSlider"
- pref="{{prefs.webkit.webprefs.default_font_size}}"
- ticks="[[fontSizeRange_]]"
- label-min="$i18n{tiny}" label-max="$i18n{huge}">
- </settings-slider>
- </div>
- <div class="settings-box">
- <div class="start settings-box-text">$i18n{minimumFont}</div>
- <div id="minimumSize">
- <settings-slider pref="{{prefs.webkit.webprefs.minimum_font_size}}"
- ticks="[[minimumFontSizeRange_]]" label-min="$i18n{tiny}"
- label-max="$i18n{huge}"></settings-slider>
- <div id="minimumSizeSample"
- style="
- font-size:[[computeMinimumFontSize_(
- prefs.webkit.webprefs.minimum_font_size.value)]]px;
- font-family:
- '[[prefs.webkit.webprefs.fonts.standard.Zyyy.value]]';"
- hidden>
- [[computeMinimumFontSize_(
- prefs.webkit.webprefs.minimum_font_size.value)]]:
- $i18n{quickBrownFox}
- </div>
- </div>
- </div>
- <div class="settings-box">
- <h2>$i18n{standardFont}</h2>
- </div>
- <div class="list-frame">
- <div class="list-item">
- <settings-dropdown-menu class="start" label="$i18n{standardFont}"
- pref="{{prefs.webkit.webprefs.fonts.standard.Zyyy}}"
- menu-options="[[fontOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item settings-box-text"
- style="
- font-size:[[prefs.webkit.webprefs.default_font_size.value]]px;
- font-family:
- '[[prefs.webkit.webprefs.fonts.standard.Zyyy.value]]';">
- <span>
- [[prefs.webkit.webprefs.default_font_size.value]]:
- $i18n{quickBrownFox}
- </span>
- </div>
- </div>
- <div class="settings-box">
- <h2>$i18n{serifFont}</h2>
- </div>
- <div class="list-frame">
- <div class="list-item">
- <settings-dropdown-menu class="start" label="$i18n{serifFont}"
- pref="{{prefs.webkit.webprefs.fonts.serif.Zyyy}}"
- menu-options="[[fontOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item settings-box-text"
- style="
- font-size:[[prefs.webkit.webprefs.default_font_size.value]]px;
- font-family:
- '[[prefs.webkit.webprefs.fonts.serif.Zyyy.value]]';">
- <span>
- [[prefs.webkit.webprefs.default_font_size.value]]:
- $i18n{quickBrownFox}
- </span>
- </div>
- </div>
- <div class="settings-box">
- <h2>$i18n{sansSerifFont}</h2>
- </div>
- <div class="list-frame">
- <div class="list-item">
- <settings-dropdown-menu class="start" label="$i18n{sansSerifFont}"
- pref="{{prefs.webkit.webprefs.fonts.sansserif.Zyyy}}"
- menu-options="[[fontOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item settings-box-text"
- style="
- font-size:[[prefs.webkit.webprefs.default_font_size.value]]px;
- font-family:
- '[[prefs.webkit.webprefs.fonts.sansserif.Zyyy.value]]';">
- <span>
- [[prefs.webkit.webprefs.default_font_size.value]]:
- $i18n{quickBrownFox}
- </span>
- </div>
- </div>
- <div class="settings-box">
- <h2>$i18n{fixedWidthFont}</h2>
- </div>
- <div class="list-frame">
- <div class="list-item">
- <settings-dropdown-menu class="start" label="$i18n{fixedWidthFont}"
- pref="{{prefs.webkit.webprefs.fonts.fixed.Zyyy}}"
- menu-options="[[fontOptions_]]">
- </settings-dropdown-menu>
- </div>
- <div class="list-item settings-box-text"
- style="
- font-size:
- [[prefs.webkit.webprefs.default_fixed_font_size.value]]px;
- font-family:
- '[[prefs.webkit.webprefs.fonts.fixed.Zyyy.value]]';">
- [[prefs.webkit.webprefs.default_font_size.value]]:
- $i18n{quickBrownFox}
- </div>
- </div>
- <template is="dom-if" if="[[!isGuest_]]">
- <cr-link-row id="advancedButton" class="hr"
- on-click="openAdvancedExtension_" label="$i18n{advancedFontSettings}"
- sub-label="[[advancedExtensionSublabel_]]" external></cr-link-row>
- </template>
- </template>
- <script src="appearance_fonts_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js b/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js
deleted file mode 100644
index 7852e2ec734..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-/** @type {!Array<number>} */
-const FONT_SIZE_RANGE = [
- 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24,
- 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72,
-];
-
-/** @type {!Array<number>} */
-const MINIMUM_FONT_SIZE_RANGE =
- [0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24];
-
-/**
- * @param {!Array<number>} ticks
- * @return {!Array<!cr_slider.SliderTick>}
- */
-function ticksWithLabels(ticks) {
- return ticks.map(x => ({label: `${x}`, value: x}));
-}
-
-/**
- * 'settings-appearance-fonts-page' is the settings page containing appearance
- * settings.
- */
-Polymer({
- is: 'settings-appearance-fonts-page',
-
- behaviors: [I18nBehavior, WebUIListenerBehavior],
-
- properties: {
- /** @private */
- advancedExtensionSublabel_: String,
-
- /** @private {!DropdownMenuOptionList} */
- fontOptions_: Object,
-
- /** @private */
- isGuest_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isGuest');
- }
- },
-
- /**
- * Common font sizes.
- * @private {!Array<!cr_slider.SliderTick>}
- */
- fontSizeRange_: {
- readOnly: true,
- type: Array,
- value: ticksWithLabels(FONT_SIZE_RANGE),
- },
-
- /**
- * Reasonable, minimum font sizes.
- * @private {!Array<!cr_slider.SliderTick>}
- */
- minimumFontSizeRange_: {
- readOnly: true,
- type: Array,
- value: ticksWithLabels(MINIMUM_FONT_SIZE_RANGE),
- },
-
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
- },
-
- observers: [
- 'onMinimumSizeChange_(prefs.webkit.webprefs.minimum_font_size.value)',
- ],
-
- /** @private {?settings.FontsBrowserProxy} */
- browserProxy_: null,
-
- /** @private {boolean} */
- advancedExtensionInstalled_: false,
-
- /** @private {?string} */
- advancedExtensionUrl_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.FontsBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'advanced-font-settings-installed',
- this.setAdvancedExtensionInstalled_.bind(this));
- this.browserProxy_.observeAdvancedFontExtensionAvailable();
-
- this.browserProxy_.fetchFontsData().then(this.setFontsData_.bind(this));
- },
-
- /** @private */
- openAdvancedExtension_: function() {
- if (this.advancedExtensionInstalled_) {
- this.browserProxy_.openAdvancedFontSettings();
- } else {
- window.open(this.advancedExtensionUrl_);
- }
- },
-
- /**
- * @param {boolean} isInstalled Whether the advanced font settings
- * extension is installed.
- * @private
- */
- setAdvancedExtensionInstalled_: function(isInstalled) {
- this.advancedExtensionInstalled_ = isInstalled;
- this.advancedExtensionSublabel_ = this.i18n(
- isInstalled ? 'openAdvancedFontSettings' : 'requiresWebStoreExtension');
- },
-
- /**
- * @param {!FontsData} response A list of fonts and the advanced
- * font settings extension URL.
- * @private
- */
- setFontsData_: function(response) {
- const fontMenuOptions = [];
- for (const fontData of response.fontList) {
- fontMenuOptions.push({value: fontData[0], name: fontData[1]});
- }
- this.fontOptions_ = fontMenuOptions;
- this.advancedExtensionUrl_ = response.extensionUrl;
- },
-
- /**
- * Get the minimum font size, accounting for unset prefs.
- * @return {number}
- * @private
- */
- computeMinimumFontSize_: function() {
- const prefValue = this.get('prefs.webkit.webprefs.minimum_font_size.value');
- return /** @type {number} */ (prefValue) || MINIMUM_FONT_SIZE_RANGE[0];
- },
-
-
- /** @private */
- onMinimumSizeChange_: function() {
- this.$.minimumSizeSample.hidden = this.computeMinimumFontSize_() <= 0;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html b/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html
deleted file mode 100644
index 3a619511bed..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html
+++ /dev/null
@@ -1,193 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="appearance_browser_proxy.html">
-<link rel="import" href="../controls/controlled_radio_button.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../controls/settings_radio_group.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="appearance_fonts_page.html">
-<link rel="import" href="home_url_input.html">
-
-<if expr="chromeos">
-<link rel="import" href="../appearance_page/wallpaper_browser_proxy.html">
-</if>
-
-<dom-module id="settings-appearance-page">
- <template>
- <style include="settings-shared md-select iron-flex">
- /* Lines up with cr-input. */
- #custom-input {
- /* (cr-input line-height + cr-input top/bottom padding) / 2 -
- cr-radio disc-wrapper height / 2 */
- --cr-radio-button-disc-margin-block-start: calc(
- (1.54em + 12px) / 2 - 8px);
- align-items: start;
- }
-
- /* The theme mixes a link and a cr-button divided by a separator with
- * grit expressions and dom-if templates. That leads to a tricky thing
- * to style correctly, these are a workaround. */
- #themeRow cr-button {
- margin-inline-end: 20px;
- }
-
- #themeRow .separator {
- margin-inline-start: 0;
- }
- </style>
- <settings-animated-pages id="pages" section="appearance"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
-<if expr="chromeos">
- <cr-link-row id="wallpaperButton"
- hidden="[[!pageVisibility.setWallpaper]]"
- on-click="openWallpaperManager_" label="$i18n{setWallpaper}"
- sub-label="$i18n{openWallpaperApp}"
- disabled="[[isWallpaperPolicyControlled_]]" external>
- <template is="dom-if" if="[[isWallpaperPolicyControlled_]]">
- <cr-policy-indicator id="wallpaperPolicyIndicator"
- indicator-type="devicePolicy">
- </cr-policy-indicator>
- </template>
- </cr-link-row>
- <div class="hr"></div>
-</if>
- <div class="settings-row continuation" id="themeRow"
- hidden="[[!pageVisibility.setTheme]]">
- <cr-link-row class="first" hidden="[[!pageVisibility.setTheme]]"
- label="$i18n{themes}" sub-label="[[themeSublabel_]]"
- on-click="openThemeUrl_" external></cr-link-row>
-<if expr="not is_linux or chromeos">
- <template is="dom-if" if="[[prefs.extensions.theme.id.value]]">
- <div class="separator"></div>
- <cr-button id="useDefault" on-click="onUseDefaultTap_">
- $i18n{resetToDefaultTheme}
- </cr-button>
- </template>
-</if>
-<if expr="is_linux and not chromeos">
- <div class="settings-row continuation"
- hidden="[[!showThemesSecondary_(
- prefs.extensions.theme.id.value, useSystemTheme_)]]"
- id="themesSecondaryActions">
- <div class="separator"></div>
- <template is="dom-if" if="[[showUseClassic_(
- prefs.extensions.theme.id.value, useSystemTheme_)]]" restamp>
- <cr-button id="useDefault" on-click="onUseDefaultTap_">
- $i18n{useClassicTheme}
- </cr-button>
- </template>
- <template is="dom-if" if="[[showUseSystem_(
- prefs.extensions.theme.id.value, useSystemTheme_)]]" restamp>
- <cr-button id="useSystem" on-click="onUseSystemTap_">
- $i18n{useSystemTheme}
- </cr-button>
- </template>
- </div>
-</if>
- </div>
- <settings-toggle-button elide-label
- hidden="[[!pageVisibility.homeButton]]"
- pref="{{prefs.browser.show_home_button}}"
- label="$i18n{showHomeButton}"
- sub-label="[[getShowHomeSubLabel_(
- prefs.browser.show_home_button.value,
- prefs.homepage_is_newtabpage.value,
- prefs.homepage.value)]]">
- </settings-toggle-button>
- <template is="dom-if" if="[[prefs.browser.show_home_button.value]]">
- <div class="list-frame" hidden="[[!pageVisibility.homeButton]]">
- <settings-radio-group pref="{{prefs.homepage_is_newtabpage}}">
- <controlled-radio-button class="list-item" name="true"
- pref="[[prefs.homepage_is_newtabpage]]"
- label="$i18n{homePageNtp}" no-extension-indicator>
- </controlled-radio-button>
- <controlled-radio-button id="custom-input" class="list-item"
- name="false" pref="[[prefs.homepage_is_newtabpage]]"
- no-extension-indicator>
- <!-- TODO(dbeam): this can show double indicators when both
- homepage and whether to use the NTP as the homepage are
- managed. -->
- <home-url-input id="customHomePage" pref="{{prefs.homepage}}"
- can-tab="[[!prefs.homepage_is_newtabpage.value]]">
- </home-url-input>
- </controlled-radio-button>
- <template is="dom-if" if="[[prefs.homepage.extensionId]]">
- <extension-controlled-indicator
- extension-id="[[prefs.homepage.extensionId]]"
- extension-can-be-disabled="[[
- prefs.homepage.extensionCanBeDisabled]]"
- extension-name="[[prefs.homepage.controlledByName]]"
- on-disable-extension="onDisableExtension_">
- </extension-controlled-indicator>
- </template>
- </settings-radio-group>
- </div>
- </template>
- <settings-toggle-button hidden="[[!pageVisibility.bookmarksBar]]"
- pref="{{prefs.bookmark_bar.show_on_all_tabs}}"
- label="$i18n{showBookmarksBar}">
- </settings-toggle-button>
-<if expr="is_linux and not chromeos">
- <settings-toggle-button
- class$="[[getFirst_(pageVisibility.bookmarksBar)]]"
- pref="{{prefs.browser.custom_chrome_frame}}"
- label="$i18n{showWindowDecorations}"
- inverted>
- </settings-toggle-button>
-</if>
- <div class="settings-box">
- <div class="start settings-box-text">$i18n{fontSize}</div>
- <settings-dropdown-menu id="defaultFontSize" label="$i18n{fontSize}"
- pref="{{prefs.webkit.webprefs.default_font_size}}"
- menu-options="[[fontSizeOptions_]]">
- </settings-dropdown-menu>
- </div>
- <cr-link-row class="hr" id="customize-fonts-subpage-trigger"
- label="$i18n{customizeFonts}" on-click="onCustomizeFontsTap_">
- </cr-link-row>
- <div class="settings-box" hidden="[[!pageVisibility.pageZoom]]">
- <div id="pageZoom" class="start settings-box-text">
- $i18n{pageZoom}
- </div>
- <select id="zoomLevel" class="md-select" aria-labelledby="pageZoom"
- on-change="onZoomLevelChange_">
- <template is="dom-repeat" items="[[pageZoomLevels_]]">
- <option value="[[item]]"
- selected="[[zoomValuesEqual_(item, defaultZoom_)]]">
- [[formatZoom_(item)]]%
- </option>
- </template>
- </select>
- </div>
-<if expr="is_macosx">
- <settings-toggle-button pref="{{prefs.webkit.webprefs.tabs_to_links}}"
- label="$i18n{tabsToLinks}">
- </settings-toggle-button>
- <settings-toggle-button pref="{{prefs.browser.confirm_to_quit}}"
- label="$i18n{warnBeforeQuitting}">
- </settings-toggle-button>
-</if>
- </div>
- <template is="dom-if" route-path="/fonts">
- <settings-subpage
- associated-control="[[$$('#customize-fonts-subpage-trigger')]]"
- page-title="$i18n{customizeFonts}">
- <settings-appearance-fonts-page prefs="{{prefs}}">
- </settings-appearance-fonts-page>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- </template>
- <script src="appearance_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.js b/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.js
deleted file mode 100644
index 582ae814c9e..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.js
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-/**
- * This is the absolute difference maintained between standard and
- * fixed-width font sizes. http://crbug.com/91922.
- * @type {number}
- */
-const SIZE_DIFFERENCE_FIXED_STANDARD = 3;
-
-/**
- * ID for autogenerated themes. Should match
- * |ThemeService::kAutogeneratedThemeID|.
- */
-const AUTOGENERATED_THEME_ID = 'autogenerated_theme_id';
-
-/**
- * 'settings-appearance-page' is the settings page containing appearance
- * settings.
- */
-Polymer({
- is: 'settings-appearance-page',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * Dictionary defining page visibility.
- * @type {!AppearancePageVisibility}
- */
- pageVisibility: Object,
-
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private */
- defaultZoom_: Number,
-
- /** @private */
- isWallpaperPolicyControlled_: {type: Boolean, value: true},
-
- /**
- * List of options for the font size drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- fontSizeOptions_: {
- readOnly: true,
- type: Array,
- value: function() {
- return [
- {value: 9, name: loadTimeData.getString('verySmall')},
- {value: 12, name: loadTimeData.getString('small')},
- {value: 16, name: loadTimeData.getString('medium')},
- {value: 20, name: loadTimeData.getString('large')},
- {value: 24, name: loadTimeData.getString('veryLarge')},
- ];
- },
- },
-
- /**
- * List of options for the page zoom drop-down menu.
- * @type {!Array<number>}
- */
- pageZoomLevels_: {
- readOnly: true,
- type: Array,
- value: [
- // TODO(dbeam): get these dynamically from C++ instead.
- 1 / 4,
- 1 / 3,
- 1 / 2,
- 2 / 3,
- 3 / 4,
- 4 / 5,
- 9 / 10,
- 1,
- 11 / 10,
- 5 / 4,
- 3 / 2,
- 7 / 4,
- 2,
- 5 / 2,
- 3,
- 4,
- 5,
- ],
- },
-
- /** @private */
- themeSublabel_: String,
-
- /** @private */
- themeUrl_: String,
-
- /** @private */
- useSystemTheme_: {
- type: Boolean,
- value: false, // Can only be true on Linux, but value exists everywhere.
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.FONTS) {
- map.set(
- settings.routes.FONTS.path, '#customize-fonts-subpage-trigger');
- }
- return map;
- },
- },
- },
-
- /** @private {?settings.AppearanceBrowserProxy} */
- appearanceBrowserProxy_: null,
-
- // <if expr="chromeos">
- /** @private {?settings.WallpaperBrowserProxy} */
- wallpaperBrowserProxy_: null,
- // </if>
-
- observers: [
- 'defaultFontSizeChanged_(prefs.webkit.webprefs.default_font_size.value)',
- 'themeChanged_(prefs.extensions.theme.id.value, useSystemTheme_)',
-
- // <if expr="is_linux and not chromeos">
- // NOTE: this pref only exists on Linux.
- 'useSystemThemePrefChanged_(prefs.extensions.theme.use_system.value)',
- // </if>
- ],
-
- /** @override */
- created: function() {
- this.appearanceBrowserProxy_ =
- settings.AppearanceBrowserProxyImpl.getInstance();
- // <if expr="chromeos">
- this.wallpaperBrowserProxy_ =
- settings.WallpaperBrowserProxyImpl.getInstance();
- // </if>
- },
-
- /** @override */
- ready: function() {
- this.$.defaultFontSize.menuOptions = this.fontSizeOptions_;
- // TODO(dschuyler): Look into adding a listener for the
- // default zoom percent.
- this.appearanceBrowserProxy_.getDefaultZoom().then(zoom => {
- this.defaultZoom_ = zoom;
- });
- // <if expr="chromeos">
- this.wallpaperBrowserProxy_.isWallpaperSettingVisible().then(
- isWallpaperSettingVisible => {
- assert(this.pageVisibility);
- this.pageVisibility.setWallpaper = isWallpaperSettingVisible;
- });
- this.wallpaperBrowserProxy_.isWallpaperPolicyControlled().then(
- isPolicyControlled => {
- this.isWallpaperPolicyControlled_ = isPolicyControlled;
- });
- // </if>
- },
-
- /**
- * @param {number} zoom
- * @return {number} A zoom easier read by users.
- * @private
- */
- formatZoom_: function(zoom) {
- return Math.round(zoom * 100);
- },
-
- /**
- * @param {boolean} showHomepage Whether to show home page.
- * @param {boolean} isNtp Whether to use the NTP as the home page.
- * @param {string} homepageValue If not using NTP, use this URL.
- * @return {string} The sub-label.
- * @private
- */
- getShowHomeSubLabel_: function(showHomepage, isNtp, homepageValue) {
- if (!showHomepage) {
- return this.i18n('homeButtonDisabled');
- }
- if (isNtp) {
- return this.i18n('homePageNtp');
- }
- return homepageValue || this.i18n('customWebAddress');
- },
-
- /** @private */
- onCustomizeFontsTap_: function() {
- settings.navigateTo(settings.routes.FONTS);
- },
-
- /** @private */
- onDisableExtension_: function() {
- this.fire('refresh-pref', 'homepage');
- },
-
- /**
- * @param {number} value The changed font size slider value.
- * @private
- */
- defaultFontSizeChanged_: function(value) {
- // This pref is handled separately in some extensions, but here it is tied
- // to default_font_size (to simplify the UI).
- this.set(
- 'prefs.webkit.webprefs.default_fixed_font_size.value',
- value - SIZE_DIFFERENCE_FIXED_STANDARD);
- },
-
- /**
- * Open URL for either current theme or the theme gallery.
- * @private
- */
- openThemeUrl_: function() {
- window.open(this.themeUrl_ || loadTimeData.getString('themesGalleryUrl'));
- },
-
- // <if expr="chromeos">
- /**
- * ChromeOS only.
- * @private
- */
- openWallpaperManager_: function() {
- this.wallpaperBrowserProxy_.openWallpaperManager();
- },
- // </if>
-
- /** @private */
- onUseDefaultTap_: function() {
- this.appearanceBrowserProxy_.useDefaultTheme();
- },
-
- // <if expr="is_linux and not chromeos">
- /**
- * @param {boolean} useSystemTheme
- * @private
- */
- useSystemThemePrefChanged_: function(useSystemTheme) {
- this.useSystemTheme_ = useSystemTheme;
- },
-
- /**
- * @param {string} themeId
- * @param {boolean} useSystemTheme
- * @return {boolean} Whether to show the "USE CLASSIC" button.
- * @private
- */
- showUseClassic_: function(themeId, useSystemTheme) {
- return !!themeId || useSystemTheme;
- },
-
- /**
- * @param {string} themeId
- * @param {boolean} useSystemTheme
- * @return {boolean} Whether to show the "USE GTK+" button.
- * @private
- */
- showUseSystem_: function(themeId, useSystemTheme) {
- return (!!themeId || !useSystemTheme) &&
- !this.appearanceBrowserProxy_.isSupervised();
- },
-
- /**
- * @param {string} themeId
- * @param {boolean} useSystemTheme
- * @return {boolean} Whether to show the secondary area where "USE CLASSIC"
- * and "USE GTK+" buttons live.
- * @private
- */
- showThemesSecondary_: function(themeId, useSystemTheme) {
- return this.showUseClassic_(themeId, useSystemTheme) ||
- this.showUseSystem_(themeId, useSystemTheme);
- },
-
- /** @private */
- onUseSystemTap_: function() {
- this.appearanceBrowserProxy_.useSystemTheme();
- },
- // </if>
-
- /**
- * @param {string} themeId
- * @param {boolean} useSystemTheme
- * @private
- */
- themeChanged_: function(themeId, useSystemTheme) {
- if (this.prefs == undefined || useSystemTheme == undefined) {
- return;
- }
-
- if (themeId.length > 0 && themeId != AUTOGENERATED_THEME_ID) {
- assert(!useSystemTheme);
-
- this.appearanceBrowserProxy_.getThemeInfo(themeId).then(info => {
- this.themeSublabel_ = info.name;
- });
-
- this.themeUrl_ = 'https://chrome.google.com/webstore/detail/' + themeId;
- return;
- }
-
- this.themeUrl_ = '';
-
- if (themeId == AUTOGENERATED_THEME_ID) {
- this.themeSublabel_ = this.i18n('chromeColors');
- return;
- }
-
- let i18nId;
- // <if expr="is_linux and not chromeos">
- i18nId = useSystemTheme ? 'systemTheme' : 'classicTheme';
- // </if>
- // <if expr="not is_linux or chromeos">
- i18nId = 'chooseFromWebStore';
- // </if>
- this.themeSublabel_ = this.i18n(i18nId);
- },
-
- /** @private */
- onZoomLevelChange_: function() {
- chrome.settingsPrivate.setDefaultZoom(parseFloat(this.$.zoomLevel.value));
- },
-
- /**
- * @param {boolean} bookmarksBarVisible if bookmarks bar option is visible.
- * @return {string} 'first' if the argument is false or empty otherwise.
- * @private
- */
- getFirst_: function(bookmarksBarVisible) {
- return !bookmarksBarVisible ? 'first' : '';
- },
-
- /**
- * @see blink::PageZoomValuesEqual().
- * @param {number} zoom1
- * @param {number} zoom2
- * @return {boolean}
- * @private
- */
- zoomValuesEqual_: function(zoom1, zoom2) {
- return Math.abs(zoom1 - zoom2) <= 0.001;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.html b/chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.html
deleted file mode 100644
index 9af652850ac..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="href" src="chrome://resources/html/cr.html">
-<script src="fonts_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.js b/chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.js
deleted file mode 100644
index 3755b3124f6..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{
- * fontList: !Array<{
- * 0: string,
- * 1: (string|undefined),
- * 2: (string|undefined)}>,
- * extensionUrl: string
- * }}
- */
-let FontsData;
-
-cr.define('settings', function() {
- /** @interface */
- class FontsBrowserProxy {
- /**
- * @return {!Promise<!FontsData>} Fonts and the advanced font settings
- * extension URL.
- */
- fetchFontsData() {}
-
- observeAdvancedFontExtensionAvailable() {}
-
- openAdvancedFontSettings() {}
- }
-
- /**
- * @implements {settings.FontsBrowserProxy}
- */
- class FontsBrowserProxyImpl {
- /** @override */
- fetchFontsData() {
- return cr.sendWithPromise('fetchFontsData');
- }
-
- /** @override */
- observeAdvancedFontExtensionAvailable() {
- chrome.send('observeAdvancedFontExtensionAvailable');
- }
-
- /** @override */
- openAdvancedFontSettings() {
- chrome.send('openAdvancedFontSettings');
- }
- }
-
- cr.addSingletonGetter(FontsBrowserProxyImpl);
-
- return {
- FontsBrowserProxy: FontsBrowserProxy,
- FontsBrowserProxyImpl: FontsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/home_url_input.html b/chromium/chrome/browser/resources/settings/appearance_page/home_url_input.html
deleted file mode 100644
index 6d293ea3061..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/home_url_input.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="../controls/pref_control_behavior.html">
-<link rel="import" href="appearance_browser_proxy.html">
-
-<dom-module id="home-url-input">
- <template>
- <style>
- :host {
- cursor: auto;
- display: block;
- width: 100%;
- }
-
- cr-input {
- width: 100%;
- --cr-input-width: 50%;
- }
-
- cr-input::part(row-container) {
- justify-content: normal;
- }
- </style>
- <!-- Max length of 100 KB to prevent browser from freezing. -->
- <cr-input id="input" value="{{value}}" error-message="$i18n{notValid}"
- placeholder="$i18n{enterCustomWebAddress}" maxlength="102400"
- on-change="onChange_" on-keydown="onKeydown_" on-input="validate_"
- invalid="{{invalid}}" tabindex="[[getTabindex_(canTab)]]"
- disabled="[[isDisabled_(disabled, pref.*)]]" spellcheck="false"
- on-keyup="stopKeyEventPropagation_"
- on-keypress="stopKeyEventPropagation_">
- <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]">
- <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]"
- slot="suffix">
- </cr-policy-pref-indicator>
- </template>
- </cr-input>
- </template>
- <script src="home_url_input.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/home_url_input.js b/chromium/chrome/browser/resources/settings/appearance_page/home_url_input.js
deleted file mode 100644
index 4aa806c5625..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/home_url_input.js
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * `home-url-input` is a single-line text field intending to be used with
- * prefs.homepage
- */
-Polymer({
- is: 'home-url-input',
-
- behaviors: [CrPolicyPrefBehavior, PrefControlBehavior],
-
- properties: {
- /**
- * The preference object to control.
- * @type {!chrome.settingsPrivate.PrefObject|undefined}
- * @override
- */
- pref: {observer: 'prefChanged_'},
-
- /* Set to true to disable editing the input. */
- disabled: {type: Boolean, value: false, reflectToAttribute: true},
-
- canTab: Boolean,
-
- invalid: {type: Boolean, value: false},
-
- /* The current value of the input, reflected to/from |pref|. */
- value: {
- type: String,
- value: '',
- notify: true,
- },
- },
-
- /** @private {?settings.AppearanceBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.AppearanceBrowserProxyImpl.getInstance();
- this.noExtensionIndicator = true; // Prevent double indicator.
- },
-
- /**
- * Focuses the 'input' element.
- */
- focus: function() {
- this.$.input.focus();
- },
-
- /**
- * Polymer changed observer for |pref|.
- * @private
- */
- prefChanged_: function() {
- if (!this.pref) {
- return;
- }
-
- // Ignore updates while the input is focused so that user input is not
- // overwritten.
- if (this.$.input.focused) {
- return;
- }
-
- this.setInputValueFromPref_();
- },
-
- /** @private */
- setInputValueFromPref_: function() {
- assert(this.pref.type == chrome.settingsPrivate.PrefType.URL);
- this.value = /** @type {string} */ (this.pref.value);
- },
-
- /**
- * Gets a tab index for this control if it can be tabbed to.
- * @param {boolean} canTab
- * @return {number}
- * @private
- */
- getTabindex_: function(canTab) {
- return canTab ? 0 : -1;
- },
-
- /**
- * Change event handler for cr-input. Updates the pref value.
- * settings-input uses the change event because it is fired by the Enter key.
- * @private
- */
- onChange_: function() {
- if (this.invalid) {
- this.resetValue_();
- return;
- }
-
- assert(this.pref.type == chrome.settingsPrivate.PrefType.URL);
- this.set('pref.value', this.value);
- },
-
- /** @private */
- resetValue_: function() {
- this.invalid = false;
- this.setInputValueFromPref_();
- this.$.input.blur();
- },
-
- /**
- * Keydown handler to specify enter-key and escape-key interactions.
- * @param {!Event} event
- * @private
- */
- onKeydown_: function(event) {
- // If pressed enter when input is invalid, do not trigger on-change.
- if (event.key == 'Enter' && this.invalid) {
- event.preventDefault();
- } else if (event.key == 'Escape') {
- this.resetValue_();
- }
-
- this.stopKeyEventPropagation_(event);
- },
-
- /**
- * This function prevents unwanted change of selection of the containing
- * cr-radio-group, when the user traverses the input with arrow keys.
- * @param {!Event} e
- * @private
- */
- stopKeyEventPropagation_: function(e) {
- e.stopPropagation();
- },
-
- /**
- * @param {boolean} disabled
- * @return {boolean} Whether the element should be disabled.
- * @private
- */
- isDisabled_: function(disabled) {
- return disabled || this.isPrefEnforced();
- },
-
- /** @private */
- validate_: function() {
- if (this.value == '') {
- this.invalid = false;
- return;
- }
-
- this.browserProxy_.validateStartupPage(this.value).then(isValid => {
- this.invalid = !isValid;
- });
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.html b/chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.html
deleted file mode 100644
index 59b04eed214..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="href" src="chrome://resources/html/cr.html">
-<script src="wallpaper_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.js b/chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.js
deleted file mode 100644
index 90a2e4db7d9..00000000000
--- a/chromium/chrome/browser/resources/settings/appearance_page/wallpaper_browser_proxy.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class WallpaperBrowserProxy {
- /**
- * @return {!Promise<boolean>} Whether the wallpaper setting row should be
- * visible.
- */
- isWallpaperSettingVisible() {}
-
- /**
- * @return {!Promise<boolean>} Whether the wallpaper is policy controlled.
- */
- isWallpaperPolicyControlled() {}
-
- openWallpaperManager() {}
- }
-
- /**
- * @implements {settings.WallpaperBrowserProxy}
- */
- class WallpaperBrowserProxyImpl {
- /** @override */
- isWallpaperSettingVisible() {
- return cr.sendWithPromise('isWallpaperSettingVisible');
- }
-
- /** @override */
- isWallpaperPolicyControlled() {
- return cr.sendWithPromise('isWallpaperPolicyControlled');
- }
-
- /** @override */
- openWallpaperManager() {
- chrome.send('openWallpaperManager');
- }
- }
-
- cr.addSingletonGetter(WallpaperBrowserProxyImpl);
-
- return {
- WallpaperBrowserProxy: WallpaperBrowserProxy,
- WallpaperBrowserProxyImpl: WallpaperBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html b/chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html
deleted file mode 100644
index b2eb9d48433..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html
+++ /dev/null
@@ -1,127 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="../controls/settings_textarea.html">
-
-<dom-module id="settings-address-edit-dialog">
- <template>
- <style include="cr-shared-style settings-shared md-select">
- :host {
- white-space: nowrap;
- }
-
- .address-row {
- display: flex;
- }
-
- .address-column {
- margin-inline-end: 16px;
- width: calc((var(--settings-input-max-width) - 16px) / 2);
- }
-
- #select-row {
- display: block;
- /* Fix issue with focus animation making labels wiggle. */
- transform: translate3d(0, 0, 0);
- }
-
- .md-select {
- --md-select-width: var(--settings-input-max-width);
- }
-
- .long {
- width: var(--settings-input-max-width);
- }
-
- cr-input {
- --cr-input-error-display: none;
- }
-
- cr-input:not(.last-row),
- settings-textarea,
- .md-select {
- margin-bottom: var(--cr-form-field-bottom-spacing);
- }
-
- #dialog::part(body-container) {
- max-height: 450px;
- }
-
- @media all and (max-height: 714px) {
- #dialog::part(body-container) {
- max-height: 270px;
- }
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">[[title_]]</div>
- <div slot="body">
- <template is="dom-repeat" items="[[addressWrapper_]]">
- <div class="address-row">
- <template is="dom-repeat" items="[[item]]">
- <template is="dom-if" if="[[item.isTextArea]]">
- <settings-textarea label="[[item.component.fieldName]]"
- value="{{item.value}}" on-value-changed="updateCanSave_"
- class$="address-column [[long_(item)]]" autofocus
- spellcheck="false">
- </settings-textarea>
- </template>
- <template is="dom-if" if="[[!item.isTextArea]]">
- <cr-input type="text" label="[[item.component.fieldName]]"
- autofocus value="{{item.value}}" spellcheck="false"
- on-value-changed="updateCanSave_"
- class$="address-column [[long_(item)]]">
- </cr-input>
- </template>
- </template>
- </div>
- </template>
- <div id="select-row" class="address-row">
- <label id="select-label" class="cr-form-field-label">
- $i18n{addressCountry}
- </label>
- <select class="md-select" aria-labelledby="select-label"
- value="[[countryCode_]]" on-change="onCountryChange_">
- <option value=""></option>
- <template is="dom-repeat" items="[[countries_]]">
- <option value="[[getCode_(item)]]"
- disabled="[[isDivision_(item)]]">
- [[getName_(item)]]
- </option>
- </template>
- </select>
- </div>
- <div class="address-row">
- <cr-input id="phoneInput" type="text" label="$i18n{addressPhone}"
- class="address-column last-row" on-value-changed="updateCanSave_"
- value="{{phoneNumber_}}" spellcheck="false">
- </cr-input>
- <cr-input id="emailInput" type="text" label="$i18n{addressEmail}"
- on-value-changed="updateCanSave_" value="{{email_}}"
- class="address-column long last-row" spellcheck="false">
- </cr-input>
- </div>
- </div>
- <div slot="button-container">
- <cr-button id="cancelButton" class="cancel-button"
- on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button id="saveButton" class="action-button"
- disabled="[[!canSave_]]" on-click="onSaveButtonTap_">
- $i18n{save}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="address_edit_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js b/chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js
deleted file mode 100644
index 7c5e40c9957..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'password-edit-dialog' is the dialog that allows showing a
- * saved password.
- */
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-address-edit-dialog',
-
- behaviors: [
- I18nBehavior,
- ],
-
- properties: {
- /** @type {chrome.autofillPrivate.AddressEntry} */
- address: Object,
-
- /** @private */
- title_: String,
-
- /** @private {!Array<!chrome.autofillPrivate.CountryEntry>} */
- countries_: Array,
-
- /**
- * Updates the address wrapper.
- * @private {string|undefined}
- */
- countryCode_: {
- type: String,
- observer: 'onUpdateCountryCode_',
- },
-
- /** @private {!Array<!Array<!settings.address.AddressComponentUI>>} */
- addressWrapper_: Object,
-
- /** @private */
- phoneNumber_: String,
-
- /** @private */
- email_: String,
-
- /** @private */
- canSave_: Boolean,
- },
-
- /** @override */
- attached: function() {
- this.countryInfo = settings.address.CountryDetailManagerImpl.getInstance();
- this.countryInfo.getCountryList().then(countryList => {
- this.countries_ = countryList;
-
- this.title_ =
- this.i18n(this.address.guid ? 'editAddressTitle' : 'addAddressTitle');
-
- // |phoneNumbers| and |emailAddresses| are a single item array.
- // See crbug.com/497934 for details.
- this.phoneNumber_ =
- this.address.phoneNumbers ? this.address.phoneNumbers[0] : '';
- this.email_ =
- this.address.emailAddresses ? this.address.emailAddresses[0] : '';
-
- this.async(() => {
- if (this.countryCode_ == this.address.countryCode) {
- this.updateAddressWrapper_();
- } else {
- this.countryCode_ = this.address.countryCode;
- }
- });
- });
-
- // Open is called on the dialog after the address wrapper has been updated.
- },
-
- /**
- * Returns a class to denote how long this entry is.
- * @param {settings.address.AddressComponentUI} setting
- * @return {string}
- */
- long_: function(setting) {
- return setting.component.isLongField ? 'long' : '';
- },
-
- /**
- * Updates the wrapper that represents this address in the country's format.
- * @private
- */
- updateAddressWrapper_: function() {
- // Default to the last country used if no country code is provided.
- const countryCode = this.countryCode_ || this.countries_[0].countryCode;
- this.countryInfo.getAddressFormat(countryCode).then(format => {
- this.addressWrapper_ = format.components.map(
- component => component.row.map(
- c => new settings.address.AddressComponentUI(this.address, c)));
-
- // Flush dom before resize and savability updates.
- Polymer.dom.flush();
-
- this.updateCanSave_();
-
- this.fire('on-update-address-wrapper'); // For easier testing.
-
- const dialog = /** @type {HTMLDialogElement} */ (this.$.dialog);
- if (!dialog.open) {
- dialog.showModal();
- }
- });
- },
-
- updateCanSave_: function() {
- const inputs = this.$.dialog.querySelectorAll('.address-column, select');
-
- for (let i = 0; i < inputs.length; ++i) {
- if (inputs[i].value) {
- this.canSave_ = true;
- this.fire('on-update-can-save'); // For easier testing.
- return;
- }
- }
-
- this.canSave_ = false;
- this.fire('on-update-can-save'); // For easier testing.
- },
-
- /**
- * @param {!chrome.autofillPrivate.CountryEntry} country
- * @return {string}
- * @private
- */
- getCode_: function(country) {
- return country.countryCode || 'SPACER';
- },
-
- /**
- * @param {!chrome.autofillPrivate.CountryEntry} country
- * @return {string}
- * @private
- */
- getName_: function(country) {
- return country.name || '------';
- },
-
- /**
- * @param {!chrome.autofillPrivate.CountryEntry} country
- * @return {boolean}
- * @private
- */
- isDivision_: function(country) {
- return !country.countryCode;
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.cancel();
- },
-
- /**
- * Handler for tapping the save button.
- * @private
- */
- onSaveButtonTap_: function() {
- // The Enter key can call this function even if the button is disabled.
- if (!this.canSave_) {
- return;
- }
-
- // Set a default country if none is set.
- if (!this.address.countryCode) {
- this.address.countryCode = this.countries_[0].countryCode;
- }
-
- this.address.phoneNumbers = this.phoneNumber_ ? [this.phoneNumber_] : [];
- this.address.emailAddresses = this.email_ ? [this.email_] : [];
-
- this.fire('save-address', this.address);
- this.$.dialog.close();
- },
-
- /**
- * Syncs the country code back to the address and rebuilds the address wrapper
- * for the new location.
- * @param {string|undefined} countryCode
- * @private
- */
- onUpdateCountryCode_: function(countryCode) {
- this.address.countryCode = countryCode;
- this.updateAddressWrapper_();
- },
-
- /** @private */
- onCountryChange_: function() {
- const countrySelect = /** @type {!HTMLSelectElement} */ (this.$$('select'));
- this.countryCode_ = countrySelect.value;
- },
-});
-})();
-
-cr.define('settings.address', function() {
- /**
- * Creates a wrapper against a single data member for an address.
- */
- class AddressComponentUI {
- /**
- * @param {!chrome.autofillPrivate.AddressEntry} address
- * @param {!chrome.autofillPrivate.AddressComponent} component
- */
- constructor(address, component) {
- Object.defineProperty(this, 'value', {
- get: function() {
- return this.getValue_();
- },
- set: function(newValue) {
- this.setValue_(newValue);
- },
- });
- this.address_ = address;
- this.component = component;
- this.isTextArea =
- component.field == chrome.autofillPrivate.AddressField.ADDRESS_LINES;
- }
-
- /**
- * Gets the value from the address that's associated with this component.
- * @return {string|undefined}
- * @private
- */
- getValue_() {
- const address = this.address_;
- switch (this.component.field) {
- case chrome.autofillPrivate.AddressField.FULL_NAME:
- // |fullNames| is a single item array. See crbug.com/497934 for
- // details.
- return address.fullNames ? address.fullNames[0] : undefined;
- case chrome.autofillPrivate.AddressField.COMPANY_NAME:
- return address.companyName;
- case chrome.autofillPrivate.AddressField.ADDRESS_LINES:
- return address.addressLines;
- case chrome.autofillPrivate.AddressField.ADDRESS_LEVEL_1:
- return address.addressLevel1;
- case chrome.autofillPrivate.AddressField.ADDRESS_LEVEL_2:
- return address.addressLevel2;
- case chrome.autofillPrivate.AddressField.ADDRESS_LEVEL_3:
- return address.addressLevel3;
- case chrome.autofillPrivate.AddressField.POSTAL_CODE:
- return address.postalCode;
- case chrome.autofillPrivate.AddressField.SORTING_CODE:
- return address.sortingCode;
- case chrome.autofillPrivate.AddressField.COUNTRY_CODE:
- return address.countryCode;
- default:
- assertNotReached();
- }
- }
-
- /**
- * Sets the value in the address that's associated with this component.
- * @param {string} value
- * @private
- */
- setValue_(value) {
- const address = this.address_;
- switch (this.component.field) {
- case chrome.autofillPrivate.AddressField.FULL_NAME:
- address.fullNames = [value];
- break;
- case chrome.autofillPrivate.AddressField.COMPANY_NAME:
- address.companyName = value;
- break;
- case chrome.autofillPrivate.AddressField.ADDRESS_LINES:
- address.addressLines = value;
- break;
- case chrome.autofillPrivate.AddressField.ADDRESS_LEVEL_1:
- address.addressLevel1 = value;
- break;
- case chrome.autofillPrivate.AddressField.ADDRESS_LEVEL_2:
- address.addressLevel2 = value;
- break;
- case chrome.autofillPrivate.AddressField.ADDRESS_LEVEL_3:
- address.addressLevel3 = value;
- break;
- case chrome.autofillPrivate.AddressField.POSTAL_CODE:
- address.postalCode = value;
- break;
- case chrome.autofillPrivate.AddressField.SORTING_CODE:
- address.sortingCode = value;
- break;
- case chrome.autofillPrivate.AddressField.COUNTRY_CODE:
- address.countryCode = value;
- break;
- default:
- assertNotReached();
- }
- }
- }
-
- /** @interface */
- class CountryDetailManager {
- /**
- * Gets the list of available countries.
- * The default country will be first, followed by a separator, followed by
- * an alphabetized list of countries available.
- * @return {!Promise<!Array<!chrome.autofillPrivate.CountryEntry>>}
- */
- getCountryList() {}
-
- /**
- * Gets the address format for a given country code.
- * @param {string} countryCode
- * @return {!Promise<!chrome.autofillPrivate.AddressComponents>}
- */
- getAddressFormat(countryCode) {}
- }
-
- /**
- * Default implementation. Override for testing.
- * @implements {settings.address.CountryDetailManager}
- */
- class CountryDetailManagerImpl {
- /** @override */
- getCountryList() {
- return new Promise(function(callback) {
- chrome.autofillPrivate.getCountryList(callback);
- });
- }
-
- /** @override */
- getAddressFormat(countryCode) {
- return new Promise(function(callback) {
- chrome.autofillPrivate.getAddressComponents(countryCode, callback);
- });
- }
- }
-
- cr.addSingletonGetter(CountryDetailManagerImpl);
-
- return {
- AddressComponentUI: AddressComponentUI,
- CountryDetailManager: CountryDetailManager,
- CountryDetailManagerImpl: CountryDetailManagerImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/autofill_page.html b/chromium/chrome/browser/resources/settings/autofill_page/autofill_page.html
deleted file mode 100644
index 51208f35ebe..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/autofill_page.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="autofill_section.html">
-<link rel="import" href="passwords_section.html">
-<link rel="import" href="payments_section.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../open_window_proxy.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-autofill-page">
- <template>
- <style include="settings-shared">
- cr-link-row {
- --cr-icon-button-margin-start: 20px;
- }
- cr-link-row:not([hidden]) + cr-link-row {
- border-top: var(--cr-separator-line);
- }
- </style>
- <settings-animated-pages id="pages" section="autofill"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <cr-link-row id="passwordManagerButton" start-icon="settings20:vpn-key"
- label="$i18n{passwords}" on-click="onPasswordsClick_"></cr-link-row>
- <cr-link-row id="paymentManagerButton"
- start-icon="settings20:credit-card" label="$i18n{creditCards}"
- on-click="onPaymentsClick_"></cr-link-row>
- <cr-link-row id="addressesManagerButton"
- start-icon="settings20:location-on" label="$i18n{addressesTitle}"
- on-click="onAddressesClick_"></cr-link-row>
- </div>
- <template is="dom-if" route-path="/passwords">
- <settings-subpage
- associated-control="[[$$('#passwordManagerButton')]]"
- page-title="$i18n{passwords}"
- learn-more-url="$i18n{passwordManagerLearnMoreURL}"
- search-label="$i18n{searchPasswords}"
- search-term="{{passwordFilter_}}">
- <passwords-section id="passwordSection" filter="[[passwordFilter_]]"
- prefs="{{prefs}}">
- </passwords-section>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/payments">
- <settings-subpage
- associated-control="[[$$('#paymentManagerButton')]]"
- page-title="$i18n{creditCards}"
- learn-more-url="$i18n{paymentMethodsLearnMoreURL}">
- <settings-payments-section id="paymentsSection" prefs="{{prefs}}">
- </settings-payments-section>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/addresses">
- <settings-subpage
- associated-control="[[$$('#addressesManagerButton')]]"
- page-title="$i18n{addressesTitle}">
- <settings-autofill-section id="autofillSection" prefs="{{prefs}}">
- </settings-autofill-section>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- </template>
- <script src="autofill_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/autofill_page.js b/chromium/chrome/browser/resources/settings/autofill_page/autofill_page.js
deleted file mode 100644
index 4e9c3437081..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/autofill_page.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-autofill-page' is the settings page containing settings for
- * passwords, payment methods and addresses.
- */
-Polymer({
- is: 'settings-autofill-page',
-
- behaviors: [PrefsBehavior],
-
- properties: {
- /** @private Filter applied to passwords and password exceptions. */
- passwordFilter_: String,
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.PASSWORDS) {
- map.set(settings.routes.PASSWORDS.path, '#passwordManagerButton');
- }
- if (settings.routes.PAYMENTS) {
- map.set(settings.routes.PAYMENTS.path, '#paymentManagerButton');
- }
- if (settings.routes.ADDRESSES) {
- map.set(settings.routes.ADDRESSES.path, '#addressesManagerButton');
- }
-
- return map;
- },
- },
- },
-
- /**
- * Shows the manage addresses sub page.
- * @param {!Event} event
- * @private
- */
- onAddressesClick_: function(event) {
- settings.navigateTo(settings.routes.ADDRESSES);
- },
-
- /**
- * Shows the manage payment methods sub page.
- * @private
- */
- onPaymentsClick_: function() {
- settings.navigateTo(settings.routes.PAYMENTS);
- },
-
- /**
- * Shows a page to manage passwords. This is either the passwords sub page or
- * the Google Password Manager page.
- * @private
- */
- onPasswordsClick_: function() {
- PasswordManagerImpl.getInstance().recordPasswordsPageAccessInSettings();
- loadTimeData.getBoolean('navigateToGooglePasswordManager') ?
- settings.OpenWindowProxyImpl.getInstance().openURL(
- loadTimeData.getString('googlePasswordManagerUrl')) :
- settings.navigateTo(settings.routes.PASSWORDS);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/autofill_section.html b/chromium/chrome/browser/resources/settings/autofill_page/autofill_section.html
deleted file mode 100644
index d7814621568..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/autofill_section.html
+++ /dev/null
@@ -1,100 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="address_edit_dialog.html">
-<link rel="import" href="passwords_shared_css.html">
-
-<dom-module id="settings-autofill-section">
- <template>
- <style include="settings-shared passwords-shared">
- #addressList .start {
- display: flex;
- overflow: hidden;
- }
-
- #addressSummary {
- display: flex;
- flex: 1;
- overflow: hidden;
- }
- </style>
- <settings-toggle-button id="autofillProfileToggle"
- class="settings-box first"
- aria-label="$i18n{addressesTitle}" no-extension-indicator
- label="$i18n{enableProfilesLabel}"
- sub-label="$i18n{enableProfilesSublabel}"
- pref="{{prefs.autofill.profile_enabled}}">
- </settings-toggle-button>
- <template is="dom-if" if="[[prefs.autofill.profile_enabled.extensionId]]">
- <div class="settings-box continuation">
- <extension-controlled-indicator class="start"
- id="autofillExtensionIndicator"
- extension-id="[[prefs.autofill.profile_enabled.extensionId]]"
- extension-name="[[prefs.autofill.profile_enabled.controlledByName]]"
- extension-can-be-disabled="[[
- prefs.autofill.profile_enabled.extensionCanBeDisabled]]">
- </extension-controlled-indicator>
- </div>
- </template>
- <div class="settings-box continuation">
- <h2 class="start">$i18n{addresses}</h2>
- <cr-button id="addAddress" class="header-aligned-button"
- on-click="onAddAddressTap_"
- hidden$="[[!prefs.autofill.profile_enabled.value]]">
- $i18n{add}
- </cr-button>
- </div>
- <div class="list-frame">
- <div id="addressList" class="vertical-list">
- <template is="dom-repeat" items="[[addresses]]">
- <div class="list-item">
- <div class="start">
- <span id="addressSummary">
- <span class="ellipses">
- [[item.metadata.summaryLabel]]
- </span>
- <span class="ellipses">
- [[item.metadata.summarySublabel]]
- </span>
- </span>
- </div>
- <template is="dom-if" if="[[item.metadata.isLocal]]">
- <cr-icon-button class="icon-more-vert" id="addressMenu"
- on-click="onAddressMenuTap_" title="$i18n{moreActions}">
- </cr-icon-button>
- </template>
- <template is="dom-if" if="[[!item.metadata.isLocal]]">
- <cr-icon-button actionable class="icon-external"
- on-click="onRemoteEditAddressTap_"></cr-icon-button>
- </template>
- </div>
- </template>
- </div>
- <div id="noAddressesLabel" class="list-item"
- hidden$="[[hasSome_(addresses)]]">
- $i18n{noAddressesFound}
- </div>
- </div>
- <cr-action-menu id="addressSharedMenu">
- <button id="menuEditAddress" class="dropdown-item"
- on-click="onMenuEditAddressTap_">$i18n{edit}</button>
- <button id="menuRemoveAddress" class="dropdown-item"
- on-click="onMenuRemoveAddressTap_">$i18n{removeAddress}</button>
- </cr-action-menu>
- <template is="dom-if" if="[[showAddressDialog_]]" restamp>
- <settings-address-edit-dialog address="[[activeAddress]]"
- on-close="onAddressDialogClose_">
- </settings-address-edit-dialog>
- </template>
- </template>
- <script src="autofill_section.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/autofill_section.js b/chromium/chrome/browser/resources/settings/autofill_page/autofill_section.js
deleted file mode 100644
index ba786243f76..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/autofill_section.js
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-autofill-section' is the section containing saved
- * addresses for use in autofill and payments APIs.
- */
-
-/**
- * Interface for all callbacks to the autofill API.
- * @interface
- */
-class AutofillManager {
- /**
- * Add an observer to the list of personal data.
- * @param {function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>):void} listener
- */
- setPersonalDataManagerListener(listener) {}
-
- /**
- * Remove an observer from the list of personal data.
- * @param {function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>):void} listener
- */
- removePersonalDataManagerListener(listener) {}
-
- /**
- * Request the list of addresses.
- * @param {function(!Array<!AutofillManager.AddressEntry>):void} callback
- */
- getAddressList(callback) {}
-
- /**
- * Saves the given address.
- * @param {!AutofillManager.AddressEntry} address
- */
- saveAddress(address) {}
-
- /** @param {string} guid The guid of the address to remove. */
- removeAddress(guid) {}
-}
-
-/** @typedef {chrome.autofillPrivate.AddressEntry} */
-AutofillManager.AddressEntry;
-
-/**
- * Implementation that accesses the private API.
- * @implements {AutofillManager}
- */
-class AutofillManagerImpl {
- /** @override */
- setPersonalDataManagerListener(listener) {
- chrome.autofillPrivate.onPersonalDataChanged.addListener(listener);
- }
-
- /** @override */
- removePersonalDataManagerListener(listener) {
- chrome.autofillPrivate.onPersonalDataChanged.removeListener(listener);
- }
-
- /** @override */
- getAddressList(callback) {
- chrome.autofillPrivate.getAddressList(callback);
- }
-
- /** @override */
- saveAddress(address) {
- chrome.autofillPrivate.saveAddress(address);
- }
-
- /** @override */
- removeAddress(guid) {
- chrome.autofillPrivate.removeEntry(assert(guid));
- }
-}
-
-cr.addSingletonGetter(AutofillManagerImpl);
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-autofill-section',
-
- properties: {
- /**
- * An array of saved addresses.
- * @type {!Array<!AutofillManager.AddressEntry>}
- */
- addresses: Array,
-
- /**
- * The model for any address related action menus or dialogs.
- * @private {?chrome.autofillPrivate.AddressEntry}
- */
- activeAddress: Object,
-
- /** @private */
- showAddressDialog_: Boolean,
- },
-
- listeners: {
- 'save-address': 'saveAddress_',
- },
-
- /**
- * The element to return focus to, when the currently active dialog is
- * closed.
- * @private {?HTMLElement}
- */
- activeDialogAnchor_: null,
-
- /**
- * @type {AutofillManager}
- * @private
- */
- autofillManager_: null,
-
- /**
- * @type {?function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>)}
- * @private
- */
- setPersonalDataListener_: null,
-
- /** @override */
- attached: function() {
- // Create listener functions.
- /** @type {function(!Array<!AutofillManager.AddressEntry>)} */
- const setAddressesListener = addressList => {
- this.addresses = addressList;
- };
-
- /**
- * @type {function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>)}
- */
- const setPersonalDataListener = (addressList, cardList) => {
- this.addresses = addressList;
- };
-
- // Remember the bound reference in order to detach.
- this.setPersonalDataListener_ = setPersonalDataListener;
-
- // Set the managers. These can be overridden by tests.
- this.autofillManager_ = AutofillManagerImpl.getInstance();
-
- // Request initial data.
- this.autofillManager_.getAddressList(setAddressesListener);
-
- // Listen for changes.
- this.autofillManager_.setPersonalDataManagerListener(
- setPersonalDataListener);
-
- // Record that the user opened the address settings.
- chrome.metricsPrivate.recordUserAction('AutofillAddressesViewed');
- },
-
- /** @override */
- detached: function() {
- this.autofillManager_.removePersonalDataManagerListener(
- /**
- @type {function(!Array<!AutofillManager.AddressEntry>,
- !Array<!PaymentsManager.CreditCardEntry>)}
- */
- (this.setPersonalDataListener_));
- },
-
- /**
- * Open the address action menu.
- * @param {!Event} e The polymer event.
- * @private
- */
- onAddressMenuTap_: function(e) {
- const menuEvent = /** @type {!{model: !{item: !Object}}} */ (e);
- const item = menuEvent.model.item;
-
- // Copy item so dialog won't update model on cancel.
- this.activeAddress = /** @type {!chrome.autofillPrivate.AddressEntry} */ (
- Object.assign({}, item));
-
- const dotsButton = /** @type {!HTMLElement} */ (e.target);
- /** @type {!CrActionMenuElement} */ (this.$.addressSharedMenu)
- .showAt(dotsButton);
- this.activeDialogAnchor_ = dotsButton;
- },
-
- /**
- * Handles tapping on the "Add address" button.
- * @param {!Event} e The polymer event.
- * @private
- */
- onAddAddressTap_: function(e) {
- e.preventDefault();
- this.activeAddress = {};
- this.showAddressDialog_ = true;
- this.activeDialogAnchor_ = this.$.addAddress;
- },
-
- /** @private */
- onAddressDialogClose_: function() {
- this.showAddressDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.activeDialogAnchor_));
- this.activeDialogAnchor_ = null;
- },
-
- /**
- * Handles tapping on the "Edit" address button.
- * @param {!Event} e The polymer event.
- * @private
- */
- onMenuEditAddressTap_: function(e) {
- e.preventDefault();
- this.showAddressDialog_ = true;
- this.$.addressSharedMenu.close();
- },
-
- /** @private */
- onRemoteEditAddressTap_: function() {
- window.open(loadTimeData.getString('manageAddressesUrl'));
- },
-
- /**
- * Handles tapping on the "Remove" address button.
- * @private
- */
- onMenuRemoveAddressTap_: function() {
- this.autofillManager_.removeAddress(
- /** @type {string} */ (this.activeAddress.guid));
- this.$.addressSharedMenu.close();
- },
-
- /**
- * Returns true if the list exists and has items.
- * @param {Array<Object>} list
- * @return {boolean}
- * @private
- */
- hasSome_: function(list) {
- return !!(list && list.length);
- },
-
- /**
- * Listens for the save-address event, and calls the private API.
- * @param {!Event} event
- * @private
- */
- saveAddress_: function(event) {
- this.autofillManager_.saveAddress(event.detail);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.html b/chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.html
deleted file mode 100644
index 12f53887d68..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="blocking_request_manager.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.js b/chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.js
deleted file mode 100644
index b82aa59e60b..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/blocking_request_manager.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Helper class for making blocking requests that are resolved
- * elsewhere in the DOM.
- */
-cr.define('settings', function() {
- class BlockingRequestManager {
- /**
- * @param {Function=} makeRequest Function to initiate flow for request. If
- * no function is provided, it defaults to this.resolve, i.e. it
- * immediately resolves all requests.
- */
- constructor(makeRequest) {
- this.makeRequest_ = makeRequest || this.resolve;
- /**
- * @private {Function} callback Provided in requests and called when the
- * request is resolved.
- */
- this.callback_ = null;
- }
-
- /**
- * Make a blocking request.
- * @param {Function} callback Function to be called if/when the request is
- * successfully resolved.
- */
- request(callback) {
- this.callback_ = callback;
- this.makeRequest_();
- }
-
- /** Called if/when request is resolved successfully. */
- resolve() {
- this.callback_();
- }
- }
-
- return {
- BlockingRequestManager: BlockingRequestManager,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html b/chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
deleted file mode 100644
index 30f32f21490..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
+++ /dev/null
@@ -1,107 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-credit-card-edit-dialog">
- <template>
- <style include="cr-shared-style settings-shared md-select">
- cr-input {
- --cr-input-error-display: none;
- margin-bottom: var(--cr-form-field-bottom-spacing);
- width: var(--settings-input-max-width);
- }
-
- .md-select + .md-select {
- margin-inline-start: 8px;
- }
-
- /* Prevent focus-outline from being chopped by bottom of dialog body. */
- .md-select {
- margin-bottom: 2px;
- }
-
- #expired {
- align-items: center;
- background-color: var(--paper-red-50);
- color: var(--settings-error-color);
- display: flex;
- height: 40px;
- margin-top: 12px;
- padding: 0 0 0 8px;
- }
-
- @media (prefers-color-scheme: dark) {
- #expired {
- background-color: unset;
- font-weight: bold;
- padding: 0;
- }
- }
-
- #month {
- width: 70px;
- }
-
- #saved-to-this-device-only-label {
- margin-top: var(--cr-form-field-bottom-spacing);
- }
-
- #year {
- width: 100px;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">[[title_]]</div>
- <div slot="body">
- <cr-input id="nameInput" label="$i18n{creditCardName}"
- value="{{creditCard.name}}" autofocus spellcheck="false"
- on-input="onCreditCardNameOrNumberChanged_">
- </cr-input>
- <cr-input id="numberInput" label="$i18n{creditCardNumber}"
- value="{{creditCard.cardNumber}}"
- on-input="onCreditCardNameOrNumberChanged_">
- </cr-input>
- <label id="creditCardExpiration" class="cr-form-field-label">
- $i18n{creditCardExpiration}
- </label>
- <select class="md-select" id="month" value="[[expirationMonth_]]"
- on-change="onMonthChange_"
- aria-labelledby="creditCardExpiration">
- <template is="dom-repeat" items="[[monthList_]]">
- <option>[[item]]</option>
- </template>
- </select>
- <select class="md-select" id="year" value="[[expirationYear_]]"
- on-change="onYearChange_"
- aria-label="$i18n{creditCardExpirationYear}">
- <template is="dom-repeat" items="[[yearList_]]">
- <option>[[item]]</option>
- </template>
- </select>
- <span id="expired"
- hidden="[[!checkIfCardExpired_(expirationMonth_, expirationYear_)]]"
- >
- $i18n{creditCardExpired}
- </span>
- <div id="saved-to-this-device-only-label">
- $i18n{savedToThisDeviceOnly}
- </div>
- </div>
- <div slot="button-container">
- <cr-button id="cancelButton" class="cancel-button"
- on-click="onCancelButtonTap_">$i18n{cancel}</cr-button>
- <cr-button id="saveButton" class="action-button"
- on-click="onSaveButtonTap_" disabled>$i18n{save}</cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="credit_card_edit_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.js b/chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.js
deleted file mode 100644
index a4ca3337d15..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.js
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-credit-card-edit-dialog' is the dialog that allows
- * editing or creating a credit card entry.
- */
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-credit-card-edit-dialog',
-
- properties: {
- /**
- * The credit card being edited.
- * @type {!chrome.autofillPrivate.CreditCardEntry}
- */
- creditCard: Object,
-
- /**
- * The actual title that's used for this dialog. Will be context sensitive
- * based on if |creditCard| is being created or edited.
- * @private
- */
- title_: String,
-
- /**
- * The list of months to show in the dropdown.
- * @private {!Array<string>}
- */
- monthList_: {
- type: Array,
- value: [
- '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'
- ],
- },
-
- /**
- * The list of years to show in the dropdown.
- * @private {!Array<string>}
- */
- yearList_: Array,
-
- /** @private */
- expirationYear_: String,
-
- /** @private {string|undefined} */
- expirationMonth_: String,
- },
-
- behaviors: [
- I18nBehavior,
- ],
-
- /**
- * @return {boolean} True iff the provided expiration date is passed.
- * @private
- */
- checkIfCardExpired_: function(expirationMonth_, expirationYear_) {
- const now = new Date();
- return (
- expirationYear_ < now.getFullYear() ||
- (expirationYear_ == now.getFullYear() &&
- expirationMonth_ <= now.getMonth()));
- },
-
- /** @override */
- attached: function() {
- this.title_ = this.i18n(
- this.creditCard.guid ? 'editCreditCardTitle' : 'addCreditCardTitle');
-
- // Needed to initialize the disabled state of the Save button.
- this.onCreditCardNameOrNumberChanged_();
-
- // Add a leading '0' if a month is 1 char.
- if (this.creditCard.expirationMonth.length == 1) {
- this.creditCard.expirationMonth = '0' + this.creditCard.expirationMonth;
- }
-
- const date = new Date();
- let firstYear = date.getFullYear();
- let lastYear = firstYear + 19; // Show next 19 years (20 total).
- let selectedYear = parseInt(this.creditCard.expirationYear, 10);
-
- // |selectedYear| must be valid and between first and last years.
- if (!selectedYear) {
- selectedYear = firstYear;
- } else if (selectedYear < firstYear) {
- firstYear = selectedYear;
- } else if (selectedYear > lastYear) {
- lastYear = selectedYear;
- }
-
- const yearList = [];
- for (let i = firstYear; i <= lastYear; ++i) {
- yearList.push(i.toString());
- }
- this.yearList_ = yearList;
-
- this.async(() => {
- this.expirationYear_ = selectedYear.toString();
- this.expirationMonth_ = this.creditCard.expirationMonth;
- this.$.dialog.showModal();
- });
- },
-
- /** Closes the dialog. */
- close: function() {
- this.$.dialog.close();
- },
-
- /**
- * Handler for tapping the 'cancel' button. Should just dismiss the dialog.
- * @private
- */
- onCancelButtonTap_: function() {
- this.$.dialog.cancel();
- },
-
- /**
- * Handler for tapping the save button.
- * @private
- */
- onSaveButtonTap_: function() {
- if (!this.saveEnabled_()) {
- return;
- }
-
- // If the card is expired, reflect the error to the user.
- // Otherwise, update the card, save and close the dialog.
- if (!this.checkIfCardExpired_(
- this.expirationMonth_, this.expirationYear_)) {
- this.creditCard.expirationYear = this.expirationYear_;
- this.creditCard.expirationMonth = this.expirationMonth_;
- this.fire('save-credit-card', this.creditCard);
- this.close();
- }
- },
-
- /** @private */
- onMonthChange_: function() {
- this.expirationMonth_ = this.monthList_[this.$.month.selectedIndex];
- },
-
- /** @private */
- onYearChange_: function() {
- this.expirationYear_ = this.yearList_[this.$.year.selectedIndex];
- },
-
- /** @private */
- onCreditCardNameOrNumberChanged_: function() {
- this.$.saveButton.disabled = !this.saveEnabled_();
- },
-
- /** @private */
- saveEnabled_: function() {
- return (this.creditCard.name && this.creditCard.name.trim()) ||
- (this.creditCard.cardNumber && this.creditCard.cardNumber.trim());
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.html b/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.html
deleted file mode 100644
index 22ebf9b02a9..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="credit_card_list_entry.html">
-<link rel="import" href="passwords_shared_css.html">
-
-<dom-module id="settings-credit-card-list">
- <template>
- <style include="settings-shared passwords-shared">
- .expiration-column {
- align-items: center;
- display: flex;
- flex: 1;
- }
- </style>
- <div id="creditCardsHeading" class="list-item column-header"
- hidden$="[[!hasSome_(creditCards)]]">
- <div class="type-column">$i18n{creditCardType}</div>
- <div class="expiration-column">$i18n{creditCardExpiration}</div>
- </div>
- <div class="vertical-list list-with-header">
- <template is="dom-repeat" items="[[creditCards]]">
- <settings-credit-card-list-entry credit-card="[[item]]">
- </settings-credit-card-list-entry>
- </template>
- </div>
- <div id="noCreditCardsLabel" class="list-item"
- hidden$="[[hasSome_(creditCards)]]">
- $i18n{noCreditCardsFound}
- </div>
- </template>
- <script src="credit_card_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.js b/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.js
deleted file mode 100644
index 503bbe22969..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'credit-card-list' is a a list of credit cards to be shown in
- * the settings page.
- */
-
-Polymer({
- is: 'settings-credit-card-list',
-
- properties: {
- /**
- * An array of all saved credit cards.
- * @type {!Array<!PaymentsManager.CreditCardEntry>}
- */
- creditCards: Array,
- },
-
- /**
- * Returns true if the list exists and has items.
- * @param {Array<Object>} list
- * @return {boolean}
- * @private
- */
- hasSome_: function(list) {
- return !!(list && list.length);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html b/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
deleted file mode 100644
index 4f62b13e1f0..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="passwords_shared_css.html">
-
-<dom-module id="settings-credit-card-list-entry">
- <template>
- <style include="settings-shared passwords-shared">
- .expiration-column {
- align-items: center;
- display: flex;
- flex: 1;
- }
-
- #creditCardExpiration {
- flex: 1;
- }
-
- .payments-label {
- color: var(--cr-secondary-text-color);
- margin-inline-start: 16px;
- }
- </style>
- <div class="list-item">
- <div class="type-column">
- <span id="creditCardLabel">[[creditCard.metadata.summaryLabel]]</span>
- <span class="payments-label"
- hidden$="[[creditCard.metadata.isLocal]]">
- <span hidden$="[[creditCard.metadata.isCached]]">
- $i18n{googlePayments}
- </span>
- <span hidden$="[[!creditCard.metadata.isCached]]">
- $i18n{googlePaymentsCached}
- </span>
- </span>
- </div>
- <div class="expiration-column">
- <div id="creditCardExpiration">
- [[creditCard.expirationMonth]]/[[creditCard.expirationYear]]
- </div>
- <template is="dom-if" if="[[showDots_(creditCard.metadata)]]">
- <cr-icon-button class="icon-more-vert" id="creditCardMenu"
- title="$i18n{moreActions}" on-click="onDotsMenuClick_">
- </cr-icon-button>
- </template>
- <template is="dom-if" if="[[!showDots_(creditCard.metadata)]]">
- <cr-icon-button actionable class="icon-external"
- id="remoteCreditCardLink" on-click="onRemoteEditClick_">
- </cr-icon-button>
- </template>
- </div>
- </div>
- </template>
- <script src="credit_card_list_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.js b/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.js
deleted file mode 100644
index 336492db8ae..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.js
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'credit-card-list-entry' is a credit card row to be shown in
- * the settings page.
- */
-
-Polymer({
- is: 'settings-credit-card-list-entry',
-
- behaviors: [
- I18nBehavior,
- ],
-
- properties: {
- /**
- * A saved credit card.
- * @type {!PaymentsManager.CreditCardEntry}
- */
- creditCard: Object,
- },
-
- /**
- * Opens the credit card action menu.
- * @private
- */
- onDotsMenuClick_: function() {
- this.fire('dots-card-menu-click', {
- creditCard: this.creditCard,
- anchorElement: this.$$('#creditCardMenu'),
- });
- },
-
- /** @private */
- onRemoteEditClick_: function() {
- this.fire('remote-card-menu-click');
- },
-
- /**
- * The 3-dot menu should not be shown if the card is entirely remote.
- * @return {boolean}
- * @private
- */
- showDots_: function() {
- return !!(
- this.creditCard.metadata.isLocal || this.creditCard.metadata.isCached);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html b/chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html
deleted file mode 100644
index 6cb9d2b64fd..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html
+++ /dev/null
@@ -1,86 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="show_password_behavior.html">
-
-<dom-module id="password-edit-dialog">
- <template>
- <style include="settings-shared">
- cr-input {
- --cr-input-error-display: none;
- }
-
- cr-input:not(:last-of-type) {
- margin-bottom: var(--cr-form-field-bottom-spacing);
- }
-
- #websiteInput,
- #usernameInput {
- width: var(--settings-input-max-width);
- }
-
- #passwordInput {
- --cr-input-width: var(--settings-input-max-width);
- }
-
- #passwordInput::part(row-container) {
- justify-content: initial;
- }
-
- #passwordInput::part(input) {
-<if expr="chromeos or is_linux">
- font-family: 'DejaVu Sans Mono', monospace;
-</if>
-<if expr="is_win">
- font-family: 'Consolas', monospace;
-</if>
-<if expr="is_macosx">
- font-family: 'Menlo', monospace;
-</if>
- }
-
- cr-icon-button {
- --cr-icon-button-icon-size: 24px;
- margin-inline-start: 2px;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{passwordDetailsTitle}</div>
- <div slot="body">
- <cr-input id="websiteInput" label="$i18n{editPasswordWebsiteLabel}"
- value="[[item.entry.urls.link]]" on-blur="onInputBlur_" readonly>
- </cr-input>
- <cr-input id="usernameInput" label="$i18n{editPasswordUsernameLabel}"
- value="[[item.entry.username]]" on-blur="onInputBlur_" readonly>
- </cr-input>
- <cr-input id="passwordInput" label="$i18n{editPasswordPasswordLabel}"
- type="[[getPasswordInputType_(item.password)]]"
- value="[[getPassword_(item.password)]]" on-blur="onInputBlur_"
- readonly>
- <cr-icon-button id="showPasswordButton"
- class$="[[getIconClass_(item.password)]]" slot="suffix"
- hidden$="[[item.entry.federationText]]"
- on-click="onShowPasswordButtonTap_"
- title="[[showPasswordTitle_(item.password,
- '$i18nPolymer{hidePassword}',
- '$i18nPolymer{showPassword}')]]">
- </cr-icon-button>
- </cr-input>
- </div>
- <div slot="button-container">
- <cr-button class="action-button" on-click="onActionButtonTap_">
- $i18n{done}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="password_edit_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js b/chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js
deleted file mode 100644
index 1e5923f1792..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'password-edit-dialog' is the dialog that allows showing a
- * saved password.
- */
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'password-edit-dialog',
-
- behaviors: [ShowPasswordBehavior],
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- /** Closes the dialog. */
- close: function() {
- this.$.dialog.close();
- },
-
- /**
- * Handler for tapping the 'done' button. Should just dismiss the dialog.
- * @private
- */
- onActionButtonTap_: function() {
- this.close();
- },
-
- /** Manually de-select texts for readonly inputs. */
- onInputBlur_: function() {
- this.shadowRoot.getSelection().removeAllRanges();
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/password_list_item.html b/chromium/chrome/browser/resources/settings/autofill_page/password_list_item.html
deleted file mode 100644
index 922166b2951..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/password_list_item.html
+++ /dev/null
@@ -1,97 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="passwords_shared_css.html">
-<link rel="import" href="show_password_behavior.html">
-
-<dom-module id="password-list-item">
- <template>
- <style include="settings-shared passwords-shared">
- #originUrl {
- /* The following non-flex directives allow eliding long originUrls from
- * the left. Forcing rtl should not cause an issue for right-to-left
- * languages in this case, since valid URL characters are restricted to
- * ASCII.
- */
- direction: rtl;
- display: flex;
- }
-
- #username,
- #password {
- color: inherit;
- /* Since #password is an input element this is necessary to prevent
- * Chrome from using the operating system's font instead of the Material
- * Design font. TODO(dbeam): why not font: inherit? */
- font-family: inherit;
- font-size: inherit;
- line-height: inherit;
- }
-
- #username,
- #password:not([type='password']) {
- text-overflow: ellipsis;
- }
-
- #password {
-<if expr="chromeos or is_linux">
- font-family: 'DejaVu Sans Mono', monospace;
-</if>
-<if expr="is_win">
- font-family: 'Consolas', monospace;
-</if>
-<if expr="is_macosx">
- font-family: 'Menlo', monospace;
-</if>
- }
- </style>
- <div class="list-item" focus-row-container>
- <div class="website-column no-min-width">
- <site-favicon url="[[item.entry.urls.link]]"></site-favicon>
- <a id="originUrl" target="_blank" class="no-min-width"
- href="[[item.entry.urls.link]]"
- focus-row-control focus-type="originUrl">
- <span class="text-elide">
- <!-- This bdo tag is necessary to fix the display of domains
- starting with numbers. -->
- <bdo dir="ltr">[[item.entry.urls.shown]]</bdo>
- </span>
- </a>
- </div>
- <input id="username" class="username-column password-field"
- aria-label="$i18n{editPasswordUsernameLabel}"
- readonly value="[[item.entry.username]]"
- focus-row-control focus-type="username">
- <div class="password-column">
- <template is="dom-if" if="[[!item.entry.federationText]]">
- <input id="password" aria-label=$i18n{editPasswordPasswordLabel}
- type="[[getPasswordInputType_(item.password)]]"
- on-click="onReadonlyInputTap_" class="password-field" readonly
- disabled$="[[!item.password]]"
- value="[[getPassword_(item.password)]]"
- focus-row-control focus-type="passwordField">
- <cr-icon-button id="showPasswordButton"
- class$="[[getIconClass_(item.password)]]"
- on-click="onShowPasswordButtonTap_"
- title="[[showPasswordTitle_(item.password,
- '$i18nPolymer{hidePassword}',
- '$i18nPolymer{showPassword}')]]"
- focus-row-control focus-type="showPassword"></cr-icon-button>
- </template>
- <span class="password-field text-elide" id="federated"
- hidden$="[[!item.entry.federationText]]">
- [[item.entry.federationText]]
- </span>
- </div>
- <cr-icon-button class="icon-more-vert" id="passwordMenu"
- on-click="onPasswordMenuTap_" title="$i18n{moreActions}"
- focus-row-control focus-type="passwordMenu"
- aria-label$="[[getMoreActionsLabel_(item)]]"></cr-icon-button>
- </div>
- </template>
- <script src="password_list_item.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/password_list_item.js b/chromium/chrome/browser/resources/settings/autofill_page/password_list_item.js
deleted file mode 100644
index 12c176fd932..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/password_list_item.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview PasswordListItem represents one row in the list of passwords.
- * It needs to be its own component because FocusRowBehavior provides good a11y.
- */
-
-Polymer({
- is: 'password-list-item',
-
- behaviors: [
- cr.ui.FocusRowBehavior,
- ShowPasswordBehavior,
- ],
-
- /**
- * Selects the password on tap if revealed.
- * @private
- */
- onReadonlyInputTap_: function() {
- if (this.item.password) {
- this.$$('#password').select();
- }
- },
-
- /**
- * Opens the password action menu.
- * @private
- */
- onPasswordMenuTap_: function() {
- this.fire(
- 'password-menu-tap', {target: this.$.passwordMenu, listItem: this});
- },
-
- /**
- * Get the aria label for the More Actions button on this row.
- * @param {!PasswordManagerProxy.UiEntryWithPassword} item This row's item.
- * @private
- */
- getMoreActionsLabel_: function(item) {
- // Avoid using I18nBehavior.i18n, because it will filter sequences, which
- // are otherwise not illegal for usernames. Polymer still protects against
- // XSS injection.
- return loadTimeData.getStringF(
- (item.entry.federationText) ? 'passwordRowFederatedMoreActionsButton' :
- 'passwordRowMoreActionsButton',
- item.entry.username, item.entry.urls.shown);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.html b/chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.html
deleted file mode 100644
index e33c98a3d6b..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-<script src="password_manager_proxy.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js b/chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
deleted file mode 100644
index fd976286b91..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview PasswordManagerProxy is an abstraction over
- * chrome.passwordsPrivate which facilitates testing.
- */
-
-/**
- * Interface for all callbacks to the password API.
- * @interface
- */
-class PasswordManagerProxy {
- /**
- * Add an observer to the list of saved passwords.
- * @param {function(!Array<!PasswordManagerProxy.PasswordUiEntry>):void}
- * listener
- */
- addSavedPasswordListChangedListener(listener) {}
-
- /**
- * Remove an observer from the list of saved passwords.
- * @param {function(!Array<!PasswordManagerProxy.PasswordUiEntry>):void}
- * listener
- */
- removeSavedPasswordListChangedListener(listener) {}
-
- /**
- * Request the list of saved passwords.
- * TODO(https://crbug.com/919483): Return a promise instead of taking a
- * callback argument.
- * @param {function(!Array<!PasswordManagerProxy.PasswordUiEntry>):void}
- * callback
- */
- getSavedPasswordList(callback) {}
-
- /**
- * Log that the Passwords page was accessed from the Chrome Settings WebUI.
- */
- recordPasswordsPageAccessInSettings() {}
-
- /**
- * Should remove the saved password and notify that the list has changed.
- * @param {number} id The id for the password entry being removed.
- * No-op if |id| is not in the list.
- */
- removeSavedPassword(id) {}
-
- /**
- * Add an observer to the list of password exceptions.
- * @param {function(!Array<!PasswordManagerProxy.ExceptionEntry>):void}
- * listener
- */
- addExceptionListChangedListener(listener) {}
-
- /**
- * Remove an observer from the list of password exceptions.
- * @param {function(!Array<!PasswordManagerProxy.ExceptionEntry>):void}
- * listener
- */
- removeExceptionListChangedListener(listener) {}
-
- /**
- * Request the list of password exceptions.
- * TODO(https://crbug.com/919483): Return a promise instead of taking a
- * callback argument.
- * @param {function(!Array<!PasswordManagerProxy.ExceptionEntry>):void}
- * callback
- */
- getExceptionList(callback) {}
-
- /**
- * Should remove the password exception and notify that the list has changed.
- * @param {number} id The id for the exception url entry being removed.
- * No-op if |id| is not in the list.
- */
- removeException(id) {}
-
- /**
- * Should undo the last saved password or exception removal and notify that
- * the list has changed.
- */
- undoRemoveSavedPasswordOrException() {}
-
- /**
- * Gets the saved password for a given login pair.
- * @param {number} id The id for password entry that should be
- * retrieved.
- * @return {!Promise<(string|undefined)>} Gets invoked with the password if
- * it could be retrieved successfully.
- */
- getPlaintextPassword(id) {}
-
- /**
- * Triggers the dialogue for importing passwords.
- */
- importPasswords() {}
-
- /**
- * Triggers the dialogue for exporting passwords.
- * TODO(https://crbug.com/919483): Return a promise instead of taking a
- * callback argument.
- * @param {function():void} callback
- */
- exportPasswords(callback) {}
-
- /**
- * Queries the status of any ongoing export.
- * TODO(https://crbug.com/919483): Return a promise instead of taking a
- * callback argument.
- * @param {function(!PasswordManagerProxy.ExportProgressStatus):void}
- * callback
- */
- requestExportProgressStatus(callback) {}
-
- /**
- * Add an observer to the export progress.
- * @param {function(!PasswordManagerProxy.PasswordExportProgress):void}
- * listener
- */
- addPasswordsFileExportProgressListener(listener) {}
-
- /**
- * Remove an observer from the export progress.
- * @param {function(!PasswordManagerProxy.PasswordExportProgress):void}
- * listener
- */
- removePasswordsFileExportProgressListener(listener) {}
-
- cancelExportPasswords() {}
-}
-
-/** @typedef {chrome.passwordsPrivate.PasswordUiEntry} */
-PasswordManagerProxy.PasswordUiEntry;
-
-/** @typedef {chrome.passwordsPrivate.ExceptionEntry} */
-PasswordManagerProxy.ExceptionEntry;
-
-/**
- * @typedef {{ entry: !PasswordManagerProxy.PasswordUiEntry, password: string }}
- */
-PasswordManagerProxy.UiEntryWithPassword;
-
-/** @typedef {chrome.passwordsPrivate.PasswordExportProgress} */
-PasswordManagerProxy.PasswordExportProgress;
-
-/** @typedef {chrome.passwordsPrivate.ExportProgressStatus} */
-PasswordManagerProxy.ExportProgressStatus;
-
-/**
- * Implementation that accesses the private API.
- * @implements {PasswordManagerProxy}
- */
-class PasswordManagerImpl {
- /** @override */
- addSavedPasswordListChangedListener(listener) {
- chrome.passwordsPrivate.onSavedPasswordsListChanged.addListener(listener);
- }
-
- /** @override */
- removeSavedPasswordListChangedListener(listener) {
- chrome.passwordsPrivate.onSavedPasswordsListChanged.removeListener(
- listener);
- }
-
- /** @override */
- getSavedPasswordList(callback) {
- chrome.passwordsPrivate.getSavedPasswordList(callback);
- }
-
- /** @override */
- recordPasswordsPageAccessInSettings() {
- chrome.passwordsPrivate.recordPasswordsPageAccessInSettings();
- }
-
- /** @override */
- removeSavedPassword(id) {
- chrome.passwordsPrivate.removeSavedPassword(id);
- }
-
- /** @override */
- addExceptionListChangedListener(listener) {
- chrome.passwordsPrivate.onPasswordExceptionsListChanged.addListener(
- listener);
- }
-
- /** @override */
- removeExceptionListChangedListener(listener) {
- chrome.passwordsPrivate.onPasswordExceptionsListChanged.removeListener(
- listener);
- }
-
- /** @override */
- getExceptionList(callback) {
- chrome.passwordsPrivate.getPasswordExceptionList(callback);
- }
-
- /** @override */
- removeException(id) {
- chrome.passwordsPrivate.removePasswordException(id);
- }
-
- /** @override */
- undoRemoveSavedPasswordOrException() {
- chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
- }
-
- /** @override */
- getPlaintextPassword(id) {
- return new Promise(resolve => {
- chrome.passwordsPrivate.requestPlaintextPassword(id, resolve);
- });
- }
-
- /** @override */
- importPasswords() {
- chrome.passwordsPrivate.importPasswords();
- }
-
- /** @override */
- exportPasswords(callback) {
- chrome.passwordsPrivate.exportPasswords(callback);
- }
-
- /** @override */
- requestExportProgressStatus(callback) {
- chrome.passwordsPrivate.requestExportProgressStatus(callback);
- }
-
- /** @override */
- addPasswordsFileExportProgressListener(listener) {
- chrome.passwordsPrivate.onPasswordsFileExportProgress.addListener(listener);
- }
-
- /** @override */
- removePasswordsFileExportProgressListener(listener) {
- chrome.passwordsPrivate.onPasswordsFileExportProgress.removeListener(
- listener);
- }
-
- /** @override */
- cancelExportPasswords() {
- chrome.passwordsPrivate.cancelExportPasswords();
- }
-}
-
-cr.addSingletonGetter(PasswordManagerImpl);
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html b/chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html
deleted file mode 100644
index ae962e2577f..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html
+++ /dev/null
@@ -1,95 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html">
-<link rel="import" href="../settings_shared_css.html">
-<if expr="chromeos">
-<link rel="import" href="blocking_request_manager.html">
-</if>
-
-<dom-module id="passwords-export-dialog">
- <template>
- <style include="settings-shared iron-flex">
- paper-progress {
- --paper-progress-active-color: var(--google-blue-500);
- width: 100%;
- }
-
- @media (prefers-color-scheme: dark) {
- paper-progress {
- /* TODO(dbeam): this is the same as downloads (and probably anywhere
- * else that uses paper-progress). Should we make something like a
- * paper_progress_style_css.html? */
- --paper-progress-active-color: var(--google-blue-refresh-300);
- }
- }
-
- .action-button {
- margin-inline-start: 8px;
- }
- </style>
- <template is="dom-if" if="[[showStartDialog_]]" restamp>
- <cr-dialog id="dialog_start" close-text="$i18n{close}" show-on-attach>
- <div slot="title">$i18n{exportPasswordsTitle}</div>
- <div slot="body">
- <div class="layout horizontal center">
- <div>$i18n{exportPasswordsDescription}</div>
- </div>
- </div>
- <div slot="button-container">
- <cr-button class="secondary-button header-aligned-button"
- on-click="onCancelButtonTap_" id="cancelButton">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button header-aligned-button"
- on-click="onExportTap_" id="exportPasswordsButton">
- $i18n{exportPasswords}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
-
- <template is="dom-if" if="[[showProgressDialog_]]" restamp>
- <cr-dialog id="dialog_progress" no-cancel="true" show-on-attach>
- <div slot="title">$i18n{exportingPasswordsTitle}</div>
- <div slot="body">
- <paper-progress indeterminate class="blue"></paper-progress>
- </div>
- <div slot="button-container">
- <cr-button id="cancel_progress_button" class="header-aligned-button"
- on-click="onCancelProgressButtonTap_">
- $i18n{cancel}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
-
- <template is="dom-if" if="[[showErrorDialog_]]" restamp>
- <cr-dialog id="dialog_error" close-text="$i18n{close}" show-on-attach>
- <div slot="title">[[exportErrorMessage]]</div>
- <div slot="body">
- $i18n{exportPasswordsFailTips}
- <ul>
- <li>$i18n{exportPasswordsFailTipsEnoughSpace}</li>
- <li>$i18n{exportPasswordsFailTipsAnotherFolder}</li>
- </ul>
- </div>
- <div slot="button-container">
- <cr-button class="header-aligned-button" on-click="onCancelButtonTap_"
- id="cancelErrorButton">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button header-aligned-button"
- on-click="onExportTap_" id="tryAgainButton">
- $i18n{exportPasswordsTryAgain}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
-
- </template>
- <script src="passwords_export_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.js b/chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.js
deleted file mode 100644
index d57d44916c2..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.js
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'passwords-export-dialog' is the dialog that allows exporting
- * passwords.
- */
-
-(function() {
-'use strict';
-
-/**
- * The states of the export passwords dialog.
- * @enum {string}
- */
-const States = {
- START: 'START',
- IN_PROGRESS: 'IN_PROGRESS',
- ERROR: 'ERROR',
-};
-
-const ProgressStatus = chrome.passwordsPrivate.ExportProgressStatus;
-
-/**
- * The amount of time (ms) between the start of the export and the moment we
- * start showing the progress bar.
- * @type {number}
- */
-const progressBarDelayMs = 100;
-
-/**
- * The minimum amount of time (ms) that the progress bar will be visible.
- * @type {number}
- */
-const progressBarBlockMs = 1000;
-
-Polymer({
- is: 'passwords-export-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /** The error that occurred while exporting. */
- exportErrorMessage: String,
-
- /** @private */
- showStartDialog_: Boolean,
-
- /** @private */
- showProgressDialog_: Boolean,
-
- /** @private */
- showErrorDialog_: Boolean,
-
- // <if expr="chromeos">
- /** @type settings.BlockingRequestManager */
- tokenRequestManager: Object
- // </if>
- },
-
- listeners: {
- 'cancel': 'close',
- },
-
- /**
- * The interface for callbacks to the browser.
- * Defined in passwords_section.js
- * @type {PasswordManagerProxy}
- * @private
- */
- passwordManager_: null,
-
- /** @private {function(!PasswordManagerProxy.PasswordExportProgress):void} */
- onPasswordsFileExportProgressListener_: null,
-
- /**
- * The task that will display the progress bar, if the export doesn't finish
- * quickly. This is null, unless the task is currently scheduled.
- * @private {?number}
- */
- progressTaskToken_: null,
-
- /**
- * The task that will display the completion of the export, if any. We display
- * the progress bar for at least |progressBarBlockMs|, therefore, if export
- * finishes earlier, we cache the result in |delayedProgress_| and this task
- * will consume it. This is null, unless the task is currently scheduled.
- * @private {?number}
- */
- delayedCompletionToken_: null,
-
- /**
- * We display the progress bar for at least |progressBarBlockMs|. If progress
- * is achieved earlier, we store the update here and consume it later.
- * @private {?PasswordManagerProxy.PasswordExportProgress}
- */
- delayedProgress_: null,
-
- /** @override */
- attached: function() {
- this.passwordManager_ = PasswordManagerImpl.getInstance();
-
- this.switchToDialog_(States.START);
-
- this.onPasswordsFileExportProgressListener_ =
- this.onPasswordsFileExportProgress_.bind(this);
-
- // If export started on a different tab and is still in progress, display a
- // busy UI.
- this.passwordManager_.requestExportProgressStatus(status => {
- if (status == ProgressStatus.IN_PROGRESS) {
- this.switchToDialog_(States.IN_PROGRESS);
- }
- });
-
- this.passwordManager_.addPasswordsFileExportProgressListener(
- this.onPasswordsFileExportProgressListener_);
- },
-
- /**
- * Handles an export progress event by changing the visible dialog or caching
- * the event for later consumption.
- * @param {!PasswordManagerProxy.PasswordExportProgress} progress
- * @private
- */
- onPasswordsFileExportProgress_(progress) {
- // If Chrome has already started displaying the progress bar
- // (|progressTaskToken_ is null) and hasn't completed its minimum display
- // time (|delayedCompletionToken_| is not null) progress should be cached
- // for consumption when the blocking time ends.
- const progressBlocked =
- !this.progressTaskToken_ && this.delayedCompletionToken_;
- if (!progressBlocked) {
- clearTimeout(this.progressTaskToken_);
- this.progressTaskToken_ = null;
- this.processProgress_(progress);
- } else {
- this.delayedProgress_ = progress;
- }
- },
-
- /**
- * Displays the progress bar and suspends further UI updates for
- * |progressBarBlockMs|.
- * @private
- */
- progressTask_() {
- this.progressTaskToken_ = null;
- this.switchToDialog_(States.IN_PROGRESS);
-
- this.delayedCompletionToken_ =
- setTimeout(this.delayedCompletionTask_.bind(this), progressBarBlockMs);
- },
-
- /**
- * Unblocks progress after showing the progress bar for |progressBarBlock|ms
- * and processes any progress that was delayed.
- * @private
- */
- delayedCompletionTask_() {
- this.delayedCompletionToken_ = null;
- if (this.delayedProgress_) {
- this.processProgress_(this.delayedProgress_);
- this.delayedProgress_ = null;
- }
- },
-
- /** Closes the dialog. */
- close: function() {
- clearTimeout(this.progressTaskToken_);
- clearTimeout(this.delayedCompletionToken_);
- this.progressTaskToken_ = null;
- this.delayedCompletionToken_ = null;
- this.passwordManager_.removePasswordsFileExportProgressListener(
- this.onPasswordsFileExportProgressListener_);
- this.showStartDialog_ = false;
- this.showProgressDialog_ = false;
- this.showErrorDialog_ = false;
- // Need to allow for the dialogs to be removed from the DOM before firing
- // the close event. Otherwise the handler will not be able to set focus.
- this.async(() => this.fire('passwords-export-dialog-close'));
- },
-
- /** @private */
- onExportTap_: function() {
- // <if expr="chromeos">
- this.tokenRequestManager.request(this.exportPasswords_.bind(this));
- // </if>
- // <if expr="not chromeos">
- this.exportPasswords_();
- // </if>
- },
-
- /**
- * Tells the PasswordsPrivate API to export saved passwords in a .csv pending
- * security checks.
- * @private
- */
- exportPasswords_: function() {
- this.passwordManager_.exportPasswords(() => {
- if (chrome.runtime.lastError &&
- chrome.runtime.lastError.message == 'in-progress') {
- // Exporting was started by a different call to exportPasswords() and is
- // is still in progress. This UI needs to be updated to the current
- // status.
- this.switchToDialog_(States.IN_PROGRESS);
- }
- });
- },
-
- /**
- * Prepares and displays the appropriate view (with delay, if necessary).
- * @param {!PasswordManagerProxy.PasswordExportProgress} progress
- * @private
- */
- processProgress_(progress) {
- if (progress.status == ProgressStatus.IN_PROGRESS) {
- this.progressTaskToken_ =
- setTimeout(this.progressTask_.bind(this), progressBarDelayMs);
- return;
- }
- if (progress.status == ProgressStatus.SUCCEEDED) {
- this.close();
- return;
- }
- if (progress.status == ProgressStatus.FAILED_WRITE_FAILED) {
- this.exportErrorMessage =
- this.i18n('exportPasswordsFailTitle', progress.folderName);
- this.switchToDialog_(States.ERROR);
- return;
- }
- },
-
- /**
- * Opens the specified dialog and hides the others.
- * @param {!States} state the dialog to open.
- * @private
- */
- switchToDialog_(state) {
- this.showStartDialog_ = state == States.START;
- this.showProgressDialog_ = state == States.IN_PROGRESS;
- this.showErrorDialog_ = state == States.ERROR;
- },
-
- /**
- * Handler for tapping the 'cancel' button. Should just dismiss the dialog.
- * @private
- */
- onCancelButtonTap_: function() {
- this.close();
- },
-
- /**
- * Handler for tapping the 'cancel' button on the progress dialog. It should
- * cancel the export and dismiss the dialog.
- * @private
- */
- onCancelProgressButtonTap_: function() {
- this.passwordManager_.cancelExportPasswords();
- this.close();
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/passwords_section.html b/chromium/chrome/browser/resources/settings/autofill_page/passwords_section.html
deleted file mode 100644
index 2f569d3e4dc..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/passwords_section.html
+++ /dev/null
@@ -1,201 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast_manager.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../global_scroll_target_behavior.html">
-<link rel="import" href="../people_page/sync_browser_proxy.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="password_edit_dialog.html">
-<link rel="import" href="passwords_export_dialog.html">
-<link rel="import" href="passwords_shared_css.html">
-<link rel="import" href="password_list_item.html">
-<link rel="import" href="password_manager_proxy.html">
-<if expr="chromeos">
-<link rel="import" href="../controls/password_prompt_dialog.html">
-<link rel="import" href="blocking_request_manager.html">
-</if>
-
-<dom-module id="passwords-section">
- <template>
- <style include="settings-shared passwords-shared">
- #savedPasswordsHeaders {
- /* This adds enough padding so that the labels are aligned with the
- * columns. It is necessary due to the absence of the "more actions"
- * button in the heading.
- */
- padding-inline-end: calc(
- var(--cr-icon-ripple-size) + var(--cr-icon-button-margin-start));
- }
-
- #undoLabel {
- display: flex;
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- #exportImportMenuButtonContainer {
- margin-inline-end: 0;
- }
- </style>
- <settings-toggle-button id="passwordToggle"
- class="first"
- aria-label="$i18n{passwords}" no-extension-indicator
- label="$i18n{passwordsSavePasswordsLabel}"
- pref="{{prefs.credentials_enable_service}}">
- </settings-toggle-button>
- <template is="dom-if"
- if="[[prefs.credentials_enable_service.extensionId]]">
- <div class="settings-box continuation">
- <extension-controlled-indicator class="start"
- id="passwordsExtensionIndicator"
- extension-id="[[prefs.credentials_enable_service.extensionId]]"
- extension-name="[[
- prefs.credentials_enable_service.controlledByName]]"
- extension-can-be-disabled="[[
- prefs.credentials_enable_service.extensionCanBeDisabled]]">
- </extension-controlled-indicator>
- </div>
- </template>
- <settings-toggle-button id="autosigninCheckbox"
- pref="{{prefs.credentials_enable_autosignin}}"
- label="$i18n{passwordsAutosigninLabel}"
- sub-label="$i18n{passwordsAutosigninDescription}">
- </settings-toggle-button>
- <div id="manageLink" class="settings-box first two-line"
- hidden$="[[hidePasswordsLink_]]">
- <!-- This span lays out the url correctly, relative to the label. -->
- <span>$i18nRaw{managePasswordsLabel}</span>
- </div>
- <div class="settings-box first">
- <h2 id="savedPasswordsHeading" class="start">
- $i18n{savedPasswordsHeading}
- </h2>
- <template is="dom-if"
- if="[[showImportOrExportPasswords_(
- showExportPasswords_, showImportPasswords_)]]">
- <cr-icon-button class="icon-more-vert" id="exportImportMenuButton"
- on-click="onImportExportMenuTap_" title="$i18n{moreActions}"
- focus-type="exportImportMenuButton"
- aria-describedby="savedPasswordsHeading"></cr-icon-button>
- </template>
- </div>
- <div class="list-frame">
- <div id="savedPasswordsHeaders" class="list-item column-header"
- hidden$="[[!hasSome_(savedPasswords, savedPasswords.splices)]]"
- aria-hidden="true">
- <div class="website-column">$i18n{editPasswordWebsiteLabel}</div>
- <div class="username-column">
- $i18n{editPasswordUsernameLabel}
- </div>
- <div class="password-column">
- $i18n{editPasswordPasswordLabel}
- </div>
- </div>
- <iron-list id="passwordList" preserve-focus
- items="[[getFilteredPasswords_(filter, savedPasswords.splices)]]"
- class="cr-separators list-with-header"
- scroll-target="[[subpageScrollTarget]]" risk-selection>
- <template>
- <password-list-item item="[[item]]" tabindex$="[[tabIndex]]"
-<if expr="chromeos">
- token-request-manager="[[tokenRequestManager_]]"
-</if>
- first$="[[!index]]" iron-list-tab-index="[[tabIndex]]"
- last-focused="{{lastFocused_}}" list-blurred="{{listBlurred_}}">
- </password-list-item>
- </template>
- </iron-list>
- <div id="noPasswordsLabel" class="list-item"
- hidden$="[[hasSome_(savedPasswords, savedPasswords.splices)]]">
- $i18n{noPasswordsFound}
- </div>
- </div>
- <cr-action-menu id="menu">
- <button id="menuEditPassword" class="dropdown-item"
- on-click="onMenuEditPasswordTap_">$i18n{passwordViewDetails}</button>
- <button id="menuRemovePassword" class="dropdown-item"
- on-click="onMenuRemovePasswordTap_">$i18n{removePassword}</button>
- </cr-action-menu>
- <cr-action-menu id="exportImportMenu">
- <button id="menuImportPassword" class="dropdown-item"
- on-click="onImportTap_" hidden="[[!showImportPasswords_]]">
- $i18n{import}
- </button>
- <button id="menuExportPassword" class="dropdown-item"
- on-click="onExportTap_" hidden="[[!showExportPasswords_]]">
- $i18n{exportMenuItem}
- </button>
- </cr-action-menu>
- <template is="dom-if" if="[[showPasswordsExportDialog_]]" restamp>
- <passwords-export-dialog
-<if expr="chromeos">
- token-request-manager="[[tokenRequestManager_]]"
-</if>
- on-passwords-export-dialog-close="onPasswordsExportDialogClosed_">
- </passwords-export-dialog>
- </template>
- <template is="dom-if" if="[[showPasswordEditDialog_]]" restamp>
- <password-edit-dialog on-close="onPasswordEditDialogClosed_"
-<if expr="chromeos">
- token-request-manager="[[tokenRequestManager_]]"
-</if>
- item="[[activePassword.item]]">
- </password-edit-dialog>
- </template>
-<if expr="chromeos">
- <template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
- <settings-password-prompt-dialog auth-token="{{authToken_}}"
- on-close="onPasswordPromptClosed_">
- </settings-password-prompt-dialog>
- </template>
-</if>
- <cr-toast-manager on-undo-click="onUndoButtonTap_"
- undo-label="$i18n{undoRemovePassword}"
- undo-description="$i18n{undoDescription}"
- duration="[[toastDuration_]]">
- </cr-toast-manager>
- <div class="settings-box block first">
- <h2>$i18n{passwordExceptionsHeading}</h2>
- </div>
- <div class="list-frame vertical-list" id="passwordExceptionsList">
- <template is="dom-repeat" items="[[passwordExceptions]]"
- filter="[[passwordExceptionFilter_(filter)]]">
- <div class="list-item">
- <div class="start website-column">
- <site-favicon url="[[item.urls.link]]"></site-favicon>
- <a id="exception" href="[[item.urls.link]]" target="_blank">
- [[item.urls.shown]]
- </a>
- </div>
- <cr-icon-button class="icon-clear" id="removeExceptionButton"
- on-click="onRemoveExceptionButtonTap_" tabindex$="[[tabIndex]]"
- title="$i18n{deletePasswordException}"></cr-icon-button>
- </div>
- </template>
- <div id="noExceptionsLabel" class="list-item"
- hidden$="[[hasSome_(passwordExceptions)]]">
- $i18n{noExceptionsFound}
- </div>
- </div>
- </template>
- <script src="passwords_section.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/passwords_section.js b/chromium/chrome/browser/resources/settings/autofill_page/passwords_section.js
deleted file mode 100644
index 0eabb41138a..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/passwords_section.js
+++ /dev/null
@@ -1,495 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'passwords-section' is the collapsible section containing
- * the list of saved passwords as well as the list of sites that will never
- * save any passwords.
- */
-
-/** @typedef {!{model: !{item: !PasswordManagerProxy.UiEntryWithPassword}}} */
-let PasswordUiEntryEvent;
-
-/** @typedef {!{model: !{item: !chrome.passwordsPrivate.ExceptionEntry}}} */
-let ExceptionEntryEntryEvent;
-
-(function() {
-'use strict';
-
-/**
- * Checks if an HTML element is an editable. An editable is either a text
- * input or a text area.
- * @param {!Element} element
- * @return {boolean}
- */
-function isEditable(element) {
- const nodeName = element.nodeName.toLowerCase();
- return element.nodeType === Node.ELEMENT_NODE &&
- (nodeName === 'textarea' ||
- (nodeName === 'input' &&
- /^(?:text|search|email|number|tel|url|password)$/i.test(element.type)));
-}
-
-Polymer({
- is: 'passwords-section',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- ListPropertyUpdateBehavior,
- Polymer.IronA11yKeysBehavior,
- settings.GlobalScrollTargetBehavior,
- PrefsBehavior,
- ],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * An array of passwords to display.
- * @type {!Array<!PasswordManagerProxy.UiEntryWithPassword>}
- */
- savedPasswords: {
- type: Array,
- value: () => [],
- },
-
- /**
- * An array of sites to display.
- * @type {!Array<!PasswordManagerProxy.ExceptionEntry>}
- */
- passwordExceptions: {
- type: Array,
- value: () => [],
- },
-
- /**
- * Duration of the undo toast in ms
- * @private
- */
- toastDuration_: {
- type: Number,
- value: 5000,
- },
-
- /** @override */
- subpageRoute: {
- type: Object,
- value: settings.routes.PASSWORDS,
- },
-
- /**
- * The model for any password related action menus or dialogs.
- * @private {?PasswordListItemElement}
- */
- activePassword: Object,
-
- /** The target of the key bindings defined below. */
- keyEventTarget: {
- type: Object,
- value: () => document,
- },
-
- /** @private */
- hidePasswordsLink_: {
- type: Boolean,
- computed: 'computeHidePasswordsLink_(syncPrefs_, syncStatus_)',
- },
-
- /** @private */
- showExportPasswords_: {
- type: Boolean,
- computed: 'hasPasswords_(savedPasswords.splices)',
- },
-
- /** @private */
- showImportPasswords_: {
- type: Boolean,
- value: function() {
- return loadTimeData.valueExists('showImportPasswords') &&
- loadTimeData.getBoolean('showImportPasswords');
- }
- },
-
- /** @private */
- showPasswordEditDialog_: Boolean,
-
- /** @private {settings.SyncPrefs} */
- syncPrefs_: Object,
-
- /** @private {settings.SyncStatus} */
- syncStatus_: Object,
-
- /** Filter on the saved passwords and exceptions. */
- filter: {
- type: String,
- value: '',
- },
-
- /** @private {!PasswordManagerProxy.UiEntryWithPassword} */
- lastFocused_: Object,
-
- /** @private */
- listBlurred_: Boolean,
-
- // <if expr="chromeos">
- /**
- * Auth token for retrieving passwords if required by OS.
- * @private
- */
- authToken_: {
- type: String,
- value: '',
- observer: 'onAuthTokenChanged_',
- },
-
- /** @private */
- showPasswordPromptDialog_: Boolean,
-
- /** @private {settings.BlockingRequestManager} */
- tokenRequestManager_: Object
- // </if>
- },
-
- listeners: {
- 'password-menu-tap': 'onPasswordMenuTap_',
- },
-
- keyBindings: {
- // <if expr="is_macosx">
- 'meta+z': 'onUndoKeyBinding_',
- // </if>
- // <if expr="not is_macosx">
- 'ctrl+z': 'onUndoKeyBinding_',
- // </if>
- },
-
- /**
- * A stack of the elements that triggered dialog to open and should therefore
- * receive focus when that dialog is closed. The bottom of the stack is the
- * element that triggered the earliest open dialog and top of the stack is the
- * element that triggered the most recent (i.e. active) dialog. If no dialog
- * is open, the stack is empty.
- * @private {!Array<Element>}
- */
- activeDialogAnchorStack_: [],
-
- /**
- * @type {PasswordManagerProxy}
- * @private
- */
- passwordManager_: null,
-
- /**
- * @type {?function(!Array<PasswordManagerProxy.PasswordUiEntry>):void}
- * @private
- */
- setSavedPasswordsListener_: null,
-
- /**
- * @type {?function(!Array<PasswordManagerProxy.ExceptionEntry>):void}
- * @private
- */
- setPasswordExceptionsListener_: null,
-
- /** @override */
- attached: function() {
- // Create listener functions.
- const setSavedPasswordsListener = list => {
- const newList = list.map(entry => ({entry: entry, password: ''}));
- // Because the backend guarantees that item.entry.id uniquely identifies a
- // given entry and is stable with regard to mutations to the list, it is
- // sufficient to just use this id to create a item uid.
- this.updateList('savedPasswords', item => item.entry.id, newList);
- };
-
- const setPasswordExceptionsListener = list => {
- this.passwordExceptions = list;
- };
-
- this.setSavedPasswordsListener_ = setSavedPasswordsListener;
- this.setPasswordExceptionsListener_ = setPasswordExceptionsListener;
-
- // Set the manager. These can be overridden by tests.
- this.passwordManager_ = PasswordManagerImpl.getInstance();
-
- // <if expr="chromeos">
- // If the user's account supports the password check, an auth token will be
- // required in order for them to view or export passwords. Otherwise there
- // is no additional security so |tokenRequestManager_| will immediately
- // resolve requests.
- if (loadTimeData.getBoolean('userCannotManuallyEnterPassword')) {
- this.tokenRequestManager_ = new settings.BlockingRequestManager();
- } else {
- this.tokenRequestManager_ = new settings.BlockingRequestManager(
- this.openPasswordPromptDialog_.bind(this));
- }
- // </if>
-
- // Request initial data.
- this.passwordManager_.getSavedPasswordList(setSavedPasswordsListener);
- this.passwordManager_.getExceptionList(setPasswordExceptionsListener);
-
- // Listen for changes.
- this.passwordManager_.addSavedPasswordListChangedListener(
- setSavedPasswordsListener);
- this.passwordManager_.addExceptionListChangedListener(
- setPasswordExceptionsListener);
-
- this.notifySplices('savedPasswords', []);
-
- const syncBrowserProxy = settings.SyncBrowserProxyImpl.getInstance();
-
- const syncStatusChanged = syncStatus => this.syncStatus_ = syncStatus;
- syncBrowserProxy.getSyncStatus().then(syncStatusChanged);
- this.addWebUIListener('sync-status-changed', syncStatusChanged);
-
- const syncPrefsChanged = syncPrefs => this.syncPrefs_ = syncPrefs;
- syncBrowserProxy.sendSyncPrefsChanged();
- this.addWebUIListener('sync-prefs-changed', syncPrefsChanged);
-
- Polymer.RenderStatus.afterNextRender(this, function() {
- Polymer.IronA11yAnnouncer.requestAvailability();
- });
- },
-
- /** @override */
- detached: function() {
- this.passwordManager_.removeSavedPasswordListChangedListener(
- /**
- * @type {function(!Array<PasswordManagerProxy.PasswordUiEntry>):void}
- */
- (this.setSavedPasswordsListener_));
- this.passwordManager_.removeExceptionListChangedListener(
- /**
- * @type {function(!Array<PasswordManagerProxy.ExceptionEntry>):void}
- */
- (this.setPasswordExceptionsListener_));
- if (cr.toastManager.getInstance().isToastOpen) {
- cr.toastManager.getInstance().hide();
- }
- },
-
- // <if expr="chromeos">
- /**
- * When |authToken_| changes to a new non-empty value, it means that the
- * password-prompt-dialog succeeded in creating a fresh token in the
- * quickUnlockPrivate API. Because new tokens can only ever be created
- * immediately following a GAIA password check, the passwordsPrivate API can
- * now safely grant requests for secure data (i.e. saved passwords) for a
- * limited time. This observer resolves the request, triggering a callback
- * that requires a fresh auth token to succeed and that was provided to the
- * BlockingRequestManager by another DOM element seeking secure data.
- *
- * @param {string} newToken The newly created auth token. Note that its
- * precise value is not relevant here, only the facts that it changed and
- * that it is non-empty (i.e. not expired).
- * @private
- */
- onAuthTokenChanged_: function(newToken) {
- if (newToken) {
- this.tokenRequestManager_.resolve();
- }
- },
-
- onPasswordPromptClosed_: function() {
- this.showPasswordPromptDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.activeDialogAnchorStack_.pop()));
- },
-
- openPasswordPromptDialog_: function() {
- this.activeDialogAnchorStack_.push(getDeepActiveElement());
- this.showPasswordPromptDialog_ = true;
- },
- // </if>
-
- /**
- * Shows the edit password dialog.
- * @param {!Event} e
- * @private
- */
- onMenuEditPasswordTap_: function(e) {
- e.preventDefault();
- /** @type {CrActionMenuElement} */ (this.$.menu).close();
- this.showPasswordEditDialog_ = true;
- },
-
- /** @private */
- onPasswordEditDialogClosed_: function() {
- this.showPasswordEditDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.activeDialogAnchorStack_.pop()));
-
- // Trigger a re-evaluation of the activePassword as the visibility state of
- // the password might have changed.
- this.activePassword.notifyPath('item.password');
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeHidePasswordsLink_: function() {
- return !!this.syncStatus_ && !!this.syncStatus_.signedIn &&
- !!this.syncPrefs_ && !!this.syncPrefs_.encryptAllData;
- },
-
- /**
- * @param {string} filter
- * @return {!Array<!PasswordManagerProxy.UiEntryWithPassword>}
- * @private
- */
- getFilteredPasswords_: function(filter) {
- if (!filter) {
- return this.savedPasswords.slice();
- }
-
- return this.savedPasswords.filter(
- p => [p.entry.urls.shown, p.entry.username].some(
- term => term.toLowerCase().includes(filter.toLowerCase())));
- },
-
- /**
- * @param {string} filter
- * @return {function(!chrome.passwordsPrivate.ExceptionEntry): boolean}
- * @private
- */
- passwordExceptionFilter_: function(filter) {
- return exception => exception.urls.shown.toLowerCase().includes(
- filter.toLowerCase());
- },
-
- /**
- * Fires an event that should delete the saved password.
- * @private
- */
- onMenuRemovePasswordTap_: function() {
- this.passwordManager_.removeSavedPassword(
- this.activePassword.item.entry.id);
- cr.toastManager.getInstance().show(
- this.i18n('passwordDeleted'),
- /* showUndo */ true);
- /** @type {CrActionMenuElement} */ (this.$.menu).close();
- },
-
- /**
- * Handle the undo shortcut.
- * @param {!Event} event
- * @private
- */
- onUndoKeyBinding_: function(event) {
- const activeElement = getDeepActiveElement();
- if (!activeElement || !isEditable(activeElement)) {
- this.passwordManager_.undoRemoveSavedPasswordOrException();
- cr.toastManager.getInstance().hide();
- // Preventing the default is necessary to not conflict with a possible
- // search action.
- event.preventDefault();
- }
- },
-
- onUndoButtonTap_: function() {
- this.passwordManager_.undoRemoveSavedPasswordOrException();
- cr.toastManager.getInstance().hide();
- },
- /**
- * Fires an event that should delete the password exception.
- * @param {!ExceptionEntryEntryEvent} e The polymer event.
- * @private
- */
- onRemoveExceptionButtonTap_: function(e) {
- this.passwordManager_.removeException(e.model.item.id);
- },
-
- /**
- * Opens the password action menu.
- * @param {!Event} event
- * @private
- */
- onPasswordMenuTap_: function(event) {
- const menu = /** @type {!CrActionMenuElement} */ (this.$.menu);
- const target = /** @type {!HTMLElement} */ (event.detail.target);
-
- this.activePassword =
- /** @type {!PasswordListItemElement} */ (event.detail.listItem);
- menu.showAt(target);
- this.activeDialogAnchorStack_.push(target);
- },
-
- /**
- * Opens the export/import action menu.
- * @private
- */
- onImportExportMenuTap_: function() {
- const menu = /** @type {!CrActionMenuElement} */ (this.$.exportImportMenu);
- const target =
- /** @type {!HTMLElement} */ (this.$$('#exportImportMenuButton'));
-
- menu.showAt(target);
- this.activeDialogAnchorStack_.push(target);
- },
-
- /**
- * Fires an event that should trigger the password import process.
- * @private
- */
- onImportTap_: function() {
- this.passwordManager_.importPasswords();
- this.$.exportImportMenu.close();
- },
-
- /**
- * Opens the export passwords dialog.
- * @private
- */
- onExportTap_: function() {
- this.showPasswordsExportDialog_ = true;
- this.$.exportImportMenu.close();
- },
-
- /** @private */
- onPasswordsExportDialogClosed_: function() {
- this.showPasswordsExportDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.activeDialogAnchorStack_.pop()));
- },
-
- /**
- * Returns true if the list exists and has items.
- * @param {Array<Object>} list
- * @return {boolean}
- * @private
- */
- hasSome_: function(list) {
- return !!(list && list.length);
- },
-
- /**
- * @private
- * @param {boolean} toggleValue
- * @return {string}
- */
- getOnOffLabel_: function(toggleValue) {
- return toggleValue ? this.i18n('toggleOn') : this.i18n('toggleOff');
- },
-
- /** @private */
- hasPasswords_: function() {
- return this.savedPasswords.length > 0;
- },
-
- /**
- * @private
- * @param {boolean} showExportPasswords
- * @param {boolean} showImportPasswords
- * @return {boolean}
- */
- showImportOrExportPasswords_: function(
- showExportPasswords, showImportPasswords) {
- return showExportPasswords || showImportPasswords;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/passwords_shared_css.html b/chromium/chrome/browser/resources/settings/autofill_page/passwords_shared_css.html
deleted file mode 100644
index d89345d33ae..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/passwords_shared_css.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-
-<!-- Common styles for Passwords and Forms -->
-<dom-module id="passwords-shared">
- <template>
- <style>
- :host {
- display: flex;
- flex-direction: column;
- }
-
- .list-with-header > div:first-of-type {
- border-top: var(--cr-separator-line);
- }
-
- .website-column {
- align-items: center;
- display: flex;
- flex: 1;
- }
-
- .website-column > a {
- color: var(--cr-primary-text-color);
- }
-
- .username-column {
- flex: 1;
- margin: 0 8px;
- }
-
- .password-column {
- align-items: center;
- display: flex;
- flex: 1;
- }
-
- .password-field {
- background-color: transparent;
- border: none;
- flex: 1;
- height: 20px;
- width: 0;
- }
-
- .type-column {
- align-items: center;
- flex: 2;
- }
-
- .ellipses {
- flex: 1;
- max-width: fit-content;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- site-favicon {
- margin-inline-end: 16px;
- min-width: 16px;
- }
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/payments_section.html b/chromium/chrome/browser/resources/settings/autofill_page/payments_section.html
deleted file mode 100644
index 2109d3395fd..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/payments_section.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../people_page/sync_browser_proxy.html">
-<link rel="import" href="credit_card_edit_dialog.html">
-<link rel="import" href="credit_card_list.html">
-<link rel="import" href="passwords_shared_css.html">
-
-<dom-module id="settings-payments-section">
- <template>
- <style include="settings-shared passwords-shared">
- .expiration-column {
- align-items: center;
- display: flex;
- flex: 1;
- }
-
- #migrateCreditCards {
- border-bottom: var(--cr-separator-line);
- border-top: none;
- }
-
- #migrateCreditCardsButton {
- margin: 0 auto;
- }
- </style>
- <settings-toggle-button id="autofillCreditCardToggle"
- class="settings-box first"
- aria-label="$i18n{creditCards}" no-extension-indicator
- label="$i18n{enableCreditCardsLabel}"
- sub-label="$i18n{enableCreditCardsSublabel}"
- pref="{{prefs.autofill.credit_card_enabled}}">
- </settings-toggle-button>
- <template is="dom-if"
- if="[[shouldShowFidoToggle_(
- prefs.autofill.credit_card_enabled.value,
- userIsFidoVerifiable_)]]">
- <settings-toggle-button
- class="settings-box first"
- id="autofillCreditCardFIDOAuthToggle"
- aria-label="$i18n{creditCards}" no-extension-indicator
- label="$i18n{enableCreditCardFIDOAuthLabel}"
- sub-label="$i18n{enableCreditCardFIDOAuthSublabel}"
- pref="{{prefs.autofill.credit_card_fido_auth_enabled}}"
- on-change="setFIDOAuthenticationEnabledState_">
- </settings-toggle-button>
- </template>
- <template is="dom-if"
- if="[[prefs.autofill.credit_card_enabled.extensionId]]">
- <div class="settings-box continuation">
- <extension-controlled-indicator class="start"
- id="autofillExtensionIndicator"
- extension-id="[[prefs.autofill.credit_card_enabled.extensionId]]"
- extension-name="[[
- prefs.autofill.credit_card_enabled.controlledByName]]"
- extension-can-be-disabled="[[
- prefs.autofill.credit_card_enabled.extensionCanBeDisabled]]">
- </extension-controlled-indicator>
- </div>
- </template>
-
- <div id="manageLink" class="settings-box first">
- <!-- This span lays out the URL correctly, relative to the label. -->
- <span>$i18nRaw{manageCreditCardsLabel}</span>
- </div>
-
- <div class="settings-box continuation">
- <h2 class="start">$i18n{creditCards}</h2>
- <cr-button id="addCreditCard" class="header-aligned-button"
- on-click="onAddCreditCardTap_"
- hidden$="[[!prefs.autofill.credit_card_enabled.value]]">
- $i18n{add}
- </cr-button>
- </div>
- <cr-link-row id="migrateCreditCards"
- hidden$="[[!checkIfMigratable_(creditCards,
- prefs.autofill.credit_card_enabled.value)]]"
- on-click="onMigrateCreditCardsClick_"
- label="$i18n{migrateCreditCardsLabel}"
- sub-label="[[migratableCreditCardsInfo_]]"></cr-link-row>
- <settings-credit-card-list id="creditCardList" class="list-frame"
- credit-cards="[[creditCards]]">
- </settings-credit-card-list>
-
- <cr-action-menu id="creditCardSharedMenu">
- <button id="menuEditCreditCard" class="dropdown-item"
- on-click="onMenuEditCreditCardTap_">$i18n{edit}</button>
- <button id="menuRemoveCreditCard" class="dropdown-item"
- hidden$="[[!activeCreditCard.metadata.isLocal]]"
- on-click="onMenuRemoveCreditCardTap_">$i18n{removeCreditCard}</button>
- <button id="menuClearCreditCard" class="dropdown-item"
- on-click="onMenuClearCreditCardTap_"
- hidden$="[[!activeCreditCard.metadata.isCached]]">
- $i18n{clearCreditCard}
- </button>
- </cr-action-menu>
- <template is="dom-if" if="[[showCreditCardDialog_]]" restamp>
- <settings-credit-card-edit-dialog credit-card="[[activeCreditCard]]"
- on-close="onCreditCardDialogClose_">
- </settings-credit-card-edit-dialog>
- </template>
- </template>
- <script src="payments_section.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/payments_section.js b/chromium/chrome/browser/resources/settings/autofill_page/payments_section.js
deleted file mode 100644
index 0c71a4b46c6..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/payments_section.js
+++ /dev/null
@@ -1,411 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-payments-section' is the section containing saved
- * credit cards for use in autofill and payments APIs.
- */
-
-/**
- * Interface for all callbacks to the payments autofill API.
- * @interface
- */
-class PaymentsManager {
- /**
- * Add an observer to the list of personal data.
- * @param {function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>):void} listener
- */
- setPersonalDataManagerListener(listener) {}
-
- /**
- * Remove an observer from the list of personal data.
- * @param {function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>):void} listener
- */
- removePersonalDataManagerListener(listener) {}
-
- /**
- * Request the list of credit cards.
- * @param {function(!Array<!PaymentsManager.CreditCardEntry>):void} callback
- */
- getCreditCardList(callback) {}
-
- /** @param {string} guid The GUID of the credit card to remove. */
- removeCreditCard(guid) {}
-
- /** @param {string} guid The GUID to credit card to remove from the cache. */
- clearCachedCreditCard(guid) {}
-
- /**
- * Saves the given credit card.
- * @param {!PaymentsManager.CreditCardEntry} creditCard
- */
- saveCreditCard(creditCard) {}
-
- /**
- * Migrate the local credit cards.
- */
- migrateCreditCards() {}
-
- /**
- * Logs that the server cards edit link was clicked.
- */
- logServerCardLinkClicked() {}
-
- /**
- * Enables FIDO authentication for card unmasking.
- */
- setCreditCardFIDOAuthEnabledState(enabled) {}
-}
-
-/** @typedef {chrome.autofillPrivate.CreditCardEntry} */
-PaymentsManager.CreditCardEntry;
-
-/**
- * Implementation that accesses the private API.
- * @implements {PaymentsManager}
- */
-class PaymentsManagerImpl {
- /** @override */
- setPersonalDataManagerListener(listener) {
- chrome.autofillPrivate.onPersonalDataChanged.addListener(listener);
- }
-
- /** @override */
- removePersonalDataManagerListener(listener) {
- chrome.autofillPrivate.onPersonalDataChanged.removeListener(listener);
- }
-
- /** @override */
- getCreditCardList(callback) {
- chrome.autofillPrivate.getCreditCardList(callback);
- }
-
- /** @override */
- removeCreditCard(guid) {
- chrome.autofillPrivate.removeEntry(assert(guid));
- }
-
- /** @override */
- clearCachedCreditCard(guid) {
- chrome.autofillPrivate.maskCreditCard(assert(guid));
- }
-
- /** @override */
- saveCreditCard(creditCard) {
- chrome.autofillPrivate.saveCreditCard(creditCard);
- }
-
- /** @override */
- migrateCreditCards() {
- chrome.autofillPrivate.migrateCreditCards();
- }
-
- /** @override */
- logServerCardLinkClicked() {
- chrome.autofillPrivate.logServerCardLinkClicked();
- }
-
- /** @override */
- setCreditCardFIDOAuthEnabledState(enabled) {
- chrome.autofillPrivate.setCreditCardFIDOAuthEnabledState(enabled);
- }
-}
-
-cr.addSingletonGetter(PaymentsManagerImpl);
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-payments-section',
-
- behaviors: [
- WebUIListenerBehavior,
- I18nBehavior,
- ],
-
- properties: {
- /**
- * An array of all saved credit cards.
- * @type {!Array<!PaymentsManager.CreditCardEntry>}
- */
- creditCards: {
- type: Array,
- value: () => [],
- },
-
- /**
- * Set to true if user can be verified through FIDO authentication.
- * @private
- */
- userIsFidoVerifiable_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean(
- 'fidoAuthenticationAvailableForAutofill');
- },
- },
-
- /**
- * The model for any credit card related action menus or dialogs.
- * @private {?chrome.autofillPrivate.CreditCardEntry}
- */
- activeCreditCard: Object,
-
- /** @private */
- showCreditCardDialog_: Boolean,
-
- /** @private */
- migratableCreditCardsInfo_: String,
-
- /**
- * Whether migration local card on settings page is enabled.
- * @private
- */
- migrationEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('migrationEnabled');
- },
- readOnly: true,
- },
- },
-
- listeners: {
- 'save-credit-card': 'saveCreditCard_',
- 'dots-card-menu-click': 'onCreditCardDotsMenuTap_',
- 'remote-card-menu-click': 'onRemoteEditCreditCardTap_',
- },
-
- /**
- * The element to return focus to, when the currently active dialog is
- * closed.
- * @private {?HTMLElement}
- */
- activeDialogAnchor_: null,
-
- /**
- * @type {PaymentsManager}
- * @private
- */
- PaymentsManager_: null,
-
- /**
- * @type {?function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>)}
- * @private
- */
- setPersonalDataListener_: null,
-
- /** @override */
- attached: function() {
- // Create listener function.
- /** @type {function(!Array<!PaymentsManager.CreditCardEntry>)} */
- const setCreditCardsListener = cardList => {
- this.creditCards = cardList;
- };
-
- // Update |userIsFidoVerifiable_| based on the availability of a platform
- // authenticator.
- if (window.PublicKeyCredential) {
- window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
- .then(r => {
- this.userIsFidoVerifiable_ = this.userIsFidoVerifiable_ && r;
- });
- }
-
- /**
- * @type {function(!Array<!AutofillManager.AddressEntry>,
- * !Array<!PaymentsManager.CreditCardEntry>)}
- */
- const setPersonalDataListener = (addressList, cardList) => {
- this.creditCards = cardList;
- };
-
- // Remember the bound reference in order to detach.
- this.setPersonalDataListener_ = setPersonalDataListener;
-
- // Set the managers. These can be overridden by tests.
- this.paymentsManager_ = PaymentsManagerImpl.getInstance();
-
- // Request initial data.
- this.paymentsManager_.getCreditCardList(setCreditCardsListener);
-
- // Listen for changes.
- this.paymentsManager_.setPersonalDataManagerListener(
- setPersonalDataListener);
-
- // Record that the user opened the payments settings.
- chrome.metricsPrivate.recordUserAction('AutofillCreditCardsViewed');
- },
-
- /** @override */
- detached: function() {
- this.paymentsManager_.removePersonalDataManagerListener(
- /**
- @type {function(!Array<!AutofillManager.AddressEntry>,
- !Array<!PaymentsManager.CreditCardEntry>)}
- */
- (this.setPersonalDataListener_));
- },
-
- /**
- * Opens the credit card action menu.
- * @param {!CustomEvent<{creditCard: !chrome.autofillPrivate.CreditCardEntry,
- * anchorElement: !HTMLElement}>} e
- * @private
- */
- onCreditCardDotsMenuTap_: function(e) {
- // Copy item so dialog won't update model on cancel.
- this.activeCreditCard = e.detail.creditCard;
-
- /** @type {!CrActionMenuElement} */ (this.$.creditCardSharedMenu)
- .showAt(e.detail.anchorElement);
- this.activeDialogAnchor_ = e.detail.anchorElement;
- },
-
- /**
- * Handles tapping on the "Add credit card" button.
- * @param {!Event} e
- * @private
- */
- onAddCreditCardTap_: function(e) {
- e.preventDefault();
- const date = new Date(); // Default to current month/year.
- const expirationMonth = date.getMonth() + 1; // Months are 0 based.
- this.activeCreditCard = {
- expirationMonth: expirationMonth.toString(),
- expirationYear: date.getFullYear().toString(),
- };
- this.showCreditCardDialog_ = true;
- this.activeDialogAnchor_ = this.$.addCreditCard;
- },
-
- /** @private */
- onCreditCardDialogClose_: function() {
- this.showCreditCardDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.activeDialogAnchor_));
- this.activeDialogAnchor_ = null;
- this.activeCreditCard = null;
- },
-
- /**
- * Handles tapping on the "Edit" credit card button.
- * @param {!Event} e The polymer event.
- * @private
- */
- onMenuEditCreditCardTap_: function(e) {
- e.preventDefault();
-
- if (this.activeCreditCard.metadata.isLocal) {
- this.showCreditCardDialog_ = true;
- } else {
- this.onRemoteEditCreditCardTap_();
- }
-
- this.$.creditCardSharedMenu.close();
- },
-
- /** @private */
- onRemoteEditCreditCardTap_: function() {
- this.paymentsManager_.logServerCardLinkClicked();
- window.open(loadTimeData.getString('manageCreditCardsUrl'));
- },
-
- /**
- * Handles tapping on the "Remove" credit card button.
- * @private
- */
- onMenuRemoveCreditCardTap_: function() {
- this.paymentsManager_.removeCreditCard(
- /** @type {string} */ (this.activeCreditCard.guid));
- this.$.creditCardSharedMenu.close();
- this.activeCreditCard = null;
- },
-
- /**
- * Handles tapping on the "Clear copy" button for cached credit cards.
- * @private
- */
- onMenuClearCreditCardTap_: function() {
- this.paymentsManager_.clearCachedCreditCard(
- /** @type {string} */ (this.activeCreditCard.guid));
- this.$.creditCardSharedMenu.close();
- this.activeCreditCard = null;
- },
-
- /**
- * Handles clicking on the "Migrate" button for migrate local credit
- * cards.
- * @private
- */
- onMigrateCreditCardsClick_: function() {
- this.paymentsManager_.migrateCreditCards();
- },
-
- /**
- * Listens for the save-credit-card event, and calls the private API.
- * @param {!Event} event
- * @private
- */
- saveCreditCard_: function(event) {
- this.paymentsManager_.saveCreditCard(event.detail);
- },
-
- /**
- * @param {boolean} creditCardEnabled
- * @return {boolean} Whether or not the user is verifiable through FIDO
- * authentication.
- * @private
- */
- shouldShowFidoToggle_: function(creditCardEnabled, userIsFidoVerifiable) {
- return creditCardEnabled && userIsFidoVerifiable;
- },
-
- /**
- * Listens for the enable-authentication event, and calls the private API.
- * @private
- */
- setFIDOAuthenticationEnabledState_: function() {
- this.paymentsManager_.setCreditCardFIDOAuthEnabledState(
- this.$$('#autofillCreditCardFIDOAuthToggle').checked);
- },
-
- /**
- * @param {!Array<!PaymentsManager.CreditCardEntry>} creditCards
- * @param {boolean} creditCardEnabled
- * @return {boolean} Whether to show the migration button.
- * @private
- */
- checkIfMigratable_: function(creditCards, creditCardEnabled) {
- // If migration prerequisites are not met, return false.
- if (!this.migrationEnabled_) {
- return false;
- }
-
- // If credit card enabled pref is false, return false.
- if (!creditCardEnabled) {
- return false;
- }
-
- const numberOfMigratableCreditCard =
- creditCards.filter(card => card.metadata.isMigratable).length;
- // Check whether exist at least one local valid card for migration.
- if (numberOfMigratableCreditCard == 0) {
- return false;
- }
-
- // Update the display text depends on the number of migratable credit
- // cards.
- this.migratableCreditCardsInfo_ = numberOfMigratableCreditCard == 1 ?
- this.i18n('migratableCardsInfoSingle') :
- this.i18n('migratableCardsInfoMultiple');
-
- return true;
- },
-
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.html b/chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.html
deleted file mode 100644
index f9787f2abb6..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-<if expr="chromeos">
-<link rel="import" href="blocking_request_manager.html">
-</if>
-
-<script src="show_password_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.js b/chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.js
deleted file mode 100644
index 4f73b4d1e1b..00000000000
--- a/chromium/chrome/browser/resources/settings/autofill_page/show_password_behavior.js
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * This behavior bundles functionality required to show a password to the user.
- * It is used by both <password-list-item> and <password-edit-dialog>.
- *
- * @polymerBehavior
- */
-const ShowPasswordBehavior = {
-
- properties: {
- /**
- * The password that is being displayed.
- * @type {!ShowPasswordBehavior.UiEntryWithPassword}
- */
- item: Object,
-
- // <if expr="chromeos">
- /** @type settings.BlockingRequestManager */
- tokenRequestManager: Object
- // </if>
- },
-
- /**
- * Gets the password input's type. Should be 'text' when password is visible
- * or when there's federated text otherwise 'password'.
- * @private
- */
- getPasswordInputType_: function() {
- return this.item.password || this.item.entry.federationText ? 'text' :
- 'password';
- },
-
- /**
- * Gets the title text for the show/hide icon.
- * @param {string} password
- * @param {string} hide The i18n text to use for 'Hide'
- * @param {string} show The i18n text to use for 'Show'
- * @private
- */
- showPasswordTitle_: function(password, hide, show) {
- return password ? hide : show;
- },
-
- /**
- * Get the right icon to display when hiding/showing a password.
- * @return {string}
- * @private
- */
- getIconClass_: function() {
- return this.item.password ? 'icon-visibility-off' : 'icon-visibility';
- },
-
- /**
- * Gets the text of the password. Will use the value of |password| unless it
- * cannot be shown, in which case it will be spaces. It can also be the
- * federated text.
- * @private
- */
- getPassword_: function() {
- if (!this.item) {
- return '';
- }
- return this.item.entry.federationText || this.item.password ||
- ' '.repeat(this.item.entry.numCharactersInPassword);
- },
-
- /**
- * Handler for tapping the show/hide button.
- * @private
- */
- onShowPasswordButtonTap_: function() {
- if (this.item.password) {
- this.set('item.password', '');
- return;
- }
- PasswordManagerImpl.getInstance()
- .getPlaintextPassword(this.item.entry.id)
- .then(password => {
- if (password) {
- this.set('item.password', password);
- }
- // <if expr="chromeos">
- if (!password) {
- // If no password was found, refresh auth token and retry.
- this.tokenRequestManager.request(
- this.onShowPasswordButtonTap_.bind(this));
- }
- // </if>
- });
- },
-};
-
-/**
- * @typedef {{
- * entry: !chrome.passwordsPrivate.PasswordUiEntry,
- * password: string
- * }}
- */
-ShowPasswordBehavior.UiEntryWithPassword;
diff --git a/chromium/chrome/browser/resources/settings/basic_page/basic_page.html b/chromium/chrome/browser/resources/settings/basic_page/basic_page.html
deleted file mode 100644
index 7c40d63c9d1..00000000000
--- a/chromium/chrome/browser/resources/settings/basic_page/basic_page.html
+++ /dev/null
@@ -1,342 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../appearance_page/appearance_page.html">
-<link rel="import" href="../autofill_page/autofill_page.html">
-<link rel="import" href="../change_password_page/change_password_page.html">
-<link rel="import" href="../controls/settings_idle_load.html">
-<link rel="import" href="../on_startup_page/on_startup_page.html">
-<link rel="import" href="../people_page/people_page.html">
-<link rel="import" href="../reset_page/reset_profile_banner.html">
-<link rel="import" href="../search_page/search_page.html">
-<link rel="import" href="../settings_page/main_page_behavior.html">
-<link rel="import" href="../settings_page/settings_section.html">
-<link rel="import" href="../settings_page_css.html">
-
-<if expr="chromeos">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="../android_apps_page/android_apps_browser_proxy.html">
-<link rel="import" href="../android_apps_page/android_apps_page.html">
-<link rel="import" href="../bluetooth_page/bluetooth_page.html">
-<link rel="import" href="../crostini_page/crostini_page.html">
-<link rel="import" href="../plugin_vm_page/plugin_vm_page.html">
-<link rel="import" href="../device_page/device_page.html">
-<link rel="import" href="../internet_page/internet_page.html">
-<link rel="import" href="../multidevice_page/multidevice_page.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-</if>
-
-<if expr="not chromeos">
-<link rel="import" href="../default_browser_page/default_browser_page.html">
-</if>
-
-<!-- TODO(michaelpg): Rename to something better than "basic" now that this page
- includes both the basic and advanced settings. -->
-<dom-module id="settings-basic-page">
- <template>
- <style include="settings-page-styles cr-hidden-style settings-shared">
- :host([is-subpage-animating]) {
- /* Prevent an unwanted horizontal scrollbar when transitioning back from
- * a sub-page. */
- overflow: hidden;
- }
-
- #advancedToggle {
- --ink-color: currentColor;
- align-items: center;
- background: transparent;
- border: none;
- box-shadow: none;
- color: currentColor;
- display: flex;
- font-weight: 400;
- margin-bottom: 3px;
- margin-top: 12px; /* Part of a 48px spacer (33px + 12px + 3px). */
- min-height: 32px;
- padding: 0 12px;
- }
-
- :host-context(.focus-outline-visible) #advancedToggle:focus {
- font-weight: 600;
- }
-
- #osSettingsBanner,
- #secondaryUserBanner {
- @apply --cr-card-elevation;
- align-items: center;
- background-color: white;
- border-radius: 2px;
- display: flex;
- margin-top: 21px;
- }
-
- #secondaryUserIcon {
- /* TODO(stevenjb): Replace this with the correct variable or color once
- * established by UX, see crbug.com/687749. */
- background-color : rgb(210, 210, 212);
- background-image: url(chrome://theme/IDR_SECONDARY_USER_SETTINGS);
- background-position: center;
- background-repeat: no-repeat;
- height: 55px;
- margin: 18px;
- width: 58px;
- }
-
- #toggleContainer {
- align-items: center;
- color: var(--cr-primary-text-color);
- display: flex;
- font: inherit;
- justify-content: center;
- margin-bottom: 0;
- margin-top: 0;
- padding-bottom: 0;
- padding-top: 0;
- }
-
- #toggleSpacer {
- padding-top: 33px; /* Part of a 48px spacer (33px + 12px + 3px). */
- }
-
- iron-icon {
- margin-inline-start: 16px;
- }
- </style>
- <template is="dom-if" if="[[showBasicPage_(
- currentRoute_, inSearchMode, hasExpandedSection_)]]">
- <div id="basicPage">
- <template is="dom-if" if="[[showResetProfileBanner_]]" restamp>
- <settings-reset-profile-banner on-close="onResetProfileBannerClosed_">
- </settings-reset-profile-banner>
- </template>
-<if expr="chromeos">
- <template is="dom-if" if="[[showOSSettingsBanner_]]">
- <div id="osSettingsBanner" class="settings-box">
- <div class="start" on-click="onOSSettingsBannerClick_">
- $i18nRaw{osSettingsBannerText}
- </div>
- <cr-icon-button class="icon-clear"
- title="$i18n{close}"
- on-click="onOSSettingsBannerClosed_">
- </cr-icon-button>
- </div>
- </template>
- <div id="secondaryUserBanner" hidden="[[!showSecondaryUserBanner_]]">
- <div id="secondaryUserIcon"></div>
- <div class="flex">$i18n{secondaryUserBannerText}</div>
- </div>
- <template is="dom-if" if="[[showPage_(pageVisibility.internet)]]"
- restamp>
- <settings-section page-title="$i18n{internetPageTitle}"
- section="internet">
- <settings-internet-page prefs="{{prefs}}">
- </settings-internet-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.bluetooth)]]"
- restamp>
- <settings-section page-title="$i18n{bluetoothPageTitle}"
- section="bluetooth">
- <settings-bluetooth-page prefs="{{prefs}}">
- </settings-bluetooth-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.multidevice)]]"
- restamp>
- <settings-section page-title="$i18n{multidevicePageTitle}"
- section="multidevice">
- <settings-multidevice-page prefs="{{prefs}}">
- </settings-multidevice-page>
- </settings-section>
- </template>
-</if>
- <template is="dom-if" if="[[showChangePassword]]" restamp>
- <settings-section section="changePassword">
- <settings-change-password-page></settings-change-password-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.people)]]" restamp>
- <settings-section page-title="$i18n{peoplePageTitle}"
- section="people">
- <settings-people-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility]]">
- </settings-people-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.autofill)]]"
- restamp>
- <settings-section page-title="$i18n{autofillPageTitle}"
- section="autofill">
- <settings-autofill-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility]]">
- </settings-autofill-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.appearance)]]"
- restamp>
- <settings-section page-title="$i18n{appearancePageTitle}"
- section="appearance">
- <settings-appearance-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.appearance]]">
- </settings-appearance-page>
- </settings-section>
- </template>
-<if expr="chromeos">
- <template is="dom-if" if="[[showPage_(pageVisibility.device)]]" restamp>
- <settings-section page-title="$i18n{devicePageTitle}"
- section="device">
- <settings-device-page prefs="{{prefs}}"
- show-crostini="[[showCrostini]]">
- </settings-device-page>
- </settings-section>
- </template>
-</if>
- <settings-section page-title="$i18n{searchPageTitle}"
- section="search">
- <settings-search-page prefs="{{prefs}}"></settings-search-page>
- </settings-section>
-<if expr="chromeos">
- <template is="dom-if" if="[[showAndroidApps]]" restamp>
- <settings-section page-title="$i18n{androidAppsPageTitle}"
- section="androidApps" hidden$="[[!shouldShowAndroidAppsSection_(
- androidAppsInfo)]]">
- <settings-android-apps-page prefs="{{prefs}}"
- android-apps-info="[[androidAppsInfo]]"
- have-play-store-app="[[havePlayStoreApp]]">
- </settings-android-apps-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showCrostini]]" restamp>
- <settings-section page-title="$i18n{crostiniPageTitle}"
- section="crostini">
- <settings-crostini-page prefs="{{prefs}}"
- allow-crostini="[[allowCrostini_]]">
- </settings-crostini-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPluginVm]]" restamp>
- <settings-section page-title="$i18n{pluginVmPageTitle}"
- section="pluginVm">
- <settings-plugin-vm-page prefs="{{prefs}}">
- </settings-plugin-vm-page>
- </settings-section>
- </template>
-</if>
-<if expr="not chromeos">
- <template is="dom-if" if="[[showPage_(pageVisibility.defaultBrowser)]]"
- restamp>
- <settings-section page-title="$i18n{defaultBrowser}"
- section="defaultBrowser">
- <settings-default-browser-page></settings-default-browser-page>
- </settings-section>
- </template>
-</if>
- <template is="dom-if" if="[[showPage_(pageVisibility.onStartup)]]"
- restamp>
- <settings-section page-title="$i18n{onStartup}" section="onStartup">
- <settings-on-startup-page prefs="{{prefs}}">
- </settings-on-startup-page>
- </settings-section>
- </template>
- </div>
- </template>
-
- <template is="dom-if" if="[[showAdvancedSettings_(pageVisibility.advancedSettings)]]">
- <template is="dom-if" if="[[showAdvancedToggle_(
- inSearchMode, hasExpandedSection_)]]">
- <div id="toggleSpacer"></div>
- <h2 id="toggleContainer">
- <cr-button id="advancedToggle" on-click="advancedToggleClicked_"
- aria-expanded$="[[boolToString_(advancedToggleExpanded)]]">
- <span>$i18n{advancedPageTitle}</span>
- <iron-icon icon="[[getArrowIcon_(advancedToggleExpanded)]]">
- </iron-icon>
- </cr-button>
- </h2>
- </template>
-
- <settings-idle-load id="advancedPageTemplate" url="/lazy_load.html">
- <template>
- <div id="advancedPage" hidden$="[[!showAdvancedPage_(
- currentRoute_, inSearchMode, hasExpandedSection_,
- advancedToggleExpanded)]]">
-<if expr="chromeos">
- <template is="dom-if" if="[[showPage_(pageVisibility.dateTime)]]"
- restamp>
- <settings-section page-title="$i18n{dateTimePageTitle}"
- section="dateTime">
- <settings-date-time-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.dateTime]]">
- </settings-date-time-page>
- </settings-section>
- </template>
-</if>
- <template is="dom-if" if="[[showPage_(pageVisibility.privacy)]]"
- restamp>
- <settings-section page-title="$i18n{privacyPageTitle}"
- section="privacy">
- <settings-privacy-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.privacy]]">
- </settings-privacy-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.languages)]]"
- restamp>
- <settings-section page-title="$i18n{languagesPageTitle}"
- section="languages">
- <settings-languages-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.languages]]">
- </settings-languages-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.downloads)]]"
- restamp>
- <settings-section page-title="$i18n{downloadsPageTitle}"
- section="downloads">
- <settings-downloads-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.downloads]]">
- </settings-downloads-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.printing)]]"
- restamp>
- <settings-section page-title="$i18n{printingPageTitle}"
- section="printing">
- <settings-printing-page prefs="{{prefs}}">
- </settings-printing-page>
- </settings-section>
- </template>
- <template is="dom-if" if="[[showPage_(pageVisibility.a11y)]]"
- restamp>
- <settings-section page-title="$i18n{a11yPageTitle}"
- section="a11y">
- <settings-a11y-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.a11y]]">
- </settings-a11y-page>
- </settings-section>
- </template>
-<if expr="not chromeos">
- <settings-section page-title="$i18n{systemPageTitle}"
- section="system">
- <settings-system-page prefs="{{prefs}}"></settings-system-page>
- </settings-section>
-</if>
- <template is="dom-if" if="[[showPage_(pageVisibility.reset)]]"
- restamp>
- <settings-section page-title="$i18n{resetPageTitle}"
- section="reset">
- <settings-reset-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility.reset]]">
- </settings-reset-page>
- </settings-section>
- </template>
- </div>
- </template>
- </settings-idle-load>
- </template>
- </template>
- <script src="basic_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/basic_page/basic_page.js b/chromium/chrome/browser/resources/settings/basic_page/basic_page.js
deleted file mode 100644
index f12eb098ddc..00000000000
--- a/chromium/chrome/browser/resources/settings/basic_page/basic_page.js
+++ /dev/null
@@ -1,451 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-basic-page' is the settings page containing the actual settings.
- */
-(function() {
-'use strict';
-
-// <if expr="chromeos">
-const OS_BANNER_INTERACTION_METRIC_NAME =
- 'ChromeOS.Settings.OsBannerInteraction';
-
-/**
- * These values are persisted to logs and should not be renumbered or re-used.
- * See tools/metrics/histograms/enums.xml.
- * @enum {number}
- */
-const CrosSettingsOsBannerInteraction = {
- NotShown: 0,
- Shown: 1,
- Clicked: 2,
- Closed: 3,
-};
-// </if>
-
-Polymer({
- is: 'settings-basic-page',
-
- behaviors: [
- settings.MainPageBehavior,
- settings.RouteObserverBehavior,
- PrefsBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- // <if expr="chromeos">
- showAndroidApps: Boolean,
-
- showCrostini: Boolean,
-
- allowCrostini_: Boolean,
-
- havePlayStoreApp: Boolean,
- // </if>
-
- /** @type {!AndroidAppsInfo|undefined} */
- androidAppsInfo: Object,
-
- showChangePassword: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!PageVisibility}
- */
- pageVisibility: {
- type: Object,
- value: function() {
- return {};
- },
- },
-
- advancedToggleExpanded: {
- type: Boolean,
- value: false,
- notify: true,
- observer: 'advancedToggleExpandedChanged_',
- },
-
- /**
- * True if a section is fully expanded to hide other sections beneath it.
- * False otherwise (even while animating a section open/closed).
- * @private {boolean}
- */
- hasExpandedSection_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * True if the basic page should currently display the reset profile banner.
- * @private {boolean}
- */
- showResetProfileBanner_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('showResetProfileBanner');
- },
- },
-
- // <if expr="chromeos">
- /**
- * Whether the user is a secondary user. Computed so that it is calculated
- * correctly after loadTimeData is available.
- * @private
- */
- showSecondaryUserBanner_: {
- type: Boolean,
- computed: 'computeShowSecondaryUserBanner_(hasExpandedSection_)',
- },
-
- /** @private */
- showOSSettingsBanner_: {
- type: Boolean,
- computed: 'computeShowOSSettingsBanner_(' +
- 'prefs.settings.cros.show_os_banner.value, currentRoute_)',
- },
- // </if>
-
- /** @private {!settings.Route|undefined} */
- currentRoute_: Object,
- },
-
- hostAttributes: {
- role: 'main',
- },
-
- listeners: {
- 'subpage-expand': 'onSubpageExpanded_',
- },
-
- /**
- * Used to avoid handling a new toggle while currently toggling.
- * @private {boolean}
- */
- advancedTogglingInProgress_: false,
-
- // <if expr="chromeos">
- /** @private {boolean} */
- osBannerShowMetricRecorded_: false,
- // </if>
-
- /** @override */
- attached: function() {
- this.currentRoute_ = settings.getCurrentRoute();
-
- this.allowCrostini_ = loadTimeData.valueExists('allowCrostini') &&
- loadTimeData.getBoolean('allowCrostini');
-
- this.addWebUIListener('change-password-visibility', visibility => {
- this.showChangePassword = visibility;
- });
-
- if (settings.AndroidAppsBrowserProxyImpl) {
- this.addWebUIListener(
- 'android-apps-info-update', this.androidAppsInfoUpdate_.bind(this));
- settings.AndroidAppsBrowserProxyImpl.getInstance()
- .requestAndroidAppsInfo();
- }
- },
-
- /**
- * @param {!settings.Route} newRoute
- * @param {settings.Route} oldRoute
- */
- currentRouteChanged: function(newRoute, oldRoute) {
- this.currentRoute_ = newRoute;
-
- if (settings.routes.ADVANCED &&
- settings.routes.ADVANCED.contains(newRoute)) {
- this.advancedToggleExpanded = true;
- }
-
- if (oldRoute && oldRoute.isSubpage()) {
- // If the new route isn't the same expanded section, reset
- // hasExpandedSection_ for the next transition.
- if (!newRoute.isSubpage() || newRoute.section != oldRoute.section) {
- this.hasExpandedSection_ = false;
- }
- } else {
- assert(!this.hasExpandedSection_);
- }
-
- settings.MainPageBehavior.currentRouteChanged.call(
- this, newRoute, oldRoute);
- },
-
- // Override settings.MainPageBehavior method.
- containsRoute: function(route) {
- return !route || settings.routes.BASIC.contains(route) ||
- settings.routes.ADVANCED.contains(route);
- },
-
- /**
- * @param {boolean|undefined} visibility
- * @return {boolean}
- * @private
- */
- showPage_: function(visibility) {
- return visibility !== false;
- },
-
- /**
- * Queues a task to search the basic sections, then another for the advanced
- * sections.
- * @param {string} query The text to search for.
- * @return {!Promise<!settings.SearchResult>} A signal indicating that
- * searching finished.
- */
- searchContents: function(query) {
- const whenSearchDone = [
- settings.getSearchManager().search(query, assert(this.$$('#basicPage'))),
- ];
-
- if (this.pageVisibility.advancedSettings !== false) {
- whenSearchDone.push(
- this.$$('#advancedPageTemplate').get().then(function(advancedPage) {
- return settings.getSearchManager().search(query, advancedPage);
- }));
- }
-
- return Promise.all(whenSearchDone).then(function(requests) {
- // Combine the SearchRequests results to a single SearchResult object.
- return {
- canceled: requests.some(function(r) {
- return r.canceled;
- }),
- didFindMatches: requests.some(function(r) {
- return r.didFindMatches();
- }),
- // All requests correspond to the same user query, so only need to check
- // one of them.
- wasClearSearch: requests[0].isSame(''),
- };
- });
- },
-
- // <if expr="chromeos">
- /**
- * @return {boolean}
- * @private
- */
- computeShowSecondaryUserBanner_: function() {
- return !this.hasExpandedSection_ &&
- loadTimeData.getBoolean('isSecondaryUser');
- },
-
- /**
- * @return {boolean|undefined}
- * @private
- */
- computeShowOSSettingsBanner_: function() {
- // this.prefs is implicitly used by this.getPref() below.
- if (!this.prefs || !this.currentRoute_) {
- return;
- }
- // Don't show the banner when SplitSettings is disabled (and hence this page
- // is already showing OS settings).
- if (loadTimeData.getBoolean('showOSSettings')) {
- return false;
- }
- const showPref = /** @type {boolean} */ (
- this.getPref('settings.cros.show_os_banner').value);
-
- // Banner only shows on the main page because direct navigations to a
- // sub-page are unlikely to be due to a user looking for an OS setting.
- const show = showPref && !this.currentRoute_.isSubpage();
-
- // Record the show metric once. We can't record the metric in attached()
- // because prefs might not be ready yet.
- if (!this.osBannerShowMetricRecorded_) {
- chrome.metricsPrivate.recordEnumerationValue(
- OS_BANNER_INTERACTION_METRIC_NAME,
- show ? CrosSettingsOsBannerInteraction.Shown :
- CrosSettingsOsBannerInteraction.NotShown,
- Object.keys(CrosSettingsOsBannerInteraction).length);
- this.osBannerShowMetricRecorded_ = true;
- }
- return show;
- },
-
- /** @private */
- onOSSettingsBannerClick_: function() {
- // The label has a link that opens the page, so just record the metric.
- chrome.metricsPrivate.recordEnumerationValue(
- OS_BANNER_INTERACTION_METRIC_NAME,
- CrosSettingsOsBannerInteraction.Clicked,
- Object.keys(CrosSettingsOsBannerInteraction).length);
- },
-
- /** @private */
- onOSSettingsBannerClosed_: function() {
- this.setPrefValue('settings.cros.show_os_banner', false);
- chrome.metricsPrivate.recordEnumerationValue(
- OS_BANNER_INTERACTION_METRIC_NAME,
- CrosSettingsOsBannerInteraction.Closed,
- Object.keys(CrosSettingsOsBannerInteraction).length);
- },
- // </if>
-
- /** @private */
- onResetProfileBannerClosed_: function() {
- this.showResetProfileBanner_ = false;
- },
-
- /**
- * @param {!AndroidAppsInfo} info
- * @private
- */
- androidAppsInfoUpdate_: function(info) {
- this.androidAppsInfo = info;
- },
-
- /**
- * Returns true in case Android apps settings should be shown. It is not
- * shown in case we don't have the Play Store app and settings app is not
- * yet available.
- * @return {boolean}
- * @private
- */
- shouldShowAndroidAppsSection_: function() {
- if (this.havePlayStoreApp ||
- (this.androidAppsInfo && this.androidAppsInfo.settingsAppAvailable)) {
- return true;
- }
- return false;
- },
-
- /**
- * Hides everything but the newly expanded subpage.
- * @private
- */
- onSubpageExpanded_: function() {
- this.hasExpandedSection_ = true;
- },
-
- /**
- * Render the advanced page now (don't wait for idle).
- * @private
- */
- advancedToggleExpandedChanged_: function() {
- if (!this.advancedToggleExpanded) {
- return;
- }
-
- // In Polymer2, async() does not wait long enough for layout to complete.
- // Polymer.RenderStatus.beforeNextRender() must be used instead.
- Polymer.RenderStatus.beforeNextRender(this, () => {
- this.$$('#advancedPageTemplate').get();
- });
- },
-
- advancedToggleClicked_: function() {
- if (this.advancedTogglingInProgress_) {
- return;
- }
-
- this.advancedTogglingInProgress_ = true;
- const toggle = this.$$('#toggleContainer');
- if (!this.advancedToggleExpanded) {
- this.advancedToggleExpanded = true;
- this.async(() => {
- this.$$('#advancedPageTemplate').get().then(() => {
- this.fire('scroll-to-top', {
- top: toggle.offsetTop,
- callback: () => {
- this.advancedTogglingInProgress_ = false;
- }
- });
- });
- });
- } else {
- this.fire('scroll-to-bottom', {
- bottom: toggle.offsetTop + toggle.offsetHeight + 24,
- callback: () => {
- this.advancedToggleExpanded = false;
- this.advancedTogglingInProgress_ = false;
- }
- });
- }
- },
-
- /**
- * @param {boolean} inSearchMode
- * @param {boolean} hasExpandedSection
- * @return {boolean}
- * @private
- */
- showAdvancedToggle_: function(inSearchMode, hasExpandedSection) {
- return !inSearchMode && !hasExpandedSection;
- },
-
- /**
- * @param {!settings.Route} currentRoute
- * @param {boolean} inSearchMode
- * @param {boolean} hasExpandedSection
- * @return {boolean} Whether to show the basic page, taking into account
- * both routing and search state.
- * @private
- */
- showBasicPage_: function(currentRoute, inSearchMode, hasExpandedSection) {
- return !hasExpandedSection || settings.routes.BASIC.contains(currentRoute);
- },
-
- /**
- * @param {!settings.Route} currentRoute
- * @param {boolean} inSearchMode
- * @param {boolean} hasExpandedSection
- * @param {boolean} advancedToggleExpanded
- * @return {boolean} Whether to show the advanced page, taking into account
- * both routing and search state.
- * @private
- */
- showAdvancedPage_: function(
- currentRoute, inSearchMode, hasExpandedSection, advancedToggleExpanded) {
- return hasExpandedSection ?
- (settings.routes.ADVANCED &&
- settings.routes.ADVANCED.contains(currentRoute)) :
- advancedToggleExpanded || inSearchMode;
- },
-
- /**
- * @param {(boolean|undefined)} visibility
- * @return {boolean} True unless visibility is false.
- * @private
- */
- showAdvancedSettings_: function(visibility) {
- return visibility !== false;
- },
-
- /**
- * @param {boolean} opened Whether the menu is expanded.
- * @return {string} Icon name.
- * @private
- */
- getArrowIcon_: function(opened) {
- return opened ? 'cr:arrow-drop-up' : 'cr:arrow-drop-down';
- },
-
- /**
- * @param {boolean} bool
- * @return {string}
- * @private
- */
- boolToString_: function(bool) {
- return bool.toString();
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html
deleted file mode 100644
index 739b1b7b29b..00000000000
--- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../chromeos/os_icons.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="bluetooth-device-list-item">
- <template>
- <style include="settings-shared">
- .name[connected] {
- font-weight: 500;
- }
-
- .state[connected] {
- color: var(--google-green-500);
- }
- </style>
- <div class="list-item">
- <iron-icon id="deviceIcon" icon="[[getDeviceIcon_(device)]]">
- </iron-icon>
- <div class="middle">
- <div class="name" connected$="[[device.connected]]">
- [[getDeviceName_(device)]]
- </div>
- <div class="state secondary" connected$="[[device.connected]]"
- hidden$="[[!hasConnectionStatusText_(device)]]">
- [[getConnectionStatusText_(device)]]
- </div>
- </div>
- <div hidden$="[[!device.paired]]">
- <cr-icon-button class="icon-more-vert" on-click="onMenuButtonTap_"
- tabindex$="[[tabindex]]" title="$i18n{moreActions}"
- on-keydown="ignoreEnterKey_"></cr-icon-button>
- <cr-action-menu id="dotsMenu">
- <button class="dropdown-item"
- on-click="onConnectActionTap_">
- [[getConnectActionText_(device.connected)]]
- </button>
- <button class="dropdown-item" on-click="onRemoveTap_">
- $i18n{bluetoothRemove}
- </button>
- </cr-action-menu>
- </div>
- </div>
- </template>
- <script src="bluetooth_device_list_item.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js
deleted file mode 100644
index 9ed885e2782..00000000000
--- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer element for displaying a bluetooth device in a list.
- */
-
-Polymer({
- is: 'bluetooth-device-list-item',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * The bluetooth device.
- * @type {!chrome.bluetooth.Device}
- */
- device: {
- type: Object,
- },
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- ignoreEnterKey_: function(event) {
- if (event.key == 'Enter') {
- event.stopPropagation();
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onMenuButtonTap_: function(event) {
- const button = /** @type {!HTMLElement} */ (event.target);
- const menu = /** @type {!CrActionMenuElement} */ (this.$.dotsMenu);
- menu.showAt(button);
- event.stopPropagation();
- },
-
- /** @private */
- onConnectActionTap_: function() {
- const action = this.isDisconnected_(this.device) ? 'connect' : 'disconnect';
- this.fire('device-event', {
- action: action,
- device: this.device,
- });
- /** @type {!CrActionMenuElement} */ (this.$.dotsMenu).close();
- },
-
- /** @private */
- onRemoveTap_: function() {
- this.fire('device-event', {
- action: 'remove',
- device: this.device,
- });
- /** @type {!CrActionMenuElement} */ (this.$.dotsMenu).close();
- },
-
- /**
- * @param {boolean} connected
- * @return {string} The text to display for the connect/disconnect menu item.
- * @private
- */
- getConnectActionText_: function(connected) {
- return this.i18n(connected ? 'bluetoothDisconnect' : 'bluetoothConnect');
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @return {string} The text to display for |device| in the device list.
- * @private
- */
- getDeviceName_: function(device) {
- return device.name || device.address;
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @return {string} The text to display the connection status of |device|.
- * @private
- */
- getConnectionStatusText_: function(device) {
- if (!this.hasConnectionStatusText_(device)) {
- return '';
- }
- if (device.connecting) {
- return this.i18n('bluetoothConnecting');
- }
- if (!device.connected) {
- return this.i18n('bluetoothNotConnected');
- }
- return device.batteryPercentage !== undefined ?
- this.i18n('bluetoothConnectedWithBattery', device.batteryPercentage) :
- this.i18n('bluetoothConnected');
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @return {boolean} True if connection status should be shown as the
- * secondary text of the |device| in device list.
- * @private
- */
- hasConnectionStatusText_: function(device) {
- return !!(device.paired || device.connecting);
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @return {boolean}
- * @private
- */
- isDisconnected_: function(device) {
- return !device.connected && !device.connecting;
- },
-
- /**
- * Returns device type icon's ID corresponding to the given device.
- * To be consistent with the Bluetooth device list in system menu, this
- * mapping needs to be synced with ash::tray::GetBluetoothDeviceIcon().
- *
- * @param {!chrome.bluetooth.Device} device
- * @return {string}
- * @private
- */
- getDeviceIcon_: function(device) {
- switch (device.type) {
- case 'computer':
- return 'cr:computer';
- case 'phone':
- return 'os-settings:smartphone';
- case 'audio':
- case 'carAudio':
- return 'os-settings:headset';
- case 'video':
- return 'cr:videocam';
- case 'joystick':
- case 'gamepad':
- return 'os-settings:gamepad';
- case 'keyboard':
- case 'keyboardMouseCombo':
- return 'os-settings:keyboard';
- case 'tablet':
- return 'os-settings:tablet';
- case 'mouse':
- return 'os-settings:mouse';
- default:
- return device.connected ? 'os-settings:bluetooth-connected' :
- 'cr:bluetooth';
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html
deleted file mode 100644
index 44dac770e3e..00000000000
--- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../chromeos/os_icons.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="bluetooth_subpage.html">
-
-<dom-module id="settings-bluetooth-page">
- <template>
- <style include="settings-shared">
- .link-wrapper {
- align-items: center;
- display: flex;
- flex-grow: 1;
- }
- </style>
- <settings-animated-pages id="pages" section="bluetooth"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <template is="dom-if" if="[[!isSecondaryUser_]]">
- <div id="bluetoothDevices" class="settings-box two-line">
- <div class="link-wrapper" actionable on-click="onTap_">
- <iron-icon icon="[[getIcon_(bluetoothToggleState_)]]"></iron-icon>
- <div class="middle settings-box-text">
- $i18n{bluetoothPageTitle}
- <div class="secondary" id="bluetoothSecondary">
- [[getOnOffString_(bluetoothToggleState_,
- '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]
- </div>
- </div>
- <cr-policy-pref-indicator
- icon-aria-label="$i18n{bluetoothPageTitle}"
- pref="[[prefs.cros.device.allow_bluetooth]]"
- hidden="[[prefs.cros.device.allow_bluetooth.value]]">
- </cr-policy-pref-indicator>
- <template is="dom-if" if="[[bluetoothToggleState_]]">
- <cr-icon-button class="subpage-arrow"
- on-click="onSubpageArrowTap_"
- aria-label="$i18n{bluetoothPageTitle}"
- aria-describedby="bluetoothSecondary"></cr-icon-button>
- </template>
- </div>
- <div class="separator"></div>
- <cr-toggle id="enableBluetooth"
- checked="{{bluetoothToggleState_}}"
- disabled$="[[!isToggleEnabled_(
- adapterState_, stateChangeInProgress_)]]"
- aria-label="$i18n{bluetoothToggleA11yLabel}">
- </cr-toggle>
- </div>
- </template>
- <template is="dom-if" if="[[isSecondaryUser_]]">
- <div id="bluetoothDevices" class="settings-box two-line">
- <iron-icon class="policy" icon="cr:group"></iron-icon>
- <div class="middle settings-box-text">
- [[i18n('bluetoothPrimaryUserControlled', primaryUserEmail_)]]
- </div>
- </div>
- </template>
- </div>
-
- <template is="dom-if" route-path="/bluetoothDevices">
- <settings-subpage associated-control="[[$$('#bluetoothDevices')]]"
- page-title="$i18n{bluetoothPageTitle}">
- <settings-bluetooth-subpage
- adapter-state="[[adapterState_]]"
- bluetooth-toggle-state="{{bluetoothToggleState_}}"
- state-change-in-progress="[[stateChangeInProgress_]]"
- bluetooth="[[bluetooth]]"
- bluetooth-private="[[bluetoothPrivate]]">
- </settings-bluetooth-subpage>
- </settings-subpage>
- </template>
-
- </settings-animated-pages>
- </template>
- <script src="bluetooth_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js
deleted file mode 100644
index fd9aabbdf7f..00000000000
--- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'Settings page for managing bluetooth properties and devices. This page
- * just provodes a summary and link to the subpage.
- */
-
-const bluetoothApis = window['bluetoothApis'] || {
- /**
- * Set this to provide a fake implementation for testing.
- * @type {Bluetooth}
- */
- bluetoothApiForTest: null,
-
- /**
- * Set this to provide a fake implementation for testing.
- * @type {BluetoothPrivate}
- */
- bluetoothPrivateApiForTest: null,
-};
-
-Polymer({
- is: 'settings-bluetooth-page',
-
- behaviors: [I18nBehavior, PrefsBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Reflects the current state of the toggle buttons (in this page and the
- * subpage). This will be set when the adapter state change or when the user
- * changes the toggle.
- * @private
- */
- bluetoothToggleState_: {
- type: Boolean,
- observer: 'bluetoothToggleStateChanged_',
- },
-
- /**
- * Set to true while an adapter state change is requested and the callback
- * hasn't fired yet. One of the factor that determines whether to disable
- * the toggle button.
- * @private
- */
- stateChangeInProgress_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The cached bluetooth adapter state.
- * @type {!chrome.bluetooth.AdapterState|undefined}
- * @private
- */
- adapterState_: {
- type: Object,
- notify: true,
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.BLUETOOTH_DEVICES) {
- map.set(
- settings.routes.BLUETOOTH_DEVICES.path,
- '#bluetoothDevices .subpage-arrow');
- }
- return map;
- },
- },
-
- /**
- * Interface for bluetooth calls. May be overriden by tests.
- * @type {Bluetooth}
- * @private
- */
- bluetooth: {
- type: Object,
- value: chrome.bluetooth,
- },
-
- /**
- * Interface for bluetoothPrivate calls. May be overriden by tests.
- * @type {BluetoothPrivate}
- * @private
- */
- bluetoothPrivate: {
- type: Object,
- value: chrome.bluetoothPrivate,
- },
-
- /**
- * Whether the user is a secondary user.
- * @private
- */
- isSecondaryUser_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isSecondaryUser');
- },
- readOnly: true,
- },
-
- /**
- * Email address for the primary user.
- * @private
- */
- primaryUserEmail_: {
- type: String,
- value: function() {
- return loadTimeData.getString('primaryUserEmail');
- },
- readOnly: true,
- },
- },
-
- observers: ['deviceListChanged_(deviceList_.*)'],
-
- /**
- * Listener for chrome.bluetooth.onAdapterStateChanged events.
- * @type {function(!chrome.bluetooth.AdapterState)|undefined}
- * @private
- */
- bluetoothAdapterStateChangedListener_: undefined,
-
- /** @override */
- ready: function() {
- if (bluetoothApis.bluetoothApiForTest) {
- this.bluetooth = bluetoothApis.bluetoothApiForTest;
- }
- if (bluetoothApis.bluetoothPrivateApiForTest) {
- this.bluetoothPrivate = bluetoothApis.bluetoothPrivateApiForTest;
- }
- },
-
- /** @override */
- attached: function() {
- this.bluetoothAdapterStateChangedListener_ =
- this.onBluetoothAdapterStateChanged_.bind(this);
- this.bluetooth.onAdapterStateChanged.addListener(
- this.bluetoothAdapterStateChangedListener_);
-
- // Request the inital adapter state.
- this.bluetooth.getAdapterState(this.bluetoothAdapterStateChangedListener_);
- },
-
- /** @override */
- detached: function() {
- if (this.bluetoothAdapterStateChangedListener_) {
- this.bluetooth.onAdapterStateChanged.removeListener(
- this.bluetoothAdapterStateChangedListener_);
- }
- },
-
- /**
- * @param {boolean} bluetoothToggleState
- * @return {string}
- * @private
- */
- getIcon_: function(bluetoothToggleState) {
- // Don't use |this.bluetoothToggleState_| here, since it has not been
- // updated yet to the latest value.
- if (!bluetoothToggleState) {
- return 'os-settings:bluetooth-disabled';
- }
- return 'cr:bluetooth';
- },
-
- /**
- * @param {boolean} enabled
- * @param {string} onstr
- * @param {string} offstr
- * @return {string}
- * @private
- */
- getOnOffString_: function(enabled, onstr, offstr) {
- return enabled ? onstr : offstr;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isToggleEnabled_: function() {
- return this.adapterState_ !== undefined && this.adapterState_.available &&
- !this.stateChangeInProgress_;
- },
-
- /**
- * Process bluetooth.onAdapterStateChanged events.
- * @param {!chrome.bluetooth.AdapterState} state
- * @private
- */
- onBluetoothAdapterStateChanged_: function(state) {
- this.adapterState_ = state;
- if (this.isToggleEnabled_()) {
- this.bluetoothToggleState_ = state.powered;
- }
- },
-
- /** @private */
- onTap_: function() {
- if (!this.isToggleEnabled_()) {
- return;
- }
- if (!this.bluetoothToggleState_) {
- this.bluetoothToggleState_ = true;
- } else {
- this.openSubpage_();
- }
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onSubpageArrowTap_: function(e) {
- this.openSubpage_();
- e.stopPropagation();
- },
-
- /** @private */
- bluetoothToggleStateChanged_: function() {
- if (!this.adapterState_ || !this.isToggleEnabled_() ||
- this.bluetoothToggleState_ == this.adapterState_.powered) {
- return;
- }
- this.stateChangeInProgress_ = true;
- this.bluetoothPrivate.setAdapterState(
- {powered: this.bluetoothToggleState_}, () => {
- // Restore the in-progress mark when the callback is called regardless
- // of error or success.
- this.stateChangeInProgress_ = false;
-
- const error = chrome.runtime.lastError;
- if (error && error != 'Error setting adapter properties: powered') {
- console.error('Error enabling bluetooth: ' + error.message);
- return;
- }
- this.setPrefValue(
- 'ash.user.bluetooth.adapter_enabled',
- this.bluetoothToggleState_);
- });
- },
-
- /** @private */
- openSubpage_: function() {
- settings.navigateTo(settings.routes.BLUETOOTH_DEVICES);
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html
deleted file mode 100644
index 9a91e3223b8..00000000000
--- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html
+++ /dev/null
@@ -1,116 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/bluetooth_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="bluetooth_device_list_item.html">
-
-<dom-module id="settings-bluetooth-subpage">
- <template>
- <style include="settings-shared iron-flex">
- .container {
- @apply --settings-list-frame-padding;
- display: flex;
- flex-direction: column;
- min-height: 10px;
- overflow-y: auto;
- }
-
- .header {
- margin-top: 10px;
- }
-
- paper-spinner-lite {
- @apply --cr-icon-height-width;
- }
-
- #onOff {
- font-weight: 500;
- }
-
- #onOff[on] {
- color: var(--cr-toggle-color);
- }
- </style>
-
- <div class="settings-box first" actionable on-click="onEnableTap_">
- <div id="onOff" class="start settings-box-text"
- on$="[[bluetoothToggleState]]">
- [[getOnOffString_(bluetoothToggleState,
- '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]
- </div>
- <cr-toggle id="enableBluetooth"
- checked="{{bluetoothToggleState}}"
- disabled$="[[!isToggleEnabled_(adapterState, stateChangeInProgress)]]"
- aria-label="$i18n{bluetoothToggleA11yLabel}">
- </cr-toggle>
- </div>
-
- <!-- Paired device list -->
- <div class="settings-box first header" hidden="[[!bluetoothToggleState]]">
- <div class="start settings-box-text">
- $i18n{bluetoothDeviceListPaired}
- </div>
- </div>
- <div id="noPairedDevices" class="list-frame settings-box-text"
- hidden="[[!showNoDevices_(bluetoothToggleState, pairedDeviceList_)]]">
- $i18n{bluetoothNoDevices}
- </div>
- <div id="pairedContainer" class="container"
- scrollable on-device-event="onDeviceEvent_"
- hidden="[[!showDevices_(bluetoothToggleState, pairedDeviceList_)]]">
- <iron-list id="pairedDevices" preserve-focus items="[[pairedDeviceList_]]"
- selection-enabled selected-item="{{selectedPairedItem_}}"
- scroll-target="pairedContainer" class="cr-separators">
- <template>
- <bluetooth-device-list-item actionable device="[[item]]"
- first$="[[!index]]" tabindex$="[[tabIndex]]">
- </bluetooth-device-list-item>
- </template>
- </iron-list>
- </div>
-
- <!-- Unpaired device list -->
- <div class="settings-box first header" hidden="[[!bluetoothToggleState]]">
- <div class="start settings-box-text">
- $i18n{bluetoothDeviceListUnpaired}
- </div>
- <paper-spinner-lite active="[[showSpinner_]]">
- </paper-spinner-lite>
- </div>
- <div id="noUnpairedDevices" class="list-frame settings-box-text"
- hidden="[[!showNoDevices_(bluetoothToggleState, unpairedDeviceList_)]]">
- $i18n{bluetoothNoDevicesFound}
- </div>
- <div id="unpairedContainer" class="container"
- scrollable on-device-event="onDeviceEvent_"
- hidden="[[!showDevices_(bluetoothToggleState, unpairedDeviceList_)]]">
- <iron-list id="unpairedDevices" class="cr-separators" preserve-focus
- items="[[unpairedDeviceList_]]"
- selection-enabled selected-item="{{selectedUnpairedItem_}}"
- scroll-target="unpairedContainer">
- <template>
- <bluetooth-device-list-item actionable device="[[item]]"
- first$="[[!index]]" tabindex$="[[tabIndex]]">
- </bluetooth-device-list-item>
- </template>
- </iron-list>
- </div>
-
- <bluetooth-dialog id="deviceDialog"
- bluetooth="[[bluetooth]]"
- bluetooth-private="[[bluetoothPrivate]]"
- dialog-title="$i18n{bluetoothPairDevicePageTitle}"
- on-close="onDialogClose_"
- pairing-device="[[pairingDevice_]]">
- </bluetooth-dialog>
-
- </template>
- <script src="bluetooth_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
deleted file mode 100644
index 7e2c35f331d..00000000000
--- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
+++ /dev/null
@@ -1,590 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Maximum number of bluetooth devices shown in bluetooth subpage.
- * @type {number}
- */
-const MAX_NUMBER_DEVICE_SHOWN = 50;
-
-/**
- * @fileoverview
- * 'settings-bluetooth-subpage' is the settings subpage for managing bluetooth
- * properties and devices.
- */
-
-Polymer({
- is: 'settings-bluetooth-subpage',
-
- behaviors: [
- I18nBehavior,
- CrScrollableBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /** Reflects the bluetooth-page property. */
- bluetoothToggleState: {
- type: Boolean,
- notify: true,
- },
-
- /** Reflects the bluetooth-page property. */
- stateChangeInProgress: Boolean,
-
- /**
- * The bluetooth adapter state, cached by bluetooth-page.
- * @type {!chrome.bluetooth.AdapterState|undefined}
- */
- adapterState: Object,
-
- /** Informs bluetooth-page whether to show the spinner in the header. */
- showSpinner_: {
- type: Boolean,
- notify: true,
- computed: 'computeShowSpinner_(adapterState.*, dialogShown_)',
- },
-
- /**
- * The ordered list of bluetooth devices.
- * @type {!Array<!chrome.bluetooth.Device>}
- * @private
- */
- deviceList_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The ordered list of paired or connecting bluetooth devices.
- * @type {!Array<!chrome.bluetooth.Device>}
- */
- pairedDeviceList_: {
- type: Array,
- value: /** @return {Array} */ function() {
- return [];
- },
- },
-
- /**
- * Reflects the iron-list selecteditem property.
- * @type {!chrome.bluetooth.Device}
- * @private
- */
- selectedPairedItem_: {
- type: Object,
- observer: 'selectedPairedItemChanged_',
- },
-
- /**
- * The ordered list of unpaired bluetooth devices.
- * @type {!Array<!chrome.bluetooth.Device>}
- */
- unpairedDeviceList_: {
- type: Array,
- value: /** @return {Array} */ function() {
- return [];
- },
- },
-
- /**
- * Reflects the iron-list selecteditem property.
- * @type {!chrome.bluetooth.Device}
- */
- selectedUnpairedItem_: {
- type: Object,
- observer: 'selectedUnpairedItemChanged_',
- },
-
- /**
- * Whether or not the dialog is shown.
- * @private
- */
- dialogShown_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Current Pairing device.
- * @type {!chrome.bluetooth.Device|undefined}
- * @private
- */
- pairingDevice_: Object,
-
- /**
- * Interface for bluetooth calls. Set in bluetooth-page.
- * @type {Bluetooth}
- * @private
- */
- bluetooth: {
- type: Object,
- value: chrome.bluetooth,
- },
-
- /**
- * Interface for bluetoothPrivate calls. Set in bluetooth-page.
- * @type {BluetoothPrivate}
- * @private
- */
- bluetoothPrivate: {
- type: Object,
- value: chrome.bluetoothPrivate,
- },
-
- /**
- * Update frequency of the bluetooth list.
- * @type {number}
- */
- listUpdateFrequencyMs: {
- type: Number,
- value: 1000,
- },
-
- /**
- * The time in milliseconds at which discovery was started attempt (when the
- * page was opened with Bluetooth on, or when Bluetooth turned on while the
- * page was active).
- * @private {?number}
- */
- discoveryStartTimestampMs_: {
- type: Number,
- value: null,
- },
-
- },
-
- observers: [
- 'adapterStateChanged_(adapterState.*)',
- 'deviceListChanged_(deviceList_.*)',
- 'listUpdateFrequencyMsChanged_(listUpdateFrequencyMs)',
- ],
-
- /**
- * Timer ID for bluetooth list update.
- * @type {number|undefined}
- * @private
- */
- updateTimerId_: undefined,
-
- /** @override */
- detached: function() {
- if (this.updateTimerId_ !== undefined) {
- window.clearInterval(this.updateTimerId_);
- this.updateTimerId_ = undefined;
- this.deviceList_ = [];
- }
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- this.updateDiscovery_();
- this.startOrStopRefreshingDeviceList_();
- },
-
- /** @private */
- computeShowSpinner_: function() {
- return !this.dialogShown_ && this.get('adapterState.discovering');
- },
-
- /** @private */
- adapterStateChanged_: function() {
- this.updateDiscovery_();
- this.startOrStopRefreshingDeviceList_();
- },
-
- /** @private */
- deviceListChanged_: function() {
- this.saveScroll(this.$.pairedDevices);
- this.saveScroll(this.$.unpairedDevices);
-
- this.pairedDeviceList_ = this.getUpdatedDeviceList_(
- this.pairedDeviceList_,
- this.deviceList_.filter(d => d.paired || d.connecting));
- this.unpairedDeviceList_ = this.getUpdatedDeviceList_(
- this.unpairedDeviceList_,
- this.deviceList_.filter(d => !(d.paired || d.connecting)));
-
- this.$.pairedDevices.fire('iron-resize');
- this.$.unpairedDevices.fire('iron-resize');
- this.updateScrollableContents();
- this.restoreScroll(this.$.unpairedDevices);
- this.restoreScroll(this.$.pairedDevices);
- },
-
- /**
- * Returns a copy of |oldDeviceList| but:
- * - Using the corresponding device objects in |newDeviceList|
- * - Removing devices not in |newDeviceList|
- * - Adding device not in |oldDeviceList| but in |newDeviceList| to the
- * end of the list.
- *
- * @param {!Array<!chrome.bluetooth.Device>} oldDeviceList
- * @param {!Array<!chrome.bluetooth.Device>} newDeviceList
- * @return {!Array<!chrome.bluetooth.Device>}
- * @private
- */
- getUpdatedDeviceList_: function(oldDeviceList, newDeviceList) {
- const newDeviceMap = new Map(newDeviceList.map(d => [d.address, d]));
- const updatedDeviceList = [];
-
- // Add elements of |oldDeviceList| that are in |newDeviceList| to
- // |updatedDeviceList|.
- for (const oldDevice of oldDeviceList) {
- const newDevice = newDeviceMap.get(oldDevice.address);
- if (newDevice === undefined) {
- continue;
- }
- updatedDeviceList.push(newDevice);
- newDeviceMap.delete(newDevice.address);
- }
-
- // Add all elements of |newDeviceList| that are not in |oldDeviceList| to
- // |updatedDeviceList|.
- for (const newDevice of newDeviceMap.values()) {
- updatedDeviceList.push(newDevice);
- }
-
- return updatedDeviceList;
- },
-
- /** @private */
- selectedPairedItemChanged_: function() {
- if (this.selectedPairedItem_) {
- this.connectDevice_(this.selectedPairedItem_);
- }
- },
-
- /** @private */
- selectedUnpairedItemChanged_: function() {
- if (this.selectedUnpairedItem_) {
- this.connectDevice_(this.selectedUnpairedItem_);
- }
- },
-
- /** @private */
- updateDiscovery_: function() {
- if (!this.adapterState || !this.adapterState.powered) {
- return;
- }
- if (settings.getCurrentRoute() == settings.routes.BLUETOOTH_DEVICES) {
- this.startDiscovery_();
- } else {
- this.stopDiscovery_();
- }
- },
-
- /** @private */
- startDiscovery_: function() {
- if (!this.adapterState || this.adapterState.discovering) {
- return;
- }
-
- this.bluetooth.startDiscovery(function() {
- const lastError = chrome.runtime.lastError;
- if (lastError) {
- if (lastError.message == 'Starting discovery failed') {
- return;
- } // May happen if also started elsewhere, ignore.
- console.error('startDiscovery Error: ' + lastError.message);
- }
- });
- },
-
- /** @private */
- stopDiscovery_: function() {
- if (!this.get('adapterState.discovering')) {
- return;
- }
-
- this.bluetooth.stopDiscovery(function() {
- const lastError = chrome.runtime.lastError;
- if (lastError) {
- if (lastError.message == 'Failed to stop discovery') {
- return;
- } // May happen if also stopped elsewhere, ignore.
- console.error('stopDiscovery Error: ' + lastError.message);
- }
- });
- },
-
- /**
- * @param {!CustomEvent<!{
- * action: string, device:
- * !chrome.bluetooth.Device
- * }>} e
- * @private
- */
- onDeviceEvent_: function(e) {
- const action = e.detail.action;
- const device = e.detail.device;
- if (action == 'connect') {
- this.connectDevice_(device);
- } else if (action == 'disconnect') {
- this.disconnectDevice_(device);
- } else if (action == 'remove') {
- this.forgetDevice_(device);
- } else {
- console.error('Unexected action: ' + action);
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onEnableTap_: function(event) {
- if (this.isToggleEnabled_()) {
- this.bluetoothToggleState = !this.bluetoothToggleState;
- }
- event.stopPropagation();
- },
-
- /**
- * @param {boolean} enabled
- * @param {string} onstr
- * @param {string} offstr
- * @return {string}
- * @private
- */
- getOnOffString_: function(enabled, onstr, offstr) {
- return enabled ? onstr : offstr;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isToggleEnabled_: function() {
- return this.adapterState !== undefined && this.adapterState.available &&
- !this.stateChangeInProgress;
- },
-
- /**
- * @param {boolean} bluetoothToggleState
- * @param {!Array<!chrome.bluetooth.Device>} deviceList
- * @return {boolean}
- * @private
- */
- showDevices_: function(bluetoothToggleState, deviceList) {
- return bluetoothToggleState && deviceList.length > 0;
- },
-
- /**
- * @param {boolean} bluetoothToggleState
- * @param {!Array<!chrome.bluetooth.Device>} deviceList
- * @return {boolean}
- * @private
- */
- showNoDevices_: function(bluetoothToggleState, deviceList) {
- return bluetoothToggleState && deviceList.length == 0;
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @private
- */
- connectDevice_: function(device) {
- if (device.connecting || device.connected) {
- return;
- }
-
- // If the device is not paired, show the pairing dialog before connecting.
- // TODO(crbug.com/966170): Need to check if the device is pairable as well.
- const isPaired = device.paired;
- if (!isPaired) {
- this.pairingDevice_ = device;
- this.openDialog_();
- }
-
- if (isPaired !== undefined && device.transport !== undefined) {
- this.recordDeviceSelectionDuration_(isPaired, device.transport);
- }
-
- const address = device.address;
- this.bluetoothPrivate.connect(address, result => {
- if (isPaired) {
- this.recordUserInitiatedReconnectionAttemptResult_(result);
- }
-
- // If |pairingDevice_| has changed, ignore the connect result.
- if (this.pairingDevice_ && address != this.pairingDevice_.address) {
- return;
- }
-
- // Let the dialog handle any errors, otherwise close the dialog.
- const dialog = this.$.deviceDialog;
- if (dialog.endConnectionAttempt(
- device, !isPaired /* wasPairing */, chrome.runtime.lastError,
- result)) {
- this.openDialog_();
- } else if (
- result != chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS) {
- this.$.deviceDialog.close();
- }
- });
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @private
- */
- disconnectDevice_: function(device) {
- this.bluetoothPrivate.disconnectAll(device.address, function() {
- if (chrome.runtime.lastError) {
- console.error(
- 'Error disconnecting: ' + device.address +
- chrome.runtime.lastError.message);
- }
- });
- },
-
- /**
- * @param {!chrome.bluetooth.Device} device
- * @private
- */
- forgetDevice_: function(device) {
- this.bluetoothPrivate.forgetDevice(device.address, () => {
- if (chrome.runtime.lastError) {
- console.error(
- 'Error forgetting: ' + device.name + ': ' +
- chrome.runtime.lastError.message);
- }
- });
- },
-
- /** @private */
- openDialog_: function() {
- if (this.dialogShown_) {
- return;
- }
- // Call flush so that the dialog gets sized correctly before it is opened.
- Polymer.dom.flush();
- this.$.deviceDialog.open();
- this.dialogShown_ = true;
- },
-
- /** @private */
- onDialogClose_: function() {
- this.dialogShown_ = false;
- this.pairingDevice_ = undefined;
- // The list is dynamic so focus the first item.
- const device = this.$$('#unpairedContainer bluetooth-device-list-item');
- if (device) {
- device.focus();
- }
- },
-
- /**
- * Requests bluetooth device list from Chrome. Update deviceList_ once the
- * results are returned from chrome.
- * @private
- */
- refreshBluetoothList_: function() {
- const filter = {
- filterType: chrome.bluetooth.FilterType.KNOWN,
- limit: MAX_NUMBER_DEVICE_SHOWN
- };
- this.bluetooth.getDevices(filter, devices => {
- this.deviceList_ = devices;
- });
- },
-
- /** @private */
- startOrStopRefreshingDeviceList_: function() {
- if (this.adapterState && this.adapterState.powered) {
- if (this.updateTimerId_ !== undefined) {
- return;
- }
-
- this.refreshBluetoothList_();
- this.updateTimerId_ =
- window.setInterval(this.refreshBluetoothList_.bind(this),
- this.listUpdateFrequencyMs);
- this.discoveryStartTimestampMs_ = Date.now();
- return;
- }
- window.clearInterval(this.updateTimerId_);
- this.updateTimerId_ = undefined;
- this.discoveryStartTimestampMs_ = null;
- this.deviceList_ = [];
- },
-
- /**
- * Restarts the timer when the frequency changes, which happens
- * during tests.
- */
- listUpdateFrequencyMsChanged_: function() {
- if (this.updateTimerId_ === undefined) {
- return;
- }
-
- window.clearInterval(this.updateTimerId_);
- this.updateTimerId_ = undefined;
-
- this.startOrStopRefreshingDeviceList_();
- },
-
- /**
- * Record metrics for user-initiated attempts to reconnect to an already
- * paired device.
- * @param {!chrome.bluetoothPrivate.ConnectResultType} result The connection
- * result.
- * @private
- */
- recordUserInitiatedReconnectionAttemptResult_: function(result) {
- let success;
- if (chrome.runtime.lastError) {
- success = false;
- } else {
- switch (result) {
- case chrome.bluetoothPrivate.ConnectResultType.SUCCESS:
- success = true;
- break;
- case chrome.bluetoothPrivate.ConnectResultType.AUTH_CANCELED:
- case chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS:
- // Don't record metrics until connection has ended, and don't record
- // cancellations.
- return;
- default:
- success = false;
- break;
- }
- }
-
- chrome.bluetoothPrivate.recordReconnection(success);
- },
-
- /**
- * Record metrics for how long it took between when discovery started on the
- * Settings page, and the user selected the device they wanted to connect to.
- * @param {!boolean} wasPaired If the selected device was already
- * paired.
- * @param {!chrome.bluetooth.Transport} transport The transport type
- * of the device.
- * @private
- */
- recordDeviceSelectionDuration_: function(wasPaired, transport) {
- if (!this.discoveryStartTimestampMs_) {
- // It's not necessarily an error that |discoveryStartTimestampMs_| isn't
- // present; it's intentionally cleared after the first device selection
- // (see further on in this method). Recording subsequent device selections
- // after the first would provide inflated durations that don't truly
- // reflect how long it took for the user to find the device they're
- // looking for.
- return;
- }
-
- chrome.bluetoothPrivate.recordDeviceSelection(
- Date.now() - this.discoveryStartTimestampMs_, wasPaired, transport);
-
- this.discoveryStartTimestampMs_ = null;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.html b/chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.html
deleted file mode 100644
index 583ede1657f..00000000000
--- a/chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="change_password_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.js b/chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.js
deleted file mode 100644
index 8118cd8ccaa..00000000000
--- a/chromium/chrome/browser/resources/settings/change_password_page/change_password_browser_proxy.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class ChangePasswordBrowserProxy {
- /** Initialize the change password handler.*/
- initializeChangePasswordHandler() {}
-
- /**
- * Initiate the change password process. e.g., for Gmail users, it
- * navigates to accounts.google.com; for GSuite users, it navigates to the
- * corresponding change password URLs.
- */
- changePassword() {}
- }
-
- /**
- * @implements {settings.ChangePasswordBrowserProxy}
- */
- class ChangePasswordBrowserProxyImpl {
- /** @override */
- initializeChangePasswordHandler() {
- chrome.send('initializeChangePasswordHandler');
- }
-
- /** @override */
- changePassword() {
- chrome.send('changePassword');
- }
- }
-
- cr.addSingletonGetter(ChangePasswordBrowserProxyImpl);
-
- return {
- ChangePasswordBrowserProxy: ChangePasswordBrowserProxy,
- ChangePasswordBrowserProxyImpl: ChangePasswordBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/change_password_page/change_password_page.html b/chromium/chrome/browser/resources/settings/change_password_page/change_password_page.html
deleted file mode 100644
index f2833f096f2..00000000000
--- a/chromium/chrome/browser/resources/settings/change_password_page/change_password_page.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="change_password_browser_proxy.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-
-<dom-module id="settings-change-password-page">
- <template>
- <style include="settings-shared">
- .icon-container {
- padding-inline-end: var(--cr-section-padding);
- }
-
- .change-password-icon {
- vertical-align: top;
- }
-
- iron-icon[icon='cr:warning'] {
- --iron-icon-fill-color: var(--settings-error-color);
- }
-
- .top-aligned-settings-box {
- align-items: start;
- min-height: 0;
- padding: var(--cr-section-vertical-padding) var(--cr-section-padding);
- }
- </style>
- <div class="settings-box first top-aligned-settings-box">
- <div class="icon-container">
- <iron-icon icon="cr:security"
- class="change-password-icon"></iron-icon>
- </div>
- <div class="start">
- <div>$i18n{changePasswordPageTitle}</div>
- <div class="secondary">
- $i18n{changePasswordPageDetails}
- </div>
- </div>
- <div class="separator"></div>
- <cr-button class="action-button" id="changePassword"
- on-click="changePassword_">
- $i18n{changePasswordPageButton}
- </cr-button>
- </div>
- </template>
- <script src="change_password_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/change_password_page/change_password_page.js b/chromium/chrome/browser/resources/settings/change_password_page/change_password_page.js
deleted file mode 100644
index 9bfaf911bd0..00000000000
--- a/chromium/chrome/browser/resources/settings/change_password_page/change_password_page.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'change-password-page' is the settings page containing change password
- * settings.
- */
-Polymer({
- is: 'settings-change-password-page',
-
- /** @private */
- changePassword_: function() {
- settings.ChangePasswordBrowserProxyImpl.getInstance().changePassword();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.html b/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.html
deleted file mode 100644
index 8f991df6e48..00000000000
--- a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.html
+++ /dev/null
@@ -1,122 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/controlled_button.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="chrome_cleanup_proxy.html">
-<link rel="import" href="items_to_remove_list.html">
-
-<dom-module id="settings-chrome-cleanup-page">
- <template>
- <style include="settings-shared">
- #status-title {
- font-weight: 500;
- }
-
- #waiting-spinner {
- flex-shrink: 0;
- height: 20px;
- width: 20px;
- }
-
- #powered-by-settings-box {
- min-height: 1em;
- }
-
- #powered-by-container {
- align-items: center;
- display: flex;
- min-height: var(--settings-row-min-height);
- width: 100%;
- }
-
- /* Apply a fixed height to the <svg> tag inside #powered-by-logo.
- Used by |chromeCleanupPoweredByHTML|. */
- #powered-by-logo > svg {
- height: 22px;
- }
- </style>
- <div class="settings-box first">
- <div class="start">
- <div id="status-title" role="status" inner-h-t-m-l="[[title_]]"></div>
- <div hidden="[[!showExplanation_]]">
- <span class="secondary">[[explanation_]]</span>
- </div>
- </div>
- <paper-spinner-lite id="waiting-spinner"
- hidden="[[!isWaitingForResult_]]" active="[[isWaitingForResult_]]">
- </paper-spinner-lite>
- <template is="dom-if" if="[[showActionButton_]]">
- <cr-policy-pref-indicator pref="[[prefs.software_reporter.enabled]]">
- </cr-policy-pref-indicator>
- <div class="separator"></div>
- <cr-button id="action-button" class="action-button"
- disabled$="[[!cleanupEnabled_]]" on-click="proceed_">
- [[actionButtonLabel_]]
- </cr-button>
- </template>
- </div>
- <div class="settings-box continuation">
- <settings-checkbox hidden="[[!showLogsPermission_]]"
- id="chromeCleanupLogsUploadControl"
- sub-label="$i18n{chromeCleanupExplanationLogsPermissionPref}"
- pref="{{prefs.software_reporter.reporting}}"
- disabled$="[[!cleanupEnabled_]]">
- </settings-checkbox>
- </div>
- <cr-expand-button
- alt="[[showItemsLinkLabel_]]"
- class="settings-box"
- expanded="{{itemsToRemoveSectionExpanded_}}"
- hidden="[[!showItemsToRemove_]]"
- id="show-items-button">
- [[showItemsLinkLabel_]]
- </cr-expand-button>
- <iron-collapse id="iron-collapse-items"
- opened="[[itemsToRemoveSectionExpanded_]]">
- <items-to-remove-list
- id="files-to-remove-list"
- hidden="[[!hasFilesToShow_]]"
- title="$i18n{chromeCleanupDetailsFilesAndPrograms}"
- items-to-show="[[
- getListEntriesFromFilePaths_(scannerResults_.files)]]">
- </items-to-remove-list>
- <items-to-remove-list
- id="registry-keys-list"
- hidden="[[!hasRegistryKeysToShow_]]"
- title="$i18n{chromeCleanupDetailsRegistryEntries}"
- items-to-show="[[
- getListEntriesFromStrings_(scannerResults_.registryKeys)]]">
- </items-to-remove-list>
- <items-to-remove-list
- id="extensions-list"
- hidden="[[!hasExtensionsToShow_]]"
- title="$i18n{chromeCleanupDetailsExtensions}"
- items-to-show="[[
- getListEntriesFromStrings_(scannerResults_.extensions)]]">
- </items-to-remove-list>
- <div class="settings-box continuation">
- <div class="secondary">
- $i18nRaw{chromeCleanupDetailsExplanation}
- </div>
- </div>
- <div id="powered-by-settings-box" class="settings-box continuation">
- <div id="powered-by-container" class="secondary"
- hidden="[[!isPoweredByPartner_]]">
- $i18nRaw{chromeCleanupPoweredByHtml}
- </div>
- </div>
- </iron-collapse>
- </template>
- <script src="chrome_cleanup_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js b/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js
deleted file mode 100644
index 0b5767e0d00..00000000000
--- a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js
+++ /dev/null
@@ -1,734 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * The reason why the controller is in state kIdle.
- * Must be kept in sync with ChromeCleanerController::IdleReason.
- * @enum {string}
- */
-settings.ChromeCleanupIdleReason = {
- INITIAL: 'initial',
- REPORTER_FOUND_NOTHING: 'reporter_found_nothing',
- REPORTER_FAILED: 'reporter_failed',
- SCANNING_FOUND_NOTHING: 'scanning_found_nothing',
- SCANNING_FAILED: 'scanning_failed',
- CONNECTION_LOST: 'connection_lost',
- USER_DECLINED_CLEANUP: 'user_declined_cleanup',
- CLEANING_FAILED: 'cleaning_failed',
- CLEANING_SUCCEEDED: 'cleaning_succeeded',
- CLEANER_DOWNLOAD_FAILED: 'cleaner_download_failed',
-};
-
-/**
- * The possible states for the cleanup card.
- * @enum {string}
- */
-settings.ChromeCleanerCardState = {
- SCANNING_OFFERED: 'scanning_offered',
- SCANNING: 'scanning',
- CLEANUP_OFFERED: 'cleanup_offered',
- CLEANING: 'cleaning',
- REBOOT_REQUIRED: 'reboot_required',
- SCANNING_FOUND_NOTHING: 'scanning_found_nothing',
- SCANNING_FAILED: 'scanning_failed',
- CLEANUP_SUCCEEDED: 'cleanup_succeeded',
- CLEANING_FAILED: 'cleanup_failed',
- CLEANER_DOWNLOAD_FAILED: 'cleaner_download_failed',
-};
-
-/**
- * Boolean properties for a cleanup card state.
- * @enum {number}
- */
-settings.ChromeCleanupCardFlags = {
- NONE: 0,
- SHOW_LOGS_PERMISSIONS: 1 << 0,
- WAITING_FOR_RESULT: 1 << 1,
- SHOW_ITEMS_TO_REMOVE: 1 << 2,
-};
-
-/**
- * Identifies an ongoing scanning/cleanup action.
- * @enum {number}
- */
-settings.ChromeCleanupOngoingAction = {
- NONE: 0,
- SCANNING: 1,
- CLEANING: 2,
-};
-
-/**
- * @typedef {{
- * label: string,
- * doAction: !function(),
- * }}
- */
-settings.ChromeCleanupCardActionButton;
-
-/**
- * @typedef {{
- * title: ?string,
- * explanation: ?string,
- * actionButton: ?settings.ChromeCleanupCardActionButton,
- * flags: number,
- * }}
- */
-settings.ChromeCleanupCardComponents;
-
-/**
- * Represents the file path structure of a base::FilePath.
- * dirname ends with a separator.
- * @typedef {{
- * dirname: string,
- * basename: string,
- * }}
- */
-settings.ChromeCleanupFilePath;
-
-/**
- * @typedef {{
- * files: Array<settings.ChromeCleanupFilePath>,
- * registryKeys: Array<string>,
- * extensions: Array<string>,
- * }}
- */
-settings.ChromeCleanerScannerResults;
-
-/**
- * @fileoverview
- * 'settings-chrome-cleanup-page' is the settings page containing Chrome
- * Cleanup settings.
- *
- * Example:
- *
- * <iron-animated-pages>
- * <settings-chrome-cleanup-page></settings-chrome-cleanup-page>
- * ... other pages ...
- * </iron-animated-pages>
- */
-Polymer({
- is: 'settings-chrome-cleanup-page',
-
- behaviors: [I18nBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private */
- title_: {
- type: String,
- value: '',
- },
-
- /** @private */
- explanation_: {
- type: String,
- value: '',
- },
-
- /** @private */
- isWaitingForResult_: {
- type: Boolean,
- value: '',
- },
-
- /** @private */
- showActionButton_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- cleanupEnabled_: {
- type: Boolean,
- value: true,
- },
-
- /** @private */
- actionButtonLabel_: {
- type: String,
- value: '',
- },
-
- /** @private */
- showExplanation_: {
- type: Boolean,
- computed: 'computeShowExplanation_(explanation_)',
- },
-
- /** @private */
- showLogsPermission_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showItemsToRemove_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- itemsToRemoveSectionExpanded_: {
- type: Boolean,
- value: false,
- observer: 'itemsToRemoveSectionExpandedChanged_',
- },
-
- /** @private */
- showItemsLinkLabel_: {
- type: String,
- value: '',
- },
-
- /** @private */
- showingAllFiles_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {!settings.ChromeCleanerScannerResults} */
- scannerResults_: {
- type: Array,
- value: function() {
- return {'files': [], 'registryKeys': [], 'extensions': []};
- },
- },
-
- /** @private */
- hasFilesToShow_: {
- type: Boolean,
- computed: 'computeHasFilesToShow_(scannerResults_)',
- },
-
- /** @private */
- hasRegistryKeysToShow_: {
- type: Boolean,
- computed: 'computeHasRegistryKeysToShow_(scannerResults_)',
- },
-
- /** @private */
- hasExtensionsToShow_: {
- type: Boolean,
- computed: 'computeHasExtensionsToShow_(scannerResults_)',
- },
-
- /** @private {chrome.settingsPrivate.PrefObject} */
- logsUploadPref_: {
- type: Object,
- value: function() {
- return /** @type {chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
-
- /** @private */
- isPoweredByPartner_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @private {!settings.ChromeCleanerScannerResults} */
- emptyChromeCleanerScannerResults_:
- {'files': [], 'registryKeys': [], 'extensions': []},
-
- /** @private {?settings.ChromeCleanupProxy} */
- browserProxy_: null,
-
- /** @private {?function()} */
- doAction_: null,
-
- /**
- * @private {?Map<settings.ChromeCleanerCardState,
- * !settings.ChromeCleanupCardComponents>}
- */
- cardStateToComponentsMap_: null,
-
- /** @private {settings.ChromeCleanupOngoingAction} */
- ongoingAction_: settings.ChromeCleanupOngoingAction.NONE,
-
- /**
- * If true, the scan offered view is rendered on state idle, regardless of
- * the idle reason received from the cleaner controller. The goal is to
- * ignore previous interactions (such as completed cleanups) performed on
- * other tabs or if this tab is reloaded.
- * Set to false whenever there is a transition to a non-idle state while the
- * current tab is open.
- * @private {boolean}
- */
- renderScanOfferedByDefault_: true,
-
- /** @override */
- attached: function() {
- this.browserProxy_ = settings.ChromeCleanupProxyImpl.getInstance();
- this.cardStateToComponentsMap_ = this.buildCardStateToComponentsMap_();
-
- this.addWebUIListener('chrome-cleanup-on-idle', this.onIdle_.bind(this));
- this.addWebUIListener(
- 'chrome-cleanup-on-scanning', this.onScanning_.bind(this));
- // Note: both reporter running and scanning share the same UI.
- this.addWebUIListener(
- 'chrome-cleanup-on-reporter-running', this.onScanning_.bind(this));
- this.addWebUIListener(
- 'chrome-cleanup-on-infected', this.onInfected_.bind(this));
- this.addWebUIListener(
- 'chrome-cleanup-on-cleaning', this.onCleaning_.bind(this));
- this.addWebUIListener(
- 'chrome-cleanup-on-reboot-required', this.onRebootRequired_.bind(this));
- this.addWebUIListener(
- 'chrome-cleanup-enabled-change',
- this.onCleanupEnabledChange_.bind(this));
- this.browserProxy_.registerChromeCleanerObserver();
- },
-
- /**
- * Implements the action for the only visible button in the UI, which can be
- * either to start an action such as a cleanup or to restart the computer.
- * @private
- */
- proceed_: function() {
- this.doAction_();
- },
-
- /**
- * Notifies Chrome that the details section was opened or closed.
- * @private
- */
- itemsToRemoveSectionExpandedChanged_: function(newVal, oldVal) {
- if (!oldVal && newVal) {
- this.browserProxy_.notifyShowDetails(this.itemsToRemoveSectionExpanded_);
- }
- },
-
- /**
- * @param {string} explanation
- * @return {boolean}
- * @private
- */
- computeShowExplanation_: function(explanation) {
- return explanation != '';
- },
-
- /**
- * Returns true if there are files to show to the user.
- * @param {!settings.ChromeCleanerScannerResults} scannerResults The cleanup
- * items to be presented to the user.
- * @return {boolean}
- * @private
- */
- computeHasFilesToShow_(scannerResults) {
- return scannerResults.files.length > 0;
- },
-
- /**
- * Returns true if user-initiated cleanups are enabled and there are registry
- * keys to show to the user.
- * @param {!settings.ChromeCleanerScannerResults} scannerResults The cleanup
- * items to be presented to the user.
- * @return {boolean}
- * @private
- */
- computeHasRegistryKeysToShow_(scannerResults) {
- return scannerResults.registryKeys.length > 0;
- },
-
- /**
- * Returns true if user-initiated cleanups are enabled and there are
- * extensions to show to the user.
- * @param {!settings.ChromeCleanerScannerResults} scannerResults The cleanup
- * items to be presented to the user.
- * @return {boolean}
- * @private
- */
- computeHasExtensionsToShow_(scannerResults) {
- return scannerResults.extensions.length > 0;
- },
-
- /**
- * Listener of event 'chrome-cleanup-on-idle'.
- * @param {string} idleReason
- * @private
- */
- onIdle_: function(idleReason) {
- this.ongoingAction_ = settings.ChromeCleanupOngoingAction.NONE;
- this.scannerResults_ = this.emptyChromeCleanerScannerResults_;
-
- // Ignore the idle reason and render the scan offered view if no
- // interaction happened on this tab.
- if (this.renderScanOfferedByDefault_) {
- idleReason = settings.ChromeCleanupIdleReason.INITIAL;
- }
-
- switch (idleReason) {
- case settings.ChromeCleanupIdleReason.INITIAL:
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.SCANNING_OFFERED);
- break;
-
- case settings.ChromeCleanupIdleReason.SCANNING_FOUND_NOTHING:
- case settings.ChromeCleanupIdleReason.REPORTER_FOUND_NOTHING:
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.SCANNING_FOUND_NOTHING);
- break;
-
- case settings.ChromeCleanupIdleReason.SCANNING_FAILED:
- case settings.ChromeCleanupIdleReason.REPORTER_FAILED:
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.SCANNING_FAILED);
- break;
-
- case settings.ChromeCleanupIdleReason.CONNECTION_LOST:
- if (this.ongoingAction_ ==
- settings.ChromeCleanupOngoingAction.SCANNING) {
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.SCANNING_FAILED);
- } else {
- assert(
- this.ongoingAction_ ==
- settings.ChromeCleanupOngoingAction.CLEANING);
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.CLEANING_FAILED);
- }
- break;
-
- case settings.ChromeCleanupIdleReason.CLEANING_FAILED:
- case settings.ChromeCleanupIdleReason.USER_DECLINED_CLEANUP:
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.CLEANING_FAILED);
- break;
-
- case settings.ChromeCleanupIdleReason.CLEANING_SUCCEEDED:
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.CLEANUP_SUCCEEDED);
- break;
-
- case settings.ChromeCleanupIdleReason.CLEANER_DOWNLOAD_FAILED:
- this.renderCleanupCard_(
- settings.ChromeCleanerCardState.CLEANER_DOWNLOAD_FAILED);
- break;
-
- default:
- assert(false, `Unknown idle reason: ${idleReason}`);
- }
- },
-
- /**
- * Listener of event 'chrome-cleanup-on-scanning'.
- * No UI will be shown in the Settings page on that state, simply hide the
- * card and cleanup this element's fields.
- * @private
- */
- onScanning_: function() {
- this.ongoingAction_ = settings.ChromeCleanupOngoingAction.SCANNING;
- this.scannerResults_ = this.emptyChromeCleanerScannerResults_;
- this.renderScanOfferedByDefault_ = false;
- this.renderCleanupCard_(settings.ChromeCleanerCardState.SCANNING);
- },
-
- /**
- * Listener of event 'chrome-cleanup-on-infected'.
- * Offers a cleanup to the user and enables presenting files to be removed.
- * @param {boolean} isPoweredByPartner If scanning results are provided by a
- * partner's engine.
- * @param {!settings.ChromeCleanerScannerResults} scannerResults The cleanup
- * items to be presented to the user.
- * @private
- */
- onInfected_: function(isPoweredByPartner, scannerResults) {
- this.isPoweredByPartner_ = isPoweredByPartner;
- this.ongoingAction_ = settings.ChromeCleanupOngoingAction.NONE;
- this.renderScanOfferedByDefault_ = false;
- this.scannerResults_ = scannerResults;
- this.updateShowItemsLinklabel_();
- this.renderCleanupCard_(settings.ChromeCleanerCardState.CLEANUP_OFFERED);
- },
-
- /**
- * Listener of event 'chrome-cleanup-on-cleaning'.
- * Shows a spinner indicating that an on-going action and enables presenting
- * files to be removed.
- * @param {boolean} isPoweredByPartner If scanning results are provided by a
- * partner's engine.
- * @param {!settings.ChromeCleanerScannerResults} scannerResults The cleanup
- * items to be presented to the user.
- * @private
- */
- onCleaning_: function(isPoweredByPartner, scannerResults) {
- this.isPoweredByPartner_ = isPoweredByPartner;
- this.ongoingAction_ = settings.ChromeCleanupOngoingAction.CLEANING;
- this.renderScanOfferedByDefault_ = false;
- this.scannerResults_ = scannerResults;
- this.updateShowItemsLinklabel_();
- this.renderCleanupCard_(settings.ChromeCleanerCardState.CLEANING);
- },
-
- /**
- * Listener of event 'chrome-cleanup-on-reboot-required'.
- * No UI will be shown in the Settings page on that state, so we simply hide
- * the card and cleanup this element's fields.
- * @private
- */
- onRebootRequired_: function() {
- this.ongoingAction_ = settings.ChromeCleanupOngoingAction.NONE;
- this.scannerResults_ = this.emptyChromeCleanerScannerResults_;
- this.renderScanOfferedByDefault_ = false;
- this.renderCleanupCard_(settings.ChromeCleanerCardState.REBOOT_REQUIRED);
- },
-
- /**
- * Renders the cleanup card given the state and list of files.
- * @param {!settings.ChromeCleanerCardState} state The card state to be
- * rendered.
- * @private
- */
- renderCleanupCard_: function(state) {
- const components = this.cardStateToComponentsMap_.get(state);
- assert(components);
-
- this.title_ = components.title || '';
- this.explanation_ = components.explanation || '';
- this.updateActionButton_(components.actionButton);
- this.updateCardFlags_(components.flags);
- },
-
- /**
- * Updates the action button on the cleanup card as the action expected for
- * the current state.
- * @param {?settings.ChromeCleanupCardActionButton} actionButton
- * The button to render, or null if no button should be shown.
- * @private
- */
- updateActionButton_: function(actionButton) {
- if (!actionButton) {
- this.showActionButton_ = false;
- this.actionButtonLabel_ = '';
- this.doAction_ = null;
- } else {
- this.showActionButton_ = true;
- this.actionButtonLabel_ = actionButton.label;
- this.doAction_ = actionButton.doAction;
- }
- },
-
- /**
- * Updates boolean flags corresponding to optional components to be rendered
- * on the card.
- * @param {number} flags Flags indicating optional components to be rendered.
- * @private
- */
- updateCardFlags_: function(flags) {
- this.showLogsPermission_ =
- (flags & settings.ChromeCleanupCardFlags.SHOW_LOGS_PERMISSIONS) != 0;
- this.isWaitingForResult_ =
- (flags & settings.ChromeCleanupCardFlags.WAITING_FOR_RESULT) != 0;
- this.showItemsToRemove_ =
- (flags & settings.ChromeCleanupCardFlags.SHOW_ITEMS_TO_REMOVE) != 0;
-
- // Files to remove list should only be expandable if details are being
- // shown, otherwise it will add extra padding at the bottom of the card.
- if (!this.showExplanation_ || !this.showItemsToRemove_) {
- this.itemsToRemoveSectionExpanded_ = false;
- }
- },
-
- /**
- * @param {boolean} enabled Whether cleanup is enabled.
- * @private
- */
- onCleanupEnabledChange_: function(enabled) {
- this.cleanupEnabled_ = enabled;
- },
-
- /**
- * Sends an action to the browser proxy to start scanning.
- * @private
- */
- startScanning_: function() {
- this.browserProxy_.startScanning(
- this.$.chromeCleanupLogsUploadControl.checked);
- },
-
- /**
- * Sends an action to the browser proxy to start the cleanup.
- * @private
- */
- startCleanup_: function() {
- this.browserProxy_.startCleanup(
- this.$.chromeCleanupLogsUploadControl.checked);
- },
-
- /**
- * Sends an action to the browser proxy to restart the machine.
- * @private
- */
- restartComputer_: function() {
- this.browserProxy_.restartComputer();
- },
-
- /**
- * Updates the label for the collapsed detailed view. If user-initiated
- * cleanups are enabled, the string is obtained from the browser proxy, since
- * it may require a plural version. Otherwise, use the default value for
- * |chromeCleanupLinkShowItems|.
- */
- updateShowItemsLinklabel_: function() {
- const setShowItemsLabel = text => this.showItemsLinkLabel_ = text;
- this.browserProxy_
- .getItemsToRemovePluralString(
- this.scannerResults_.files.length +
- this.scannerResults_.registryKeys.length +
- this.scannerResults_.extensions.length)
- .then(setShowItemsLabel);
- },
-
- /**
- * Returns the map of card states to components to be rendered.
- * @return {!Map<settings.ChromeCleanerCardState,
- * !settings.ChromeCleanupCardComponents>}
- * @private
- */
- buildCardStateToComponentsMap_: function() {
- /**
- * The action buttons to show on the card.
- * @enum {settings.ChromeCleanupCardActionButton}
- */
- const actionButtons = {
- FIND: {
- label: this.i18n('chromeCleanupFindButtonLable'),
- doAction: this.startScanning_.bind(this),
- },
-
- REMOVE: {
- label: this.i18n('chromeCleanupRemoveButtonLabel'),
- doAction: this.startCleanup_.bind(this),
- },
-
- RESTART_COMPUTER: {
- label: this.i18n('chromeCleanupRestartButtonLabel'),
- doAction: this.restartComputer_.bind(this),
- },
-
- TRY_SCAN_AGAIN: {
- label: this.i18n('chromeCleanupTitleTryAgainButtonLabel'),
- // TODO(crbug.com/776538): do not run the reporter component again.
- // Try downloading the cleaner and scan with it instead.
- doAction: this.startScanning_.bind(this),
- },
- };
-
- return new Map([
- [
- settings.ChromeCleanerCardState.CLEANUP_OFFERED, {
- title: this.i18n('chromeCleanupTitleRemove'),
- explanation: this.i18n('chromeCleanupExplanationRemove'),
- actionButton: actionButtons.REMOVE,
- flags: settings.ChromeCleanupCardFlags.SHOW_LOGS_PERMISSIONS |
- settings.ChromeCleanupCardFlags.SHOW_ITEMS_TO_REMOVE,
- }
- ],
- [
- settings.ChromeCleanerCardState.CLEANING, {
- title: this.i18n('chromeCleanupTitleRemoving'),
- explanation: this.i18n('chromeCleanupExplanationRemoving'),
- actionButton: null,
- flags: settings.ChromeCleanupCardFlags.WAITING_FOR_RESULT |
- settings.ChromeCleanupCardFlags.SHOW_ITEMS_TO_REMOVE,
- }
- ],
- [
- settings.ChromeCleanerCardState.REBOOT_REQUIRED, {
- title: this.i18n('chromeCleanupTitleRestart'),
- explanation: null,
- actionButton: actionButtons.RESTART_COMPUTER,
- flags: settings.ChromeCleanupCardFlags.NONE,
- }
- ],
- [
- settings.ChromeCleanerCardState.CLEANUP_SUCCEEDED, {
- title: this.i18nAdvanced('chromeCleanupTitleRemoved', {tags: ['a']}),
- explanation: null,
- actionButton: null,
- flags: settings.ChromeCleanupCardFlags.NONE,
- }
- ],
- [
- settings.ChromeCleanerCardState.CLEANING_FAILED, {
- title: this.i18n('chromeCleanupTitleErrorCantRemove'),
- explanation: this.i18n('chromeCleanupExplanationCleanupError'),
- actionButton: null,
- flags: settings.ChromeCleanupCardFlags.NONE,
- }
- ],
- [
- settings.ChromeCleanerCardState.SCANNING_OFFERED, {
- title: this.i18n('chromeCleanupTitleFindAndRemove'),
- explanation: this.i18n('chromeCleanupExplanationFindAndRemove'),
- actionButton: actionButtons.FIND,
- flags: settings.ChromeCleanupCardFlags.SHOW_LOGS_PERMISSIONS,
- }
- ],
- [
- settings.ChromeCleanerCardState.SCANNING, {
- title: this.i18n('chromeCleanupTitleScanning'),
- explanation: null,
- actionButton: null,
- flags: settings.ChromeCleanupCardFlags.WAITING_FOR_RESULT,
- }
- ],
- [
- // TODO(crbug.com/776538): Could we offer to reset settings here?
- settings.ChromeCleanerCardState.SCANNING_FOUND_NOTHING, {
- title: this.i18n('chromeCleanupTitleNothingFound'),
- explanation: null,
- actionButton: null,
- flags: settings.ChromeCleanupCardFlags.NONE,
- }
- ],
- [
- settings.ChromeCleanerCardState.SCANNING_FAILED, {
- title: this.i18n('chromeCleanupTitleScanningFailed'),
- explanation: this.i18n('chromeCleanupExplanationScanError'),
- actionButton: null,
- flags: settings.ChromeCleanupCardFlags.NONE,
- }
- ],
- [
- settings.ChromeCleanerCardState.CLEANER_DOWNLOAD_FAILED,
- {
- // TODO(crbug.com/776538): distinguish between missing network
- // connectivity and cleanups being disabled by the server.
- title: this.i18n('chromeCleanupTitleCleanupUnavailable'),
- explanation: this.i18n('chromeCleanupExplanationCleanupUnavailable'),
- actionButton: actionButtons.TRY_SCAN_AGAIN,
- flags: settings.ChromeCleanupCardFlags.NONE,
- },
- ],
- ]);
- },
-
- /**
- * @param {!Array<string>} list
- * @return {!Array<settings.ChromeCleanupRemovalListItem>}
- * @private
- */
- getListEntriesFromStrings_: function(list) {
- return list.map(entry => ({text: entry, highlightSuffix: null}));
- },
-
- /**
- * @param {!Array<settings.ChromeCleanupFilePath>} paths
- * @return {!Array<settings.ChromeCleanupRemovalListItem>}
- * @private
- */
- getListEntriesFromFilePaths_: function(paths) {
- return paths.map(
- path => ({text: path.dirname, highlightSuffix: path.basename}));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.html b/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.html
deleted file mode 100644
index f1a87133a46..00000000000
--- a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="chrome_cleanup_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js b/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js
deleted file mode 100644
index 64845129d50..00000000000
--- a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class ChromeCleanupProxy {
- /**
- * Registers the current ChromeCleanupHandler as an observer of
- * ChromeCleanerController events.
- */
- registerChromeCleanerObserver() {}
-
- /**
- * Starts scanning the user's computer.
- * @param {boolean} logsUploadEnabled
- */
- startScanning(logsUploadEnabled) {}
-
- /**
- * Starts a cleanup on the user's computer.
- * @param {boolean} logsUploadEnabled
- */
- startCleanup(logsUploadEnabled) {}
-
- /**
- * Restarts the user's computer.
- */
- restartComputer() {}
-
- /**
- * Notifies Chrome that the state of the details section changed.
- * @param {boolean} enabled
- */
- notifyShowDetails(enabled) {}
-
- /**
- * Notifies Chrome that the "learn more" link was clicked.
- */
- notifyLearnMoreClicked() {}
-
- /**
- * Requests the plural string for the "show more" link in the detailed
- * view for either files to delete or registry keys.
- * @param {number} numHiddenItems
- * @return {!Promise<string>}
- */
- getMoreItemsPluralString(numHiddenItems) {}
-
- /**
- * Requests the plural string for the "items to remove" link in the detailed
- * view.
- * @param {number} numItems
- * @return {!Promise<string>}
- */
- getItemsToRemovePluralString(numItems) {}
- }
-
- /**
- * @implements {settings.ChromeCleanupProxy}
- */
- class ChromeCleanupProxyImpl {
- /** @override */
- registerChromeCleanerObserver() {
- chrome.send('registerChromeCleanerObserver');
- }
-
- /** @override */
- startScanning(logsUploadEnabled) {
- chrome.send('startScanning', [logsUploadEnabled]);
- }
-
- /** @override */
- startCleanup(logsUploadEnabled) {
- chrome.send('startCleanup', [logsUploadEnabled]);
- }
-
- /** @override */
- restartComputer() {
- chrome.send('restartComputer');
- }
-
- /** @override */
- notifyShowDetails(enabled) {
- chrome.send('notifyShowDetails', [enabled]);
- }
-
- /** @override */
- notifyLearnMoreClicked() {
- chrome.send('notifyChromeCleanupLearnMoreClicked');
- }
-
- /** @override */
- getMoreItemsPluralString(numHiddenItems) {
- return cr.sendWithPromise('getMoreItemsPluralString', numHiddenItems);
- }
-
- /** @override */
- getItemsToRemovePluralString(numItems) {
- return cr.sendWithPromise('getItemsToRemovePluralString', numItems);
- }
- }
-
- cr.addSingletonGetter(ChromeCleanupProxyImpl);
-
- return {
- ChromeCleanupProxy: ChromeCleanupProxy,
- ChromeCleanupProxyImpl: ChromeCleanupProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.html b/chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.html
deleted file mode 100644
index 2ff69ed6d66..00000000000
--- a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="chrome_cleanup_proxy.html">
-
-<dom-module id="items-to-remove-list">
- <template>
- <style include="settings-shared">
- :host {
- display: block;
- margin: 0;
- padding: 0 var(--cr-section-padding);
- word-break: break-all;
- }
-
- #more-items-link {
- color: var(--cr-link-color);
- cursor: pointer;
- }
-
- #remaining-list :first-child {
- margin-top: -1em;
- }
-
- .highlight-suffix {
- font-weight: bold;
- }
-
- </style>
- <div id="title" class="secondary">[[title]]</div>
- <ul class="secondary">
- <template is="dom-repeat" items="[[initialItems_]]">
- <li class="visible-item">
- <span>[[item.text]]</span><!--
- --><span class="highlight-suffix"
- hidden="[[!hasHighlightSuffix_(item)]]"><!--
- -->[[item.highlightSuffix]]
- </span>
- </li>
- </template>
- <li id="more-items-link" hidden="[[expanded_]]" on-click="expandList_">
- [[moreItemsLinkText_]]
- </li>
- </ul>
- <!-- Remaining items are kept in a separate <ul> element so that screen
- readers don't get confused when the list is expanded. If new items are
- simply added to the first <ul> element, the first new item (which will
- replace the "N more" link), will be skipped by the reader. As a
- consequence, visual impaired users will only have a chance to inspect
- that item if they move up on the list, which can't be considered an
- expected action. -->
- <ul id="remaining-list" hidden="[[!expanded_]]" class="secondary">
- <template is="dom-repeat" items="[[remainingItems_]]">
- <li class$="[[remainingItemsClass_(expanded_)]]">
- <span>[[item.text]]</span><!--
- --><span class="highlight-suffix"
- hidden="[[!hasHighlightSuffix_(item)]]"><!--
- -->[[item.highlightSuffix]]
- </span>
- </li>
- </template>
- </ul>
- </template>
- <script src="items_to_remove_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.js b/chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.js
deleted file mode 100644
index f527a77e8c5..00000000000
--- a/chromium/chrome/browser/resources/settings/chrome_cleanup_page/items_to_remove_list.js
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * For each line in the item list, the text field will be shown in normal style
- * at front of the line. The highlightSuffix will be appended to the end of line
- * and emphasized with bold font.
- * @typedef {{
- * text: string,
- * highlightSuffix: ?string,
- * }}
- */
-settings.ChromeCleanupRemovalListItem;
-
-/**
- * The default number of items to show for files, registry keys and extensions
- * on the detailed view when user-initiated cleanups are enabled.
- */
-settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW = 4;
-
-/**
- * @fileoverview
- * 'items-to-remove-list' represents a list of items to
- * be removed or changed to be shown on the Chrome Cleanup page.
- * TODO(crbug.com/776538): Update the strings to say that some items are only
- * changed and not removed.
- *
- * Example:
- *
- * <!-- Items list initially shows |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|
- * items. If there are more than |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|
- * items on the list, then a "show more" link is shown; tapping on it
- * expands the list. -->
- * <items-to-remove-list
- * title="Files and programs:"
- * items-to-show="[[filesToShow]]">
- * </items-to-remove-list>
- */
-Polymer({
- is: 'items-to-remove-list',
-
- properties: {
- title: {
- type: String,
- value: '',
- },
-
- /** @type {!Array<settings.ChromeCleanupRemovalListItem>} */
- itemsToShow: {
- type: Array,
- observer: 'updateVisibleState_',
- },
-
- /**
- * If true, all items from |itemsToShow| will be presented on the card,
- * and the "show more" link will be omitted.
- */
- expanded_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The first |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items of |itemsToShow|
- * if the list is longer than |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|.
- * @private {?Array<settings.ChromeCleanupRemovalListItem>}
- */
- initialItems_: Array,
-
- /**
- * The remaining items to be presented that are not included in
- * |initialItems_|. Items in this list are only shown to the user if
- * |expanded_| is true.
- * @private {?Array<settings.ChromeCleanupRemovalListItem>}
- */
- remainingItems_: Array,
-
- /**
- * The text for the "show more" link available if not all files are visible
- * in the card.
- * @private
- */
- moreItemsLinkText_: {
- type: String,
- value: '',
- },
- },
-
- /** @private */
- expandList_: function() {
- this.expanded_ = true;
- this.moreItemsLinkText_ = '';
- },
-
- /**
- * Decides which elements will be visible in the card and if the "show more"
- * link will be rendered.
- *
- * 1. If size(itemsToShow) < CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW, then all
- * items will be visible.
- * 2. Otherwise, exactly |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1| will be
- * visible and the "show more" link will be rendered. The list presented to
- * the user will contain exactly |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|
- * elements, and the last one will be the "show more" link.
- *
- * @param {!Array<settings.ChromeCleanupRemovalListItem>} itemsToShow
- */
- updateVisibleState_: function(itemsToShow) {
- // Start expanded if there are less than
- // |settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items to show.
- this.expanded_ =
- itemsToShow.length <= settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW;
-
- if (this.expanded_) {
- this.initialItems_ = itemsToShow;
- this.remainingItems_ = [];
- this.moreItemsLinkText_ = '';
- return;
- }
-
- this.initialItems_ =
- itemsToShow.slice(0, settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1);
- this.remainingItems_ =
- itemsToShow.slice(settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1);
-
- const browserProxy = settings.ChromeCleanupProxyImpl.getInstance();
- browserProxy.getMoreItemsPluralString(this.remainingItems_.length)
- .then(linkText => {
- this.moreItemsLinkText_ = linkText;
- });
- },
-
- /**
- * Returns the class for the <li> elements that correspond to the items hidden
- * in the default view.
- * @param {boolean} expanded
- */
- remainingItemsClass_: function(expanded) {
- return expanded ? 'visible-item' : 'hidden-item';
- },
-
- /**
- * @param {settings.ChromeCleanupRemovalListItem} item
- * @return {boolean} Whether a highlight suffix exists.
- * @private
- */
- hasHighlightSuffix_: function(item) {
- return item.highlightSuffix !== null;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.html b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.html
deleted file mode 100644
index 03eb0509a18..00000000000
--- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="clear_browsing_data_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.js b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.js
deleted file mode 100644
index 849c9564b5a..00000000000
--- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Clear browsing data" dialog
- * to interact with the browser.
- */
-
-
-cr.define('settings', function() {
- /** @interface */
- class ClearBrowsingDataBrowserProxy {
- /**
- * @param {!Array<string>} dataTypes
- * @param {number} timePeriod
- * @return {!Promise<boolean>}
- * A promise resolved when data clearing has completed. The boolean
- * indicates whether an additional dialog should be shown, informing the
- * user about other forms of browsing history.
- */
- clearBrowsingData(dataTypes, timePeriod) {}
-
- /**
- * Kick off counter updates and return initial state.
- * @return {!Promise<void>} Signal when the setup is complete.
- */
- initialize() {}
- }
-
- /**
- * @implements {settings.ClearBrowsingDataBrowserProxy}
- */
- class ClearBrowsingDataBrowserProxyImpl {
- /** @override */
- clearBrowsingData(dataTypes, timePeriod) {
- return cr.sendWithPromise('clearBrowsingData', dataTypes, timePeriod);
- }
-
- /** @override */
- initialize() {
- return cr.sendWithPromise('initializeClearBrowsingData');
- }
- }
-
- cr.addSingletonGetter(ClearBrowsingDataBrowserProxyImpl);
-
- return {
- ClearBrowsingDataBrowserProxy: ClearBrowsingDataBrowserProxy,
- ClearBrowsingDataBrowserProxyImpl: ClearBrowsingDataBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.html b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.html
deleted file mode 100644
index 52e133a23ab..00000000000
--- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.html
+++ /dev/null
@@ -1,288 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_tabs/cr_tabs.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="clear_browsing_data_browser_proxy.html">
-<link rel="import" href="history_deletion_dialog.html">
-<link rel="import" href="../controls/settings_checkbox.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-clear-browsing-data-dialog">
- <template>
- <style include="settings-shared">
- :host {
- /* Fixed height to allow multiple tabs with different height.
- * The last entry in the advanced tab should show half an entry.
- * crbug.com/652027 */
- --body-container-height: 322px;
- }
-
- #clearBrowsingDataDialog {
- --border-top-color: var(--paper-grey-300);
- --cr-dialog-top-container-min-height: 42px;
- --cr-dialog-body-border-top: 1px solid var(--border-top-color);
- }
-
- @media (prefers-color-scheme: dark) {
- #clearBrowsingDataDialog {
- --border-top-color: var(--cr-separator-color);
- }
- }
-
- #clearBrowsingDataDialog:not(.fully-rendered) {
- visibility: hidden;
- }
-
- #clearBrowsingDataDialog [slot=title] {
- padding-bottom: 8px;
- }
-
- #clearBrowsingDataDialog::part(body-container) {
- height: var(--body-container-height);
- }
-
- #clearBrowsingDataDialog [slot=body] {
- padding-top: 8px;
- }
-
- #clearBrowsingDataDialog [slot=footer] {
- background: var(--paper-grey-50);
- border-top: none;
- padding: 0;
- }
-
- @media (prefers-color-scheme: dark) {
- #clearBrowsingDataDialog [slot=footer] {
- background: rgb(50, 54, 57); /* Custom color from Namrata. */
- }
- }
-
- .row {
- align-items: center;
- display: flex;
- min-height: 40px;
- }
-
- paper-spinner-lite {
- margin-bottom: auto;
- margin-inline-end: 16px;
- margin-top: auto;
- }
-
- settings-checkbox {
- --settings-row-two-line-min-height: 48px;
- }
-
- #basic-tab settings-checkbox + settings-checkbox {
- --settings-checkbox-margin-top: 12px;
- }
-
- cr-tabs {
- --cr-tabs-font-size: 100%;
- --cr-tabs-height: 40px;
- }
-
- .time-range-row {
- margin-bottom: 12px;
- }
-
- .time-range-select {
- margin-inline-start: 12px;
- }
-
- [slot=title] .secondary {
- font-size: calc(13 / 15 * 100%);
- padding-top: 8px;
- }
-
- .divider {
- border-top: var(--cr-separator-line);
- margin: 0 16px;
- }
-
- #footer-description {
- color: var(--cr-secondary-text-color);
- padding: 16px;
- }
- </style>
-
- <cr-dialog id="clearBrowsingDataDialog"
- close-text="$i18n{close}" ignore-popstate has-tabs>
- <div slot="title">
- <div>$i18n{clearBrowsingData}</div>
- </div>
- <div slot="header">
- <cr-tabs tab-names="[[tabsNames_]]"
- selected="{{prefs.browser.last_clear_browsing_data_tab.value}}"
- on-selected-changed="recordTabChange_"></cr-tabs>
- </div>
- <div slot="body">
- <iron-pages id="tabs"
- selected="[[prefs.browser.last_clear_browsing_data_tab.value]]"
- on-selected-item-changed="updateClearButtonState_">
- <div id="basic-tab">
- <div class="row time-range-row">
- <span class="time-range-label">
- $i18n{clearTimeRange}
- </span>
- <settings-dropdown-menu id="clearFromBasic"
- class="time-range-select"
- label="$i18n{clearTimeRange}"
- pref="{{prefs.browser.clear_data.time_period_basic}}"
- menu-options="[[clearFromOptions_]]">
- </settings-dropdown-menu>
- </div>
- <!-- Note: whether these checkboxes are checked are ignored if
- deleting history is disabled (i.e. supervised users, policy),
- so it's OK to have a hidden checkbox that's also checked (as
- the C++ accounts for whether a user is allowed to delete
- history independently). -->
- <settings-checkbox id="browsingCheckboxBasic"
- pref="{{prefs.browser.clear_data.browsing_history_basic}}"
- label="$i18n{clearBrowsingHistory}"
- sub-label-html="[[browsingCheckboxLabel_(
- isSignedIn_, isSyncingHistory_, syncStatus.hasError,
- '$i18nPolymer{clearBrowsingHistorySummary}',
- '$i18nPolymer{clearBrowsingHistorySummarySignedIn}',
- '$i18nPolymer{clearBrowsingHistorySummarySynced}')]]"
- disabled="[[clearingInProgress_]]"
- hidden="[[isSupervised_]]">
- </settings-checkbox>
- <settings-checkbox id="cookiesCheckboxBasic"
- class="cookies-checkbox"
- pref="{{prefs.browser.clear_data.cookies_basic}}"
- label="$i18n{clearCookies}"
- sub-label="[[cookiesCheckboxLabel_(
- shouldShowCookieException_,
- '$i18nPolymer{clearCookiesSummary}',
- '$i18nPolymer{clearCookiesSummarySignedIn}')]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- <settings-checkbox id="cacheCheckboxBasic"
- class="cache-checkbox"
- pref="{{prefs.browser.clear_data.cache_basic}}"
- label="$i18n{clearCache}"
- sub-label="[[counters_.cache_basic]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- </div>
- <div id="advanced-tab">
- <div class="row time-range-row">
- <span class="time-range-label">
- $i18n{clearTimeRange}
- </span>
- <settings-dropdown-menu id="clearFrom"
- class="time-range-select"
- label="$i18n{clearTimeRange}"
- pref="{{prefs.browser.clear_data.time_period}}"
- menu-options="[[clearFromOptions_]]">
- </settings-dropdown-menu>
- </div>
- <settings-checkbox id="browsingCheckbox"
- pref="{{prefs.browser.clear_data.browsing_history}}"
- label="$i18n{clearBrowsingHistory}"
- sub-label="[[counters_.browsing_history]]"
- disabled="[[clearingInProgress_]]"
- hidden="[[isSupervised_]]">
- </settings-checkbox>
- <settings-checkbox id="downloadCheckbox"
- pref="{{prefs.browser.clear_data.download_history}}"
- label="$i18n{clearDownloadHistory}"
- sub-label="[[counters_.download_history]]"
- disabled="[[clearingInProgress_]]"
- hidden="[[isSupervised_]]">
- </settings-checkbox>
- <settings-checkbox id="cookiesCheckbox"
- class="cookies-checkbox"
- pref="{{prefs.browser.clear_data.cookies}}"
- label="$i18n{clearCookies}"
- sub-label="[[counters_.cookies]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- <settings-checkbox id="cacheCheckbox"
- class="cache-checkbox"
- pref="{{prefs.browser.clear_data.cache}}"
- label="$i18n{clearCache}"
- sub-label="[[counters_.cache]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- <settings-checkbox
- pref="{{prefs.browser.clear_data.passwords}}"
- label="$i18n{clearPasswords}"
- sub-label="[[counters_.passwords]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- <settings-checkbox
- pref="{{prefs.browser.clear_data.form_data}}"
- label="$i18n{clearFormData}"
- sub-label="[[counters_.form_data]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- <settings-checkbox
- pref="{{prefs.browser.clear_data.site_settings}}"
- label="$i18nPolymer{siteSettings}"
- sub-label="[[counters_.site_settings]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- <settings-checkbox
- pref="{{prefs.browser.clear_data.hosted_apps_data}}"
- label="$i18n{clearHostedAppData}"
- sub-label="[[counters_.hosted_apps_data]]"
- disabled="[[clearingInProgress_]]">
- </settings-checkbox>
- </div>
- </iron-pages>
- </div>
- <div slot="button-container">
- <paper-spinner-lite active="[[clearingInProgress_]]">
- </paper-spinner-lite>
- <cr-button class="cancel-button" disabled="[[clearingInProgress_]]"
- on-click="onCancelTap_">$i18n{cancel}</cr-button>
- <cr-button id="clearBrowsingDataConfirm"
- class="action-button" on-click="clearBrowsingData_"
- disabled="[[isClearButtonDisabled_(clearingInProgress_,
- clearButtonDisabled_)]]">
- $i18n{clearData}
- </cr-button>
- </div>
- <div slot="footer"
- hidden="[[!shouldShowFooter_(syncStatus.signedIn, diceEnabled_)]]">
- <settings-sync-account-control sync-status="[[syncStatus]]"
- hide-buttons>
- </settings-sync-account-control>
- <div class="divider"></div>
- <div id="footer-description" on-click="onSyncDescriptionLinkClicked_">
- <span id="sync-info" hidden="[[syncStatus.hasError]]">
- $i18nRaw{clearBrowsingDataWithSync}
- </span>
- <span id="sync-paused-info" hidden="[[!isSyncPaused_]]">
- $i18nRaw{clearBrowsingDataWithSyncPaused}
- </span>
- <span id="sync-passphrase-error-info"
- hidden="[[!hasPassphraseError_]]">
- $i18nRaw{clearBrowsingDataWithSyncPassphraseError}
- </span>
- <span id="sync-other-error-info" hidden="[[!hasOtherSyncError_]]">
- $i18nRaw{clearBrowsingDataWithSyncError}
- </span>
- </div>
- </div>
- </cr-dialog>
-
- <template is="dom-if" if="[[showHistoryDeletionDialog_]]" restamp>
- <settings-history-deletion-dialog id="notice"
- on-close="onHistoryDeletionDialogClose_">
- </settings-history-deletion-dialog>
- </template>
- </template>
- <script src="clear_browsing_data_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js
deleted file mode 100644
index d64aa86e82e..00000000000
--- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js
+++ /dev/null
@@ -1,448 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-clear-browsing-data-dialog' allows the user to
- * delete browsing data that has been cached by Chromium.
- */
-Polymer({
- is: 'settings-clear-browsing-data-dialog',
-
- behaviors: [
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * The current sync status, supplied by SyncBrowserProxy.
- * TODO(dpapad): make |syncStatus| private.
- * @type {?settings.SyncStatus}
- */
- syncStatus: Object,
-
- /**
- * Results of browsing data counters, keyed by the suffix of
- * the corresponding data type deletion preference, as reported
- * by the C++ side.
- * @private {!Object<string>}
- */
- counters_: {
- type: Object,
- // Will be filled as results are reported.
- value: function() {
- return {};
- }
- },
-
- /**
- * List of options for the dropdown menu.
- * @private {!DropdownMenuOptionList}
- */
- clearFromOptions_: {
- readOnly: true,
- type: Array,
- value: [
- {value: 0, name: loadTimeData.getString('clearPeriodHour')},
- {value: 1, name: loadTimeData.getString('clearPeriod24Hours')},
- {value: 2, name: loadTimeData.getString('clearPeriod7Days')},
- {value: 3, name: loadTimeData.getString('clearPeriod4Weeks')},
- {value: 4, name: loadTimeData.getString('clearPeriodEverything')},
- ],
- },
-
- /** @private */
- clearingInProgress_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- clearButtonDisabled_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isSupervised_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isSupervised');
- },
- },
-
- /** @private */
- showHistoryDeletionDialog_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isSignedIn_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isSyncingHistory_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- shouldShowCookieException_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isSyncPaused_: {
- type: Boolean,
- value: false,
- computed: 'computeIsSyncPaused_(syncStatus)',
- },
-
- /** @private */
- hasPassphraseError_: {
- type: Boolean,
- value: false,
- computed: 'computeHasPassphraseError_(syncStatus)',
- },
-
- /** @private */
- hasOtherSyncError_: {
- type: Boolean,
- value: false,
- computed:
- 'computeHasOtherError_(syncStatus, isSyncPaused_, hasPassphraseError_)',
- },
-
- /**
- * This flag is used to conditionally show the footer for the dialog.
- * @private
- */
- diceEnabled_: {
- type: Boolean,
- value: function() {
- let diceEnabled = false;
- // <if expr="not chromeos">
- diceEnabled = loadTimeData.getBoolean('diceEnabled');
- // </if>
- return diceEnabled;
- },
- },
-
- /**
- * Time in ms, when the dialog was opened.
- * @private
- */
- dialogOpenedTime_: {
- type: Number,
- value: 0,
- },
-
- /** @private {Array<string>} */
- tabsNames_: {
- type: Array,
- value: () =>
- [loadTimeData.getString('basicPageTitle'),
- loadTimeData.getString('advancedPageTitle'),
-],
- },
- },
-
- listeners: {'settings-boolean-control-change': 'updateClearButtonState_'},
-
- /** @private {settings.ClearBrowsingDataBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?settings.SyncBrowserProxy} */
- syncBrowserProxy_: null,
-
- /** @override */
- ready: function() {
- this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
- this.syncBrowserProxy_.getSyncStatus().then(
- this.handleSyncStatus_.bind(this));
- this.addWebUIListener(
- 'sync-status-changed', this.handleSyncStatus_.bind(this));
-
- this.addWebUIListener(
- 'update-sync-state', this.updateSyncState_.bind(this));
- this.addWebUIListener(
- 'update-counter-text', this.updateCounterText_.bind(this));
- },
-
- /** @override */
- attached: function() {
- this.browserProxy_ =
- settings.ClearBrowsingDataBrowserProxyImpl.getInstance();
- this.dialogOpenedTime_ = Date.now();
- this.browserProxy_.initialize().then(() => {
- this.$.clearBrowsingDataDialog.showModal();
- });
- },
-
- /**
- * Handler for when the sync state is pushed from the browser.
- * @param {?settings.SyncStatus} syncStatus
- * @private
- */
- handleSyncStatus_: function(syncStatus) {
- this.syncStatus = syncStatus;
- },
-
- /**
- * Returns true if either clearing is in progress or no data type is selected.
- * @param {boolean} clearingInProgress
- * @param {boolean} clearButtonDisabled
- * @return {boolean}
- * @private
- */
- isClearButtonDisabled_: function(clearingInProgress, clearButtonDisabled) {
- return clearingInProgress || clearButtonDisabled;
- },
-
- /**
- * Disables the Clear Data button if no data type is selected.
- * @private
- */
- updateClearButtonState_: function() {
- // on-select-item-changed gets called with undefined during a tab change.
- // https://github.com/PolymerElements/iron-selector/issues/95
- const tab = this.$.tabs.selectedItem;
- if (!tab) {
- return;
- }
- this.clearButtonDisabled_ = this.getSelectedDataTypes_(tab).length == 0;
- },
-
- /**
- * Record visits to the CBD dialog.
- *
- * settings.RouteObserverBehavior
- * @param {!settings.Route} currentRoute
- * @protected
- */
- currentRouteChanged: function(currentRoute) {
- if (currentRoute == settings.routes.CLEAR_BROWSER_DATA) {
- chrome.metricsPrivate.recordUserAction('ClearBrowsingData_DialogCreated');
- this.dialogOpenedTime_ = Date.now();
- }
- },
-
- /**
- * Updates the history description to show the relevant information
- * depending on sync and signin state.
- *
- * @param {boolean} signedIn Whether the user is signed in.
- * @param {boolean} syncing Whether the user is syncing history.
- * @param {boolean} shouldShowCookieException Whether the exception about not
- * being signed out of your Google account should be shown.
- * @private
- */
- updateSyncState_: function(signedIn, syncing, shouldShowCookieException) {
- this.isSignedIn_ = signedIn;
- this.isSyncingHistory_ = syncing;
- this.shouldShowCookieException_ = shouldShowCookieException;
- this.$.clearBrowsingDataDialog.classList.add('fully-rendered');
- },
-
- /**
- * Choose a label for the history checkbox.
- * @param {boolean} isSignedIn
- * @param {boolean} isSyncingHistory
- * @param {string} historySummary
- * @param {string} historySummarySignedIn
- * @param {string} historySummarySynced
- * @return {string}
- * @private
- */
- browsingCheckboxLabel_: function(
- isSignedIn, isSyncingHistory, hasSyncError, historySummary,
- historySummarySignedIn, historySummarySynced) {
- if (isSyncingHistory && !hasSyncError) {
- return historySummarySynced;
- } else if (isSignedIn && !this.isSyncPaused_) {
- return historySummarySignedIn;
- }
- return historySummary;
- },
-
- /**
- * Choose a label for the cookie checkbox.
- * @param {boolean} shouldShowCookieException
- * @param {string} cookiesSummary
- * @param {string} cookiesSummarySignedIn
- * @return {string}
- * @private
- */
- cookiesCheckboxLabel_: function(
- shouldShowCookieException, cookiesSummary, cookiesSummarySignedIn) {
- if (shouldShowCookieException) {
- return cookiesSummarySignedIn;
- }
- return cookiesSummary;
- },
-
- /**
- * Updates the text of a browsing data counter corresponding to the given
- * preference.
- * @param {string} prefName Browsing data type deletion preference.
- * @param {string} text The text with which to update the counter
- * @private
- */
- updateCounterText_: function(prefName, text) {
- // Data type deletion preferences are named "browser.clear_data.<datatype>".
- // Strip the common prefix, i.e. use only "<datatype>".
- const matches = prefName.match(/^browser\.clear_data\.(\w+)$/);
- this.set('counters_.' + assert(matches[1]), text);
- },
-
- /**
- * Returns a list of selected data types.
- * @param {!HTMLElement} tab
- * @return {!Array<string>}
- * @private
- */
- getSelectedDataTypes_: function(tab) {
- const checkboxes = tab.querySelectorAll('settings-checkbox');
- const dataTypes = [];
- checkboxes.forEach((checkbox) => {
- if (checkbox.checked && !checkbox.hidden) {
- dataTypes.push(checkbox.pref.key);
- }
- });
- return dataTypes;
- },
-
- /**
- * Clears browsing data and maybe shows a history notice.
- * @private
- */
- clearBrowsingData_: function() {
- this.clearingInProgress_ = true;
- const tab = this.$.tabs.selectedItem;
- const dataTypes = this.getSelectedDataTypes_(tab);
- const timePeriod = tab.querySelector('.time-range-select').pref.value;
-
- if (tab.id == 'basic-tab') {
- chrome.metricsPrivate.recordUserAction('ClearBrowsingData_BasicTab');
- } else {
- chrome.metricsPrivate.recordUserAction('ClearBrowsingData_AdvancedTab');
- }
-
- this.browserProxy_.clearBrowsingData(dataTypes, timePeriod)
- .then(shouldShowNotice => {
- this.clearingInProgress_ = false;
- this.showHistoryDeletionDialog_ = shouldShowNotice;
- chrome.metricsPrivate.recordMediumTime(
- 'History.ClearBrowsingData.TimeSpentInDialog',
- Date.now() - this.dialogOpenedTime_);
- if (!shouldShowNotice) {
- this.$.clearBrowsingDataDialog.close();
- }
- });
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.clearBrowsingDataDialog.cancel();
- },
-
- /**
- * Handles the closing of the notice about other forms of browsing history.
- * @private
- */
- onHistoryDeletionDialogClose_: function() {
- this.showHistoryDeletionDialog_ = false;
- this.$.clearBrowsingDataDialog.close();
- },
-
- /**
- * Records an action when the user changes between the basic and advanced tab.
- * @param {!Event} event
- * @private
- */
- recordTabChange_: function(event) {
- if (event.detail.value == 0) {
- chrome.metricsPrivate.recordUserAction(
- 'ClearBrowsingData_SwitchTo_BasicTab');
- } else {
- chrome.metricsPrivate.recordUserAction(
- 'ClearBrowsingData_SwitchTo_AdvancedTab');
- }
- },
-
- /**
- * Called when the user clicks the link in the footer.
- * @param {!Event} e
- * @private
- */
- onSyncDescriptionLinkClicked_: function(e) {
- if (e.target.tagName === 'A') {
- e.preventDefault();
- if (!this.syncStatus.hasError) {
- chrome.metricsPrivate.recordUserAction('ClearBrowsingData_Sync_Pause');
- this.syncBrowserProxy_.pauseSync();
- } else if (this.isSyncPaused_) {
- chrome.metricsPrivate.recordUserAction('ClearBrowsingData_Sync_SignIn');
- this.syncBrowserProxy_.startSignIn();
- } else {
- if (this.hasPassphraseError_) {
- chrome.metricsPrivate.recordUserAction(
- 'ClearBrowsingData_Sync_NavigateToPassphrase');
- } else {
- chrome.metricsPrivate.recordUserAction(
- 'ClearBrowsingData_Sync_NavigateToError');
- }
- // In any other error case, navigate to the sync page.
- settings.navigateTo(settings.routes.SYNC);
- }
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeIsSyncPaused_: function() {
- return !!this.syncStatus.hasError &&
- this.syncStatus.statusAction === settings.StatusAction.REAUTHENTICATE;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeHasPassphraseError_: function() {
- return !!this.syncStatus.hasError &&
- this.syncStatus.statusAction === settings.StatusAction.ENTER_PASSPHRASE;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeHasOtherError_: function() {
- return this.syncStatus !== undefined && !!this.syncStatus.hasError &&
- !this.isSyncPaused_ && !this.hasPassphraseError_;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowFooter_: function() {
- return this.diceEnabled_ && !!this.syncStatus && !!this.syncStatus.signedIn;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.html b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.html
deleted file mode 100644
index 7dad80083a6..00000000000
--- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-history-deletion-dialog">
- <template>
- <style include="settings-shared"></style>
-
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{historyDeletionDialogTitle}</div>
- <div slot="body">$i18nRaw{historyDeletionDialogBody}</div>
- <div slot="button-container">
- <cr-button class="action-button" on-click="onOkTap_">
- $i18n{historyDeletionDialogOK}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="history_deletion_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.js b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.js
deleted file mode 100644
index c49359cf14d..00000000000
--- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/history_deletion_dialog.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-history-deletion-dialog' is a dialog that is
- * optionally shown inside settings-clear-browsing-data-dialog after deleting
- * browsing history. It informs the user about the existence of other forms
- * of browsing history in their account.
- */
-Polymer({
- is: 'settings-history-deletion-dialog',
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- /**
- * Tap handler for the "OK" button.
- * @private
- */
- onOkTap_: function() {
- this.$.dialog.close();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_button.html b/chromium/chrome/browser/resources/settings/controls/controlled_button.html
deleted file mode 100644
index 79fb7d2e405..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/controlled_button.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="controlled-button">
- <template>
- <style include="settings-shared">
- :host {
- --justify-margin: 8px;
- align-items: center;
- display: flex;
- }
-
- :host([enforced_]) {
- /* Disable pointer events for this whole element, as outer on-tap gets
- * triggered when clicking/tapping anywhere in :host. */
- pointer-events: none;
- }
-
- cr-policy-pref-indicator {
- /* Enable pointer events for the indicator so :hover works. Disable
- * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
- pointer-events: all;
- }
-
- :host(:not([end-justified])) cr-policy-pref-indicator {
- margin-inline-start: var(--cr-controlled-by-spacing);
- }
-
- :host([end-justified]) cr-policy-pref-indicator {
- margin-inline-end: var(--cr-controlled-by-spacing);
- margin-inline-start: calc(
- var(--cr-controlled-by-spacing) - var(--justify-margin));
- order: -1;
- }
- </style>
-
- <cr-button class$="[[actionClass_]]"
- disabled="[[!buttonEnabled_(enforced_, disabled)]]">
- [[label]]
- </cr-button>
-
- <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]" restamp>
- <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorTap_"
- icon-aria-label="[[label]]">
- </cr-policy-pref-indicator>
- </template>
-
- </template>
- <script src="controlled_button.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_button.js b/chromium/chrome/browser/resources/settings/controls/controlled_button.js
deleted file mode 100644
index e751237cab2..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/controlled_button.js
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-Polymer({
- is: 'controlled-button',
-
- behaviors: [
- CrPolicyPrefBehavior,
- PrefControlBehavior,
- ],
-
- properties: {
- endJustified: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
-
- label: String,
-
- disabled: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
-
- /** @private */
- actionClass_: {
- type: String,
- value: ''
- },
-
- /** @private */
- enforced_: {
- type: Boolean,
- computed: 'isPrefEnforced(pref.*)',
- reflectToAttribute: true,
- },
- },
-
- /** @override */
- attached: function() {
- if (this.classList.contains('action-button')) {
- this.actionClass_ = 'action-button';
- }
- },
-
- /** Focus on the inner cr-button. */
- focus: function() {
- this.$$('cr-button').focus();
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onIndicatorTap_: function(e) {
- // Disallow <controlled-button on-click="..."> when controlled.
- e.preventDefault();
- e.stopPropagation();
- },
-
- /**
- * @param {!boolean} enforced
- * @param {!boolean} disabled
- * @return {boolean} True if the button should be enabled.
- * @private
- */
- buttonEnabled_(enforced, disabled) {
- return !enforced && !disabled;
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html b/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html
deleted file mode 100644
index de6672af7a9..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../prefs/pref_util.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="controlled-radio-button">
- <template>
- <style include="settings-shared cr-radio-button-style">
- :host([disabled]) {
- opacity: 1;
- }
-
- /* Disc and label should be transluscent, but not the policy indicator. */
- :host([disabled]) .disc-wrapper,
- :host([disabled]) #labelWrapper {
- opacity: var(--cr-disabled-opacity);
- }
-
- cr-policy-pref-indicator {
- margin-inline-start: var(--settings-controlled-by-spacing);
- /* Enable pointer events for the indicator so :hover works. Disable
- * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
- pointer-events: all;
- }
- </style>
-
- <div
- aria-checked$="[[getAriaChecked_(checked)]]"
- aria-describedby="slotted-content"
- aria-disabled$="[[getAriaDisabled_(disabled)]]"
- aria-labelledby="label"
- class="disc-wrapper"
- id="button"
- role="radio"
- tabindex="0"
- on-keydown="onInputKeydown_">
- <div class="disc-border"></div>
- <div class="disc"></div>
- </div>
-
- <div id="labelWrapper">
- <span id="label" hidden$="[[!label]]">[[label]]</span>
- <span id="slotted-content">
- <slot></slot>
- </span>
- </div>
-
- <template is="dom-if" if="[[showIndicator_(disabled, name, pref.*)]]">
- <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorTap_"
- icon-aria-label="[[label]]">
- </cr-policy-pref-indicator>
- </template>
-
- </template>
- <script src="controlled_radio_button.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js b/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js
deleted file mode 100644
index 3c0a4ab90d0..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-Polymer({
- is: 'controlled-radio-button',
-
- behaviors: [
- PrefControlBehavior,
- CrRadioButtonBehavior,
- ],
-
- observers: [
- 'updateDisabled_(pref.enforcement)',
- ],
-
- /** @private */
- updateDisabled_: function() {
- this.disabled =
- this.pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- showIndicator_: function() {
- return this.disabled &&
- this.name == Settings.PrefUtil.prefToString(assert(this.pref));
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onIndicatorTap_: function(e) {
- // Disallow <controlled-radio-button on-click="..."> when disabled.
- e.preventDefault();
- e.stopPropagation();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html b/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html
deleted file mode 100644
index e09561aa0ef..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../extension_control_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="extension-controlled-indicator">
- <template>
- <style include="settings-shared">
- :host {
- align-items: center;
- display: flex;
- margin-inline-start: 36px;
- min-height: var(--settings-row-min-height);
- }
-
- img {
- @apply --cr-icon-height-width;
- margin-inline-end: 16px;
- }
-
- /* Using ">" operator to ensure that this CSS rule will not accidentally
- * be applied to a search highlight span (which is inserted dynamically if
- * when search "hit" occurs within this element. */
- :host > span {
- flex: 1;
- margin-inline-end: 8px;
- }
- </style>
- <img role="presentation" src="chrome://extension-icon/[[extensionId]]/40/1">
- <span inner-h-t-m-l="[[getLabel_(extensionId, extensionName)]]"></span>
- <template is="dom-if" if="[[extensionCanBeDisabled]]" restamp>
- <cr-button on-click="onDisableTap_">$i18n{disable}</cr-button>
- </template>
- </template>
-</dom-module>
-
-<script src="extension_controlled_indicator.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.js b/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.js
deleted file mode 100644
index e48a8e47d71..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-Polymer({
- is: 'extension-controlled-indicator',
-
- behaviors: [I18nBehavior],
-
- properties: {
- extensionCanBeDisabled: Boolean,
- extensionId: String,
- extensionName: String,
- },
-
- /**
- * @param {string} extensionId
- * @param {string} extensionName
- * @return {string}
- * @private
- */
- getLabel_: function(extensionId, extensionName) {
- if (this.extensionId == undefined || this.extensionName == undefined) {
- return '';
- }
-
- const manageUrl = 'chrome://extensions/?id=' + this.extensionId;
- return this.i18nAdvanced('controlledByExtension', {
- substitutions:
- ['<a href="' + manageUrl + '" target="_blank">' + this.extensionName +
- '</a>'],
- });
- },
-
- /** @private */
- onDisableTap_: function() {
- assert(this.extensionCanBeDisabled);
- settings.ExtensionControlBrowserProxyImpl.getInstance().disableExtension(
- assert(this.extensionId));
- this.fire('extension-disable');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.html b/chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.html
deleted file mode 100644
index 810fd0b8b00..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-password-prompt-dialog">
- <template>
- <style include="settings-shared">
- cr-dialog::part(dialog) {
- width: 320px;
- }
-
- #passwordPrompt {
- padding-bottom: 20px;
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{passwordPromptTitle}</div>
- <div slot="body">
- <div id="passwordPrompt" class="settings-box first"
- hidden="[[!passwordPromptText]]">
- [[passwordPromptText]]
- </div>
- <cr-input id="passwordInput" type="password"
- placeholder="$i18n{passwordPromptPasswordLabel}"
- invalid="[[passwordInvalid_]]"
- error-message="$i18n{passwordPromptInvalidPassword}"
- value="{{inputValue_}}"
- aria-disabled="false">
- </cr-input>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
-
- <cr-button id="confirmButton" class="action-button"
- disabled$="[[!isConfirmEnabled_(inputValue_, passwordInvalid_)]]"
- on-click="submitPassword_">
- $i18n{confirm}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="password_prompt_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.js b/chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.js
deleted file mode 100644
index 574dd209c16..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/password_prompt_dialog.js
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- *
- * 'settings-password-prompt-dialog' shows a dialog which asks for the user to
- * enter their password. It validates the password is correct. Once the user has
- * entered their account password, the page fires an 'authenticated' event and
- * updates the authToken binding.
- *
- * Example:
- *
- * <settings-password-prompt-dialog
- * id="passwordPrompt"
- * password-prompt-text="{{passwordPromptText}}"
- * auth-token="{{authToken}}">
- * </settings-password-prompt-dialog>
- */
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-password-prompt-dialog',
-
- properties: {
- /**
- * The subtext to be displayed above the password input field. Embedders
- * may choose to change this value for their specific use case.
- * @type {string}
- */
- passwordPromptText: {
- type: String,
- notify: true,
- value: '',
- },
-
- /**
- * Authentication token returned by quickUnlockPrivate.getAuthToken().
- * Should be passed to API calls which require authentication.
- * @type {string}
- */
- authToken: {
- type: String,
- notify: true,
- },
-
- /**
- * @private {string}
- */
- inputValue_: {
- type: String,
- value: '',
- observer: 'onInputValueChange_',
- },
-
- /**
- * Helper property which marks password as valid/invalid.
- * @private {boolean}
- */
- passwordInvalid_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Interface for chrome.quickUnlockPrivate calls. May be overridden by
- * tests.
- * @type {QuickUnlockPrivate}
- */
- quickUnlockPrivate: {type: Object, value: chrome.quickUnlockPrivate},
- },
-
- /** @return {!CrInputElement} */
- get passwordInput() {
- return this.$.passwordInput;
- },
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- // This needs to occur at the next paint otherwise the password input will
- // not receive focus.
- this.async(() => {
- // TODO(crbug.com/876377): This is unusual; the 'autofocus' attribute on
- // the cr-input element should work. Investigate.
- this.passwordInput.focus();
- }, 1 /* waitTime */);
- },
-
- /** @private */
- onCancelTap_: function() {
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
- },
-
- /**
- * The timeout ID to pass to clearTimeout() to cancel auth token
- * invalidation.
- * @private {number|undefined}
- */
- clearAccountPasswordTimeoutId_: undefined,
-
- /**
- * Run the account password check.
- * @private
- */
- submitPassword_: function() {
- clearTimeout(this.clearAccountPasswordTimeoutId_);
-
- const password = this.passwordInput.value;
- // The user might have started entering a password and then deleted it all.
- // Do not submit/show an error in this case.
- if (!password) {
- this.passwordInvalid_ = false;
- return;
- }
-
- this.quickUnlockPrivate.getAuthToken(password, (tokenInfo) => {
- if (chrome.runtime.lastError) {
- this.passwordInvalid_ = true;
- // Select the whole password if user entered an incorrect password.
- this.passwordInput.select();
- return;
- }
-
- this.authToken = tokenInfo.token;
- this.passwordInvalid_ = false;
-
- // Clear |this.authToken| after tokenInfo.lifetimeSeconds.
- // Subtract time from the expiration time to account for IPC delays.
- // Treat values less than the minimum as 0 for testing.
- const IPC_SECONDS = 2;
- const lifetimeMs = tokenInfo.lifetimeSeconds > IPC_SECONDS ?
- (tokenInfo.lifetimeSeconds - IPC_SECONDS) * 1000 :
- 0;
- this.clearAccountPasswordTimeoutId_ = setTimeout(() => {
- this.authToken = '';
- }, lifetimeMs);
-
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
- });
- },
-
- /** @private */
- onInputValueChange_: function() {
- this.passwordInvalid_ = false;
- },
-
- /** @private */
- isConfirmEnabled_: function() {
- return !this.passwordInvalid_ && this.inputValue_;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/controls/pref_control_behavior.html b/chromium/chrome/browser/resources/settings/controls/pref_control_behavior.html
deleted file mode 100644
index 71841dc7bd4..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/pref_control_behavior.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="../prefs/prefs_types.html">
-<script src="pref_control_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/controls/pref_control_behavior.js b/chromium/chrome/browser/resources/settings/controls/pref_control_behavior.js
deleted file mode 100644
index 8db2d75dc99..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/pref_control_behavior.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @polymerBehavior Tracks the initialization of a specified preference and
- * logs an error if the pref is not defined after prefs have been fetched.
- */
-const PrefControlBehavior = {
- properties: {
- /**
- * The Preference object being tracked.
- * @type {!chrome.settingsPrivate.PrefObject|undefined}
- */
- pref: {
- type: Object,
- notify: true,
- observer: 'validatePref_',
- },
- },
-
- /** @override */
- ready: function() {
- this.validatePref_();
- },
-
- /**
- * Logs an error once prefs are initialized if the tracked pref is not found.
- * @private
- */
- validatePref_: function() {
- CrSettingsPrefs.initialized.then(() => {
- if (this.pref === undefined) {
- let error = 'Pref not found for element ' + this.tagName;
- if (this.id) {
- error += '#' + this.id;
- }
- error += ' in ' + this.domHost.tagName;
- console.error(error);
- } else if (
- this.pref.enforcement ==
- chrome.settingsPrivate.Enforcement.PARENT_SUPERVISED) {
- console.error('PARENT_SUPERVISED is not enforced by pref controls');
- }
- });
- },
-};
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html b/chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html
deleted file mode 100644
index c0b6c5705ee..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="pref_control_behavior.html">
-<script src="settings_boolean_control_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js b/chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js
deleted file mode 100644
index b69ab4e2593..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * A behavior to help controls that handle a boolean preference, such as
- * checkbox and toggle button.
- */
-
-/** @polymerBehavior SettingsBooleanControlBehavior */
-const SettingsBooleanControlBehaviorImpl = {
- properties: {
- /** Whether the control should represent the inverted value. */
- inverted: {
- type: Boolean,
- value: false,
- },
-
- /** Whether the control is checked. */
- checked: {
- type: Boolean,
- value: false,
- notify: true,
- reflectToAttribute: true,
- },
-
- /** Disabled property for the element. */
- disabled: {
- type: Boolean,
- value: false,
- notify: true,
- reflectToAttribute: true,
- },
-
- /**
- * If true, do not automatically set the preference value. This allows the
- * container to confirm the change first then call either sendPrefChange
- * or resetToPrefValue accordingly.
- */
- noSetPref: {
- type: Boolean,
- value: false,
- },
-
- /** The main label. */
- label: {
- type: String,
- value: '',
- },
-
- /** Additional (optional) sub-label. */
- subLabel: {
- type: String,
- value: '',
- },
-
- /**
- * For numeric prefs only, the integer value equivalent to the unchecked
- * state. This is the value sent to prefs if the user unchecks the control.
- * During initialization, the control is unchecked if and only if the pref
- * value is equal to the this value. (Values 2, 3, 4, etc. all are checked.)
- */
- numericUncheckedValue: {
- type: Number,
- value: 0,
- }
- },
-
- observers: [
- 'prefValueChanged_(pref.value)',
- ],
-
- notifyChangedByUserInteraction: function() {
- this.fire('settings-boolean-control-change');
-
- if (!this.pref || this.noSetPref) {
- return;
- }
- this.sendPrefChange();
- },
-
- /** Reset the checked state to match the current pref value. */
- resetToPrefValue: function() {
- this.checked = this.getNewValue_(this.pref.value);
- },
-
- /** Update the pref to the current |checked| value. */
- sendPrefChange: function() {
- // Ensure that newValue is the correct type for the pref type, either
- // a boolean or a number.
- if (this.pref.type == chrome.settingsPrivate.PrefType.NUMBER) {
- assert(!this.inverted);
- this.set('pref.value', this.checked ? 1 : this.numericUncheckedValue);
- return;
- }
- this.set('pref.value', this.inverted ? !this.checked : this.checked);
- },
-
- /**
- * Polymer observer for pref.value.
- * @param {*} prefValue
- * @private
- */
- prefValueChanged_: function(prefValue) {
- this.checked = this.getNewValue_(prefValue);
- },
-
- /**
- * @param {*} value
- * @return {boolean} The value as a boolean, inverted if |inverted| is true.
- * @private
- */
- getNewValue_: function(value) {
- // For numeric prefs, the control is only false if the value is exactly
- // equal to the unchecked-equivalent value.
- if (this.pref.type == chrome.settingsPrivate.PrefType.NUMBER) {
- assert(!this.inverted);
- return value != this.numericUncheckedValue;
- }
- return this.inverted ? !value : !!value;
- },
-
- /**
- * @return {boolean} Whether the control should be disabled.
- * @protected
- */
- controlDisabled: function() {
- return this.disabled || this.isPrefEnforced();
- },
-};
-
-/** @polymerBehavior */
-const SettingsBooleanControlBehavior = [
- CrPolicyPrefBehavior,
- PrefControlBehavior,
- SettingsBooleanControlBehaviorImpl,
-];
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html b/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html
deleted file mode 100644
index c5433716c5c..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="settings_boolean_control_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-checkbox">
- <template>
- <style include="settings-shared">
- #outerRow {
- align-items: center;
- display: flex;
- min-height: var(--settings-row-two-line-min-height);
- width: 100%;
- }
-
- #outerRow[noSubLabel] {
- min-height: var(--settings-row-min-height);
- }
-
- cr-checkbox {
- /* Additional margin in case subLabel needs more than one line. */
- margin-bottom: 4px;
- margin-top: var(--settings-checkbox-margin-top, 4px);
- width: 100%;
- }
-
- cr-policy-pref-indicator {
- margin-inline-start: var(--settings-controlled-by-spacing);
- }
- </style>
- <div id="outerRow" noSubLabel$="[[!hasSubLabel_(subLabel, subLabelHtml)]]">
- <cr-checkbox id="checkbox" checked="{{checked}}"
- on-change="notifyChangedByUserInteraction"
- disabled="[[controlDisabled(disabled, pref.*)]]"
- aria-label="[[label]]">
- <div id="label" class="label">[[label]] <slot></slot></div>
- <div id="subLabel" class="secondary label">
- <div inner-h-t-m-l="[[subLabelHtml]]"></div>
- [[subLabel]]
- </div>
- </cr-checkbox>
- <template is="dom-if" if="[[pref.controlledBy]]">
- <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]">
- </cr-policy-pref-indicator>
- </template>
- </div>
- </template>
- <script src="settings_checkbox.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js b/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js
deleted file mode 100644
index 3bc4f6c49a4..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * `settings-checkbox` is a checkbox that controls a supplied preference.
- */
-Polymer({
- is: 'settings-checkbox',
-
- behaviors: [SettingsBooleanControlBehavior],
-
- properties: {
- /**
- * Alternative source for the sub-label that can contain html markup.
- * Only use with trusted input.
- */
- subLabelHtml: {
- type: String,
- value: '',
- observer: 'onSubLabelHtmlChanged_',
- },
- },
-
- observers: [
- 'onSubLabelChanged_(subLabel, subLabelHtml)',
- ],
-
- /** @private */
- onSubLabelChanged_: function() {
- this.$.checkbox.ariaDescription = this.$.subLabel.textContent;
- },
-
- /**
- * Don't let clicks on a link inside the secondary label reach the checkbox.
- * @private
- */
- onSubLabelHtmlChanged_: function() {
- const links = this.root.querySelectorAll('.secondary.label a');
- links.forEach((link) => {
- link.addEventListener('click', this.stopPropagation);
- });
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- stopPropagation: function(event) {
- event.stopPropagation();
- },
-
- /**
- * @param {string} subLabel
- * @param {string} subLabelHtml
- * @return {boolean} Whether there is a subLabel
- * @private
- */
- hasSubLabel_: function(subLabel, subLabelHtml) {
- return !!subLabel || !!subLabelHtml;
- },
-
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.html b/chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
deleted file mode 100644
index 39d0615a9eb..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/pref_util.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-dropdown-menu">
- <template>
- <style include="settings-shared md-select">
- :host {
- align-items: center;
- display: inline-flex;
- }
-
- /* When settings-dropdown-menu is start-aligned, we probably want policy
- * indicator to be be displayed after the dropdown.
- * Setting --settings-dropdown-menu-policy-order to 1 will do the job.
- */
- cr-policy-pref-indicator {
- height: var(--iron-icon-width, 24px);
- margin: 0 var(--settings-controlled-by-spacing);
- order: var(--settings-dropdown-menu-policy-order, 0);
- width: var(--iron-icon-width, 24px);
- }
-
- /* Hide "Custom" value when unselectable. */
- option:disabled {
- display: none;
- }
- </style>
- <template is="dom-if" if="[[pref.controlledBy]]" restamp>
- <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
- </template>
- <select class="md-select" id="dropdownMenu" on-change="onChange_"
- aria-label$="[[label]]"
- disabled="[[shouldDisableMenu_(disabled, menuOptions, pref.*)]]">
- <template is="dom-repeat" items="[[menuOptions]]">
- <option value="[[item.value]]">[[item.name]]</option>
- </template>
- <option value="[[notFoundValue_]]"
- disabled="[[!showNotFoundValue_(menuOptions, pref.value)]]">
- $i18n{custom}
- </option>
- </select>
- </template>
- <script src="settings_dropdown_menu.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.js b/chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.js
deleted file mode 100644
index b4187b59e08..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_dropdown_menu.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * The |name| is shown in the gui. The |value| us use to set or compare with
- * the preference value.
- * @typedef {{
- * name: string,
- * value: (number|string)
- * }}
- */
-let DropdownMenuOption;
-
-/**
- * @typedef {!Array<!DropdownMenuOption>}
- */
-let DropdownMenuOptionList;
-
-/**
- * 'settings-dropdown-menu' is a control for displaying options
- * in the settings.
- *
- * Example:
- *
- * <settings-dropdown-menu pref="{{prefs.foo}}">
- * </settings-dropdown-menu>
- */
-Polymer({
- is: 'settings-dropdown-menu',
-
- behaviors: [CrPolicyPrefBehavior, PrefControlBehavior],
-
- properties: {
- /**
- * List of options for the drop-down menu.
- * @type {!DropdownMenuOptionList}
- */
- menuOptions: Array,
-
- /** Whether the dropdown menu should be disabled. */
- disabled: {
- type: Boolean,
- reflectToAttribute: true,
- value: false,
- },
-
- /**
- If this is a dictionary pref, this is the key for the item
- we are interested in.
- */
- prefKey: {
- type: String,
- value: null,
- },
-
- /**
- * The value of the "custom" item.
- * @private
- */
- notFoundValue_: {
- type: String,
- value: 'SETTINGS_DROPDOWN_NOT_FOUND_ITEM',
- readOnly: true,
- },
-
- /** Label for a11y purposes */
- label: String,
- },
-
- observers: [
- 'updateSelected_(menuOptions, pref.value.*, prefKey)',
- ],
-
- /**
- * Pass the selection change to the pref value.
- * @private
- */
- onChange_: function() {
- const selected = this.$.dropdownMenu.value;
-
- if (selected == this.notFoundValue_) {
- return;
- }
-
- if (this.prefKey) {
- assert(this.pref);
- this.set(`pref.value.${this.prefKey}`, selected);
- } else {
- const prefValue =
- Settings.PrefUtil.stringToPrefValue(selected, assert(this.pref));
- if (prefValue !== undefined) {
- this.set('pref.value', prefValue);
- }
- }
-
- // settings-control-change only fires when the selection is changed to
- // a valid property.
- this.fire('settings-control-change');
- },
-
- /**
- * Updates the selected item when the pref or menuOptions change.
- * @private
- */
- updateSelected_: function() {
- if (this.menuOptions === undefined || this.pref === undefined ||
- this.prefKey === undefined) {
- return;
- }
-
- if (!this.menuOptions.length) {
- return;
- }
-
- const prefValue = this.prefStringValue_();
- const option = this.menuOptions.find(function(menuItem) {
- return menuItem.value == prefValue;
- });
-
- // Wait for the dom-repeat to populate the <select> before setting
- // <select>#value so the correct option gets selected.
- this.async(() => {
- this.$.dropdownMenu.value =
- option == undefined ? this.notFoundValue_ : prefValue;
- });
- },
-
- /**
- * Gets the current value of the preference as a string.
- * @return {string}
- * @private
- */
- prefStringValue_: function() {
- if (this.prefKey) {
- // Dictionary pref, values are always strings.
- return this.pref.value[this.prefKey];
- } else {
- return Settings.PrefUtil.prefToString(assert(this.pref));
- }
- },
-
- /**
- * @param {?DropdownMenuOptionList} menuOptions
- * @param {string} prefValue
- * @return {boolean}
- * @private
- */
- showNotFoundValue_: function(menuOptions, prefValue) {
- if (menuOptions === undefined || prefValue === undefined) {
- return false;
- }
-
- // Don't show "Custom" before the options load.
- if (menuOptions === null || menuOptions.length == 0) {
- return false;
- }
-
- const option = menuOptions.find((menuItem) => {
- return menuItem.value == this.prefStringValue_();
- });
- return !option;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldDisableMenu_: function() {
- return this.disabled || this.isPrefEnforced() ||
- this.menuOptions === undefined || this.menuOptions.length == 0;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_idle_load.html b/chromium/chrome/browser/resources/settings/controls/settings_idle_load.html
deleted file mode 100644
index 76d06b59511..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_idle_load.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<dom-module id="settings-idle-load">
- <template>
- <slot></slot>
- </template>
- <script src="settings_idle_load.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_idle_load.js b/chromium/chrome/browser/resources/settings/controls/settings_idle_load.js
deleted file mode 100644
index 21f1c0bb783..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_idle_load.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * settings-idle-load is a simple variant of dom-if designed for lazy
- * loading and rendering of elements that are accessed imperatively. A URL is
- * given that holds the elements to be loaded lazily.
- */
-Polymer({
- is: 'settings-idle-load',
-
- behaviors: [Polymer.Templatizer],
-
- properties: {
- /**
- * If specified, it will be loaded via an HTML import before stamping the
- * template.
- */
- url: String,
- },
-
- /** @private {?Element} */
- child_: null,
-
- /** @private {?Element} */
- instance_: null,
-
- /** @private {number} */
- idleCallback_: 0,
-
- /** @override */
- attached: function() {
- this.idleCallback_ = requestIdleCallback(this.get.bind(this));
- },
-
- /** @override */
- detached: function() {
- // No-op if callback already fired.
- cancelIdleCallback(this.idleCallback_);
- },
-
- /**
- * @return {!Promise<Element>} Child element which has been stamped into the
- * DOM tree.
- */
- get: function() {
- if (this.loading_) {
- return this.loading_;
- }
-
- this.loading_ = new Promise((resolve, reject) => {
- this.importHref(this.url, () => {
- assert(!this.ctor);
- this.templatize(this.getContentChildren()[0]);
- assert(this.ctor);
-
- this.instance_ = this.stamp({});
-
- assert(!this.child_);
- this.child_ = this.instance_.root.firstElementChild;
-
- this.parentNode.insertBefore(this.instance_.root, this);
- resolve(this.child_);
-
- this.fire('lazy-loaded');
- }, reject, true);
- });
-
- return this.loading_;
- },
-
- /**
- * @param {string} prop
- * @param {Object} value
- */
- _forwardHostPropV2: function(prop, value) {
- if (this.instance_) {
- this.instance_.forwardHostProp(prop, value);
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_radio_group.html b/chromium/chrome/browser/resources/settings/controls/settings_radio_group.html
deleted file mode 100644
index ff6a6759926..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_radio_group.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../prefs/pref_util.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-radio-group">
- <template>
- <style include="settings-shared"></style>
- <cr-radio-group selected="[[selected]]"
- on-selected-changed="onSelectedChanged_"
- aria-label$="[[groupAriaLabel]]">
- <slot></slot>
- </cr-radio-group>
- </template>
- <script src="settings_radio_group.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_radio_group.js b/chromium/chrome/browser/resources/settings/controls/settings_radio_group.js
deleted file mode 100644
index 542e6df91c7..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_radio_group.js
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * `settings-radio-group` wraps cr-radio-group and set of radio-buttons that
- * control a supplied preference.
- *
- * Example:
- * <settings-radio-group pref="{{prefs.settings.foo}}"
- * label="Foo Options." buttons="{{fooOptionsList}}">
- * </settings-radio-group>
- */
-Polymer({
- is: 'settings-radio-group',
-
- behaviors: [PrefControlBehavior],
-
- properties: {
- groupAriaLabel: String,
-
- selected: String,
- },
-
- hostAttributes: {
- role: 'none',
- },
-
- observers: [
- 'prefChanged_(pref.*)',
- ],
-
- /** @private */
- prefChanged_: function() {
- const pref = /** @type {!chrome.settingsPrivate.PrefObject} */ (this.pref);
- this.selected = Settings.PrefUtil.prefToString(pref);
- },
-
- /** @private */
- onSelectedChanged_: function() {
- if (!this.pref) {
- return;
- }
- this.selected = this.$$('cr-radio-group').selected;
- this.set(
- 'pref.value',
- Settings.PrefUtil.stringToPrefValue(this.selected, this.pref));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_slider.html b/chromium/chrome/browser/resources/settings/controls/settings_slider.html
deleted file mode 100644
index 5c2e7028a8a..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_slider.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_slider/cr_slider.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-slider">
- <template>
- <style>
- :host {
- display: inline-flex;
- }
-
- cr-policy-pref-indicator {
- align-self: center;
- margin-inline-start: var(--settings-controlled-by-spacing);
- }
-
- #labels[disabled] {
- color: var(--paper-grey-400);
- }
-
- @media (prefers-color-scheme: dark) {
- #labels[disabled] {
- color: var(--google-grey-refresh-500);
- }
- }
-
- div.outer {
- align-items: stretch;
- display: flex;
- flex-direction: column;
- margin: 8px 0;
- min-width: 200px;
- }
-
- #labels {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- margin: -4px 16px 0 16px;
- }
-
- #labels > div {
- font-size: 12px;
- }
-
- #label-begin {
- margin-inline-end: 4px;
- }
-
- #label-end {
- margin-inline-start: 4px;
- }
- </style>
- <template is="dom-if" if="[[pref.controlledBy]]" restamp>
- <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
- </template>
- <div class="outer">
- <cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]"
- on-cr-slider-value-changed="onSliderChanged_" max="[[max]]"
- min="[[min]]" on-dragging-changed="onSliderChanged_"
- on-updating-from-key="onSliderChanged_"></cr-slider>
- <div id="labels" disabled$="[[disableSlider_]]">
- <div id="label-begin">[[labelMin]]</div>
- <div id="label-end">[[labelMax]]</div>
- </div>
- </div>
- </template>
- <script src="settings_slider.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_slider.js b/chromium/chrome/browser/resources/settings/controls/settings_slider.js
deleted file mode 100644
index 5c46c6bcc36..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_slider.js
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * settings-slider wraps a cr-slider. It maps the slider's values from a
- * linear UI range to a range of real values. When |value| does not map exactly
- * to a tick mark, it interpolates to the nearest tick.
- */
-Polymer({
- is: 'settings-slider',
-
- behaviors: [CrPolicyPrefBehavior],
-
- properties: {
- /** @type {!chrome.settingsPrivate.PrefObject} */
- pref: Object,
-
- /**
- * Values corresponding to each tick.
- * @type {!Array<cr_slider.SliderTick>|!Array<number>}
- */
- ticks: {
- type: Array,
- value: () => [],
- },
-
- /**
- * A scale factor used to support fractional pref values. This is not
- * compatible with |ticks|, i.e. if |scale| is not 1 then |ticks| must be
- * empty.
- */
- scale: {
- type: Number,
- value: 1,
- },
-
- min: Number,
-
- max: Number,
-
- labelMin: String,
-
- labelMax: String,
-
- disabled: Boolean,
-
- showMarkers: Boolean,
-
- /** @private */
- disableSlider_: {
- computed: 'computeDisableSlider_(pref.*, disabled, ticks.*)',
- type: Boolean,
- },
-
- updateValueInstantly: {
- type: Boolean,
- value: true,
- observer: 'onSliderChanged_',
- },
-
- loaded_: Boolean,
- },
-
- observers: [
- 'valueChanged_(pref.*, ticks.*, loaded_)',
- ],
-
- attached: function() {
- this.loaded_ = true;
- },
-
- /**
- * @param {number|cr_slider.SliderTick} tick
- * @return {number|undefined}
- */
- getTickValue_: function(tick) {
- return typeof tick == 'object' ? tick.value : tick;
- },
-
- /**
- * @param {number} index
- * @return {number|undefined}
- * @private
- */
- getTickValueAtIndex_: function(index) {
- return this.getTickValue_(this.ticks[index]);
- },
-
- /**
- * Sets the |pref.value| property to the value corresponding to the knob
- * position after a user action.
- * @private
- */
- onSliderChanged_: function() {
- if (!this.loaded_) {
- return;
- }
-
- if (this.$.slider.dragging && !this.updateValueInstantly) {
- return;
- }
-
- const sliderValue = this.$.slider.value;
-
- let newValue;
- if (this.ticks && this.ticks.length > 0) {
- newValue = this.getTickValueAtIndex_(sliderValue);
- } else {
- newValue = sliderValue / this.scale;
- }
-
- this.set('pref.value', newValue);
- },
-
- /** @private */
- computeDisableSlider_: function() {
- return this.disabled || this.isPrefEnforced();
- },
-
- /**
- * Updates the knob position when |pref.value| changes. If the knob is still
- * being dragged, this instead forces |pref.value| back to the current
- * position.
- * @private
- */
- valueChanged_: function() {
- if (this.pref == undefined || !this.loaded_ || this.$.slider.dragging ||
- this.$.slider.updatingFromKey) {
- return;
- }
-
- // First update the slider settings if |ticks| was set.
- const numTicks = this.ticks.length;
- if (numTicks == 1) {
- this.$.slider.disabled = true;
- return;
- }
-
- const prefValue = /** @type {number} */ (this.pref.value);
-
- // The preference and slider values are continuous when |ticks| is empty.
- if (numTicks == 0) {
- this.$.slider.value = prefValue * this.scale;
- return;
- }
-
- assert(this.scale == 1);
- // Limit the number of ticks to 10 to keep the slider from looking too busy.
- const MAX_TICKS = 10;
- this.$.slider.markerCount =
- (this.showMarkers || numTicks <= MAX_TICKS) ? numTicks : 0;
-
- // Convert from the public |value| to the slider index (where the knob
- // should be positioned on the slider).
- const index =
- this.ticks.map(tick => Math.abs(this.getTickValue_(tick) - prefValue))
- .reduce(
- (acc, diff, index) => diff < acc.diff ? {index, diff} : acc,
- {index: -1, diff: Number.MAX_VALUE})
- .index;
- assert(index != -1);
- if (this.$.slider.value != index) {
- this.$.slider.value = index;
- }
- const tickValue = this.getTickValueAtIndex_(index);
- if (this.pref.value != tickValue) {
- this.set('pref.value', tickValue);
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_textarea.html b/chromium/chrome/browser/resources/settings/controls/settings_textarea.html
deleted file mode 100644
index 0d6cf960e5d..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_textarea.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input_style_css.html">
-
-<dom-module id="settings-textarea">
- <template>
- <style include="cr-hidden-style cr-input-style cr-shared-style">
- textarea {
- display: block;
- resize: none;
- }
-
- #input-container {
- background-color: var(--cr-input-background-color);
- }
-
- #underline {
- position: static;
- }
- </style>
- <div id="label" class="cr-form-field-label" hidden="[[!label]]">
- [[label]]
- </div>
- <div id="input-container">
- <!-- The textarea is limited to |rows| height. If the content exceeds the
- bounds, it scrolls by default. No space or comments are allowed
- before the closing tag. -->
- <textarea id="input" autofocus="[[autofocus]]" rows="[[rows]]"
- value="{{value::input}}" aria-label$="[[label]]"
- on-focus="onInputFocusChange_" on-blur="onInputFocusChange_"
- on-change="onInputChange_" disabled="[[disabled]]"></textarea>
- <div id="underline"></div>
- </div>
- </template>
- <script src="settings_textarea.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_textarea.js b/chromium/chrome/browser/resources/settings/controls/settings_textarea.js
deleted file mode 100644
index a66898fccb3..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_textarea.js
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-textarea' is a component similar to native textarea,
- * and inherits styling from cr-input.
- */
-Polymer({
- is: 'settings-textarea',
-
- properties: {
- /**
- * Whether the text area should automatically get focus when the page
- * loads.
- */
- autofocus: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
-
- /**
- * Whether the text area is disabled. When disabled, the text area loses
- * focus and is not reachable by tabbing.
- */
- disabled: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- observer: 'onDisabledChanged_'
- },
-
- /** Number of rows (lines) of the text area. */
- rows: {
- type: Number,
- value: 3,
- reflectToAttribute: true,
- },
-
- /** Caption of the text area. */
- label: {
- type: String,
- value: '',
- },
-
- /**
- * Text inside the text area. If the text exceeds the bounds of the text
- * area, i.e. if it has more than |rows| lines, a scrollbar is shown by
- * default.
- */
- value: {
- type: String,
- value: '',
- notify: true,
- },
- },
-
- /**
- * 'change' event fires when <input> value changes and user presses 'Enter'.
- * This function helps propagate it to host since change events don't
- * propagate across Shadow DOM boundary by default.
- * @param {!Event} e
- * @private
- */
- onInputChange_: function(e) {
- this.fire('change', {sourceEvent: e});
- },
-
- /**@private */
- onInputFocusChange_: function() {
- // focused_ is used instead of :focus-within, so focus on elements within
- // the suffix slot does not trigger a change in input styles.
- if (this.shadowRoot.activeElement == this.$.input) {
- this.setAttribute('focused_', '');
- } else {
- this.removeAttribute('focused_');
- }
- },
-
- /**@private */
- onDisabledChanged_: function() {
- this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html b/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html
deleted file mode 100644
index 91963de86f8..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html
+++ /dev/null
@@ -1,74 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="settings_boolean_control_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-toggle-button">
- <template>
- <style include="settings-shared iron-flex">
- :host {
- @apply --cr-section;
- }
-
- :host(.first),
- :host(.continuation) {
- border-top: none;
- }
-
- :host([elide-label]),
- :host([elide-label]) #outerRow,
- :host([elide-label]) #outerRow > div.flex {
- min-width: 0;
- }
-
- :host([elide-label]) .label {
- @apply --cr-text-elide;
- }
-
- #outerRow {
- align-items: center;
- display: flex;
- min-height: var(--settings-row-two-line-min-height);
- width: 100%;
- }
-
- #outerRow[noSubLabel] {
- min-height: var(--settings-row-min-height);
- }
-
- #labelWrapper {
- padding: 12px 0;
- }
-
- #labelWrapper,
- ::slotted([slot='more-actions']) {
- margin-inline-end: var(--settings-control-label-spacing) !important;
- }
-
- cr-policy-pref-indicator {
- margin-inline-end: var(--settings-controlled-by-spacing);
- }
- </style>
- <div id="outerRow" noSubLabel$="[[!subLabel]]">
- <div class="flex" id="labelWrapper" hidden$="[[!label]]">
- <div class="label" aria-hidden="true">[[label]]</div>
- <div class="secondary label">[[subLabel]]</div>
- </div>
- <slot name="more-actions"></slot>
- <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]">
- <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]">
- </cr-policy-pref-indicator>
- </template>
- <cr-toggle id="control" checked="{{checked}}"
- on-change="onChange_"
- aria-label$="[[getAriaLabel_(label, ariaLabel)]]"
- disabled="[[controlDisabled(disabled, pref)]]">
- </cr-toggle>
- </div>
- </template>
- <script src="settings_toggle_button.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.js b/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.js
deleted file mode 100644
index e7de4256027..00000000000
--- a/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * `settings-toggle-button` is a toggle that controls a supplied preference.
- */
-Polymer({
- is: 'settings-toggle-button',
-
- behaviors: [SettingsBooleanControlBehavior],
-
- properties: {
- ariaLabel: {
- type: String,
- reflectToAttribute: false, // Handled by #control.
- observer: 'onAriaLabelSet_',
- value: '',
- },
-
- elideLabel: {
- type: Boolean,
- reflectToAttribute: true,
- },
- },
-
- listeners: {
- 'click': 'onHostTap_',
- },
-
- observers: [
- 'onDisableOrPrefChange_(disabled, pref.*)',
- ],
-
- /** @override */
- focus: function() {
- this.$.control.focus();
- },
-
- /**
- * Removes the aria-label attribute if it's added by $i18n{...}.
- * @private
- */
- onAriaLabelSet_: function() {
- if (this.hasAttribute('aria-label')) {
- const ariaLabel = this.ariaLabel;
- this.removeAttribute('aria-label');
- this.ariaLabel = ariaLabel;
- }
- },
-
- /**
- * @return {string}
- * @private
- */
- getAriaLabel_: function() {
- return this.label || this.ariaLabel;
- },
-
- /** @private */
- onDisableOrPrefChange_: function() {
- if (this.controlDisabled()) {
- this.removeAttribute('actionable');
- } else {
- this.setAttribute('actionable', '');
- }
- },
-
- /**
- * Handles non cr-toggle button clicks (cr-toggle handles its own click events
- * which don't bubble).
- * @param {!Event} e
- * @private
- */
- onHostTap_: function(e) {
- e.stopPropagation();
- if (this.controlDisabled()) {
- return;
- }
-
- this.checked = !this.checked;
- this.notifyChangedByUserInteraction();
- this.fire('change');
- },
-
- /**
- * @param {!CustomEvent<boolean>} e
- * @private
- */
- onChange_: function(e) {
- this.checked = e.detail;
- this.notifyChangedByUserInteraction();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.html
deleted file mode 100644
index 3404b910144..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="crostini_browser_proxy.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
deleted file mode 100644
index f9b965ea621..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{path: string,
- * pathDisplayText: string}}
- */
-let CrostiniSharedPath;
-
-/**
- * @typedef {{label: string,
- * guid: string,
- * shared: boolean}}
- */
-let CrostiniSharedUsbDevice;
-
-/**
- * @fileoverview A helper object used by the "Linux Apps" (Crostini) section
- * to install and uninstall Crostini.
- */
-cr.define('settings', function() {
- /** @interface */
- class CrostiniBrowserProxy {
- /* Show crostini installer. */
- requestCrostiniInstallerView() {}
-
- /* Show remove crostini dialog. */
- requestRemoveCrostini() {}
-
- /**
- * @param {!Array<string>} paths Paths to sanitze.
- * @return {!Promise<!Array<string>>} Text to display in UI.
- */
- getCrostiniSharedPathsDisplayText(paths) {}
-
- /**
- * @return {!Promise<!Array<CrostiniSharedUsbDevice>>}
- */
- getCrostiniSharedUsbDevices() {}
-
- /**
- * @param {string} guid Unique device identifier.
- * @param {boolean} shared Whether device is currently shared with Crostini.
- */
- setCrostiniUsbDeviceShared(guid, shared) {}
-
- /**
- * @param {string} vmName VM to stop sharing path with.
- * @param {string} path Path to stop sharing.
- */
- removeCrostiniSharedPath(vmName, path) {}
-
- /**
- * Request chrome send a crostini-installer-status-changed event with the
- * current installer status
- */
- requestCrostiniInstallerStatus() {}
-
- /**
- * Request chrome send a crostini-export-import-operation-status-changed
- * event with the current operation status
- */
- requestCrostiniExportImportOperationStatus() {}
-
- /**
- * Export crostini container.
- */
- exportCrostiniContainer() {}
-
- /**
- * Import crostini container.
- */
- importCrostiniContainer() {}
- }
-
- /** @implements {settings.CrostiniBrowserProxy} */
- class CrostiniBrowserProxyImpl {
- /** @override */
- requestCrostiniInstallerView() {
- chrome.send('requestCrostiniInstallerView');
- }
-
- /** @override */
- requestRemoveCrostini() {
- chrome.send('requestRemoveCrostini');
- }
-
- /** @override */
- getCrostiniSharedPathsDisplayText(paths) {
- return cr.sendWithPromise('getCrostiniSharedPathsDisplayText', paths);
- }
-
- /** @override */
- getCrostiniSharedUsbDevices() {
- return cr.sendWithPromise('getCrostiniSharedUsbDevices');
- }
-
- /** @override */
- setCrostiniUsbDeviceShared(guid, shared) {
- return chrome.send('setCrostiniUsbDeviceShared', [guid, shared]);
- }
-
- /** @override */
- removeCrostiniSharedPath(vmName, path) {
- chrome.send('removeCrostiniSharedPath', [vmName, path]);
- }
-
- /** @override */
- requestCrostiniInstallerStatus() {
- chrome.send('requestCrostiniInstallerStatus');
- }
-
- /** @override */
- requestCrostiniExportImportOperationStatus() {
- chrome.send('requestCrostiniExportImportOperationStatus');
- }
-
- /** @override */
- exportCrostiniContainer() {
- chrome.send('exportCrostiniContainer');
- }
-
- /** @override */
- importCrostiniContainer() {
- chrome.send('importCrostiniContainer');
- }
- }
-
- // The singleton instance_ can be replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(CrostiniBrowserProxyImpl);
-
- return {
- CrostiniBrowserProxy: CrostiniBrowserProxy,
- CrostiniBrowserProxyImpl: CrostiniBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.html
deleted file mode 100644
index 43c4d73d8da..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="crostini_browser_proxy.html">
-<link rel="import" href="crostini_import_confirmation_dialog.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-crostini-export-import">
- <template>
- <style include="settings-shared"></style>
- <div class="list-frame vertical-list">
- <div id="export" class="list-item">
- <div id="exportCrostiniLabel" class="start secondary">
- $i18n{crostiniExportLabel}
- </div>
- <cr-button on-click="onExportClick_" disabled="[[!enableButtons_]]"
- aria-labelledby="exportCrostiniLabel">
- $i18n{crostiniExport}
- </cr-button>
- </div>
- <div id="import" class="list-item">
- <div id="importCrostiniLabel" class="start secondary">
- $i18n{crostiniImportLabel}
- </div>
- <cr-button on-click="onImportClick_" disabled="[[!enableButtons_]]"
- aria-labelledby="importCrostiniLabel">
- $i18n{crostiniImport}
- </cr-button>
- </div>
- </div>
- <template is="dom-if" if="[[showImportConfirmationDialog_]]" restamp>
- <settings-crostini-import-confirmation-dialog
- on-close="onImportConfirmationDialogClose_">
- </settings-crostini-import-confirmation-dialog>
- </template>
- </template>
- <script src="crostini_export_import.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.js
deleted file mode 100644
index 1cb7d34abc2..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_export_import.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'crostini-export-import' is the settings backup and restore subpage for
- * Crostini.
- */
-
-Polymer({
- is: 'settings-crostini-export-import',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /** @private */
- showImportConfirmationDialog_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Whether the export import buttons should be enabled. Initially false
- * until status has been confirmed.
- * @private {boolean}
- */
- enableButtons_: {
- type: Boolean,
- value: false,
- },
- },
-
- attached: function() {
- this.addWebUIListener(
- 'crostini-export-import-operation-status-changed', inProgress => {
- this.enableButtons_ = !inProgress;
- });
- settings.CrostiniBrowserProxyImpl.getInstance()
- .requestCrostiniExportImportOperationStatus();
- },
-
- /** @private */
- onExportClick_: function() {
- settings.CrostiniBrowserProxyImpl.getInstance().exportCrostiniContainer();
- },
-
- /** @private */
- onImportClick_: function() {
- this.showImportConfirmationDialog_ = true;
- },
-
- /** @private */
- onImportConfirmationDialogClose_: function() {
- this.showImportConfirmationDialog_ = false;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.html
deleted file mode 100644
index 4351e857f57..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="crostini_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-crostini-import-confirmation-dialog">
- <template>
- <style include="settings-shared"></style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{crostiniImportConfirmationDialogTitle}</div>
- <div slot="body">$i18n{crostiniImportConfirmationDialogMessage}</div>
- <div slot="button-container">
- <cr-button id="cancel" class="cancel-button"
- on-click="onCancelTap_">$i18n{cancel}</cr-button>
- <cr-button id="continue" class="action-button"
- on-click="onContinueTap_">
- $i18n{crostiniImportConfirmationDialogConfirmationButton}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="crostini_import_confirmation_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.js
deleted file mode 100644
index acfbdc9ac9a..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_import_confirmation_dialog.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-crostini-import-confirmation-dialog' is a component
- * warning the user that importing a container overrides the existing container.
- * By clicking 'Continue', the user agrees to start the import.
- */
-Polymer({
- is: 'settings-crostini-import-confirmation-dialog',
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onContinueTap_: function() {
- settings.CrostiniBrowserProxyImpl.getInstance().importCrostiniContainer();
- this.$.dialog.close();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_page.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_page.html
deleted file mode 100644
index d241d5baf3a..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_page.html
+++ /dev/null
@@ -1,95 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="crostini_browser_proxy.html">
-<link rel="import" href="crostini_export_import.html">
-<link rel="import" href="crostini_shared_paths.html">
-<link rel="import" href="crostini_shared_usb_devices.html">
-<link rel="import" href="crostini_subpage.html">
-
-<dom-module id="settings-crostini-page">
- <template>
- <style include="settings-shared"></style>
-
- <settings-animated-pages id="pages" section="crostini"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <div id="crostini" class="settings-box two-line first"
- actionable$="[[prefs.crostini.enabled.value]]"
- on-click="onSubpageTap_">
- <div class="start">
- $i18n{crostiniPageLabel}
- <div class="secondary" id="secondaryText"
- inner-h-t-m-l="[[i18nAdvanced('crostiniSubtext')]]">
- </div>
- </div>
- <template is="dom-if" if="[[!allowCrostini]]" restamp>
- <cr-policy-indicator indicator-type="userPolicy">
- </cr-policy-indicator>
- </template>
- <template is="dom-if" if="[[prefs.crostini.enabled.value]]">
- <cr-icon-button class="subpage-arrow"
- aria-label="$i18n{crostiniPageTitle}"
- aria-describedby="secondaryText"></cr-icon-button>
- </template>
- <template is="dom-if" if="[[!prefs.crostini.enabled.value]]">
- <div class="separator"></div>
- <cr-button id="enable"
- disabled$="[[disableCrostiniInstall_]]"
- on-click="onEnableTap_"
- aria-label="$i18n{crostiniPageTitle}"
- aria-describedby="secondaryText">
- $i18n{crostiniEnable}
- </cr-button>
- </template>
- </div>
- </div>
-
- <template is="dom-if" route-path="/crostini/details">
- <settings-subpage
- associated-control="[[$$('#crostini')]]"
- page-title="$i18n{crostiniPageLabel}">
- <settings-crostini-subpage prefs="{{prefs}}">
- </settings-crostini-subpage>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/crostini/exportImport">
- <settings-subpage
- associated-control="[[$$('#crostini')]]"
- page-title="$i18n{crostiniExportImportTitle}">
- <settings-crostini-export-import prefs="{{prefs}}">
- </settings-crostini-export-import>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/crostini/sharedPaths">
- <settings-subpage
- associated-control="[[$$('#crostini')]]"
- page-title="$i18n{crostiniSharedPaths}">
- <settings-crostini-shared-paths prefs="{{prefs}}">
- </settings-crostini-shared-paths>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/crostini/sharedUsbDevices">
- <settings-subpage
- associated-control="[[$$('#crostini')]]"
- page-title="$i18n{crostiniSharedUsbDevicesLabel}">
- <settings-crostini-shared-usb-devices prefs="{{prefs}}">
- </settings-crostini-shared-usb-devices>
- </settings-subpage>
- </template>
- </settings-animated-pages>
-
- </template>
- <script src="crostini_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_page.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_page.js
deleted file mode 100644
index 7a74d5b5fbd..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_page.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'crostini-page' is the settings page for enabling Crostini.
- * Crostini Containers run Linux inside a Termina VM, allowing
- * the user to run Linux apps on their Chromebook.
- */
-
-Polymer({
- is: 'settings-crostini-page',
-
- behaviors: [I18nBehavior, PrefsBehavior, WebUIListenerBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.CROSTINI_DETAILS) {
- map.set(
- settings.routes.CROSTINI_DETAILS.path,
- '#crostini .subpage-arrow');
- }
- if (settings.routes.CROSTINI_EXPORT_IMPORT) {
- map.set(
- settings.routes.CROSTINI_EXPORT_IMPORT.path,
- '#crostini .subpage-arrow');
- }
- if (settings.routes.CROSTINI_SHARED_PATHS) {
- map.set(
- settings.routes.CROSTINI_SHARED_PATHS.path,
- '#crostini .subpage-arrow');
- }
- if (settings.routes.CROSTINI_SHARED_USB_DEVICES) {
- map.set(
- settings.routes.CROSTINI_SHARED_USB_DEVICES.path,
- '#crostini .subpage-arrow');
- }
- return map;
- },
- },
-
- /**
- * Whether the install option should be enabled.
- * @private {boolean}
- */
- disableCrostiniInstall_: {
- type: Boolean,
- },
- },
-
- attached: function() {
- if (!loadTimeData.getBoolean('allowCrostini')) {
- this.disableCrostiniInstall_ = true;
- return;
- }
- this.addWebUIListener(
- 'crostini-installer-status-changed', (installerShowing) => {
- this.disableCrostiniInstall_ = installerShowing;
- });
- settings.CrostiniBrowserProxyImpl.getInstance()
- .requestCrostiniInstallerStatus();
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onEnableTap_: function(event) {
- settings.CrostiniBrowserProxyImpl.getInstance()
- .requestCrostiniInstallerView();
- event.stopPropagation();
- },
-
- /** @private */
- onSubpageTap_: function(event) {
- // We do not open the subpage if the click was on a link.
- if (event.target && event.target.tagName == 'A') {
- event.stopPropagation();
- return;
- }
-
- if (this.getPref('crostini.enabled.value')) {
- settings.navigateTo(settings.routes.CROSTINI_DETAILS);
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.html
deleted file mode 100644
index 62fd01cdd54..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="crostini_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-crostini-shared-paths">
- <template>
- <style include="settings-shared"></style>
- <div class="settings-box first">
- <div>
- $i18n{crostiniSharedPathsInstructionsLocate}
- $i18n{crostiniSharedPathsInstructionsAdd}
- <span id="crostiniInstructionsRemove"
- hidden="[[!sharedPaths_.length]]">
- $i18n{crostiniSharedPathsInstructionsRemove}
- </span>
- </div>
- </div>
- <div class="settings-box continuation">
- <h2 class="start">$i18n{crostiniSharedPathsListHeading}</h2>
- </div>
- <div class="list-frame vertical-list">
- <template is="dom-repeat" items="[[sharedPaths_]]">
- <div class="list-item">
- <div class="start">[[item.pathDisplayText]]</div>
- <cr-icon-button class="icon-clear" on-click="onRemoveSharedPathTap_"
- title="$i18n{crostiniSharedPathsRemoveSharing}"></cr-icon-button>
- </div>
- </template>
- </div>
- </template>
- <script src="crostini_shared_paths.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js
deleted file mode 100644
index 0072edd8a4a..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'crostini-shared-paths' is the settings shared paths subpage for Crostini.
- */
-
-(function() {
-
-/**
- * The default crostini VM is named 'termina'.
- * https://cs.chromium.org/chromium/src/chrome/browser/chromeos/crostini/crostini_util.h?q=kCrostiniDefaultVmName&dr=CSs
- * @type {string}
- */
-const DEFAULT_CROSTINI_VM = 'termina';
-
-Polymer({
- is: 'settings-crostini-shared-paths',
-
- behaviors: [PrefsBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * The shared path string suitable for display in the UI.
- * @private {Array<!CrostiniSharedPath>}
- */
- sharedPaths_: Array,
- },
-
- observers: [
- 'onCrostiniSharedPathsChanged_(prefs.guest_os.paths_shared_to_vms.value)'
- ],
-
- /**
- * @param {!Object<!Array<string>>} paths
- * @private
- */
- onCrostiniSharedPathsChanged_: function(paths) {
- const vmPaths = [];
- for (const path in paths) {
- const vms = paths[path];
- if (vms.includes(DEFAULT_CROSTINI_VM)) {
- vmPaths.push(path);
- }
- }
- settings.CrostiniBrowserProxyImpl.getInstance()
- .getCrostiniSharedPathsDisplayText(vmPaths)
- .then(text => {
- this.sharedPaths_ = vmPaths.map(
- (path, i) => ({path: path, pathDisplayText: text[i]}));
- });
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onRemoveSharedPathTap_: function(event) {
- settings.CrostiniBrowserProxyImpl.getInstance().removeCrostiniSharedPath(
- DEFAULT_CROSTINI_VM, event.model.item.path);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.html
deleted file mode 100644
index e1490cf5dfb..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-crostini-shared-usb-devices">
- <template>
- <style include="settings-shared">
- .toggle-container {
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- justify-content: space-between;
- }
- </style>
- <div class="settings-box first">
- <div class="settings-box-text">
- $i18n{crostiniSharedUsbDevicesDescription}
- <div class="secondary" id="secondaryText">
- $i18n{crostiniSharedUsbDevicesExtraDescription}
- </div>
- </div>
- </div>
- <div class="settings-box secondary continuation"
- hidden="[[sharedUsbDevices_.length]]" >
- $i18n{crostiniSharedUsbDevicesListEmptyMessage}
- </div>
- <div class="list-frame vertical-list"
- hidden="[[!sharedUsbDevices_.length]]">
- <template is="dom-repeat" items="[[sharedUsbDevices_]]">
- <div class="list-item toggle-container">
- <div class="label">[[item.label]]</div>
- <cr-toggle class="toggle" checked="[[item.shared]]"
- on-change="onDeviceSharedChange_"
- aria-label$="[[item.label]]"></cr-toggle>
- </div>
- </div>
- </template>
- </template>
- <script src="crostini_shared_usb_devices.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.js
deleted file mode 100644
index 8d4db11bbb0..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_shared_usb_devices.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'crostini-shared-usb-devices' is the settings shared usb devices subpage for
- * Crostini.
- */
-
-Polymer({
- is: 'settings-crostini-shared-usb-devices',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /**
- * The USB Devices available for connection to a VM.
- * @private {Array<!CrostiniSharedUsbDevice>}
- */
- sharedUsbDevices_: Array,
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'crostini-shared-usb-devices-changed',
- this.onCrostiniSharedUsbDevicesChanged_.bind(this));
- settings.CrostiniBrowserProxyImpl.getInstance()
- .getCrostiniSharedUsbDevices()
- .then(this.onCrostiniSharedUsbDevicesChanged_.bind(this));
- },
-
- /**
- * @param {!Array<CrostiniSharedUsbDevice>} devices
- * @private
- */
- onCrostiniSharedUsbDevicesChanged_: function(devices) {
- this.sharedUsbDevices_ = devices;
- },
-
- /**
- * @param {!CustomEvent<!CrostiniSharedUsbDevice>} event
- * @private
- */
- onDeviceSharedChange_: function(event) {
- const deviceInfo = event.model.item;
- settings.CrostiniBrowserProxyImpl.getInstance().setCrostiniUsbDeviceShared(
- deviceInfo.guid, event.target.checked);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.html b/chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.html
deleted file mode 100644
index c77f6161ec1..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="crostini_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-crostini-subpage">
- <template>
- <style include="settings-shared"></style>
- <cr-link-row
- id="crostini-shared-paths"
- label="$i18n{crostiniSharedPaths}"
- on-click="onSharedPathsClick_">
- </cr-link-row>
- <cr-link-row
- class="hr"
- label="$i18n{crostiniSharedUsbDevicesLabel}"
- id="crostini-shared-usb-devices"
- on-click="onSharedUsbDevicesClick_">
- </cr-link-row>
- <template is="dom-if" if="[[showCrostiniExportImport_]]">
- <cr-link-row
- class="hr"
- label="$i18n{crostiniExportImportTitle}"
- id="crostini-export-import"
- on-click="onExportImportClick_">
- </cr-link-row>
- </template>
- <template is="dom-if" if="[[!hideCrostiniUninstall_]]">
- <div id="remove" class="settings-box">
- <div id="removeCrostiniLabel" class="start">$i18n{crostiniRemove}</div>
- <cr-button on-click="onRemoveClick_"
- aria-labelledby="removeCrostiniLabel">
- $i18n{crostiniRemoveButton}
- </cr-button>
- </div>
- </template>
- </template>
- <script src="crostini_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.js b/chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.js
deleted file mode 100644
index 66d8ca13fce..00000000000
--- a/chromium/chrome/browser/resources/settings/crostini_page/crostini_subpage.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'crostini-subpage' is the settings subpage for managing Crostini.
- */
-
-Polymer({
- is: 'settings-crostini-subpage',
-
- behaviors: [PrefsBehavior, WebUIListenerBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Whether export / import UI should be displayed.
- * @private {boolean}
- */
- showCrostiniExportImport_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('showCrostiniExportImport');
- },
- },
-
- /**
- * Whether the uninstall options should be displayed.
- * @private {boolean}
- */
- hideCrostiniUninstall_: {
- type: Boolean,
- },
- },
-
- observers: ['onCrostiniEnabledChanged_(prefs.crostini.enabled.value)'],
-
- attached: function() {
- const callback = (status) => {
- this.hideCrostiniUninstall_ = status;
- };
- this.addWebUIListener('crostini-installer-status-changed', callback);
- settings.CrostiniBrowserProxyImpl.getInstance()
- .requestCrostiniInstallerStatus();
- },
-
- /** @private */
- onCrostiniEnabledChanged_: function(enabled) {
- if (!enabled &&
- settings.getCurrentRoute() == settings.routes.CROSTINI_DETAILS) {
- settings.navigateToPreviousRoute();
- }
- },
-
- /** @private */
- onExportImportClick_: function() {
- settings.navigateTo(settings.routes.CROSTINI_EXPORT_IMPORT);
- },
-
- /**
- * Shows a confirmation dialog when removing crostini.
- * @private
- */
- onRemoveClick_: function() {
- settings.CrostiniBrowserProxyImpl.getInstance().requestRemoveCrostini();
- },
-
- /** @private */
- onSharedPathsClick_: function() {
- settings.navigateTo(settings.routes.CROSTINI_SHARED_PATHS);
- },
-
- /** @private */
- onSharedUsbDevicesClick_: function() {
- settings.navigateTo(settings.routes.CROSTINI_SHARED_USB_DEVICES);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html
deleted file mode 100644
index 1575327101d..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="date_time_types.html">
-<link rel="import" href="timezone_selector.html">
-<link rel="import" href="timezone_subpage.html">
-
-<dom-module id="settings-date-time-page">
- <template>
- <style include="settings-shared">
- #timeZoneAutoDetect {
- padding: 0;
- width: 100%;
- }
- #timeZoneButton {
- display: flex;
- flex-direction: column;
- justify-content: center;
- width: 100%;
- }
- </style>
- <settings-animated-pages id="pages" section="dateTime"
- focus-config="[[focusConfig_]]">
- <div id="main" route-path="default">
- <template is="dom-if"
- if="[[!prefs.cros.flags.fine_grained_time_zone_detection_enabled.value]]"
- restamp>
- <div class="settings-box first">
- <settings-toggle-button id="timeZoneAutoDetect"
- label="$i18n{timeZoneGeolocation}"
- pref="{{prefs.generated.resolve_timezone_by_geolocation_on_off}}">
- </settings-toggle-button>
- </div>
- </template>
- <template is="dom-if"
- if="[[prefs.cros.flags.fine_grained_time_zone_detection_enabled.value]]"
- restamp>
- <cr-link-row id="timeZoneSettingsTrigger" class="hr"
- on-click="onTimeZoneSettings_" label="$i18n{timeZoneButton}"
- sub-label="[[timeZoneSettingSubLabel_]]">
- <div class="flex"></div>
- <cr-policy-pref-indicator
- pref="[[prefs.generated.resolve_timezone_by_geolocation_on_off]]">
- </cr-policy-pref-indicator>
- </cr-link-row>
- </template>
- <div class="settings-box continuation embedded"
- hidden="[[prefs.cros.flags.fine_grained_time_zone_detection_enabled.value]]">
- <timezone-selector prefs="{{prefs}}"
- active-time-zone-display-name="{{activeTimeZoneDisplayName}}">
- </timezone-selector>
- </div>
- <settings-toggle-button
- pref="{{prefs.settings.clock.use_24hour_clock}}"
- label="$i18n{use24HourClock}">
- </settings-toggle-button>
- <cr-link-row class="hr" id="setDateTime"
- on-click="onSetDateTimeTap_" hidden$="[[!canSetDateTime_]]"
- label="$i18n{setDateTime}">
- <template is="dom-if" if="[[displayManagedByParentIcon_]]">
- <cr-policy-indicator indicator-type="parent"></cr-policy-indicator>
- </template>
- </cr-link-row>
- </div>
- <template is="dom-if" route-path="/dateTime/timeZone">
- <settings-subpage data-route="DATETIME_TIMEZONE_SUBPAGE"
- associated-control="[[$$('#timeZoneSettingsTrigger')]]"
- page-title="$i18n{timeZoneSubpageTitle}"
- learn-more-url="$i18n{timeZoneSettingsLearnMoreURL}">
- <timezone-subpage id="timezoneSubpage" prefs="{{prefs}}"
- active-time-zone-display-name="{{activeTimeZoneDisplayName}}">
- </timezone-subpage>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- </template>
- <script src="date_time_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js
deleted file mode 100644
index f5f8a8f3761..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-date-time-page' is the settings page containing date and time
- * settings.
- */
-
-Polymer({
- is: 'settings-date-time-page',
-
- behaviors: [I18nBehavior, PrefsBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * Whether date and time are settable. Normally the date and time are forced
- * by network time, so default to false to initially hide the button.
- * @private
- */
- canSetDateTime_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * This is used to get current time zone display name from
- * <timezone-selector> via bi-directional binding.
- */
- activeTimeZoneDisplayName: {
- type: String,
- value: loadTimeData.getString('timeZoneName'),
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.DATETIME_TIMEZONE_SUBPAGE) {
- map.set(
- settings.routes.DATETIME_TIMEZONE_SUBPAGE.path,
- '#timeZoneSettingsTrigger');
- }
- return map;
- },
- },
-
- /** @private */
- timeZoneSettingSubLabel_: {
- type: String,
- computed: `computeTimeZoneSettingSubLabel_(
- activeTimeZoneDisplayName,
- prefs.generated.resolve_timezone_by_geolocation_on_off.value,
- prefs.generated.resolve_timezone_by_geolocation_method_short.value)`
- },
-
- /** @private */
- isChild_: {type: Boolean, value: loadTimeData.getBoolean('isChild')},
-
- /**
- * Whether the icon informing that this action is managed by a parent is
- * displayed.
- * @private
- */
- displayManagedByParentIcon_: {
- type: Boolean,
- value: loadTimeData.getBoolean('isChild') &&
- loadTimeData.getBoolean('timeActionsProtectedForChild')
- },
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'can-set-date-time-changed', this.onCanSetDateTimeChanged_.bind(this));
- this.addWebUIListener(
- 'access-code-validation-complete',
- this.openTimeZoneSubpage_.bind(this));
-
- chrome.send('dateTimePageReady');
- },
-
- /**
- * @param {boolean} canSetDateTime Whether date and time are settable.
- * @private
- */
- onCanSetDateTimeChanged_: function(canSetDateTime) {
- this.canSetDateTime_ = canSetDateTime;
- },
-
- /** @private */
- onSetDateTimeTap_: function() {
- chrome.send('showSetDateTimeUI');
- },
-
- /**
- * @return {string}
- * @private
- */
- computeTimeZoneSettingSubLabel_: function() {
- if (!this.getPref('generated.resolve_timezone_by_geolocation_on_off')
- .value) {
- return this.activeTimeZoneDisplayName;
- }
- const method = /** @type {number} */ (
- this.getPref('generated.resolve_timezone_by_geolocation_method_short')
- .value);
- const id = [
- 'setTimeZoneAutomaticallyDisabled',
- 'setTimeZoneAutomaticallyIpOnlyDefault',
- 'setTimeZoneAutomaticallyWithWiFiAccessPointsData',
- 'setTimeZoneAutomaticallyWithAllLocationInfo',
- ][method];
- return id ? this.i18n(id) : '';
- },
-
- /**
- * Called when the timezone row is clicked. Child accounts need parental
- * approval to modify their timezone, this method starts this process on the
- * C++ side, and once it is complete the 'access-code-validation-complete'
- * event is triggered which invokes openTimeZoneSubpage_. For non-child
- * accounts the method is invoked immediately.
- * @private
- */
- onTimeZoneSettings_: function() {
- if (this.isChild_) {
- chrome.send('handleShowParentAccessForTimeZone');
- return;
- }
- this.openTimeZoneSubpage_();
- },
-
- /** @private */
- openTimeZoneSubpage_: function() {
- settings.navigateTo(settings.routes.DATETIME_TIMEZONE_SUBPAGE);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_types.html b/chromium/chrome/browser/resources/settings/date_time_page/date_time_types.html
deleted file mode 100644
index b3ecb802c52..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/date_time_types.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="date_time_types.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_types.js b/chromium/chrome/browser/resources/settings/date_time_page/date_time_types.js
deleted file mode 100644
index 87241d8bd8c..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/date_time_types.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * This defines some types for settings-date-time-page.
- */
-
-cr.exportPath('settings');
-
-/**
- * Describes the effective policy restriction on time zone automatic detection.
- * @enum {number}
- */
-settings.TimeZoneAutoDetectPolicyRestriction = {
- NONE: 0,
- FORCED_ON: 1,
- FORCED_OFF: 2,
-};
-
-/**
- * Describes values of
- * prefs.generated.resolve_timezone_by_geolocation_method_short. Must be kept in
- * sync with TimeZoneResolverManager::TimeZoneResolveMethod enum.
- * @enum {number}
- */
-settings.TimeZoneAutoDetectMethod = {
- DISABLED: 0,
- IP_ONLY: 1,
- SEND_WIFI_ACCESS_POINTS: 2,
- SEND_ALL_LOCATION_INFO: 3
-};
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.html b/chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.html
deleted file mode 100644
index dd6c68405b9..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="date_time_types.html">
-
-<dom-module id="timezone-selector">
- <template>
- <style include="settings-shared">
- settings-dropdown-menu {
- --md-select-width: 400px;
- }
- /* When per_user_timezone_enabled is true, dropdowns are start-aligned. */
- #userTimeZoneSelector,
- #systemTimezoneSelector {
- --settings-dropdown-menu-policy-order: 1;
- }
- </style>
- <template is="dom-if" restamp
- if="[[!prefs.cros.flags.per_user_timezone_enabled.value]]">
- <settings-dropdown-menu pref="{{prefs.cros.system.timezone}}"
- label="$i18n{timeZone}"
- menu-options="[[timeZoneList_]]"
- disabled="[[prefs.generated.resolve_timezone_by_geolocation_on_off.value]]">
- </settings-dropdown-menu>
- </template>
- <template is="dom-if" restamp
- if="[[prefs.cros.flags.per_user_timezone_enabled.value]]">
- <settings-dropdown-menu id="userTimeZoneSelector"
- pref="{{prefs.settings.timezone}}"
- label="$i18n{timeZone}"
- menu-options="[[timeZoneList_]]"
- hidden="[[isUserTimeZoneSelectorHidden_(prefs.settings.timezone,
- prefs.generated.resolve_timezone_by_geolocation_on_off.value)]]">
- </settings-dropdown-menu>
- <settings-dropdown-menu id="systemTimezoneSelector"
- pref="{{prefs.cros.system.timezone}}"
- label="$i18n{timeZone}"
- menu-options="[[timeZoneList_]]"
- hidden="[[!isUserTimeZoneSelectorHidden_(prefs.settings.timezone,
- prefs.generated.resolve_timezone_by_geolocation_on_off.value)]]"
- disabled>
- </settings-dropdown-menu>
- </template>
- </template>
- <script src="timezone_selector.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.js b/chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.js
deleted file mode 100644
index 2ee33b7f61e..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/timezone_selector.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'timezone-selector' is the time zone selector dropdown.
- */
-(function() {
-'use strict';
-
-Polymer({
- is: 'timezone-selector',
-
- behaviors: [I18nBehavior, PrefsBehavior],
-
- properties: {
- /**
- * This stores active time zone display name to be used in other UI
- * via bi-directional binding.
- */
- activeTimeZoneDisplayName: {
- type: String,
- notify: true,
- },
-
- /**
- * Initialized with the current time zone so the menu displays the
- * correct value. The full option list is fetched lazily if necessary by
- * maybeGetTimeZoneList_.
- * @private {!DropdownMenuOptionList}
- */
- timeZoneList_: {
- type: Array,
- value: function() {
- return [{
- name: loadTimeData.getString('timeZoneName'),
- value: loadTimeData.getString('timeZoneID'),
- }];
- },
- },
- },
-
- observers: [
- 'maybeGetTimeZoneListPerUser_(' +
- 'prefs.settings.timezone.value,' +
- 'prefs.generated.resolve_timezone_by_geolocation_on_off.value)',
- 'maybeGetTimeZoneListPerSystem_(' +
- 'prefs.cros.system.timezone.value,' +
- 'prefs.generated.resolve_timezone_by_geolocation_on_off.value)',
- 'updateActiveTimeZoneName_(prefs.cros.system.timezone.value)',
- ],
-
- /** @override */
- attached: function() {
- this.maybeGetTimeZoneList_();
- },
-
- /**
- * True if getTimeZones request was sent to Chrome, but result is not
- * yet received. */
- getTimeZonesRequestSent_: false,
-
- /**
- * Fetches the list of time zones if necessary.
- * @param {boolean=} perUserTimeZoneMode Expected value of per-user time zone.
- * @private
- * @suppress {missingProperties} Property finally never defined on
- */
- maybeGetTimeZoneList_: function(perUserTimeZoneMode) {
- if (typeof(perUserTimeZoneMode) !== 'undefined') {
- /* This method is called as observer. Skip if if current mode does not
- * match expected.
- */
- if (perUserTimeZoneMode !=
- this.getPref('cros.flags.per_user_timezone_enabled').value) {
- return;
- }
- }
-
- // Only fetch the list once.
- if (this.timeZoneList_.length > 1 || !CrSettingsPrefs.isInitialized) {
- return;
- }
-
- if (this.getTimeZonesRequestSent_) {
- return;
- }
-
- // If auto-detect is enabled, we only need the current time zone.
- if (this.getPref('generated.resolve_timezone_by_geolocation_on_off')
- .value) {
- const isPerUserTimezone =
- this.getPref('cros.flags.per_user_timezone_enabled').value;
- if (this.timeZoneList_[0].value ==
- (isPerUserTimezone ? this.getPref('settings.timezone').value :
- this.getPref('cros.system.timezone').value)) {
- return;
- }
- }
- // Setting several preferences at once will trigger several
- // |maybeGetTimeZoneList_| calls, which we don't want.
- this.getTimeZonesRequestSent_ = true;
- cr.sendWithPromise('getTimeZones')
- .then((timezones) => {
- this.setTimeZoneList_(timezones);
- })
- .finally(() => {
- this.getTimeZonesRequestSent_ = false;
- });
- },
-
- /**
- * Prefs observer for Per-user time zone enabled mode.
- * @private
- */
- maybeGetTimeZoneListPerUser_: function() {
- this.maybeGetTimeZoneList_(true);
- },
-
- /**
- * Prefs observer for Per-user time zone disabled mode.
- * @private
- */
- maybeGetTimeZoneListPerSystem_: function() {
- this.maybeGetTimeZoneList_(false);
- },
-
- /**
- * Converts the C++ response into an array of menu options.
- * @param {!Array<!Array<string>>} timeZones C++ time zones response.
- * @private
- */
- setTimeZoneList_: function(timeZones) {
- this.timeZoneList_ = timeZones.map(function(timeZonePair) {
- return {
- name: timeZonePair[1],
- value: timeZonePair[0],
- };
- });
- this.updateActiveTimeZoneName_(
- /** @type {!String} */ (this.getPref('cros.system.timezone').value));
- },
-
- /**
- * Updates active time zone display name when changed.
- * @param {!String} activeTimeZoneId value of cros.system.timezone preference.
- * @private
- */
- updateActiveTimeZoneName_: function(activeTimeZoneId) {
- const activeTimeZone = this.timeZoneList_.find(
- (timeZone) => timeZone.value == activeTimeZoneId);
- if (activeTimeZone) {
- this.activeTimeZoneDisplayName = activeTimeZone.name;
- }
- },
-
-
- /**
- * Computes visibility of user timezone preference.
- * @param {?chrome.settingsPrivate.PrefObject} prefUserTimezone
- * pref.settings.timezone
- * @param {boolean} prefResolveOnOffValue
- * prefs.generated.resolve_timezone_by_geolocation_on_off.value
- * @return {boolean}
- * @private
- */
- isUserTimeZoneSelectorHidden_: function(
- prefUserTimezone, prefResolveOnOffValue) {
- return (prefUserTimezone && prefUserTimezone.controlledBy != null) ||
- prefResolveOnOffValue;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.html b/chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.html
deleted file mode 100644
index 2ee6c254ec8..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="../controls/controlled_radio_button.html">
-<link rel="import" href="../controls/settings_radio_group.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="date_time_types.html">
-<link rel="import" href="timezone_selector.html">
-
-<dom-module id="timezone-subpage">
- <template>
- <style include="settings-shared">
- .block {
- display: block;
- }
- settings-dropdown-menu {
- --md-select-width: 400px;
- --settings-dropdown-menu-policy-order: 1;
- }
- #timeZoneResolveMethodDropdown,
- #timezoneSelector {
- padding-inline-start: 28px;
- }
- </style>
- <div class="settings-box block first">
- <settings-radio-group id="timeZoneRadioGroup"
- pref="{{prefs.generated.resolve_timezone_by_geolocation_on_off}}">
- <controlled-radio-button
- id="timeZoneAutoDetectOn"
- name="true"
- pref="[[prefs.generated.resolve_timezone_by_geolocation_on_off]]"
- label="$i18n{setTimeZoneAutomaticallyOn}"
- no-extension-indicator>
- </controlled-radio-button>
- <settings-dropdown-menu id="timeZoneResolveMethodDropdown"
- pref="{{prefs.generated.resolve_timezone_by_geolocation_method_short}}"
- label="$i18n{selectTimeZoneResolveMethod}"
- disabled="[[!prefs.generated.resolve_timezone_by_geolocation_on_off.value]]"
- menu-options="[[getTimeZoneResolveMethodsList_(
- prefs.generated.resolve_timezone_by_geolocation_method_short)]]">
- </settings-dropdown-menu>
- <controlled-radio-button
- id="timeZoneAutoDetectOff"
- name="false"
- pref="[[prefs.generated.resolve_timezone_by_geolocation_on_off]]"
- label="$i18n{setTimeZoneAutomaticallyOff}"
- no-extension-indicator>
- </controlled-radio-button>
- <timezone-selector id="timezoneSelector" prefs="{{prefs}}"
- active-time-zone-display-name="{{activeTimeZoneDisplayName}}">
- </timezone-selector>
- </settings-radio-group>
- </div>
- </template>
- <script src="timezone_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.js b/chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.js
deleted file mode 100644
index 3e29c0d9f05..00000000000
--- a/chromium/chrome/browser/resources/settings/date_time_page/timezone_subpage.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'timezone-subpage' is the collapsible section containing
- * time zone settings.
- */
-Polymer({
- is: 'timezone-subpage',
-
- behaviors: [PrefsBehavior],
-
- properties: {
- /**
- * This is <timezone-selector> parameter.
- */
- activeTimeZoneDisplayName: {
- type: String,
- notify: true,
- },
- },
-
- /**
- * Returns value list for timeZoneResolveMethodDropdown menu.
- * @private
- */
- getTimeZoneResolveMethodsList_: function() {
- const result = [];
- const pref =
- this.getPref('generated.resolve_timezone_by_geolocation_method_short');
- // Make sure current value is in the list, even if it is not
- // user-selectable.
- if (pref.value == settings.TimeZoneAutoDetectMethod.DISABLED) {
- // If disabled by policy, show the 'Automatic timezone disabled' label.
- // Otherwise, just show the default string, since the control will be
- // disabled as well.
- const label = pref.controlledBy ?
- loadTimeData.getString('setTimeZoneAutomaticallyDisabled') :
- loadTimeData.getString('setTimeZoneAutomaticallyIpOnlyDefault');
- result.push(
- {value: settings.TimeZoneAutoDetectMethod.DISABLED, name: label});
- }
- result.push({
- value: settings.TimeZoneAutoDetectMethod.IP_ONLY,
- name: loadTimeData.getString('setTimeZoneAutomaticallyIpOnlyDefault')
- });
-
- if (pref.value ==
- settings.TimeZoneAutoDetectMethod.SEND_WIFI_ACCESS_POINTS) {
- result.push({
- value: settings.TimeZoneAutoDetectMethod.SEND_WIFI_ACCESS_POINTS,
- name: loadTimeData.getString(
- 'setTimeZoneAutomaticallyWithWiFiAccessPointsData')
- });
- }
- result.push({
- value: settings.TimeZoneAutoDetectMethod.SEND_ALL_LOCATION_INFO,
- name:
- loadTimeData.getString('setTimeZoneAutomaticallyWithAllLocationInfo')
- });
- return result;
- },
-
-});
diff --git a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.html b/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.html
deleted file mode 100644
index ae0ae417d65..00000000000
--- a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="default_browser_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js b/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js
deleted file mode 100644
index 34fe0350b02..00000000000
--- a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Default Browser" section
- * to interact with the browser.
- */
-
-/**
- * @typedef {{
- * canBeDefault: boolean,
- * isDefault: boolean,
- * isDisabledByPolicy: boolean,
- * isUnknownError: boolean,
- * }};
- */
-let DefaultBrowserInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class DefaultBrowserBrowserProxy {
- /**
- * Get the initial DefaultBrowserInfo and begin sending updates to
- * 'settings.updateDefaultBrowserState'.
- * @return {!Promise<!DefaultBrowserInfo>}
- */
- requestDefaultBrowserState() {}
-
- /*
- * Try to set the current browser as the default browser. The new status of
- * the settings will be sent to 'settings.updateDefaultBrowserState'.
- */
- setAsDefaultBrowser() {}
- }
-
- /**
- * @implements {settings.DefaultBrowserBrowserProxy}
- */
- class DefaultBrowserBrowserProxyImpl {
- /** @override */
- requestDefaultBrowserState() {
- return cr.sendWithPromise('requestDefaultBrowserState');
- }
-
- /** @override */
- setAsDefaultBrowser() {
- chrome.send('setAsDefaultBrowser');
- }
- }
-
- cr.addSingletonGetter(DefaultBrowserBrowserProxyImpl);
-
- return {
- DefaultBrowserBrowserProxy: DefaultBrowserBrowserProxy,
- DefaultBrowserBrowserProxyImpl: DefaultBrowserBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.html b/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.html
deleted file mode 100644
index 8be48c881e0..00000000000
--- a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="default_browser_browser_proxy.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-default-browser-page">
- <template>
- <style include="settings-shared">
- </style>
- <template is="dom-if" if="[[maySetDefaultBrowser_]]">
- <div class="settings-box first">
- <div class="start settings-box-text">
- <div id="canBeDefaultBrowser">$i18n{defaultBrowser}</div>
- <div class="secondary">$i18n{defaultBrowserMakeDefault}</div>
- </div>
- <div class="separator"></div>
- <cr-button on-click="onSetDefaultBrowserTap_">
- $i18n{defaultBrowserMakeDefaultButton}
- </cr-button>
- </div>
- </template>
- <template is="dom-if" if="[[!maySetDefaultBrowser_]]">
- <div class="settings-box first">
- <div class="start settings-box-text" hidden$="[[!isDefault_]]"
- id="isDefault">
- $i18n{defaultBrowserDefault}
- </div>
- <div class="start settings-box-text" hidden$="[[!isSecondaryInstall_]]"
- id="isSecondaryInstall">
- $i18n{defaultBrowserSecondary}
- </div>
- <div class="start settings-box-text" hidden$="[[!isUnknownError_]]"
- id="isUnknownError">
- $i18n{defaultBrowserError}
- </div>
- </div>
- </template>
- </template>
- <script src="default_browser_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.js b/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.js
deleted file mode 100644
index 4e3d8eb9492..00000000000
--- a/chromium/chrome/browser/resources/settings/default_browser_page/default_browser_page.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-default-browser-page' is the settings page that contains
- * settings to change the default browser (i.e. which the OS will open).
- */
-Polymer({
- is: 'settings-default-browser-page',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /** @private */
- isDefault_: Boolean,
-
- /** @private */
- isSecondaryInstall_: Boolean,
-
- /** @private */
- isUnknownError_: Boolean,
-
- /** @private */
- maySetDefaultBrowser_: Boolean,
- },
-
- /** @private {settings.DefaultBrowserBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.DefaultBrowserBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'browser-default-state-changed',
- this.updateDefaultBrowserState_.bind(this));
-
- this.browserProxy_.requestDefaultBrowserState().then(
- this.updateDefaultBrowserState_.bind(this));
- },
-
- /**
- * @param {!DefaultBrowserInfo} defaultBrowserState
- * @private
- */
- updateDefaultBrowserState_: function(defaultBrowserState) {
- this.isDefault_ = false;
- this.isSecondaryInstall_ = false;
- this.isUnknownError_ = false;
- this.maySetDefaultBrowser_ = false;
-
- if (defaultBrowserState.isDefault) {
- this.isDefault_ = true;
- } else if (!defaultBrowserState.canBeDefault) {
- this.isSecondaryInstall_ = true;
- } else if (
- !defaultBrowserState.isDisabledByPolicy &&
- !defaultBrowserState.isUnknownError) {
- this.maySetDefaultBrowser_ = true;
- } else {
- this.isUnknownError_ = true;
- }
- },
-
- /** @private */
- onSetDefaultBrowserTap_: function() {
- this.browserProxy_.setAsDefaultBrowser();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/device_page.html b/chromium/chrome/browser/resources/settings/device_page/device_page.html
deleted file mode 100644
index 8eacafb0ba7..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/device_page.html
+++ /dev/null
@@ -1,108 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="device_page_browser_proxy.html">
-<link rel="import" href="display.html">
-<link rel="import" href="keyboard.html">
-<link rel="import" href="pointers.html">
-<link rel="import" href="power.html">
-<link rel="import" href="storage.html">
-<link rel="import" href="storage_external.html">
-<link rel="import" href="stylus.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-device-page">
- <template>
- <style include="settings-shared"></style>
- <settings-animated-pages id="pages" section="device"
- focus-config="[[focusConfig_]]">
- <div id="main" route-path="default">
- <cr-link-row id="pointersRow"
- label="[[getPointersTitle_(hasMouse_, hasTouchpad_)]]"
- on-click="onPointersTap_"></cr-link-row>
- <cr-link-row class="hr" id="keyboardRow" label="$i18n{keyboardTitle}"
- on-click="onKeyboardTap_"></cr-link-row>
- <template is="dom-if" if="[[hasStylus_]]">
- <cr-link-row class="hr" id="stylusRow" label="$i18n{stylusTitle}"
- on-click="onStylusTap_"></cr-link-row>
- </template>
- <cr-link-row class="hr" id="displayRow" label="$i18n{displayTitle}"
- on-click="onDisplayTap_"> </cr-link-row>
- <cr-link-row class="hr" hidden="[[hideStorageInfo_]]" id="storageRow"
- label="$i18n{storageTitle}" on-click="onStorageTap_"></cr-link-row>
- <template is="dom-if" if="[[enablePowerSettings_]]">
- <cr-link-row class="hr" id="powerRow" label="$i18n{powerTitle}"
- on-click="onPowerTap_"></cr-link-row>
- </template>
- </div>
- <template is="dom-if" route-path="/pointer-overlay">
- <settings-subpage
- associated-control="[[$$('#pointersRow')]]"
- page-title="[[getPointersTitle_(hasMouse_, hasTouchpad_)]]">
- <settings-pointers prefs="{{prefs}}"
- has-mouse="[[hasMouse_]]" has-touchpad="[[hasTouchpad_]]">
- </settings-pointers>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/keyboard-overlay">
- <settings-subpage
- associated-control="[[$$('#keyboardRow')]]"
- page-title="$i18n{keyboardTitle}">
- <settings-keyboard prefs="{{prefs}}"></settings-keyboard>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/stylus" no-search="[[!hasStylus_]]">
- <settings-subpage
- associated-control="[[$$('#stylusRow')]]"
- page-title="$i18n{stylusTitle}"
- no-search$="[[!hasStylus_]]">
- <settings-stylus prefs="{{prefs}}"></settings-stylus>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/display">
- <settings-subpage
- associated-control="[[$$('#displayRow')]]"
- page-title="$i18n{displayTitle}">
- <settings-display prefs="{{prefs}}"></settings-display>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/storage">
- <settings-subpage
- associated-control="[[$$('#storageRow')]]"
- page-title="$i18n{storageTitle}">
- <settings-storage prefs="{{prefs}}"
- show-crostini="[[showCrostini]]"
- android-enabled="[[androidEnabled_]]">
- </settings-storage>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[androidEnabled_]]">
- <template is="dom-if" route-path="/storage/externalStoragePreferences">
- <settings-subpage
- associated-control="[[$$('#storageRow')]]"
- page-title="$i18n{storageExternal}">
- <settings-storage-external prefs="{{prefs}}">
- </settings-storage-external>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" route-path="/power"
- no-search="[[!enablePowerSettings_]]">
- <settings-subpage
- associated-control="[[$$('#powerRow')]]"
- page-title="$i18n{powerTitle}"
- no-search$="[[!enablePowerSettings_]]">
- <settings-power enable-power-settings="[[enablePowerSettings_]]">
- </settings-power>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- </template>
- <script src="device_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/device_page.js b/chromium/chrome/browser/resources/settings/device_page/device_page.js
deleted file mode 100644
index 0e8bc51ca03..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/device_page.js
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-device-page' is the settings page for device and
- * peripheral settings.
- */
-Polymer({
- is: 'settings-device-page',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
-
- showCrostini: Boolean,
-
- /**
- * |hasMouse_| and |hasTouchpad_| start undefined so observers don't trigger
- * until they have been populated.
- * @private
- */
- hasMouse_: Boolean,
-
- /** @private */
- hasTouchpad_: Boolean,
-
- /**
- * |hasStylus_| is initialized to false so that dom-if behaves correctly.
- * @private
- */
- hasStylus_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Whether power status and settings should be fetched and displayed.
- * @private
- */
- enablePowerSettings_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enablePowerSettings');
- },
- readOnly: true,
- },
-
- /**
- * Whether storage management info should be hidden.
- * @private
- */
- hideStorageInfo_: {
- type: Boolean,
- value: function() {
- // TODO(crbug.com/868747): Show an explanatory message instead.
- return loadTimeData.valueExists('isDemoSession') &&
- loadTimeData.getBoolean('isDemoSession');
- },
- readOnly: true,
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.POINTERS) {
- map.set(settings.routes.POINTERS.path, '#pointersRow');
- }
- if (settings.routes.KEYBOARD) {
- map.set(settings.routes.KEYBOARD.path, '#keyboardRow');
- }
- if (settings.routes.STYLUS) {
- map.set(settings.routes.STYLUS.path, '#stylusRow');
- }
- if (settings.routes.DISPLAY) {
- map.set(settings.routes.DISPLAY.path, '#displayRow');
- }
- if (settings.routes.STORAGE) {
- map.set(settings.routes.STORAGE.path, '#storageRow');
- }
- if (settings.routes.EXTERNAL_STORAGE_PREFERENCES) {
- map.set(
- settings.routes.EXTERNAL_STORAGE_PREFERENCES.path,
- '#externalStoragePreferencesRow');
- }
- if (settings.routes.POWER) {
- map.set(settings.routes.POWER.path, '#powerRow');
- }
- return map;
- },
- },
-
- /** @private */
- androidEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('androidEnabled');
- },
- },
- },
-
- observers: [
- 'pointersChanged_(hasMouse_, hasTouchpad_)',
- ],
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'has-mouse-changed', this.set.bind(this, 'hasMouse_'));
- this.addWebUIListener(
- 'has-touchpad-changed', this.set.bind(this, 'hasTouchpad_'));
- settings.DevicePageBrowserProxyImpl.getInstance().initializePointers();
-
- this.addWebUIListener(
- 'has-stylus-changed', this.set.bind(this, 'hasStylus_'));
- settings.DevicePageBrowserProxyImpl.getInstance().initializeStylus();
-
- this.addWebUIListener(
- 'storage-android-enabled-changed',
- this.set.bind(this, 'androidEnabled_'));
- settings.DevicePageBrowserProxyImpl.getInstance().updateAndroidEnabled();
- },
-
- /**
- * @return {string}
- * @private
- */
- getPointersTitle_: function() {
- if (this.hasMouse_ && this.hasTouchpad_) {
- return this.i18n('mouseAndTouchpadTitle');
- }
- if (this.hasMouse_) {
- return this.i18n('mouseTitle');
- }
- if (this.hasTouchpad_) {
- return this.i18n('touchpadTitle');
- }
- return '';
- },
-
- /**
- * Handler for tapping the mouse and touchpad settings menu item.
- * @private
- */
- onPointersTap_: function() {
- settings.navigateTo(settings.routes.POINTERS);
- },
-
- /**
- * Handler for tapping the Keyboard settings menu item.
- * @private
- */
- onKeyboardTap_: function() {
- settings.navigateTo(settings.routes.KEYBOARD);
- },
-
- /**
- * Handler for tapping the Keyboard settings menu item.
- * @private
- */
- onStylusTap_: function() {
- settings.navigateTo(settings.routes.STYLUS);
- },
-
- /**
- * Handler for tapping the Display settings menu item.
- * @private
- */
- onDisplayTap_: function() {
- settings.navigateTo(settings.routes.DISPLAY);
- },
-
- /**
- * Handler for tapping the Storage settings menu item.
- * @private
- */
- onStorageTap_: function() {
- settings.navigateTo(settings.routes.STORAGE);
- },
-
- /**
- * Handler for tapping the Power settings menu item.
- * @private
- */
- onPowerTap_: function() {
- settings.navigateTo(settings.routes.POWER);
- },
-
- /** @protected */
- currentRouteChanged: function() {
- this.checkPointerSubpage_();
- },
-
- /**
- * @param {boolean} hasMouse
- * @param {boolean} hasTouchpad
- * @private
- */
- pointersChanged_: function(hasMouse, hasTouchpad) {
- this.$.pointersRow.hidden = !hasMouse && !hasTouchpad;
- this.checkPointerSubpage_();
- },
-
- /**
- * Leaves the pointer subpage if all pointing devices are detached.
- * @private
- */
- checkPointerSubpage_: function() {
- // Check that the properties have explicitly been set to false.
- if (this.hasMouse_ === false && this.hasTouchpad_ === false &&
- settings.getCurrentRoute() == settings.routes.POINTERS) {
- settings.navigateTo(settings.routes.DEVICE);
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.html b/chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.html
deleted file mode 100644
index b375f99ee72..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="device_page_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js b/chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js
deleted file mode 100644
index 13fa10e6b8a..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview A helper object used for testing the Device page. */
-cr.exportPath('settings');
-
-/**
- * @typedef {{
- * id: string,
- * is_dedicated_charger: boolean,
- * description: string
- * }}
- */
-settings.PowerSource;
-
-/**
- * @typedef {{
- * present: boolean,
- * charging: boolean,
- * calculating: boolean,
- * percent: number,
- * statusText: string,
- * }}
- */
-settings.BatteryStatus;
-
-/**
- * Mirrors chromeos::settings::PowerHandler::IdleBehavior.
- * @enum {number}
- */
-settings.IdleBehavior = {
- DISPLAY_OFF_SLEEP: 0,
- DISPLAY_OFF: 1,
- DISPLAY_ON: 2,
- OTHER: 3,
-};
-
-/**
- * Mirrors chromeos::PowerPolicyController::Action.
- * @enum {number}
- */
-settings.LidClosedBehavior = {
- SUSPEND: 0,
- STOP_SESSION: 1,
- SHUT_DOWN: 2,
- DO_NOTHING: 3,
-};
-
-/**
- * @typedef {{
- * idleBehavior: settings.IdleBehavior,
- * idleControlled: boolean,
- * lidClosedBehavior: settings.LidClosedBehavior,
- * lidClosedControlled: boolean,
- * hasLid: boolean,
- * }}
- */
-settings.PowerManagementSettings;
-
-/**
- * A note app's availability for running as note handler app from lock screen.
- * Mirrors chromeos::NoteTakingLockScreenSupport.
- * @enum {number}
- */
-settings.NoteAppLockScreenSupport = {
- NOT_SUPPORTED: 0,
- NOT_ALLOWED_BY_POLICY: 1,
- SUPPORTED: 2,
- ENABLED: 3
-};
-
-/**
- * @typedef {{name:string,
- * value:string,
- * preferred:boolean,
- * lockScreenSupport: settings.NoteAppLockScreenSupport}}
- */
-settings.NoteAppInfo;
-
-/**
- * @typedef {{
- * label: string,
- * uuid: string
- * }}
- */
-settings.ExternalStorage;
-
-cr.define('settings', function() {
- /** @interface */
- class DevicePageBrowserProxy {
- /** Initializes the mouse and touchpad handler. */
- initializePointers() {}
-
- /** Initializes the stylus handler. */
- initializeStylus() {}
-
- /** Initializes the keyboard WebUI handler. */
- initializeKeyboard() {}
-
- /** Shows the Ash keyboard shortcut viewer. */
- showKeyboardShortcutViewer() {}
-
- /** Requests an ARC status update. */
- updateAndroidEnabled() {}
-
- /** Requests a power status update. */
- updatePowerStatus() {}
-
- /**
- * Sets the ID of the power source to use.
- * @param {string} powerSourceId ID of the power source. '' denotes the
- * battery (no external power source).
- */
- setPowerSource(powerSourceId) {}
-
- /** Requests the current power management settings. */
- requestPowerManagementSettings() {}
-
- /**
- * Sets the idle power management behavior.
- * @param {settings.IdleBehavior} behavior Idle behavior.
- */
- setIdleBehavior(behavior) {}
-
- /**
- * Sets the lid-closed power management behavior.
- * @param {settings.LidClosedBehavior} behavior Lid-closed behavior.
- */
- setLidClosedBehavior(behavior) {}
-
- /**
- * |callback| is run when there is new note-taking app information
- * available or after |requestNoteTakingApps| has been called.
- * @param {function(Array<settings.NoteAppInfo>, boolean):void} callback
- */
- setNoteTakingAppsUpdatedCallback(callback) {}
-
- /**
- * Open up the play store with the given URL.
- * @param {string} url
- */
- showPlayStore(url) {}
-
- /**
- * Request current note-taking app info. Invokes any callback registered in
- * |onNoteTakingAppsUpdated|.
- */
- requestNoteTakingApps() {}
-
- /**
- * Changes the preferred note taking app.
- * @param {string} appId The app id. This should be a value retrieved from a
- * |onNoteTakingAppsUpdated| callback.
- */
- setPreferredNoteTakingApp(appId) {}
-
- /**
- * Sets whether the preferred note taking app should be enabled to run as a
- * lock screen note action handler.
- * @param {boolean} enabled Whether the app should be enabled to handle note
- * actions from the lock screen.
- */
- setPreferredNoteTakingAppEnabledOnLockScreen(enabled) {}
-
- /** Requests an external storage list update. */
- updateExternalStorages() {}
-
- /**
- * |callback| is run when the list of plugged-in external storages is
- * available after |updateExternalStorages| has been called.
- * @param {function(Array<!settings.ExternalStorage>):void} callback
- */
- setExternalStoragesUpdatedCallback(callback) {}
- }
-
- /**
- * @implements {settings.DevicePageBrowserProxy}
- */
- class DevicePageBrowserProxyImpl {
- /** @override */
- initializePointers() {
- chrome.send('initializePointerSettings');
- }
-
- /** @override */
- initializeStylus() {
- chrome.send('initializeStylusSettings');
- }
-
- /** @override */
- initializeKeyboard() {
- chrome.send('initializeKeyboardSettings');
- }
-
- /** @override */
- showKeyboardShortcutViewer() {
- chrome.send('showKeyboardShortcutViewer');
- }
-
- /** @override */
- updateAndroidEnabled() {
- chrome.send('updateAndroidEnabled');
- }
-
- /** @override */
- updatePowerStatus() {
- chrome.send('updatePowerStatus');
- }
-
- /** @override */
- setPowerSource(powerSourceId) {
- chrome.send('setPowerSource', [powerSourceId]);
- }
-
- /** @override */
- requestPowerManagementSettings() {
- chrome.send('requestPowerManagementSettings');
- }
-
- /** @override */
- setIdleBehavior(behavior) {
- chrome.send('setIdleBehavior', [behavior]);
- }
-
- /** @override */
- setLidClosedBehavior(behavior) {
- chrome.send('setLidClosedBehavior', [behavior]);
- }
-
- /** @override */
- setNoteTakingAppsUpdatedCallback(callback) {
- cr.addWebUIListener('onNoteTakingAppsUpdated', callback);
- }
-
- /** @override */
- showPlayStore(url) {
- chrome.send('showPlayStoreApps', [url]);
- }
-
- /** @override */
- requestNoteTakingApps() {
- chrome.send('requestNoteTakingApps');
- }
-
- /** @override */
- setPreferredNoteTakingApp(appId) {
- chrome.send('setPreferredNoteTakingApp', [appId]);
- }
-
- /** @override */
- setPreferredNoteTakingAppEnabledOnLockScreen(enabled) {
- chrome.send('setPreferredNoteTakingAppEnabledOnLockScreen', [enabled]);
- }
-
- /** @override */
- updateExternalStorages() {
- chrome.send('updateExternalStorages');
- }
-
- /** @override */
- setExternalStoragesUpdatedCallback(callback) {
- cr.addWebUIListener('onExternalStoragesUpdated', callback);
- }
- }
-
- cr.addSingletonGetter(DevicePageBrowserProxyImpl);
-
- return {
- DevicePageBrowserProxy: DevicePageBrowserProxy,
- DevicePageBrowserProxyImpl: DevicePageBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/display.html b/chromium/chrome/browser/resources/settings/device_page/display.html
deleted file mode 100644
index cee6fafa793..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/display.html
+++ /dev/null
@@ -1,295 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_tabs/cr_tabs.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="display_layout.html">
-<link rel="import" href="display_overscan_dialog.html">
-<link rel="import" href="night_light_slider.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-display">
- <template>
- <style include="settings-shared md-select iron-flex iron-flex-alignment">
- .indented {
- align-self: stretch;
- margin-inline-start: var(--cr-section-indent-padding);
- padding: 0;
- }
-
- #nightLightTemperatureDiv[disabled] {
- opacity: 0.38;
- pointer-events: none;
- }
-
- .display-tabs {
- width: 100%;
- }
-
- display-layout {
- align-self: stretch;
- flex: 1 1;
- height: 300px;
- margin: 10px;
- }
-
- .text-area {
- margin: 10px 0;
- }
-
- .settings-box > cr-button:first-child {
- padding-inline-start: 0
- }
-
- .settings-box > cr-policy-pref-indicator {
- margin-inline-end: var(--cr-controlled-by-spacing);
- }
-
- #controlsDiv > .settings-box:first-of-type {
- border-top: none;
- }
-
- #nightLightSlider {
- flex-grow: 1;
- margin-top: 20px;
- }
-
- iron-collapse {
- width: 100%;
- }
- </style>
- <div class="settings-box first layout vertical self-stretch">
- <h2 class="layout self-start">
- $i18n{displayArrangementTitle}
- </h2>
- <div class="secondary layout self-start"
- hidden="[[!hasMultipleDisplays_(displays)]]">
- $i18n{displayArrangementText}
- </div>
- <display-layout id="displayLayout"
- selected-display="[[selectedDisplay]]"
- on-select-display="onSelectDisplay_">
- </display-layout>
-
- <template is="dom-if" if="[[showMirror_(unifiedDesktopMode_, displays)]]"
- restamp>
- <div class="secondary self-start">
- <cr-checkbox checked="[[isMirrored_(displays)]]"
- on-click="onMirroredTap_"
- aria-label="[[getDisplayMirrorText_(displays)]]">
- <div class="text-area">[[getDisplayMirrorText_(displays)]]</div>
- </cr-checkbox>
- </div>
- </template>
-
- </div>
- <div hidden="[[!hasMultipleDisplays_(displays)]]" class="settings-box">
- <cr-tabs selected="[[selectedTab_]]" class="display-tabs"
- on-selected-changed="onSelectDisplayTab_"
- tab-names="[[displayTabNames_]]"></cr-tabs>
- </div>
- <div hidden="[[hasMultipleDisplays_(displays)]]"
- class="settings-box line-only"></div>
-
- <div id="controlsDiv" class="settings-box layout vertical first">
- <h2>[[selectedDisplay.name]]</h2>
- <template is="dom-if" if="[[showUnifiedDesktop_(unifiedDesktopAvailable_,
- unifiedDesktopMode_, displays)]]" restamp>
- <div class="settings-box indented two-line">
- <div class="start">
- <div id="displayUnifiedDesktopCheckboxLabel">
- $i18n{displayUnifiedDesktop}
- </div>
- <div class="secondary">
- [[getUnifiedDesktopText_(unifiedDesktopMode_)]]
- </div>
- </div>
- <cr-toggle checked="[[unifiedDesktopMode_]]"
- on-click="onUnifiedDesktopTap_"
- aria-labelledby="displayUnifiedDesktopCheckboxLabel">
- </cr-toggle>
- </div>
- </template>
-
- <template is="dom-if" restamp
- if="[[showDisplaySelectMenu_(displays, selectedDisplay)]]">
- <div class="settings-box indented">
- <div id="displayScreenTitle" class="start">
- $i18n{displayScreenTitle}
- </div>
- <select class="md-select" on-change="updatePrimaryDisplay_"
- aria-labelledby="displayScreenTitle"
- value="[[getDisplaySelectMenuIndex_(
- selectedDisplay, primaryDisplayId)]]">
- <option value="0">$i18n{displayScreenPrimary}</option>
- <option value="1">$i18n{displayScreenExtended}</option>
- </select>
- </div>
- </div>
- </template>
-
- <!-- Display zoom selection slider -->
- <div class="settings-box indented two-line">
- <div class="start text-area layout vertical">
- <div>$i18n{displayZoomTitle}</div>
- <div class="secondary self-start">$i18n{displayZoomSublabel}</div>
- <div class="secondary self-start"
- hidden$="[[!logicalResolutionText_]]">
- [[logicalResolutionText_]]
- </div>
- </div>
- <template is="dom-if" if="[[isDisplayScaleManagedByPolicy_(
- selectedDisplay, prefs.cros.device_display_resolution)]]">
- <cr-policy-pref-indicator
- pref="[[prefs.cros.device_display_resolution]]"
- icon-aria-label="$i18n{displayResolutionText}">
- </cr-policy-pref-indicator>
- </template>
- <settings-slider id="displaySizeSlider"
- ticks="[[zoomValues_]]" pref="{{selectedZoomPref_}}"
- label-min="$i18n{displaySizeSliderMinLabel}"
- label-max="$i18n{displaySizeSliderMaxLabel}"
- disabled="[[isDisplayScaleMandatory_(
- selectedDisplay,
- prefs.cros.device_display_resolution)]]"
- on-cr-slider-value-changed="onDisplaySizeSliderDrag_">
- </settings-slider>
- </div>
-
- <!-- Drop down select menu for resolution -->
- <div class="settings-box indented two-line"
- hidden$="[[!showDropDownResolutionSetting_(selectedDisplay)]]">
- <div class="start text-area layout vertical">
- <div>$i18n{displayResolutionTitle}</div>
- <div class="secondary self-start">
- $i18n{displayResolutionSublabel}
- </div>
- </div>
- <template is="dom-if" if="[[isDisplayResolutionManagedByPolicy_(
- prefs.cros.device_display_resolution)]]">
- <cr-policy-pref-indicator
- pref="[[prefs.cros.device_display_resolution]]"
- icon-aria-label="$i18n{displayResolutionText}">
- </cr-policy-pref-indicator>
- </template>
- <settings-dropdown-menu id="displayModeSelector"
- pref="{{selectedModePref_}}"
- disabled="[[isDisplayResolutionMandatory_(
- prefs.cros.device_display_resolution)]]"
- label="Display Mode Menu"
- menu-options="[[displayModeList_]]">
- </settings-dropdown-menu>
- </div>
- <template is="dom-if" if="[[!unifiedDesktopMode_]]" restamp>
- <div class="settings-box indented">
- <div id="displayOrientation" class="start text-area">
- $i18n{displayOrientation}
- </div>
- <template is="dom-if" if="[[isDevicePolicyEnabled_(
- prefs.cros.display_rotation_default)]]">
- <cr-policy-pref-indicator
- pref="[[prefs.cros.display_rotation_default]]"
- icon-aria-label="$i18n{displayOrientation}">
- </cr-policy-pref-indicator>
- </template>
- <select class="md-select" value="[[selectedDisplay.rotation]]"
- disabled="[[selectedDisplay.isTabletMode]]"
- aria-labelledby="displayOrientation"
- on-change="onOrientationChange_">
- <option value="0">$i18n{displayOrientationStandard}</option>
- <option value="90">90&deg;</option>
- <option value="180">180&deg;</option>
- <option value="270">270&deg;</option>
- </select>
- </div>
- </template>
-
- <cr-link-row class="indented hr" id="overscan"
- label="$i18n{displayOverscanPageTitle}"
- sub-label="$i18n{displayOverscanPageText}" on-click="onOverscanTap_"
- hidden$="[[!showOverscanSetting_(selectedDisplay)]]" embedded>
- </cr-link-row>
-
- <settings-display-overscan-dialog id="displayOverscan"
- display-id="{{overscanDisplayId}}"
- on-close="onCloseOverscanDialog_">
- </settings-display-overscan-dialog>
-
- <cr-link-row class="indented hr" id="touchCalibration"
- label="$i18n{displayTouchCalibrationTitle}"
- sub-label="$i18n{displayTouchCalibrationText}"
- on-click="onTouchCalibrationTap_"
- hidden$="[[!showTouchCalibrationSetting_(selectedDisplay)]]" embedded>
- </cr-link-row>
- </div>
-
- <!-- Night Light Settings -->
- <settings-toggle-button
- id="nightLightToggleButton"
- label="$i18n{displayNightLightLabel}"
- pref="{{prefs.ash.night_light.enabled}}"
- sub-label="$i18n{displayNightLightText}">
- </settings-toggle-button>
-
- <div id="nightLightSettingsDiv"
- class="settings-box continuation start layout vertical">
- <!-- Color temperature slider -->
- <div id="nightLightTemperatureDiv"
- class="settings-box indented continuation"
- hidden$="[[!prefs.ash.night_light.enabled.value]]">
- <div class="start text-area" id="colorTemperatureLabel">
- $i18n{displayNightLightTemperatureLabel}
- </div>
- <settings-slider id="colorTemperatureSlider"
- aria-labelledby="colorTemperatureLabel" min="0" max="100"
- scale="100" label-min="$i18n{displayNightLightTempSliderMinLabel}"
- label-max="$i18n{displayNightLightTempSliderMaxLabel}"
- pref="{{prefs.ash.night_light.color_temperature}}">
- </settings-slider>
- </div>
- <!-- Schedule settings -->
- <div class="settings-box indented">
- <div class="start text-area">
- <div id="nightLightScheduleLabel" class="label">
- $i18n{displayNightLightScheduleLabel}
- </div>
- <div id="nightLightScheduleSubLabel" class="secondary label"
- hidden$="[[!nightLightScheduleSubLabel_]]">
- [[nightLightScheduleSubLabel_]]
- </div>
- </div>
- <settings-dropdown-menu
- id="nightLightScheduleTypeDropDown"
- aria-labelledby="nightLightScheduleLabel"
- pref="{{prefs.ash.night_light.schedule_type}}"
- menu-options="[[scheduleTypesList_]]">
- </settings-dropdown-menu>
- </div>
- <!-- Custom schedule slider -->
- <iron-collapse id="nightLightCustomScheduleCollapse"
- opened="[[shouldOpenCustomScheduleCollapse_]]">
- <div class="settings-box indented continuation">
- <div class="start text-area layout vertical">
- <div class="settings-box continuation self-stretch">
- <night-light-slider id="nightLightSlider" prefs="{{prefs}}">
- </night-light-slider>
- </div>
- </div>
- </div>
- </iron-collapse>
- </div>
-
- </template>
- <script src="display.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/display.js b/chromium/chrome/browser/resources/settings/device_page/display.js
deleted file mode 100644
index 630be8d593a..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/display.js
+++ /dev/null
@@ -1,973 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-display' is the settings subpage for display settings.
- */
-
-/**
- * The types of Night Light automatic schedule. The values of the enum values
- * are synced with the pref "prefs.ash.night_light.schedule_type".
- * @enum {number}
- */
-const NightLightScheduleType = {
- NEVER: 0,
- SUNSET_TO_SUNRISE: 1,
- CUSTOM: 2,
-};
-
-/**
- * @typedef {{
- * value: (!{
- * recommended: (boolean|undefined),
- * external_width: (number|undefined),
- * external_height: (number|undefined),
- * external_use_native: (boolean|undefined),
- * external_scale_percentage: (number|undefined),
- * internal_scale_percentage: (number|undefined)
- * }|null)
- * }}
- */
-let DisplayResolutionPrefObject;
-
-cr.define('settings.display', function() {
- const systemDisplayApi =
- /** @type {!SystemDisplay} */ (chrome.system.display);
-
- return {
- systemDisplayApi: systemDisplayApi,
- };
-});
-
-Polymer({
- is: 'settings-display',
-
- behaviors: [
- I18nBehavior,
- PrefsBehavior,
- ],
-
- properties: {
- /**
- * @type {!chrome.settingsPrivate.PrefObject}
- * @private
- */
- selectedModePref_: {
- type: Object,
- value: function() {
- return {
- key: 'fakeDisplaySliderPref',
- type: chrome.settingsPrivate.PrefType.NUMBER,
- value: 0,
- };
- },
- },
-
- /**
- * @type {!chrome.settingsPrivate.PrefObject}
- * @private
- */
- selectedZoomPref_: {
- type: Object,
- value: function() {
- return {
- key: 'fakeDisplaySliderZoomPref',
- type: chrome.settingsPrivate.PrefType.NUMBER,
- value: 0,
- };
- },
- },
-
- /**
- * Array of displays.
- * @type {!Array<!chrome.system.display.DisplayUnitInfo>}
- */
- displays: Array,
-
- /**
- * Array of display layouts.
- * @type {!Array<!chrome.system.display.DisplayLayout>}
- */
- layouts: Array,
-
- /**
- * String listing the ids in displays. Used to observe changes to the
- * display configuration (i.e. when a display is added or removed).
- */
- displayIds: {type: String, observer: 'onDisplayIdsChanged_'},
-
- /** Primary display id */
- primaryDisplayId: String,
-
- /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */
- selectedDisplay: Object,
-
- /** Id passed to the overscan dialog. */
- overscanDisplayId: {
- type: String,
- notify: true,
- },
-
- /** Ids for mirroring destination displays. */
- mirroringDestinationIds: Array,
-
- /** @private {!Array<number>} Mode index values for slider. */
- modeValues_: Array,
-
- /**
- * @private {!Array<cr_slider.SliderTick>} Display zoom slider tick values.
- */
- zoomValues_: Array,
-
- /** @private {!DropdownMenuOptionList} */
- displayModeList_: {
- type: Array,
- value: [],
- },
-
- /** @private */
- unifiedDesktopAvailable_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('unifiedDesktopAvailable');
- }
- },
-
- /** @private */
- listAllDisplayModes_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('listAllDisplayModes');
- }
- },
-
- /** @private */
- unifiedDesktopMode_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- scheduleTypesList_: {
- type: Array,
- value: function() {
- return [
- {
- name: loadTimeData.getString('displayNightLightScheduleNever'),
- value: NightLightScheduleType.NEVER
- },
- {
- name: loadTimeData.getString(
- 'displayNightLightScheduleSunsetToSunRise'),
- value: NightLightScheduleType.SUNSET_TO_SUNRISE
- },
- {
- name: loadTimeData.getString('displayNightLightScheduleCustom'),
- value: NightLightScheduleType.CUSTOM
- }
- ];
- },
- },
-
- /** @private */
- shouldOpenCustomScheduleCollapse_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- nightLightScheduleSubLabel_: String,
-
- /** @private */
- logicalResolutionText_: String,
-
- /** @private {!Array<string>} */
- displayTabNames_: Array,
-
- /** @private */
- selectedTab_: Number,
- },
-
- observers: [
- 'updateNightLightScheduleSettings_(prefs.ash.night_light.schedule_type.*,' +
- ' prefs.ash.night_light.enabled.*)',
- 'onSelectedModeChange_(selectedModePref_.value)',
- 'onSelectedZoomChange_(selectedZoomPref_.value)'
- ],
-
- /** @private {number} Selected mode index received from chrome. */
- currentSelectedModeIndex_: -1,
-
- /**
- * Listener for chrome.system.display.onDisplayChanged events.
- * @type {function(void)|undefined}
- * @private
- */
- displayChangedListener_: undefined,
-
- /** @override */
- attached: function() {
- this.displayChangedListener_ =
- this.displayChangedListener_ || this.getDisplayInfo_.bind(this);
- settings.display.systemDisplayApi.onDisplayChanged.addListener(
- this.displayChangedListener_);
-
- this.getDisplayInfo_();
- this.$.displaySizeSlider.updateValueInstantly = false;
- },
-
- /** @override */
- detached: function() {
- settings.display.systemDisplayApi.onDisplayChanged.removeListener(
- assert(this.displayChangedListener_));
-
- this.currentSelectedModeIndex_ = -1;
- },
-
- /**
- * Shows or hides the overscan dialog.
- * @param {boolean} showOverscan
- * @private
- */
- showOverscanDialog_: function(showOverscan) {
- if (showOverscan) {
- this.$.displayOverscan.open();
- this.$.displayOverscan.focus();
- } else {
- this.$.displayOverscan.close();
- }
- },
-
- /** @private */
- onDisplayIdsChanged_: function() {
- // Close any overscan dialog (which will cancel any overscan operation)
- // if displayIds changes.
- this.showOverscanDialog_(false);
- },
-
- /** @private */
- getDisplayInfo_: function() {
- /** @type {chrome.system.display.GetInfoFlags} */ const flags = {
- singleUnified: true
- };
- settings.display.systemDisplayApi.getInfo(
- flags, this.displayInfoFetched_.bind(this));
- },
-
- /**
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @private
- */
- displayInfoFetched_: function(displays) {
- if (!displays.length) {
- return;
- }
- settings.display.systemDisplayApi.getDisplayLayout(
- this.displayLayoutFetched_.bind(this, displays));
- if (this.isMirrored_(displays)) {
- this.mirroringDestinationIds = displays[0].mirroringDestinationIds;
- } else {
- this.mirroringDestinationIds = [];
- }
- },
-
- /**
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @param {!Array<!chrome.system.display.DisplayLayout>} layouts
- * @private
- */
- displayLayoutFetched_: function(displays, layouts) {
- this.layouts = layouts;
- this.displays = displays;
- this.displayTabNames_ = displays.map(({name}) => name);
- this.updateDisplayInfo_();
- },
-
- /**
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {number} The index of the currently selected mode of the
- * |selectedDisplay|. If the display has no modes, returns 0.
- * @private
- */
- getSelectedModeIndex_: function(selectedDisplay) {
- for (let i = 0; i < selectedDisplay.modes.length; ++i) {
- if (selectedDisplay.modes[i].isSelected) {
- return i;
- }
- }
- return 0;
- },
-
- /**
- * Checks if the given device policy is enabled.
- * @param {DisplayResolutionPrefObject} policyPref
- * @return {boolean}
- * @private
- */
- isDevicePolicyEnabled_: function(policyPref) {
- return policyPref !== undefined && policyPref.value !== null;
- },
-
- /**
- * Checks if display resolution is managed by device policy.
- * @param {DisplayResolutionPrefObject} resolutionPref
- * @return {boolean}
- * @private
- */
- isDisplayResolutionManagedByPolicy_: function(resolutionPref) {
- return this.isDevicePolicyEnabled_(resolutionPref) &&
- (resolutionPref.value.external_use_native !== undefined ||
- (resolutionPref.value.external_width !== undefined &&
- resolutionPref.value.external_height !== undefined));
- },
-
- /**
- * Checks if display resolution is managed by policy and the policy
- * is mandatory.
- * @param {DisplayResolutionPrefObject} resolutionPref
- * @return {boolean}
- * @private
- */
- isDisplayResolutionMandatory_: function(resolutionPref) {
- return this.isDisplayResolutionManagedByPolicy_(resolutionPref) &&
- !resolutionPref.value.recommended;
- },
-
- /**
- * Checks if display scale factor is managed by device policy.
- * @param {chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @param {DisplayResolutionPrefObject} resolutionPref
- * @return {boolean}
- * @private
- */
- isDisplayScaleManagedByPolicy_: function(selectedDisplay, resolutionPref) {
- if (!this.isDevicePolicyEnabled_(resolutionPref) || !selectedDisplay) {
- return false;
- }
- if (selectedDisplay.isInternal) {
- return resolutionPref.value.internal_scale_percentage !== undefined;
- }
- return resolutionPref.value.external_scale_percentage !== undefined;
- },
-
- /**
- * Checks if display scale factor is managed by policy and the policy
- * is mandatory.
- * @param {DisplayResolutionPrefObject} resolutionPref
- * @return {boolean}
- * @private
- */
- isDisplayScaleMandatory_: function(selectedDisplay, resolutionPref) {
- return this.isDisplayScaleManagedByPolicy_(
- selectedDisplay, resolutionPref) &&
- !resolutionPref.value.recommended;
- },
-
- /**
- * Returns the list of display modes that is shown to the user in a drop down
- * menu.
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {!DropdownMenuOptionList}
- * @private
- */
- getDisplayModeOptionList_: function(selectedDisplay) {
- const optionList = [];
-
- const listAllModes = this.listAllDisplayModes_;
-
- for (let i = 0; i < selectedDisplay.modes.length; ++i) {
- const mode = selectedDisplay.modes[i];
-
- const id = listAllModes && mode.isInterlaced ?
- 'displayResolutionInterlacedMenuItem' :
- 'displayResolutionMenuItem';
- const refreshRate = Math.round(mode.refreshRate * 100) / 100;
- const option = this.i18n(
- id, mode.width.toString(), mode.height.toString(),
- refreshRate.toString());
-
- optionList.push({
- name: option,
- value: i,
- });
- }
- return optionList;
- },
-
- /**
- * Returns a value from |zoomValues_| that is closest to the display zoom
- * percentage currently selected for the |selectedDisplay|.
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {number}
- * @private
- */
- getSelectedDisplayZoom_: function(selectedDisplay) {
- const selectedZoom = selectedDisplay.displayZoomFactor;
- let closestMatch = this.zoomValues_[0].value;
- let minimumDiff = Math.abs(closestMatch - selectedZoom);
-
- for (let i = 0; i < this.zoomValues_.length; i++) {
- const currentDiff = Math.abs(this.zoomValues_[i].value - selectedZoom);
- if (currentDiff < minimumDiff) {
- closestMatch = this.zoomValues_[i].value;
- minimumDiff = currentDiff;
- }
- }
-
- return /** @type {number} */ (closestMatch);
- },
-
- /**
- * Given the display with the current display mode, this function lists all
- * the display zoom values and their labels to be used by the slider.
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {!Array<cr_slider.SliderTick>}
- */
- getZoomValues_: function(selectedDisplay) {
- return selectedDisplay.availableDisplayZoomFactors.map(value => {
- const ariaValue = Math.round(value * 100);
- return {
- value,
- ariaValue,
- label: this.i18n('displayZoomValue', ariaValue.toString())
- };
- });
- },
-
- /**
- * We need to call this explicitly rather than relying on change events
- * so that we can control the update order.
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @private
- */
- setSelectedDisplay_: function(selectedDisplay) {
- // |modeValues_| controls the resolution slider's tick values. Changing it
- // might trigger a change in the |selectedModePref_.value| if the number of
- // modes differs and the current mode index is out of range of the new modes
- // indices. Thus, we need to set |currentSelectedModeIndex_| to -1 to
- // indicate that the |selectedDisplay| and |selectedModePref_.value| are out
- // of sync, and therefore getResolutionText_() and onSelectedModeChange_()
- // will be no-ops.
- this.currentSelectedModeIndex_ = -1;
- const numModes = selectedDisplay.modes.length;
- this.modeValues_ = numModes == 0 ? [] : Array.from(Array(numModes).keys());
-
- // Note that the display zoom values has the same number of ticks for all
- // displays, so the above problem doesn't apply here.
- this.zoomValues_ = this.getZoomValues_(selectedDisplay);
- this.set(
- 'selectedZoomPref_.value',
- this.getSelectedDisplayZoom_(selectedDisplay));
-
- this.displayModeList_ = this.getDisplayModeOptionList_(selectedDisplay);
- // Set |selectedDisplay| first since only the resolution slider depends
- // on |selectedModePref_|.
- this.selectedDisplay = selectedDisplay;
- this.selectedTab_ = this.displays.indexOf(this.selectedDisplay);
-
- // Now that everything is in sync, set the selected mode to its correct
- // value right before updating the pref.
- this.currentSelectedModeIndex_ =
- this.getSelectedModeIndex_(selectedDisplay);
- this.set('selectedModePref_.value', this.currentSelectedModeIndex_);
-
- this.updateLogicalResolutionText_(
- /** @type {number} */ (this.selectedZoomPref_.value));
- },
-
- /**
- * Returns true if the resolution setting needs to be displayed.
- * @param {!chrome.system.display.DisplayUnitInfo} display
- * @return {boolean}
- * @private
- */
- showDropDownResolutionSetting_: function(display) {
- return !display.isInternal;
- },
-
- /**
- * Returns true if external touch devices are connected and the current
- * display is not an internal display. If the feature is not enabled via the
- * switch, this will return false.
- * @param {!chrome.system.display.DisplayUnitInfo} display Display being
- * checked for touch support.
- * @return {boolean}
- * @private
- */
- showTouchCalibrationSetting_: function(display) {
- return !display.isInternal &&
- loadTimeData.getBoolean('hasExternalTouchDevice') &&
- loadTimeData.getBoolean('enableTouchCalibrationSetting');
- },
-
- /**
- * Returns true if the overscan setting should be shown for |display|.
- * @param {!chrome.system.display.DisplayUnitInfo} display
- * @return {boolean}
- * @private
- */
- showOverscanSetting_: function(display) {
- return !display.isInternal;
- },
-
- /**
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @return {boolean}
- * @private
- */
- hasMultipleDisplays_: function(displays) {
- return displays.length > 1;
- },
-
- /**
- * Returns false if the display select menu has to be hidden.
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {boolean}
- * @private
- */
- showDisplaySelectMenu_: function(displays, selectedDisplay) {
- if (selectedDisplay) {
- return displays.length > 1 && !selectedDisplay.isPrimary;
- }
-
- return false;
- },
-
- /**
- * Returns the select menu index indicating whether the display currently is
- * primary or extended.
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @param {string} primaryDisplayId
- * @return {number} Returns 0 if the display is primary else returns 1.
- * @private
- */
- getDisplaySelectMenuIndex_: function(selectedDisplay, primaryDisplayId) {
- if (selectedDisplay && selectedDisplay.id == primaryDisplayId) {
- return 0;
- }
- return 1;
- },
-
- /**
- * Returns the i18n string for the text to be used for mirroring settings.
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @return {string} i18n string for mirroring settings text.
- * @private
- */
- getDisplayMirrorText_: function(displays) {
- return this.i18n('displayMirror', displays[0].name);
- },
-
- /**
- * @param {boolean} unifiedDesktopAvailable
- * @param {boolean} unifiedDesktopMode
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @return {boolean}
- * @private
- */
- showUnifiedDesktop_: function(
- unifiedDesktopAvailable, unifiedDesktopMode, displays) {
- if (displays === undefined) {
- return false;
- }
-
- return unifiedDesktopMode ||
- (unifiedDesktopAvailable && displays.length > 1 &&
- !this.isMirrored_(displays));
- },
-
- /**
- * @param {boolean} unifiedDesktopMode
- * @return {string}
- * @private
- */
- getUnifiedDesktopText_: function(unifiedDesktopMode) {
- return this.i18n(unifiedDesktopMode ? 'toggleOn' : 'toggleOff');
- },
-
- /**
- * @param {boolean} unifiedDesktopMode
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @return {boolean}
- * @private
- */
- showMirror_: function(unifiedDesktopMode, displays) {
- if (displays === undefined) {
- return false;
- }
-
- return this.isMirrored_(displays) ||
- (!unifiedDesktopMode && displays.length > 1);
- },
-
- /**
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @return {boolean}
- * @private
- */
- isMirrored_: function(displays) {
- return displays !== undefined && displays.length > 0 &&
- !!displays[0].mirroringSourceId;
- },
-
- /**
- * @param {!chrome.system.display.DisplayUnitInfo} display
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {boolean}
- * @private
- */
- isSelected_: function(display, selectedDisplay) {
- return display.id == selectedDisplay.id;
- },
-
- /**
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {boolean}
- * @private
- */
- enableSetResolution_: function(selectedDisplay) {
- return selectedDisplay.modes.length > 1;
- },
-
- /**
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {boolean}
- * @private
- */
- enableDisplayZoomSlider_: function(selectedDisplay) {
- return selectedDisplay.availableDisplayZoomFactors.length > 1;
- },
-
- /**
- * Returns true if the given mode is the best mode for the |selectedDisplay|.
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @param {!chrome.system.display.DisplayMode} mode
- * @return {boolean}
- * @private
- */
- isBestMode_: function(selectedDisplay, mode) {
- if (!selectedDisplay.isInternal) {
- return mode.isNative;
- }
-
- // Things work differently for full HD devices(1080p). The best mode is the
- // one with 1.25 device scale factor and 0.8 ui scale.
- if (mode.heightInNativePixels == 1080) {
- return Math.abs(mode.uiScale - 0.8) < 0.001 &&
- Math.abs(mode.deviceScaleFactor - 1.25) < 0.001;
- }
-
- return mode.uiScale == 1.0;
- },
-
- /**
- * @return {string}
- * @private
- */
- getResolutionText_: function() {
- if (this.selectedDisplay.modes.length == 0 ||
- this.currentSelectedModeIndex_ == -1) {
- // If currentSelectedModeIndex_ == -1, selectedDisplay and
- // |selectedModePref_.value| are not in sync.
- return this.i18n(
- 'displayResolutionText', this.selectedDisplay.bounds.width.toString(),
- this.selectedDisplay.bounds.height.toString());
- }
- const mode = this.selectedDisplay.modes[
- /** @type {number} */ (this.selectedModePref_.value)];
- assert(mode);
- const widthStr = mode.width.toString();
- const heightStr = mode.height.toString();
- if (this.isBestMode_(this.selectedDisplay, mode)) {
- return this.i18n('displayResolutionTextBest', widthStr, heightStr);
- } else if (mode.isNative) {
- return this.i18n('displayResolutionTextNative', widthStr, heightStr);
- }
- return this.i18n('displayResolutionText', widthStr, heightStr);
- },
-
- /**
- * Updates the logical resolution text to be used for the display size section
- * @param {number} zoomFactor Current zoom factor applied on the selected
- * display.
- * @private
- */
- updateLogicalResolutionText_: function(zoomFactor) {
- if (!this.selectedDisplay.isInternal) {
- this.logicalResolutionText_ = '';
- return;
- }
- const mode = this.selectedDisplay.modes[
- /** @type {number} */ (this.selectedModePref_.value)];
- const deviceScaleFactor = mode.deviceScaleFactor;
- const inverseZoomFactor = 1.0 / zoomFactor;
- let logicalResolutionStrId = 'displayZoomLogicalResolutionText';
- if (Math.abs(deviceScaleFactor - inverseZoomFactor) < 0.001) {
- logicalResolutionStrId = 'displayZoomNativeLogicalResolutionNativeText';
- } else if (Math.abs(inverseZoomFactor - 1.0) < 0.001) {
- logicalResolutionStrId = 'displayZoomLogicalResolutionDefaultText';
- }
- const widthStr =
- Math.round(mode.widthInNativePixels / (deviceScaleFactor * zoomFactor))
- .toString();
- const heightStr =
- Math.round(mode.heightInNativePixels / (deviceScaleFactor * zoomFactor))
- .toString();
- this.logicalResolutionText_ =
- this.i18n(logicalResolutionStrId, widthStr, heightStr);
- },
-
- /**
- * Handles the event where the display size slider is being dragged, i.e. the
- * mouse or tap has not been released.
- * @private
- */
- onDisplaySizeSliderDrag_: function() {
- if (!this.selectedDisplay) {
- return;
- }
-
- const sliderValue = this.$.displaySizeSlider.$$('#slider').value;
- const zoomFactor = this.$.displaySizeSlider.ticks[sliderValue].value;
- this.updateLogicalResolutionText_(
- /** @type {number} */ (zoomFactor));
- },
-
- /**
- * @param {!CustomEvent<string>} e |e.detail| is the id of the selected
- * display.
- * @private
- */
- onSelectDisplay_: function(e) {
- const id = e.detail;
- for (let i = 0; i < this.displays.length; ++i) {
- const display = this.displays[i];
- if (id == display.id) {
- if (this.selectedDisplay != display) {
- this.setSelectedDisplay_(display);
- }
- return;
- }
- }
- },
-
- /** @private */
- onSelectDisplayTab_: function() {
- const {selected} = this.$$('cr-tabs');
- if (this.selectedTab_ != selected) {
- this.setSelectedDisplay_(this.displays[selected]);
- }
- },
-
- /**
- * Handles event when a touch calibration option is selected.
- * @param {!Event} e
- * @private
- */
- onTouchCalibrationTap_: function(e) {
- settings.display.systemDisplayApi.showNativeTouchCalibration(
- this.selectedDisplay.id);
- },
-
- /**
- * Handles the event when an option from display select menu is selected.
- * @param {!{target: !HTMLSelectElement}} e
- * @private
- */
- updatePrimaryDisplay_: function(e) {
- /** @type {number} */ const PRIMARY_DISP_IDX = 0;
- if (!this.selectedDisplay) {
- return;
- }
- if (this.selectedDisplay.id == this.primaryDisplayId) {
- return;
- }
- if (e.target.value != PRIMARY_DISP_IDX) {
- return;
- }
-
- /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
- isPrimary: true
- };
- settings.display.systemDisplayApi.setDisplayProperties(
- this.selectedDisplay.id, properties,
- this.setPropertiesCallback_.bind(this));
- },
-
- /**
- * Updates the selected mode based on the latest pref value.
- * @private
- */
- onSelectedModeSliderChange_: function() {
- if (this.currentSelectedModeIndex_ == -1 ||
- this.currentSelectedModeIndex_ == this.selectedModePref_.value) {
- // Don't change the selected display mode until we have received an update
- // from Chrome and the mode differs from the current mode.
- return;
- }
- /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
- displayMode: this.selectedDisplay.modes[
- /** @type {number} */ (this.selectedModePref_.value)]
- };
- settings.display.systemDisplayApi.setDisplayProperties(
- this.selectedDisplay.id, properties,
- this.setPropertiesCallback_.bind(this));
- },
-
- /**
- * Handles a change in the |selectedModePref| value triggered via the observer
- * @param {number} newModeIndex The new index value for which thie function is
- * called.
- * @private
- */
- onSelectedModeChange_: function(newModeIndex) {
- // We want to ignore all value changes to the pref due to the slider being
- // dragged. See http://crbug/845712 for more info.
- if (this.currentSelectedModeIndex_ == newModeIndex) {
- return;
- }
- this.onSelectedModeSliderChange_();
- },
-
- /**
- * Triggerend when the display size slider changes its value. This only
- * occurs when the value is committed (i.e. not while the slider is being
- * dragged).
- * @private
- */
- onSelectedZoomChange_: function() {
- if (this.currentSelectedModeIndex_ == -1 || !this.selectedDisplay) {
- return;
- }
-
- /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
- displayZoomFactor:
- /** @type {number} */ (this.selectedZoomPref_.value)
- };
-
- settings.display.systemDisplayApi.setDisplayProperties(
- this.selectedDisplay.id, properties,
- this.setPropertiesCallback_.bind(this));
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onOrientationChange_: function(event) {
- const target = /** @type {!HTMLSelectElement} */ (event.target);
- /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
- rotation: parseInt(target.value, 10)
- };
- settings.display.systemDisplayApi.setDisplayProperties(
- this.selectedDisplay.id, properties,
- this.setPropertiesCallback_.bind(this));
- },
-
- /** @private */
- onMirroredTap_: function(event) {
- // Blur the control so that when the transition animation completes and the
- // UI is focused, the control does not receive focus. crbug.com/785070
- event.target.blur();
-
- /** @type {!chrome.system.display.MirrorModeInfo} */
- const mirrorModeInfo = {
- mode: this.isMirrored_(this.displays) ?
- chrome.system.display.MirrorMode.OFF :
- chrome.system.display.MirrorMode.NORMAL
- };
- settings.display.systemDisplayApi.setMirrorMode(mirrorModeInfo, () => {
- const error = chrome.runtime.lastError;
- if (error) {
- console.error('setMirrorMode Error: ' + error.message);
- }
- });
- },
-
- /** @private */
- onUnifiedDesktopTap_: function() {
- /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
- isUnified: !this.unifiedDesktopMode_,
- };
- settings.display.systemDisplayApi.setDisplayProperties(
- this.primaryDisplayId, properties,
- this.setPropertiesCallback_.bind(this));
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onOverscanTap_: function(e) {
- e.preventDefault();
- this.overscanDisplayId = this.selectedDisplay.id;
- this.showOverscanDialog_(true);
- },
-
- /** @private */
- onCloseOverscanDialog_: function() {
- cr.ui.focusWithoutInk(assert(this.$$('#overscan')));
- },
-
- /** @private */
- updateDisplayInfo_: function() {
- let displayIds = '';
- let primaryDisplay = undefined;
- let selectedDisplay = undefined;
- for (let i = 0; i < this.displays.length; ++i) {
- const display = this.displays[i];
- if (displayIds) {
- displayIds += ',';
- }
- displayIds += display.id;
- if (display.isPrimary && !primaryDisplay) {
- primaryDisplay = display;
- }
- if (this.selectedDisplay && display.id == this.selectedDisplay.id) {
- selectedDisplay = display;
- }
- }
- this.displayIds = displayIds;
- this.primaryDisplayId = (primaryDisplay && primaryDisplay.id) || '';
- selectedDisplay = selectedDisplay || primaryDisplay ||
- (this.displays && this.displays[0]);
- this.setSelectedDisplay_(selectedDisplay);
-
- this.unifiedDesktopMode_ = !!primaryDisplay && primaryDisplay.isUnified;
-
- this.$.displayLayout.updateDisplays(
- this.displays, this.layouts, this.mirroringDestinationIds);
- },
-
- /** @private */
- setPropertiesCallback_: function() {
- if (chrome.runtime.lastError) {
- console.error(
- 'setDisplayProperties Error: ' + chrome.runtime.lastError.message);
- }
- },
-
- /**
- * Invoked when the status of Night Light or its schedule type are changed, in
- * order to update the schedule settings, such as whether to show the custom
- * schedule slider, and the schedule sub label.
- * @private
- */
- updateNightLightScheduleSettings_: function() {
- const scheduleType = this.getPref('ash.night_light.schedule_type').value;
- this.shouldOpenCustomScheduleCollapse_ =
- scheduleType == NightLightScheduleType.CUSTOM;
-
- if (scheduleType == NightLightScheduleType.SUNSET_TO_SUNRISE) {
- const nightLightStatus = this.getPref('ash.night_light.enabled').value;
- this.nightLightScheduleSubLabel_ = nightLightStatus ?
- this.i18n('displayNightLightOffAtSunrise') :
- this.i18n('displayNightLightOnAtSunset');
- } else {
- this.nightLightScheduleSubLabel_ = '';
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/display_layout.html b/chromium/chrome/browser/resources/settings/device_page/display_layout.html
deleted file mode 100644
index 4b138cb2b17..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/display_layout.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/shadow.html">
-<link rel="import" href="drag_behavior.html">
-<link rel="import" href="layout_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="display-layout">
- <template>
- <style include="settings-shared">
- /* Use relative position with no offset so that display divs (children),
- which have absolute positions, are offset from the displayArea div. */
- #displayArea {
- height: 100%;
- overflow: hidden;
- position: relative;
- width: 100%;
- }
-
- /* Note: the size of the border / box shadow affects the style generated
- in getDivStyle_ and getMirrorDivStyle_ */
- .display {
- align-items: center;
- background: var(--paper-grey-100);
- color: var(--paper-grey-700);
- cursor: default;
- display: flex;
- font-size: 100%;
- font-weight: 500;
- justify-content: center;
- margin: 4px;
- padding: 3px;
- position: absolute;
- text-align: center;
- }
- .display[selected] {
- border: var(--google-blue-500) solid 1px;
- }
- .display.mirror {
- border: var(--google-blue-500) solid 1px;
- }
- .highlight-left {
- border-left: var(--google-blue-700) solid 1px;
- }
- .highlight-right {
- border-right: var(--google-blue-700) solid 1px;
- }
- .highlight-top {
- border-top: var(--google-blue-700) solid 1px;
- }
- .highlight-bottom {
- border-bottom: var(--google-blue-700) solid 1px;
- }
-
- .display.elevate {
- @apply --shadow-elevation-2dp;
- }
- </style>
- <div id="displayArea" on-iron-resize="calculateVisualScale_">
- <template is="dom-repeat" items="[[mirroringDestinationIds_]]">
- <div id="_mirror_[[item]]" class="display mirror"
- hidden$="[[!mirroring]]"
- style$="[[getMirrorDivStyle_(index, mirroringDestinationIds_.length,
- displays, visualScale)]]">
- </div>
- </template>
- <template is="dom-repeat" items="[[displays]]">
- <div id="_[[item.id]]" class="display elevate"
- draggable="[[dragEnabled]]" on-click="onSelectDisplayTap_"
- style$="[[getDivStyle_(item.id, item.bounds, visualScale)]]"
- selected$="[[isSelected_(item, selectedDisplay)]]">
- [[getDisplayName_(mirroring, item.name,
- '$i18nPolymer{displayMirrorDisplayName}')]]
- </div>
- </template>
- </div>
- </template>
- <script src="display_layout.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/display_layout.js b/chromium/chrome/browser/resources/settings/device_page/display_layout.js
deleted file mode 100644
index ad91d4e47ab..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/display_layout.js
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'display-layout' presents a visual representation of the layout of one or
- * more displays and allows them to be arranged.
- */
-
-(function() {
-
-/** @type {number} */ const MIN_VISUAL_SCALE = .01;
-
-Polymer({
- is: 'display-layout',
-
- behaviors: [
- Polymer.IronResizableBehavior,
- DragBehavior,
- LayoutBehavior,
- ],
-
- properties: {
- /**
- * Array of displays.
- * @type {!Array<!chrome.system.display.DisplayUnitInfo>}
- */
- displays: Array,
-
- /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */
- selectedDisplay: Object,
-
- /**
- * The ratio of the display area div (in px) to DisplayUnitInfo.bounds.
- * @type {number}
- */
- visualScale: {
- type: Number,
- value: 1,
- },
-
- /**
- * Ids for mirroring destination displays.
- * @type {!Array<string>|undefined}
- * @private
- */
- mirroringDestinationIds_: Array,
- },
-
- /** @private {!{left: number, top: number}} */
- visualOffset_: {left: 0, top: 0},
-
- /** @override */
- detached: function() {
- this.initializeDrag(false);
- },
-
- /**
- * Called explicitly when |this.displays| and their associated |this.layouts|
- * have been fetched from chrome.
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @param {!Array<!chrome.system.display.DisplayLayout>} layouts
- * @param {!Array<string>} mirroringDestinationIds
- */
- updateDisplays: function(displays, layouts, mirroringDestinationIds) {
- this.displays = displays;
- this.layouts = layouts;
- this.mirroringDestinationIds_ = mirroringDestinationIds;
-
- this.initializeDisplayLayout(displays, layouts);
-
- const self = this;
- const retry = 100; // ms
- function tryCalcVisualScale() {
- if (!self.calculateVisualScale_()) {
- setTimeout(tryCalcVisualScale, retry);
- }
- }
- tryCalcVisualScale();
-
- this.initializeDrag(
- !this.mirroring, this.$.displayArea, this.onDrag_.bind(this));
- },
-
- /**
- * Calculates the visual offset and scale for the display area
- * (i.e. the ratio of the display area div size to the area required to
- * contain the DisplayUnitInfo bounding boxes).
- * @return {boolean} Whether the calculation was successful.
- * @private
- */
- calculateVisualScale_: function() {
- const displayAreaDiv = this.$.displayArea;
- if (!displayAreaDiv || !displayAreaDiv.offsetWidth || !this.displays ||
- !this.displays.length) {
- return false;
- }
-
- let display = this.displays[0];
- let bounds = this.getCalculatedDisplayBounds(display.id);
- const boundsBoundingBox = {
- left: bounds.left,
- right: bounds.left + bounds.width,
- top: bounds.top,
- bottom: bounds.top + bounds.height,
- };
- let maxWidth = bounds.width;
- let maxHeight = bounds.height;
- for (let i = 1; i < this.displays.length; ++i) {
- display = this.displays[i];
- bounds = this.getCalculatedDisplayBounds(display.id);
- boundsBoundingBox.left = Math.min(boundsBoundingBox.left, bounds.left);
- boundsBoundingBox.right =
- Math.max(boundsBoundingBox.right, bounds.left + bounds.width);
- boundsBoundingBox.top = Math.min(boundsBoundingBox.top, bounds.top);
- boundsBoundingBox.bottom =
- Math.max(boundsBoundingBox.bottom, bounds.top + bounds.height);
- maxWidth = Math.max(maxWidth, bounds.width);
- maxHeight = Math.max(maxHeight, bounds.height);
- }
-
- // Create a margin around the bounding box equal to the size of the
- // largest displays.
- const boundsWidth = boundsBoundingBox.right - boundsBoundingBox.left;
- const boundsHeight = boundsBoundingBox.bottom - boundsBoundingBox.top;
-
- // Calculate the scale.
- const horizontalScale =
- displayAreaDiv.offsetWidth / (boundsWidth + maxWidth * 2);
- const verticalScale =
- displayAreaDiv.offsetHeight / (boundsHeight + maxHeight * 2);
- const scale = Math.min(horizontalScale, verticalScale);
-
- // Calculate the offset.
- this.visualOffset_.left =
- ((displayAreaDiv.offsetWidth - (boundsWidth * scale)) / 2) -
- boundsBoundingBox.left * scale;
- this.visualOffset_.top =
- ((displayAreaDiv.offsetHeight - (boundsHeight * scale)) / 2) -
- boundsBoundingBox.top * scale;
-
- // Update the scale which will trigger calls to getDivStyle_.
- this.visualScale = Math.max(MIN_VISUAL_SCALE, scale);
-
- return true;
- },
-
- /**
- * @param {string} id
- * @param {!chrome.system.display.Bounds} displayBounds
- * @param {number} visualScale
- * @param {number=} opt_offset
- * @return {string} The style string for the div.
- * @private
- */
- getDivStyle_: function(id, displayBounds, visualScale, opt_offset) {
- // This matches the size of the box-shadow or border in CSS.
- /** @type {number} */ const BORDER = 1;
- /** @type {number} */ const MARGIN = 4;
- /** @type {number} */ const OFFSET = opt_offset || 0;
- /** @type {number} */ const PADDING = 3;
- const bounds = this.getCalculatedDisplayBounds(id, true /* notest */);
- if (!bounds) {
- return '';
- }
- const height = Math.round(bounds.height * this.visualScale) - BORDER * 2 -
- MARGIN * 2 - PADDING * 2;
- const width = Math.round(bounds.width * this.visualScale) - BORDER * 2 -
- MARGIN * 2 - PADDING * 2;
- const left = OFFSET +
- Math.round(this.visualOffset_.left + (bounds.left * this.visualScale));
- const top = OFFSET +
- Math.round(this.visualOffset_.top + (bounds.top * this.visualScale));
- return 'height: ' + height + 'px; width: ' + width + 'px;' +
- ' left: ' + left + 'px; top: ' + top + 'px';
- },
-
- /**
- * @param {number} mirroringDestinationIndex
- * @param {number} mirroringDestinationDisplayNum
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @param {number} visualScale
- * @return {string} The style string for the mirror div.
- * @private
- */
- getMirrorDivStyle_: function(
- mirroringDestinationIndex, mirroringDestinationDisplayNum, displays,
- visualScale) {
- // All destination displays have the same bounds as the mirroring source
- // display, but we add a little offset to each destination display's bounds
- // so that they can be distinguished from each other in the layout.
- return this.getDivStyle_(
- displays[0].id, displays[0].bounds, visualScale,
- (mirroringDestinationDisplayNum - mirroringDestinationIndex) * -4);
- },
-
- /**
- * @param {boolean} mirroring
- * @param {string} displayName
- * @param {string} mirroringName
- * @return {string}
- * @private
- */
- getDisplayName_: function(mirroring, displayName, mirroringName) {
- return mirroring ? mirroringName : displayName;
- },
-
- /**
- * @param {!chrome.system.display.DisplayUnitInfo} display
- * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
- * @return {boolean}
- * @private
- */
- isSelected_: function(display, selectedDisplay) {
- return display.id == selectedDisplay.id;
- },
-
- /**
- * @param {!{model: !{item: !chrome.system.display.DisplayUnitInfo},
- * target: !HTMLDivElement}} e
- * @private
- */
- onSelectDisplayTap_: function(e) {
- this.fire('select-display', e.model.item.id);
- // Force active in case the selected display was clicked.
- // TODO(dpapad): Ask @stevenjb, why are we setting 'active' on a div?
- e.target.active = true;
- },
-
- /**
- * @param {string} id
- * @param {?DragPosition} amount
- */
- onDrag_: function(id, amount) {
- id = id.substr(1); // Skip prefix
-
- let newBounds;
- if (!amount) {
- this.finishUpdateDisplayBounds(id);
- newBounds = this.getCalculatedDisplayBounds(id);
- } else {
- // Make sure the dragged display is also selected.
- if (id != this.selectedDisplay.id) {
- this.fire('select-display', id);
- }
-
- const calculatedBounds = this.getCalculatedDisplayBounds(id);
- newBounds =
- /** @type {chrome.system.display.Bounds} */ (
- Object.assign({}, calculatedBounds));
- newBounds.left += Math.round(amount.x / this.visualScale);
- newBounds.top += Math.round(amount.y / this.visualScale);
-
- if (this.displays.length >= 2) {
- newBounds = this.updateDisplayBounds(id, newBounds);
- }
- }
- const left =
- this.visualOffset_.left + Math.round(newBounds.left * this.visualScale);
- const top =
- this.visualOffset_.top + Math.round(newBounds.top * this.visualScale);
- const div = this.$$('#_' + id);
- div.style.left = '' + left + 'px';
- div.style.top = '' + top + 'px';
- },
-
-});
-
-})();
diff --git a/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.html b/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.html
deleted file mode 100644
index 7fcd324f6fe..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../chromeos/os_icons.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-display-overscan-dialog">
- <template>
- <style include="settings-shared iron-flex iron-flex-alignment">
- .subtitle {
- margin-top: 10px;
- }
-
- .instructions {
- color: var(--paper-grey-600);
- margin-top: 4px;
- }
-
- .details {
- margin: 40px;
- }
-
- iron-icon {
- --iron-icon-fill-color: white;
- background: black;
- margin: 5px;
- }
-
- #move > div {
- color: grey;
- font-size: 150%;
- }
-
- #move > div.shift {
- background: black;
- color: white;
- font-size: 100%;
- margin: 0 8px;
- padding: 4px 8px 0;
- }
- </style>
- <cr-dialog id="dialog" on-close="close"
- close-text="$i18n{close}">
- <div slot="title">$i18n{displayOverscanPageTitle}</div>
- <div slot="body">
- <div class="subtitle" >$i18n{displayOverscanSubtitle}</div>
- <div class="instructions" >$i18n{displayOverscanInstructions}</div>
- <div class="details layout horizontal around-justified self-stretch">
- <div class="layout vertical center">
- <div class="layout horizontal">
- <iron-icon icon="cr:expand-less"></iron-icon>
- </div>
- <div class="layout horizontal">
- <iron-icon icon="os-settings:chevron-left"></iron-icon>
- <iron-icon icon="cr:expand-more"></iron-icon>
- <iron-icon icon="cr:chevron-right"></iron-icon>
- </div>
- <div class="label" >$i18n{displayOverscanResize}</div>
- </div>
- <div class="layout vertical center">
- <div class="layout vertical center-justified flex">
- <div id="move" class="layout horizontal">
- <!-- TODO(stevenjb): Localize 'shift' for other keyboards -->
- <!-- crbug.com/614827 -->
- <div>( + </div><div class="shift">shift</div><div>)</div>
- </div>
- </div>
- <div class="label">$i18n{displayOverscanPosition}</div>
- </div>
- </div>
- </div>
- <div slot="button-container">
- <cr-button id="reset" class="cancel-button" on-click="onResetTap_">
- $i18n{displayOverscanReset}
- </cr-button>
- <cr-button class="action-button" on-click="onSaveTap_">
- $i18n{ok}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="display_overscan_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.js b/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.js
deleted file mode 100644
index bff52734166..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.js
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-display-overscan-dialog' is the dialog for display overscan
- * adjustments.
- */
-
-Polymer({
- is: 'settings-display-overscan-dialog',
-
- properties: {
- /** Id of the display for which overscan is being applied (or empty). */
- displayId: {
- type: String,
- notify: true,
- observer: 'displayIdChanged_',
- },
-
- /** Set to true once changes are saved to avoid a reset/cancel on close. */
- committed_: Boolean,
- },
-
- /**
- * Keyboard event handler for overscan adjustments.
- * @type {?function(!Event)}
- * @private
- */
- keyHandler_: null,
-
- open: function() {
- this.keyHandler_ = this.handleKeyEvent_.bind(this);
- // We need to attach the event listener to |window|, not |this| so that
- // changing focus does not prevent key events from occurring.
- window.addEventListener('keydown', this.keyHandler_);
- this.committed_ = false;
- this.$.dialog.showModal();
- // Don't focus 'reset' by default. 'Tab' will focus 'OK'.
- this.$$('#reset').blur();
- },
-
- close: function() {
- window.removeEventListener('keydown', this.keyHandler_);
-
- this.displayId = ''; // Will trigger displayIdChanged_.
-
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
- },
-
- /** @private */
- displayIdChanged_: function(newValue, oldValue) {
- if (oldValue && !this.committed_) {
- settings.display.systemDisplayApi.overscanCalibrationReset(oldValue);
- settings.display.systemDisplayApi.overscanCalibrationComplete(oldValue);
- }
- if (!newValue) {
- return;
- }
- this.committed_ = false;
- settings.display.systemDisplayApi.overscanCalibrationStart(newValue);
- },
-
- /** @private */
- onResetTap_: function() {
- settings.display.systemDisplayApi.overscanCalibrationReset(this.displayId);
- },
-
- /** @private */
- onSaveTap_: function() {
- settings.display.systemDisplayApi.overscanCalibrationComplete(
- this.displayId);
- this.committed_ = true;
- this.close();
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- handleKeyEvent_: function(event) {
- if (event.altKey || event.ctrlKey || event.metaKey) {
- return;
- }
- switch (event.keyCode) {
- case 37: // left arrow
- if (event.shiftKey) {
- this.move_(-1, 0);
- } else {
- this.resize_(1, 0);
- }
- break;
- case 38: // up arrow
- if (event.shiftKey) {
- this.move_(0, -1);
- } else {
- this.resize_(0, -1);
- }
- break;
- case 39: // right arrow
- if (event.shiftKey) {
- this.move_(1, 0);
- } else {
- this.resize_(-1, 0);
- }
- break;
- case 40: // down arrow
- if (event.shiftKey) {
- this.move_(0, 1);
- } else {
- this.resize_(0, 1);
- }
- break;
- default:
- // Allow unhandled key events to propagate.
- return;
- }
- event.preventDefault();
- },
-
- /**
- * @param {number} x
- * @param {number} y
- * @private
- */
- move_: function(x, y) {
- /** @type {!chrome.system.display.Insets} */ const delta = {
- left: x,
- top: y,
- right: x ? -x : 0, // negating 0 will produce a double.
- bottom: y ? -y : 0,
- };
- settings.display.systemDisplayApi.overscanCalibrationAdjust(
- this.displayId, delta);
- },
-
- /**
- * @param {number} x
- * @param {number} y
- * @private
- */
- resize_: function(x, y) {
- /** @type {!chrome.system.display.Insets} */ const delta = {
- left: x,
- top: y,
- right: x,
- bottom: y,
- };
- settings.display.systemDisplayApi.overscanCalibrationAdjust(
- this.displayId, delta);
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/drag_behavior.html b/chromium/chrome/browser/resources/settings/device_page/drag_behavior.html
deleted file mode 100644
index 3612890a99a..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/drag_behavior.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="drag_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/device_page/drag_behavior.js b/chromium/chrome/browser/resources/settings/device_page/drag_behavior.js
deleted file mode 100644
index b7b9b36e8ed..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/drag_behavior.js
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Behavior for handling dragging elements in a container.
- * Draggable elements must have the 'draggable' attribute set.
- */
-
-/**
- * @typedef {{
- * x: number,
- * y: number
- * }}
- */
-let DragPosition;
-
-/** @polymerBehavior */
-const DragBehavior = {
- properties: {
- /** Whether or not drag is enabled (e.g. not mirrored). */
- dragEnabled: Boolean,
- },
-
- /**
- * The id of the element being dragged, or empty if not dragging.
- * @protected {string}
- */
- dragId: '',
-
- /** @private {!HTMLDivElement|undefined} */
- container_: undefined,
-
- /** @private {?function(string, ?DragPosition):void} */
- callback_: null,
-
- /** @private {!DragPosition} */
- dragStartLocation_: {x: 0, y: 0},
-
- /**
- * Used to ignore unnecessary drag events.
- * @private {?DragPosition}
- */
- lastTouchLocation_: null,
-
- /** @private {?function(!Event)} */
- mouseDownListener_: null,
-
- /** @private {?function(!Event)} */
- mouseMoveListener_: null,
-
- /** @private {?function(!Event)} */
- touchStartListener_: null,
-
- /** @private {?function(!Event)} */
- touchMoveListener_: null,
-
- /** @private {?function(!Event)} */
- endDragListener_: null,
-
- /**
- * @param {boolean} enabled
- * @param {!HTMLDivElement=} opt_container
- * @param {!function(string, ?DragPosition):void=} opt_callback
- */
- initializeDrag: function(enabled, opt_container, opt_callback) {
- this.dragEnabled = enabled;
- if (!enabled) {
- this.removeListeners_();
- return;
- }
-
- if (opt_container !== undefined) {
- this.container_ = opt_container;
- }
-
- this.addListeners_();
-
- if (opt_callback !== undefined) {
- this.callback_ = opt_callback;
- }
- },
-
- /** @private */
- addListeners_: function() {
- const container = this.container_;
- if (!container || this.mouseDownListener_) {
- return;
- }
- this.mouseDownListener_ = this.onMouseDown_.bind(this);
- container.addEventListener('mousedown', this.mouseDownListener_);
-
- this.mouseMoveListener_ = this.onMouseMove_.bind(this);
- container.addEventListener('mousemove', this.mouseMoveListener_);
-
- this.touchStartListener_ = this.onTouchStart_.bind(this);
- container.addEventListener('touchstart', this.touchStartListener_);
-
- this.touchMoveListener_ = this.onTouchMove_.bind(this);
- container.addEventListener('touchmove', this.touchMoveListener_);
-
- this.endDragListener_ = this.endDrag_.bind(this);
- window.addEventListener('mouseup', this.endDragListener_);
- container.addEventListener('touchend', this.endDragListener_);
- },
-
- /** @private */
- removeListeners_: function() {
- const container = this.container_;
- if (!container || !this.mouseDownListener_) {
- return;
- }
- container.removeEventListener('mousedown', this.mouseDownListener_);
- this.mouseDownListener_ = null;
- container.removeEventListener('mousemove', this.mouseMoveListener_);
- this.mouseMoveListener_ = null;
- container.removeEventListener('touchstart', this.touchStartListener_);
- this.touchStartListener_ = null;
- container.removeEventListener('touchmove', this.touchMoveListener_);
- this.touchMoveListener_ = null;
- container.removeEventListener('touchend', this.endDragListener_);
- if (this.endDragListener_) {
- window.removeEventListener('mouseup', this.endDragListener_);
- }
- this.endDragListener_ = null;
- },
-
- /**
- * @param {!Event} e The mouse down event.
- * @return {boolean}
- * @private
- */
- onMouseDown_: function(e) {
- if (e.button != 0 || !e.target.getAttribute('draggable')) {
- return true;
- }
- e.preventDefault();
- const target = assertInstanceof(e.target, HTMLElement);
- return this.startDrag_(target, {x: e.pageX, y: e.pageY});
- },
-
- /**
- * @param {!Event} e The mouse move event.
- * @return {boolean}
- * @private
- */
- onMouseMove_: function(e) {
- e.preventDefault();
- return this.processDrag_(e, {x: e.pageX, y: e.pageY});
- },
-
- /**
- * @param {!Event} e The touch start event.
- * @return {boolean}
- * @private
- */
- onTouchStart_: function(e) {
- if (e.touches.length != 1) {
- return false;
- }
-
- e.preventDefault();
- const touch = e.touches[0];
- this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
- const target = assertInstanceof(e.target, HTMLElement);
- return this.startDrag_(target, this.lastTouchLocation_);
- },
-
- /**
- * @param {!Event} e The touch move event.
- * @return {boolean}
- * @private
- */
- onTouchMove_: function(e) {
- if (e.touches.length != 1) {
- return true;
- }
-
- const touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
- // Touch move events can happen even if the touch location doesn't change
- // and on small unintentional finger movements. Ignore these small changes.
- if (this.lastTouchLocation_) {
- const IGNORABLE_TOUCH_MOVE_PX = 1;
- const xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
- const yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
- if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
- yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
- return true;
- }
- }
- this.lastTouchLocation_ = touchLocation;
- e.preventDefault();
- return this.processDrag_(e, touchLocation);
- },
-
- /**
- * @param {!HTMLElement} target
- * @param {!DragPosition} eventLocation
- * @return {boolean}
- * @private
- */
- startDrag_: function(target, eventLocation) {
- assert(this.dragEnabled);
- this.dragId = target.id;
- this.dragStartLocation_ = eventLocation;
- return false;
- },
-
- /**
- * @param {!Event} e
- * @return {boolean}
- * @private
- */
- endDrag_: function(e) {
- assert(this.dragEnabled);
- if (this.dragId && this.callback_) {
- this.callback_(this.dragId, null);
- }
- this.dragId = '';
- this.lastTouchLocation_ = null;
- return false;
- },
-
- /**
- * @param {!Event} e The event which triggers this drag.
- * @param {DragPosition} eventLocation The location of the event.
- * @return {boolean}
- * @private
- */
- processDrag_: function(e, eventLocation) {
- assert(this.dragEnabled);
- if (!this.dragId) {
- return true;
- }
- if (this.callback_) {
- const delta = {
- x: eventLocation.x - this.dragStartLocation_.x,
- y: eventLocation.y - this.dragStartLocation_.y,
- };
- this.callback_(this.dragId, delta);
- }
- return false;
- },
-};
diff --git a/chromium/chrome/browser/resources/settings/device_page/keyboard.html b/chromium/chrome/browser/resources/settings/device_page/keyboard.html
deleted file mode 100644
index c88a6f7bb5f..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/keyboard.html
+++ /dev/null
@@ -1,139 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="../controls/settings_dropdown_menu.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-keyboard">
- <template>
- <style include="settings-shared"></style>
- <template is="dom-if" if="[[hasInternalKeyboard_]]">
- <div class="settings-box first" id="internalSearchKey">
- <div class="start">$i18n{keyboardKeySearch}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeySearch}"
- pref="{{prefs.settings.language.xkb_remap_search_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <div class="settings-box">
- <div class="start">$i18n{keyboardKeyCtrl}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeyCtrl}"
- pref="{{prefs.settings.language.xkb_remap_control_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- <div class="settings-box">
- <div class="start">$i18n{keyboardKeyAlt}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeyAlt}"
- pref="{{prefs.settings.language.xkb_remap_alt_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- <div class="settings-box">
- <div class="start">$i18n{keyboardKeyEscape}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeyEscape}"
- pref="{{prefs.settings.language.remap_escape_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- <div class="settings-box">
- <div class="start">$i18n{keyboardKeyBackspace}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeyBackspace}"
- pref="{{prefs.settings.language.remap_backspace_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- <template is="dom-if" if="[[hasAssistantKey_]]">
- <div class="settings-box" id="assistantKey">
- <div class="start">$i18n{keyboardKeyAssistant}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeyAssistant}"
- pref="{{prefs.settings.language.xkb_remap_assistant_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <template is="dom-if" if="[[showCapsLock_]]">
- <div class="settings-box" id="capsLockKey">
- <div class="start">$i18n{keyboardKeyCapsLock}</div>
- <settings-dropdown-menu label="$i18n{keyboardKeyCapsLock}"
- pref="{{prefs.settings.language.remap_caps_lock_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <template is="dom-if" if="[[showExternalMetaKey_]]">
- <div class="settings-box" id="externalMetaKey">
- <div class="start">
- [[getExternalMetaKeyLabel_(hasInternalKeyboard_)]]
- </div>
- <settings-dropdown-menu
- label="[[getExternalMetaKeyLabel_(hasInternalKeyboard_)]]"
- pref="{{prefs.settings.language.remap_external_meta_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <template is="dom-if" if="[[showAppleCommandKey_]]">
- <div class="settings-box" id="externalCommandKey">
- <div class="start">
- [[getExternalCommandKeyLabel_(hasInternalKeyboard_)]]
- </div>
- <settings-dropdown-menu
- label="[[getExternalCommandKeyLabel_(hasInternalKeyboard_)]]"
- pref="{{prefs.settings.language.remap_external_command_key_to}}"
- menu-options="[[keyMapTargets_]]">
- </settings-dropdown-menu>
- </div>
- </template>
- <settings-toggle-button
- pref="{{prefs.settings.language.send_function_keys}}"
- label="$i18n{keyboardSendFunctionKeys}"
- sub-label="$i18n{keyboardSendFunctionKeysDescription}">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.settings.language.xkb_auto_repeat_enabled_r2}}"
- label="$i18n{keyboardEnableAutoRepeat}">
- </settings-toggle-button>
- <iron-collapse
- opened="[[prefs.settings.language.xkb_auto_repeat_enabled_r2.value]]">
- <div class="settings-box continuation embedded">
- <div class="start" id="repeatDelayLabel">$i18n{keyRepeatDelay}</div>
- <settings-slider id="delaySlider"
- pref="{{prefs.settings.language.xkb_auto_repeat_delay_r2}}"
- ticks="[[autoRepeatDelays_]]"
- disabled="[[
- !prefs.settings.language.xkb_auto_repeat_enabled_r2.value]]"
- aria-labelledby="repeatDelayLabel"
- label-min="$i18n{keyRepeatDelayLong}"
- label-max="$i18n{keyRepeatDelayShort}">
- </settings-slider>
- </div>
- <div class="settings-box continuation embedded">
- <div class="start" id="repeatRateLabel">$i18n{keyRepeatRate}</div>
- <settings-slider id="repeatRateSlider"
- pref="{{
- prefs.settings.language.xkb_auto_repeat_interval_r2}}"
- ticks="[[autoRepeatIntervals_]]"
- disabled="[[
- !prefs.settings.language.xkb_auto_repeat_enabled_r2.value]]"
- aria-labelledby="repeatRateLabel"
- label-min="$i18n{keyRepeatRateSlow}"
- label-max="$i18n{keyRepeatRateFast}">
- </settings-slider>
- </div>
- </iron-collapse>
- <cr-link-row id="keyboardShortcutViewer" class="hr"
- on-click="onShowKeyboardShortcutViewerTap_"
- label="$i18n{showKeyboardShortcutViewer}" external></cr-link-row>
- <cr-link-row class="hr" on-click="onShowLanguageInputTap_"
- label="$i18n{keyboardShowLanguageAndInput}"></cr-link-row>
- </template>
- <script src="keyboard.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/keyboard.js b/chromium/chrome/browser/resources/settings/device_page/keyboard.js
deleted file mode 100644
index a636592e369..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/keyboard.js
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-keyboard' is the settings subpage with keyboard settings.
- */
-cr.exportPath('settings');
-
-/**
- * Modifier key IDs corresponding to the ModifierKey enumerators in
- * /ui/base/ime/chromeos/ime_keyboard.h.
- * @enum {number}
- */
-settings.ModifierKey = {
- SEARCH_KEY: 0,
- CONTROL_KEY: 1,
- ALT_KEY: 2,
- VOID_KEY: 3, // Represents a disabled key.
- CAPS_LOCK_KEY: 4,
- ESCAPE_KEY: 5,
- BACKSPACE_KEY: 6,
- ASSISTANT_KEY: 7,
-};
-
-Polymer({
- is: 'settings-keyboard',
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private Whether to show Caps Lock options. */
- showCapsLock_: Boolean,
-
- /** @private Whether this device has an internal keyboard. */
- hasInternalKeyboard_: Boolean,
-
- /** @private Whether this device has an Assistant key on keyboard. */
- hasAssistantKey_: Boolean,
-
- /**
- * Whether to show a remapping option for external keyboard's Meta key
- * (Search/Windows keys). This is true only when there's an external
- * keyboard connected that is a non-Apple keyboard.
- * @private
- */
- showExternalMetaKey_: Boolean,
-
- /**
- * Whether to show a remapping option for the Command key. This is true when
- * one of the connected keyboards is an Apple keyboard.
- * @private
- */
- showAppleCommandKey_: Boolean,
-
- /** @private {!DropdownMenuOptionList} Menu items for key mapping. */
- keyMapTargets_: Object,
-
- /**
- * Auto-repeat delays (in ms) for the corresponding slider values, from
- * long to short. The values were chosen to provide a large range while
- * giving several options near the defaults.
- * @private {!Array<number>}
- */
- autoRepeatDelays_: {
- type: Array,
- value: [2000, 1500, 1000, 500, 300, 200, 150],
- readOnly: true,
- },
-
- /**
- * Auto-repeat intervals (in ms) for the corresponding slider values, from
- * long to short. The slider itself is labeled "rate", the inverse of
- * interval, and goes from slow (long interval) to fast (short interval).
- * @private {!Array<number>}
- */
- autoRepeatIntervals_: {
- type: Array,
- value: [2000, 1000, 500, 300, 200, 100, 50, 30, 20],
- readOnly: true,
- },
- },
-
- /** @override */
- ready: function() {
- cr.addWebUIListener('show-keys-changed', this.onShowKeysChange_.bind(this));
- settings.DevicePageBrowserProxyImpl.getInstance().initializeKeyboard();
- this.setUpKeyMapTargets_();
- },
-
- /**
- * Initializes the dropdown menu options for remapping keys.
- * @private
- */
- setUpKeyMapTargets_: function() {
- // Ordering is according to UX, but values match settings.ModifierKey.
- this.keyMapTargets_ = [
- {
- value: settings.ModifierKey.SEARCH_KEY,
- name: loadTimeData.getString('keyboardKeySearch'),
- },
- {
- value: settings.ModifierKey.CONTROL_KEY,
- name: loadTimeData.getString('keyboardKeyCtrl')
- },
- {
- value: settings.ModifierKey.ALT_KEY,
- name: loadTimeData.getString('keyboardKeyAlt')
- },
- {
- value: settings.ModifierKey.CAPS_LOCK_KEY,
- name: loadTimeData.getString('keyboardKeyCapsLock')
- },
- {
- value: settings.ModifierKey.ESCAPE_KEY,
- name: loadTimeData.getString('keyboardKeyEscape')
- },
- {
- value: settings.ModifierKey.BACKSPACE_KEY,
- name: loadTimeData.getString('keyboardKeyBackspace')
- },
- {
- value: settings.ModifierKey.ASSISTANT_KEY,
- name: loadTimeData.getString('keyboardKeyAssistant')
- },
- {
- value: settings.ModifierKey.VOID_KEY,
- name: loadTimeData.getString('keyboardKeyDisabled')
- }
- ];
- },
-
- /**
- * Handler for updating which keys to show.
- * @param {Object} keyboardParams
- * @private
- */
- onShowKeysChange_: function(keyboardParams) {
- this.hasInternalKeyboard_ = keyboardParams['hasInternalKeyboard'];
- this.hasAssistantKey_ = keyboardParams['hasAssistantKey'];
- this.showCapsLock_ = keyboardParams['showCapsLock'];
- this.showExternalMetaKey_ = keyboardParams['showExternalMetaKey'];
- this.showAppleCommandKey_ = keyboardParams['showAppleCommandKey'];
- },
-
- onShowKeyboardShortcutViewerTap_: function() {
- settings.DevicePageBrowserProxyImpl.getInstance()
- .showKeyboardShortcutViewer();
- },
-
- onShowLanguageInputTap_: function() {
- settings.navigateTo(
- settings.routes.LANGUAGES_DETAILS,
- /* dynamicParams */ null, /* removeSearch */ true);
- },
-
- getExternalMetaKeyLabel_: function(hasInternalKeyboard) {
- return loadTimeData.getString(
- hasInternalKeyboard ? 'keyboardKeyExternalMeta' : 'keyboardKeyMeta');
- },
-
- getExternalCommandKeyLabel_: function(hasInternalKeyboard) {
- return loadTimeData.getString(
- hasInternalKeyboard ? 'keyboardKeyExternalCommand' :
- 'keyboardKeyCommand');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/layout_behavior.html b/chromium/chrome/browser/resources/settings/device_page/layout_behavior.html
deleted file mode 100644
index 089ea11096a..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/layout_behavior.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="layout_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/device_page/layout_behavior.js b/chromium/chrome/browser/resources/settings/device_page/layout_behavior.js
deleted file mode 100644
index ae353b0c1ac..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/layout_behavior.js
+++ /dev/null
@@ -1,727 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Behavior for handling display layout, specifically
- * edge snapping and collisions.
- */
-
-/** @polymerBehavior */
-const LayoutBehavior = {
- properties: {
- /**
- * Array of display layouts.
- * @type {!Array<!chrome.system.display.DisplayLayout>}
- */
- layouts: Array,
-
- /**
- * Whether or not mirroring is enabled.
- * @type {boolean}
- */
- mirroring: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @private {!Map<string, chrome.system.display.Bounds>} */
- displayBoundsMap_: new Map(),
-
- /** @private {!Map<string, chrome.system.display.DisplayLayout>} */
- displayLayoutMap_: new Map(),
-
- /**
- * The calculated bounds used for generating the div bounds.
- * @private {!Map<string, chrome.system.display.Bounds>}
- */
- calculatedBoundsMap_: new Map(),
-
- /** @private {string} */
- dragLayoutId_: '',
-
- /** @private {string} */
- dragParentId_: '',
-
- /** @private {!chrome.system.display.Bounds|undefined} */
- dragBounds_: undefined,
-
- /** @private {!chrome.system.display.LayoutPosition|undefined} */
- dragLayoutPosition_: undefined,
-
- /**
- * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
- * @param {!Array<!chrome.system.display.DisplayLayout>} layouts
- */
- initializeDisplayLayout: function(displays, layouts) {
- this.dragLayoutId_ = '';
- this.dragParentId_ = '';
-
- this.mirroring = displays.length > 0 && !!displays[0].mirroringSourceId;
-
- this.displayBoundsMap_.clear();
- for (const display of displays) {
- this.displayBoundsMap_.set(display.id, display.bounds);
- }
- this.displayLayoutMap_.clear();
- for (const layout of layouts) {
- this.displayLayoutMap_.set(layout.id, layout);
- }
- this.calculatedBoundsMap_.clear();
- for (const display of displays) {
- if (!this.calculatedBoundsMap_.has(display.id)) {
- const bounds = display.bounds;
- this.calculateBounds_(display.id, bounds.width, bounds.height);
- }
- }
- },
-
- /**
- * Called when a drag event occurs. Checks collisions and updates the layout.
- * @param {string} id
- * @param {!chrome.system.display.Bounds} newBounds The new calculated
- * bounds for the display.
- * @return {!chrome.system.display.Bounds}
- */
- updateDisplayBounds: function(id, newBounds) {
- this.dragLayoutId_ = id;
-
- // Find the closest parent.
- const closestId = this.findClosest_(id, newBounds);
- assert(closestId);
-
- // Find the closest edge.
- const closestBounds = this.getCalculatedDisplayBounds(closestId);
- const layoutPosition =
- this.getLayoutPositionForBounds_(newBounds, closestBounds);
-
- // Snap to the closest edge.
- const snapPos = this.snapBounds_(newBounds, closestId, layoutPosition);
- newBounds.left = snapPos.x;
- newBounds.top = snapPos.y;
-
- // Calculate the new bounds and delta.
- const oldBounds = this.dragBounds_ || this.getCalculatedDisplayBounds(id);
- const deltaPos = {
- x: newBounds.left - oldBounds.left,
- y: newBounds.top - oldBounds.top
- };
-
- // Check for collisions after snapping. This should not collide with the
- // closest parent.
- this.collideAndModifyDelta_(id, oldBounds, deltaPos);
-
- // If the edge changed, update and highlight it.
- if (layoutPosition != this.dragLayoutPosition_ ||
- closestId != this.dragParentId_) {
- this.dragLayoutPosition_ = layoutPosition;
- this.dragParentId_ = closestId;
- this.highlightEdge_(closestId, layoutPosition);
- }
-
- newBounds.left = oldBounds.left + deltaPos.x;
- newBounds.top = oldBounds.top + deltaPos.y;
-
- this.dragBounds_ = newBounds;
-
- return newBounds;
- },
-
- /**
- * Called when dragging ends. Sends the updated layout to chrome.
- * @param {string} id
- */
- finishUpdateDisplayBounds: function(id) {
- this.highlightEdge_('', undefined); // Remove any highlights.
- if (id != this.dragLayoutId_ || !this.dragBounds_ ||
- !this.dragLayoutPosition_) {
- return;
- }
-
- const layout = this.displayLayoutMap_.get(id);
-
- let orphanIds;
- if (!layout || layout.parentId == '') {
- // Primary display. Set the calculated position to |dragBounds_|.
- this.setCalculatedDisplayBounds_(id, this.dragBounds_);
-
- // We cannot re-parent the primary display, so instead make all other
- // displays orphans and clear their calculated bounds.
- orphanIds = this.findChildren_(id, true /* recurse */);
-
- // Re-parent |dragParentId_|. It will be forced to parent to the dragged
- // display since it is the only non-orphan.
- this.reparentOrphan_(this.dragParentId_, orphanIds);
- orphanIds.splice(orphanIds.indexOf(this.dragParentId_), 1);
- } else {
- // All immediate children of |layout| will need to be re-parented.
- orphanIds = this.findChildren_(id, false /* do not recurse */);
-
- // When re-parenting to a descendant, also parent any immediate child to
- // drag display's current parent.
- let topLayout = this.displayLayoutMap_.get(this.dragParentId_);
- while (topLayout && topLayout.parentId != '') {
- if (topLayout.parentId == id) {
- topLayout.parentId = layout.parentId;
- break;
- }
- topLayout = this.displayLayoutMap_.get(topLayout.parentId);
- }
-
- // Re-parent the dragged display.
- layout.parentId = this.dragParentId_;
- this.updateOffsetAndPosition_(
- this.dragBounds_, this.dragLayoutPosition_, layout);
- }
-
- // Update any orphaned children. This may cause the dragged display to
- // be re-attached if it was attached to a child.
- this.updateOrphans_(orphanIds);
-
- // Send the updated layouts.
- chrome.system.display.setDisplayLayout(this.layouts, function() {
- if (chrome.runtime.lastError) {
- console.error(
- 'setDisplayLayout Error: ' + chrome.runtime.lastError.message);
- }
- });
- },
-
- /**
- * @param {string} displayId
- * @param {boolean=} opt_notest Set to true if bounds may not be set.
- * @return {!chrome.system.display.Bounds} bounds
- */
- getCalculatedDisplayBounds: function(displayId, opt_notest) {
- const bounds = this.calculatedBoundsMap_.get(displayId);
- assert(opt_notest || bounds);
- return bounds;
- },
-
- /**
- * @param {string} displayId
- * @param {!chrome.system.display.Bounds|undefined} bounds
- * @private
- */
- setCalculatedDisplayBounds_: function(displayId, bounds) {
- assert(bounds);
- this.calculatedBoundsMap_.set(
- displayId,
- /** @type {!chrome.system.display.Bounds} */
- (Object.assign({}, bounds)));
- },
-
- /**
- * Re-parents all entries in |orphanIds| and any children.
- * @param {!Array<string>} orphanIds The list of ids affected by the move.
- * @private
- */
- updateOrphans_: function(orphanIds) {
- const orphans = orphanIds.slice();
- for (let i = 0; i < orphanIds.length; ++i) {
- const orphan = orphanIds[i];
- const newOrphans = this.findChildren_(orphan, true /* recurse */);
- // If the dragged display was re-parented to one of its children,
- // there may be duplicates so merge the lists.
- for (let ii = 0; ii < newOrphans.length; ++ii) {
- const o = newOrphans[ii];
- if (!orphans.includes(o)) {
- orphans.push(o);
- }
- }
- }
-
- // Remove each orphan from the list as it is re-parented so that
- // subsequent orphans can be parented to it.
- while (orphans.length) {
- const orphanId = orphans.shift();
- this.reparentOrphan_(orphanId, orphans);
- }
- },
-
- /**
- * Re-parents the orphan to a layout that is not a member of
- * |otherOrphanIds|.
- * @param {string} orphanId The id of the orphan to re-parent.
- * @param {Array<string>} otherOrphanIds The list of ids of other orphans
- * to ignore when re-parenting.
- * @private
- */
- reparentOrphan_: function(orphanId, otherOrphanIds) {
- const layout = this.displayLayoutMap_.get(orphanId);
- assert(layout);
- if (orphanId == this.dragId && layout.parentId != '') {
- this.setCalculatedDisplayBounds_(orphanId, this.dragBounds_);
- return;
- }
- const bounds = this.getCalculatedDisplayBounds(orphanId);
-
- // Find the closest parent.
- const newParentId = this.findClosest_(orphanId, bounds, otherOrphanIds);
- assert(newParentId != '');
- layout.parentId = newParentId;
-
- // Find the closest edge.
- const parentBounds = this.getCalculatedDisplayBounds(newParentId);
- const layoutPosition =
- this.getLayoutPositionForBounds_(bounds, parentBounds);
-
- // Move from the nearest corner to the desired location and get the delta.
- const cornerBounds = this.getCornerBounds_(bounds, parentBounds);
- const desiredPos = this.snapBounds_(bounds, newParentId, layoutPosition);
- const deltaPos = {
- x: desiredPos.x - cornerBounds.left,
- y: desiredPos.y - cornerBounds.top
- };
-
- // Check for collisions.
- this.collideAndModifyDelta_(orphanId, cornerBounds, deltaPos);
- const desiredBounds = {
- left: cornerBounds.left + deltaPos.x,
- top: cornerBounds.top + deltaPos.y,
- width: bounds.width,
- height: bounds.height
- };
-
- this.updateOffsetAndPosition_(desiredBounds, layoutPosition, layout);
- },
-
- /**
- * @param {string} parentId
- * @param {boolean} recurse Include descendants of children.
- * @return {!Array<string>}
- * @private
- */
- findChildren_: function(parentId, recurse) {
- let children = [];
- this.displayLayoutMap_.forEach((value, key) => {
- const childId = key;
- if (childId != parentId && value.parentId == parentId) {
- // Insert immediate children at the front of the array.
- children.unshift(childId);
- if (recurse) {
- // Descendants get added to the end of the list.
- children = children.concat(this.findChildren_(childId, true));
- }
- }
- });
- return children;
- },
-
- /**
- * Recursively calculates the absolute bounds of a display.
- * Caches the display bounds so that parent bounds are only calculated once.
- * @param {string} id
- * @param {number} width
- * @param {number} height
- * @private
- */
- calculateBounds_: function(id, width, height) {
- let left, top;
- const layout = this.displayLayoutMap_.get(id);
- if (this.mirroring || !layout || !layout.parentId) {
- left = -width / 2;
- top = -height / 2;
- } else {
- if (!this.calculatedBoundsMap_.has(layout.parentId)) {
- const pbounds = this.displayBoundsMap_.get(layout.parentId);
- this.calculateBounds_(layout.parentId, pbounds.width, pbounds.height);
- }
- const parentBounds = this.getCalculatedDisplayBounds(layout.parentId);
- left = parentBounds.left;
- top = parentBounds.top;
- switch (layout.position) {
- case chrome.system.display.LayoutPosition.TOP:
- left += layout.offset;
- top -= height;
- break;
- case chrome.system.display.LayoutPosition.RIGHT:
- left += parentBounds.width;
- top += layout.offset;
- break;
- case chrome.system.display.LayoutPosition.BOTTOM:
- left += layout.offset;
- top += parentBounds.height;
- break;
- case chrome.system.display.LayoutPosition.LEFT:
- left -= width;
- top += layout.offset;
- break;
- }
- }
- const result = {
- left: left,
- top: top,
- width: width,
- height: height,
- };
- this.setCalculatedDisplayBounds_(id, result);
- },
-
- /**
- * Finds the display closest to |bounds| ignoring |opt_ignoreIds|.
- * @param {string} displayId
- * @param {!chrome.system.display.Bounds} bounds
- * @param {Array<string>=} opt_ignoreIds Ids to ignore.
- * @return {string}
- * @private
- */
- findClosest_: function(displayId, bounds, opt_ignoreIds) {
- const x = bounds.left + bounds.width / 2;
- const y = bounds.top + bounds.height / 2;
- let closestId = '';
- let closestDelta2 = 0;
- const keys = this.calculatedBoundsMap_.keys();
- for (let iter = keys.next(); !iter.done; iter = keys.next()) {
- const otherId = iter.value;
- if (otherId == displayId) {
- continue;
- }
- if (opt_ignoreIds && opt_ignoreIds.includes(otherId)) {
- continue;
- }
- const otherBounds = this.getCalculatedDisplayBounds(otherId);
- const left = otherBounds.left;
- const top = otherBounds.top;
- const width = otherBounds.width;
- const height = otherBounds.height;
- if (x >= left && x < left + width && y >= top && y < top + height) {
- return otherId;
- } // point is inside rect
- let dx, dy;
- if (x < left) {
- dx = left - x;
- } else if (x > left + width) {
- dx = x - (left + width);
- } else {
- dx = 0;
- }
- if (y < top) {
- dy = top - y;
- } else if (y > top + height) {
- dy = y - (top + height);
- } else {
- dy = 0;
- }
- const delta2 = dx * dx + dy * dy;
- if (closestId == '' || delta2 < closestDelta2) {
- closestId = otherId;
- closestDelta2 = delta2;
- }
- }
- return closestId;
- },
-
- /**
- * Calculates the LayoutPosition for |bounds| relative to |parentId|.
- * @param {!chrome.system.display.Bounds} bounds
- * @param {!chrome.system.display.Bounds} parentBounds
- * @return {!chrome.system.display.LayoutPosition}
- */
- getLayoutPositionForBounds_: function(bounds, parentBounds) {
- // Translate bounds from top-left to center.
- const x = bounds.left + bounds.width / 2;
- const y = bounds.top + bounds.height / 2;
-
- // Determine the distance from the new bounds to both of the near edges.
- const left = parentBounds.left;
- const top = parentBounds.top;
- const width = parentBounds.width;
- const height = parentBounds.height;
-
- // Signed deltas to the center.
- const dx = x - (left + width / 2);
- const dy = y - (top + height / 2);
-
- // Unsigned distance to each edge.
- const distx = Math.abs(dx) - width / 2;
- const disty = Math.abs(dy) - height / 2;
-
- if (distx > disty) {
- if (dx < 0) {
- return chrome.system.display.LayoutPosition.LEFT;
- } else {
- return chrome.system.display.LayoutPosition.RIGHT;
- }
- } else {
- if (dy < 0) {
- return chrome.system.display.LayoutPosition.TOP;
- } else {
- return chrome.system.display.LayoutPosition.BOTTOM;
- }
- }
- },
-
- /**
- * Modifies |bounds| to the position closest to it along the edge of
- * |parentId| specified by |layoutPosition|.
- * @param {!chrome.system.display.Bounds} bounds
- * @param {string} parentId
- * @param {!chrome.system.display.LayoutPosition} layoutPosition
- * @return {!{x: number, y: number}}
- */
- snapBounds_: function(bounds, parentId, layoutPosition) {
- const parentBounds = this.getCalculatedDisplayBounds(parentId);
-
- let x;
- if (layoutPosition == chrome.system.display.LayoutPosition.LEFT) {
- x = parentBounds.left - bounds.width;
- } else if (layoutPosition == chrome.system.display.LayoutPosition.RIGHT) {
- x = parentBounds.left + parentBounds.width;
- } else {
- x = this.snapToX_(bounds, parentBounds);
- }
-
- let y;
- if (layoutPosition == chrome.system.display.LayoutPosition.TOP) {
- y = parentBounds.top - bounds.height;
- } else if (layoutPosition == chrome.system.display.LayoutPosition.BOTTOM) {
- y = parentBounds.top + parentBounds.height;
- } else {
- y = this.snapToY_(bounds, parentBounds);
- }
-
- return {x: x, y: y};
- },
-
- /**
- * Snaps a horizontal value, see snapToEdge.
- * @param {!chrome.system.display.Bounds} newBounds
- * @param {!chrome.system.display.Bounds} parentBounds
- * @param {number=} opt_snapDistance Provide to override the snap distance.
- * 0 means snap from any distance.
- * @return {number}
- */
- snapToX_: function(newBounds, parentBounds, opt_snapDistance) {
- return this.snapToEdge_(
- newBounds.left, newBounds.width, parentBounds.left, parentBounds.width,
- opt_snapDistance);
- },
-
- /**
- * Snaps a vertical value, see snapToEdge.
- * @param {!chrome.system.display.Bounds} newBounds
- * @param {!chrome.system.display.Bounds} parentBounds
- * @param {number=} opt_snapDistance Provide to override the snap distance.
- * 0 means snap from any distance.
- * @return {number}
- */
- snapToY_: function(newBounds, parentBounds, opt_snapDistance) {
- return this.snapToEdge_(
- newBounds.top, newBounds.height, parentBounds.top, parentBounds.height,
- opt_snapDistance);
- },
-
- /**
- * Snaps the region [point, width] to [basePoint, baseWidth] if
- * the [point, width] is close enough to the base's edge.
- * @param {number} point The starting point of the region.
- * @param {number} width The width of the region.
- * @param {number} basePoint The starting point of the base region.
- * @param {number} baseWidth The width of the base region.
- * @param {number=} opt_snapDistance Provide to override the snap distance.
- * 0 means snap at any distance.
- * @return {number} The moved point. Returns the point itself if it doesn't
- * need to snap to the edge.
- * @private
- */
- snapToEdge_: function(point, width, basePoint, baseWidth, opt_snapDistance) {
- // If the edge of the region is smaller than this, it will snap to the
- // base's edge.
- const SNAP_DISTANCE_PX = 16;
- const snapDist =
- (opt_snapDistance !== undefined) ? opt_snapDistance : SNAP_DISTANCE_PX;
-
- const startDiff = Math.abs(point - basePoint);
- const endDiff = Math.abs(point + width - (basePoint + baseWidth));
- // Prefer the closer one if both edges are close enough.
- if ((!snapDist || startDiff < snapDist) && startDiff < endDiff) {
- return basePoint;
- } else if (!snapDist || endDiff < snapDist) {
- return basePoint + baseWidth - width;
- }
-
- return point;
- },
-
- /**
- * Intersects |layout| with each other layout and reduces |deltaPos| to
- * avoid any collisions (or sets it to [0,0] if the display can not be moved
- * in the direction of |deltaPos|).
- * Note: this assumes that deltaPos is already 'snapped' to the parent edge,
- * and therefore will not collide with the parent, i.e. this is to prevent
- * overlapping with displays other than the parent.
- * @param {string} id
- * @param {!chrome.system.display.Bounds} bounds
- * @param {!{x: number, y: number}} deltaPos
- */
- collideAndModifyDelta_: function(id, bounds, deltaPos) {
- const keys = this.calculatedBoundsMap_.keys();
- const others = new Set(keys);
- others.delete(id);
- let checkCollisions = true;
- while (checkCollisions) {
- checkCollisions = false;
- const othersValues = others.values();
- for (let iter = othersValues.next(); !iter.done;
- iter = othersValues.next()) {
- const otherId = iter.value;
- const otherBounds = this.getCalculatedDisplayBounds(otherId);
- if (this.collideWithBoundsAndModifyDelta_(
- bounds, otherBounds, deltaPos)) {
- if (deltaPos.x == 0 && deltaPos.y == 0) {
- return;
- }
- others.delete(otherId);
- checkCollisions = true;
- break;
- }
- }
- }
- },
-
- /**
- * Intersects |bounds| with |otherBounds|. If there is a collision, modifies
- * |deltaPos| to limit movement to a single axis and avoid the collision
- * and returns true. See note for |collideAndModifyDelta_|.
- * @param {!chrome.system.display.Bounds} bounds
- * @param {!chrome.system.display.Bounds} otherBounds
- * @param {!{x: number, y: number}} deltaPos
- * @return {boolean} Whether there was a collision.
- */
- collideWithBoundsAndModifyDelta_: function(bounds, otherBounds, deltaPos) {
- const newX = bounds.left + deltaPos.x;
- const newY = bounds.top + deltaPos.y;
-
- if ((newX + bounds.width <= otherBounds.left) ||
- (newX >= otherBounds.left + otherBounds.width) ||
- (newY + bounds.height <= otherBounds.top) ||
- (newY >= otherBounds.top + otherBounds.height)) {
- return false;
- }
-
- // |deltaPos| should already be restricted to X or Y. This shortens the
- // delta to stay outside the bounds, however it does not change the sign of
- // the delta, i.e. it does not "push" the point outside the bounds if
- // the point is already inside.
- if (Math.abs(deltaPos.x) > Math.abs(deltaPos.y)) {
- deltaPos.y = 0;
- let snapDeltaX;
- if (deltaPos.x > 0) {
- snapDeltaX =
- Math.max(0, (otherBounds.left - bounds.width) - bounds.left);
- } else {
- snapDeltaX =
- Math.min(0, (otherBounds.left + otherBounds.width) - bounds.left);
- }
- deltaPos.x = snapDeltaX;
- } else {
- deltaPos.x = 0;
- let snapDeltaY;
- if (deltaPos.y > 0) {
- snapDeltaY =
- Math.min(0, (otherBounds.top - bounds.height) - bounds.top);
- } else if (deltaPos.y < 0) {
- snapDeltaY =
- Math.max(0, (otherBounds.top + otherBounds.height) - bounds.top);
- } else {
- snapDeltaY = 0;
- }
- deltaPos.y = snapDeltaY;
- }
-
- return true;
- },
-
- /**
- * Updates the offset for |layout| from |bounds|.
- * @param {!chrome.system.display.Bounds} bounds
- * @param {!chrome.system.display.LayoutPosition} position
- * @param {!chrome.system.display.DisplayLayout} layout
- */
- updateOffsetAndPosition_: function(bounds, position, layout) {
- layout.position = position;
- if (!layout.parentId) {
- layout.offset = 0;
- return;
- }
-
- // Offset is calculated from top or left edge.
- const parentBounds = this.getCalculatedDisplayBounds(layout.parentId);
- let offset, minOffset, maxOffset;
- if (position == chrome.system.display.LayoutPosition.LEFT ||
- position == chrome.system.display.LayoutPosition.RIGHT) {
- offset = bounds.top - parentBounds.top;
- minOffset = -bounds.height;
- maxOffset = parentBounds.height;
- } else {
- offset = bounds.left - parentBounds.left;
- minOffset = -bounds.width;
- maxOffset = parentBounds.width;
- }
- const MIN_OFFSET_OVERLAP = 50;
- minOffset += MIN_OFFSET_OVERLAP;
- maxOffset -= MIN_OFFSET_OVERLAP;
- layout.offset = Math.max(minOffset, Math.min(offset, maxOffset));
-
- // Update the calculated bounds to match the new offset.
- this.calculateBounds_(layout.id, bounds.width, bounds.height);
- },
-
- /**
- * Returns |bounds| translated to touch the closest corner of |parentBounds|.
- * @param {!chrome.system.display.Bounds} bounds
- * @param {!chrome.system.display.Bounds} parentBounds
- * @return {!chrome.system.display.Bounds}
- * @private
- */
- getCornerBounds_: function(bounds, parentBounds) {
- let x;
- if (bounds.left > parentBounds.left + parentBounds.width / 2) {
- x = parentBounds.left + parentBounds.width;
- } else {
- x = parentBounds.left - bounds.width;
- }
- let y;
- if (bounds.top > parentBounds.top + parentBounds.height / 2) {
- y = parentBounds.top + parentBounds.height;
- } else {
- y = parentBounds.top - bounds.height;
- }
- return {
- left: x,
- top: y,
- width: bounds.width,
- height: bounds.height,
- };
- },
-
- /**
- * Highlights the edge of the div associated with |id| based on
- * |layoutPosition| and removes any other highlights. If |layoutPosition| is
- * undefined, removes all highlights.
- * @param {string} id
- * @param {chrome.system.display.LayoutPosition|undefined} layoutPosition
- * @private
- */
- highlightEdge_: function(id, layoutPosition) {
- for (let i = 0; i < this.layouts.length; ++i) {
- const layout = this.layouts[i];
- const highlight = (layout.id == id) ? layoutPosition : undefined;
- const div = this.$$('#_' + layout.id);
- div.classList.toggle(
- 'highlight-right',
- highlight == chrome.system.display.LayoutPosition.RIGHT);
- div.classList.toggle(
- 'highlight-left',
- highlight == chrome.system.display.LayoutPosition.LEFT);
- div.classList.toggle(
- 'highlight-top',
- highlight == chrome.system.display.LayoutPosition.TOP);
- div.classList.toggle(
- 'highlight-bottom',
- highlight == chrome.system.display.LayoutPosition.BOTTOM);
- }
- },
-};
diff --git a/chromium/chrome/browser/resources/settings/device_page/night_light_slider.html b/chromium/chrome/browser/resources/settings/device_page/night_light_slider.html
deleted file mode 100644
index 6c273f3beee..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/night_light_slider.html
+++ /dev/null
@@ -1,196 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-ripple-behavior.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-
-<dom-module id="night-light-slider">
- <template>
- <style>
- :host {
- cursor: default;
- font-weight: 500;
- text-align: center;
- user-select: none;
- }
-
- #sliderContainer {
- display: inline-block;
- position: relative;
- user-select: none;
- width: 100%;
- }
-
- #sliderBar {
- background-color: rgba(var(--google-blue-600-rgb), .24);
- background-size: 100%;
- display: inline-block;
- height: 2px;
- position: relative;
- width: inherit;
- }
-
- .knob {
- height: 32px;
- margin-left: -16px;
- margin-top: -15px;
- position: absolute;
- width: 32px;
- z-index: 3;
- }
-
- .knob:focus {
- outline: none;
- }
-
- .knob-inner {
- background: var(--google-blue-600);
- border-radius: 6px;
- height: 10px;
- left: 0;
- margin: 11px;
- position: absolute;
- width: 10px;
- z-index: 3;
- }
-
- .knob-inner:focus {
- outline: none;
- }
-
- #progressContainer {
- height: 100%;
- overflow: hidden;
- position: absolute;
- width: 100%;
- }
-
- .progress {
- background: var(--google-blue-600);
- height: 100%;
- position: absolute;
- z-index: 1;
- }
-
- #labelContainer {
- height: 1.75em;
- }
-
- .label {
- background: var(--google-blue-600);
- border-radius: 14px;
- color: white;
- font-size: 12px;
- left: 0;
- line-height: 1.5em;
- margin-left: -2.5em;
- position: absolute;
- text-align: center;
- transition: margin-top 200ms cubic-bezier(0, 0, 0.2, 1);
- vertical-align: middle;
- width: 5em;
- }
-
- .end-label-overlap {
- margin-top: -2em;
- }
-
- #markersContainer {
- display: flex;
- height: 100%;
- left: 0;
- position: absolute;
- width: 100%;
- }
-
- .active-marker,
- .inactive-marker {
- background-color: rgba(255, 255, 255, 0.54);
- border-radius: 50%;
- display: block;
- height: 100%;
- margin-left: -1px;
- padding: 0;
- position: absolute;
- width: 2PX;
- z-index: 2;
- }
-
- .inactive-marker {
- background-color: rgba(26, 115, 232, 0.54);
- }
-
- #legendContainer {
- height: 10px;
- position: relative;
- width: inherit;
- }
-
- #legendContainer > div {
- color: rgb(100, 100, 100);
- font-size: 12px;
- margin-left: -2.5em;
- position: absolute;
- text-align: center;
- top: 5px;
- width: 5em;
- }
-
- paper-ripple {
- color: var(--google-blue-600);
- }
- </style>
-
- <div id="sliderContainer">
- <div id="labelContainer">
- <div id="startLabel" class="label"
- aria-label="$i18n{displayNightLightStartTime}">
- [[getTimeString_(prefs.ash.night_light.custom_start_time.value,
- shouldUse24Hours_)]]
- </div>
- <div id="endLabel" class="label"
- aria-label="$i18n{displayNightLightStopTime}">
- [[getTimeString_(prefs.ash.night_light.custom_end_time.value,
- shouldUse24Hours_)]]
- </div>
- </div>
- <div id="sliderBar">
- <div id="progressContainer">
- <div id="endProgress" class="progress"></div>
- <div id="startProgress" class="progress"></div>
- </div>
- <div id="markersContainer">
- </div>
- <div id="startKnob" class="knob" tabindex="1" on-down="startDrag_"
- on-up="endDrag_" on-track="continueDrag_">
- <div class="knob-inner" tabindex="-1"></div>
- </div>
- <div id="endKnob" class="knob" tabindex="2" on-down="startDrag_"
- on-up="endDrag_" on-track="continueDrag_">
- <div class="knob-inner" tabindex="-1"></div>
- </div>
- </div>
- <div id="legendContainer">
- <div style="[[getLegendStyle_(0, isRTL_)]]">
- [[getLocaleTimeString_(18, 0, shouldUse24Hours_)]]
- </div>
- <div style="[[getLegendStyle_(25, isRTL_)]]">
- [[getLocaleTimeString_(0, 0, shouldUse24Hours_)]]
- </div>
- <div style="[[getLegendStyle_(50, isRTL_)]]">
- [[getLocaleTimeString_(6, 0, shouldUse24Hours_)]]
- </div>
- <div style="[[getLegendStyle_(75, isRTL_)]]">
- [[getLocaleTimeString_(12, 0, shouldUse24Hours_)]]
- </div>
- <div style="[[getLegendStyle_(100, isRTL_)]]">
- [[getLocaleTimeString_(18, 0, shouldUse24Hours_)]]
- </div>
- </div>
- <div id="dummyRippleContainer" hidden></div>
- </div>
-
- </template>
- <script src="night_light_slider.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/night_light_slider.js b/chromium/chrome/browser/resources/settings/device_page/night_light_slider.js
deleted file mode 100644
index 9e08fbad501..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/night_light_slider.js
+++ /dev/null
@@ -1,678 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-/**
- * @fileoverview
- * night-light-slider is used to set the custom automatic schedule of the
- * Night Light feature, so that users can set their desired start and end
- * times.
- */
-
-const HOURS_PER_DAY = 24;
-const MIN_KNOBS_DISTANCE_MINUTES = 60;
-const OFFSET_MINUTES_6PM = 18 * 60;
-const TOTAL_MINUTES_PER_DAY = 24 * 60;
-
-/**
- * % is the javascript remainder operator that satisfies the following for the
- * resultant z given the operands x and y as in (z = x % y):
- * 1. x = k * y + z
- * 2. k is an integer.
- * 3. |z| < |y|
- * 4. z has the same sign as x.
- *
- * It is more convenient to have z be the same sign as y. In most cases y
- * is a positive integer, and it is more intuitive to have z also be a positive
- * integer (0 <= z < y).
- *
- * For example (-1 % 24) equals -1 whereas modulo(-1, 24) equals 23.
- * @param {number} x
- * @param {number} y
- * @return {number}
- */
-function modulo(x, y) {
- return ((x % y) + y) % y;
-}
-
-Polymer({
- is: 'night-light-slider',
-
- behaviors: [
- PrefsBehavior,
- Polymer.IronResizableBehavior,
- Polymer.PaperRippleBehavior,
- ],
-
- properties: {
- /**
- * Whether the element is ready and fully rendered.
- * @private
- */
- isReady_: Boolean,
-
- /**
- * Whether the window is in RTL locales.
- * @private
- */
- isRTL_: Boolean,
-
- /**
- * Whether to use the 24-hour format for the time shown in the label
- * bubbles.
- * @private
- */
- shouldUse24Hours_: Boolean,
- },
-
- listeners: {
- 'iron-resize': 'onResize_',
- focus: 'onFocus_',
- blur: 'onBlur_',
- keydown: 'onKeyDown_',
- },
-
- observers: [
- 'updateKnobs_(prefs.ash.night_light.custom_start_time.*, ' +
- 'prefs.ash.night_light.custom_end_time.*, isRTL_, isReady_)',
- 'hourFormatChanged_(prefs.settings.clock.use_24hour_clock.*)',
- 'updateMarkers_(prefs.ash.night_light.custom_start_time.*, ' +
- 'prefs.ash.night_light.custom_end_time.*, isRTL_, isReady_)',
- ],
-
- /**
- * The object currently being dragged. Either the start or end knobs.
- * @type {Element}
- * @private
- */
- dragObject_: null,
-
- /** @override */
- attached: function() {
- this.isRTL_ = window.getComputedStyle(this).direction == 'rtl';
-
- this.$.sliderContainer.addEventListener('contextmenu', function(e) {
- // Prevent the context menu from interfering with dragging the knobs using
- // touch.
- e.preventDefault();
- return false;
- });
-
- setTimeout(() => {
- // This is needed to make sure that the positions of the knobs and their
- // label bubbles are correctly updated when the display settings page is
- // opened for the first time after login. The page need to be fully
- // rendered.
- this.isReady_ = true;
- });
- },
-
- /**
- * @return {boolean}
- * @private
- */
- prefsAvailable: function() {
- return ['custom_start_time', 'custom_end_time']
- .map(key => `prefs.ash.night_light.${key}.value`)
- .every(path => this.get(path) != undefined);
- },
-
- /** @private */
- updateMarkers_: function() {
- if (!this.isReady_ || !this.prefsAvailable()) {
- return;
- }
-
- const startHour = /** @type {number} */ (
- this.getPref('ash.night_light.custom_start_time').value) / 60.0;
- const endHour = /** @type {number} */ (
- this.getPref('ash.night_light.custom_end_time').value) / 60.0;
-
- const markersContainer = this.$.markersContainer;
- markersContainer.innerHTML = '';
- for (let i = 0; i <= HOURS_PER_DAY; ++i) {
- const marker = document.createElement('div');
-
- const hourIndex = this.isRTL_ ? 24 - i : i;
- // Rotate around clock by 18 hours for the 6pm start.
- const hour = (hourIndex + 18) % 24;
- if (startHour < endHour) {
- marker.className = hour > startHour && hour < endHour ?
- 'active-marker' :
- 'inactive-marker';
- } else {
- marker.className = hour > endHour && hour < startHour ?
- 'inactive-marker' :
- 'active-marker';
- }
- markersContainer.appendChild(marker);
- marker.style.left = (i * 100 / HOURS_PER_DAY) + '%';
- }
- },
-
- /**
- * Invoked when the element is resized and the knobs positions need to be
- * updated.
- * @private
- */
- onResize_: function() {
- this.updateKnobs_();
- },
-
- /**
- * Called when the value of the pref associated with whether to use the
- * 24-hour clock format is changed. This will also refresh the slider.
- * @private
- */
- hourFormatChanged_: function() {
- this.shouldUse24Hours_ = /** @type {boolean} */ (
- this.getPref('settings.clock.use_24hour_clock').value);
- },
-
- /**
- * Gets the style of legend div determining its absolute left position.
- * @param {number} percent The value of the div's left as a percent (0 - 100).
- * @param {boolean} isRTL whether window is in RTL locale.
- * @return {string} The CSS style of the legend div.
- * @private
- */
- getLegendStyle_: function(percent, isRTL) {
- percent = isRTL ? 100 - percent : percent;
- return 'left: ' + percent + '%';
- },
-
- /**
- * If one of the two knobs is focused, this function blurs it.
- * @private
- */
- blurAnyFocusedKnob_: function() {
- const activeElement = this.shadowRoot.activeElement;
- if (activeElement == this.$.startKnob || activeElement == this.$.endKnob) {
- activeElement.blur();
- }
- },
-
- /**
- * Start dragging the target knob.
- * @param {!Event} event
- * @private
- */
- startDrag_: function(event) {
- event.preventDefault();
-
- // Only handle start or end knobs. Use the "knob-inner" divs just to display
- // the knobs.
- if (event.target == this.$.startKnob ||
- event.target == this.$.startKnob.firstElementChild) {
- this.dragObject_ = this.$.startKnob;
- } else if (event.target == this.$.endKnob ||
- event.target == this.$.endKnob.firstElementChild) {
- this.dragObject_ = this.$.endKnob;
- } else {
- return;
- }
-
- this.handleKnobEvent_(event, this.dragObject_);
-
- this.valueAtDragStart_ = this.getPrefValue_(this.dragObject_);
- },
-
- /**
- * Continues dragging the selected knob if any.
- * @param {!Event} event
- * @private
- */
- continueDrag_: function(event) {
- if (!this.dragObject_) {
- return;
- }
-
- event.stopPropagation();
- switch (event.detail.state) {
- case 'start':
- this.startDrag_(event);
- break;
- case 'track':
- this.doKnobTracking_(event);
- break;
- case 'end':
- this.endDrag_(event);
- break;
- }
- },
-
- /**
- * Converts horizontal pixels into number of minutes.
- * @param {number} deltaX
- * @return {number}
- * @private
- */
- getDeltaMinutes_: function(deltaX) {
- return (this.isRTL_ ? -1 : 1) *
- Math.floor(
- TOTAL_MINUTES_PER_DAY * deltaX / this.$.sliderBar.offsetWidth);
- },
-
- /**
- * Updates the knob's corresponding pref value in response to dragging, which
- * will in turn update the location of the knob and its corresponding label
- * bubble and its text contents.
- * @param {!Event} event
- * @private
- */
- doKnobTracking_: function(event) {
- const lastDeltaMinutes = this.getDeltaMinutes_(event.detail.ddx);
- if (Math.abs(lastDeltaMinutes) < 1) {
- return;
- }
-
- // Using |ddx| to compute the delta minutes and adding that to the current
- // value will result in a rounding error for every update. The cursor will
- // drift away from the knob. Storing the original value and calculating the
- // delta minutes from |dx| will provide a stable update that will not lose
- // pixel movement due to rounding.
- this.updatePref_(
- this.valueAtDragStart_ + this.getDeltaMinutes_(event.detail.dx), true);
- },
-
- /**
- * Ends the dragging.
- * @param {!Event} event
- * @private
- */
- endDrag_: function(event) {
- event.preventDefault();
- this.dragObject_ = null;
- this.removeRipple_();
- },
-
- /**
- * Gets the given knob's offset ratio with respect to its parent element
- * (which is the slider bar).
- * @param {HTMLDivElement} knob Either one of the two knobs.
- * @return {number}
- * @private
- */
- getKnobRatio_: function(knob) {
- return parseFloat(knob.style.left) / this.$.sliderBar.offsetWidth;
- },
-
- /**
- * Converts the time of day, given as |hour| and |minutes|, to its language-
- * sensitive time string representation.
- * @param {number} hour The hour of the day (0 - 23).
- * @param {number} minutes The minutes of the hour (0 - 59).
- * @param {boolean} shouldUse24Hours Whether to use the 24-hour time format.
- * @return {string}
- * @private
- */
- getLocaleTimeString_: function(hour, minutes, shouldUse24Hours) {
- const d = new Date();
- d.setHours(hour);
- d.setMinutes(minutes);
- d.setSeconds(0);
- d.setMilliseconds(0);
-
- return d.toLocaleTimeString(
- [], {hour: '2-digit', minute: '2-digit', hour12: !shouldUse24Hours});
- },
-
- /**
- * Converts the |offsetMinutes| value (which the number of minutes since
- * 00:00) to its language-sensitive time string representation.
- * @param {number} offsetMinutes The time of day represented as the number of
- * minutes from 00:00.
- * @param {boolean} shouldUse24Hours Whether to use the 24-hour time format.
- * @return {string}
- * @private
- */
- getTimeString_: function(offsetMinutes, shouldUse24Hours) {
- const hour = Math.floor(offsetMinutes / 60);
- const minute = Math.floor(offsetMinutes % 60);
-
- return this.getLocaleTimeString_(hour, minute, shouldUse24Hours);
- },
-
- /**
- * Using the current start and end times prefs, this function updates the
- * knobs and their label bubbles and refreshes the slider.
- * @private
- */
- updateKnobs_: function() {
- if (!this.isReady_ || !this.prefsAvailable() ||
- this.$.sliderBar.offsetWidth == 0) {
- return;
- }
- const startOffsetMinutes = /** @type {number} */ (
- this.getPref('ash.night_light.custom_start_time').value);
- this.updateKnobLeft_(this.$.startKnob, startOffsetMinutes);
- const endOffsetMinutes = /** @type {number} */ (
- this.getPref('ash.night_light.custom_end_time').value);
- this.updateKnobLeft_(this.$.endKnob, endOffsetMinutes);
- this.refresh_();
- },
-
- /**
- * Updates the absolute left coordinate of the given |knob| based on the time
- * it represents given as an |offsetMinutes| value.
- * @param {HTMLDivElement} knob
- * @param {number} offsetMinutes
- * @private
- */
- updateKnobLeft_: function(knob, offsetMinutes) {
- const offsetAfter6pm =
- (offsetMinutes + TOTAL_MINUTES_PER_DAY - OFFSET_MINUTES_6PM) %
- TOTAL_MINUTES_PER_DAY;
- let ratio = offsetAfter6pm / TOTAL_MINUTES_PER_DAY;
-
- if (ratio == 0) {
- // If the ratio is 0, then there are two possibilities:
- // - The knob time is 6:00 PM on the left side of the slider.
- // - The knob time is 6:00 PM on the right side of the slider.
- // We need to check the current knob offset ratio to determine which case
- // it is.
- const currentKnobRatio = this.getKnobRatio_(knob);
- ratio = currentKnobRatio > 0.5 ? 1.0 : 0.0;
- } else {
- ratio = this.isRTL_ ? (1.0 - ratio) : ratio;
- }
- knob.style.left = (ratio * this.$.sliderBar.offsetWidth) + 'px';
- },
-
- /**
- * Refreshes elements of the slider other than the knobs (the label bubbles,
- * and the progress bar).
- * @private
- */
- refresh_: function() {
- // The label bubbles have the same left coordinates as their corresponding
- // knobs.
- this.$.startLabel.style.left = this.$.startKnob.style.left;
- this.$.endLabel.style.left = this.$.endKnob.style.left;
-
- // In RTL locales, the relative positions of the knobs are flipped for the
- // purpose of calculating the styles of the progress bars below.
- const rtl = this.isRTL_;
- const endKnob = rtl ? this.$.startKnob : this.$.endKnob;
- const startKnob = rtl ? this.$.endKnob : this.$.startKnob;
- const startProgress = rtl ? this.$.endProgress : this.$.startProgress;
- const endProgress = rtl ? this.$.startProgress : this.$.endProgress;
-
- // The end progress bar starts from either the start knob or the start of
- // the slider (whichever is to its left) and ends at the end knob.
- const endProgressLeft = startKnob.offsetLeft >= endKnob.offsetLeft ?
- '0px' :
- startKnob.style.left;
- endProgress.style.left = endProgressLeft;
- endProgress.style.width =
- (parseFloat(endKnob.style.left) - parseFloat(endProgressLeft)) + 'px';
-
- // The start progress bar starts at the start knob, and ends at either the
- // end knob or the end of the slider (whichever is to its right).
- const startProgressRight = endKnob.offsetLeft < startKnob.offsetLeft ?
- this.$.sliderBar.offsetWidth :
- endKnob.style.left;
- startProgress.style.left = startKnob.style.left;
- startProgress.style.width =
- (parseFloat(startProgressRight) - parseFloat(startKnob.style.left)) +
- 'px';
-
- this.fixLabelsOverlapIfAny_();
- },
-
- /**
- * If the label bubbles overlap, this function fixes them by moving the end
- * label up a little.
- * @private
- */
- fixLabelsOverlapIfAny_: function() {
- const startLabel = this.$.startLabel;
- const endLabel = this.$.endLabel;
- const distance = Math.abs(
- parseFloat(startLabel.style.left) - parseFloat(endLabel.style.left));
- // Both knobs have the same width, but the one being dragged is scaled up by
- // 125%.
- if (distance <= (1.25 * startLabel.offsetWidth)) {
- // Shift the end label up so that it doesn't overlap with the start label.
- endLabel.classList.add('end-label-overlap');
- } else {
- endLabel.classList.remove('end-label-overlap');
- }
- },
-
- /**
- * Given the |prefPath| that corresponds to one knob time, it gets the value
- * of the pref that corresponds to the other knob.
- * @param {string} prefPath
- * @return {number}
- * @private
- */
- getOtherKnobPrefValue_: function(prefPath) {
- if (prefPath == 'ash.night_light.custom_start_time') {
- return /** @type {number} */ (
- this.getPref('ash.night_light.custom_end_time').value);
- }
-
- return /** @type {number} */ (
- this.getPref('ash.night_light.custom_start_time').value);
- },
-
- /**
- * Updates the value of the pref and wraps around if necessary.
- *
- * When the |updatedValue| would put the start and end times closer than the
- * minimum distance, the |updatedValue| is changed to maintain the minimum
- * distance.
- *
- * When |fromUserGesture| is true the update source is from a pointer such as
- * a mouse, touch or pen. When the knobs are close, the dragging knob will
- * stay on the same side with respect to the other knob. For example, when the
- * minimum distance is 1 hour, the start knob is at 8:30 am, and the end knob
- * is at 7:00, let's examine what happens if the start knob is dragged past
- * the end knob. At first the start knob values will change past 8:20 and
- * 8:10, all the way up to 8:00. Further movements in the same direction will
- * not change the start knob value until the pointer crosses past the end knob
- * (modulo the bar width). At that point, the start knob value will be updated
- * to 6:00 and remain at 6:00 until the pointer passes the 6:00 location.
- *
- * When |fromUserGesture| is false, the input is coming from a key event. As
- * soon as the |updatedValue| is closer than the minimum distance, the knob
- * is moved to the other side of the other knob. For example, with a minimum
- * distance of 1 hour, the start knob is at 8:00 am, and the end knob is at
- * 7:00, if the start knob value is decreased, then the start knob will be
- * updated to 6:00.
- * @param {number} updatedValue
- * @param {boolean} fromUserGesture
- * @private
- */
- updatePref_: function(updatedValue, fromUserGesture) {
- const prefPath = assert(this.getFocusedKnobPrefPathIfAny_());
- const otherValue = this.getOtherKnobPrefValue_(prefPath);
-
- const totalMinutes = TOTAL_MINUTES_PER_DAY;
- const minDistance = MIN_KNOBS_DISTANCE_MINUTES;
- if (modulo(otherValue - updatedValue, totalMinutes) < minDistance) {
- updatedValue = otherValue + (fromUserGesture ? -1 : 1) * minDistance;
- } else if (modulo(updatedValue - otherValue, totalMinutes) < minDistance) {
- updatedValue = otherValue + (fromUserGesture ? 1 : -1) * minDistance;
- }
-
- // The knobs are allowed to wrap around.
- this.setPrefValue(prefPath, modulo(updatedValue, TOTAL_MINUTES_PER_DAY));
- },
-
- /**
- * @param {Element} knob
- * @returns {?string}
- * @private
- */
- getPrefPath_: function(knob) {
- if (knob == this.$.startKnob) {
- return 'ash.night_light.custom_start_time';
- }
-
- if (knob == this.$.endKnob) {
- return 'ash.night_light.custom_end_time';
- }
-
- return null;
- },
-
- /**
- * @param {Element} knob
- * @returns {?number}
- * @private
- */
- getPrefValue_: function(knob) {
- const path = this.getPrefPath_(knob);
- return path ? /** @type {number} */ (this.getPref(path).value) : null;
- },
-
- /**
- * Gets the pref path of the currently focused knob. Returns null if no knob
- * is currently focused.
- * @return {?string}
- * @private
- */
- getFocusedKnobPrefPathIfAny_: function() {
- return this.getPrefPath_(this.shadowRoot.activeElement);
- },
-
- /**
- * @return {boolean} Whether either of the two knobs is focused.
- * @private
- */
- isEitherKnobFocused_: function() {
- const activeElement = this.shadowRoot.activeElement;
- return activeElement == this.$.startKnob || activeElement == this.$.endKnob;
- },
-
- /**
- * Overrides _createRipple() from PaperRippleBehavior to create the ripple
- * only on a knob if it's focused, or on a dummy hidden element so that it
- * doesn't show.
- * @protected
- */
- _createRipple: function() {
- if (this.isEitherKnobFocused_()) {
- this._rippleContainer = this.shadowRoot.activeElement;
- } else {
- // We can't just skip the ripple creation and return early with null here.
- // The code inherited from PaperRippleBehavior expects that this function
- // returns a ripple element. So to avoid crashes, we'll setup the ripple
- // to be created under a hidden element.
- this._rippleContainer = this.$.dummyRippleContainer;
- }
- const ripple = Polymer.PaperRippleBehavior._createRipple();
- ripple.id = 'ink';
- ripple.setAttribute('recenters', '');
- ripple.classList.add('circle', 'toggle-ink');
- return ripple;
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onFocus_: function(event) {
- this.handleKnobEvent_(event);
- },
-
- /**
- * Handles focus, drag and key events on the start and end knobs.
- * If |overrideElement| is provided, it will be the knob that gains focus and
- * and the ripple. Otherwise, the knob is determined from the |event|.
- * @param {!Event} event
- * @param {Element=} overrideElement
- * @private
- */
- handleKnobEvent_: function(event, overrideElement) {
- const knob = overrideElement ||
- event.path.find(el => el.classList && el.classList.contains('knob'));
- if (!knob) {
- event.preventDefault();
- return;
- }
-
- if (this._rippleContainer != knob) {
- this.removeRipple_();
- knob.focus();
- }
-
- this.ensureRipple();
-
- if (this.hasRipple()) {
- this._ripple.style.display = '';
- this._ripple.holdDown = true;
- }
- },
-
- /**
- * Handles blur events on the start and end knobs.
- * @private
- */
- onBlur_: function() {
- this.removeRipple_();
- },
-
- /**
- * Removes ripple if one exists.
- * @private
- */
- removeRipple_: function() {
- if (this.hasRipple()) {
- this._ripple.remove();
- this._ripple = null;
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onKeyDown_: function(event) {
- const activeElement = this.shadowRoot.activeElement;
- if (event.key == 'Tab') {
- if (event.shiftKey && this.$.endKnob == activeElement) {
- event.preventDefault();
- this.handleKnobEvent_(event, this.$.startKnob);
- return;
- }
-
- if (!event.shiftKey && this.$.startKnob == activeElement) {
- event.preventDefault();
- this.handleKnobEvent_(event, this.$.endKnob);
- }
- return;
- }
-
- if (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey) {
- return;
- }
-
- const deltaKeyMap = {
- ArrowDown: -1,
- ArrowLeft: this.isRTL_ ? 1 : -1,
- ArrowRight: this.isRTL_ ? -1 : 1,
- ArrowUp: 1,
- PageDown: -15,
- PageUp: 15,
- };
-
- if (event.key in deltaKeyMap) {
- this.handleKnobEvent_(event);
-
- event.preventDefault();
- const value = this.getPrefValue_(activeElement);
- if (value == null) {
- return;
- }
-
- const delta = deltaKeyMap[event.key];
- this.updatePref_(value + delta, false);
- }
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/device_page/pointers.html b/chromium/chrome/browser/resources/settings/device_page/pointers.html
deleted file mode 100644
index 6a628f2f5b2..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/pointers.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../controls/settings_radio_group.html">
-<link rel="import" href="../controls/settings_slider.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="device_page_browser_proxy.html">
-
-<dom-module id="settings-pointers">
- <template>
- <style include="settings-shared">
- h2 {
- padding-inline-start: var(--cr-section-padding);
- }
-
- .subsection {
- padding-inline-end: var(--cr-section-padding);
- padding-inline-start: var(--cr-section-indent-padding);
- }
-
- .subsection > settings-toggle-button,
- .subsection > .settings-box {
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
- </style>
- <div id="mouse" hidden$="[[!hasMouse]]">
- <!-- Subsection title only appears if both mouse and touchpad exist. -->
- <h2 hidden$="[[!hasTouchpad]]">$i18n{mouseTitle}</h2>
- <div class$="[[getSubsectionClass_(hasMouse, hasTouchpad)]]">
- <!-- Do not change the mouse button pref before the mouse is released.
- See crbug.com/686949 -->
- <settings-toggle-button id="mouseSwapButton" class="first"
- pref="{{prefs.settings.mouse.primary_right}}"
- label="$i18n{mouseSwapButtons}"
- on-settings-boolean-control-change="onMouseSwapButtonsChange_"
- on-down="onMouseSwapButtonsDown_" on-up="onMouseSwapButtonsUp_"
- no-set-pref>
- </settings-toggle-button>
- <settings-toggle-button id="mouseReverseScroll"
- pref="{{prefs.settings.mouse.reverse_scroll}}"
- label="$i18n{mouseReverseScroll}">
- </settings-toggle-button>
- <template is="dom-if" if="[[allowDisableAcceleration_]]">
- <settings-toggle-button id="mouseAcceleration"
- pref="{{prefs.settings.mouse.acceleration}}"
- label="$i18n{mouseAccelerationLabel}">
- </settings-toggle-button>
- </template>
- <div class="settings-box">
- <div class="start" id="mouseSpeedLabel">$i18n{mouseSpeed}</div>
- <settings-slider pref="{{prefs.settings.mouse.sensitivity2}}"
- ticks="[[sensitivityValues_]]"
- aria-labelledby="mouseSpeedLabel"
- label-min="$i18n{pointerSlow}"
- label-max="$i18n{pointerFast}">
- </settings-slider>
- </div>
- </div>
- </div>
- <div id="touchpad" hidden$="[[!hasTouchpad]]">
- <!-- Subsection title only appears if both mouse and touchpad exist. -->
- <h2 hidden$="[[!hasMouse]]">$i18n{touchpadTitle}</h2>
- <div class$="[[getSubsectionClass_(hasMouse, hasTouchpad)]]">
- <settings-toggle-button id="enableTapToClick" class="first"
- pref="{{prefs.settings.touchpad.enable_tap_to_click}}"
- label="$i18n{touchpadTapToClickEnabledLabel}">
- </settings-toggle-button>
- <settings-toggle-button id="enableTapDragging"
- pref="{{prefs.settings.touchpad.enable_tap_dragging}}"
- label="$i18n{tapDraggingLabel}">
- </settings-toggle-button>
- <template is="dom-if" if="[[allowDisableAcceleration_]]">
- <settings-toggle-button id="touchpadAcceleration"
- pref="{{prefs.settings.touchpad.acceleration}}"
- label="$i18n{touchpadAccelerationLabel}">
- </settings-toggle-button>
- </template>
- <div class="settings-box">
- <div class="start" id="touchpadSpeedLabel">$i18n{touchpadSpeed}</div>
- <settings-slider id="touchpadSensitivity"
- pref="{{prefs.settings.touchpad.sensitivity2}}"
- ticks="[[sensitivityValues_]]"
- aria-labelledby="touchpadSpeedLabel"
- label-min="$i18n{pointerSlow}"
- label-max="$i18n{pointerFast}">
- </settings-slider>
- </div>
- <div class="settings-box">$i18n{scrollLabel}</div>
- <div class="list-frame">
- <settings-radio-group
- pref="{{prefs.settings.touchpad.natural_scroll}}">
- <cr-radio-button name="false">
- $i18n{traditionalScrollLabel}
- </cr-radio-button>
- <cr-radio-button name="true">
- $i18n{naturalScrollLabel}
- <a href="$i18n{naturalScrollLearnMoreLink}" target="_blank">
- $i18n{naturalScrollLearnMore}
- </a>
- </cr-radio-button>
- </settings-radio-group>
- </div>
- </div>
- </div>
- </template>
- <script src="pointers.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/pointers.js b/chromium/chrome/browser/resources/settings/device_page/pointers.js
deleted file mode 100644
index 1db873949f2..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/pointers.js
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-pointers' is the settings subpage with mouse and touchpad settings.
- */
-Polymer({
- is: 'settings-pointers',
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
-
- hasMouse: Boolean,
-
- hasTouchpad: Boolean,
-
- /**
- * TODO(michaelpg): settings-slider should optionally take a min and max so
- * we don't have to generate a simple range of natural numbers ourselves.
- * @type {!Array<number>}
- * @private
- */
- sensitivityValues_: {
- type: Array,
- value: [1, 2, 3, 4, 5],
- readOnly: true,
- },
-
- /**
- * TODO(zentaro): Remove this conditional once the feature is launched.
- * @private
- */
- allowDisableAcceleration_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('allowDisableMouseAcceleration');
- },
- },
- },
-
- // Used to correctly identify when the mouse button has been released.
- // crbug.com/686949.
- receivedMouseSwapButtonsDown_: false,
-
- /**
- * Mouse and touchpad sections are only subsections if they are both present.
- * @param {boolean} hasMouse
- * @param {boolean} hasTouchpad
- * @return {string}
- * @private
- */
- getSubsectionClass_: function(hasMouse, hasTouchpad) {
- return hasMouse && hasTouchpad ? 'subsection' : '';
- },
-
- /** @private */
- onMouseSwapButtonsDown_: function() {
- this.receivedMouseSwapButtonsDown_ = true;
- },
-
- /** @private */
- onMouseSwapButtonsUp_: function() {
- this.receivedMouseSwapButtonsDown_ = false;
- /** @type {!SettingsToggleButtonElement} */ (this.$.mouseSwapButton)
- .sendPrefChange();
- },
-
- /** @private */
- onMouseSwapButtonsChange_: function(event) {
- if (!this.receivedMouseSwapButtonsDown_) {
- /** @type {!SettingsToggleButtonElement} */ (this.$.mouseSwapButton)
- .sendPrefChange();
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/power.html b/chromium/chrome/browser/resources/settings/device_page/power.html
deleted file mode 100644
index 8df6cf9fd7b..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/power.html
+++ /dev/null
@@ -1,65 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-power">
- <template>
- <style include="settings-shared md-select"></style>
- <div id="powerSourceRow" class="settings-box first two-line"
- hidden$="[[!batteryStatus_.present]]">
- <div class="start">
- <div>[[powerSourceLabel_]]</div>
- <div class="secondary">[[batteryStatus_.statusText]]</div>
- </div>
- <select id="powerSource" class="md-select"
- hidden$="[[!showPowerSourceDropdown_]]"
- aria-label="$i18n{powerSourceLabel}"
- on-change="onPowerSourceChange_">
- <option value="" selected="[[isEqual_('', selectedPowerSourceId_)]]">
- $i18n{powerSourceBattery}
- </option>
- <template is="dom-repeat" items="[[powerSources_]]">
- <option value="[[item.id]]"
- selected="[[isEqual_(item.id, selectedPowerSourceId_)]]">
- [[item.description]]
- </option>
- </template>
- </select>
- <div hidden$="[[showPowerSourceDropdown_]]">
- [[powerSourceName_]]
- </div>
- </div>
-
- <div class$="settings-box [[getFirst_(batteryStatus_.present)]]">
- <div class="start">$i18n{powerIdleLabel}</div>
- <template is="dom-if" if="[[idleControlled_]]" restamp>
- <cr-policy-indicator id="idleControlledIndicator"
- indicator-type="devicePolicy"
- icon-aria-label="$i18n{powerIdleLabel}">
- </cr-policy-indicator>
- </template>
- <select id="idleSelect" class="md-select"
- on-change="onIdleSelectChange_" disabled="[[idleControlled_]]"
- aria-label="$i18n{powerIdleLabel}">
- <template is="dom-repeat" items="[[idleOptions_]]">
- <option value="[[item.value]]">[[item.name]]</option>
- </template>
- </select>
- </div>
-
- <settings-toggle-button class="continuation" hidden$="[[!hasLid_]]"
- id="lidClosedToggle"
- pref="[[lidClosedPref_]]" label="[[lidClosedLabel_]]"
- on-settings-boolean-control-change="onLidClosedToggleChange_"
- no-set-pref>
- </settings-toggle-button>
- </template>
- <script src="power.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/power.js b/chromium/chrome/browser/resources/settings/device_page/power.js
deleted file mode 100644
index dbc4c752838..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/power.js
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-power' is the settings subpage for power settings.
- */
-
-Polymer({
- is: 'settings-power',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- enablePowerSettings: Boolean,
-
- /** @private {string} ID of the selected power source, or ''. */
- selectedPowerSourceId_: String,
-
- /** @private {!settings.BatteryStatus|undefined} */
- batteryStatus_: Object,
-
- /** @private {boolean} Whether a low-power (USB) charger is being used. */
- lowPowerCharger_: Boolean,
-
- /** @private {boolean} Whether the idle behavior is controlled by policy. */
- idleControlled_: Boolean,
-
- /** @private {string} Text for label describing the lid-closed behavior. */
- lidClosedLabel_: String,
-
- /** @private {boolean} Whether the system possesses a lid. */
- hasLid_: Boolean,
-
- /**
- * List of available dual-role power sources, if enablePowerSettings is on.
- * @private {!Array<!settings.PowerSource>|undefined}
- */
- powerSources_: Array,
-
- /** @private */
- powerSourceLabel_: {
- type: String,
- computed:
- 'computePowerSourceLabel_(powerSources_, batteryStatus_.calculating)',
- },
-
- /** @private */
- showPowerSourceDropdown_: {
- type: Boolean,
- computed: 'computeShowPowerSourceDropdown_(powerSources_)',
- value: false,
- },
-
- /**
- * The name of the dedicated charging device being used, if present.
- * @private {string}
- */
- powerSourceName_: {
- type: String,
- computed: 'computePowerSourceName_(powerSources_, lowPowerCharger_)',
- },
-
- /** @private */
- idleOptions_: {
- type: Array,
- computed: 'computeIdleOptions_(idleControlled_)',
- },
-
- /** @private {!chrome.settingsPrivate.PrefObject} */
- lidClosedPref_: {
- type: Object,
- value: function() {
- return /** @type {!chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
- },
-
- /** @override */
- ready: function() {
- // enablePowerSettings comes from loadTimeData, so it will always be set
- // before attached() is called.
- if (!this.enablePowerSettings) {
- settings.navigateToPreviousRoute();
- }
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'battery-status-changed', this.set.bind(this, 'batteryStatus_'));
- this.addWebUIListener(
- 'power-sources-changed', this.powerSourcesChanged_.bind(this));
- settings.DevicePageBrowserProxyImpl.getInstance().updatePowerStatus();
-
- this.addWebUIListener(
- 'power-management-settings-changed',
- this.powerManagementSettingsChanged_.bind(this));
- settings.DevicePageBrowserProxyImpl.getInstance()
- .requestPowerManagementSettings();
- },
-
- /**
- * @param {!Array<!settings.PowerSource>|undefined} powerSources
- * @param {boolean} calculating
- * @return {string} The primary label for the power source row.
- * @private
- */
- computePowerSourceLabel_: function(powerSources, calculating) {
- return this.i18n(
- calculating ?
- 'calculatingPower' :
- powerSources && powerSources.length ? 'powerSourceLabel' :
- 'powerSourceBattery');
- },
-
- /**
- * @param {!Array<!settings.PowerSource>} powerSources
- * @return {boolean} True if at least one power source is attached and all of
- * them are dual-role (no dedicated chargers).
- * @private
- */
- computeShowPowerSourceDropdown_: function(powerSources) {
- return powerSources.length > 0 && powerSources.every(function(source) {
- return !source.is_dedicated_charger;
- });
- },
-
- /**
- * @param {!Array<!settings.PowerSource>} powerSources
- * @param {boolean} lowPowerCharger
- * @return {string} Description of the power source.
- * @private
- */
- computePowerSourceName_: function(powerSources, lowPowerCharger) {
- if (lowPowerCharger) {
- return this.i18n('powerSourceLowPowerCharger');
- }
- if (powerSources.length) {
- return this.i18n('powerSourceAcAdapter');
- }
- return '';
- },
-
- /**
- * @param {boolean} idleControlled
- * @return {!Array<!{value: settings.IdleBehavior, name: string}>} Options to
- * display in idle-behavior select.
- * @private
- */
- computeIdleOptions_: function(idleControlled) {
- const options = [
- {
- value: settings.IdleBehavior.DISPLAY_OFF_SLEEP,
- name: loadTimeData.getString('powerIdleDisplayOffSleep'),
- },
- {
- value: settings.IdleBehavior.DISPLAY_OFF,
- name: loadTimeData.getString('powerIdleDisplayOff'),
- },
- {
- value: settings.IdleBehavior.DISPLAY_ON,
- name: loadTimeData.getString('powerIdleDisplayOn'),
- },
- ];
- if (idleControlled) {
- options.push({
- value: settings.IdleBehavior.OTHER,
- name: loadTimeData.getString('powerIdleOther'),
- });
- }
- return options;
- },
-
- /** @private */
- onPowerSourceChange_: function() {
- settings.DevicePageBrowserProxyImpl.getInstance().setPowerSource(
- this.$.powerSource.value);
- },
-
- /** @private */
- onIdleSelectChange_: function() {
- const behavior = /** @type {settings.IdleBehavior} */
- (parseInt(this.$.idleSelect.value, 10));
- settings.DevicePageBrowserProxyImpl.getInstance().setIdleBehavior(behavior);
- },
-
- /** @private */
- onLidClosedToggleChange_: function() {
- // Other behaviors are only displayed when the setting is controlled, in
- // which case the toggle can't be changed by the user.
- settings.DevicePageBrowserProxyImpl.getInstance().setLidClosedBehavior(
- this.$.lidClosedToggle.checked ? settings.LidClosedBehavior.SUSPEND :
- settings.LidClosedBehavior.DO_NOTHING);
- },
-
- /**
- * @param {!Array<settings.PowerSource>} sources External power sources.
- * @param {string} selectedId The ID of the currently used power source.
- * @param {boolean} lowPowerCharger Whether the currently used power source
- * is a low-powered USB charger.
- * @private
- */
- powerSourcesChanged_: function(sources, selectedId, lowPowerCharger) {
- this.powerSources_ = sources;
- this.selectedPowerSourceId_ = selectedId;
- this.lowPowerCharger_ = lowPowerCharger;
- },
-
- /**
- * @param {settings.LidClosedBehavior} behavior Current behavior.
- * @param {boolean} isControlled Whether the underlying pref is controlled.
- * @private
- */
- updateLidClosedLabelAndPref_: function(behavior, isControlled) {
- const pref = {
- key: '',
- type: chrome.settingsPrivate.PrefType.BOOLEAN,
- // Most behaviors get a dedicated label and appear as checked.
- value: true,
- };
-
- switch (behavior) {
- case settings.LidClosedBehavior.SUSPEND:
- case settings.LidClosedBehavior.DO_NOTHING:
- // "Suspend" and "do nothing" share the "sleep" label and communicate
- // their state via the toggle state.
- this.lidClosedLabel_ = loadTimeData.getString('powerLidSleepLabel');
- pref.value = behavior == settings.LidClosedBehavior.SUSPEND;
- break;
- case settings.LidClosedBehavior.STOP_SESSION:
- this.lidClosedLabel_ = loadTimeData.getString('powerLidSignOutLabel');
- break;
- case settings.LidClosedBehavior.SHUT_DOWN:
- this.lidClosedLabel_ = loadTimeData.getString('powerLidShutDownLabel');
- break;
- }
-
- if (isControlled) {
- pref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
- pref.controlledBy = chrome.settingsPrivate.ControlledBy.USER_POLICY;
- }
-
- this.lidClosedPref_ = pref;
- },
-
- /**
- * @param {!settings.PowerManagementSettings} browserSettings Current power
- * management settings.
- * @private
- */
- powerManagementSettingsChanged_: function(browserSettings) {
- this.idleControlled_ = browserSettings.idleControlled;
- this.hasLid_ = browserSettings.hasLid;
- this.updateLidClosedLabelAndPref_(
- browserSettings.lidClosedBehavior, browserSettings.lidClosedControlled);
-
- // The idle behavior select element includes an "Other" option when
- // controlled but omits it otherwise. Make sure that the option is there
- // before we potentially try to select it.
- this.async(function() {
- this.$.idleSelect.value = browserSettings.idleBehavior;
- });
- },
-
- /**
- * @param {boolean} batteryPresent if battery is present
- * @return {string} 'first' if idle/lid settings are first visible div
- * @private
- */
- getFirst_: function(batteryPresent) {
- return !batteryPresent ? 'first' : '';
- },
-
- /**
- * @param {*} lhs
- * @param {*} rhs
- * @return {boolean}
- * @private
- */
- isEqual_: function(lhs, rhs) {
- return lhs === rhs;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/storage.html b/chromium/chrome/browser/resources/settings/device_page/storage.html
deleted file mode 100644
index 6526d4f1fb1..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/storage.html
+++ /dev/null
@@ -1,228 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="storage_external.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-storage">
- <template>
- <style include="settings-shared">
- progress {
- -webkit-appearance: none;
- display: block;
- height: 28px;
- width: 100%;
- }
-
- progress::-webkit-progress-bar {
- background-color: rgba(0, 0, 0, 0.06);
- border-radius: 2px;
- }
-
- progress::-webkit-progress-value {
- background-color: rgb(0, 175, 255);
- border-radius: 2px;
- }
-
- progress.space-low::-webkit-progress-value {
- background-color: rgb(255, 176, 0);
- }
-
- progress.space-critically-low::-webkit-progress-value {
- background-color: var(--google-red-500);
- }
-
- iron-icon {
- --iron-icon-fill-color: rgb(255, 176, 0);
- --iron-icon-height: 32px;
- --iron-icon-width: 32px;
- }
-
- #criticallyLowMessage iron-icon {
- --iron-icon-fill-color: var(--google-red-500);
- }
-
- .storage-size {
- color: var(--cr-secondary-text-color);
- }
-
- .message-area {
- background-color: var(--google-grey-100);
- border-radius: 2px;
- display: flex;
- margin: 14px 0 16px;
- padding-bottom: 12px;
- padding-inline-end: 48px;
- padding-inline-start: 16px;
- padding-top: 16px;
- width: 100%;
- }
-
- .message-area > iron-icon {
- flex: none;
- padding-inline-end: 16px;
- }
-
- .message-title {
- font-size: 115%;
- }
-
- .message-description {
- color: rgb(90, 90, 90);
- font-size: 92%;
- line-height: 1.6em;
- margin: 1em 0;
- }
-
- #barArea {
- display: flex;
- flex-direction: column;
- margin: 24px 0 54px;
- width: 100%;
- }
-
- #barLabels {
- display: flex;
- }
-
- .bar-label {
- display: flex;
- flex-direction: column;
- }
-
- .bar-label .vertical-line {
- align-self: center;
- background-color: rgba(0, 0, 0, 0.17);
- height: 8px;
- margin-bottom: 4px;
- width: 1px;
- }
-
- .bar-label .wrapper {
- color: rgb(51, 51, 51);
- text-align: center;
- white-space: nowrap;
- }
-
- /* If the "Available" part in the ber is too small, the label "Available"
- * and a label for its size can overstep the right edge of bar area.
- * To prevent it, we invert the direction to put the text labels here.
- * We restore the direction for inner span elements not to change how to
- * render the text contents. */
- .end-aligned .wrapper {
- direction: rtl;
- }
-
- :host-context([dir=rtl]) .end-aligned .wrapper {
- direction: ltr;
- }
-
- .end-aligned .wrapper span {
- direction: initial;
- unicode-bidi: embed;
- }
-
- #deleteButton {
- display: none;
- }
-
- [actionable] #deleteButton {
- display: block;
- }
-
- button > iron-icon {
- --iron-icon-fill-color: var(--paper-grey-400);
- --iron-icon-height: 24px;
- --iron-icon-width: 24px;
- }
- </style>
- <template is="dom-if" if="[[isSpaceLow_(sizeStat_.spaceState)]]">
- <div class="settings-box first">
- <div class="message-area">
- <iron-icon icon="cr:warning"></iron-icon>
- <div class="message">
- <div class="message-title">$i18n{storageSpaceLowMessageTitle}</div>
- <div class="message-description">
- <span>$i18n{storageSpaceLowMessageLine1}</span>
- <span>$i18n{storageSpaceLowMessageLine2}</span>
- </div>
- </div>
- </div>
- </div>
- </template>
- <template is="dom-if" if="[[isSpaceCriticallyLow_(sizeStat_.spaceState)]]">
- <div class="settings-box first">
- <div id="criticallyLowMessage" class="message-area">
- <iron-icon icon="cr:warning"></iron-icon>
- <div class="message">
- <div class="message-title">
- $i18n{storageSpaceCriticallyLowMessageTitle}
- </div>
- <div class="message-description">
- <span>$i18n{storageSpaceCriticallyLowMessageLine1}</span>
- <span>$i18n{storageSpaceCriticallyLowMessageLine2}</span>
- </div>
- </div>
- </div>
- </div>
- </template>
- <div class="settings-box first">
- <div id="barArea">
- <progress id="bar" class$="[[getBarClass_(sizeStat_.spaceState)]]"
- value="[[sizeStat_.usedRatio]]"></progress>
- <div id="barLabels">
- <div id="inUseLabelArea" class="bar-label">
- <div class="vertical-line"></div>
- <div class="wrapper"><span>$i18n{storageItemInUse}</span></div>
- <div class="wrapper">
- <span class="storage-size">[[sizeStat_.usedSize]]</span>
- </div>
- </div>
- <div id="availableLabelArea" class="bar-label end-aligned">
- <div class="vertical-line"></div>
- <div class="wrapper"><span>$i18n{storageItemAvailable}</span></div>
- <div class="wrapper">
- <span class="storage-size">[[sizeStat_.availableSize]]</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <cr-link-row id="downloadsSize" class="hr" on-click="onDownloadsTap_"
- label="$i18n{storageItemDownloads}"
- sub-label="$i18n{storageSizeComputing}" external></cr-link-row>
- <cr-link-row id="browsingDataSize" class="hr" on-click="onBrowsingDataTap_"
- label="$i18n{storageItemBrowsingData}"
- sub-label="$i18n{storageSizeComputing}" external></cr-link-row>
- <template is="dom-if" if="[[androidRunning_]]">
- <cr-link-row id="androidSize" class="hr" on-click="onAndroidTap_"
- label="$i18n{storageItemAndroid}"
- sub-label="$i18n{storageSizeComputing}" external></cr-link-row>
- </template>
- <template is="dom-if" if="[[showCrostiniStorage_]]">
- <cr-link-row id="crostiniSize" class="hr" on-click="onCrostiniTap_"
- label="$i18n{storageItemCrostini}"
- sub-label="$i18n{storageSizeComputing}"></cr-link-row>
- </template>
- <template is="dom-if" if="[[!isGuest_]]">
- <cr-link-row id="otherUsersSize" class="hr" on-click="onOtherUsersTap_"
- label="$i18n{storageItemOtherUsers}"
- sub-label="$i18n{storageSizeComputing}"></cr-link-row>
- </template>
- <template is="dom-if" if="[[androidEnabled]]">
- <cr-link-row id="externalStoragePreferences" class="hr"
- on-click="onExternalStoragePreferencesTap_"
- label="$i18n{storageExternal}"></cr-link-row>
- </template>
- </template>
- <script src="storage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/storage.js b/chromium/chrome/browser/resources/settings/device_page/storage.js
deleted file mode 100644
index ae7598fdbba..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/storage.js
+++ /dev/null
@@ -1,314 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-storage' is the settings subpage for storage settings.
- */
-cr.exportPath('settings');
-
-/**
- * Enumeration for device state about remaining space.
- * These values must be kept in sync with
- * StorageManagerHandler::StorageSpaceState in C++ code.
- * @enum {number}
- */
-settings.StorageSpaceState = {
- NORMAL: 0,
- LOW: 1,
- CRITICALLY_LOW: 2
-};
-
-/**
- * @typedef {{
- * totalSize: string,
- * availableSize: string,
- * usedSize: string,
- * usedRatio: number,
- * spaceState: settings.StorageSpaceState,
- * }}
- */
-settings.StorageSizeStat;
-
-Polymer({
- is: 'settings-storage',
-
- behaviors: [settings.RouteObserverBehavior, WebUIListenerBehavior],
-
- properties: {
- androidEnabled: Boolean,
-
- /** @private */
- androidRunning_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showCrostiniStorage_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showCrostini: Boolean,
-
- /** @private */
- isGuest_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isGuest');
- }
- },
-
- /** @private {settings.StorageSizeStat} */
- sizeStat_: Object,
- },
-
- observers: ['handleCrostiniEnabledChanged_(prefs.crostini.enabled.value)'],
-
- /**
- * Timer ID for periodic update.
- * @private {number}
- */
- updateTimerId_: -1,
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'storage-size-stat-changed', this.handleSizeStatChanged_.bind(this));
- this.addWebUIListener(
- 'storage-downloads-size-changed',
- this.handleDownloadsSizeChanged_.bind(this));
- this.addWebUIListener(
- 'storage-browsing-data-size-changed',
- this.handleBrowsingDataSizeChanged_.bind(this));
- this.addWebUIListener(
- 'storage-android-size-changed',
- this.handleAndroidSizeChanged_.bind(this));
- this.addWebUIListener(
- 'storage-crostini-size-changed',
- this.handleCrostiniSizeChanged_.bind(this));
- if (!this.isGuest_) {
- this.addWebUIListener(
- 'storage-other-users-size-changed',
- this.handleOtherUsersSizeChanged_.bind(this));
- }
- this.addWebUIListener(
- 'storage-android-running-changed',
- this.handleAndroidRunningChanged_.bind(this));
- },
-
- /**
- * Overridden from settings.RouteObserverBehavior.
- * @protected
- */
- currentRouteChanged: function() {
- if (settings.getCurrentRoute() == settings.routes.STORAGE) {
- this.onPageShown_();
- }
- },
-
- /** @private */
- onPageShown_: function() {
- // Updating storage information can be expensive (e.g. computing directory
- // sizes recursively), so we delay this operation until the page is shown.
- chrome.send('updateStorageInfo');
- // We update the storage usage periodically when the overlay is visible.
- this.startPeriodicUpdate_();
- },
-
- /**
- * Handler for tapping the "Downloads" item.
- * @private
- */
- onDownloadsTap_: function() {
- chrome.send('openDownloads');
- },
-
- /**
- * Handler for tapping the "Browsing data" item.
- * @private
- */
- onBrowsingDataTap_: function() {
- window.open('chrome://settings/clearBrowserData');
- },
-
- /**
- * Handler for tapping the "Android storage" item.
- * @private
- */
- onAndroidTap_: function() {
- chrome.send('openArcStorage');
- },
-
- /**
- * Handler for tapping the "Linux storage" item.
- * @private
- */
- onCrostiniTap_: function() {
- settings.navigateTo(
- settings.routes.CROSTINI_DETAILS, /* dynamicParams */ null,
- /* removeSearch */ true);
- },
-
- /**
- * Handler for tapping the "Other users" item.
- * @private
- */
- onOtherUsersTap_: function() {
- settings.navigateTo(
- settings.routes.ACCOUNTS,
- /* dynamicParams */ null, /* removeSearch */ true);
- },
-
- /**
- * Handler for tapping the "External storage preferences" item.
- * @private
- */
- onExternalStoragePreferencesTap_: function() {
- settings.navigateTo(settings.routes.EXTERNAL_STORAGE_PREFERENCES);
- },
-
- /**
- * @param {!settings.StorageSizeStat} sizeStat
- * @private
- */
- handleSizeStatChanged_: function(sizeStat) {
- this.sizeStat_ = sizeStat;
- this.$.inUseLabelArea.style.width = (sizeStat.usedRatio * 100) + '%';
- this.$.availableLabelArea.style.width =
- ((1 - sizeStat.usedRatio) * 100) + '%';
- },
-
- /**
- * @param {string} size Formatted string representing the size of Downloads.
- * @private
- */
- handleDownloadsSizeChanged_: function(size) {
- this.$.downloadsSize.subLabel = size;
- },
-
- /**
- * @param {string} size Formatted string representing the size of Browsing
- * data.
- * @private
- */
- handleBrowsingDataSizeChanged_: function(size) {
- this.$.browsingDataSize.subLabel = size;
- },
-
- /**
- * @param {string} size Formatted string representing the size of Android
- * storage.
- * @private
- */
- handleAndroidSizeChanged_: function(size) {
- if (this.androidRunning_) {
- this.$$('#androidSize').subLabel = size;
- }
- },
-
- /**
- * @param {string} size Formatted string representing the size of Crostini
- * storage.
- * @private
- */
- handleCrostiniSizeChanged_: function(size) {
- if (this.showCrostiniStorage_) {
- this.$$('#crostiniSize').subLabel = size;
- }
- },
-
- /**
- * @param {string} size Formatted string representing the size of Other users.
- * @private
- */
- handleOtherUsersSizeChanged_: function(size) {
- if (!this.isGuest_) {
- this.$$('#otherUsersSize').subLabel = size;
- }
- },
-
- /**
- * @param {boolean} running True if Android (ARC) is running.
- * @private
- */
- handleAndroidRunningChanged_: function(running) {
- this.androidRunning_ = running;
- },
-
- /**
- * @param {boolean} enabled True if Crostini is enabled.
- * @private
- */
- handleCrostiniEnabledChanged_: function(enabled) {
- this.showCrostiniStorage_ = enabled && this.showCrostini;
- },
-
- /**
- * Starts periodic update for storage usage.
- * @private
- */
- startPeriodicUpdate_: function() {
- // We update the storage usage every 5 seconds.
- if (this.updateTimerId_ == -1) {
- this.updateTimerId_ = window.setInterval(() => {
- if (settings.getCurrentRoute() != settings.routes.STORAGE) {
- this.stopPeriodicUpdate_();
- return;
- }
- chrome.send('updateStorageInfo');
- }, 5000);
- }
- },
-
- /**
- * Stops periodic update for storage usage.
- * @private
- */
- stopPeriodicUpdate_: function() {
- if (this.updateTimerId_ != -1) {
- window.clearInterval(this.updateTimerId_);
- this.updateTimerId_ = -1;
- }
- },
-
- /**
- * Returns true if the remaining space is low, but not critically low.
- * @param {!settings.StorageSpaceState} spaceState Status about the remaining
- * space.
- * @private
- */
- isSpaceLow_: function(spaceState) {
- return spaceState == settings.StorageSpaceState.LOW;
- },
-
- /**
- * Returns true if the remaining space is critically low.
- * @param {!settings.StorageSpaceState} spaceState Status about the remaining
- * space.
- * @private
- */
- isSpaceCriticallyLow_: function(spaceState) {
- return spaceState == settings.StorageSpaceState.CRITICALLY_LOW;
- },
-
- /**
- * Computes class name of the bar based on the remaining space size.
- * @param {!settings.StorageSpaceState} spaceState Status about the remaining
- * space.
- * @private
- */
- getBarClass_: function(spaceState) {
- switch (spaceState) {
- case settings.StorageSpaceState.LOW:
- return 'space-low';
- case settings.StorageSpaceState.CRITICALLY_LOW:
- return 'space-critically-low';
- default:
- return '';
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/storage_external.html b/chromium/chrome/browser/resources/settings/device_page/storage_external.html
deleted file mode 100644
index da2d3cafc7d..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/storage_external.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="storage_external_entry.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-storage-external">
- <template>
- <style include="settings-shared">
- h2 {
- padding-inline-start: var(--cr-section-padding);
- }
-
- </style>
- <div class="settings-box first">
- <span>
- $i18nRaw{storageAndroidAppsExternalDrivesNote}
- </span>
- </div>
- <h2>$i18n{storageExternalStorageListHeader}</h2>
- <iron-list id="removableDevices" preserve-focus
- items="[[externalStorages_]]">
- <template>
- <storage-external-entry uuid="[[item.uuid]]" label="[[item.label]]"
- prefs="{{prefs}}">
- </storage-external-entry>
- </template>
- </iron-list>
- </template>
- <script src="storage_external.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/storage_external.js b/chromium/chrome/browser/resources/settings/device_page/storage_external.js
deleted file mode 100644
index b52ab99a3ae..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/storage_external.js
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-storage-external' is the settings subpage for external storage
- * settings.
- */
-
-Polymer({
- is: 'settings-storage-external',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /**
- * List of the plugged-in external storages.
- * @private {Arrray<!settings.ExternalStorage>}
- */
- externalStorages_: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- /** @private {!chrome.settingsPrivate.PrefObject} */
- externalStorageVisiblePref_: {
- type: Object,
- value: function() {
- return /** @type {!chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
- },
-
- /** @private {?settings.DevicePageBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.DevicePageBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.browserProxy_.setExternalStoragesUpdatedCallback(
- this.handleExternalStoragesUpdated_.bind(this));
- this.browserProxy_.updateExternalStorages();
- },
-
- /**
- * @param {Array<!settings.ExternalStorage>} storages
- * @private
- */
- handleExternalStoragesUpdated_: function(storages) {
- this.externalStorages_ = storages;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/device_page/storage_external_entry.html b/chromium/chrome/browser/resources/settings/device_page/storage_external_entry.html
deleted file mode 100644
index c7f20266418..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/storage_external_entry.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="storage-external-entry">
- <template>
- <style include="settings-shared">
- settings-toggle-button {
- margin-inline-end: var(--cr-section-padding);
- margin-inline-start: var(--cr-section-indent-padding);
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
- </style>
- <settings-toggle-button class="hr"
- pref="{{visiblePref_}}"
- label="[[label]]"
- on-settings-boolean-control-change="onVisibleChange_">
- </settings-toggle-button>
- </template>
- <script src="storage_external_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/storage_external_entry.js b/chromium/chrome/browser/resources/settings/device_page/storage_external_entry.js
deleted file mode 100644
index 8749afa8033..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/storage_external_entry.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'storage-external-entry' is the polymer element for showing a certain
- * external storage device with a toggle switch. When the switch is ON,
- * the storage's uuid will be saved to a preference.
- */
-Polymer({
- is: 'storage-external-entry',
-
- behaviors: [WebUIListenerBehavior, PrefsBehavior],
-
- properties: {
- /**
- * FileSystem UUID of an external storage.
- */
- uuid: String,
-
- /**
- * Label of an external storage.
- */
- label: String,
-
- /** @private {chrome.settingsPrivate.PrefObject} */
- visiblePref_: {
- type: Object,
- value: function() {
- return /** @type {chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
- },
-
- observers: [
- 'updateVisible_(prefs.arc.visible_external_storages.*)',
- ],
-
- /**
- * Handler for when the toggle button for this entry is clicked by a user.
- * @param {!Event} event
- * @private
- */
- onVisibleChange_: function(event) {
- const visible = !!event.target.checked;
- if (visible) {
- this.appendPrefListItem('arc.visible_external_storages', this.uuid);
- } else {
- this.deletePrefListItem('arc.visible_external_storages', this.uuid);
- }
- chrome.metricsPrivate.recordBoolean(
- 'Arc.ExternalStorage.SetVisible', visible);
- },
-
- /**
- * Updates |visiblePref_| by reading the preference and check if it contains
- * UUID of this storage.
- * @private
- */
- updateVisible_: function() {
- const uuids = /** @type {!Array<string>} */ (
- this.getPref('arc.visible_external_storages').value);
- const visible = uuids.some((id) => id === this.uuid);
- const pref = {
- key: '',
- type: chrome.settingsPrivate.PrefType.BOOLEAN,
- value: visible,
- };
- this.visiblePref_ = pref;
- },
-}); \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/device_page/stylus.html b/chromium/chrome/browser/resources/settings/device_page/stylus.html
deleted file mode 100644
index 7dad79f1c65..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/stylus.html
+++ /dev/null
@@ -1,118 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-stylus">
- <template>
- <style include="settings-shared md-select">
- .settings-box > .secondary {
- align-items: center;
- display: flex;
- /* 'secondary' class applies a top margin. This breaks centering. */
- margin-top: 0;
- }
-
- paper-spinner-lite {
- margin-inline-start: 12px;
- @apply --cr-icon-height-width;
- }
-
- cr-policy-indicator {
- padding: 0 var(--cr-controlled-by-spacing);
- }
-
- #note-taking-app-lock-screen-settings {
- padding: 0 var(--cr-section-padding);
- }
- </style>
-
- <settings-toggle-button id="enableStylusToolsToggle"
- class="continuation"
- pref="{{prefs.settings.enable_stylus_tools}}"
- label="$i18n{stylusEnableStylusTools}">
- </settings-toggle-button>
-
- <template is="dom-if" if="[[hasInternalStylus_]]">
- <settings-toggle-button
- id ="launchPaletteOnEjectEventToggle"
- pref="{{prefs.settings.launch_palette_on_eject_event}}"
- label="$i18n{stylusAutoOpenStylusTools}"
- disabled="[[!prefs.settings.enable_stylus_tools.value]]">
- </settings-toggle-button>
- </template>
-
- <div class="settings-box">
- <div id="stylusNoteTakingAppLabel" class="start">
- $i18n{stylusNoteTakingApp}
- </div>
-
- <div id="no-apps" class="secondary"
- hidden$="[[!showNoApps_(appChoices_, waitingForAndroid_)]]">
- $i18n{stylusNoteTakingAppNoneAvailable}
- </div>
-
- <div id="waiting" class="secondary" hidden$="[[!waitingForAndroid_]]">
- $i18n{stylusNoteTakingAppWaitingForAndroid}
- <paper-spinner-lite active></paper-spinner-lite>
- </div>
-
- <select id="selectApp" class="md-select"
- on-change="onSelectedAppChanged_"
- aria-labelledby="stylusNoteTakingAppLabel"
- hidden$="[[!showApps_(appChoices_, waitingForAndroid_)]]">
- <template is="dom-repeat" items="[[appChoices_]]">
- <option value="[[item.value]]" selected="[[item.preferred]]">
- [[item.name]]
- </option>
- </template>
- </select>
- </div>
-
- <cr-link-row class="hr" on-click="onFindAppsTap_"
- hidden$="[[!prefs.arc.enabled.value]]"
- label="$i18n{stylusFindMoreAppsPrimary}"
- sub-label="$i18n{stylusFindMoreAppsSecondary}" external></cr-link-row>
-
- <template is="dom-if" if="[[supportsLockScreen_(selectedApp_)]]">
- <div id="note-taking-app-lock-screen-settings">
- <h2>$i18n{stylusNoteTakingAppLockScreenSettingsHeader}</h2>
-
- <div class="settings-box first">
- <div id="lock-screen-toggle-label" class="start"
- actionable$="[[!disallowedOnLockScreenByPolicy_(selectedApp_)]]"
- on-click="toggleLockScreenSupport_">
- $i18n{stylusNoteTakingAppEnabledOnLockScreen}
- </div>
- <template is="dom-if"
- if="[[disallowedOnLockScreenByPolicy_(selectedApp_)]]">
- <cr-policy-indicator
- id="enable-app-on-lock-screen-policy-indicator"
- indicator-type="[[userPolicyIndicator_]]">
- </cr-policy-indicator>
- </template>
- <cr-toggle id="enable-app-on-lock-screen-toggle"
- aria-labelledby="lock-screen-toggle-label"
- disabled="[[disallowedOnLockScreenByPolicy_(selectedApp_)]]"
- checked="[[lockScreenSupportEnabled_(selectedApp_)]]"
- on-change="toggleLockScreenSupport_">
- </cr-toggle>
- </div>
-
- <template is="dom-if" if="[[lockScreenSupportEnabled_(selectedApp_)]]">
- <settings-toggle-button id="keep-last-note-on-lock-screen-toggle"
- pref="{{prefs.settings.restore_last_lock_screen_note}}"
- label="$i18n{stylusNoteTakingAppKeepsLastNoteOnLockScreen}">
- </settings-toggle-button>
- </template>
- </div>
- </template>
-
- </template>
- <script src="stylus.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/device_page/stylus.js b/chromium/chrome/browser/resources/settings/device_page/stylus.js
deleted file mode 100644
index 1ee8863dbdf..00000000000
--- a/chromium/chrome/browser/resources/settings/device_page/stylus.js
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-stylus' is the settings subpage with stylus-specific settings.
- */
-
-const FIND_MORE_APPS_URL = 'https://play.google.com/store/apps/' +
- 'collection/promotion_30023cb_stylus_apps';
-
-Polymer({
- is: 'settings-stylus',
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Policy indicator type for user policy - used for policy indicator UI
- * shown when an app that is not allowed to run on lock screen by policy is
- * selected.
- * @type {CrPolicyIndicatorType}
- * @private
- */
- userPolicyIndicator_: {
- type: String,
- value: CrPolicyIndicatorType.USER_POLICY,
- },
-
- /**
- * Note taking apps the user can pick between.
- * @private {Array<!settings.NoteAppInfo>}
- */
- appChoices_: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- /**
- * True if the device has an internal stylus.
- * @private
- */
- hasInternalStylus_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('hasInternalStylus');
- },
- readOnly: true,
- },
-
- /**
- * Currently selected note taking app.
- * @private {?settings.NoteAppInfo}
- */
- selectedApp_: {
- type: Object,
- value: null,
- },
-
- /**
- * True if the ARC container has not finished starting yet.
- * @private
- */
- waitingForAndroid_: {
- type: Boolean,
- value: false,
- },
- },
-
- /**
- * @return {boolean} Whether note taking from the lock screen is supported
- * by the selected note-taking app.
- * @private
- */
- supportsLockScreen_: function() {
- return !!this.selectedApp_ &&
- this.selectedApp_.lockScreenSupport !=
- settings.NoteAppLockScreenSupport.NOT_SUPPORTED;
- },
-
- /**
- * @return {boolean} Whether the selected app is disallowed to handle note
- * actions from lock screen as a result of a user policy.
- * @private
- */
- disallowedOnLockScreenByPolicy_: function() {
- return !!this.selectedApp_ &&
- this.selectedApp_.lockScreenSupport ==
- settings.NoteAppLockScreenSupport.NOT_ALLOWED_BY_POLICY;
- },
-
- /**
- * @return {boolean} Whether the selected app is enabled as a note action
- * handler on the lock screen.
- * @private
- */
- lockScreenSupportEnabled_: function() {
- return !!this.selectedApp_ &&
- this.selectedApp_.lockScreenSupport ==
- settings.NoteAppLockScreenSupport.ENABLED;
- },
-
- /** @private {?settings.DevicePageBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.DevicePageBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_.setNoteTakingAppsUpdatedCallback(
- this.onNoteAppsUpdated_.bind(this));
- this.browserProxy_.requestNoteTakingApps();
- },
-
- /**
- * Finds note app info with the provided app id.
- * @param {!string} id
- * @return {?settings.NoteAppInfo}
- * @private
- */
- findApp_: function(id) {
- return this.appChoices_.find(function(app) {
- return app.value == id;
- }) ||
- null;
- },
-
- /**
- * Toggles whether the selected app is enabled as a note action handler on
- * the lock screen.
- * @private
- */
- toggleLockScreenSupport_: function() {
- assert(this.selectedApp_);
- if (this.selectedApp_.lockScreenSupport !=
- settings.NoteAppLockScreenSupport.ENABLED &&
- this.selectedApp_.lockScreenSupport !=
- settings.NoteAppLockScreenSupport.SUPPORTED) {
- return;
- }
-
- this.browserProxy_.setPreferredNoteTakingAppEnabledOnLockScreen(
- this.selectedApp_.lockScreenSupport ==
- settings.NoteAppLockScreenSupport.SUPPORTED);
- },
-
- /** @private */
- onSelectedAppChanged_: function() {
- const app = this.findApp_(this.$.selectApp.value);
- this.selectedApp_ = app;
-
- if (app && !app.preferred) {
- this.browserProxy_.setPreferredNoteTakingApp(app.value);
- }
- },
-
- /**
- * @param {Array<!settings.NoteAppInfo>} apps
- * @param {boolean} waitingForAndroid
- * @private
- */
- onNoteAppsUpdated_: function(apps, waitingForAndroid) {
- this.waitingForAndroid_ = waitingForAndroid;
- this.appChoices_ = apps;
-
- // Wait until app selection UI is updated before setting the selected app.
- this.async(this.onSelectedAppChanged_.bind(this));
- },
-
- /**
- * @param {Array<!settings.NoteAppInfo>} apps
- * @param {boolean} waitingForAndroid
- * @private
- */
- showNoApps_: function(apps, waitingForAndroid) {
- return apps.length == 0 && !waitingForAndroid;
- },
-
- /**
- * @param {Array<!settings.NoteAppInfo>} apps
- * @param {boolean} waitingForAndroid
- * @private
- */
- showApps_: function(apps, waitingForAndroid) {
- return apps.length > 0 && !waitingForAndroid;
- },
-
- /** @private */
- onFindAppsTap_: function() {
- this.browserProxy_.showPlayStore(FIND_MORE_APPS_URL);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.html b/chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.html
deleted file mode 100644
index 0a32fb448e8..00000000000
--- a/chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="downloads_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.js b/chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.js
deleted file mode 100644
index 0ce7ea25259..00000000000
--- a/chromium/chrome/browser/resources/settings/downloads_page/downloads_browser_proxy.js
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class DownloadsBrowserProxy {
- initializeDownloads() {}
- selectDownloadLocation() {}
- resetAutoOpenFileTypes() {}
- // <if expr="chromeos">
- /**
- * @param {string} path path to sanitze.
- * @return {!Promise<string>} string to display in UI.
- */
- getDownloadLocationText(path) {}
- // </if>
- }
-
- /**
- * @implements {settings.DownloadsBrowserProxy}
- */
- class DownloadsBrowserProxyImpl {
- /** @override */
- initializeDownloads() {
- chrome.send('initializeDownloads');
- }
-
- /** @override */
- selectDownloadLocation() {
- chrome.send('selectDownloadLocation');
- }
-
- /** @override */
- resetAutoOpenFileTypes() {
- chrome.send('resetAutoOpenFileTypes');
- }
-
- // <if expr="chromeos">
- /** @override */
- getDownloadLocationText(path) {
- return cr.sendWithPromise('getDownloadLocationText', path);
- }
- // </if>
- }
-
- cr.addSingletonGetter(DownloadsBrowserProxyImpl);
-
- return {
- DownloadsBrowserProxy: DownloadsBrowserProxy,
- DownloadsBrowserProxyImpl: DownloadsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html
deleted file mode 100644
index 966a88d2381..00000000000
--- a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html
+++ /dev/null
@@ -1,89 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/controlled_button.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="downloads_browser_proxy.html">
-<if expr="chromeos">
-<link rel="import" href="smb_shares_page.html">
-</if>
-
-<dom-module id="settings-downloads-page">
- <template>
- <style include="settings-shared">
- .block {
- display: block;
- }
- </style>
- <settings-animated-pages id="pages" section="downloads"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <div class="settings-box first two-line">
- <div class="start settings-box-text">
- <div>$i18n{downloadLocation}</div>
- <div class="secondary" id="defaultDownloadPath">
-<if expr="not chromeos">
- [[prefs.download.default_directory.value]]
-</if>
-<if expr="chromeos">
- [[downloadLocation_]]
-</if>
- </div>
- </div>
- <div class="separator"></div>
- <controlled-button id="changeDownloadsPath"
- label="$i18n{changeDownloadLocation}"
- on-click="selectDownloadLocation_"
- pref="[[prefs.download.default_directory]]"
- end-justified>
- </controlled-button>
- </div>
- <settings-toggle-button
- pref="{{prefs.download.prompt_for_download}}"
- label="$i18n{promptForDownload}">
- </settings-toggle-button>
-<if expr="chromeos">
- <settings-toggle-button class="continuation"
- pref="{{prefs.gdata.disabled}}"
- label="$i18n{disconnectGoogleDriveAccount}"
- hidden="[[!pageVisibility.googleDrive]]">
- </settings-toggle-button>
- <cr-link-row id="smbShares" class="hr" on-click="onTapSmbShares_"
- hidden="[[!pageVisibility.smbShares]]" label="$i18n{smbSharesTitle}">
- </cr-link-row>
-</if>
- <template is="dom-if" if="[[autoOpenDownloads_]]" restamp>
- <div class="settings-box">
- <div class="start">
- $i18n{openFileTypesAutomatically}
- </div>
- <div class="separator"></div>
- <cr-button id="resetAutoOpenFileTypes"
- on-click="onClearAutoOpenFileTypesTap_">
- $i18n{clear}
- </cr-button>
- </div>
- </template>
- </div>
-
-<if expr="chromeos">
- <template is="dom-if" route-path="/smbShares">
- <settings-subpage
- hidden="[[!pageVisibility.smbShares]]"
- associated-control="[[$$('#smbShares')]]"
- page-title="$i18n{smbSharesTitle}">
- <settings-smb-shares-page prefs="[[prefs]]">
- </settings-smb-shares-page>
- </settings-subpage>
- </template>
-</if>
- </settings-animated-pages>
- </template>
- <script src="downloads_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js
deleted file mode 100644
index 4ad4f50c39b..00000000000
--- a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-downloads-page' is the settings page containing downloads
- * settings.
- *
- * Example:
- *
- * <iron-animated-pages>
- * <settings-downloads-page prefs="{{prefs}}">
- * </settings-downloads-page>
- * ... other pages ...
- * </iron-animated-pages>
- */
-Polymer({
- is: 'settings-downloads-page',
-
- behaviors: [WebUIListenerBehavior, PrefsBehavior],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!DownloadsPageVisibility}
- */
- pageVisibility: Object,
-
- /** @private */
- autoOpenDownloads_: {
- type: Boolean,
- value: false,
- },
-
- // <if expr="chromeos">
- /**
- * The download location string that is suitable to display in the UI.
- */
- downloadLocation_: String,
- // </if>
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- // <if expr="chromeos">
- if (settings.routes.SMB_SHARES) {
- map.set(settings.routes.SMB_SHARES.path, '#smbShares');
- }
- // </if>
- return map;
- },
- },
-
- },
-
- // <if expr="chromeos">
- observers: [
- 'handleDownloadLocationChanged_(prefs.download.default_directory.value)'
- ],
- // </if>
-
- /** @private {?settings.DownloadsBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.DownloadsBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener('auto-open-downloads-changed', autoOpen => {
- this.autoOpenDownloads_ = autoOpen;
- });
-
- this.browserProxy_.initializeDownloads();
- },
-
- /** @private */
- selectDownloadLocation_: function() {
- listenOnce(this, 'transitionend', () => {
- this.browserProxy_.selectDownloadLocation();
- });
- },
-
- // <if expr="chromeos">
- /** @private */
- onTapSmbShares_: function() {
- settings.navigateTo(settings.routes.SMB_SHARES);
- },
-
- /**
- * @private
- */
- handleDownloadLocationChanged_: function() {
- this.browserProxy_
- .getDownloadLocationText(/** @type {string} */ (
- this.getPref('download.default_directory').value))
- .then(text => {
- this.downloadLocation_ = text;
- });
- },
- // </if>
-
- /** @private */
- onClearAutoOpenFileTypesTap_: function() {
- this.browserProxy_.resetAutoOpenFileTypes();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.html b/chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.html
deleted file mode 100644
index a5dfd95d3be..00000000000
--- a/chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-smb-shares-page">
- <template>
- <style include="settings-shared"></style>
- <div class="settings-box first">
- <div class="start">
- <span>$i18n{smbSharesLearnMoreLabel}</span>
- <a href="$i18n{smbSharesLearnMoreURL}" target="_blank">
- $i18n{learnMore}
- </a>
- </div>
- <template is="dom-if" if="[[!prefs.network_file_shares.allowed.value]]"
- restamp>
- <cr-policy-pref-indicator
- pref="[[prefs.network_file_shares.allowed]]"
- icon-aria-label="$i18n{smbSharesTitle}">
- </cr-policy-pref-indicator>
- </template>
- <cr-button class="action-button" id="addShare"
- on-click="onAddShareTap_"
- disabled="[[!prefs.network_file_shares.allowed.value]]">
- $i18n{addSmbShare}
- </cr-button>
- </div>
- <template is="dom-if" if="[[showAddSmbDialog_]]" restamp>
- <add-smb-share-dialog on-close="onAddSmbDialogClosed_"
- last-url="[[prefs.network_file_shares.most_recently_used_url.value]]"
- should-open-file-manager-after-mount
- </add-smb-share-dialog>
- </template>
- </template>
- <script src="smb_shares_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.js b/chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.js
deleted file mode 100644
index 28515494b40..00000000000
--- a/chromium/chrome/browser/resources/settings/downloads_page/smb_shares_page.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-Polymer({
- is: 'settings-smb-shares-page',
-
- behaviors: [
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private */
- showAddSmbDialog_: Boolean,
- },
-
- /**
- * Overridden from settings.RouteObserverBehavior.
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- if (route == settings.routes.SMB_SHARES) {
- this.showAddSmbDialog_ =
- settings.getQueryParameters().get('showAddShare') == 'true';
- }
- },
-
- /** @private */
- onAddShareTap_: function() {
- this.showAddSmbDialog_ = true;
- },
-
- /** @private */
- onAddSmbDialogClosed_: function() {
- this.showAddSmbDialog_ = false;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/extension_control_browser_proxy.html b/chromium/chrome/browser/resources/settings/extension_control_browser_proxy.html
deleted file mode 100644
index 4578d45356d..00000000000
--- a/chromium/chrome/browser/resources/settings/extension_control_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="extension_control_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/extension_control_browser_proxy.js b/chromium/chrome/browser/resources/settings/extension_control_browser_proxy.js
deleted file mode 100644
index 84b350ef2db..00000000000
--- a/chromium/chrome/browser/resources/settings/extension_control_browser_proxy.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class ExtensionControlBrowserProxy {
- // TODO(dbeam): should be be returning !Promise<boolean> to indicate whether
- // it succeeded?
- /** @param {string} extensionId */
- disableExtension(extensionId) {}
-
- /** @param {string} extensionId */
- manageExtension(extensionId) {}
- }
-
- /**
- * @implements {settings.ExtensionControlBrowserProxy}
- */
- class ExtensionControlBrowserProxyImpl {
- /** @override */
- disableExtension(extensionId) {
- chrome.send('disableExtension', [extensionId]);
- }
-
- /** @override */
- manageExtension(extensionId) {
- window.open('chrome://extensions?id=' + extensionId);
- }
- }
-
- cr.addSingletonGetter(ExtensionControlBrowserProxyImpl);
-
- return {
- ExtensionControlBrowserProxy: ExtensionControlBrowserProxy,
- ExtensionControlBrowserProxyImpl: ExtensionControlBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/global_scroll_target_behavior.html b/chromium/chrome/browser/resources/settings/global_scroll_target_behavior.html
deleted file mode 100644
index 556132fd20d..00000000000
--- a/chromium/chrome/browser/resources/settings/global_scroll_target_behavior.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="route.html">
-<script src="global_scroll_target_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/global_scroll_target_behavior.js b/chromium/chrome/browser/resources/settings/global_scroll_target_behavior.js
deleted file mode 100644
index 3677f8fab0d..00000000000
--- a/chromium/chrome/browser/resources/settings/global_scroll_target_behavior.js
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview |GlobalScrollTargetBehavior| allows an element to be aware of
- * the global scroll target.
- *
- * |scrollTarget| will be populated async by |setGlobalScrollTarget|.
- *
- * |subpageScrollTarget| will be equal to the |scrollTarget|, but will only be
- * populated when the current route is the |subpageRoute|.
- *
- * |setGlobalScrollTarget| should only be called once.
- */
-
-cr.define('settings', function() {
- const scrollTargetResolver = new PromiseResolver();
-
- /** @polymerBehavior */
- const GlobalScrollTargetBehaviorImpl = {
- properties: {
- /**
- * Read only property for the scroll target.
- * @type {HTMLElement}
- */
- scrollTarget: {
- type: Object,
- readOnly: true,
- },
-
- /**
- * Read only property for the scroll target that a subpage should use.
- * It will be set/cleared based on the current route.
- * @type {HTMLElement}
- */
- subpageScrollTarget: {
- type: Object,
- computed: 'getActiveTarget_(scrollTarget, active_)',
- },
-
- /**
- * The |subpageScrollTarget| should only be set for this route.
- * @type {settings.Route}
- * @private
- */
- subpageRoute: Object,
-
- /** Whether the |subpageRoute| is active or not. */
- active_: Boolean,
- },
-
- /** @override */
- attached: function() {
- this.active_ = settings.getCurrentRoute() == this.subpageRoute;
- scrollTargetResolver.promise.then(this._setScrollTarget.bind(this));
- },
-
- /** @param {!settings.Route} route */
- currentRouteChanged: function(route) {
- // Immediately set the scroll target to active when this page is
- // activated, but wait a task to remove the scroll target when the page is
- // deactivated. This gives scroll handlers like iron-list a chance to
- // handle scroll events that are fired as a result of the route changing.
- // TODO(https://crbug.com/859794): Having this timeout can result some
- // jumpy behaviour in the scroll handlers. |this.active_| can be set
- // immediately when this bug is fixed.
- if (route == this.subpageRoute) {
- this.active_ = true;
- } else {
- setTimeout(() => {
- this.active_ = false;
- });
- }
- },
-
- /**
- * Returns the target only when the route is active.
- * @param {HTMLElement} target
- * @param {boolean} active
- * @return {?HTMLElement}
- * @private
- */
- getActiveTarget_: function(target, active) {
- if (target === undefined || active === undefined) {
- return undefined;
- }
-
- return active ? target : null;
- },
- };
-
- /**
- * This should only be called once.
- * @param {HTMLElement} scrollTarget
- */
- const setGlobalScrollTarget = function(scrollTarget) {
- scrollTargetResolver.resolve(scrollTarget);
- };
-
- return {
- GlobalScrollTargetBehaviorImpl: GlobalScrollTargetBehaviorImpl,
- setGlobalScrollTarget: setGlobalScrollTarget,
- scrollTargetResolver: scrollTargetResolver,
- };
-});
-
-// This is done to make the closure compiler happy: it needs fully qualified
-// names when specifying an array of behaviors.
-/** @polymerBehavior */
-settings.GlobalScrollTargetBehavior =
- [settings.RouteObserverBehavior, settings.GlobalScrollTargetBehaviorImpl];
diff --git a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.html b/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.html
deleted file mode 100644
index 81603a750e5..00000000000
--- a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="google_assistant_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.js b/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.js
deleted file mode 100644
index c2ef77d4c49..00000000000
--- a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_browser_proxy.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the google assistant section
- * to interact with the browser.
- */
-
-cr.define('settings', function() {
- /** @interface */
- class GoogleAssistantBrowserProxy {
- /** Launches into the Google Assistant app settings. */
- launchGoogleAssistantSettings() {}
-
- /** Retrain the Assistant voice model. */
- retrainAssistantVoiceModel() {}
-
- /** Sync the voice model status. */
- syncVoiceModelStatus() {}
- }
-
- /** @implements {settings.GoogleAssistantBrowserProxy} */
- class GoogleAssistantBrowserProxyImpl {
- /** @override */
- showGoogleAssistantSettings() {
- chrome.send('showGoogleAssistantSettings');
- }
-
- /** @override */
- retrainAssistantVoiceModel() {
- chrome.send('retrainAssistantVoiceModel');
- }
-
- /** @override */
- syncVoiceModelStatus() {
- if (loadTimeData.getBoolean('voiceMatchEnabled')) {
- chrome.send('syncVoiceModelStatus');
- }
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(GoogleAssistantBrowserProxyImpl);
-
- return {
- GoogleAssistantBrowserProxy: GoogleAssistantBrowserProxy,
- GoogleAssistantBrowserProxyImpl: GoogleAssistantBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html b/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html
deleted file mode 100644
index 52053aae4d7..00000000000
--- a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html
+++ /dev/null
@@ -1,120 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="google_assistant_browser_proxy.html">
-<link rel="import" href="../controls/controlled_button.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../prefs/pref_util.html">
-
-<dom-module id="settings-google-assistant-page">
- <template>
- <style include="settings-shared md-select">
- .text-area {
- margin-inline-end: 24px;
- }
-
- select.md-select {
- min-width: 200px;
- }
-
- cr-policy-pref-indicator {
- margin-inline-end: var(--cr-controlled-by-spacing);
- }
- </style>
- <settings-toggle-button id="google-assistant-enable"
- class="first primary-toggle"
- pref="{{prefs.settings.voice_interaction.enabled}}"
- label="[[getAssistantOnOffLabel_(
- prefs.settings.voice_interaction.enabled.value)]]"
- disabled="{{prefs.settings.assistant.disabled_by_policy.value}}">
- </settings-toggle-button>
- <template is="dom-if"
- if="[[prefs.settings.voice_interaction.enabled.value]]">
- <settings-toggle-button id="google-assistant-context-enable"
- pref="{{prefs.settings.voice_interaction.context.enabled}}"
- label="$i18n{googleAssistantEnableContext}"
- sub-label="$i18n{googleAssistantEnableContextDescription}">
- </settings-toggle-button>
- <template is="dom-if" if="[[isAssistantAllowed_]]">
- <settings-toggle-button id="google-assistant-hotword-enable"
- pref="{{prefs.settings.voice_interaction.hotword.enabled}}"
- label="$i18n{googleAssistantEnableHotword}"
- sub-label="$i18n{googleAssistantEnableHotwordDescription}"
- on-settings-boolean-control-change="onEnableHotwordChange_"
- hidden="[[!hotwordDspAvailable_]]">
- </settings-toggle-button>
- <div class="settings-box" id="dsp-hotword-container"
- hidden="[[hotwordDspAvailable_]]">
- <div class="start text-area settings-box-text">
- <div class="label">
- $i18n{googleAssistantEnableHotword}
- </div>
- <div class="secondary label">
- $i18n{googleAssistantEnableHotwordWithoutDspDescription}
- </div>
- </div>
- <template is="dom-if" if="[[hotwordEnforced_]]" restamp>
- <cr-policy-pref-indicator id="hotword-policy-pref-indicator"
- pref="{{prefs.settings.voice_interaction.hotword.enabled}}">
- </cr-policy-pref-indicator>
- </template>
- <!-- We don't use the settings_dropdown_menu for the dspHotwordState
- drop down, because the dspHotwordState manages 2 preferences.
- The settings_dropdown_menu is designed to manage only a single
- preference. -->
- <select id="dsp-hotword-state" class="md-select"
- aria-labelledby="googleAssistantEnableHotword"
- on-change="onDspHotwordStateChange_"
- disabled="[[hotwordEnforced_]]">
- <template is="dom-repeat" items="[[hotwordDropdownList_]]">
- <option value="[[item.value]]"
- selected="[[isDspHotwordStateMatch_(item.value)]]">
- [[item.name]]
- </option>
- </template>
- </select>
- </div>
- <template is="dom-if" if="[[shouldShowVoiceMatchSettings_]]">
- <div class="settings-box continuation embedded">
- <div class="start text-area settings-box-text">
- <div class="label">
- $i18n{googleAssistantVoiceSettings}
- </div>
- <div class="secondary label">
- $i18n{googleAssistantVoiceSettingsDescription}
- </div>
- </div>
- <controlled-button id="retrain-voice-model"
- on-click="onRetrainVoiceModelTapped_"
- label="$i18n{googleAssistantVoiceSettingsRetrainButton}"
- pref="{{prefs.settings.voice_interaction.hotword.enabled}}">
- </controlled-button>
- </div>
- </template>
- <settings-toggle-button id="google-assistant-notification-enable"
- pref="{{prefs.settings.voice_interaction.notification.enabled}}"
- label="$i18n{googleAssistantEnableNotification}"
- sub-label="$i18n{googleAssistantEnableNotificationDescription}">
- </settings-toggle-button>
- <settings-toggle-button id="google-assistant-launch-with-mic-open"
- pref="{{prefs.settings.voice_interaction.launch_with_mic_open}}"
- label="$i18n{googleAssistantLaunchWithMicOpen}"
- sub-label="$i18n{googleAssistantLaunchWithMicOpenDescription}">
- </settings-toggle-button>
- </template>
- <cr-link-row id="google-assistant-settings" class="hr"
- on-click="onGoogleAssistantSettingsTapped_"
- label="$i18n{googleAssistantSettings}" external></cr-link-row>
- </template>
- </template>
- <script src="google_assistant_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js b/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js
deleted file mode 100644
index 5237e0168f7..00000000000
--- a/chromium/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * The types of Hotword enable status without Dsp support.
- * @enum {number}
- */
-const DspHotwordState = {
- DEFAULT_ON: 0,
- ALWAYS_ON: 1,
- OFF: 2,
-};
-
-/**
- * Indicates user's activity control consent status.
- *
- * Note: This should be kept in sync with ConsentStatus in
- * chromeos/services/assistant/public/cpp/assistant_prefs.h
- * @enum {number}
- */
-const ConsentStatus = {
- // The status is unknown.
- kUnknown: 0,
-
- // The user accepted activity control access.
- kActivityControlAccepted: 1,
-
- // The user is not authorized to give consent.
- kUnauthorized: 2,
-
- // The user's consent information is not found. This is typically the case
- // when consent from the user has never been requested.
- kNotFound: 3,
-};
-
-/**
- * @fileoverview 'settings-google-assistant-page' is the settings page
- * containing Google Assistant settings.
- */
-Polymer({
- is: 'settings-google-assistant-page',
-
- behaviors: [I18nBehavior, PrefsBehavior, WebUIListenerBehavior],
-
- properties: {
- /** @private */
- isAssistantAllowed_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isAssistantAllowed');
- },
- },
-
- /** @private */
- shouldShowVoiceMatchSettings_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- hotwordDspAvailable_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('hotwordDspAvailable');
- },
- },
-
- /** @private */
- hotwordDropdownList_: {
- type: Array,
- value: function() {
- return [
- {
- name: loadTimeData.getString(
- 'googleAssistantEnableHotwordWithoutDspRecommended'),
- value: DspHotwordState.DEFAULT_ON
- },
- {
- name: loadTimeData.getString(
- 'googleAssistantEnableHotwordWithoutDspAlwaysOn'),
- value: DspHotwordState.ALWAYS_ON
- },
- {
- name: loadTimeData.getString(
- 'googleAssistantEnableHotwordWithoutDspOff'),
- value: DspHotwordState.OFF
- }
- ];
- },
- },
-
- /** @private */
- hotwordEnforced_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- dspHotwordState_: {
- type: DspHotwordState,
- }
- },
-
- observers: [
- 'onPrefsChanged_(prefs.settings.voice_interaction.hotword.enabled.value)',
- 'onPrefsChanged_(prefs.settings.voice_interaction.hotword.always_on.value)',
- `onPrefsChanged_(
- prefs.settings.voice_interaction.activity_control.consent_status.value)`,
- 'onPrefsChanged_(prefs.settings.assistant.disabled_by_policy.value)',
- ],
-
- /** @private {?settings.GoogleAssistantBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.GoogleAssistantBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener('hotwordDeviceUpdated', (hasHotword) => {
- this.hotwordDspAvailable_ = hasHotword;
- });
-
- chrome.send('initializeGoogleAssistantPage');
- },
-
- /**
- * @param {boolean} toggleValue
- * @return {string}
- * @private
- */
- getAssistantOnOffLabel_: function(toggleValue) {
- return this.i18n(toggleValue ? 'toggleOn' : 'toggleOff');
- },
-
- /** @private */
- onGoogleAssistantSettingsTapped_: function() {
- this.browserProxy_.showGoogleAssistantSettings();
- },
-
- /** @private */
- onRetrainVoiceModelTapped_: function() {
- this.browserProxy_.retrainAssistantVoiceModel();
- },
-
- /** @private */
- onEnableHotwordChange_: function(event) {
- if (event.target.checked) {
- this.browserProxy_.syncVoiceModelStatus();
- }
- },
-
- /** @private */
- onDspHotwordStateChange_: function() {
- switch (Number(this.$$('#dsp-hotword-state').value)) {
- case DspHotwordState.DEFAULT_ON:
- this.setPrefValue('settings.voice_interaction.hotword.enabled', true);
- this.setPrefValue(
- 'settings.voice_interaction.hotword.always_on', false);
- this.browserProxy_.syncVoiceModelStatus();
- break;
- case DspHotwordState.ALWAYS_ON:
- this.setPrefValue('settings.voice_interaction.hotword.enabled', true);
- this.setPrefValue('settings.voice_interaction.hotword.always_on', true);
- this.browserProxy_.syncVoiceModelStatus();
- break;
- case DspHotwordState.OFF:
- this.setPrefValue('settings.voice_interaction.hotword.enabled', false);
- this.setPrefValue(
- 'settings.voice_interaction.hotword.always_on', false);
- break;
- default:
- console.error('Invalid Dsp hotword settings state');
- }
- },
-
- /**
- * @param {number} state
- * @return {boolean}
- * @private
- */
- isDspHotwordStateMatch_: function(state) {
- return state == this.dspHotwordState_;
- },
-
- /** @private */
- onPrefsChanged_: function() {
- if (this.getPref('settings.assistant.disabled_by_policy.value')) {
- this.setPrefValue('settings.voice_interaction.enabled', false);
- return;
- }
-
- this.refreshDspHotwordState_();
-
- this.shouldShowVoiceMatchSettings_ =
- !loadTimeData.getBoolean('voiceMatchDisabled') &&
- this.getPref('settings.voice_interaction.hotword.enabled.value') &&
- (this.getPref(
- 'settings.voice_interaction.activity_control.consent_status.value') ==
- ConsentStatus.kActivityControlAccepted);
-
- const hotwordEnabled =
- this.getPref('settings.voice_interaction.hotword.enabled');
-
- this.hotwordEnforced_ = hotwordEnabled.enforcement ==
- chrome.settingsPrivate.Enforcement.ENFORCED;
- },
-
- /** @private */
- refreshDspHotwordState_: function() {
- if (!this.getPref('settings.voice_interaction.hotword.enabled.value')) {
- this.dspHotwordState_ = DspHotwordState.OFF;
- } else if (this.getPref(
- 'settings.voice_interaction.hotword.always_on.value')) {
- this.dspHotwordState_ = DspHotwordState.ALWAYS_ON;
- } else {
- this.dspHotwordState_ = DspHotwordState.DEFAULT_ON;
- }
-
- if (this.$$('#dsp-hotword-state')) {
- this.$$('#dsp-hotword-state').value = this.dspHotwordState_;
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/i18n_setup.html b/chromium/chrome/browser/resources/settings/i18n_setup.html
deleted file mode 100644
index cdc7c9b2a5b..00000000000
--- a/chromium/chrome/browser/resources/settings/i18n_setup.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<script src="strings.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/icons.html b/chromium/chrome/browser/resources/settings/icons.html
deleted file mode 100644
index 595f018d907..00000000000
--- a/chromium/chrome/browser/resources/settings/icons.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
-
-<!--
-List icons here rather than importing large sets of (e.g. Polymer) icons.
-NOTE: Chrome OS icons go in ./chromeos/os_icons.html.
--->
-<iron-iconset-svg name="settings20" size="20">
- <svg>
- <defs>
- <g id="credit-card"><path d="M16.4,4 L3.6,4 C2.716,4 2.008,4.7271875 2.008,5.625 L2,15.375 C2,16.2728125 2.716,17 3.6,17 L16.4,17 C17.284,17 18,16.2728125 18,15.375 L18,5.625 C18,4.7271875 17.284,4 16.4,4 Z M16.5,15 L3.5,15 L3.5,10 L16.5,10 L16.5,15 Z M16.5,7 L3.5,7 L3.5,5.5 L16.5,5.5 L16.5,7 Z"></path></g>
- <g id="googleg"><path d="M16.58 8H9v2.75h4.47c-.24 1.2-1.42 3.27-4.47 3.27-2.72 0-4.93-2.25-4.93-5.02S6.28 3.98 9 3.98c1.54 0 2.57.66 3.17 1.22l2.19-2.12C12.97 1.79 11.16 1 9 1 4.58 1 1 4.58 1 9s3.58 8 8 8c4.62 0 7.68-3.25 7.68-7.82 0-.46-.04-.83-.1-1.18z"></path></g>
- <g id="location-on"><path d="M10,2 C6.95928571,2 4.5,4.504 4.5,7.6 C4.5,11.8 10,18 10,18 C10,18 15.5,11.8 15.5,7.6 C15.5,4.504 13.0407143,2 10,2 Z M10,9.5 C8.896,9.5 8,8.604 8,7.5 C8,6.396 8.896,5.5 10,5.5 C11.104,5.5 12,6.396 12,7.5 C12,8.604 11.104,9.5 10,9.5 Z"></path></g>
- <g id="vpn-key"><path d="M10.4727273,8 C9.87272727,6.2525 8.26181818,5 6.36363636,5 C3.95272727,5 2,7.01375 2,9.5 C2,11.98625 3.95272727,14 6.36363636,14 C8.26181818,14 9.87272727,12.7475 10.4727273,11 L13.6363636,11 L13.6363636,14 L16.5454545,14 L16.5454545,11 L18,11 L18,8 L10.4727273,8 Z M6.36363636,11 C5.56,11 4.90909091,10.32875 4.90909091,9.5 C4.90909091,8.67125 5.56,8 6.36363636,8 C7.16727273,8 7.81818182,8.67125 7.81818182,9.5 C7.81818182,10.32875 7.16727273,11 6.36363636,11 Z"></path></g>
- <g id="cloud-off"><path d="M16.4732571,13.3443682 C16.8002856,12.9882746 17,12.5134184 17,11.9922 C17,10.8882 16.104,9.9922 15,9.9922 L13.494,9.9922 L13.494,9.0002 C13.494,7.0672 11.927,5.5002 9.994,5.5002 C9.5847901,5.5002 9.1930204,5.57089988 8.82954884,5.70065995 L7.33083687,4.20194798 C8.11843435,3.75577808 9.02717677,3.5002 10,3.5002 C12.71,3.5002 14.957,5.4612 15.411,8.0412 C17.424,8.2502 19,9.9312 19,12.0002 C19,13.0718701 18.5784721,14.0451601 17.8921876,14.7632987 L16.4732571,13.3443682 Z M17.8711111,17 L16.8711111,18 L14.8713111,16.0002 L6,16.0002 C3.239,16.0002 1,13.7622 1,11.0002 C1,8.58475294 2.71868905,6.59044755 4.99627833,6.12516722 L2,3.12888889 L3,2.12888889 L17.8711111,17 Z M6.86331111,7.9922 L6,7.9922 C4.343,7.9922 3,9.3352 3,10.9922 C3,12.6492 4.343,13.9922 6,13.9922 L12.8633111,13.9922 L6.86331111,7.9922 Z"></path></g>
- <!-- The polygon ("+" shape) within this icon will always be filled with
- the color #4285F4. Any color fills specified programmatically will
- only be applied to the outer layer. -->
- <g id="printer-add"><path d="M17.8734304,8.29826826 C17.2839707,8.10470383 16.6542128,8 16,8 C13.3875623,8 11.1650842,9.66961525 10.3414114,12 L7,12 L7,15 L10.0829584,15 C10.2034032,15.7179235 10.4513404,16.3926158 10.8026932,17 L5,17 L5,14 L2,14 L2,9 C2,7.8954305 2.8954305,7 4,7 L5,7 L5,3 L15,3 L15,7 L16,7 C16.8576527,7 17.5892179,7.53984453 17.8734304,8.29826826 Z M7,5 L7,7 L13,7 L13,5 L7,5 Z"></path><polygon fill="#4285F4" points="17 13 19 13 19 15 17 15 17 17 15 17 15 15 13 15 13 13 15 13 15 11 17 11"></polygon></g>
- </defs>
- </svg>
-</iron-iconset-svg>
-
-<!-- NOTE: In the common case that the final icon will be 20x20, export the SVG
- at 20px and place it in the section above. -->
-<iron-iconset-svg name="settings" size="24">
- <svg>
- <defs>
- <!-- Ads icon in the Content Settings -->
- <g id="ads">
- <path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm0 14H5V8h14v10z"></path>
- </g>
-
- <!-- Cookie SVG obtained from rolfe@ -->
- <g id="cookie">
- <path d="M15.5 7.5V7c0-.98-.5-1.5-1.5-1.5h-.5c-.276 0-.5-.224-.5-.5V3c0-.98-1-1-1-1C6.3 2 1.712 6.77 2.014 12.54c.265 5.046 4.4 9.18 9.448 9.446C17.23 22.288 22 17.7 22 12v-1c0-.553-.447-1-1-1h-1.998c-.277 0-.502-.225-.502-.502V9c0-.938-.48-1.48-1.5-1.5h-1.5zm-9.706 4.972c-1.057.2-1.966-.71-1.766-1.766.112-.587.592-1.067 1.18-1.178 1.055-.2 1.965.71 1.764 1.765-.11.588-.59 1.068-1.178 1.18zm1.734-5.178c-.2-1.057.71-1.966 1.766-1.766.587.11 1.067.59 1.178 1.178.2 1.057-.708 1.966-1.765 1.766-.587-.11-1.068-.59-1.18-1.178zm3.766 12.178c-1.057.2-1.966-.71-1.766-1.766.112-.587.592-1.067 1.18-1.178 1.056-.2 1.965.71 1.764 1.766-.11.587-.59 1.067-1.178 1.178zM11.5 14c-.828 0-1.5-.67-1.5-1.5s.672-1.5 1.5-1.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5zm5 2c-.828 0-1.5-.67-1.5-1.5s.672-1.5 1.5-1.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" fill-rule="evenodd"></path>
- </g>
-
- <!-- The Google "G" icon in the Clear Browsing Data dialog. -->
- <g id="googleg">
- <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"></path>
- <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"></path>
- <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"></path>
- <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"></path>
- <path fill="none" d="M1 1h22v22H1z"></path>
- </g>
-
- <!-- Protected Content SVG -->
- <g id="protected-content">
- <path d="M10,15 L6,11.1783439 L7.41,9.83121019 L10,12.2961783 L16.59,6 L18,7.3566879 L10,15 Z M21,3 L3,3 C1.89,3 1,3.89 1,5 L1,17 C1,18.1 1.89,19 3,19 L8,19 L8,21 L16,21 L16,19 L21,19 C22.1,19 22.99,18.1 22.99,17 L23,5 C23,3.89 22.1,3 21,3 Z M21,17 L3,17 L3,5 L21,5 L21,17 Z"></path>
- </g>
-
- <!--
- These icons are copied from Polymer's iron-icons and kept in sorted order.
- See http://goo.gl/Y1OdAq for instructions on adding additional icons.
- -->
- <g id="accessibility"><path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"></path></g>
- <g id="apps"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"></path></g>
- <g id="assignment"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path></g>
-<if expr="not chromeos">
- <g id="build"><path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"></path></g>
-</if>
- <g id="check-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></g>
- <g id="clipboard"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path></g>
- <g id="cloud"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"></path></g>
- <g id="code"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"></path></g>
- <g id="content-copy"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path></g>
- <g id="exit-to-app"><path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g>
- <g id="language"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"></path></g>
- <g id="midi"><path d="M21,19.1H3V5h18V19.1z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z"></path><path fill="none" d="M21,19.1H3V5h18V19.1z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z"></path><path d="M14,5h3v8h-3V5z"></path><path d="M15,12h1v8h-1V12z"></path><path fill="none" d="M0,0h24v24H0V0z"></path><rect x="7" y="5" width="3" height="8"></rect><rect x="8" y="12" width="1" height="8"></rect></g>
- <g id="music-note"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"></path></g>
- <g id="notifications"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path></g>
- <g id="pdf"><path d="M7 11.5h1v-1H7v1zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9.5 8.5c0 .83-.67 1.5-1.5 1.5H7v2H5.5V9H8c.83 0 1.5.67 1.5 1.5v1zm10-1H17v1h1.5V13H17v2h-1.5V9h4v1.5zm-5 3c0 .83-.67 1.5-1.5 1.5h-2.5V9H13c.83 0 1.5.67 1.5 1.5v3zm-2.5 0h1v-3h-1v3z"></path><path fill="none" d="M0 0h24v24H0z"></path></g>
- <g id="palette"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
- <g id="payment-handler"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g>
- <g id="insecure-content"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g>
- <g id="photo"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g>
- <g id="power-settings-new"><path d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z"></path></g>
- <g id="protocol-handler"><path d="M21.72 11.33l-6.644-7.035a.97.97 0 0 0-1.38-.01l-1.67 1.72-1.617-1.712a.97.97 0 0 0-1.38-.01l-6.737 6.935c-.187.191-.29.447-.292.719-.002.272.099.529.28.722l6.644 7.034a.949.949 0 0 0 1.38.011l1.671-1.718 1.615 1.71a.949.949 0 0 0 1.381.01l6.74-6.935a1.054 1.054 0 0 0 .01-1.44zM6.947 12.464l3.657 3.785-.974.98-5.273-5.456 5.349-5.378.929.962-3.677 3.7a.998.998 0 0 0-.292.702 1 1 0 0 0 .28.705zm7.35 4.768l-.931-.963 3.68-3.7a1.012 1.012 0 0 0 .007-1.407l-3.656-3.784.974-.98 5.273 5.456-5.348 5.378z"></path></g>
- <g id="restore"><path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"></path></g>
- <g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g>
- <g id="save-original"><path d="M16.058 24.876H8.002c-1.107 0-2.002-.94-2.002-2.088l.01-16.7C6.01 4.939 6.905 4 8.012 4h8.046l6.035 6.263v4.175h-2.011v-3.131h-5.03v-5.22h-7.04v16.701h4.023v2.088h4.023zm7.581-6.157l1.474 1.481-4.18 4.2-4.182-4.2 1.474-1.48 1.662 1.659V16h2.09v4.378l1.662-1.659zM26.308 28H15.557v-2.4h10.75V28z" id="a"></path></g>
- <g id="sensors"><path d="M10 8.5c-0.8 0-1.5 0.7-1.5 1.5s0.7 1.5 1.5 1.5s1.5-0.7 1.5-1.5S10.8 8.5 10 8.5z M7.6 5.8 C6.2 6.7 5.2 8.2 5.2 10c0 1.8 1 3.4 2.4 4.2l0.8-1.4c-1-0.6-1.6-1.6-1.6-2.8c0-1.2 0.6-2.2 1.6-2.8L7.6 5.8z M14.8 10 c0-1.8-1-3.4-2.4-4.2l-0.8 1.4c0.9 0.6 1.6 1.6 1.6 2.8c0 1.2-0.6 2.2-1.6 2.8l0.8 1.4C13.8 13.4 14.8 11.8 14.8 10z M6 3 c-2.4 1.4-4 4-4 7c0 3 1.6 5.6 4 7l0.8-1.4c-1.9-1.1-3.2-3.2-3.2-5.6c0-2.4 1.3-4.5 3.2-5.6L6 3z M13.2 4.4 c1.9 1.1 3.2 3.2 3.2 5.6c0 2.4-1.3 4.5-3.2 5.6L14 17c2.4-1.4 4-4 4-7c0-3-1.6-5.6-4-7L13.2 4.4z"></path></g>
- <g id="serial-port"><path d="M22 9V7h-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-2h2v-2h-2v-2h2v-2h-2V9h2zm-4 10H4V5h14v14zM6 13h5v4H6zm6-6h4v3h-4zM6 7h5v5H6zm6 4h4v6h-4z"></path></g>
- <g id="supervisor-account"><path d="M16.5 12c1.38 0 2.49-1.12 2.49-2.5S17.88 7 16.5 7C15.12 7 14 8.12 14 9.5s1.12 2.5 2.5 2.5zM9 11c1.66 0 2.99-1.34 2.99-3S10.66 5 9 5C7.34 5 6 6.34 6 8s1.34 3 3 3zm7.5 3c-1.83 0-5.5.92-5.5 2.75V19h11v-2.25c0-1.83-3.67-2.75-5.5-2.75zM9 13c-2.33 0-7 1.17-7 3.5V19h7v-2.25c0-.85.33-2.34 2.37-3.47C10.5 13.1 9.66 13 9 13z"></path></g>
- <g id="sync-disabled"><path d="M10 6.35V4.26c-.8.21-1.55.54-2.23.96l1.46 1.46c.25-.12.5-.24.77-.33zm-7.14-.94l2.36 2.36C4.45 8.99 4 10.44 4 12c0 2.21.91 4.2 2.36 5.64L4 20h6v-6l-2.24 2.24C6.68 15.15 6 13.66 6 12c0-1 .25-1.94.68-2.77l8.08 8.08c-.25.13-.5.25-.77.34v2.09c.8-.21 1.55-.54 2.23-.96l2.36 2.36 1.27-1.27L4.14 4.14 2.86 5.41zM20 4h-6v6l2.24-2.24C17.32 8.85 18 10.34 18 12c0 1-.25 1.94-.68 2.77l1.46 1.46C19.55 15.01 20 13.56 20 12c0-2.21-.91-4.2-2.36-5.64L20 4z"></path></g>
- <g id="sync-problem"><path d="M3 12c0 2.21.91 4.2 2.36 5.64L3 20h6v-6l-2.24 2.24C5.68 15.15 5 13.66 5 12c0-2.61 1.67-4.83 4-5.65V4.26C5.55 5.15 3 8.27 3 12zm8 5h2v-2h-2v2zM21 4h-6v6l2.24-2.24C18.32 8.85 19 10.34 19 12c0 2.61-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74 0-2.21-.91-4.2-2.36-5.64L21 4zm-10 9h2V7h-2v6z"></path></g>
- <g id="usb"><path d="M15 7v4h1v2h-3V5h2l-3-4-3 4h2v8H8v-2.07c.7-.37 1.2-1.08 1.2-1.93 0-1.21-.99-2.2-2.2-2.2-1.21 0-2.2.99-2.2 2.2 0 .85.5 1.56 1.2 1.93V13c0 1.11.89 2 2 2h3v3.05c-.71.37-1.2 1.1-1.2 1.95 0 1.22.99 2.2 2.2 2.2 1.21 0 2.2-.98 2.2-2.2 0-.85-.49-1.58-1.2-1.95V15h3c1.11 0 2-.89 2-2v-2h1V7h-4z"></path></g>
- <g id="volume-up"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></g>
- <g id="bluetooth-scanning"><path d="M14.24 12.01l2.32 2.32c.28-.72.44-1.51.44-2.33 0-.82-.16-1.59-.43-2.31l-2.33 2.32zm5.29-5.3l-1.26 1.26c.63 1.21.98 2.57.98 4.02s-.36 2.82-.98 4.02l1.2 1.2a9.936 9.936 0 0 0 1.54-5.31c-.01-1.89-.55-3.67-1.48-5.19zm-3.82 1L10 2H9v7.59L4.41 5 3 6.41 8.59 12 3 17.59 4.41 19 9 14.41V22h1l5.71-5.71-4.3-4.29 4.3-4.29zM11 5.83l1.88 1.88L11 9.59V5.83zm1.88 10.46L11 18.17v-3.76l1.88 1.88z"></path></g>
-<if expr="not chromeos">
- <g id="web"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z"></path></g>
-</if>
- <g id="zoom-in"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></g>
- </defs>
- </svg>
-</iron-iconset-svg>
diff --git a/chromium/chrome/browser/resources/settings/images/settings_icon_add_circle.svg b/chromium/chrome/browser/resources/settings/images/settings_icon_add_circle.svg
deleted file mode 100644
index b4422925f95..00000000000
--- a/chromium/chrome/browser/resources/settings/images/settings_icon_add_circle.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="#4285f4" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/images/settings_icon_add_wifi.svg b/chromium/chrome/browser/resources/settings/images/settings_icon_add_wifi.svg
deleted file mode 100644
index 7174d59523f..00000000000
--- a/chromium/chrome/browser/resources/settings/images/settings_icon_add_wifi.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="#4285f4" d="M10.92 16.85l-.91 1.14L10 18l-.01-.01L0 5.46C.387 5.164 4.227 2 10 2c5.765 0 9.613 3.165 10 3.46l-2.198 2.756a6 6 0 0 0-6.882 8.634z" fill-opacity=".3"/><path fill="#4285f4" d="M17 13v-3h-2v3h-3v2h3v3h2v-3h3v-2h-3z"/></svg> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/images/sync_banner.svg b/chromium/chrome/browser/resources/settings/images/sync_banner.svg
deleted file mode 100644
index 12f4c756f9b..00000000000
--- a/chromium/chrome/browser/resources/settings/images/sync_banner.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1360" height="240"><defs><clipPath id="a"><path fill="none" d="M0-.36h1360v239.72H0z"/></clipPath></defs><path fill="#f8f9fa" d="M0-.36h1360v240H0z"/><g clip-path="url(#a)"><path d="M458.53 122.23h-1.31V51.9a16.06 16.06 0 0 1 16-16h46.8v1.31h-46.8a14.75 14.75 0 0 0-14.69 14.69z" fill="#bdc0c5"/><path d="M377.09 158a30.17 30.17 0 0 1 30.1 28.82H326a19.6 19.6 0 0 1 26.15-17.2l.87.31.58-.72A30 30 0 0 1 377.09 158m0-1.31a31.38 31.38 0 0 0-24.46 11.7 20.91 20.91 0 0 0-27.94 19.75h83.85a31.44 31.44 0 0 0-31.44-31.44z" fill="#e8e9eb"/><path d="M496.39 207.3h-23.13a14.75 14.75 0 0 1-14.73-14.73v-70.34h-1.31v70.33a16.06 16.06 0 0 0 16 16h23.13zm43.32-170.66V38a14.74 14.74 0 0 1 9.82 13.87v71.43h1.31V51.9a16.06 16.06 0 0 0-11.13-15.26z" fill="#bdc0c5"/><path d="M531.89 199.89h-56.43A10.44 10.44 0 0 1 465 189.46v-135A10.44 10.44 0 0 1 475.46 44h56.43a10.44 10.44 0 0 1 10.43 10.43v135a10.44 10.44 0 0 1-10.43 10.46zM475.46 45.3a9.13 9.13 0 0 0-9.12 9.12v135a9.13 9.13 0 0 0 9.12 9.12h56.43a9.13 9.13 0 0 0 9.12-9.12v-135a9.13 9.13 0 0 0-9.12-9.12z" fill="#e8e9eb"/><path d="M549.53 178.93v13.64a14.75 14.75 0 0 1-14.73 14.73h-23.13v1.31h23.13a16.06 16.06 0 0 0 16-16v-13.68z" fill="#e54440"/><path d="M629 90.92a39.3 39.3 0 1 1-39.3 39.3 39.35 39.35 0 0 1 39.3-39.3m0-2.62a41.92 41.92 0 1 0 42 41.93 41.92 41.92 0 0 0-42-41.93z" fill="#e8e9eb"/><circle cx="650.37" cy="145.95" r="5.24" fill="#4b87f1"/><circle cx="563.16" cy="130.23" r="41.92" fill="#34a751"/><path d="M587.12 130.23a41.73 41.73 0 0 0 9 25.92 41.88 41.88 0 0 0 0-51.83 41.73 41.73 0 0 0-9 25.91z" opacity=".12"/><path d="M457.17 75.43V65c21.47 0 38.35-6 50.18-17.91C525.93 28.37 526 1.06 526 .79l10.5-.08c0 1.29 0 31.8-21.63 53.64-13.87 13.99-33.29 21.08-57.7 21.08z" fill="#f7bb2a"/><path d="M387.74 137h-10.49a80.9 80.9 0 0 1 10-36c9.21-16.42 28.8-36 69.92-36v10.43c-28.93 0-49.37 10.34-60.77 30.72a70.69 70.69 0 0 0-8.66 30.85z" fill="#e8e9eb"/><path d="M562.83 146.74v-5.08h.66v3.5l4.93-4.93-4.93-4.93v3.5h-.66v-5.08l6.51 6.51zm.66-20l-6.49-6.52 6.51-6.51v5.08h-.66v-3.5l-4.93 4.93 4.93 4.93v-3.5h.66z" fill="#fff"/><path d="M572.14 135.51l-.56-.33a9.77 9.77 0 0 0-8.42-14.72v-.66a10.42 10.42 0 0 1 9 15.71zm-8.98 5.13a10.42 10.42 0 0 1-9-15.71l.56.33a9.77 9.77 0 0 0 8.44 14.74z" fill="#fff"/><path d="M112 114.83H76.35v-.54a17.82 17.82 0 0 1 35.64 0zm-34.55-1.08h33.45a16.74 16.74 0 0 0-33.46 0z" fill="#bdc0c5"/><path d="M433.65 136.79h-34.56a17.28 17.28 0 1 1 34.56 0z" fill="#e8e9eb"/><path d="M339.22 200H157.78V75.65a5.3 5.3 0 0 1 5.28-5.28h170.88a5.3 5.3 0 0 1 5.28 5.28z" fill="#f4f4f4"/><path d="M330.58 71.45a7.57 7.57 0 0 1 7.56 7.55v119.89H158.86V79a7.57 7.57 0 0 1 7.56-7.56h164.16m0-1.08H166.42a8.67 8.67 0 0 0-8.64 8.64v121h181.44V79a8.67 8.67 0 0 0-8.64-8.64zM131.86 207.53h233.28v1.08H131.86z" fill="#bdc0c5"/><path d="M169.2 115.84a25.87 25.87 0 0 0-20.2 9.64 17.24 17.24 0 0 0-23 16.28h69.12a25.92 25.92 0 0 0-25.92-25.92z" fill="#fff"/><path d="M195.66 142.3h-70.2v-.54a17.78 17.78 0 0 1 23.39-16.91 26.46 26.46 0 0 1 46.81 16.91zm-69.11-1.08h68a25.38 25.38 0 0 0-45.12-15.4l-.24.3-.36-.13a16.7 16.7 0 0 0-22.31 15.23z" fill="#bdc0c5"/><path d="M76.24 163.72l7.41-7.41c15.18 15.18 31.38 22.86 48.15 22.82 26.34-.06 45.71-19.31 45.9-19.5l7.47 7.35c-.9.92-22.47 22.5-53.22 22.63-19.71.08-38.45-8.61-55.71-25.89z" fill="#4285f4"/><path d="M-16.4 158.16l-7.41-7.41a80.9 80.9 0 0 1 32.5-18.36c18.15-5.14 45.89-5.16 75 23.92l-7.41 7.41c-20.5-20.46-42.28-27.61-64.74-21.25a70.69 70.69 0 0 0-27.94 15.69z" fill="#e8e9eb"/><path d="M1273.24 78.47a22.41 22.41 0 1 1 8.31-1.61 22.29 22.29 0 0 1-8.31 1.61zm0-43.53a21.16 21.16 0 0 0-19.64 29 21.14 21.14 0 1 0 19.64-29zM1238 259h-160a13.57 13.57 0 0 1-13.55-13.55v-20h1.2v20a12.37 12.37 0 0 0 12.35 12.31h160a12.37 12.37 0 0 0 12.35-12.35v-87.1A12.37 12.37 0 0 0 1238 146h-24v-1.2h24a13.57 13.57 0 0 1 13.55 13.55v87.05A13.57 13.57 0 0 1 1238 259z" fill="#bdc0c5"/><path fill="#bdc0c5" d="M1214.01 145.96H1122l6-1.2h86.01v1.2z"/><path d="M1065.6 199.36h-1.2v-41a13.57 13.57 0 0 1 13.6-13.6h59V146h-59a12.37 12.37 0 0 0-12.35 12.35z" fill="#bdc0c5"/><path d="M1080 185.56a28.74 28.74 0 0 0-22.41 10.71 19.16 19.16 0 0 0-25.59 18.09h76.8a28.8 28.8 0 0 0-28.8-28.8z" fill="#fff"/><path d="M1109.45 215h-78v-.6a19.76 19.76 0 0 1 26-18.79 29.4 29.4 0 0 1 52 18.79zm-76.79-1.2h75.59a28.2 28.2 0 0 0-50.13-17.11l-.27.33-.4-.14a18.55 18.55 0 0 0-24.78 16.92z" fill="#bdc0c5"/><path fill="#f8f9fa" d="M997.34 58.76l71.32-60.81"/><path fill="#e8e9eb" d="M996.638 57.935l71.36-60.755 1.297 1.523-71.36 60.754zm286.222 53.225h-11.65a89.89 89.89 0 0 0 11.11 40c10.22 18.29 32 40.1 77.68 40.1v-11.68c-32.14 0-54.86-11.49-67.52-34.14a78.55 78.55 0 0 1-9.62-34.28z"/></g></svg> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/images/sync_banner_dark.svg b/chromium/chrome/browser/resources/settings/images/sync_banner_dark.svg
deleted file mode 100644
index 42cc59bc033..00000000000
--- a/chromium/chrome/browser/resources/settings/images/sync_banner_dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1360" height="240"><path fill="#28282b" d="M-14.74-2.96h1389.47v245.2H-14.74z"/><path d="M458.53 122.23h-1.31V51.9a16.06 16.06 0 0 1 16-16h46.8v1.31h-46.8a14.75 14.75 0 0 0-14.69 14.69zM377.09 158a30.17 30.17 0 0 1 30.1 28.82H326a19.6 19.6 0 0 1 26.15-17.2l.87.31.58-.72A30 30 0 0 1 377.09 158m0-1.31a31.38 31.38 0 0 0-24.46 11.7 20.91 20.91 0 0 0-27.94 19.75h83.85a31.44 31.44 0 0 0-31.44-31.44zm119.3 50.61h-23.13a14.75 14.75 0 0 1-14.73-14.73v-70.34h-1.31v70.33a16.06 16.06 0 0 0 16 16h23.13zm43.32-170.66V38a14.74 14.74 0 0 1 9.82 13.87v71.43h1.31V51.9a16.06 16.06 0 0 0-11.13-15.26z" fill="#5f6368"/><path d="M531.89 199.89h-56.43A10.44 10.44 0 0 1 465 189.46v-135A10.44 10.44 0 0 1 475.46 44h56.43a10.44 10.44 0 0 1 10.43 10.43v135a10.44 10.44 0 0 1-10.43 10.46zM475.46 45.3a9.13 9.13 0 0 0-9.12 9.12v135a9.13 9.13 0 0 0 9.12 9.12h56.43a9.13 9.13 0 0 0 9.12-9.12v-135a9.13 9.13 0 0 0-9.12-9.12z" fill="#4e5154"/><path d="M549.53 178.93v13.64a14.75 14.75 0 0 1-14.73 14.73h-23.13v1.31h23.13a16.06 16.06 0 0 0 16-16v-13.68z" fill="#f18a81"/><path d="M629 90.92a39.3 39.3 0 1 1-39.3 39.3 39.35 39.35 0 0 1 39.3-39.3m0-2.62a41.92 41.92 0 1 0 42 41.93 41.92 41.92 0 0 0-42-41.93z" fill="#5f6368"/><circle cx="650.37" cy="145.95" r="5.24" fill="#8ab4f8"/><circle cx="563.16" cy="130.23" r="41.92" fill="#81c995"/><path d="M587.12 130.23a41.73 41.73 0 0 0 9 25.92 41.88 41.88 0 0 0 0-51.83 41.73 41.73 0 0 0-9 25.91z" opacity=".12"/><path d="M457.17 75.43V65c21.47 0 38.35-6 50.18-17.91C525.93 28.37 526 1.06 526 .79l10.5-.08c0 1.29 0 31.8-21.63 53.64-13.87 13.99-33.29 21.08-57.7 21.08z" fill="#f7bb2a"/><path d="M387.74 137h-10.49a80.9 80.9 0 0 1 10-36c9.21-16.42 28.8-36 69.92-36v10.43c-28.93 0-49.37 10.34-60.77 30.72a70.69 70.69 0 0 0-8.66 30.85z" fill="#414447"/><path d="M562.83 146.74v-5.08h.66v3.5l4.93-4.93-4.93-4.93v3.5h-.66v-5.08l6.51 6.51zm.66-20l-6.49-6.52 6.51-6.51v5.08h-.66v-3.5l-4.93 4.93 4.93 4.93v-3.5h.66z" fill="#292a2d"/><path d="M572.14 135.51l-.56-.33a9.77 9.77 0 0 0-8.42-14.72v-.66a10.42 10.42 0 0 1 9 15.71zm-8.98 5.13a10.42 10.42 0 0 1-9-15.71l.56.33a9.77 9.77 0 0 0 8.44 14.74z" fill="#292a2d"/><path d="M112 114.83H76.35v-.54a17.82 17.82 0 0 1 35.64 0zm-34.55-1.08h33.45a16.74 16.74 0 0 0-33.46 0z" fill="#5f6368"/><path d="M433.65 136.79h-34.56a17.28 17.28 0 1 1 34.56 0z" fill="#414447"/><path d="M339.22 200H157.78V75.65a5.3 5.3 0 0 1 5.28-5.28h170.88a5.3 5.3 0 0 1 5.28 5.28z" fill="#262628"/><path d="M330.58 71.45a7.57 7.57 0 0 1 7.56 7.55v119.89H158.86V79a7.57 7.57 0 0 1 7.56-7.56h164.16m0-1.08H166.42a8.67 8.67 0 0 0-8.64 8.64v121h181.44V79a8.67 8.67 0 0 0-8.64-8.64zM131.86 207.53h233.28v1.08H131.86z" fill="#5f6368"/><path d="M169.2 115.84a25.87 25.87 0 0 0-20.2 9.64 17.24 17.24 0 0 0-23 16.28h69.12a25.92 25.92 0 0 0-25.92-25.92z" fill="#292a2d"/><path d="M195.66 142.3h-70.2v-.54a17.78 17.78 0 0 1 23.39-16.91 26.46 26.46 0 0 1 46.81 16.91zm-69.11-1.08h68a25.38 25.38 0 0 0-45.12-15.4l-.24.3-.36-.13a16.7 16.7 0 0 0-22.31 15.23z" fill="#5f6368"/><path d="M76.24 163.72l7.41-7.41c15.18 15.18 31.38 22.86 48.15 22.82 26.34-.06 45.71-19.31 45.9-19.5l7.47 7.35c-.9.92-22.47 22.5-53.22 22.63-19.71.08-38.45-8.61-55.71-25.89z" fill="#8ab4f8"/><path d="M-16.4 158.16l-7.41-7.41a80.9 80.9 0 0 1 32.5-18.36c18.15-5.14 45.89-5.16 75 23.92l-7.41 7.41c-20.5-20.46-42.28-27.61-64.74-21.25a70.69 70.69 0 0 0-27.94 15.69z" fill="#414447"/><path d="M1273.24 78.47a22.41 22.41 0 1 1 8.31-1.61 22.29 22.29 0 0 1-8.31 1.61zm0-43.53a21.16 21.16 0 0 0-19.64 29 21.14 21.14 0 1 0 19.64-29zM1238 259h-160a13.57 13.57 0 0 1-13.55-13.55v-20h1.2v20a12.37 12.37 0 0 0 12.35 12.31h160a12.37 12.37 0 0 0 12.35-12.35v-87.1A12.37 12.37 0 0 0 1238 146h-24v-1.2h24a13.57 13.57 0 0 1 13.55 13.55v87.05A13.57 13.57 0 0 1 1238 259z" fill="#5f6368"/><path fill="#5f6368" d="M1214.01 145.96H1122l6-1.2h86.01v1.2z"/><path d="M1065.6 199.36h-1.2v-41a13.57 13.57 0 0 1 13.6-13.6h59V146h-59a12.37 12.37 0 0 0-12.35 12.35z" fill="#5f6368"/><path d="M1080 185.56a28.74 28.74 0 0 0-22.41 10.71 19.16 19.16 0 0 0-25.59 18.09h76.8a28.8 28.8 0 0 0-28.8-28.8z" fill="#262628"/><path d="M1109.45 215h-78v-.6a19.76 19.76 0 0 1 26-18.79 29.4 29.4 0 0 1 52 18.79zm-76.79-1.2h75.59a28.2 28.2 0 0 0-50.13-17.11l-.27.33-.4-.14a18.55 18.55 0 0 0-24.78 16.92z" fill="#5f6368"/><path fill="#f8f9fa" d="M997.34 58.76l71.32-60.81"/><path fill="#5f6368" d="M996.638 57.935l71.36-60.755 1.297 1.523-71.36 60.754z"/><path d="M1282.86 111.16h-11.65a89.89 89.89 0 0 0 11.11 40c10.22 18.29 32 40.1 77.68 40.1v-11.68c-32.14 0-54.86-11.49-67.52-34.14a78.55 78.55 0 0 1-9.62-34.28z" fill="#414447"/><path fill="none" d="M0-.36h1360v239.72H0z"/></svg> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.html b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.html
deleted file mode 100644
index ad5b501f448..00000000000
--- a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="incompatible_applications_browser_proxy.html">
-
-<dom-module id="incompatible-application-item">
- <template>
- <style include="settings-shared">
- :host {
- display: block;
- }
- </style>
- <div class="list-item">
- <div class="start">[[applicationName]]</div>
- <div class="separator"></div>
- <cr-button class="action-button" on-click="onActionTap_">
- [[getActionName_(actionType)]]
- </cr-button>
- </div>
- </template>
- <script src="incompatible_application_item.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.js b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.js
deleted file mode 100644
index 2a8916a4ed0..00000000000
--- a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.js
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'incompatible-application-item' represents one item in a "list-box" of
- * incompatible applications, as defined in
- * chrome/browser/win/conflicts/incompatible_applications_updater_win.h.
- * This element contains a button that can be used to remove or update the
- * incompatible application, depending on the value of the action-type property.
- *
- * Example usage:
- *
- * <div class="list-box">
- * <incompatible-application-item
- * application-name="Google Chrome"
- * action-type="1"
- * action-url="https://www.google.com/chrome/more-info">
- * </incompatible-application-item>
- * </div>
- *
- * or
- *
- * <div class="list-box">
- * <template is="dom-repeat" items="[[applications]]" as="application">
- * <incompatible-application-item
- * application-name="[[application.name]]"
- * action-type="[[application.actionType]]"
- * action-url="[[application.actionUrl]]">
- * </incompatible-application-item>
- * </template>
- * </div>
- */
-
-Polymer({
- is: 'incompatible-application-item',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * The name of the application to be displayed. Also used for the UNINSTALL
- * action, where the name is passed to the startApplicationUninstallation()
- * call.
- */
- applicationName: String,
-
- /**
- * The type of the action to be taken on this incompatible application. Must
- * be one of BlacklistMessageType in
- * chrome/browser/win/conflicts/proto/module_list.proto.
- * @type {!settings.ActionTypes}
- */
- actionType: Number,
-
- /**
- * For the actions MORE_INFO and UPGRADE, this is the URL that must be
- * opened when the action button is tapped.
- */
- actionUrl: String,
- },
-
- /** @private {settings.IncompatibleApplicationsBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ =
- settings.IncompatibleApplicationsBrowserProxyImpl.getInstance();
- },
-
- /**
- * Executes the action for this incompatible application, depending on
- * actionType.
- * @private
- */
- onActionTap_: function() {
- if (this.actionType === settings.ActionTypes.UNINSTALL) {
- this.browserProxy_.startApplicationUninstallation(this.applicationName);
- } else if (
- this.actionType === settings.ActionTypes.MORE_INFO ||
- this.actionType === settings.ActionTypes.UPGRADE) {
- this.browserProxy_.openURL(this.actionUrl);
- } else {
- assertNotReached();
- }
- },
-
- /**
- * @return {string} The label that should be applied to the action button.
- * @private
- */
- getActionName_: function(actionType) {
- if (actionType === settings.ActionTypes.UNINSTALL) {
- return this.i18n('incompatibleApplicationsRemoveButton');
- }
- if (actionType === settings.ActionTypes.MORE_INFO) {
- return this.i18n('learnMore');
- }
- if (actionType === settings.ActionTypes.UPGRADE) {
- return this.i18n('incompatibleApplicationsUpdateButton');
- }
- assertNotReached();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.html b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.html
deleted file mode 100644
index 8d55a232daa..00000000000
--- a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="incompatible_applications_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.js b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.js
deleted file mode 100644
index 5dcc1b16048..00000000000
--- a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.js
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the Incompatible Applications section
- * to interact with the browser.
- */
-
-cr.exportPath('settings');
-
-/**
- * All possible actions to take on an incompatible application.
- *
- * Must be kept in sync with BlacklistMessageType in
- * chrome/browser/win/conflicts/proto/module_list.proto
- * @readonly
- * @enum {number}
- */
-settings.ActionTypes = {
- UNINSTALL: 0,
- MORE_INFO: 1,
- UPGRADE: 2,
-};
-
-/**
- * @typedef {{
- * name: string,
- * actionType: {settings.ActionTypes},
- * actionUrl: string,
- * }}
- */
-settings.IncompatibleApplication;
-
-cr.define('settings', function() {
- /** @interface */
- class IncompatibleApplicationsBrowserProxy {
- /**
- * Get the list of incompatible applications.
- * @return {!Promise<!Array<!settings.IncompatibleApplication>>}
- */
- requestIncompatibleApplicationsList() {}
-
- /**
- * Launches the Apps & Features page that allows uninstalling
- * 'applicationName'.
- * @param {string} applicationName
- */
- startApplicationUninstallation(applicationName) {}
-
- /**
- * Opens the specified URL in a new tab.
- * @param {!string} url
- */
- openURL(url) {}
-
- /**
- * Requests the plural string for the subtitle of the Incompatible
- * Applications subpage.
- * @param {number} numApplications
- * @return {!Promise<string>}
- */
- getSubtitlePluralString(numApplications) {}
-
- /**
- * Requests the plural string for the subtitle of the Incompatible
- * Applications subpage, when the user does not have administrator rights.
- * @param {number} numApplications
- * @return {!Promise<string>}
- */
- getSubtitleNoAdminRightsPluralString(numApplications) {}
-
- /**
- * Requests the plural string for the title of the list of Incompatible
- * Applications.
- * @param {number} numApplications
- * @return {!Promise<string>}
- */
- getListTitlePluralString(numApplications) {}
- }
-
- /** @implements {settings.IncompatibleApplicationsBrowserProxy} */
- class IncompatibleApplicationsBrowserProxyImpl {
- /** @override */
- requestIncompatibleApplicationsList() {
- return cr.sendWithPromise('requestIncompatibleApplicationsList');
- }
-
- /** @override */
- startApplicationUninstallation(applicationName) {
- chrome.send('startApplicationUninstallation', [applicationName]);
- }
-
- /** @override */
- openURL(url) {
- window.open(url);
- }
-
- /** @override */
- getSubtitlePluralString(numApplications) {
- return cr.sendWithPromise('getSubtitlePluralString', numApplications);
- }
-
- /** @override */
- getSubtitleNoAdminRightsPluralString(numApplications) {
- return cr.sendWithPromise(
- 'getSubtitleNoAdminRightsPluralString', numApplications);
- }
-
- /** @override */
- getListTitlePluralString(numApplications) {
- return cr.sendWithPromise('getListTitlePluralString', numApplications);
- }
- }
-
- cr.addSingletonGetter(IncompatibleApplicationsBrowserProxyImpl);
-
- return {
- IncompatibleApplicationsBrowserProxy: IncompatibleApplicationsBrowserProxy,
- IncompatibleApplicationsBrowserProxyImpl:
- IncompatibleApplicationsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.html b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.html
deleted file mode 100644
index eda09c38a22..00000000000
--- a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="incompatible_application_item.html">
-<link rel="import" href="incompatible_applications_browser_proxy.html">
-
-<dom-module id="settings-incompatible-applications-page">
- <template>
- <style include="settings-shared">
- #is-done-section > iron-icon {
- --iron-icon-fill-color: var(--cr-checked-color);
- }
- </style>
-
- <div hidden$="[[!isDone_]]" id="is-done-section" class="settings-box first">
- <iron-icon icon="settings:check-circle"></iron-icon>
- <div class="middle no-min-width">
- $i18n{incompatibleApplicationsDone}
- </div>
- </div>
-
- <template is="dom-if" if="[[!isDone_]]">
- <div class="settings-box first">
- <iron-icon icon="cr:security"></iron-icon>
- <div class="middle no-min-width">
- <div hidden$="[[!hasAdminRights_]]">
- [[subtitleText_]] $i18nRaw{incompatibleApplicationsSubpageLearnHow}
- </div>
- <div hidden$="[[hasAdminRights_]]">
- [[subtitleNoAdminRightsText_]]
- </div>
- </div>
- </div>
- <div class="settings-box continuation">
- <h2 class="secondary">[[listTitleText_]]</h2>
- </div>
- <div id="incompatible-applications-list" class="list-frame vertical-list">
- <template is="dom-repeat" items="[[applications_]]" as="application">
- <incompatible-application-item
- hidden$="[[!hasAdminRights_]]"
- class="incompatible-application"
- application-name="[[application.name]]"
- action-type="[[application.type]]"
- action-url="[[application.url]]">
- </incompatible-application-item>
- <div hidden$="[[hasAdminRights_]]"
- class="list-item incompatible-application">
- [[application.name]]
- </div>
- </template>
- </div>
- </template>
- </template>
- <script src="incompatible_applications_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.js b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.js
deleted file mode 100644
index a66420db409..00000000000
--- a/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.js
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-incompatible-applications-page' is the settings subpage containing
- * the list of incompatible applications.
- *
- * Example:
- *
- * <iron-animated-pages>
- * <settings-incompatible-applications-page">
- * </settings-incompatible-applications-page>
- * ... other pages ...
- * </iron-animated-pages>
- */
-
-Polymer({
- is: 'settings-incompatible-applications-page',
-
- behaviors: [I18nBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * Indicates if the current user has administrator rights.
- * @private
- */
- hasAdminRights_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('hasAdminRights');
- },
- },
-
- /**
- * The list of all the incompatible applications.
- * @private {Array<settings.IncompatibleApplication>}
- */
- applications_: Array,
-
- /**
- * Determines if the user has finished with this page.
- * @private
- */
- isDone_: {
- type: Boolean,
- computed: 'computeIsDone_(applications_.*)',
- },
-
- /**
- * The text for the subtitle of the subpage.
- * @private
- */
- subtitleText_: {
- type: String,
- value: '',
- },
-
- /**
- * The text for the subtitle of the subpage, when the user does not have
- * administrator rights.
- * @private
- */
- subtitleNoAdminRightsText_: {
- type: String,
- value: '',
- },
-
- /**
- * The text for the title of the list of incompatible applications.
- * @private
- */
- listTitleText_: {
- type: String,
- value: '',
- },
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'incompatible-application-removed',
- this.onIncompatibleApplicationRemoved_.bind(this));
-
- settings.IncompatibleApplicationsBrowserProxyImpl.getInstance()
- .requestIncompatibleApplicationsList()
- .then(list => {
- this.applications_ = list;
- this.updatePluralStrings_();
- });
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeIsDone_: function() {
- return this.applications_.length === 0;
- },
-
- /**
- * Removes a single incompatible application from the |applications_| list.
- * @private
- */
- onIncompatibleApplicationRemoved_: function(applicationName) {
- // Find the index of the element.
- const index = this.applications_.findIndex(function(application) {
- return application.name == applicationName;
- });
-
- assert(index !== -1);
-
- this.splice('applications_', index, 1);
- },
-
- /**
- * Updates the texts of the Incompatible Applications subpage that depends on
- * the length of |applications_|.
- * @private
- */
- updatePluralStrings_: function() {
- const browserProxy =
- settings.IncompatibleApplicationsBrowserProxyImpl.getInstance();
- const numApplications = this.applications_.length;
-
- // The plural strings are not displayed when there is no applications.
- if (this.applications_.length === 0) {
- return;
- }
-
- Promise
- .all([
- browserProxy.getSubtitlePluralString(numApplications),
- browserProxy.getSubtitleNoAdminRightsPluralString(numApplications),
- browserProxy.getListTitlePluralString(numApplications),
- ])
- .then(strings => {
- this.subtitleText_ = strings[0];
- this.subtitleNoAdminRightsText_ = strings[1];
- this.listTitleText_ = strings[2];
- });
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/internet_page/OWNERS b/chromium/chrome/browser/resources/settings/internet_page/OWNERS
deleted file mode 100644
index 137a1129bd3..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://chromeos/network/OWNERS
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_config.html b/chromium/chrome/browser/resources/settings/internet_page/internet_config.html
deleted file mode 100644
index 65ef26edeee..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_config.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_config.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="internet_shared_css.html">
-
-<dom-module id="internet-config">
- <template>
- <style include="internet-shared iron-flex">
- cr-dialog::part(dialog) {
- width: 460px;
- }
-
- .error {
- color: red;
- font-weight: 500;
- }
- </style>
-
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">
- [[getDialogTitle_(name, type, showConnect)]]
- </div>
- <div slot="body">
- <network-config id="networkConfig" class="flex"
- guid="[[guid]]" name="{{name}}" type="{{type}}"
- enable-connect="{{enableConnect_}}" enable-save="{{enableSave_}}"
- share-allow-enable="[[shareAllowEnable_]]"
- share-default="[[shareDefault_]]"
- error="{{error_}}"
- on-close="onClose_"
- connect-on-enter="[[showConnect]]">
- </network-config>
- </div>
-
- <div class="layout horizontal center" slot="button-container">
- <template is="dom-if" if="[[error_]]" restamp>
- <div class="flex error">[[getError_(error_)]]</div>
- </template>
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <template is="dom-if" if="[[!showConnect]]">
- <cr-button class="action-button" on-click="onSaveTap_"
- disabled="[[!enableSave_]]">
- $i18n{save}
- </cr-button>
- </template>
- <template is="dom-if" if="[[showConnect]]">
- <cr-button class="action-button" on-click="onConnectTap_"
- disabled="[[!enableConnect_]]">
- $i18n{networkButtonConnect}
- </cr-button>
- </template>
- </div>
-
- </cr-dialog>
- </template>
- <script src="internet_config.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_config.js b/chromium/chrome/browser/resources/settings/internet_page/internet_config.js
deleted file mode 100644
index d5bd42d5da2..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_config.js
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'internet-config' is a Settings dialog wrapper for network-config.
- */
-Polymer({
- is: 'internet-config',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /** @private */
- shareAllowEnable_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('shareNetworkAllowEnable');
- }
- },
-
- /** @private */
- shareDefault_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('shareNetworkDefault');
- }
- },
-
- /**
- * The GUID when an existing network is being configured. This will be
- * empty when configuring a new network.
- */
- guid: String,
-
- /**
- * The type of network to be configured as a string. May be set initially or
- * updated by network-config.
- */
- type: String,
-
- /**
- * The name of the network. May be set initially or updated by
- * network-config.
- */
- name: String,
-
- /**
- * Set to true to show the 'connect' button instead of 'save'.
- */
- showConnect: Boolean,
-
- /** @private */
- enableConnect_: Boolean,
-
- /** @private */
- enableSave_: Boolean,
-
- /**
- * Set by network-config when a configuration error occurs.
- * @private
- */
- error_: {
- type: String,
- value: '',
- },
- },
-
- open: function() {
- const dialog = /** @type {!CrDialogElement} */ (this.$.dialog);
- if (!dialog.open) {
- dialog.showModal();
- }
-
- this.$.networkConfig.init();
- },
-
- close: function() {
- const dialog = /** @type {!CrDialogElement} */ (this.$.dialog);
- if (dialog.open) {
- dialog.close();
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onClose_: function(event) {
- this.close();
- },
-
- /**
- * @return {string}
- * @private
- */
- getDialogTitle_: function() {
- if (this.name && !this.showConnect) {
- return this.i18n('internetConfigName', HTMLEscape(this.name));
- }
- const type = this.i18n('OncType' + this.type);
- return this.i18n('internetJoinType', type);
- },
-
- /**
- * @return {string}
- * @private
- */
- getError_: function() {
- if (this.i18nExists(this.error_)) {
- return this.i18n(this.error_);
- }
- return this.i18n('networkErrorUnknown');
- },
-
- /** @private */
- onCancelTap_: function() {
- this.close();
- },
-
- /** @private */
- onSaveTap_: function() {
- this.$.networkConfig.save();
- },
-
- /** @private */
- onConnectTap_: function() {
- this.$.networkConfig.connect();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html
deleted file mode 100644
index 2d27c7dc86d..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html
+++ /dev/null
@@ -1,368 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_apnlist.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_choose_mobile.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_ip_config.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_nameservers.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_property_list_mojo.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_siminfo.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_icon.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior_mojo.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_indicator_mojo.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../controls/controlled_button.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="internet_page_browser_proxy.html">
-<link rel="import" href="internet_shared_css.html">
-<link rel="import" href="network_proxy_section.html">
-<link rel="import" href="tether_connection_dialog.html">
-
-<dom-module id="settings-internet-detail-page">
- <template>
- <style include="internet-shared settings-shared iron-flex">
- :host {
- padding-bottom: 40px;
- }
-
- iron-collapse {
- margin: 10px 0;
- }
-
- iron-icon {
- margin-inline-end: 10px;
- }
-
- cr-toggle {
- margin-inline-start: var(--settings-control-label-spacing);
- }
-
- cr-policy-network-indicator-mojo,
- cr-policy-indicator,
- cr-policy-pref-indicator {
- margin-inline-start: var(--settings-controlled-by-spacing);
- }
-
- #networkState[connected] {
- color: var(--google-green-500);
- }
-
- #networkState[error] {
- color: var(--google-red-500);
- }
-
- paper-spinner-lite {
- @apply --cr-icon-height-width;
- }
-
- .warning {
- color: var(--cr-secondary-text-color);
- margin-inline-start: var(--settings-controlled-by-spacing);
- }
-
- #mac-address-container {
- border-top: none;
- }
- </style>
- <!-- Title section: Icon + name + connection state. -->
- <div id="titleDiv" class="settings-box first">
- <div class="start layout horizontal center">
- <cr-network-icon
- show-technology-badge="[[showTechnologyBadge_]]"
- network-state="[[getNetworkState_(managedProperties_)]]">
- </cr-network-icon>
- <div id="networkState" class="title settings-box-text"
- connected$="[[isConnectedState_(managedProperties_)]]"
- error$="[[isConnectionErrorState_(
- managedProperties_, outOfRange_, deviceState_)]]">
- [[getStateText_(managedProperties_, propertiesReceived_,
- outOfRange_, deviceState_)]]
- </div>
- <template is="dom-if"
- if="[[isPolicySource(managedProperties_.source))]]">
- <cr-policy-indicator
- indicator-type="[[getIndicatorTypeForSource(
- managedProperties_.source)]]">
- </cr-policy-indicator>
- </template>
- </div>
- <cr-button on-click="onForgetTap_"
- hidden$="[[!showForget_(managedProperties_)]]"
- disabled="[[disableForget_(managedProperties_,
- prefs.vpn_config_allowed)]]">
- $i18n{networkButtonForget}
- </cr-button>
- <cr-button on-click="onViewAccountTap_"
- hidden$="[[!showViewAccount_(managedProperties_)]]">
- $i18n{networkButtonViewAccount}
- </cr-button>
- <cr-button on-click="onActivateTap_"
- hidden$="[[!showActivate_(managedProperties_)]]">
- $i18n{networkButtonActivate}
- </cr-button>
- <cr-button on-click="onConfigureTap_"
- hidden$="[[!showConfigure_(managedProperties_, globalPolicy,
- managedNetworkAvailable)]]"
- disabled="[[disableConfigure_(managedProperties_,
- prefs.vpn_config_allowed)]]">
- $i18n{networkButtonConfigure}
- </cr-button>
- <!-- Use policy properties from vpn_config_allowed to indicate when that
- pref disables buttons in this row. -->
- <controlled-button id="connect" class="action-button"
- on-click="onConnectTap_"
- hidden$="[[!showConnect_(managedProperties_, globalPolicy,
- managedNetworkAvailable, deviceState_)]]"
- disabled="[[!enableConnect_(managedProperties_, defaultNetwork,
- propertiesReceived_, outOfRange_, globalPolicy,
- managedNetworkAvailable, deviceState_)]]"
- label="$i18n{networkButtonConnect}"
- pref="[[getFakeVpnConfigPrefForEnforcement_(managedProperties_,
- prefs.vpn_config_allowed)]]">
- </controlled-button>
- <controlled-button id="disconnect" class="action-button"
- on-click="onDisconnectTap_"
- hidden$="[[!showDisconnect_(managedProperties_)]]"
- label="$i18n{networkButtonDisconnect}"
- pref="[[getFakeVpnConfigPrefForEnforcement_(managedProperties_,
- prefs.vpn_config_allowed)]]">
- </controlled-button>
- </div>
-
- <!-- Disabled by policy / Shared messages. -->
- <div class="settings-box continuation"
- hidden$="[[!isBlockedByPolicy_(managedProperties_, globalPolicy,
- managedNetworkAvailable)]]">
- <iron-icon class="policy" icon="cr20:domain"></iron-icon>
- <div class="settings-box-text">$i18n{networkConnectNotAllowed}</div>
- </div>
- <div class="settings-box continuation settings-box-text"
- hidden$="[[!showShared_(managedProperties_, globalPolicy,
- managedNetworkAvailable)]]">
- $i18n{networkShared}
- </div>
-
- <template is="dom-if" if="[[isSecondaryUser_]]">
- <!-- Show message for non primary users. -->
- <div class="settings-box continuation single-column">
- <div class="layout horizontal center">
- <iron-icon class="policy" icon="cr:group"></iron-icon>
- <div class="settings-box-text">
- [[i18n('networkPrimaryUserControlled', primaryUserEmail_)]]
- </div>
- </div>
- </div>
- </template>
-
- <template is="dom-if" if="[[!isSecondaryUser_]]">
- <!-- Prefer this network. -->
- <template is="dom-if"
- if="[[showPreferNetwork_(managedProperties_, globalPolicy,
- managedNetworkAvailable)]]">
- <div class="settings-box" on-click="onPreferNetworkRowClicked_"
- actionable$="[[!isNetworkPolicyEnforced(
- managedProperties_.priority)]]">
- <div id="preferNetworkToggleLabel" class="start settings-box-text">
- $i18n{networkPrefer}
- </div>
- <cr-policy-network-indicator-mojo
- property="[[managedProperties_.priority]]">
- </cr-policy-network-indicator-mojo>
- <cr-toggle id="preferNetworkToggle" checked="{{preferNetwork_}}"
- disabled="[[isNetworkPolicyEnforced(
- managedProperties_.priority)]]"
- aria-labelledby="preferNetworkToggleLabel">
- </cr-toggle>
- </div>
- </template>
- <!-- Autoconnect. -->
- <template is="dom-if"
- if="[[showAutoConnect_(managedProperties_, globalPolicy,
- managedNetworkAvailable)]]">
- <settings-toggle-button
- id="autoConnectToggle"
- pref="{{autoConnectPref_}}"
- label="[[getAutoConnectToggleLabel_(managedProperties_)]]">
- </settings-toggle-button>
- <!-- Hidden Network Warning -->
- <template is="dom-if"
- if="[[showHiddenNetworkWarning_(autoConnectPref_.*,
- managedProperties_)]]"
- restamp>
- <div class="warning">
- <iron-icon icon="cr:warning"></iron-icon>
- [[i18n('hiddenNetworkWarning')]]
- </div>
- </template>
- </template>
- <!-- Always-on VPN. -->
- <template is="dom-if"
- if="[[showAlwaysOnVpn_(managedProperties_)]]">
- <settings-toggle-button id="alwaysOnVpnToggle"
- pref="{{alwaysOnVpn_}}"
- label="$i18n{networkAlwaysOnVpn}">
- </settings-toggle-button>
- </template>
- <!-- Data roaming (Cellular only). -->
- <template is="dom-if" if="[[isCellular_(managedProperties_)]]">
- <settings-toggle-button id="allowDataRoaming"
- pref="{{prefs.cros.signed.data_roaming_enabled}}"
- label="$i18n{networkAllowDataRoaming}"
- sub-label="[[getRoamingDetails_(managedProperties_)]]">
- </settings-toggle-button>
- </template>
- <!-- SIM Info (Cellular only). -->
- <template is="dom-if" if="[[showCellularSim_(managedProperties_)]]"
- restamp>
- <div class="settings-box single-column stretch">
- <network-siminfo device-state="[[deviceState_]]">
- </network-siminfo>
- </div>
- </template>
- <!-- IP Address. -->
- <div class="settings-box two-line single-column stretch settings-box-text"
- hidden$="[[!showIpAddress_(ipAddress_, managedProperties_)]]">
- <div>$i18n{networkIPAddress}</div>
- <div class="secondary">[[ipAddress_]]</div>
- </div>
- <!-- Properties to always show if present. -->
- <template is="dom-if" if="[[hasInfoFields_(managedProperties_)]]">
- <div class="settings-box single-column stretch">
- <network-property-list-mojo
- fields="[[getInfoFields_(managedProperties_)]]"
- edit-field-types="[[getInfoEditFieldTypes_(managedProperties_)]]"
- property-dict="[[managedProperties_]]"
- on-property-change="onNetworkPropertyChange_">
- </network-property-list-mojo>
- </div>
- </template>
-
- <template is="dom-if"
- if="[[showAdvanced_(managedProperties_, propertiesReceived_)]]">
- <!-- Advanced toggle. -->
- <cr-expand-button
- alt="$i18n{networkSectionAdvancedA11yLabel}"
- class="settings-box"
- expanded="{{advancedExpanded_}}">
- $i18n{networkSectionAdvanced}
- </cr-expand-button>
-
- <!-- Advanced section -->
- <iron-collapse opened="[[advancedExpanded_]]">
- <div class="settings-box single-column stretch indented first"
- hidden$="[[!hasAdvancedOrDeviceFields_(managedProperties_)]]">
- <!-- Advanced properties -->
- <network-property-list-mojo
- hidden$="[[!hasAdvancedFields_(managedProperties_)]]"
- fields="[[getAdvancedFields_(managedProperties_)]]"
- property-dict="[[managedProperties_]]">
- </network-property-list-mojo>
- <!-- Device properties -->
- <network-property-list-mojo
- hidden$="[[!hasDeviceFields_(managedProperties_)]]"
- fields="[[getDeviceFields_(managedProperties_)]]"
- property-dict="[[managedProperties_]]">
- </network-property-list-mojo>
- </div>
- </iron-collapse>
- </template>
-
- <template is="dom-if" if="[[hasNetworkSection_(managedProperties_,
- globalPolicy, managedNetworkAvailable)]]">
- <!-- Network toggle -->
- <cr-expand-button
- alt="$i18n{networkSectionNetworkExpandA11yLabel}"
- class="settings-box"
- expanded="{{networkExpanded_}}">
- <div class="settings-row">
- <div class="start">
- $i18n{networkSectionNetwork}
- </div>
- <template is="dom-if"
- if="[[showScanningSpinner_(managedProperties_, deviceState_)]]">
- <paper-spinner-lite active></paper-spinner-lite>
- </template>
- </div>
- </cr-expand-button>
-
- <iron-collapse opened="[[networkExpanded_]]">
- <div class="settings-box single-column stretch indented first">
- <!-- Choose Mobile Network (Cellular only). -->
- <template is="dom-if"
- if="[[showCellularChooseNetwork_(managedProperties_)]]">
- <network-choose-mobile device-state="[[deviceState_]]"
- managed-properties="[[managedProperties_]]">
- </network-choose-mobile>
- </template>
-
- <!-- APN -->
- <template is="dom-if" if="[[isCellular_(managedProperties_)]]">
- <network-apnlist on-apn-change="onApnChange_"
- managed-properties="[[managedProperties_]]">
- </network-apnlist>
- </template>
-
- <!-- IP Config, Nameservers -->
- <template is="dom-if"
- if="[[isRememberedOrConnected_(managedProperties_)]]">
- <network-ip-config on-ip-change="onIPConfigChange_"
- managed-properties="[[managedProperties_]]">
- </network-ip-config>
- <network-nameservers on-nameservers-change="onIPConfigChange_"
- managed-properties="[[managedProperties_]]">
- </network-nameservers>
- </template>
- </div>
-
- <!-- MAC Address. -->
- <div class="settings-box two-line single-column stretch indented"
- id="mac-address-container"
- hidden$="[[!deviceState_.macAddress]]">
- <div>$i18n{OncMacAddress}</div>
- <div class="secondary">[[deviceState_.macAddress]]</div>
- </div>
- </iron-collapse>
- </template>
-
- <template is="dom-if" if="[[hasProxySection_(managedProperties_,
- globalPolicy, managedNetworkAvailable)]]">
- <!-- Proxy toggle -->
- <cr-expand-button
- alt="$i18n{networkSectionProxyExpandA11yLabel}"
- class="settings-box"
- expanded="{{proxyExpanded_}}">
- $i18n{networkSectionProxy}
- </cr-expand-button>
-
- <iron-collapse opened="[[proxyExpanded_]]">
- <network-proxy-section prefs="{{prefs}}"
- on-proxy-change="onProxyChange_"
- managed-properties="[[managedProperties_]]">
- </network-proxy-section>
- </iron-collapse>
- </template>
- </template>
-
- <template is="dom-if" if="[[isTether_(managedProperties_)]]" restamp>
- <tether-connection-dialog id="tetherDialog"
- managed-properties="[[managedProperties_]]"
- on-tether-connect="onTetherConnect_"
- out-of-range="[[outOfRange_]]">
- </tether-connection-dialog>
- </template>
- </template>
- <script src="internet_detail_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js
deleted file mode 100644
index 7c15915a2c8..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js
+++ /dev/null
@@ -1,1682 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-internet-detail' is the settings subpage containing details
- * for a network.
- */
-(function() {
-'use strict';
-
-const mojom = chromeos.networkConfig.mojom;
-
-Polymer({
- is: 'settings-internet-detail-page',
-
- behaviors: [
- CrNetworkListenerBehavior,
- CrPolicyNetworkBehaviorMojo,
- settings.RouteObserverBehavior,
- I18nBehavior,
- ],
-
- properties: {
- /** The network GUID to display details for. */
- guid: String,
-
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private {!chromeos.networkConfig.mojom.ManagedProperties|undefined} */
- managedProperties_: {
- type: Object,
- observer: 'managedPropertiesChanged_',
- },
-
- /** @private {?OncMojo.DeviceStateProperties} */
- deviceState_: {
- type: Object,
- value: null,
- },
-
- /**
- * Whether the user is a secondary user.
- * @private
- */
- isSecondaryUser_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isSecondaryUser');
- },
- readOnly: true,
- },
-
- /**
- * Email address for the primary user.
- * @private
- */
- primaryUserEmail_: {
- type: String,
- value: function() {
- return loadTimeData.getBoolean('isSecondaryUser') ?
- loadTimeData.getString('primaryUserEmail') :
- '';
- },
- readOnly: true,
- },
-
- /**
- * Whether the network has been lost (e.g., has gone out of range). A
- * network is considered to be lost when a OnNetworkStateListChanged
- * is signaled and the new network list does not contain the GUID of the
- * current network.
- * @private
- */
- outOfRange_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Highest priority connected network or null.
- * @type {?OncMojo.NetworkStateProperties}
- */
- defaultNetwork: {
- type: Object,
- value: null,
- },
-
- /** @type {!chromeos.networkConfig.mojom.GlobalPolicy|undefined} */
- globalPolicy: Object,
-
- /**
- * Whether a managed network is available in the visible network list.
- * @private {boolean}
- */
- managedNetworkAvailable: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The network AutoConnect state as a fake preference object.
- * @private {!chrome.settingsPrivate.PrefObject|undefined}
- */
- autoConnectPref_: {
- type: Object,
- observer: 'autoConnectPrefChanged_',
- },
-
- /**
- * The always-on VPN state as a fake preference object.
- * @private {!chrome.settingsPrivate.PrefObject|undefined}
- */
- alwaysOnVpn_: {
- type: Object,
- observer: 'alwaysOnVpnChanged_',
- value: function() {
- return {
- key: 'fakeAlwaysOnPref',
- type: chrome.settingsPrivate.PrefType.BOOLEAN,
- value: false,
- };
- }
- },
-
- /**
- * The network preferred state.
- * @private
- */
- preferNetwork_: {
- type: Boolean,
- value: false,
- observer: 'preferNetworkChanged_',
- },
-
- /**
- * The network IP Address.
- * @private
- */
- ipAddress_: {
- type: String,
- value: '',
- },
-
- /**
- * Whether to show technology badge on mobile network icons.
- * @private
- */
- showTechnologyBadge_: {
- type: Boolean,
- value: function() {
- return loadTimeData.valueExists('showTechnologyBadge') &&
- loadTimeData.getBoolean('showTechnologyBadge');
- }
- },
-
- /** @private */
- advancedExpanded_: Boolean,
-
- /** @private */
- networkExpanded_: Boolean,
-
- /** @private */
- proxyExpanded_: Boolean,
- },
-
- observers: [
- 'updateAlwaysOnVpnPrefValue_(prefs.arc.vpn.always_on.*)',
- 'updateAlwaysOnVpnPrefEnforcement_(managedProperties_,' +
- 'prefs.vpn_config_allowed.*)',
- 'updateAutoConnectPref_(globalPolicy)',
- 'autoConnectPrefChanged_(autoConnectPref_.*)',
- 'alwaysOnVpnChanged_(alwaysOnVpn_.*)',
- ],
-
- /** @private {boolean} */
- didSetFocus_: false,
-
- /**
- * Set to true to once the initial properties have been received. This
- * prevents setProperties from being called when setting default properties.
- * @private {boolean}
- */
- propertiesReceived_: false,
-
- /**
- * Set in currentRouteChanged() if the showConfigure URL query
- * parameter is set to true. The dialog cannot be shown until the
- * network properties have been fetched in managedPropertiesChanged_().
- * @private {boolean}
- */
- shouldShowConfigureWhenNetworkLoaded_: false,
-
- /** @private {settings.InternetPageBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.InternetPageBrowserProxyImpl.getInstance();
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @param {!settings.Route} oldRoute
- * @protected
- */
- currentRouteChanged: function(route, oldRoute) {
- if (route != settings.routes.NETWORK_DETAIL) {
- return;
- }
-
- const queryParams = settings.getQueryParameters();
- const guid = queryParams.get('guid') || '';
- if (!guid) {
- console.error('No guid specified for page:' + route);
- this.close();
- }
-
- this.shouldShowConfigureWhenNetworkLoaded_ =
- queryParams.get('showConfigure') == 'true';
- const type = queryParams.get('type') || 'WiFi';
- const name = queryParams.get('name') || type;
- this.init(guid, type, name);
- },
-
- /**
- * @param {string} guid
- * @param {string} type
- * @param {string} name
- */
- init: function(guid, type, name) {
- this.guid = guid;
- // Set default properties until they are loaded.
- this.propertiesReceived_ = false;
- this.deviceState_ = null;
- this.managedProperties_ = OncMojo.getDefaultManagedProperties(
- OncMojo.getNetworkTypeFromString(type), this.guid, name);
- this.didSetFocus_ = false;
- this.getNetworkDetails_();
- },
-
- close: function() {
- // If the page is already closed, return early to avoid navigating backward
- // erroneously.
- if (!this.guid) {
- return;
- }
-
- this.guid = '';
-
- // Delay navigating to allow other subpages to load first.
- requestAnimationFrame(() => {
- // Clear network properties before navigating away to ensure that a future
- // navigation back to the details page does not show a flicker of
- // incorrect text. See https://crbug.com/905986.
- this.managedProperties_ = undefined;
- this.propertiesReceived_ = false;
-
- settings.navigateToPreviousRoute();
- });
- },
-
- /**
- * CrosNetworkConfigObserver impl
- * @param {!Array<OncMojo.NetworkStateProperties>} networks
- */
- onActiveNetworksChanged: function(networks) {
- if (!this.guid || !this.managedProperties_) {
- return;
- }
- // If the network was or is active, request an update.
- if (this.managedProperties_.connectionState !=
- mojom.ConnectionStateType.kNotConnected ||
- networks.find(network => network.guid == this.guid)) {
- this.getNetworkDetails_();
- }
- },
-
- /**
- * CrosNetworkConfigObserver impl
- * @param {!chromeos.networkConfig.mojom.NetworkStateProperties} network
- */
- onNetworkStateChanged: function(network) {
- if (!this.guid || !this.managedProperties_) {
- return;
- }
- if (network.guid == this.guid) {
- this.getNetworkDetails_();
- }
- },
-
- /** CrosNetworkConfigObserver impl */
- onNetworkStateListChanged: function() {
- if (!this.guid || !this.managedProperties_) {
- return;
- }
- this.checkNetworkExists_();
- },
-
- /** CrosNetworkConfigObserver impl */
- onDeviceStateListChanged: function() {
- if (!this.guid || !this.managedProperties_) {
- return;
- }
- this.getDeviceState_();
- this.getNetworkDetails_();
- },
-
- /** @private */
- managedPropertiesChanged_: function() {
- if (!this.managedProperties_) {
- return;
- }
- this.updateAutoConnectPref_();
-
- const priority = this.managedProperties_.priority;
- if (priority) {
- const preferNetwork = priority.activeValue > 0;
- if (preferNetwork != this.preferNetwork_) {
- this.preferNetwork_ = preferNetwork;
- }
- }
-
- // Set the IPAddress property to the IPv4 Address.
- const ipv4 = OncMojo.getIPConfigForType(this.managedProperties_, 'IPv4');
- this.ipAddress_ = (ipv4 && ipv4.ipAddress) || '';
-
- // Update the detail page title.
- const networkName = OncMojo.getNetworkName(this.managedProperties_);
- this.parentNode.pageTitle = networkName;
- Polymer.dom.flush();
-
- if (!this.didSetFocus_) {
- // Focus a button once the initial state is set.
- this.didSetFocus_ = true;
- const button = this.$$('#titleDiv .action-button:not([hidden])');
- if (button) {
- Polymer.RenderStatus.afterNextRender(this, () => button.focus());
- }
- }
-
- if (this.shouldShowConfigureWhenNetworkLoaded_ &&
- this.managedProperties_.type == mojom.NetworkType.kTether) {
- // Set |this.shouldShowConfigureWhenNetworkLoaded_| back to false to
- // ensure that the Tether dialog is only shown once.
- this.shouldShowConfigureWhenNetworkLoaded_ = false;
- // Async call to ensure dialog is stamped.
- setTimeout(() => this.showTetherDialog_());
- }
- },
-
- /** @private */
- getDeviceState_: function() {
- if (!this.managedProperties_) {
- return;
- }
- const type = this.managedProperties_.type;
- this.networkConfig_.getDeviceStateList().then(response => {
- const devices = response.result;
- this.deviceState_ = devices.find(device => device.type == type) || null;
- });
- },
-
- /** @private */
- autoConnectPrefChanged_: function() {
- if (!this.propertiesReceived_) {
- return;
- }
- const config = this.getDefaultConfigProperties_();
- config.autoConnect = {value: !!this.autoConnectPref_.value};
- this.setMojoNetworkProperties_(config);
- },
-
- /**
- * Updates auto-connect pref value.
- * @private
- */
- updateAutoConnectPref_: function() {
- if (!this.managedProperties_) {
- return;
- }
- const autoConnect = OncMojo.getManagedAutoConnect(this.managedProperties_);
- if (!autoConnect) {
- return;
- }
-
- let enforcement;
- let controlledBy;
- if (autoConnect.enforced ||
- (!!this.globalPolicy &&
- !!this.globalPolicy.allowOnlyPolicyNetworksToAutoconnect)) {
- enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
- controlledBy = chrome.settingsPrivate.ControlledBy.DEVICE_POLICY;
- }
-
- if (this.autoConnectPref_ &&
- this.autoConnectPref_.value == autoConnect.activeValue &&
- enforcement == this.autoConnectPref_.enforcement &&
- controlledBy == this.autoConnectPref_.controlledBy) {
- return;
- }
-
- const newPrefValue = {
- key: 'fakeAutoConnectPref',
- value: autoConnect.activeValue,
- type: chrome.settingsPrivate.PrefType.BOOLEAN,
- };
- if (enforcement) {
- newPrefValue.enforcement = enforcement;
- newPrefValue.controlledBy = controlledBy;
- }
-
- this.autoConnectPref_ = newPrefValue;
- },
-
- /** @private */
- preferNetworkChanged_: function() {
- if (!this.propertiesReceived_) {
- return;
- }
- const config = this.getDefaultConfigProperties_();
- config.priority = {value: this.preferNetwork_ ? 1 : 0};
- this.setMojoNetworkProperties_(config);
- },
-
- /** @private */
- checkNetworkExists_: function() {
- const filter = {
- filter: mojom.FilterType.kVisible,
- networkType: mojom.NetworkType.kAll,
- limit: mojom.NO_LIMIT,
- };
- this.networkConfig_.getNetworkState(this.guid).then(response => {
- if (response.result) {
- // Don't update the state, a change event will trigger the update.
- return;
- }
- this.outOfRange_ = true;
- if (this.managedProperties_) {
- // Set the connection state since we won't receive an update for a non
- // existent network.
- this.managedProperties_.connectionState =
- mojom.ConnectionStateType.kNotConnected;
- }
- });
- },
-
- /** @private */
- getNetworkDetails_: function() {
- assert(this.guid);
- if (this.isSecondaryUser_) {
- this.networkConfig_.getNetworkState(this.guid).then(response => {
- this.getStateCallback_(response.result);
- });
- } else {
- this.networkConfig_.getManagedProperties(this.guid).then(response => {
- this.getPropertiesCallback_(response.result);
- });
- }
- },
-
- /**
- * @param {?mojom.ManagedProperties} properties
- * @private
- */
- getPropertiesCallback_: function(properties) {
- // Details page was closed while request was in progress, ignore the result.
- if (!this.guid) {
- return;
- }
-
- if (!properties) {
- console.error('Details page: GUID no longer exists: ' + this.guid);
- this.close();
- return;
- }
-
- this.managedProperties_ = properties;
- // Detail page should not be shown when Arc VPN is not connected.
- if (this.isArcVpn_(this.managedProperties_) &&
- !this.isConnectedState_(this.managedProperties_)) {
- this.guid = '';
- this.close();
- }
- this.propertiesReceived_ = true;
- this.outOfRange_ = false;
- if (!this.deviceState_) {
- this.getDeviceState_();
- }
- },
-
- /**
- * @param {?OncMojo.NetworkStateProperties} networkState
- * @private
- */
- getStateCallback_: function(networkState) {
- if (!networkState) {
- // Edge case, may occur when disabling. Close this.
- this.close();
- return;
- }
-
- const managedProperties = OncMojo.getDefaultManagedProperties(
- networkState.type, networkState.guid, networkState.name);
- managedProperties.connectable = networkState.connectable;
- managedProperties.connectionState = networkState.connectionState;
- switch (networkState.type) {
- case mojom.NetworkType.kCellular:
- managedProperties.typeProperties.cellular.signalStrength =
- networkState.typeState.cellular.signalStrength;
- break;
- case mojom.NetworkType.kTether:
- managedProperties.typeProperties.tether.signalStrength =
- networkState.typeState.tether.signalStrength;
- break;
- case mojom.NetworkType.kWiFi:
- managedProperties.typeProperties.wifi.signalStrength =
- networkState.typeState.wifi.signalStrength;
- break;
- }
- this.managedProperties_ = managedProperties;
-
- this.propertiesReceived_ = true;
- this.outOfRange_ = false;
- },
-
- /**
- * @param {!mojom.ManagedProperties} properties
- * @return {!OncMojo.NetworkStateProperties|undefined}
- */
- getNetworkState_: function(properties) {
- if (!properties) {
- return undefined;
- }
- return OncMojo.managedPropertiesToNetworkState(properties);
- },
-
- /**
- * @return {!mojom.ConfigProperties}
- * @private
- */
- getDefaultConfigProperties_: function() {
- return OncMojo.getDefaultConfigProperties(this.managedProperties_.type);
- },
-
- /**
- * @param {!mojom.ConfigProperties} config
- * @private
- */
- setMojoNetworkProperties_: function(config) {
- if (!this.propertiesReceived_ || !this.guid) {
- return;
- }
- this.networkConfig_.setProperties(this.guid, config).then(response => {
- if (!response.success) {
- console.error('Unable to set properties: ' + JSON.stringify(config));
- // An error typically indicates invalid input; request the properties
- // to update any invalid fields.
- this.getNetworkDetails_();
- }
- });
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {boolean} propertiesReceived
- * @param {boolean} outOfRange
- * @param {?OncMojo.DeviceStateProperties} deviceState
- * @return {string} The text to display for the network connection state.
- * @private
- */
- getStateText_: function(
- managedProperties, propertiesReceived, outOfRange, deviceState) {
- if (!managedProperties || !propertiesReceived) {
- return '';
- }
-
- if (this.isOutOfRangeOrNotEnabled_(outOfRange, deviceState)) {
- return managedProperties.type == mojom.NetworkType.kTether ?
- this.i18n('tetherPhoneOutOfRange') :
- this.i18n('networkOutOfRange');
- }
-
- if (managedProperties.type == mojom.NetworkType.kCellular &&
- !managedProperties.connectable) {
- if (managedProperties.typeProperties.cellular.homeProvider &&
- managedProperties.typeProperties.cellular.homeProvider.name) {
- return this.i18n(
- 'cellularContactSpecificCarrier',
- managedProperties.typeProperties.cellular.homeProvider.name);
- }
- return this.i18n('cellularContactDefaultCarrier');
- }
-
- return this.i18n(
- OncMojo.getConnectionStateString(managedProperties.connectionState));
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string} The text to display for auto-connect toggle label.
- * @private
- */
- getAutoConnectToggleLabel_: function(managedProperties) {
- return this.isCellular_(managedProperties) ?
- this.i18n('networkAutoConnectCellular') :
- this.i18n('networkAutoConnect');
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string} The text to display with roaming details.
- * @private
- */
- getRoamingDetails_: function(managedProperties) {
- if (!this.isCellular_(managedProperties)) {
- return '';
- }
- if (!managedProperties.typeProperties.cellular.allowRoaming) {
- return this.i18n('networkAllowDataRoamingDisabled');
- }
-
- return managedProperties.typeProperties.cellular.roamingState == 'Roaming' ?
- this.i18n('networkAllowDataRoamingEnabledRoaming') :
- this.i18n('networkAllowDataRoamingEnabledHome');
- },
-
- /**
- * @param {!mojom.ManagedProperties|undefined} managedProperties
- * @return {boolean} True if the network is connected.
- * @private
- */
- isConnectedState_: function(managedProperties) {
- return !!managedProperties &&
- OncMojo.connectionStateIsConnected(managedProperties.connectionState);
- },
-
- /**
- * @param {!mojom.ManagedProperties|undefined} managedProperties
- * @param {boolean} outOfRange
- * @param {?OncMojo.DeviceStateProperties} deviceState
- * @return {boolean} True if the network shown cannot initiate a connection.
- * @private
- */
- isConnectionErrorState_: function(
- managedProperties, outOfRange, deviceState) {
- if (this.isOutOfRangeOrNotEnabled_(outOfRange, deviceState)) {
- return true;
- }
-
- if (!managedProperties) {
- return false;
- }
-
- // It's still possible to initiate a connection to a network if it is not
- // connectable as long as the network has an associated configuration flow.
- // Cellular networks do not have a configuration flow, so a Cellular network
- // that is not connectable represents an error state.
- return managedProperties.type == mojom.NetworkType.kCellular &&
- !managedProperties.connectable;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- isRemembered_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.source != mojom.OncSource.kNone;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- isRememberedOrConnected_: function(managedProperties) {
- return this.isRemembered_(managedProperties) ||
- this.isConnectedState_(managedProperties);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- isCellular_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.type == mojom.NetworkType.kCellular;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- isTether_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.type == mojom.NetworkType.kTether;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy|undefined} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean}
- * @private
- */
- isBlockedByPolicy_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- if (!managedProperties || !globalPolicy ||
- managedProperties.type != mojom.NetworkType.kWiFi ||
- this.isPolicySource(managedProperties.source)) {
- return false;
- }
- const hexSsid =
- OncMojo.getActiveString(managedProperties.typeProperties.wifi.hexSsid);
- return !!globalPolicy.allowOnlyPolicyNetworksToConnect ||
- (!!globalPolicy.allowOnlyPolicyNetworksToConnectIfAvailable &&
- !!managedNetworkAvailable) ||
- (!!hexSsid && !!globalPolicy.blockedHexSsids &&
- globalPolicy.blockedHexSsids.includes(hexSsid));
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @param {?OncMojo.DeviceStateProperties} deviceState
- * @return {boolean}
- * @private
- */
- showConnect_: function(
- managedProperties, globalPolicy, managedNetworkAvailable, deviceState) {
- if (!managedProperties) {
- return false;
- }
-
- if (this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable)) {
- return false;
- }
-
- // TODO(lgcheng@) support connect Arc VPN from UI once Android support API
- // to initiate a VPN session.
- if (this.isArcVpn_(managedProperties)) {
- return false;
- }
-
- if (managedProperties.connectionState !=
- mojom.ConnectionStateType.kNotConnected) {
- return false;
- }
-
- if (deviceState &&
- deviceState.deviceState !=
- chromeos.networkConfig.mojom.DeviceStateType.kEnabled) {
- return false;
- }
-
- // Cellular is not configurable, so we always show the connect button, and
- // disable it if 'connectable' is false.
- if (managedProperties.type == mojom.NetworkType.kCellular) {
- return true;
- }
-
- // If 'connectable' is false we show the configure button.
- return managedProperties.connectable &&
- managedProperties.type != mojom.NetworkType.kEthernet;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showDisconnect_: function(managedProperties) {
- if (!managedProperties ||
- managedProperties.type == mojom.NetworkType.kEthernet) {
- return false;
- }
- return managedProperties.connectionState !=
- mojom.ConnectionStateType.kNotConnected;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showForget_: function(managedProperties) {
- if (!managedProperties || this.isSecondaryUser_) {
- return false;
- }
- const type = managedProperties.type;
- if (type != mojom.NetworkType.kWiFi && type != mojom.NetworkType.kVPN) {
- return false;
- }
- if (this.isArcVpn_(managedProperties)) {
- return false;
- }
- return !this.isPolicySource(managedProperties.source) &&
- this.isRemembered_(managedProperties);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showActivate_: function(managedProperties) {
- if (!managedProperties || this.isSecondaryUser_) {
- return false;
- }
- if (!this.isCellular_(managedProperties)) {
- return false;
- }
- const activation =
- managedProperties.typeProperties.cellular.activationState;
- return activation == mojom.ActivationStateType.kNotActivated ||
- activation == mojom.ActivationStateType.kPartiallyActivated;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean}
- * @private
- */
- showConfigure_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- if (!managedProperties || this.isSecondaryUser_) {
- return false;
- }
- if (this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable)) {
- return false;
- }
- const type = managedProperties.type;
- if (type == mojom.NetworkType.kCellular ||
- type == mojom.NetworkType.kTether) {
- return false;
- }
- if (type == mojom.NetworkType.kWiFi &&
- managedProperties.typeProperties.wifi.security ==
- mojom.SecurityType.kNone) {
- return false;
- }
- if (type == mojom.NetworkType.kWiFi &&
- (managedProperties.connectionState !=
- mojom.ConnectionStateType.kNotConnected)) {
- return false;
- }
- if (this.isArcVpn_(managedProperties) &&
- !this.isConnectedState_(managedProperties)) {
- return false;
- }
- return true;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!chrome.settingsPrivate.PrefObject} vpnConfigAllowed
- * @return {boolean}
- * @private
- */
- disableForget_: function(managedProperties, vpnConfigAllowed) {
- if (!managedProperties) {
- return true;
- }
- return managedProperties.type == mojom.NetworkType.kVPN &&
- vpnConfigAllowed && !vpnConfigAllowed.value;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!chrome.settingsPrivate.PrefObject} vpnConfigAllowed
- * @return {boolean}
- * @private
- */
- disableConfigure_: function(managedProperties, vpnConfigAllowed) {
- if (!managedProperties) {
- return true;
- }
- if (managedProperties.type == mojom.NetworkType.kVPN && vpnConfigAllowed &&
- !vpnConfigAllowed.value) {
- return true;
- }
- return this.isPolicySource(managedProperties.source) &&
- !this.hasRecommendedFields_(managedProperties);
- },
-
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- */
- hasRecommendedFields_: function(managedProperties) {
- if (!managedProperties) {
- return false;
- }
- for (const key of Object.keys(managedProperties)) {
- const value = managedProperties[key];
- if (typeof value != 'object' || value === null) {
- continue;
- }
- if ('activeValue' in value) {
- if (this.isNetworkPolicyRecommended(value)) {
- return true;
- }
- } else if (this.hasRecommendedFields_(value)) {
- return true;
- }
- }
- return false;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showViewAccount_: function(managedProperties) {
- if (!managedProperties || this.isSecondaryUser_) {
- return false;
- }
-
- // Show either the 'Activate' or the 'View Account' button (Cellular only).
- if (!this.isCellular_(managedProperties) ||
- this.showActivate_(managedProperties)) {
- return false;
- }
-
- const paymentPortal =
- managedProperties.typeProperties.cellular.paymentPortal;
- if (!paymentPortal || !paymentPortal.url) {
- return false;
- }
-
- // Only show for connected networks or LTE networks with a valid MDN.
- if (!this.isConnectedState_(managedProperties)) {
- const technology =
- managedProperties.typeProperties.cellular.networkTechnology;
- if (technology != 'LTE' && technology != 'LTEAdvanced') {
- return false;
- }
- if (!managedProperties.typeProperties.cellular.mdn) {
- return false;
- }
- }
-
- return true;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {?OncMojo.NetworkStateProperties} defaultNetwork
- * @param {boolean} propertiesReceived
- * @param {boolean} outOfRange
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @param {?OncMojo.DeviceStateProperties} deviceState
- * @return {boolean} Whether or not to enable the network connect button.
- * @private
- */
- enableConnect_: function(
- managedProperties, defaultNetwork, propertiesReceived, outOfRange,
- globalPolicy, managedNetworkAvailable, deviceState) {
- if (!this.showConnect_(
- managedProperties, globalPolicy, managedNetworkAvailable,
- deviceState)) {
- return false;
- }
- if (!propertiesReceived || outOfRange) {
- return false;
- }
- // Cellular networks are not configurable, so we show a disabled 'Connect'
- // button when not connectable.
- if (managedProperties.type == mojom.NetworkType.kCellular &&
- !managedProperties.connectable) {
- return false;
- }
- if (managedProperties.type == mojom.NetworkType.kVPN && !defaultNetwork) {
- return false;
- }
- return true;
- },
-
- /** @private */
- updateAlwaysOnVpnPrefValue_: function() {
- this.alwaysOnVpn_.value = this.prefs.arc && this.prefs.arc.vpn &&
- this.prefs.arc.vpn.always_on && this.prefs.arc.vpn.always_on.lockdown &&
- this.prefs.arc.vpn.always_on.lockdown.value;
- },
-
- /**
- * @private
- * @return {!chrome.settingsPrivate.PrefObject}
- */
- getFakeVpnConfigPrefForEnforcement_: function() {
- const fakeAlwaysOnVpnEnforcementPref = {
- key: 'fakeAlwaysOnPref',
- type: chrome.settingsPrivate.PrefType.BOOLEAN,
- value: false,
- };
- // Only mark VPN networks as enforced. This fake pref also controls the
- // policy indicator on the connect/disconnect buttons, so it shouldn't be
- // shown on non-VPN networks.
- if (this.managedProperties_ &&
- this.managedProperties_.type == mojom.NetworkType.kVPN && this.prefs &&
- this.prefs.vpn_config_allowed && !this.prefs.vpn_config_allowed.value) {
- fakeAlwaysOnVpnEnforcementPref.enforcement =
- chrome.settingsPrivate.Enforcement.ENFORCED;
- fakeAlwaysOnVpnEnforcementPref.controlledBy =
- this.prefs.vpn_config_allowed.controlledBy;
- }
- return fakeAlwaysOnVpnEnforcementPref;
- },
-
- /** @private */
- updateAlwaysOnVpnPrefEnforcement_: function() {
- const prefForEnforcement = this.getFakeVpnConfigPrefForEnforcement_();
- this.alwaysOnVpn_.enforcement = prefForEnforcement.enforcement;
- this.alwaysOnVpn_.controlledBy = prefForEnforcement.controlledBy;
- },
-
- /**
- * @return {!TetherConnectionDialogElement}
- * @private
- */
- getTetherDialog_: function() {
- return /** @type {!TetherConnectionDialogElement} */ (
- this.$$('#tetherDialog'));
- },
-
- /** @private */
- onConnectTap_: function() {
- if (this.managedProperties_.type == mojom.NetworkType.kTether &&
- (!this.managedProperties_.typeProperties.tether.hasConnectedToHost)) {
- this.showTetherDialog_();
- return;
- }
- this.fireNetworkConnect_(/*bypassDialog=*/ false);
- },
-
- /** @private */
- onTetherConnect_: function() {
- this.getTetherDialog_().close();
- this.fireNetworkConnect_(/*bypassDialog=*/ true);
- },
-
- /**
- * @param {boolean} bypassDialog
- * @private
- */
- fireNetworkConnect_: function(bypassDialog) {
- assert(this.managedProperties_);
- const networkState =
- OncMojo.managedPropertiesToNetworkState(this.managedProperties_);
- this.fire(
- 'network-connect',
- {networkState: networkState, bypassConnectionDialog: bypassDialog});
- },
-
- /** @private */
- onDisconnectTap_: function() {
- this.networkConfig_.startDisconnect(this.guid).then(response => {
- if (!response.success) {
- console.error('Disconnect failed for: ' + this.guid);
- }
- });
- },
-
- /** @private */
- onForgetTap_: function() {
- this.networkConfig_.forgetNetwork(this.guid).then(response => {
- if (!response.success) {
- console.error('Froget network failed for: ' + this.guid);
- }
- // A forgotten network no longer has a valid GUID, close the subpage.
- this.close();
- });
- },
-
- /** @private */
- onActivateTap_: function() {
- this.browserProxy_.showCellularSetupUI(this.guid);
- },
-
- /** @private */
- onConfigureTap_: function() {
- if (this.managedProperties_ &&
- (this.isThirdPartyVpn_(this.managedProperties_) ||
- this.isArcVpn_(this.managedProperties_))) {
- this.browserProxy_.configureThirdPartyVpn(this.guid);
- return;
- }
-
- this.fire('show-config', {
- guid: this.guid,
- type: OncMojo.getNetworkTypeString(this.managedProperties_.type),
- name: OncMojo.getNetworkName(this.managedProperties_)
- });
- },
-
- /** @private */
- onViewAccountTap_: function() {
- // Currently 'Account Details' is the same as the activation UI.
- this.browserProxy_.showCellularSetupUI(this.guid);
- },
-
- /** @type {string} */
- CR_EXPAND_BUTTON_TAG: 'CR-EXPAND-BUTTON',
-
- /** @private */
- showTetherDialog_: function() {
- this.getTetherDialog_().open();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- showHiddenNetworkWarning_: function() {
- return loadTimeData.getBoolean('showHiddenNetworkWarning') &&
- !!this.autoConnectPref_ && !!this.autoConnectPref_.value &&
- !!this.managedProperties_ &&
- this.managedProperties_.type == mojom.NetworkType.kWiFi &&
- !!OncMojo.getActiveValue(
- this.managedProperties_.typeProperties.wifi.hiddenSsid);
- },
-
- /**
- * Event triggered for elements associated with network properties.
- * @param {!CustomEvent<!{
- * field: string,
- * value: (string|number|boolean|!Array<string>)
- * }>} e
- * @private
- */
- onNetworkPropertyChange_: function(e) {
- if (!this.propertiesReceived_) {
- return;
- }
- const field = e.detail.field;
- const value = e.detail.value;
- const config = this.getDefaultConfigProperties_();
- const valueType = typeof value;
- if (valueType != 'string' && valueType != 'number' &&
- valueType != 'boolean' && !Array.isArray(value)) {
- console.error(
- 'Unexpected property change event, Key: ' + field +
- ' Value: ' + JSON.stringify(value));
- return;
- }
- OncMojo.setConfigProperty(config, field, value);
- // Ensure that any required configuration properties for partial
- // configurations are set.
- const vpnConfig = config.typeConfig.vpn;
- if (vpnConfig) {
- vpnConfig.type = this.managedProperties_.typeProperties.vpn.type;
- if (vpnConfig.openVpn && vpnConfig.openVpn.saveCredentials == undefined) {
- vpnConfig.openVpn.saveCredentials = false;
- }
- if (vpnConfig.l2tp && vpnConfig.l2tp.saveCredentials == undefined) {
- vpnConfig.l2tp.saveCredentials = false;
- }
- }
- this.setMojoNetworkProperties_(config);
- },
-
- /**
- * @param {!CustomEvent<!mojom.ApnProperties>} event
- * @private
- */
- onApnChange_: function(event) {
- if (!this.propertiesReceived_) {
- return;
- }
- const config = this.getDefaultConfigProperties_();
- const apn = event.detail;
- config.typeConfig.cellular = {apn: apn};
- this.setMojoNetworkProperties_(config);
- },
-
-
- /**
- * Event triggered when the IP Config or NameServers element changes.
- * @param {!CustomEvent<!{
- * field: string,
- * value: (string|!mojom.IPConfigProperties|!Array<string>)
- * }>} event The network-ip-config or network-nameservers change event.
- * @private
- */
- onIPConfigChange_: function(event) {
- if (!this.managedProperties_) {
- return;
- }
- const config = OncMojo.getUpdatedIPConfigProperties(
- this.managedProperties_, event.detail.field, event.detail.value);
- if (config) {
- this.setMojoNetworkProperties_(config);
- }
- },
-
- /**
- * Event triggered when the Proxy configuration element changes.
- * @param {!CustomEvent<!mojom.ProxySettings>} event
- * @private
- */
- onProxyChange_: function(event) {
- if (!this.propertiesReceived_) {
- return;
- }
- const config = this.getDefaultConfigProperties_();
- config.proxySettings = event.detail;
- this.setMojoNetworkProperties_(config);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean} True if the shared message should be shown.
- * @private
- */
- showShared_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- return !!managedProperties &&
- (managedProperties.source == mojom.OncSource.kDevice ||
- managedProperties.source == mojom.OncSource.kDevicePolicy) &&
- !this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean} True if the AutoConnect checkbox should be shown.
- * @private
- */
- showAutoConnect_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- return !!managedProperties &&
- managedProperties.type != mojom.NetworkType.kEthernet &&
- this.isRemembered_(managedProperties) &&
- !this.isArcVpn_(managedProperties) &&
- !this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean} Whether the toggle for the Always-on VPN feature is
- * displayed.
- * @private
- */
- showAlwaysOnVpn_: function(managedProperties) {
- return this.isArcVpn_(managedProperties) && this.prefs.arc &&
- this.prefs.arc.vpn && this.prefs.arc.vpn.always_on &&
- this.prefs.arc.vpn.always_on.vpn_package &&
- OncMojo.getActiveValue(managedProperties.typeProperties.vpn.host) ===
- this.prefs.arc.vpn.always_on.vpn_package.value;
- },
-
- /** @private */
- alwaysOnVpnChanged_: function() {
- if (this.prefs && this.prefs.arc && this.prefs.arc.vpn &&
- this.prefs.arc.vpn.always_on && this.prefs.arc.vpn.always_on.lockdown) {
- this.set(
- 'prefs.arc.vpn.always_on.lockdown.value',
- !!this.alwaysOnVpn_ && this.alwaysOnVpn_.value);
- }
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean} True if the prefer network checkbox should be shown.
- * @private
- */
- showPreferNetwork_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- if (!managedProperties) {
- return false;
- }
-
- const type = managedProperties.type;
- if (type == mojom.NetworkType.kEthernet ||
- type == mojom.NetworkType.kCellular ||
- this.isArcVpn_(managedProperties)) {
- return false;
- }
-
- return this.isRemembered_(managedProperties) &&
- !this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable);
- },
-
- /**
- * @param {Event} event
- * @private
- */
- onPreferNetworkRowClicked_: function(event) {
- // Stop propagation because the toggle and policy indicator handle clicks
- // themselves.
- event.stopPropagation();
- const preferNetworkToggle =
- this.shadowRoot.querySelector('#preferNetworkToggle');
- if (!preferNetworkToggle || preferNetworkToggle.disabled) {
- return;
- }
-
- this.preferNetwork_ = !this.preferNetwork_;
- },
-
- /**
- * @param {!Array<string>} fields
- * @return {boolean}
- * @private
- */
- hasVisibleFields_: function(fields) {
- for (let i = 0; i < fields.length; ++i) {
- const key = OncMojo.getManagedPropertyKey(fields[i]);
- const value = this.get(key, this.managedProperties_);
- if (value !== undefined && value !== '') {
- return true;
- }
- }
- return false;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- hasInfoFields_: function() {
- return this.getInfoEditFieldTypes_().length > 0 ||
- this.hasVisibleFields_(this.getInfoFields_());
- },
-
- /**
- * @return {!Array<string>} The fields to display in the info section.
- * @private
- */
- getInfoFields_: function() {
- if (!this.managedProperties_) {
- return [];
- }
-
- /** @type {!Array<string>} */ const fields = [];
- switch (this.managedProperties_.type) {
- case mojom.NetworkType.kCellular:
- fields.push(
- 'cellular.activationState', 'cellular.servingOperator.name');
- if (this.managedProperties_.restrictedConnectivity) {
- fields.push('restrictedConnectivity');
- }
- break;
- case mojom.NetworkType.kTether:
- fields.push(
- 'tether.batteryPercentage', 'tether.signalStrength',
- 'tether.carrier');
- break;
- case mojom.NetworkType.kVPN:
- const vpnType = this.managedProperties_.typeProperties.vpn.type;
- switch (vpnType) {
- case mojom.VpnType.kExtension:
- fields.push('vpn.providerName');
- break;
- case mojom.VpnType.kArc:
- fields.push('vpn.type');
- fields.push('vpn.providerName');
- break;
- case mojom.VpnType.kOpenVPN:
- fields.push(
- 'vpn.type', 'vpn.host', 'vpn.openVpn.username',
- 'vpn.openVpn.extraHosts');
- break;
- case mojom.VpnType.kL2TPIPsec:
- fields.push('vpn.type', 'vpn.host', 'vpn.l2tp.username');
- break;
- }
- break;
- case mojom.NetworkType.kWiFi:
- if (this.managedProperties_.restrictedConnectivity) {
- fields.push('restrictedConnectivity');
- }
- break;
- }
- return fields;
- },
-
- /**
- * Provides the list of editable fields to <network-property-list>.
- * NOTE: Entries added to this list must be reflected in ConfigProperties in
- * chromeos.network_config.mojom and handled in the service implementation.
- * @return {!Object} A dictionary of editable fields in the info section.
- * @private
- */
- getInfoEditFieldTypes_: function() {
- if (!this.managedProperties_) {
- return [];
- }
-
- /** @dict */ const editFields = {};
- const type = this.managedProperties_.type;
- if (type == mojom.NetworkType.kVPN) {
- const vpnType = this.managedProperties_.typeProperties.vpn.type;
- if (vpnType != mojom.VpnType.kExtension) {
- editFields['vpn.host'] = 'String';
- }
- if (vpnType == mojom.VpnType.kOpenVPN) {
- editFields['vpn.openVpn.username'] = 'String';
- editFields['vpn.openVpn.extraHosts'] = 'StringArray';
- }
- }
- return editFields;
- },
-
- /**
- * @return {!Array<string>} The fields to display in the Advanced section.
- * @private
- */
- getAdvancedFields_: function() {
- if (!this.managedProperties_) {
- return [];
- }
-
- /** @type {!Array<string>} */ const fields = [];
- const type = this.managedProperties_.type;
- switch (type) {
- case mojom.NetworkType.kCellular:
- fields.push(
- 'cellular.family', 'cellular.networkTechnology',
- 'cellular.servingOperator.code');
- break;
- case mojom.NetworkType.kWiFi:
- fields.push(
- 'wifi.ssid', 'wifi.bssid', 'wifi.signalStrength', 'wifi.security',
- 'wifi.eap.outer', 'wifi.eap.inner', 'wifi.eap.subjectMatch',
- 'wifi.eap.identity', 'wifi.eap.anonymousIdentity',
- 'wifi.frequency');
- break;
- }
- return fields;
- },
-
- /**
- * @return {!Array<string>} The fields to display in the device section.
- * @private
- */
- getDeviceFields_: function() {
- if (!this.managedProperties_ ||
- this.managedProperties_.type !== mojom.NetworkType.kCellular) {
- return [];
- }
-
- return [
- 'cellular.homeProvider.name', 'cellular.homeProvider.country',
- 'cellular.homeProvider.code', 'cellular.manufacturer', 'cellular.modelId',
- 'cellular.firmwareRevision', 'cellular.hardwareRevision', 'cellular.esn',
- 'cellular.iccid', 'cellular.imei', 'cellular.imsi', 'cellular.mdn',
- 'cellular.meid', 'cellular.min'
- ];
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {boolean} propertiesReceived
- * @return {boolean}
- * @private
- */
- showAdvanced_: function(managedProperties, propertiesReceived) {
- if (!managedProperties || !propertiesReceived) {
- return false;
- }
- if (managedProperties.type == mojom.NetworkType.kTether) {
- // These settings apply to the underlying WiFi network, not the Tether
- // network.
- return false;
- }
- return this.hasAdvancedFields_() || this.hasDeviceFields_();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- hasAdvancedFields_: function() {
- return this.hasVisibleFields_(this.getAdvancedFields_());
- },
-
- /**
- * @return {boolean}
- * @private
- */
- hasDeviceFields_: function() {
- return this.hasVisibleFields_(this.getDeviceFields_());
- },
-
- /**
- * @return {boolean}
- * @private
- */
- hasAdvancedOrDeviceFields_: function() {
- return this.hasAdvancedFields_() || this.hasDeviceFields_();
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean}
- * @private
- */
- hasNetworkSection_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- if (!managedProperties ||
- managedProperties.type == mojom.NetworkType.kTether) {
- // These settings apply to the underlying WiFi network, not the Tether
- // network.
- return false;
- }
- if (this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable)) {
- return false;
- }
- if (managedProperties.type == mojom.NetworkType.kCellular) {
- return true;
- }
- return this.isRememberedOrConnected_(managedProperties);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean}
- * @private
- */
- hasProxySection_: function(
- managedProperties, globalPolicy, managedNetworkAvailable) {
- if (!managedProperties ||
- managedProperties.type == mojom.NetworkType.kTether) {
- // Proxy settings apply to the underlying WiFi network, not the Tether
- // network.
- return false;
- }
- if (this.isBlockedByPolicy_(
- managedProperties, globalPolicy, managedNetworkAvailable)) {
- return false;
- }
- return this.isRememberedOrConnected_(managedProperties);
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showCellularChooseNetwork_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.type == mojom.NetworkType.kCellular &&
- managedProperties.typeProperties.cellular.supportNetworkScan;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- showScanningSpinner_: function() {
- if (!this.managedProperties_ ||
- this.managedProperties_.type != mojom.NetworkType.kCellular) {
- return false;
- }
- return !!this.deviceState_ && this.deviceState_.scanning;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showCellularSim_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.type == mojom.NetworkType.kCellular &&
- managedProperties.typeProperties.cellular.family != 'CDMA';
- },
-
- /**
- * @param {!mojom.ManagedProperties|undefined} managedProperties
- * @return {boolean}
- * @private
- */
- isArcVpn_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.type == mojom.NetworkType.kVPN &&
- managedProperties.typeProperties.vpn.type == mojom.VpnType.kArc;
- },
-
- /**
- * @param {!mojom.ManagedProperties|undefined} managedProperties
- * @return {boolean}
- * @private
- */
- isThirdPartyVpn_: function(managedProperties) {
- return !!managedProperties &&
- managedProperties.type == mojom.NetworkType.kVPN &&
- managedProperties.typeProperties.vpn.type == mojom.VpnType.kExtension;
- },
-
- /**
- * @param {string} ipAddress
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- showIpAddress_: function(ipAddress, managedProperties) {
- // Arc Vpn does not currently pass IP configuration to ChromeOS. IP address
- // property holds an internal IP address Android uses to talk to ChromeOS.
- // TODO(lgcheng@) Show correct IP address when we implement IP configuration
- // correctly.
- if (this.isArcVpn_(managedProperties)) {
- return false;
- }
-
- // Cellular IP addresses are shown under the network details section.
- if (this.isCellular_(managedProperties)) {
- return false;
- }
-
- return !!ipAddress && this.isConnectedState_(managedProperties);
- },
-
- /**
- * @param {!Object} curValue
- * @param {!Object} newValue
- * @return {boolean} True if all properties set in |newValue| are equal to
- * the corresponding properties in |curValue|. Note: Not all properties
- * of |curValue| need to be specified in |newValue| for this to return
- * true.
- * @private
- */
- allPropertiesMatch_: function(curValue, newValue) {
- for (const key in newValue) {
- if (newValue[key] != curValue[key]) {
- return false;
- }
- }
- return true;
- },
-
- /**
- * @param {boolean} outOfRange
- * @param {?OncMojo.DeviceStateProperties} deviceState
- * @return {boolean}
- * @private
- */
- isOutOfRangeOrNotEnabled_: function(outOfRange, deviceState) {
- return outOfRange ||
- (!!deviceState &&
- deviceState.deviceState !=
- chromeos.networkConfig.mojom.DeviceStateType.kEnabled);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html b/chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html
deleted file mode 100644
index b0d29715c33..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html
+++ /dev/null
@@ -1,94 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior_mojo.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-internet-known-networks-page">
- <template>
- <style include="internet-shared iron-flex">
- cr-policy-indicator {
- margin-inline-start: var(--settings-controlled-by-spacing);
- }
- </style>
-
- <div class="settings-box first">
- <div class="settings-box-text">$i18n{knownNetworksMessage}</div>
- </div>
-
- <div class="settings-box settings-box-text">
- <div class="secondary">$i18n{knownNetworksPreferred}</div>
- </div>
- <div class="list-frame vertical-list"
- hidden$="[[havePreferred_(networkStateList_)]]">
- <div class="list-item settings-box-text">
- $i18n{internetNoNetworks}
- </div>
- </div>
- <div class="list-frame vertical-list"
- hidden$="[[!havePreferred_(networkStateList_)]]">
- <template is="dom-repeat" items="[[networkStateList_]]"
- filter="networkIsPreferred_">
- <div class="list-item">
- <cr-link-row embedded label="[[getNetworkDisplayName_(item)]]"
- on-click="fireShowDetails_">
- <template is="dom-if" if="[[isPolicySource(item.source))]]">
- <cr-policy-indicator on-click="doNothing_"
- indicator-type="[[getIndicatorTypeForSource(item.source)]]">
- </cr-policy-indicator>
- </template>
- </cr-link-row>
- <div class="separator"></div>
- <cr-icon-button class="icon-more-vert" tabindex$="[[tabindex]]"
- on-click="onMenuButtonTap_" title="$i18n{moreActions}">
- </cr-icon-button>
- </div>
- </template>
- </div>
-
- <div class="settings-box settings-box-text">
- <div class="secondary">$i18n{knownNetworksAll}</div>
- </div>
- <div class="list-frame vertical-list"
- hidden$="[[!haveNotPreferred_(networkStateList_)]]">
- <template is="dom-repeat" items="[[networkStateList_]]"
- filter="networkIsNotPreferred_">
- <div class="list-item">
- <cr-link-row embedded label="[[getNetworkDisplayName_(item)]]"
- on-click="fireShowDetails_">
- <template is="dom-if" if="[[isPolicySource(item.source))]]">
- <cr-policy-indicator on-click="doNothing_"
- indicator-type="[[getIndicatorTypeForSource(item.source)]]">
- </cr-policy-indicator>
- </template>
- </cr-link-row>
- <div class="separator"></div>
- <cr-icon-button class="icon-more-vert" tabindex$="[[tabindex]]"
- on-click="onMenuButtonTap_" title="$i18n{moreActions}">
- </cr-icon-button>
- </div>
- </template>
- </div>
-
- <cr-action-menu id="dotsMenu">
- <button class="dropdown-item" hidden="[[!showAddPreferred_]]"
- on-click="onAddPreferredTap_">
- $i18n{knownNetworksMenuAddPreferred}
- </button>
- <button class="dropdown-item"
- hidden="[[!showRemovePreferred_]]" on-click="onRemovePreferredTap_">
- $i18n{knownNetworksMenuRemovePreferred}
- </button>
- <button class="dropdown-item" disabled="[[!enableForget_]]"
- on-click="onForgetTap_">
- $i18n{knownNetworksMenuForget}
- </button>
- </cr-action-menu>
-
- </template>
- <script src="internet_known_networks_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js b/chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js
deleted file mode 100644
index 3351442766d..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-internet-known-networks' is the settings subpage listing the
- * known networks for a type (currently always WiFi).
- */
-Polymer({
- is: 'settings-internet-known-networks-page',
-
- behaviors: [
- CrNetworkListenerBehavior,
- CrPolicyNetworkBehaviorMojo,
- ],
-
- properties: {
- /**
- * The type of networks to list.
- * @type {chromeos.networkConfig.mojom.NetworkType|undefined}
- */
- networkType: {
- type: Number,
- observer: 'networkTypeChanged_',
- },
-
- /**
- * List of all network state data for the network type.
- * @private {!Array<!OncMojo.NetworkStateProperties>}
- */
- networkStateList_: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- /** @private */
- showAddPreferred_: Boolean,
-
- /** @private */
- showRemovePreferred_: Boolean,
-
- /**
- * We always show 'Forget' since we do not know whether or not to enable
- * it until we fetch the managed properties, and we do not want an empty
- * menu.
- * @private
- */
- enableForget_: Boolean,
- },
-
- /** @private {string} */
- selectedGuid_: '',
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @override */
- created: function() {
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /** CrosNetworkConfigObserver impl */
- onNetworkStateListChanged: function() {
- this.refreshNetworks_();
- },
-
- /** @private */
- networkTypeChanged_: function() {
- this.refreshNetworks_();
- },
-
- /**
- * Requests the list of network states from Chrome. Updates networkStates
- * once the results are returned from Chrome.
- * @private
- */
- refreshNetworks_: function() {
- if (this.networkType === undefined) {
- return;
- }
- const filter = {
- filter: chromeos.networkConfig.mojom.FilterType.kConfigured,
- limit: chromeos.networkConfig.mojom.NO_LIMIT,
- networkType: this.networkType,
- };
- this.networkConfig_.getNetworkStateList(filter).then(response => {
- this.networkStateList_ = response.result;
- });
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} networkState
- * @return {boolean}
- * @private
- */
- networkIsPreferred_: function(networkState) {
- // Currently we treat NetworkStateProperties.Priority as a boolean.
- return networkState.priority > 0;
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} networkState
- * @return {boolean}
- * @private
- */
- networkIsNotPreferred_: function(networkState) {
- return networkState.priority == 0;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- havePreferred_: function() {
- return this.networkStateList_.find(
- state => this.networkIsPreferred_(state)) !== undefined;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- haveNotPreferred_: function() {
- return this.networkStateList_.find(
- state => this.networkIsNotPreferred_(state)) !== undefined;
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} networkState
- * @return {string}
- * @private
- */
- getNetworkDisplayName_: function(networkState) {
- return OncMojo.getNetworkStateDisplayName(networkState);
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onMenuButtonTap_: function(event) {
- const button = event.target;
- const networkState =
- /** @type {!OncMojo.NetworkStateProperties} */ (event.model.item);
- this.selectedGuid_ = networkState.guid;
- // We need to make a round trip to Chrome in order to retrieve the managed
- // properties for the network. The delay is not noticeable (~5ms) and is
- // preferable to initiating a query for every known network at load time.
- this.networkConfig_.getManagedProperties(this.selectedGuid_)
- .then(response => {
- const properties = response.result;
- if (!properties) {
- console.error('Properties not found for: ' + this.selectedGuid_);
- return;
- }
- if (properties.priority &&
- this.isNetworkPolicyEnforced(properties.priority)) {
- this.showAddPreferred_ = false;
- this.showRemovePreferred_ = false;
- } else {
- const preferred = this.networkIsPreferred_(networkState);
- this.showAddPreferred_ = !preferred;
- this.showRemovePreferred_ = preferred;
- }
- this.enableForget_ = !this.isPolicySource(networkState.source);
- /** @type {!CrActionMenuElement} */ (this.$.dotsMenu)
- .showAt(/** @type {!Element} */ (button));
- });
- event.stopPropagation();
- },
-
- /**
- * @param {!chromeos.networkConfig.mojom.ConfigProperties} config
- * @private
- */
- setProperties_: function(config) {
- this.networkConfig_.setProperties(this.selectedGuid_, config)
- .then(response => {
- if (!response.success) {
- console.error(
- 'Unable to set properties for: ' + this.selectedGuid_ + ': ' +
- JSON.stringify(config));
- }
- });
- },
-
- /** @private */
- onRemovePreferredTap_: function() {
- assert(this.networkType !== undefined);
- const config = OncMojo.getDefaultConfigProperties(this.networkType);
- config.priority = {value: 0};
- this.setProperties_(config);
- /** @type {!CrActionMenuElement} */ (this.$.dotsMenu).close();
- },
-
- /** @private */
- onAddPreferredTap_: function() {
- assert(this.networkType !== undefined);
- const config = OncMojo.getDefaultConfigProperties(this.networkType);
- config.priority = {value: 1};
- this.setProperties_(config);
- /** @type {!CrActionMenuElement} */ (this.$.dotsMenu).close();
- },
-
- /** @private */
- onForgetTap_: function() {
- this.networkConfig_.forgetNetwork(this.selectedGuid_).then(response => {
- if (!response.success) {
- console.error('Froget network failed for: ' + this.selectedGuid_);
- }
- });
- /** @type {!CrActionMenuElement} */ (this.$.dotsMenu).close();
- },
-
- /**
- * Fires a 'show-detail' event with an item containing a |networkStateList_|
- * entry in the event model.
- * @param {!Event} event
- * @private
- */
- fireShowDetails_: function(event) {
- const networkState =
- /** @type {!OncMojo.NetworkStateProperties} */ (event.model.item);
- this.fire('show-detail', networkState);
- event.stopPropagation();
- },
-
- /**
- * Make sure events in embedded components do not propagate to onDetailsTap_.
- * @param {!Event} event
- * @private
- */
- doNothing_: function(event) {
- event.stopPropagation();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page.html b/chromium/chrome/browser/resources/settings/internet_page/internet_page.html
deleted file mode 100644
index f9b99636a51..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_page.html
+++ /dev/null
@@ -1,135 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/mojo_interface_provider.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="internet_config.html">
-<link rel="import" href="internet_detail_page.html">
-<link rel="import" href="internet_known_networks_page.html">
-<link rel="import" href="internet_page_browser_proxy.html">
-<link rel="import" href="internet_subpage.html">
-<link rel="import" href="network_summary.html">
-
-<dom-module id="settings-internet-page">
- <template>
- <style include="settings-shared">
- iron-icon.policy {
- height: 24px;
- margin-inline-end: 12px;
- margin-inline-start: 4px;
- width: 24px;
- }
- </style>
- <settings-animated-pages id="pages" section="internet"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <network-summary default-network="{{defaultNetwork}}"
- device-states="{{deviceStates}}">
- </network-summary>
- <template is="dom-if" if="[[allowAddConnection_(globalPolicy_,
- managedNetworkAvailable)]]">
- <cr-expand-button
- alt="$i18n{internetAddConnectionExpandA11yLabel}"
- class="settings-box two-line"
- expanded="{{addConnectionExpanded_}}"
- id="expandAddConnections">
- $i18n{internetAddConnection}
- </cr-expand-button>
- <template is="dom-if" if="[[addConnectionExpanded_]]">
- <div class="list-frame vertical-list">
- <template is="dom-if" if="[[wifiIsEnabled_(deviceStates)]]">
- <div actionable class="list-item" on-click="onAddWiFiTap_">
- <div class="start settings-box-text">
- $i18n{internetAddWiFi}
- </div>
- <cr-icon-button class="icon-add-wifi"
- aria-label="$i18n{internetAddWiFi}"></cr-icon-button>
- </div>
- </template>
- <div actionable class="list-item" on-click="onAddVPNTap_">
- <div class="start settings-box-text">
- $i18n{internetAddVPN}
- </div>
- <cr-icon-button class="icon-add-circle"
- aria-label="$i18n{internetAddVPN}"></cr-icon-button>
- </div>
- <template is="dom-repeat" items="[[vpnProviders_]]">
- <div actionable class="list-item"
- on-click="onAddThirdPartyVpnTap_">
- <div class="start settings-box-text">
- [[getAddThirdPartyVpnLabel_(item)]]
- </div>
- <cr-icon-button class="icon-external"
- aria-label$="[[getAddThirdPartyVpnLabel_(item)]]">
- </cr-icon-button>
- </div>
- </template>
- </div>
- </template>
- </template>
- <template is="dom-if" if="[[!allowAddConnection_(globalPolicy_,
- managedNetworkAvailable)]]">
- <div class="settings-box">
- <iron-icon class="policy" icon="cr20:domain"></iron-icon>
- <div class="settings-box-text">
- $i18n{internetAddConnectionNotAllowed}
- </div>
- </div>
- </template>
- </div>
-
- <template is="dom-if" route-path="/networkDetail" no-search restamp>
- <settings-subpage page-title="$i18n{internetDetailPageTitle}">
- <settings-internet-detail-page prefs="{{prefs}}"
- default-network="[[defaultNetwork]]"
- global-policy="[[globalPolicy_]]"
- managed-network-available="[[managedNetworkAvailable]]">
- </settings-internet-detail-page>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/knownNetworks" no-search restamp>
- <settings-subpage page-title="$i18n{internetKnownNetworksPageTitle}">
- <settings-internet-known-networks-page
- network-type="[[knownNetworksType_]]">
- </settings-internet-known-networks-page>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/networks" no-search restamp>
- <settings-subpage page-title="[[getNetworksPageTitle_(subpageType_)]]"
- show-spinner="[[showSpinner_]]"
- spinner-title="$i18n{networkScanningLabel}">
- <settings-internet-subpage
- default-network="[[defaultNetwork]]"
- device-state="[[getDeviceState_(subpageType_, deviceStates)]]"
- tether-device-state="[[getTetherDeviceState_(deviceStates)]]"
- global-policy="[[globalPolicy_]]"
- vpn-providers="[[vpnProviders_]]"
- show-spinner="{{showSpinner_}}">
- </settings-internet-subpage>
- </settings-subpage>
- </template>
-
- </settings-animated-pages>
-
- <template is="dom-if" if="[[showInternetConfig_]]" restamp>
- <internet-config id="configDialog" on-close="onInternetConfigClose_">
- </internet-config>
- </template>
-
- </template>
- <script src="internet_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page.js b/chromium/chrome/browser/resources/settings/internet_page/internet_page.js
deleted file mode 100644
index 8f2b2d7b745..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_page.js
+++ /dev/null
@@ -1,557 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-const mojom = chromeos.networkConfig.mojom;
-
-/**
- * @fileoverview
- * 'settings-internet-page' is the settings page containing internet
- * settings.
- */
-Polymer({
- is: 'settings-internet-page',
-
- behaviors: [
- CrNetworkListenerBehavior,
- I18nBehavior,
- settings.RouteObserverBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
-
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * The device state for each network device type, keyed by NetworkType. Set
- * by network-summary.
- * @type {!Object<!OncMojo.DeviceStateProperties>|undefined}
- * @private
- */
- deviceStates: {
- type: Object,
- notify: true,
- observer: 'onDeviceStatesChanged_',
- },
-
- /**
- * Highest priority connected network or null. Set by network-summary.
- * @type {?OncMojo.NetworkStateProperties|undefined}
- */
- defaultNetwork: {
- type: Object,
- notify: true,
- },
-
- /**
- * Set by internet-subpage. Controls spinner visibility in subpage header.
- * @private
- */
- showSpinner_: Boolean,
-
- /**
- * The network type for the networks subpage when shown.
- * @type {chromeos.networkConfig.mojom.NetworkType}
- * @private
- */
- subpageType_: Number,
-
- /**
- * The network type for the known networks subpage when shown.
- * @type {chromeos.networkConfig.mojom.NetworkType}
- * @private
- */
- knownNetworksType_: Number,
-
- /**
- * Whether the 'Add connection' section is expanded.
- * @private
- */
- addConnectionExpanded_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {!chromeos.networkConfig.mojom.GlobalPolicy|undefined} */
- globalPolicy_: Object,
-
- /**
- * Whether a managed network is available in the visible network list.
- * @private {boolean}
- */
- managedNetworkAvailable: {
- type: Boolean,
- value: false,
- },
-
- /**
- * List of third party (Extension + Arc) VPN providers.
- * @type {!Array<!chromeos.networkConfig.mojom.VpnProvider>}
- * @private
- */
- vpnProviders_: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- /** @private {boolean} */
- showInternetConfig_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {!Map<string, Element>} */
- focusConfig_: {
- type: Object,
- value: function() {
- return new Map();
- },
- },
- },
-
- /**
- * Type of last detail page visited
- * @private {chromeos.networkConfig.mojom.NetworkType|undefined}
- */
- detailType_: undefined,
-
- // Element event listeners
- listeners: {
- 'device-enabled-toggled': 'onDeviceEnabledToggled_',
- 'network-connect': 'onNetworkConnect_',
- 'show-config': 'onShowConfig_',
- 'show-detail': 'onShowDetail_',
- 'show-known-networks': 'onShowKnownNetworks_',
- 'show-networks': 'onShowNetworks_',
- },
-
- /** @private {?settings.InternetPageBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.InternetPageBrowserProxyImpl.getInstance();
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /** @override */
- attached: function() {
- this.networkConfig_.getGlobalPolicy().then(response => {
- this.globalPolicy_ = response.result;
- });
- this.onVpnProvidersChanged();
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @param {!settings.Route} oldRoute
- * @protected
- */
- currentRouteChanged: function(route, oldRoute) {
- if (route == settings.routes.INTERNET_NETWORKS) {
- // Handle direct navigation to the networks page,
- // e.g. chrome://settings/internet/networks?type=WiFi
- const queryParams = settings.getQueryParameters();
- const type = queryParams.get('type');
- if (type) {
- this.subpageType_ = OncMojo.getNetworkTypeFromString(type);
- }
- } else if (route == settings.routes.KNOWN_NETWORKS) {
- // Handle direct navigation to the known networks page,
- // e.g. chrome://settings/internet/knownNetworks?type=WiFi
- const queryParams = settings.getQueryParameters();
- const type = queryParams.get('type');
- if (type) {
- this.knownNetworksType_ = OncMojo.getNetworkTypeFromString(type);
- }
- } else if (
- route != settings.routes.INTERNET && route != settings.routes.BASIC) {
- // If we are navigating to a non internet section, do not set focus.
- return;
- }
-
- if (!settings.routes.INTERNET ||
- !settings.routes.INTERNET.contains(oldRoute)) {
- return;
- }
-
- // Focus the subpage arrow where appropriate.
- let element;
- if (route == settings.routes.INTERNET_NETWORKS) {
- // iron-list makes the correct timing to focus an item in the list
- // very complicated, and the item may not exist, so just focus the
- // entire list for now.
- const subPage = this.$$('settings-internet-subpage');
- if (subPage) {
- element = subPage.$$('#networkList');
- }
- } else if (this.detailType_ !== undefined) {
- const oncType = OncMojo.getNetworkTypeString(this.detailType_);
- const rowForDetailType = this.$$('network-summary').$$(`#${oncType}`);
-
- // Note: It is possible that the row is no longer present in the DOM
- // (e.g., when a Cellular dongle is unplugged or when Instant Tethering
- // becomes unavailable due to the Bluetooth controller disconnecting).
- if (rowForDetailType) {
- element = rowForDetailType.$$('.subpage-arrow');
- }
- }
- if (element) {
- this.focusConfig_.set(oldRoute.path, element);
- } else {
- this.focusConfig_.delete(oldRoute.path);
- }
- },
-
- /** CrNetworkListenerBehavior override */
- onVpnProvidersChanged: function() {
- this.networkConfig_.getVpnProviders().then(response => {
- const providers = response.providers;
- providers.sort(this.compareVpnProviders_);
- this.vpnProviders_ = providers;
- });
- },
-
- /**
- * Event triggered by a device state enabled toggle.
- * @param {!CustomEvent<!{
- * enabled: boolean,
- * type: chromeos.networkConfig.mojom.NetworkType
- * }>} event
- * @private
- */
- onDeviceEnabledToggled_: function(event) {
- this.networkConfig_.setNetworkTypeEnabledState(
- event.detail.type, event.detail.enabled);
- },
-
- /**
- * @param {!CustomEvent<!{type: string, guid: ?string, name: ?string}>} event
- * @private
- */
- onShowConfig_: function(event) {
- const type = OncMojo.getNetworkTypeFromString(event.detail.type);
- if (!event.detail.guid) {
- // New configuration
- this.showConfig_(true /* configAndConnect */, type);
- } else {
- this.showConfig_(
- false /* configAndConnect */, type, event.detail.guid,
- event.detail.name);
- }
- },
-
- /**
- * @param {boolean} configAndConnect
- * @param {chromeos.networkConfig.mojom.NetworkType} type
- * @param {?string=} opt_guid
- * @param {?string=} opt_name
- * @private
- */
- showConfig_: function(configAndConnect, type, opt_guid, opt_name) {
- assert(
- type != chromeos.networkConfig.mojom.NetworkType.kCellular &&
- type != chromeos.networkConfig.mojom.NetworkType.kTether);
- if (this.showInternetConfig_) {
- return;
- }
- this.showInternetConfig_ = true;
- // Async call to ensure dialog is stamped.
- setTimeout(() => {
- const configDialog =
- /** @type {!InternetConfigElement} */ (this.$$('#configDialog'));
- assert(!!configDialog);
- configDialog.type = OncMojo.getNetworkTypeString(type);
- configDialog.guid = opt_guid || '';
- configDialog.name = opt_name || '';
- configDialog.showConnect = configAndConnect;
- configDialog.open();
- });
- },
-
- /** @private */
- onInternetConfigClose_: function() {
- this.showInternetConfig_ = false;
- },
-
- /**
- * @param {!CustomEvent<!OncMojo.NetworkStateProperties>} event
- * @private
- */
- onShowDetail_: function(event) {
- const networkState = event.detail;
- this.detailType_ = networkState.type;
- const params = new URLSearchParams;
- params.append('guid', networkState.guid);
- params.append('type', OncMojo.getNetworkTypeString(networkState.type));
- params.append('name', OncMojo.getNetworkStateDisplayName(networkState));
- settings.navigateTo(settings.routes.NETWORK_DETAIL, params);
- },
-
- /**
- * @param {!CustomEvent<chromeos.networkConfig.mojom.NetworkType>} event
- * @private
- */
- onShowNetworks_: function(event) {
- this.showNetworksSubpage_(event.detail);
- },
-
- /**
- * @return {string}
- * @private
- */
- getNetworksPageTitle_: function() {
- // The shared Cellular/Tether subpage is referred to as "Mobile".
- // TODO(khorimoto): Remove once Cellular/Tether are split into their own
- // sections.
- if (this.subpageType_ == mojom.NetworkType.kCellular ||
- this.subpageType_ == mojom.NetworkType.kTether) {
- return this.i18n('OncTypeMobile');
- }
- return this.i18n(
- 'OncType' + OncMojo.getNetworkTypeString(this.subpageType_));
- },
-
- /**
- * @param {chromeos.networkConfig.mojom.NetworkType} subpageType
- * @param {!Object<!OncMojo.DeviceStateProperties>|undefined} deviceStates
- * @return {!OncMojo.DeviceStateProperties|undefined}
- * @private
- */
- getDeviceState_: function(subpageType, deviceStates) {
- if (subpageType === undefined) {
- return undefined;
- }
- // If both Tether and Cellular are enabled, use the Cellular device state
- // when directly navigating to the Tether page.
- if (subpageType == mojom.NetworkType.kTether &&
- this.deviceStates[mojom.NetworkType.kCellular]) {
- subpageType = mojom.NetworkType.kCellular;
- }
- return deviceStates[subpageType];
- },
-
- /**
- * @param {!Object<!OncMojo.DeviceStateProperties>|undefined} deviceStates
- * @return {!OncMojo.DeviceStateProperties|undefined}
- * @private
- */
- getTetherDeviceState_: function(deviceStates) {
- return deviceStates[mojom.NetworkType.kTether];
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} newValue
- * @param {!OncMojo.DeviceStateProperties|undefined} oldValue
- * @private
- */
- onDeviceStatesChanged_: function(newValue, oldValue) {
- const wifiDeviceState =
- this.getDeviceState_(mojom.NetworkType.kWiFi, newValue);
- let managedNetworkAvailable = false;
- if (wifiDeviceState) {
- managedNetworkAvailable = !!wifiDeviceState.managedNetworkAvailable;
- }
-
- if (this.managedNetworkAvailable != managedNetworkAvailable) {
- this.managedNetworkAvailable = managedNetworkAvailable;
- }
-
- if (this.detailType_ && !this.deviceStates[this.detailType_]) {
- // If the device type associated with the current network has been
- // removed (e.g., due to unplugging a Cellular dongle), the details page,
- // if visible, displays controls which are no longer functional. If this
- // case occurs, close the details page.
- const detailPage = this.$$('settings-internet-detail-page');
- if (detailPage) {
- detailPage.close();
- }
- }
- },
-
- /**
- * @param {!CustomEvent<chromeos.networkConfig.mojom.NetworkType>} event
- * @private
- */
- onShowKnownNetworks_: function(event) {
- const type = event.detail;
- this.detailType_ = type;
- this.knownNetworksType_ = type;
- const params = new URLSearchParams;
- params.append('type', OncMojo.getNetworkTypeString(type));
- settings.navigateTo(settings.routes.KNOWN_NETWORKS, params);
- },
-
- /** @private */
- onAddWiFiTap_: function() {
- this.showConfig_(
- true /* configAndConnect */,
- chromeos.networkConfig.mojom.NetworkType.kWiFi);
- },
-
- /** @private */
- onAddVPNTap_: function() {
- this.showConfig_(
- true /* configAndConnect */,
- chromeos.networkConfig.mojom.NetworkType.kVPN);
- },
-
- /**
- * @param {!{model: !{item: !mojom.VpnProvider}}} event
- * @private
- */
- onAddThirdPartyVpnTap_: function(event) {
- const provider = event.model.item;
- this.browserProxy_.addThirdPartyVpn(provider.appId);
- },
-
- /**
- * @param {chromeos.networkConfig.mojom.NetworkType} type
- * @private
- */
- showNetworksSubpage_: function(type) {
- this.detailType_ = type;
- const params = new URLSearchParams;
- params.append('type', OncMojo.getNetworkTypeString(type));
- this.subpageType_ = type;
- settings.navigateTo(settings.routes.INTERNET_NETWORKS, params);
- },
-
- /**
- * @param {!mojom.VpnProvider} vpnProvider1
- * @param {!mojom.VpnProvider} vpnProvider2
- * @return {number}
- */
- compareVpnProviders_: function(vpnProvider1, vpnProvider2) {
- // Show Extension VPNs before Arc VPNs.
- if (vpnProvider1.type < vpnProvider2.type) {
- return -1;
- }
- if (vpnProvider1.type > vpnProvider2.type) {
- return 1;
- }
- // Show VPNs of the same type by lastLaunchTime.
- if (vpnProvider1.lastLaunchTime.internalValue >
- vpnProvider2.lastLaunchTime.internalValue) {
- return -1;
- }
- if (vpnProvider1.lastLaunchTime.internalValue <
- vpnProvider2.lastLaunchTime.internalValue) {
- return 1;
- }
- return 0;
- },
-
- /**
- * @param {!Array<!OncMojo.DeviceStateProperties>} deviceStates
- * @return {boolean}
- * @private
- */
- wifiIsEnabled_: function(deviceStates) {
- const wifi = deviceStates[mojom.NetworkType.kWiFi];
- return !!wifi &&
- wifi.deviceState ==
- chromeos.networkConfig.mojom.DeviceStateType.kEnabled;
- },
-
- /**
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @param {boolean} managedNetworkAvailable
- * @return {boolean}
- */
- allowAddConnection_: function(globalPolicy, managedNetworkAvailable) {
- if (!globalPolicy) {
- return true;
- }
-
- return !globalPolicy.allowOnlyPolicyNetworksToConnect &&
- (!globalPolicy.allowOnlyPolicyNetworksToConnectIfAvailable ||
- !managedNetworkAvailable);
- },
-
- /**
- * @param {!mojom.VpnProvider} provider
- * @return {string}
- */
- getAddThirdPartyVpnLabel_: function(provider) {
- return this.i18n('internetAddThirdPartyVPN', provider.providerName || '');
- },
-
- /**
- * Handles UI requests to connect to a network.
- * TODO(stevenjb): Handle Cellular activation, etc.
- * @param {!CustomEvent<!{
- * networkState: !OncMojo.NetworkStateProperties,
- * bypassConnectionDialog: (boolean|undefined)
- * }>} event
- * @private
- */
- onNetworkConnect_: function(event) {
- const networkState = event.detail.networkState;
- const type = networkState.type;
- const displayName = OncMojo.getNetworkStateDisplayName(networkState);
-
- if (!event.detail.bypassConnectionDialog &&
- type == mojom.NetworkType.kTether &&
- !networkState.typeState.tether.hasConnectedToHost) {
- const params = new URLSearchParams;
- params.append('guid', networkState.guid);
- params.append('type', OncMojo.getNetworkTypeString(type));
- params.append('name', displayName);
- params.append('showConfigure', true.toString());
-
- settings.navigateTo(settings.routes.NETWORK_DETAIL, params);
- return;
- }
-
- const isMobile = OncMojo.networkTypeIsMobile(type);
- if (!isMobile && (!networkState.connectable || !!networkState.errorState)) {
- this.showConfig_(
- true /* configAndConnect */, type, networkState.guid, displayName);
- return;
- }
-
- this.networkConfig_.startConnect(networkState.guid).then(response => {
- switch (response.result) {
- case mojom.StartConnectResult.kSuccess:
- return;
- case mojom.StartConnectResult.kInvalidGuid:
- case mojom.StartConnectResult.kInvalidState:
- case mojom.StartConnectResult.kCanceled:
- // TODO(stevenjb/khorimoto): Consider handling these cases.
- return;
- case mojom.StartConnectResult.kNotConfigured:
- if (!isMobile) {
- this.showConfig_(
- true /* configAndConnect */, type, networkState.guid,
- displayName);
- }
- return;
- case mojom.StartConnectResult.kBlocked:
- // This shouldn't happen, the UI should prevent this, fall through and
- // show the error.
- case mojom.StartConnectResult.kUnknown:
- console.error(
- 'startConnect failed for: ' + networkState.guid +
- ' Error: ' + response.message);
- return;
- }
- assertNotReached();
- });
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.html b/chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.html
deleted file mode 100644
index 5989a2fed95..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="internet_page_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.js b/chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.js
deleted file mode 100644
index f39bf9141c8..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_page_browser_proxy.js
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview A helper object used for Internet page. */
-cr.exportPath('settings');
-
-cr.define('settings', function() {
- /** @interface */
- class InternetPageBrowserProxy {
- /**
- * Shows the Cellular activation UI.
- * @param {string} guid
- */
- showCellularSetupUI(guid) {}
-
- /**
- * Shows configuration for external VPNs. Includes ThirdParty (extension
- * configured) VPNs, and Arc VPNs.
- * @param {string} guid
- */
- configureThirdPartyVpn(guid) {}
-
- /**
- * Sends an add VPN request to the external VPN provider (ThirdParty VPN
- * extension or Arc VPN provider app).
- * @param {string} appId
- */
- addThirdPartyVpn(appId) {}
-
- /**
- * Requests that Chrome send the list of devices whose "Google Play
- * Services" notifications are disabled (these notifications must be enabled
- * to utilize Instant Tethering). The names will be provided via
- * setGmsCoreNotificationsDisabledDeviceNamesCallback().
- */
- requestGmsCoreNotificationsDisabledDeviceNames() {}
-
- /**
- * Sets the callback to be used to receive the list of devices whose "Google
- * Play Services" notifications are disabled. |callback| is invoked with an
- * array of the names of these devices; note that if no devices have this
- * property, the provided list of device names is empty.
- * @param {function(!Array<string>):void} callback
- */
- setGmsCoreNotificationsDisabledDeviceNamesCallback(callback) {}
- }
-
- /**
- * @implements {settings.InternetPageBrowserProxy}
- */
- class InternetPageBrowserProxyImpl {
- /** @override */
- showCellularSetupUI(guid) {
- chrome.send('showCellularSetupUI', [guid]);
- }
-
- /** @override */
- configureThirdPartyVpn(guid) {
- chrome.send('configureThirdPartyVpn', [guid]);
- }
-
- /** @override */
- addThirdPartyVpn(appId) {
- chrome.send('addThirdPartyVpn', [appId]);
- }
-
- /** @override */
- requestGmsCoreNotificationsDisabledDeviceNames() {
- chrome.send('requestGmsCoreNotificationsDisabledDeviceNames');
- }
-
- /** @override */
- setGmsCoreNotificationsDisabledDeviceNamesCallback(callback) {
- cr.addWebUIListener(
- 'sendGmsCoreNotificationsDisabledDeviceNames', callback);
- }
- }
-
- cr.addSingletonGetter(InternetPageBrowserProxyImpl);
-
- return {
- InternetPageBrowserProxy: InternetPageBrowserProxy,
- InternetPageBrowserProxyImpl: InternetPageBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_shared_css.html b/chromium/chrome/browser/resources/settings/internet_page/internet_shared_css.html
deleted file mode 100644
index fb67a79809c..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_shared_css.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<!-- Common styles for Internet settings. -->
-<dom-module id="internet-shared">
- <template>
- <style include="settings-shared">
- cr-network-icon {
- padding-inline-end: var(--cr-section-padding);
- }
-
- iron-icon.policy {
- margin-inline-end: var(--cr-controlled-by-spacing);
- }
-
- .indented {
- margin-inline-start: var(--cr-section-padding);
- }
-
- .stretch {
- align-items: stretch;
- }
-
- .title {
- font-size: 107.69%; /* 14px / 13px */
- font-weight: 500;
- }
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html b/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html
deleted file mode 100644
index 3307caa6c8e..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html
+++ /dev/null
@@ -1,213 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_list.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior_mojo.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="internet_page_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-internet-subpage">
- <template>
- <style include="settings-shared iron-flex">
- #networkListDiv {
- margin-top: var(--cr-section-vertical-margin);
- min-height: var(--settings-row-min-height);
- }
-
- /* Set padding on children instead of the container itself to ensure that
- separator lines can fill the entire width of the page. */
- #networkListDiv > * {
- /* cr-network-list is padded to the right to allow space for a ripple */
- padding-inline-end: calc(var(--cr-section-padding) -
- var(--cr-icon-ripple-padding));
- padding-inline-start: var(--cr-section-padding);
- }
-
- #addButton {
- margin-inline-end: var(--settings-control-label-spacing);
- }
-
- #onOff {
- font-weight: 500;
- }
-
- #onOff[on] {
- color: var(--cr-toggle-color);
- }
-
- .vpn-header {
- margin-bottom: 8px;
- margin-inline-end: 12px;
- margin-inline-start: 4px;
- margin-top: 8px;
- }
-
- .no-networks {
- margin: 4px;
- }
-
- cr-network-list {
- flex: 1;
- }
-
- #gmscore-notifications-container {
- border-top: var(--cr-separator-line);
- margin: 10px 0;
- padding-bottom: var(--cr-section-padding);
- padding-top: var(--cr-section-padding);
- }
-
- #gmscore-notifications-container[no-networks-text-below] {
- border-bottom: var(--cr-separator-line);
- margin-top: 0;
- }
-
- #gmscore-notifications-device-string {
- @apply --cr-secondary-text;
- margin-top: 5px;
- }
-
- #gmscore-notifications-instructions {
- @apply --cr-secondary-text;
- margin: 0;
- padding-inline-start: 15px;
- }
- </style>
-
- <template is="dom-if" if="[[enableToggleIsVisible_(deviceState)]]">
- <div class="settings-box first">
- <div id="onOff" class="start" on$="[[deviceIsEnabled_(deviceState)]]">
- [[getOffOnString_(deviceState,
- '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]
- </div>
- <!-- The add button in this row is only shown for WiFi networks. -->
- <cr-icon-button class="icon-add-wifi" id="addButton"
- hidden$="[[!showAddButton_(deviceState, globalPolicy)]]"
- aria-label="$i18n{internetAddWiFi}" on-click="onAddButtonTap_"
- tabindex$="[[tabindex]]"></cr-icon-button>
- <cr-toggle id="deviceEnabledButton"
- aria-label$="[[getToggleA11yString_(deviceState)]]"
- checked="[[deviceIsEnabled_(deviceState)]]"
- disabled="[[!enableToggleIsEnabled_(deviceState)]]"
- on-change="onDeviceEnabledChange_">
- </cr-toggle>
- </div>
- </template>
-
- <template is="dom-if" if="[[knownNetworksIsVisible_(deviceState)]]">
- <cr-link-row
- class="hr"
- label="$i18n{knownNetworksButton}"
- on-click="onKnownNetworksTap_">
- </cr-link-row>
- </template>
-
- <template is="dom-if" if="[[deviceIsEnabled_(deviceState)]]">
- <div id="networkListDiv" class="layout vertical flex">
- <!-- VPN only header for built-in VPNs. -->
- <template is="dom-if" if="[[matchesType_('VPN', deviceState)]]">
- <div class="vpn-header layout horizontal center">
- <div class="flex settings-box-text">$i18n{networkVpnBuiltin}</div>
- <cr-icon-button class="icon-add-circle"
- aria-label="$i18n{internetAddVPN}" on-click="onAddButtonTap_"
- tabindex$="[[tabindex]]"></cr-icon-button>
- </div>
- </template>
-
- <!-- List of networks (empty if no networks exist). -->
- <template is="dom-if"
- if="[[shouldShowNetworkList_(networkStateList_)]]">
- <cr-network-list id="networkList" show-buttons
- show-technology-badge="[[showTechnologyBadge_]]"
- networks="[[networkStateList_]]"
- on-selected="onNetworkSelected_">
- </cr-network-list>
- </template>
-
- <!-- Instructions for how to enable "Google Play Services" notifications
- (needed for Instant Tethering). -->
- <template is="dom-if" if="[[showGmsCoreNotificationsSection_(
- notificationsDisabledDeviceNames_)]]">
- <div id="gmscore-notifications-container"
- no-networks-text-below$="[[!networkStateList_.length]]">
- <div>$i18n{gmscoreNotificationsTitle}</div>
- <div id="gmscore-notifications-device-string">
- [[getGmsCoreNotificationsDevicesString_(
- notificationsDisabledDeviceNames_)]]
- </div>
- <ol id="gmscore-notifications-instructions">
- <li>$i18n{gmscoreNotificationsFirstStep}</li>
- <li>$i18n{gmscoreNotificationsSecondStep}</li>
- <li>$i18n{gmscoreNotificationsThirdStep}</li>
- <li>$i18n{gmscoreNotificationsFourthStep}</li>
- </ol>
- </div>
- </template>
-
- <!-- Text shown if no networks exist. -->
- <div hidden="[[shouldShowNetworkList_(networkStateList_)]]"
- inner-h-t-m-l=
- "[[getNoNetworksInnerHtml_(deviceState, tetherDeviceState)]]"
- class="no-networks">
- </div>
-
- <template is="dom-if" if="[[matchesType_('VPN', deviceState)]]">
- <!-- Third party VPNs. -->
- <template is="dom-repeat"
- items="[[getVpnProviders_(vpnProviders, thirdPartyVpns_)]]">
- <div id="[[item.providerName]]"
- class="vpn-header layout horizontal center">
- <div class="flex settings-box-text">[[item.providerName]]</div>
- <cr-icon-button class="icon-add-circle"
- aria-label$="[[getAddThirdPartyVpnA11yString_(item)]]"
- on-click="onAddThirdPartyVpnTap_" tabindex$="[[tabindex]]">
- </cr-icon-button>
- </div>
- <cr-network-list show-buttons
- hidden$="[[!haveThirdPartyVpnNetwork_(thirdPartyVpns_, item)]]"
- networks="[[getThirdPartyVpnNetworks_(thirdPartyVpns_, item)]]"
- on-selected="onNetworkSelected_">
- </cr-network-list>
- <div hidden$="[[haveThirdPartyVpnNetwork_(thirdPartyVpns_, item)]]"
- class="no-networks settings-box-text">
- $i18n{internetNoNetworks}
- </div>
- </template>
- </template>
- </div>
-
- <template is="dom-if"
- if="[[tetherToggleIsVisible_(deviceState, tetherDeviceState)]]">
- <div class="settings-box two-line" actionable
- on-click="onTetherEnabledChange_">
- <div class="start settings-box-text">
- $i18n{internetToggleTetherLabel}
- <div id="tetherSecondary" class="secondary">
- $i18n{internetToggleTetherSubtext}
- </div>
- </div>
- <cr-toggle id="tetherEnabledButton"
- aria-label="$i18n{internetToggleTetherLabel}"
- aria-describedby="tetherSecondary"
- checked="[[deviceIsEnabled_(tetherDeviceState)]]"
- disabled="[[!tetherToggleIsEnabled_(deviceState,
- tetherDeviceState)]]"
- on-change="onTetherEnabledChange_">
- </cr-toggle>
- </div>
- </template>
- </template>
-
- </template>
- <script src="internet_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js b/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js
deleted file mode 100644
index 8ce6c994e2f..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js
+++ /dev/null
@@ -1,731 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer element for displaying information about WiFi,
- * Cellular, or virtual networks.
- */
-
-(function() {
-
-const mojom = chromeos.networkConfig.mojom;
-
-Polymer({
- is: 'settings-internet-subpage',
-
- behaviors: [
- CrNetworkListenerBehavior,
- CrPolicyNetworkBehaviorMojo,
- settings.RouteObserverBehavior,
- I18nBehavior,
- ],
-
- properties: {
- /**
- * Highest priority connected network or null. Provided by
- * settings-internet-page (but set in network-summary).
- * @type {?OncMojo.NetworkStateProperties|undefined}
- */
- defaultNetwork: Object,
-
- /**
- * Device state for the network type. Note: when both Cellular and Tether
- * are available this will always be set to the Cellular device state and
- * |tetherDeviceState| will be set to the Tether device state.
- * @type {!OncMojo.DeviceStateProperties|undefined}
- */
- deviceState: Object,
-
- /**
- * If both Cellular and Tether technologies exist, we combine the subpages
- * and set this to the device state for Tether.
- * @type {!OncMojo.DeviceStateProperties|undefined}
- */
- tetherDeviceState: Object,
-
- /** @type {!chromeos.networkConfig.mojom.GlobalPolicy|undefined} */
- globalPolicy: Object,
-
- /**
- * List of third party (Extension + Arc) VPN providers.
- * @type {!Array<!chromeos.networkConfig.mojom.VpnProvider>}
- */
- vpnProviders: Array,
-
- showSpinner: {
- type: Boolean,
- notify: true,
- value: false,
- },
-
- /**
- * List of all network state data for the network type.
- * @private {!Array<!OncMojo.NetworkStateProperties>}
- */
- networkStateList_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * Dictionary of lists of network states for third party VPNs.
- * @private {!Object<!Array<!OncMojo.NetworkStateProperties>>}
- */
- thirdPartyVpns_: {
- type: Object,
- value: function() {
- return {};
- },
- },
-
- /**
- * List of potential Tether hosts whose "Google Play Services" notifications
- * are disabled (these notifications are required to use Instant Tethering).
- * @private {!Array<string>}
- */
- notificationsDisabledDeviceNames_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * Whether to show technology badge on mobile network icons.
- * @private
- */
- showTechnologyBadge_: {
- type: Boolean,
- value: function() {
- return loadTimeData.valueExists('showTechnologyBadge') &&
- loadTimeData.getBoolean('showTechnologyBadge');
- }
- },
-
- /** @private */
- hasCompletedScanSinceLastEnabled_: {
- type: Boolean,
- value: false,
- },
- },
-
- observers: ['deviceStateChanged_(deviceState)'],
-
- /** @private {number|null} */
- scanIntervalId_: null,
-
- /** @private {settings.InternetPageBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.InternetPageBrowserProxyImpl.getInstance();
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_.setGmsCoreNotificationsDisabledDeviceNamesCallback(
- this.onNotificationsDisabledDeviceNamesReceived_.bind(this));
- this.browserProxy_.requestGmsCoreNotificationsDisabledDeviceNames();
- },
-
- /** override */
- detached: function() {
- this.stopScanning_();
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- if (route != settings.routes.INTERNET_NETWORKS) {
- this.stopScanning_();
- return;
- }
- this.init();
- },
-
- init: function() {
- // Clear any stale data.
- this.networkStateList_ = [];
- this.thirdPartyVpns_ = {};
- this.hasCompletedScanSinceLastEnabled_ = false;
- this.showSpinner = false;
-
- // Request the list of networks and start scanning if necessary.
- this.getNetworkStateList_();
- this.updateScanning_();
- },
-
- /**
- * CrNetworkListenerBehavior override
- * @param {!Array<OncMojo.NetworkStateProperties>} networks
- */
- onActiveNetworksChanged: function(networks) {
- this.getNetworkStateList_();
- },
-
- /** CrNetworkListenerBehavior override */
- onNetworkStateListChanged: function() {
- this.getNetworkStateList_();
- },
-
- /** CrNetworkListenerBehavior override */
- onVpnProvidersChanged: function() {
- if (this.deviceState.type != mojom.NetworkType.kVPN) {
- return;
- }
- this.getNetworkStateList_();
- },
-
- /** @private */
- deviceStateChanged_: function() {
- if (this.deviceState !== undefined) {
- // A scan has completed if the spinner was active (i.e., scanning was
- // active) and the device is no longer scanning.
- this.hasCompletedScanSinceLastEnabled_ = this.showSpinner &&
- !this.deviceState.scanning &&
- this.deviceState.deviceState == mojom.DeviceStateType.kEnabled;
- this.showSpinner = !!this.deviceState.scanning;
- }
-
- // Scans should only be triggered by the "networks" subpage.
- if (settings.getCurrentRoute() != settings.routes.INTERNET_NETWORKS) {
- this.stopScanning_();
- return;
- }
-
- this.getNetworkStateList_();
- this.updateScanning_();
- },
-
- /** @private */
- updateScanning_: function() {
- if (!this.deviceState) {
- return;
- }
-
- if (this.shouldStartScan_()) {
- this.startScanning_();
- return;
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldStartScan_: function() {
- // Scans should be kicked off from the Wi-Fi networks subpage.
- if (this.deviceState.type == mojom.NetworkType.kWiFi) {
- return true;
- }
-
- // Scans should be kicked off from the Mobile data subpage, as long as it
- // includes Tether networks.
- if (this.deviceState.type == mojom.NetworkType.kTether ||
- (this.deviceState.type == mojom.NetworkType.kCellular &&
- this.tetherDeviceState)) {
- return true;
- }
-
- return false;
- },
-
- /** @private */
- startScanning_: function() {
- if (this.scanIntervalId_ != null) {
- return;
- }
- const INTERVAL_MS = 10 * 1000;
- this.networkConfig_.requestNetworkScan(this.deviceState.type);
- this.scanIntervalId_ = window.setInterval(() => {
- this.networkConfig_.requestNetworkScan(this.deviceState.type);
- }, INTERVAL_MS);
- },
-
- /** @private */
- stopScanning_: function() {
- if (this.scanIntervalId_ == null) {
- return;
- }
- window.clearInterval(this.scanIntervalId_);
- this.scanIntervalId_ = null;
- },
-
- /** @private */
- getNetworkStateList_: function() {
- if (!this.deviceState) {
- return;
- }
- const filter = {
- filter: chromeos.networkConfig.mojom.FilterType.kVisible,
- limit: chromeos.networkConfig.mojom.NO_LIMIT,
- networkType: this.deviceState.type,
- };
- this.networkConfig_.getNetworkStateList(filter).then(response => {
- this.onGetNetworks_(response.result);
- });
- },
-
- /**
- * @param {!Array<!OncMojo.NetworkStateProperties>} networkStates
- * @private
- */
- onGetNetworks_: function(networkStates) {
- if (!this.deviceState) {
- // Edge case when device states change before this callback.
- return;
- }
-
- // For the Cellular/Mobile subpage, also request Tether networks.
- if (this.deviceState.type == mojom.NetworkType.kCellular &&
- this.tetherDeviceState) {
- const filter = {
- filter: chromeos.networkConfig.mojom.FilterType.kVisible,
- limit: chromeos.networkConfig.mojom.NO_LIMIT,
- networkType: mojom.NetworkType.kTether,
- };
- this.networkConfig_.getNetworkStateList(filter).then(response => {
- const tetherNetworkStates = response.result;
- this.networkStateList_ = networkStates.concat(tetherNetworkStates);
- });
- return;
- }
-
- // For VPNs, separate out third party (Extension + Arc) VPNs.
- if (this.deviceState.type == mojom.NetworkType.kVPN) {
- const builtinNetworkStates = [];
- const thirdPartyVpns = {};
- networkStates.forEach(state => {
- assert(state.type == mojom.NetworkType.kVPN);
- switch (state.typeState.vpn.type) {
- case mojom.VpnType.kL2TPIPsec:
- case mojom.VpnType.kOpenVPN:
- builtinNetworkStates.push(state);
- break;
- case mojom.VpnType.kArc:
- // Only show connected Arc VPNs.
- if (!OncMojo.connectionStateIsConnected(state.connectionState)) {
- break;
- }
- // Otherwise Arc VPNs are treated the same as Extension VPNs.
- case mojom.VpnType.kExtension:
- const providerId = state.typeState.vpn.providerId;
- thirdPartyVpns[providerId] = thirdPartyVpns[providerId] || [];
- thirdPartyVpns[providerId].push(state);
- break;
- }
- });
- networkStates = builtinNetworkStates;
- this.thirdPartyVpns_ = thirdPartyVpns;
- }
-
- this.networkStateList_ = networkStates;
- },
-
- /**
- * Returns an ordered list of VPN providers for all third party VPNs and any
- * other known providers.
- * @param {!Array<!chromeos.networkConfig.mojom.VpnProvider>} vpnProviders
- * @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
- * @return {!Array<!chromeos.networkConfig.mojom.VpnProvider>}
- * @private
- */
- getVpnProviders_(vpnProviders, thirdPartyVpns) {
- // First add providers for configured thirdPartyVpns. This list will
- // generally be empty or small.
- const configuredProviders = [];
- for (const vpnList of Object.values(thirdPartyVpns)) {
- assert(vpnList.length > 0);
- // All vpns in the list will have the same type and provider id.
- const vpn = vpnList[0].typeState.vpn;
- const provider = {
- type: vpn.type,
- providerId: vpn.providerId,
- providerName: vpn.providerName || vpn.providerId,
- appId: '',
- lastLaunchTime: {internalValue: 0}
- };
- configuredProviders.push(provider);
- }
- // Next update or append known third party providers.
- const unconfiguredProviders = [];
- for (const provider of vpnProviders) {
- const idx = configuredProviders.findIndex(
- p => p.providerId == provider.providerId);
- if (idx >= 0) {
- configuredProviders[idx] = provider;
- } else {
- unconfiguredProviders.push(provider);
- }
- }
- return configuredProviders.concat(unconfiguredProviders);
- },
-
- /**
- * @param {!Array<string>} notificationsDisabledDeviceNames
- * @private
- */
- onNotificationsDisabledDeviceNamesReceived_: function(
- notificationsDisabledDeviceNames) {
- this.notificationsDisabledDeviceNames_ = notificationsDisabledDeviceNames;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean} Whether or not the device state is enabled.
- * @private
- */
- deviceIsEnabled_: function(deviceState) {
- return !!deviceState &&
- deviceState.deviceState ==
- chromeos.networkConfig.mojom.DeviceStateType.kEnabled;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {string} onstr
- * @param {string} offstr
- * @return {string}
- * @private
- */
- getOffOnString_: function(deviceState, onstr, offstr) {
- return this.deviceIsEnabled_(deviceState) ? onstr : offstr;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean}
- * @private
- */
- enableToggleIsVisible_: function(deviceState) {
- return !!deviceState && deviceState.type != mojom.NetworkType.kEthernet &&
- deviceState.type != mojom.NetworkType.kVPN;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean}
- * @private
- */
- enableToggleIsEnabled_: function(deviceState) {
- return !!deviceState &&
- deviceState.deviceState !=
- chromeos.networkConfig.mojom.DeviceStateType.kProhibited;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {string}
- * @private
- */
- getToggleA11yString_: function(deviceState) {
- if (!this.enableToggleIsVisible_(deviceState)) {
- return '';
- }
- switch (deviceState.type) {
- case mojom.NetworkType.kTether:
- case mojom.NetworkType.kCellular:
- return this.i18n('internetToggleMobileA11yLabel');
- case mojom.NetworkType.kWiFi:
- return this.i18n('internetToggleWiFiA11yLabel');
- }
- assertNotReached();
- return '';
- },
-
- /**
- * @param {!mojom.VpnProvider} provider
- * @return {string}
- * @private
- */
- getAddThirdPartyVpnA11yString_: function(provider) {
- return this.i18n('internetAddThirdPartyVPN', provider.providerName || '');
- },
-
- /**
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @return {boolean}
- * @private
- */
- allowAddConnection_: function(globalPolicy) {
- return globalPolicy && !globalPolicy.allowOnlyPolicyNetworksToConnect;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!mojom.GlobalPolicy} globalPolicy
- * @return {boolean}
- * @private
- */
- showAddButton_: function(deviceState, globalPolicy) {
- if (!deviceState || deviceState.type != mojom.NetworkType.kWiFi) {
- return false;
- }
- if (!this.deviceIsEnabled_(deviceState)) {
- return false;
- }
- return this.allowAddConnection_(globalPolicy);
- },
-
- /** @private */
- onAddButtonTap_: function() {
- assert(this.deviceState);
- const type = this.deviceState.type;
- assert(type != mojom.NetworkType.kCellular);
- this.fire('show-config', {type: OncMojo.getNetworkTypeString(type)});
- },
-
- /**
- * @param {!{model: !{item: !mojom.VpnProvider}}} event
- * @private
- */
- onAddThirdPartyVpnTap_: function(event) {
- const provider = event.model.item;
- this.browserProxy_.addThirdPartyVpn(provider.appId);
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean}
- * @private
- */
- knownNetworksIsVisible_: function(deviceState) {
- return !!deviceState && deviceState.type == mojom.NetworkType.kWiFi;
- },
-
- /**
- * Event triggered when the known networks button is clicked.
- * @private
- */
- onKnownNetworksTap_: function() {
- assert(this.deviceState.type == mojom.NetworkType.kWiFi);
- this.fire('show-known-networks', this.deviceState.type);
- },
-
- /**
- * Event triggered when the enable button is toggled.
- * @param {!Event} event
- * @private
- */
- onDeviceEnabledChange_: function(event) {
- assert(this.deviceState);
- this.fire('device-enabled-toggled', {
- enabled: !this.deviceIsEnabled_(this.deviceState),
- type: this.deviceState.type
- });
- },
-
- /**
- * @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
- * @param {!mojom.VpnProvider} provider
- * @return {!Array<!OncMojo.NetworkStateProperties>}
- * @private
- */
- getThirdPartyVpnNetworks_: function(thirdPartyVpns, provider) {
- return thirdPartyVpns[provider.providerId] || [];
- },
-
- /**
- * @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
- * @param {!mojom.VpnProvider} provider
- * @return {boolean}
- * @private
- */
- haveThirdPartyVpnNetwork_: function(thirdPartyVpns, provider) {
- const list = this.getThirdPartyVpnNetworks_(thirdPartyVpns, provider);
- return !!list.length;
- },
-
- /**
- * Event triggered when a network list item is selected.
- * @param {!{target: HTMLElement, detail: !OncMojo.NetworkStateProperties}} e
- * @private
- */
- onNetworkSelected_: function(e) {
- assert(this.globalPolicy);
- assert(this.defaultNetwork !== undefined);
- const networkState = e.detail;
- e.target.blur();
- if (this.canAttemptConnection_(networkState)) {
- this.fire('network-connect', {networkState: networkState});
- return;
- }
- this.fire('show-detail', networkState);
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} state The network state.
- * @return {boolean}
- * @private
- */
- isBlockedByPolicy_: function(state) {
- if (state.type != mojom.NetworkType.kWiFi ||
- this.isPolicySource(state.source) || !this.globalPolicy) {
- return false;
- }
- return !!this.globalPolicy.allowOnlyPolicyNetworksToConnect ||
- (!!this.globalPolicy.allowOnlyPolicyNetworksToConnectIfAvailable &&
- !!this.deviceState && !!this.deviceState.managedNetworkAvailable) ||
- (!!this.globalPolicy.blockedHexSsids &&
- this.globalPolicy.blockedHexSsids.includes(
- state.typeState.wifi.hexSsid));
- },
-
- /**
- * Determines whether or not it is possible to attempt a connection to the
- * provided network (e.g., whether it's possible to connect or configure the
- * network for connection).
- * @param {!OncMojo.NetworkStateProperties} state The network state.
- * @private
- */
- canAttemptConnection_: function(state) {
- if (state.connectionState != mojom.ConnectionStateType.kNotConnected) {
- return false;
- }
- if (this.isBlockedByPolicy_(state)) {
- return false;
- }
- if (state.type == mojom.NetworkType.kVPN &&
- (!this.defaultNetwork ||
- !OncMojo.connectionStateIsConnected(
- this.defaultNetwork.connectionState))) {
- return false;
- }
- // Cellular networks do not have a configuration flow, so it's not possible
- // to attempt a connection if the network is not conncetable.
- if (state.type == mojom.NetworkType.kCellular && !state.connectable) {
- return false;
- }
- return true;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
- * @return {boolean}
- * @private
- */
- tetherToggleIsVisible_: function(deviceState, tetherDeviceState) {
- return !!deviceState && deviceState.type == mojom.NetworkType.kCellular &&
- !!tetherDeviceState;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
- * @return {boolean}
- * @private
- */
- tetherToggleIsEnabled_: function(deviceState, tetherDeviceState) {
- return this.tetherToggleIsVisible_(deviceState, tetherDeviceState) &&
- this.enableToggleIsEnabled_(tetherDeviceState) &&
- tetherDeviceState.deviceState !=
- chromeos.networkConfig.mojom.DeviceStateType.kUninitialized;
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onTetherEnabledChange_: function(event) {
- this.fire('device-enabled-toggled', {
- enabled: !this.deviceIsEnabled_(this.tetherDeviceState),
- type: mojom.NetworkType.kTether,
- });
- event.stopPropagation();
- },
-
- /**
- * @param {string} typeString
- * @param {OncMojo.DeviceStateProperties} device
- * @return {boolean}
- * @private
- */
- matchesType_: function(typeString, device) {
- return device &&
- device.type == OncMojo.getNetworkTypeFromString(typeString);
- },
-
- /**
- * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
- * @return {boolean}
- * @private
- */
- shouldShowNetworkList_: function(networkStateList) {
- return networkStateList.length > 0;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
- * @return {string}
- * @private
- */
- getNoNetworksInnerHtml_: function(deviceState, tetherDeviceState) {
- const type = deviceState.type;
- if (type == mojom.NetworkType.kTether ||
- (type == mojom.NetworkType.kCellular && this.tetherDeviceState)) {
- return this.i18nAdvanced('internetNoNetworksMobileData');
- }
-
- if (type == mojom.NetworkType.kVPN) {
- return this.i18n('internetNoNetworks');
- }
-
- // If a scan has not yet completed since the device was last enabled, it may
- // be the case that scan results are still in the process of arriving, so
- // display a message stating that scanning is in progress. If a scan has
- // already completed and there are still no networks present, this implies
- // that there has been sufficient time to find a network, so display a
- // messages stating that there are no networks. See https://crbug.com/974169
- // for more details.
- return this.hasCompletedScanSinceLastEnabled_ ?
- this.i18n('internetNoNetworks') :
- this.i18n('networkScanningLabel');
- },
-
- /**
- * @param {!Array<string>} notificationsDisabledDeviceNames
- * @return {boolean}
- * @private
- */
- showGmsCoreNotificationsSection_: function(notificationsDisabledDeviceNames) {
- return notificationsDisabledDeviceNames.length > 0;
- },
-
- /**
- * @param {!Array<string>} notificationsDisabledDeviceNames
- * @return {string}
- * @private
- */
- getGmsCoreNotificationsDevicesString_: function(
- notificationsDisabledDeviceNames) {
- if (notificationsDisabledDeviceNames.length == 1) {
- return this.i18n(
- 'gmscoreNotificationsOneDeviceSubtitle',
- notificationsDisabledDeviceNames[0]);
- }
-
- if (notificationsDisabledDeviceNames.length == 2) {
- return this.i18n(
- 'gmscoreNotificationsTwoDevicesSubtitle',
- notificationsDisabledDeviceNames[0],
- notificationsDisabledDeviceNames[1]);
- }
-
- return this.i18n('gmscoreNotificationsManyDevicesSubtitle');
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.html b/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.html
deleted file mode 100644
index f44b433b079..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.html
+++ /dev/null
@@ -1,103 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_proxy.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior_mojo.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_indicator_mojo.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="internet_shared_css.html">
-
-<dom-module id="network-proxy-section">
- <template>
- <style include="internet-shared cr-hidden-style iron-flex
- iron-flex-alignment">
- cr-policy-network-indicator-mojo {
- margin-inline-end: 10px;
- }
-
- extension-controlled-indicator {
- margin-inline-start: 0;
- width: 100%;
- }
-
- .settings-box:first-of-type {
- border-top: none;
- }
- </style>
-
- <!-- Policy indicator. Only one dom-if below will be shown. -->
- <template is="dom-if"
- if="[[shouldShowNetworkPolicyIndicator_(managedProperties)]]">
- <div class="settings-box">
- <div class="layout horizontal center">
- <cr-policy-network-indicator-mojo
- property="[[managedProperties.proxySettings.type]]"
- no-extension-indicator>
- </cr-policy-network-indicator-mojo>
- <div>$i18n{networkProxyEnforcedPolicy}</div>
- </div>
- </div>
- </template>
- <template is="dom-if"
- if="[[shouldShowExtensionIndicator_(managedProperties)]]">
- <div class="settings-box">
- <extension-controlled-indicator
- extension-id="[[prefs.proxy.extensionId]]"
- extension-name="[[prefs.proxy.controlledByName]]"
- extension-can-be-disabled="[[prefs.proxy.extensionCanBeDisabled]]">
- </extension-controlled-indicator>
- </div>
- </template>
-
- <!-- Allow shared proxies -->
- <settings-toggle-button id="allowShared" class="continuation indented"
- hidden$="[[!shouldShowAllowShared_(managedProperties.source)]]"
- pref="{{prefs.settings.use_shared_proxies}}"
- label="$i18n{networkProxyAllowShared}"
- on-settings-boolean-control-change="onAllowSharedProxiesChange_"
- no-set-pref>
- </settings-toggle-button>
-
- <div class="settings-box single-column stretch continuation indented">
- <network-proxy editable
- managed-properties="[[managedProperties]]"
- use-shared-proxies="[[useSharedProxies_]]">
- </network-proxy>
- </div>
-
- <!-- Confirm Allow shared proxies dialog -->
- <cr-dialog id="confirmAllowSharedDialog"
- close-text="$i18n{close}" on-cancel="onAllowSharedDialogCancel_"
- on-close="onAllowSharedDialogClose_">
- <div slot="title">
- [[getAllowSharedDialogTitle_(prefs.settings.use_shared_proxies.value,
- '$i18n{networkProxyAllowSharedEnableWarningTitle}',
- '$i18n{networkProxyAllowSharedDisableWarningTitle}')]]
- </div>
- <div slot="body">
- $i18n{networkProxyAllowSharedWarningMessage}
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button"
- on-click="onAllowSharedDialogCancel_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button"
- on-click="onAllowSharedDialogConfirm_">
- $i18n{confirm}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="network_proxy_section.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.js b/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.js
deleted file mode 100644
index f04803663de..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.js
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer element hosting <network-proxy> in the internet
- * detail page. This element is responsible for setting 'Allow proxies for
- * shared networks'.
- */
-(function() {
-'use strict';
-
-const mojom = chromeos.networkConfig.mojom;
-
-Polymer({
- is: 'network-proxy-section',
-
- behaviors: [
- CrPolicyNetworkBehaviorMojo,
- I18nBehavior,
- PrefsBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /** @private {!chromeos.networkConfig.mojom.ManagedProperties|undefined} */
- managedProperties: Object,
-
- /**
- * Reflects prefs.settings.use_shared_proxies for data binding.
- * @private
- */
- useSharedProxies_: Boolean,
- },
-
- observers: [
- 'useSharedProxiesChanged_(prefs.settings.use_shared_proxies.value)',
- ],
-
- /** @protected settings.RouteObserverBehavior */
- currentRouteChanged: function(newRoute) {
- if (newRoute == settings.routes.NETWORK_DETAIL) {
- /** @type {NetworkProxyElement} */ (this.$$('network-proxy')).reset();
- }
- },
-
- /** @private */
- useSharedProxiesChanged_: function() {
- const pref = this.getPref('settings.use_shared_proxies');
- this.useSharedProxies_ = !!pref && !!pref.value;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isShared_: function() {
- return this.managedProperties.source == mojom.OncSource.kDevice ||
- this.managedProperties.source == mojom.OncSource.kDevicePolicy;
- },
-
- /**
- * @return {!mojom.ManagedString|undefined}
- * @private
- */
- getProxySettingsTypeProperty_: function() {
- const proxySettings = this.managedProperties.proxySettings;
- return proxySettings ? proxySettings.type : undefined;
- },
-
- /**
- * @param {boolean} allowShared
- * @param {string} enableStr
- * @param {string} disableStr
- * @return {string}
- * @private
- */
- getAllowSharedDialogTitle_: function(allowShared, enableStr, disableStr) {
- return allowShared ? disableStr : enableStr;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowNetworkPolicyIndicator_: function() {
- const property = this.getProxySettingsTypeProperty_();
- return !!property && !this.isExtensionControlled(property) &&
- this.isNetworkPolicyEnforced(property);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowExtensionIndicator_: function() {
- const property = this.getProxySettingsTypeProperty_();
- return !!property && this.isExtensionControlled(property);
- },
-
- /**
- * @param {!OncMojo.ManagedProperty} property
- * @return {boolean}
- * @private
- */
- shouldShowAllowShared_: function(property) {
- if (!this.isShared_()) {
- return false;
- }
- // We currently do not accurately determine the source if the policy
- // controlling the proxy setting, so always show the 'allow shared'
- // toggle for shared networks. http://crbug.com/662529.
- return true;
- },
-
- /**
- * Handles the change event for the shared proxy checkbox. Shows a
- * confirmation dialog.
- * @param {!Event} event
- * @private
- */
- onAllowSharedProxiesChange_: function(event) {
- this.$.confirmAllowSharedDialog.showModal();
- },
-
- /**
- * Handles the shared proxy confirmation dialog 'Confirm' button.
- * @private
- */
- onAllowSharedDialogConfirm_: function() {
- /** @type {!SettingsCheckboxElement} */ (this.$.allowShared)
- .sendPrefChange();
- this.$.confirmAllowSharedDialog.close();
- },
-
- /**
- * Handles the shared proxy confirmation dialog 'Cancel' button or a cancel
- * event.
- * @private
- */
- onAllowSharedDialogCancel_: function() {
- /** @type {!SettingsCheckboxElement} */ (this.$.allowShared)
- .resetToPrefValue();
- this.$.confirmAllowSharedDialog.close();
- },
-
- /** @private */
- onAllowSharedDialogClose_: function() {
- this.$.allowShared.focus();
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary.html b/chromium/chrome/browser/resources/settings/internet_page/network_summary.html
deleted file mode 100644
index 2ceba972978..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/network_summary.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="network_summary_item.html">
-
-<dom-module id="network-summary">
- <template>
- <div id="summary">
- <template is="dom-repeat" items="[[activeNetworkStates_]]">
- <network-summary-item id="[[getTypeString_(item)]]"
- active-network-state="[[item]]"
- device-state="[[get(item.type, deviceStates)]]"
- network-state-list="[[get(item.type, networkStateLists_)]]"
- tether-device-state="[[get('Tether', deviceStates)]]">
- </network-summary-item>
- </template>
- </div>
- </template>
- <script src="network_summary.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary.js b/chromium/chrome/browser/resources/settings/internet_page/network_summary.js
deleted file mode 100644
index bc02417ee3e..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/network_summary.js
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer element for displaying a summary of network states
- * by type: Ethernet, WiFi, Cellular, and VPN.
- */
-
-(function() {
-
-const mojom = chromeos.networkConfig.mojom;
-
-Polymer({
- is: 'network-summary',
-
- behaviors: [
- CrNetworkListenerBehavior,
- ],
-
- properties: {
- /**
- * Highest priority connected network or null. Set here to update
- * internet-page which updates internet-subpage and internet-detail-page.
- * @type {?OncMojo.NetworkStateProperties}
- */
- defaultNetwork: {
- type: Object,
- value: null,
- notify: true,
- },
-
- /**
- * The device state for each network device type. We initialize this to
- * include a disabled WiFi type since WiFi is always present. This reduces
- * the amount of visual change on first load.
- * @private {!Object<!OncMojo.DeviceStateProperties>}
- */
- deviceStates: {
- type: Object,
- value: function() {
- const result = {};
- result[chromeos.networkConfig.mojom.NetworkType.kWiFi] = {
- deviceState: chromeos.networkConfig.mojom.DeviceStateType.kDisabled,
- type: chromeos.networkConfig.mojom.NetworkType.kWiFi,
- };
- return result;
- },
- notify: true,
- },
-
- /**
- * Array of active network states, one per device type. Initialized to
- * include a default WiFi state (see deviceStates comment).
- * @private {!Array<!OncMojo.NetworkStateProperties>}
- */
- activeNetworkStates_: {
- type: Array,
- value: function() {
- return [OncMojo.getDefaultNetworkState(mojom.NetworkType.kWiFi)];
- },
- },
-
- /**
- * List of network state data for each network type.
- * @private {!Object<!Array<!OncMojo.NetworkStateProperties>>}
- */
- networkStateLists_: {
- type: Object,
- value: function() {
- const result = {};
- result[mojom.NetworkType.kWiFi] = [];
- return result;
- },
- },
- },
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /**
- * Set of GUIDs identifying active networks, one for each type.
- * @private {?Set<string>}
- */
- activeNetworkIds_: null,
-
- /** @override */
- created: function() {
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /** @override */
- attached: function() {
- this.getNetworkLists_();
- },
-
- /**
- * CrosNetworkConfigObserver impl
- * Updates any matching existing active networks. Note: newly active networks
- * will trigger onNetworkStateListChanged which triggers getNetworkLists_.
- * @param {!Array<OncMojo.NetworkStateProperties>} networks
- */
- onActiveNetworksChanged: function(networks) {
- if (!this.activeNetworkIds_) {
- // Initial list of networks not received yet.
- return;
- }
- networks.forEach(network => {
- const index = this.activeNetworkStates_.findIndex(
- state => state.guid == network.guid);
- if (index != -1) {
- this.set(['activeNetworkStates_', index], network);
- }
- });
- },
-
- /** CrosNetworkConfigObserver impl */
- onNetworkStateListChanged: function() {
- this.getNetworkLists_();
- },
-
- /** CrosNetworkConfigObserver impl */
- onDeviceStateListChanged: function() {
- this.getNetworkLists_();
- },
-
- /**
- * Requests the list of device states and network states from Chrome.
- * Updates deviceStates, activeNetworkStates, and networkStateLists once the
- * results are returned from Chrome.
- * @private
- */
- getNetworkLists_: function() {
- // First get the device states.
- this.networkConfig_.getDeviceStateList().then(response => {
- // Second get the network states.
- this.getNetworkStates_(response.result);
- });
- },
-
- /**
- * Requests the list of network states from Chrome. Updates
- * activeNetworkStates and networkStateLists once the results are returned
- * from Chrome.
- * @param {!Array<!OncMojo.DeviceStateProperties>} deviceStateList
- * @private
- */
- getNetworkStates_: function(deviceStateList) {
- const filter = {
- filter: chromeos.networkConfig.mojom.FilterType.kVisible,
- limit: chromeos.networkConfig.mojom.NO_LIMIT,
- networkType: mojom.NetworkType.kAll,
- };
- this.networkConfig_.getNetworkStateList(filter).then(response => {
- this.updateNetworkStates_(response.result, deviceStateList);
- });
- },
-
- /**
- * Called after network states are received from getNetworks.
- * @param {!Array<!OncMojo.NetworkStateProperties>} networkStates The state
- * properties for all visible networks.
- * @param {!Array<!OncMojo.DeviceStateProperties>} deviceStateList
- * @private
- */
- updateNetworkStates_: function(networkStates, deviceStateList) {
- const newDeviceStates = {};
- for (const device of deviceStateList) {
- newDeviceStates[device.type] = device;
- }
-
- const orderedNetworkTypes = [
- mojom.NetworkType.kEthernet,
- mojom.NetworkType.kWiFi,
- mojom.NetworkType.kCellular,
- mojom.NetworkType.kTether,
- mojom.NetworkType.kVPN,
- ];
-
- // Clear any current networks.
- const activeNetworkStatesByType =
- /** @type {!Map<mojom.NetworkType, !OncMojo.NetworkStateProperties>} */
- (new Map);
-
- // Complete list of states by type.
- const newNetworkStateLists = {};
- for (const type of orderedNetworkTypes) {
- newNetworkStateLists[type] = [];
- }
-
- let firstConnectedNetwork = null;
- networkStates.forEach(function(networkState) {
- const type = networkState.type;
- if (!activeNetworkStatesByType.has(type)) {
- activeNetworkStatesByType.set(type, networkState);
- if (!firstConnectedNetwork &&
- networkState.type != mojom.NetworkType.kVPN &&
- OncMojo.connectionStateIsConnected(networkState.connectionState)) {
- firstConnectedNetwork = networkState;
- }
- }
- newNetworkStateLists[type].push(networkState);
- }, this);
-
- this.defaultNetwork = firstConnectedNetwork;
-
- // Create a VPN entry in deviceStates if there are any VPN networks.
- if (newNetworkStateLists[mojom.NetworkType.kVPN].length > 0) {
- newDeviceStates[mojom.NetworkType.kVPN] = {
- type: mojom.NetworkType.kVPN,
- deviceState: chromeos.networkConfig.mojom.DeviceStateType.kEnabled,
- };
- }
-
- // Push the active networks onto newActiveNetworkStates in order based on
- // device priority, creating an empty state for devices with no networks.
- const newActiveNetworkStates = [];
- this.activeNetworkIds_ = new Set;
- for (const type of orderedNetworkTypes) {
- const device = newDeviceStates[type];
- if (!device) {
- continue; // The technology for this device type is unavailable.
- }
-
- // If both 'Tether' and 'Cellular' technologies exist, merge the network
- // lists and do not add an active network for 'Tether' so that there is
- // only one 'Mobile data' section / subpage.
- if (type == mojom.NetworkType.kTether &&
- newDeviceStates[mojom.NetworkType.kCellular]) {
- newNetworkStateLists[mojom.NetworkType.kCellular] =
- newNetworkStateLists[mojom.NetworkType.kCellular].concat(
- newNetworkStateLists[mojom.NetworkType.kTether]);
- continue;
- }
-
- // Note: The active state for 'Cellular' may be a Tether network if both
- // types are enabled but no Cellular network exists (edge case).
- const networkState =
- this.getActiveStateForType_(activeNetworkStatesByType, type);
- if (networkState.source == mojom.OncSource.kNone &&
- device.deviceState == mojom.DeviceStateType.kProhibited) {
- // Prohibited technologies are enforced by the device policy.
- networkState.source =
- chromeos.networkConfig.mojom.OncSource.kDevicePolicy;
- }
- newActiveNetworkStates.push(networkState);
- this.activeNetworkIds_.add(networkState.guid);
- }
-
- this.deviceStates = newDeviceStates;
- this.networkStateLists_ = newNetworkStateLists;
- // Set activeNetworkStates last to rebuild the dom-repeat.
- this.activeNetworkStates_ = newActiveNetworkStates;
- },
-
- /**
- * Returns the active network state for |type| or a default network state.
- * If there is no 'Cellular' network, return the active 'Tether' network if
- * any since the two types are represented by the same section / subpage.
- * @param {!Map<mojom.NetworkType, !OncMojo.NetworkStateProperties>}
- * activeStatesByType
- * @param {!mojom.NetworkType} type
- * @return {!OncMojo.NetworkStateProperties|undefined}
- * @private
- */
- getActiveStateForType_: function(activeStatesByType, type) {
- let activeState = activeStatesByType.get(type);
- if (!activeState && type == mojom.NetworkType.kCellular) {
- activeState = activeStatesByType.get(mojom.NetworkType.kTether);
- }
- return activeState || OncMojo.getDefaultNetworkState(type);
- },
-
- /**
- * Provides an id string for summary items. Used in tests.
- * @param {!OncMojo.NetworkStateProperties} network
- * @return {string}
- * @private
- */
- getTypeString_: function(network) {
- return OncMojo.getNetworkTypeString(network.type);
- },
-
- /**
- * @param {!Object<!OncMojo.DeviceStateProperties>} deviceStates
- * @return {!OncMojo.DeviceStateProperties|undefined}
- * @private
- */
- getTetherDeviceState_: function(deviceStates) {
- return this.deviceStates[mojom.NetworkType.kTether];
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html
deleted file mode 100644
index 4c6312d3dc0..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html
+++ /dev/null
@@ -1,88 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_siminfo.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_icon.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_strings.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior_mojo.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-
-<dom-module id="network-summary-item">
- <template>
- <style include="internet-shared iron-flex">
- network-siminfo {
- padding-inline-start: var(--cr-section-padding);
- }
-
- #outerBox {
- padding: 0 var(--cr-section-padding);
- }
-
- #details {
- align-items: center;
- display: flex;
- flex: auto;
- }
-
- #networkState {
- color: var(--cr-secondary-text-color);
- font-size: inherit;
- }
- </style>
- <div id="outerBox" class="settings-box two-line">
- <div actionable class="flex layout horizontal center"
- on-click="onShowDetailsTap_">
- <div id="details" no-flex$="[[showSimInfo_(deviceState)]]">
- <cr-network-icon
- show-technology-badge="[[showTechnologyBadge_]]"
- network-state="[[activeNetworkState]]"
- device-state="[[deviceState]]">
- </cr-network-icon>
- <div class="flex settings-box-text">
- <div id="networkTitleText">
- [[getTitleText_(activeNetworkState)]]
- </div>
- <div id="networkState">
- [[getNetworkStateText_(activeNetworkState, deviceState)]]
- </div>
- </div>
- </div>
-
- <template is="dom-if" if="[[showSimInfo_(deviceState)]]" restamp>
- <network-siminfo device-state="[[deviceState]]" on-click="doNothing_">
- </network-siminfo>
- </template>
-
- <template is="dom-if" if="[[showPolicyIndicator_(activeNetworkState)]]">
- <cr-policy-indicator indicator-type="[[getIndicatorTypeForSource(
- activeNetworkState.source)]]" on-click="doNothing_">
- </cr-policy-indicator>
- </template>
-
- <template is="dom-if" if="[[showDetailsIsVisible_(activeNetworkState,
- deviceState, networkStateList)]]">
- <cr-icon-button class="subpage-arrow"
- aria-label$="[[getDetailsA11yString_(activeNetworkState,
- deviceState, networkStateList)]]"></cr-icon-button>
- </template>
- </div>
-
- <template is="dom-if" if="[[enableToggleIsVisible_(deviceState)]]">
- <div class="separator"></div>
- <cr-toggle id="deviceEnabledButton"
- aria-label$="[[getToggleA11yString_(deviceState)]]"
- checked="[[deviceIsEnabled_(deviceState)]]"
- disabled="[[!enableToggleIsEnabled_(deviceState)]]"
- on-change="onDeviceEnabledChange_">
- </cr-toggle>
- </template>
- </div>
- </template>
- <script src="network_summary_item.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js
deleted file mode 100644
index 7b26f618a41..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js
+++ /dev/null
@@ -1,398 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer element for displaying the network state for a specific
- * type and a list of networks for that type. NOTE: It both Cellular and Tether
- * technologies are available, they are combined into a single 'Mobile data'
- * section. See crbug.com/726380.
- */
-
-(function() {
-
-const mojom = chromeos.networkConfig.mojom;
-
-Polymer({
- is: 'network-summary-item',
-
- behaviors: [
- CrPolicyNetworkBehaviorMojo,
- I18nBehavior,
- ],
-
- properties: {
- /**
- * Device state for the network type. This might briefly be undefined if
- * a device becomes unavailable.
- * @type {!OncMojo.DeviceStateProperties|undefined}
- */
- deviceState: {
- type: Object,
- notify: true,
- },
-
- /**
- * If both Cellular and Tether technologies exist, we combine the
- * sections and set this to the device state for Tether.
- * @type {!OncMojo.DeviceStateProperties|undefined}
- */
- tetherDeviceState: Object,
-
- /**
- * Network state for the active network.
- * @type {!OncMojo.NetworkStateProperties|undefined}
- */
- activeNetworkState: Object,
-
- /**
- * List of all network state data for the network type.
- * @type {!Array<!OncMojo.NetworkStateProperties>}
- */
- networkStateList: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * Title line describing the network type to appear in the row's top
- * line. If it is undefined, the title text is a default from
- * CrOncStrings (see this.getTitleText_() below).
- * @type {string|undefined}
- */
- networkTitleText: String,
-
- /**
- * Whether to show technology badge on mobile network icon.
- * @private
- */
- showTechnologyBadge_: {
- type: Boolean,
- value: function() {
- return loadTimeData.valueExists('showTechnologyBadge') &&
- loadTimeData.getBoolean('showTechnologyBadge');
- }
- },
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} activeNetworkState
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {string}
- * @private
- */
- getNetworkStateText_: function(activeNetworkState, deviceState) {
- const stateText =
- this.getConnectionStateText_(activeNetworkState, deviceState);
- if (stateText) {
- return stateText;
- }
- // No network state, use device state.
- if (deviceState) {
- // Type specific scanning or initialization states.
- if (deviceState.type == mojom.NetworkType.kCellular) {
- if (deviceState.scanning) {
- return this.i18n('internetMobileSearching');
- }
- if (deviceState.deviceState == mojom.DeviceStateType.kUninitialized) {
- return this.i18n('internetDeviceInitializing');
- }
- } else if (deviceState.type == mojom.NetworkType.kTether) {
- if (deviceState.deviceState == mojom.DeviceStateType.kUninitialized) {
- return this.i18n('tetherEnableBluetooth');
- }
- }
- // Enabled or enabling states.
- if (deviceState.deviceState == mojom.DeviceStateType.kEnabled) {
- if (this.networkStateList.length > 0) {
- return CrOncStrings.networkListItemNotConnected;
- }
- return CrOncStrings.networkListItemNoNetwork;
- }
- if (deviceState.deviceState == mojom.DeviceStateType.kEnabling) {
- return this.i18n('internetDeviceEnabling');
- }
- }
- // No device or unknown device state, use 'off'.
- return this.i18n('deviceOff');
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} networkState
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {string}
- * @private
- */
- getConnectionStateText_: function(networkState, deviceState) {
- if (!networkState || !networkState.guid) {
- return '';
- }
- const connectionState = networkState.connectionState;
- const name = OncMojo.getNetworkStateDisplayName(networkState);
- if (OncMojo.connectionStateIsConnected(connectionState)) {
- // Ethernet networks always have the display name 'Ethernet' so we use the
- // state text 'Connected' to avoid repeating the label in the sublabel.
- // See http://crbug.com/989907 for details.
- return networkState.type == mojom.NetworkType.kEthernet ?
- CrOncStrings.networkListItemConnected :
- name;
- }
- if (connectionState == mojom.ConnectionStateType.kConnecting) {
- return name ?
- CrOncStrings.networkListItemConnectingTo.replace('$1', name) :
- CrOncStrings.networkListItemConnecting;
- }
- if (networkState.type == mojom.NetworkType.kCellular && deviceState &&
- deviceState.scanning) {
- return this.i18n('internetMobileSearching');
- }
- return CrOncStrings.networkListItemNotConnected;
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} activeNetworkState
- * @return {boolean}
- * @private
- */
- showPolicyIndicator_: function(activeNetworkState) {
- return (activeNetworkState !== undefined &&
- OncMojo.connectionStateIsConnected(
- activeNetworkState.connectionState)) ||
- this.isPolicySource(activeNetworkState.source);
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean}
- * @private
- */
- showSimInfo_: function(deviceState) {
- if (!deviceState || deviceState.type != mojom.NetworkType.kCellular) {
- return false;
- }
- return this.simLockedOrAbsent_(deviceState);
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties} deviceState
- * @return {boolean}
- * @private
- */
- simLockedOrAbsent_: function(deviceState) {
- if (this.deviceIsEnabled_(deviceState)) {
- return false;
- }
- if (!deviceState.simLockStatus) {
- return false;
- }
- if (deviceState.simLockStatus.simAbsent) {
- return true;
- }
- const simLockType = deviceState.simLockStatus.lockType;
- return simLockType == 'sim-pin' || simLockType == 'sim-puk';
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean} Whether or not the device state is enabled.
- * @private
- */
- deviceIsEnabled_: function(deviceState) {
- return !!deviceState &&
- deviceState.deviceState == mojom.DeviceStateType.kEnabled;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean}
- * @private
- */
- enableToggleIsVisible_: function(deviceState) {
- if (!deviceState) {
- return false;
- }
- switch (deviceState.type) {
- case mojom.NetworkType.kEthernet:
- case mojom.NetworkType.kVPN:
- return false;
- case mojom.NetworkType.kTether:
- return true;
- case mojom.NetworkType.kWiFi:
- return deviceState.deviceState != mojom.DeviceStateType.kUninitialized;
- case mojom.NetworkType.kCellular:
- return deviceState.deviceState !=
- mojom.DeviceStateType.kUninitialized &&
- !this.simLockedOrAbsent_(deviceState);
- }
- assertNotReached();
- return false;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {boolean}
- * @private
- */
- enableToggleIsEnabled_: function(deviceState) {
- return this.enableToggleIsVisible_(deviceState) &&
- deviceState.deviceState != mojom.DeviceStateType.kProhibited &&
- deviceState.deviceState != mojom.DeviceStateType.kUninitialized;
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @return {string}
- * @private
- */
- getToggleA11yString_: function(deviceState) {
- if (!this.enableToggleIsVisible_(deviceState)) {
- return '';
- }
- switch (deviceState.type) {
- case mojom.NetworkType.kTether:
- case mojom.NetworkType.kCellular:
- return this.i18n('internetToggleMobileA11yLabel');
- case mojom.NetworkType.kWiFi:
- return this.i18n('internetToggleWiFiA11yLabel');
- }
- assertNotReached();
- return '';
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} activeNetworkState
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
- * @return {boolean}
- * @private
- */
- showDetailsIsVisible_: function(
- activeNetworkState, deviceState, networkStateList) {
- return this.deviceIsEnabled_(deviceState) &&
- (!!activeNetworkState.guid || networkStateList.length > 0);
- },
-
- /**
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
- * @return {boolean}
- * @private
- */
- shouldShowSubpage_: function(deviceState, networkStateList) {
- if (!deviceState) {
- return false;
- }
- const type = deviceState.type;
- if (type == mojom.NetworkType.kTether ||
- (type == mojom.NetworkType.kCellular && this.tetherDeviceState)) {
- // The "Mobile data" subpage should always be shown if Tether is
- // available, even if there are currently no associated networks.
- return true;
- }
- let minlen;
- if (type == mojom.NetworkType.kVPN) {
- // VPN subpage provides provider info so show if there are any networks.
- minlen = 1;
- } else if (type == mojom.NetworkType.kWiFi) {
- // WiFi subpage includes 'Known Networks' so always show, even if the
- // technology is still enabling / scanning, or none are visible.
- minlen = 0;
- } else {
- // By default, only show the subpage if there are 2+ networks
- minlen = 2;
- }
- return networkStateList.length >= minlen;
- },
-
- /**
- * @param {!Event} event The enable button event.
- * @private
- */
- onShowDetailsTap_: function(event) {
- if (!this.deviceIsEnabled_(this.deviceState)) {
- if (this.enableToggleIsEnabled_(this.deviceState)) {
- const type = this.deviceState.type;
- this.fire('device-enabled-toggled', {enabled: true, type: type});
- }
- } else if (this.shouldShowSubpage_(
- this.deviceState, this.networkStateList)) {
- this.fire('show-networks', this.deviceState.type);
- } else if (this.activeNetworkState.guid) {
- this.fire('show-detail', this.activeNetworkState);
- } else if (this.networkStateList.length > 0) {
- this.fire('show-detail', this.networkStateList[0]);
- }
- event.stopPropagation();
- },
-
- /**
- * @param {!OncMojo.NetworkStateProperties} activeNetworkState
- * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
- * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
- * @return {string}
- * @private
- */
- getDetailsA11yString_: function(
- activeNetworkState, deviceState, networkStateList) {
- if (!this.shouldShowSubpage_(deviceState, networkStateList)) {
- if (activeNetworkState.guid) {
- return OncMojo.getNetworkStateDisplayName(activeNetworkState);
- }
- if (networkStateList.length > 0) {
- return OncMojo.getNetworkStateDisplayName(networkStateList[0]);
- }
- }
- return this.getNetworkTypeString_(deviceState.type);
- },
-
- /**
- * Event triggered when the enable button is toggled.
- * @param {!Event} event
- * @private
- */
- onDeviceEnabledChange_: function(event) {
- assert(this.deviceState);
- const deviceIsEnabled = this.deviceIsEnabled_(this.deviceState);
- this.fire(
- 'device-enabled-toggled',
- {enabled: !deviceIsEnabled, type: this.deviceState.type});
- },
-
- /**
- * @return {string}
- * @private
- */
- getTitleText_: function() {
- assert(CrOncStrings);
- return this.networkTitleText ||
- this.getNetworkTypeString_(this.activeNetworkState.type);
- },
-
- /**
- * Make sure events in embedded components do not propagate to onDetailsTap_.
- * @param {!Event} event
- * @private
- */
- doNothing_: function(event) {
- event.stopPropagation();
- },
-
- /**
- * @param {!mojom.NetworkType} type
- * @return {string}
- * @private
- */
- getNetworkTypeString_: function(type) {
- // The shared Cellular/Tether subpage is referred to as "Mobile".
- // TODO(khorimoto): Remove once Cellular/Tether are split into their own
- // sections.
- if (type == mojom.NetworkType.kCellular ||
- type == mojom.NetworkType.kTether) {
- type = mojom.NetworkType.kMobile;
- }
- return this.i18n('OncType' + OncMojo.getNetworkTypeString(type));
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html b/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html
deleted file mode 100644
index 22d07f9ecb5..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html
+++ /dev/null
@@ -1,123 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_icon.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../chromeos/os_icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="tether-connection-dialog">
- <template>
- <style include="settings-shared iron-flex">
- [slot=title] {
- margin-top: 9px;
- }
-
- [slot=body] {
- border-top: solid 2px rgba(0, 0, 0, .14);
- }
-
- [slot=body] > * {
- margin-inline-start: 5px;
- }
-
- iron-icon {
- --iron-icon-fill-color: #4285F4;
- }
-
- #host-device-text-container {
- display: flex;
- flex-direction: column;
- margin-inline-start: 18px;
- }
-
- #availability-title {
- color: black;
- margin-top: 5px;
- opacity: 0.54;
- }
-
- #host-device-container {
- align-items: center;
- display: flex;
- margin-top: 12px;
- min-height: 46px;
- }
-
- #tether-explanation,
- #tether-carrier-warning,
- #tether-description-title {
- margin-top: var(--cr-section-vertical-margin);
- }
-
- #tether-carrier-warning {
- font-weight: 600;
- }
-
- #tether-description-list {
- padding-inline-start: 16px;
- }
-
- #host-device-lost-container {
- color: var(--google-red-500);
- font-weight: 500;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{tetherConnectionDialogTitle}</div>
- <div slot="body">
- <span id="availability-title">
- $i18n{tetherConnectionAvailableDeviceTitle}
- </span>
- <div id="host-device-container">
- <!-- TODO(hsuregan): Add an a11y label. -->
- <iron-icon icon="[[getSignalStrengthIconName_(managedProperties)]]">
- </iron-icon>
- <div id="host-device-text-container">
- <span id="host-device-text-name">
- [[getDeviceName_(managedProperties)]]
- </span>
- <span id="host-device-text-battery" class="secondary">
- [[getBatteryPercentageString_(managedProperties)]]
- </span>
- </div>
- <div class="flex"></div>
- <div id="host-device-lost-container" hidden$="[[!outOfRange]]">
- <iron-icon icon="os-settings:alert-device-out-of-range">
- </iron-icon>
- $i18n{tetherPhoneOutOfRange}
- </div>
- </div>
- <div id="tether-explanation">
- [[getExplanation_(managedProperties)]]
- </div>
- <div id="tether-carrier-warning">
- $i18n{tetherConnectionCarrierWarning}
- </div>
- <div id="tether-description-title">
- [[getDescriptionTitle_(managedProperties)]]
- </div>
- <ul id="tether-description-list">
- <li>$i18n{tetherConnectionDescriptionMobileData}</li>
- <li>[[getBatteryDescription_(managedProperties)]]</li>
- <li hidden$="[[!shouldShowDisconnectFromWifi_(managedProperties)]]">
- $i18n{tetherConnectionDescriptionWiFi}
- </li>
- </ul>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onNotNowTap_">
- $i18n{tetherConnectionNotNowButton}
- </cr-button>
- <cr-button id="connectButton" class="action-button"
- on-click="onConnectTap_" disabled="[[outOfRange]]">
- $i18n{tetherConnectionConnectButton}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="tether_connection_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.js b/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.js
deleted file mode 100644
index cce1a1de45c..00000000000
--- a/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.js
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-const mojom = chromeos.networkConfig.mojom;
-
-Polymer({
- is: 'tether-connection-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /** @private {!chromeos.networkConfig.mojom.ManagedProperties|undefined} */
- managedProperties: Object,
-
- /**
- * Whether the network has been lost (e.g., has gone out of range).
- * @type {boolean}
- */
- outOfRange: Boolean,
- },
-
- open: function() {
- const dialog = this.getDialog_();
- if (!dialog.open) {
- this.getDialog_().showModal();
- }
-
- this.$.connectButton.focus();
- },
-
- close: function() {
- const dialog = this.getDialog_();
- if (dialog.open) {
- dialog.close();
- }
- },
-
- /**
- * @return {!CrDialogElement}
- * @private
- */
- getDialog_: function() {
- return /** @type {!CrDialogElement} */ (this.$.dialog);
- },
-
- /** @private */
- onNotNowTap_: function() {
- this.getDialog_().cancel();
- },
-
- /**
- * Fires the 'connect-tap' event.
- * @private
- */
- onConnectTap_: function() {
- this.fire('tether-connect');
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {boolean}
- * @private
- */
- shouldShowDisconnectFromWifi_: function(managedProperties) {
- // TODO(khorimoto): Pipe through a new network property which describes
- // whether the tether host is currently connected to a Wi-Fi network. Return
- // whether it is here.
- return true;
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string} The battery percentage integer value converted to a
- * string. Note that this will not return a string with a "%" suffix.
- * @private
- */
- getBatteryPercentageAsString_: function(managedProperties) {
- return managedProperties.typeProperties.tether.batteryPercentage.toString();
- },
-
- /**
- * Retrieves an image that corresponds to signal strength of the tether host.
- * Custom icons are used here instead of a <cr-network-icon> because this
- * dialog uses a special color scheme.
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string} The name of the icon to be used to represent the network's
- * signal strength.
- */
- getSignalStrengthIconName_: function(managedProperties) {
- const signalStrength =
- managedProperties.typeProperties.tether.signalStrength;
- return 'os-settings:signal-cellular-' +
- Math.min(4, Math.max(signalStrength, 0)) + '-bar';
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string}
- * @private
- */
- getDeviceName_: function(managedProperties) {
- return managedProperties ? OncMojo.getNetworkName(managedProperties) : '';
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string}
- * @private
- */
- getBatteryPercentageString_: function(managedProperties) {
- return this.i18n(
- 'tetherConnectionBatteryPercentage',
- this.getBatteryPercentageAsString_(managedProperties));
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string}
- * @private
- */
- getExplanation_: function(managedProperties) {
- return this.i18n(
- 'tetherConnectionExplanation',
- HTMLEscape(OncMojo.getNetworkName(managedProperties)));
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string}
- * @private
- */
- getDescriptionTitle_: function(managedProperties) {
- return this.i18n(
- 'tetherConnectionDescriptionTitle',
- HTMLEscape(OncMojo.getNetworkName(managedProperties)));
- },
-
- /**
- * @param {!mojom.ManagedProperties} managedProperties
- * @return {string}
- * @private
- */
- getBatteryDescription_: function(managedProperties) {
- return this.i18n(
- 'tetherConnectionDescriptionBattery',
- this.getBatteryPercentageAsString_(managedProperties));
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.html b/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.html
deleted file mode 100644
index 9611202ce04..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.html
+++ /dev/null
@@ -1,74 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/find_shortcut_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="languages.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-add-languages-dialog">
- <template>
- <style include="settings-shared">
- #dialog-body {
- display: flex;
- flex-direction: column;
- height: 350px;
- overflow: auto;
- padding-inline-end: 0;
- }
-
- cr-search-field {
- padding-inline-end: 24px;
- }
-
- iron-list {
- flex: 1;
- }
-
- .ripple-padding {
- /* Create a little extra space for checkbox ink ripple to flow into. */
- padding-inline-start: 20px;
- }
-
- cr-checkbox::part(label-container) {
- white-space: nowrap;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{addLanguagesDialogTitle}</div>
- <div id="dialog-body" slot="body" scrollable>
- <cr-search-field label="$i18n{searchLanguages}" id="search"
- on-search-changed="onSearchChanged_"
- on-keydown="onKeydown_" autofocus>
- </cr-search-field>
- <iron-list class="ripple-padding" scroll-target="[[$$('[slot=body]')]]"
- items="[[getLanguages_(
- languages.supported, languages.enabled.*, filterValue_)]]">
- <template>
- <cr-checkbox class="list-item no-outline"
- checked="[[willAdd_(item.code)]]" tab-index="[[tabIndex]]"
- title$="[[item.nativeDisplayName]]"
- on-change="onLanguageCheckboxChange_">
- [[getDisplayText_(item)]]
- </cr-checkbox>
- </template>
- </iron-list>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelButtonTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onActionButtonTap_"
- disabled="[[disableActionButton_]]">
- $i18n{add}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="add_languages_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.js b/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.js
deleted file mode 100644
index 9d498bb4393..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.js
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-add-languages-dialog' is a dialog for enabling
- * languages.
- */
-Polymer({
- is: 'settings-add-languages-dialog',
-
- behaviors: [
- CrScrollableBehavior,
- FindShortcutBehavior,
- ],
-
- properties: {
- /** @type {!LanguagesModel|undefined} */
- languages: {
- type: Object,
- notify: true,
- },
-
- /** @type {!LanguageHelper} */
- languageHelper: Object,
-
- /** @private {!Set<string>} */
- languagesToAdd_: {
- type: Object,
- value: function() {
- return new Set();
- },
- },
-
- /** @private */
- disableActionButton_: {
- type: Boolean,
- value: true,
- },
-
- /** @private */
- filterValue_: {
- type: String,
- value: '',
- },
- },
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- // Override FindShortcutBehavior methods.
- handleFindShortcut: function(modalContextOpen) {
- // Assumes this is the only open modal.
- const searchInput = this.$.search.getSearchInput();
- searchInput.scrollIntoViewIfNeeded();
- if (!this.searchInputHasFocus()) {
- searchInput.focus();
- }
- return true;
- },
-
- // Override FindShortcutBehavior methods.
- searchInputHasFocus: function() {
- return this.$.search.getSearchInput() ==
- this.$.search.shadowRoot.activeElement;
- },
-
- /**
- * @param {!CustomEvent<string>} e
- * @private
- */
- onSearchChanged_: function(e) {
- this.filterValue_ = e.detail;
- },
-
- /**
- * @return {!Array<!chrome.languageSettingsPrivate.Language>} A list of
- * languages to be displayed.
- * @private
- */
- getLanguages_: function() {
- const filterValue =
- this.filterValue_ ? this.filterValue_.toLowerCase() : null;
- return this.languages.supported.filter(language => {
- if (!this.languageHelper.canEnableLanguage(language)) {
- return false;
- }
-
- if (filterValue === null) {
- return true;
- }
-
- return language.displayName.toLowerCase().includes(filterValue) ||
- language.nativeDisplayName.toLowerCase().includes(filterValue);
- });
- },
-
- /**
- * @param {!chrome.languageSettingsPrivate.Language} language
- * @return {string} The text to be displayed.
- * @private
- */
- getDisplayText_: function(language) {
- let displayText = language.displayName;
- // If the native name is different, add it.
- if (language.displayName != language.nativeDisplayName) {
- displayText += ' - ' + language.nativeDisplayName;
- }
- return displayText;
- },
-
- /**
- * True if the user has chosen to add this language (checked its checkbox).
- * @param {string} languageCode
- * @return {boolean}
- * @private
- */
- willAdd_: function(languageCode) {
- return this.languagesToAdd_.has(languageCode);
- },
-
- /**
- * Handler for checking or unchecking a language item.
- * @param {!{model: !{item: !chrome.languageSettingsPrivate.Language},
- * target: !Element}} e
- * @private
- */
- onLanguageCheckboxChange_: function(e) {
- // Add or remove the item to the Set. No need to worry about data binding:
- // willAdd_ is called to initialize the checkbox state (in case the
- // iron-list re-uses a previous checkbox), and the checkbox can only be
- // changed after that by user action.
- const language = e.model.item;
- if (e.target.checked) {
- this.languagesToAdd_.add(language.code);
- } else {
- this.languagesToAdd_.delete(language.code);
- }
-
- this.disableActionButton_ = !this.languagesToAdd_.size;
- },
-
- /** @private */
- onCancelButtonTap_: function() {
- this.$.dialog.close();
- },
-
- /**
- * Enables the checked languages.
- * @private
- */
- onActionButtonTap_: function() {
- this.$.dialog.close();
- this.languagesToAdd_.forEach(languageCode => {
- this.languageHelper.enableLanguage(languageCode);
- });
- },
-
- /**
- * @param {!KeyboardEvent} e
- * @private
- */
- onKeydown_: function(e) {
- // Close dialog if 'esc' is pressed and the search box is already empty.
- if (e.key == 'Escape' && !this.$.search.getValue().trim()) {
- this.$.dialog.close();
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html b/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html
deleted file mode 100644
index a6905ca7fbf..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys/iron-a11y-keys.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="../global_scroll_target_behavior.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-edit-dictionary-page">
- <template>
- <style include="settings-shared">
- :host {
- display: flex;
- flex-direction: column;
- }
-
- #newWord {
- width: 100%;
- --cr-input-width: var(--settings-input-max-width);
- }
-
- #newWord::part(row-container) {
- justify-content: normal;
- }
-
- iron-list .word {
- flex: 1;
- }
- </style>
- <div class="settings-box first">
- <iron-a11y-keys id="keys" keys="enter esc"
- on-keys-pressed="onKeysPress_"></iron-a11y-keys>
- <cr-input id="newWord" value="{{newWordValue_}}"
- placeholder="$i18n{addDictionaryWordLabel}"
- invalid="[[isWordInvalid_(newWordValue_, words_.*)]]"
- error-message="[[getErrorMessage_(newWordValue_, words_.*)]]"
- spellcheck="false">
- <cr-button on-click="onAddWordTap_" id="addWord" slot="suffix"
- disabled$="[[disableAddButton_(newWordValue_, words_.*)]]">
- $i18n{addDictionaryWordButton}
- </cr-button>
- </cr-input>
- </div>
- <div class="settings-box continuation block">
- <h2>$i18n{customDictionaryWords}</h2>
- </div>
- <div class="list-frame">
- <template is="dom-if" if="[[hasWords_]]">
- <iron-list id="list" items="[[words_]]" preserve-focus
- scroll-target="[[subpageScrollTarget]]">
- <template>
- <div class="list-item">
- <div id$="word[[index]]" class="word text-elide">[[item]]</div>
- <cr-icon-button class="icon-clear" on-click="onRemoveWordTap_"
- tabindex$="[[tabIndex]]"
- title="$i18n{deleteDictionaryWordButton}"
- aria-describedby$="word[[index]]">
- </cr-icon-button>
- </div>
- </template>
- </iron-list>
- </template>
- <div id="noWordsLabel" class="list-item" hidden$="[[hasWords_]]">
- $i18n{noCustomDictionaryWordsFound}
- </div>
- </div>
- </template>
- <script src="edit_dictionary_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js b/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js
deleted file mode 100644
index cb66b2e7b95..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-edit-dictionary-page' is a sub-page for editing
- * the "dictionary" of custom words used for spell check.
- */
-
-// Max valid word size defined in
-// https://cs.chromium.org/chromium/src/components/spellcheck/common/spellcheck_common.h?l=28
-const MAX_CUSTOM_DICTIONARY_WORD_BYTES = 99;
-
-Polymer({
- is: 'settings-edit-dictionary-page',
-
- behaviors: [settings.GlobalScrollTargetBehavior],
-
- properties: {
- /** @private {string} */
- newWordValue_: {
- type: String,
- value: '',
- },
-
- /**
- * Needed by GlobalScrollTargetBehavior.
- * @override
- */
- subpageRoute: {
- type: Object,
- value: settings.routes.EDIT_DICTIONARY,
- },
-
- /** @private {!Array<string>} */
- words_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /** @private {boolean} */
- hasWords_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @type {LanguageSettingsPrivate} */
- languageSettingsPrivate: null,
-
- /** @override */
- ready: function() {
- this.languageSettingsPrivate = settings.languageSettingsPrivateApiForTest ||
- /** @type {!LanguageSettingsPrivate} */
- (chrome.languageSettingsPrivate);
-
- this.languageSettingsPrivate.getSpellcheckWords(words => {
- this.hasWords_ = words.length > 0;
- this.words_ = words;
- });
-
- this.languageSettingsPrivate.onCustomDictionaryChanged.addListener(
- this.onCustomDictionaryChanged_.bind(this));
-
- // Add a key handler for the new-word input.
- this.$.keys.target = this.$.newWord;
- },
-
- /**
- * Adds the word in the new-word input to the dictionary.
- * @private
- */
- addWordFromInput_: function() {
- // Spaces are allowed, but removing leading and trailing whitespace.
- const word = this.getTrimmedNewWord_();
- this.newWordValue_ = '';
- if (word) {
- this.languageSettingsPrivate.addSpellcheckWord(word);
- }
- },
-
- /**
- * Check if the field is empty or invalid.
- * @return {boolean}
- * @private
- */
- disableAddButton_: function() {
- return this.getTrimmedNewWord_().length == 0 || this.isWordInvalid_();
- },
-
- /**
- * @return {string}
- * @private
- */
- getErrorMessage_: function() {
- if (this.newWordIsTooLong_()) {
- return loadTimeData.getString('addDictionaryWordLengthError');
- }
- if (this.newWordAlreadyAdded_()) {
- return loadTimeData.getString('addDictionaryWordDuplicateError');
- }
- return '';
- },
-
- /**
- * @return {string}
- * @private
- */
- getTrimmedNewWord_: function() {
- return this.newWordValue_.trim();
- },
-
- /**
- * If the word is invalid, returns true (or a message if one is provided).
- * Otherwise returns false.
- * @return {boolean}
- * @private
- */
- isWordInvalid_: function() {
- return this.newWordAlreadyAdded_() || this.newWordIsTooLong_();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- newWordAlreadyAdded_: function() {
- return this.words_.includes(this.getTrimmedNewWord_());
- },
-
- /**
- * @return {boolean}
- * @private
- */
- newWordIsTooLong_: function() {
- return this.getTrimmedNewWord_().length > MAX_CUSTOM_DICTIONARY_WORD_BYTES;
- },
-
- /**
- * Handles tapping on the Add Word button.
- */
- onAddWordTap_: function(e) {
- this.addWordFromInput_();
- this.$.newWord.focus();
- },
-
- /**
- * Handles updates to the word list. Additions are unshifted to the top
- * of the list so that users can see them easily.
- * @param {!Array<string>} added
- * @param {!Array<string>} removed
- */
- onCustomDictionaryChanged_: function(added, removed) {
- const wasEmpty = this.words_.length == 0;
-
- for (const word of removed) {
- this.arrayDelete('words_', word);
- }
-
- if (this.words_.length === 0 && added.length === 0 && !wasEmpty) {
- this.hasWords_ = false;
- }
-
- // This is a workaround to ensure the dom-if is set to true before items
- // are rendered so that focus works correctly in Polymer 2; see
- // https://crbug.com/912523.
- if (wasEmpty && added.length > 0) {
- this.hasWords_ = true;
- }
-
- for (const word of added) {
- if (!this.words_.includes(word)) {
- this.unshift('words_', word);
- }
- }
-
- // When adding a word to an _empty_ list, the template is expanded. This
- // is a workaround to resize the iron-list as well.
- // TODO(dschuyler): Remove this hack after iron-list no longer needs
- // this workaround to update the list at the same time the template
- // wrapping the list is expanded.
- if (wasEmpty && this.words_.length > 0) {
- Polymer.dom.flush();
- this.$$('#list').notifyResize();
- }
- },
-
- /**
- * Handles Enter and Escape key presses for the new-word input.
- * @param {!CustomEvent<!{key: string}>} e
- */
- onKeysPress_: function(e) {
- if (e.detail.key == 'enter' && !this.disableAddButton_()) {
- this.addWordFromInput_();
- } else if (e.detail.key == 'esc') {
- e.detail.keyboardEvent.target.value = '';
- }
- },
-
- /**
- * Handles tapping on a "Remove word" icon button.
- * @param {!{model: !{item: string}}} e
- */
- onRemoveWordTap_: function(e) {
- this.languageSettingsPrivate.removeSpellcheckWord(e.model.item);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages.html b/chromium/chrome/browser/resources/settings/languages_page/languages.html
deleted file mode 100644
index fb88eb32730..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/promise_resolver.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="languages_browser_proxy.html">
-<script src="languages.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages.js b/chromium/chrome/browser/resources/settings/languages_page/languages.js
deleted file mode 100644
index 6e46eeda8bd..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages.js
+++ /dev/null
@@ -1,1090 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-languages' handles Chrome's language and input
- * method settings. The 'languages' property, which reflects the current
- * language settings, must not be changed directly. Instead, changes to
- * language settings should be made using the LanguageHelper APIs provided by
- * this class via languageHelper.
- */
-
-(function() {
-'use strict';
-
-cr.exportPath('settings');
-
-const MoveType = chrome.languageSettingsPrivate.MoveType;
-
-// Translate server treats some language codes the same.
-// See also: components/translate/core/common/translate_util.cc.
-const kLanguageCodeToTranslateCode = {
- 'nb': 'no',
- 'fil': 'tl',
- 'zh-HK': 'zh-TW',
- 'zh-MO': 'zh-TW',
- 'zh-SG': 'zh-CN',
-};
-
-// Some ISO 639 language codes have been renamed, e.g. "he" to "iw", but
-// Translate still uses the old versions. TODO(michaelpg): Chrome does too.
-// Follow up with Translate owners to understand the right thing to do.
-const kTranslateLanguageSynonyms = {
- 'he': 'iw',
- 'jv': 'jw',
-};
-
-// The fake language name used for ARC IMEs. The value must be in sync with the
-// one in ui/base/ime/chromeos/extension_ime_util.h.
-const kArcImeLanguage = '_arc_ime_language_';
-
-const preferredLanguagesPrefName = cr.isChromeOS ?
- 'settings.language.preferred_languages' :
- 'intl.accept_languages';
-
-/**
- * Singleton element that generates the languages model on start-up and
- * updates it whenever Chrome's pref store and other settings change.
- * @implements {LanguageHelper}
- */
-Polymer({
- is: 'settings-languages',
-
- behaviors: [PrefsBehavior],
-
- properties: {
- /**
- * @type {!LanguagesModel|undefined}
- */
- languages: {
- type: Object,
- notify: true,
- readOnly: true,
- },
-
- /**
- * This element, as a LanguageHelper instance for API usage.
- * @type {!LanguageHelper}
- */
- languageHelper: {
- type: Object,
- notify: true,
- readOnly: true,
- value: function() {
- return /** @type {!LanguageHelper} */ (this);
- },
- },
-
- /**
- * PromiseResolver to be resolved when the singleton has been initialized.
- * @private {!PromiseResolver}
- */
- resolver_: {
- type: Object,
- value: function() {
- return new PromiseResolver();
- },
- },
-
- /**
- * Hash map of supported languages by language codes for fast lookup.
- * @private {!Map<string, !chrome.languageSettingsPrivate.Language>}
- */
- supportedLanguageMap_: {
- type: Object,
- value: function() {
- return new Map();
- },
- },
-
- /**
- * Hash set of enabled language codes for membership testing.
- * @private {!Set<string>}
- */
- enabledLanguageSet_: {
- type: Object,
- value: function() {
- return new Set();
- },
- },
-
- /**
- * Hash map of supported input methods by ID for fast lookup.
- * @private {!Map<string, chrome.languageSettingsPrivate.InputMethod>}
- */
- supportedInputMethodMap_: {
- type: Object,
- value: function() {
- return new Map();
- },
- },
-
- /**
- * Hash map of input methods supported for each language.
- * @type {!Map<string,
- * !Array<!chrome.languageSettingsPrivate.InputMethod>>}
- * @private
- */
- languageInputMethods_: {
- type: Object,
- value: function() {
- return new Map();
- },
- },
-
- /** @private Prospective UI language when the page was loaded. */
- originalProspectiveUILanguage_: String,
- },
-
- observers: [
- // All observers wait for the model to be populated by including the
- // |languages| property.
- 'prospectiveUILanguageChanged_(prefs.intl.app_locale.value, languages)',
- 'preferredLanguagesPrefChanged_(' +
- 'prefs.' + preferredLanguagesPrefName + '.value, languages)',
- 'spellCheckDictionariesPrefChanged_(' +
- 'prefs.spellcheck.dictionaries.value.*, ' +
- 'prefs.spellcheck.forced_dictionaries.value.*, ' +
- 'prefs.spellcheck.blacklisted_dictionaries.value.*, languages)',
- 'translateLanguagesPrefChanged_(' +
- 'prefs.translate_blocked_languages.value.*, languages)',
- 'updateRemovableLanguages_(' +
- 'prefs.intl.app_locale.value, languages.enabled)',
- 'updateRemovableLanguages_(' +
- 'prefs.translate_blocked_languages.value.*)',
- // Observe Chrome OS prefs (ignored for non-Chrome OS).
- 'updateRemovableLanguages_(' +
- 'prefs.settings.language.preload_engines.value, ' +
- 'prefs.settings.language.enabled_extension_imes.value, ' +
- 'languages)',
- ],
-
- /** @private {?Function} */
- boundOnInputMethodChanged_: null,
-
- // <if expr="not is_macosx">
- /** @private {?Function} */
- boundOnSpellcheckDictionariesChanged_: null,
- // </if>
-
- /** @private {?settings.LanguagesBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?LanguageSettingsPrivate} */
- languageSettingsPrivate_: null,
-
- // <if expr="chromeos">
- /** @private {?InputMethodPrivate} */
- inputMethodPrivate_: null,
-
- /** @private {?Function} */
- boundOnInputMethodAdded_: null,
-
- /** @private {?Function} */
- boundOnInputMethodRemoved_: null,
- // </if>
-
- /** @override */
- attached: function() {
- this.browserProxy_ = settings.LanguagesBrowserProxyImpl.getInstance();
- this.languageSettingsPrivate_ =
- this.browserProxy_.getLanguageSettingsPrivate();
- // <if expr="chromeos">
- this.inputMethodPrivate_ = this.browserProxy_.getInputMethodPrivate();
- // </if>
-
- const promises = [];
-
- // Wait until prefs are initialized before creating the model, so we can
- // include information about enabled languages.
- promises[0] = CrSettingsPrefs.initialized;
-
- // Get the language list.
- promises[1] = new Promise(resolve => {
- this.languageSettingsPrivate_.getLanguageList(resolve);
- });
-
- // Get the translate target language.
- promises[2] = new Promise(resolve => {
- this.languageSettingsPrivate_.getTranslateTargetLanguage(resolve);
- });
-
- if (cr.isChromeOS) {
- promises[3] = new Promise(resolve => {
- this.languageSettingsPrivate_.getInputMethodLists(function(lists) {
- resolve(lists.componentExtensionImes.concat(
- lists.thirdPartyExtensionImes));
- });
- });
-
- promises[4] = new Promise(resolve => {
- this.inputMethodPrivate_.getCurrentInputMethod(resolve);
- });
- }
-
- if (cr.isWindows || cr.isChromeOS) {
- // Fetch the starting UI language, which affects which actions should be
- // enabled.
- promises.push(this.browserProxy_.getProspectiveUILanguage().then(
- prospectiveUILanguage => {
- this.originalProspectiveUILanguage_ =
- prospectiveUILanguage || window.navigator.language;
- }));
- }
-
- Promise.all(promises).then(results => {
- if (!this.isConnected) {
- // Return early if this element was detached from the DOM before
- // this async callback executes (can happen during testing).
- return;
- }
-
- // TODO(dpapad): Cleanup this code. It uses results[3] and results[4]
- // which only exist for ChromeOS.
- this.createModel_(results[1], results[2], results[3], results[4]);
-
- // <if expr="not is_macosx">
- this.boundOnSpellcheckDictionariesChanged_ =
- this.onSpellcheckDictionariesChanged_.bind(this);
- this.languageSettingsPrivate_.onSpellcheckDictionariesChanged.addListener(
- this.boundOnSpellcheckDictionariesChanged_);
- this.languageSettingsPrivate_.getSpellcheckDictionaryStatuses(
- this.boundOnSpellcheckDictionariesChanged_);
- // </if>
-
- this.resolver_.resolve();
- });
-
- if (cr.isChromeOS) {
- this.boundOnInputMethodChanged_ = this.onInputMethodChanged_.bind(this);
- this.inputMethodPrivate_.onChanged.addListener(
- assert(this.boundOnInputMethodChanged_));
- this.boundOnInputMethodAdded_ = this.onInputMethodAdded_.bind(this);
- this.languageSettingsPrivate_.onInputMethodAdded.addListener(
- this.boundOnInputMethodAdded_);
- this.boundOnInputMethodRemoved_ = this.onInputMethodRemoved_.bind(this);
- this.languageSettingsPrivate_.onInputMethodRemoved.addListener(
- this.boundOnInputMethodRemoved_);
- }
- },
-
- /** @override */
- detached: function() {
- if (cr.isChromeOS) {
- this.inputMethodPrivate_.onChanged.removeListener(
- assert(this.boundOnInputMethodChanged_));
- this.boundOnInputMethodChanged_ = null;
- this.languageSettingsPrivate_.onInputMethodAdded.removeListener(
- assert(this.boundOnInputMethodAdded_));
- this.boundOnInputMethodAdded_ = null;
- this.languageSettingsPrivate_.onInputMethodRemoved.removeListener(
- assert(this.boundOnInputMethodRemoved_));
- this.boundOnInputMethodRemoved_ = null;
- }
-
- // <if expr="not is_macosx">
- if (this.boundOnSpellcheckDictionariesChanged_) {
- this.languageSettingsPrivate_.onSpellcheckDictionariesChanged
- .removeListener(this.boundOnSpellcheckDictionariesChanged_);
- this.boundOnSpellcheckDictionariesChanged_ = null;
- }
- // </if>
- },
-
- /**
- * Updates the prospective UI language based on the new pref value.
- * @param {string} prospectiveUILanguage
- * @private
- */
- prospectiveUILanguageChanged_: function(prospectiveUILanguage) {
- this.set(
- 'languages.prospectiveUILanguage',
- prospectiveUILanguage || this.originalProspectiveUILanguage_);
- },
-
- /**
- * Updates the list of enabled languages from the preferred languages pref.
- * @private
- */
- preferredLanguagesPrefChanged_: function() {
- if (this.prefs == undefined || this.languages == undefined) {
- return;
- }
-
- const enabledLanguageStates = this.getEnabledLanguageStates_(
- this.languages.translateTarget, this.languages.prospectiveUILanguage);
-
- // Recreate the enabled language set before updating languages.enabled.
- this.enabledLanguageSet_.clear();
- for (let i = 0; i < enabledLanguageStates.length; i++) {
- this.enabledLanguageSet_.add(enabledLanguageStates[i].language.code);
- }
-
- this.set('languages.enabled', enabledLanguageStates);
-
- // <if expr="not is_macosx">
- if (this.boundOnSpellcheckDictionariesChanged_) {
- this.languageSettingsPrivate_.getSpellcheckDictionaryStatuses(
- this.boundOnSpellcheckDictionariesChanged_);
- }
-
- // Recreate the set of spellcheck forced languages in case a forced
- // spellcheck language was removed from the languages list.
- this.set(
- 'languages.forcedSpellCheckLanguages',
- this.getForcedSpellCheckLanguages_(this.languages.enabled));
- // </if>
-
- // Update translate target language.
- new Promise(resolve => {
- this.languageSettingsPrivate_.getTranslateTargetLanguage(resolve);
- }).then(result => {
- this.set('languages.translateTarget', result);
- });
- },
-
- /**
- * Updates the spellCheckEnabled state of each enabled language.
- * @private
- */
- spellCheckDictionariesPrefChanged_: function() {
- if (this.prefs == undefined || this.languages == undefined) {
- return;
- }
-
- const spellCheckSet = this.makeSetFromArray_(/** @type {!Array<string>} */ (
- this.getPref('spellcheck.dictionaries').value));
- const spellCheckForcedSet =
- this.makeSetFromArray_(/** @type {!Array<string>} */ (
- this.getPref('spellcheck.forced_dictionaries').value));
- const spellCheckBlacklistedSet =
- this.makeSetFromArray_(/** @type {!Array<string>} */ (
- this.getPref('spellcheck.blacklisted_dictionaries').value));
-
- for (let i = 0; i < this.languages.enabled.length; i++) {
- const languageState = this.languages.enabled[i];
- const isUser = spellCheckSet.has(languageState.language.code);
- const isForced = spellCheckForcedSet.has(languageState.language.code);
- const isBlacklisted =
- spellCheckBlacklistedSet.has(languageState.language.code);
- this.set(
- `languages.enabled.${i}.spellCheckEnabled`,
- (isUser && !isBlacklisted) || isForced);
- this.set(`languages.enabled.${i}.isManaged`, isForced || isBlacklisted);
- }
-
- this.set(
- 'languages.forcedSpellCheckLanguages',
- this.getForcedSpellCheckLanguages_(this.languages.enabled));
- },
-
- /**
- * Returns an array of language codes for the spellcheck languages that are
- * force-enabled by policy, but that are not "enabled" languages.
- * @param {!Array<!LanguageState>} enabledLanguages An array of enabled
- * languages.
- * @return {!Array<!string>}
- * @private
- */
- getForcedSpellCheckLanguages_: function(enabledLanguages) {
- const enabledSet = this.makeSetFromArray_(/** @type {!Array<string>} */ (
- enabledLanguages.map(x => x.language.code)));
- const spellCheckForcedDictionaries = /** @type {!Array<string>} */ (
- this.getPref('spellcheck.forced_dictionaries').value);
-
- const forcedLanguages = [];
- for (let i = 0; i < spellCheckForcedDictionaries.length; i++) {
- const code = spellCheckForcedDictionaries[i];
- if (!enabledSet.has(code) && this.supportedLanguageMap_.has(code)) {
- forcedLanguages.push({
- language: this.supportedLanguageMap_.get(code),
- isManaged: true,
- spellCheckEnabled: true,
- downloadDictionaryFailureCount: 0,
- });
- }
- }
- return forcedLanguages;
- },
-
- /** @private */
- translateLanguagesPrefChanged_: function() {
- if (this.prefs == undefined || this.languages == undefined) {
- return;
- }
-
- const translateBlockedPref = this.getPref('translate_blocked_languages');
- const translateBlockedSet = this.makeSetFromArray_(
- /** @type {!Array<string>} */ (translateBlockedPref.value));
-
- for (let i = 0; i < this.languages.enabled.length; i++) {
- const language = this.languages.enabled[i].language;
- const translateEnabled = this.isTranslateEnabled_(
- language.code, !!language.supportsTranslate, translateBlockedSet,
- this.languages.translateTarget, this.languages.prospectiveUILanguage);
- this.set(
- 'languages.enabled.' + i + '.translateEnabled', translateEnabled);
- }
- },
-
- /**
- * Constructs the languages model.
- * @param {!Array<!chrome.languageSettingsPrivate.Language>}
- * supportedLanguages
- * @param {string} translateTarget Language code of the default translate
- * target language.
- * @param {!Array<!chrome.languageSettingsPrivate.InputMethod>|undefined}
- * supportedInputMethods Input methods (Chrome OS only).
- * @param {string|undefined} currentInputMethodId ID of the currently used
- * input method (Chrome OS only).
- * @private
- */
- createModel_: function(
- supportedLanguages, translateTarget, supportedInputMethods,
- currentInputMethodId) {
- // Populate the hash map of supported languages.
- for (let i = 0; i < supportedLanguages.length; i++) {
- const language = supportedLanguages[i];
- language.supportsUI = !!language.supportsUI;
- language.supportsTranslate = !!language.supportsTranslate;
- language.supportsSpellcheck = !!language.supportsSpellcheck;
- language.isProhibitedLanguage = !!language.isProhibitedLanguage;
- this.supportedLanguageMap_.set(language.code, language);
- }
-
- if (supportedInputMethods) {
- this.createInputMethodModel_(supportedInputMethods);
- }
-
- let prospectiveUILanguage;
- if (cr.isChromeOS || cr.isWindows) {
- prospectiveUILanguage =
- /** @type {string} */ (this.getPref('intl.app_locale').value) ||
- this.originalProspectiveUILanguage_;
- }
-
- // Create a list of enabled languages from the supported languages.
- const enabledLanguageStates =
- this.getEnabledLanguageStates_(translateTarget, prospectiveUILanguage);
- // Populate the hash set of enabled languages.
- for (let l = 0; l < enabledLanguageStates.length; l++) {
- this.enabledLanguageSet_.add(enabledLanguageStates[l].language.code);
- }
-
- const forcedSpellCheckLanguages =
- this.getForcedSpellCheckLanguages_(enabledLanguageStates);
-
- const model = /** @type {!LanguagesModel} */ ({
- supported: supportedLanguages,
- enabled: enabledLanguageStates,
- translateTarget: translateTarget,
- forcedSpellCheckLanguages: forcedSpellCheckLanguages,
- });
-
- if (cr.isChromeOS || cr.isWindows) {
- model.prospectiveUILanguage = prospectiveUILanguage;
- }
-
- if (cr.isChromeOS) {
- model.inputMethods = /** @type {!InputMethodsModel} */ ({
- supported: supportedInputMethods,
- enabled: this.getEnabledInputMethods_(),
- currentId: currentInputMethodId,
- });
- }
-
- // Initialize the Polymer languages model.
- this._setLanguages(model);
- },
-
- /**
- * Constructs the input method part of the languages model.
- * @param {!Array<!chrome.languageSettingsPrivate.InputMethod>}
- * supportedInputMethods Input methods.
- * @private
- */
- createInputMethodModel_: function(supportedInputMethods) {
- assert(cr.isChromeOS);
- // Populate the hash map of supported input methods.
- this.supportedInputMethodMap_.clear();
- this.languageInputMethods_.clear();
- for (let j = 0; j < supportedInputMethods.length; j++) {
- const inputMethod = supportedInputMethods[j];
- inputMethod.enabled = !!inputMethod.enabled;
- inputMethod.isProhibitedByPolicy = !!inputMethod.isProhibitedByPolicy;
- // Add the input method to the map of IDs.
- this.supportedInputMethodMap_.set(inputMethod.id, inputMethod);
- // Add the input method to the list of input methods for each language
- // it supports.
- for (let k = 0; k < inputMethod.languageCodes.length; k++) {
- const languageCode = inputMethod.languageCodes[k];
- if (!this.supportedLanguageMap_.has(languageCode)) {
- continue;
- }
- if (!this.languageInputMethods_.has(languageCode)) {
- this.languageInputMethods_.set(languageCode, [inputMethod]);
- } else {
- this.languageInputMethods_.get(languageCode).push(inputMethod);
- }
- }
- }
- },
-
- /**
- * Returns a list of LanguageStates for each enabled language in the supported
- * languages list.
- * @param {string} translateTarget Language code of the default translate
- * target language.
- * @param {(string|undefined)} prospectiveUILanguage Prospective UI display
- * language. Only defined on Windows and Chrome OS.
- * @return {!Array<!LanguageState>}
- * @private
- */
- getEnabledLanguageStates_: function(translateTarget, prospectiveUILanguage) {
- assert(CrSettingsPrefs.isInitialized);
-
- const pref = this.getPref(preferredLanguagesPrefName);
- const enabledLanguageCodes = pref.value.split(',');
- const spellCheckPref = this.getPref('spellcheck.dictionaries');
- const spellCheckForcedPref = this.getPref('spellcheck.forced_dictionaries');
- const spellCheckBlacklistedPref =
- this.getPref('spellcheck.blacklisted_dictionaries');
- const spellCheckSet = this.makeSetFromArray_(
- /** @type {!Array<string>} */ (
- spellCheckPref.value.concat(spellCheckForcedPref.value)));
- const spellCheckForcedSet = this.makeSetFromArray_(
- /** @type {!Array<string>} */ (spellCheckForcedPref.value));
- const spellCheckBlacklistedSet = this.makeSetFromArray_(
- /** @type {!Array<string>} */ (spellCheckBlacklistedPref.value));
-
- const translateBlockedPref = this.getPref('translate_blocked_languages');
- const translateBlockedSet = this.makeSetFromArray_(
- /** @type {!Array<string>} */ (translateBlockedPref.value));
-
- const enabledLanguageStates = [];
- for (let i = 0; i < enabledLanguageCodes.length; i++) {
- const code = enabledLanguageCodes[i];
- const language = this.supportedLanguageMap_.get(code);
- // Skip unsupported languages.
- if (!language) {
- continue;
- }
- const languageState = /** @type {LanguageState} */ ({});
- languageState.language = language;
- languageState.spellCheckEnabled =
- spellCheckSet.has(code) && !spellCheckBlacklistedSet.has(code) ||
- spellCheckForcedSet.has(code);
- languageState.translateEnabled = this.isTranslateEnabled_(
- code, !!language.supportsTranslate, translateBlockedSet,
- translateTarget, prospectiveUILanguage);
- languageState.isManaged =
- spellCheckForcedSet.has(code) || spellCheckBlacklistedSet.has(code);
- languageState.downloadDictionaryFailureCount = 0;
- enabledLanguageStates.push(languageState);
- }
- return enabledLanguageStates;
- },
-
- /**
- * True iff we translate pages that are in the given language.
- * @param {string} code Language code.
- * @param {boolean} supportsTranslate If translation supports the given
- * language.
- * @param {!Set<string>} translateBlockedSet Set of languages for which
- * translation is blocked.
- * @param {string} translateTarget Language code of the default translate
- * target language.
- * @param {(string|undefined)} prospectiveUILanguage Prospective UI display
- * language. Only defined on Windows and Chrome OS.
- * @return {boolean}
- * @private
- */
- isTranslateEnabled_: function(
- code, supportsTranslate, translateBlockedSet, translateTarget,
- prospectiveUILanguage) {
- const translateCode = this.convertLanguageCodeForTranslate(code);
- return supportsTranslate && !translateBlockedSet.has(translateCode) &&
- translateCode != translateTarget &&
- (!prospectiveUILanguage || code != prospectiveUILanguage);
- },
-
- // <if expr="not is_macosx">
- /**
- * Updates the dictionary download status for languages in
- * |this.languages.enabled| and |this.languages.forcedSpellCheckLanguages| in
- * order to track the number of times a spell check dictionary download has
- * failed.
- * @param {!Array<!chrome.languageSettingsPrivate.SpellcheckDictionaryStatus>}
- * statuses
- * @private
- */
- onSpellcheckDictionariesChanged_: function(statuses) {
- const statusMap = new Map();
- statuses.forEach(status => {
- statusMap.set(status.languageCode, status);
- });
-
- ['enabled', 'forcedSpellCheckLanguages'].forEach(collectionName => {
- this.languages[collectionName].forEach((languageState, index) => {
- const status = statusMap.get(languageState.language.code);
- if (!status) {
- return;
- }
-
- const previousStatus = languageState.downloadDictionaryStatus;
- const keyPrefix = `languages.${collectionName}.${index}`;
- this.set(`${keyPrefix}.downloadDictionaryStatus`, status);
-
- const failureCountKey = `${keyPrefix}.downloadDictionaryFailureCount`;
- if (status.downloadFailed &&
- !(previousStatus && previousStatus.downloadFailed)) {
- const failureCount = languageState.downloadDictionaryFailureCount + 1;
- this.set(failureCountKey, failureCount);
- } else if (
- status.isReady && !(previousStatus && previousStatus.isReady)) {
- this.set(failureCountKey, 0);
- }
- });
- });
- },
- // </if>
-
- /**
- * Returns a list of enabled input methods.
- * @return {!Array<!chrome.languageSettingsPrivate.InputMethod>}
- * @private
- */
- getEnabledInputMethods_: function() {
- assert(cr.isChromeOS);
- assert(CrSettingsPrefs.isInitialized);
-
- let enabledInputMethodIds =
- this.getPref('settings.language.preload_engines').value.split(',');
- enabledInputMethodIds = enabledInputMethodIds.concat(
- this.getPref('settings.language.enabled_extension_imes')
- .value.split(','));
-
- // Return only supported input methods.
- return enabledInputMethodIds
- .map(id => this.supportedInputMethodMap_.get(id))
- .filter(function(inputMethod) {
- return !!inputMethod;
- });
- },
-
- /** @private */
- updateSupportedInputMethods_: function() {
- assert(cr.isChromeOS);
- const promise = new Promise(resolve => {
- this.languageSettingsPrivate_.getInputMethodLists(function(lists) {
- resolve(
- lists.componentExtensionImes.concat(lists.thirdPartyExtensionImes));
- });
- });
- promise.then(result => {
- const supportedInputMethods = result;
- this.createInputMethodModel_(supportedInputMethods);
- this.set('languages.inputMethods.supported', supportedInputMethods);
- this.updateEnabledInputMethods_();
- });
- },
-
- /** @private */
- updateEnabledInputMethods_: function() {
- assert(cr.isChromeOS);
- const enabledInputMethods = this.getEnabledInputMethods_();
- const enabledInputMethodSet = this.makeSetFromArray_(enabledInputMethods);
-
- for (let i = 0; i < this.languages.inputMethods.supported.length; i++) {
- this.set(
- 'languages.inputMethods.supported.' + i + '.enabled',
- enabledInputMethodSet.has(this.languages.inputMethods.supported[i]));
- }
- this.set('languages.inputMethods.enabled', enabledInputMethods);
- },
-
- /**
- * Updates the |removable| property of the enabled language states based
- * on what other languages and input methods are enabled.
- * @private
- */
- updateRemovableLanguages_: function() {
- if (this.prefs == undefined || this.languages == undefined) {
- return;
- }
-
- // TODO(michaelpg): Enabled input methods can affect which languages are
- // removable, so run updateEnabledInputMethods_ first (if it has been
- // scheduled).
- if (cr.isChromeOS) {
- this.updateEnabledInputMethods_();
- }
-
- for (let i = 0; i < this.languages.enabled.length; i++) {
- const languageState = this.languages.enabled[i];
- this.set(
- 'languages.enabled.' + i + '.removable',
- this.canDisableLanguage(languageState));
- }
- },
-
- /**
- * Creates a Set from the elements of the array.
- * @param {!Array<T>} list
- * @return {!Set<T>}
- * @template T
- * @private
- */
- makeSetFromArray_: function(list) {
- return new Set(list);
- },
-
- // LanguageHelper implementation.
- // TODO(michaelpg): replace duplicate docs with @override once b/24294625
- // is fixed.
-
- /** @return {!Promise} */
- whenReady: function() {
- return this.resolver_.promise;
- },
-
- // <if expr="chromeos or is_win">
- /**
- * Sets the prospective UI language to the chosen language. This won't affect
- * the actual UI language until a restart.
- * @param {string} languageCode
- */
- setProspectiveUILanguage: function(languageCode) {
- this.browserProxy_.setProspectiveUILanguage(languageCode);
- },
-
- /**
- * True if the prospective UI language was changed from its starting value.
- * @return {boolean}
- */
- requiresRestart: function() {
- return this.originalProspectiveUILanguage_ !=
- this.languages.prospectiveUILanguage;
- },
- // </if>
-
- /**
- * @return {string} The language code for ARC IMEs.
- */
- getArcImeLanguageCode: function() {
- return kArcImeLanguage;
- },
-
- /**
- * @param {string} languageCode
- * @return {boolean} True if the language is for ARC IMEs.
- */
- isLanguageCodeForArcIme: function(languageCode) {
- return languageCode == kArcImeLanguage;
- },
-
- /**
- * @param {string} languageCode
- * @return {boolean} True if the language is enabled.
- */
- isLanguageEnabled: function(languageCode) {
- return this.enabledLanguageSet_.has(languageCode);
- },
-
- /**
- * Enables the language, making it available for spell check and input.
- * @param {string} languageCode
- */
- enableLanguage: function(languageCode) {
- if (!CrSettingsPrefs.isInitialized) {
- return;
- }
-
- this.languageSettingsPrivate_.enableLanguage(languageCode);
- },
-
- /**
- * Disables the language.
- * @param {string} languageCode
- */
- disableLanguage: function(languageCode) {
- if (!CrSettingsPrefs.isInitialized) {
- return;
- }
-
- // Remove the language from spell check.
- this.deletePrefListItem('spellcheck.dictionaries', languageCode);
-
- if (cr.isChromeOS) {
- // Remove input methods that don't support any other enabled language.
- const inputMethods = this.languageInputMethods_.get(languageCode) || [];
- for (let i = 0; i < inputMethods.length; i++) {
- const inputMethod = inputMethods[i];
- const supportsOtherEnabledLanguages = inputMethod.languageCodes.some(
- otherLanguageCode => otherLanguageCode != languageCode &&
- this.isLanguageEnabled(otherLanguageCode));
- if (!supportsOtherEnabledLanguages) {
- this.removeInputMethod(inputMethod.id);
- }
- }
- }
-
- // Remove the language from preferred languages.
- this.languageSettingsPrivate_.disableLanguage(languageCode);
- },
-
- /**
- * @param {!LanguageState} languageState
- * @return {boolean}
- */
- isOnlyTranslateBlockedLanguage: function(languageState) {
- return !languageState.translateEnabled &&
- this.languages.enabled.filter(lang => !lang.translateEnabled).length ==
- 1;
- },
-
- /**
- * @param {!LanguageState} languageState
- * @return {boolean}
- */
- canDisableLanguage: function(languageState) {
- // Cannot disable the prospective UI language.
- if (languageState.language.code == this.languages.prospectiveUILanguage) {
- return false;
- }
-
- // Cannot disable the only enabled language.
- if (this.languages.enabled.length == 1) {
- return false;
- }
-
- // Cannot disable the last translate blocked language.
- if (this.isOnlyTranslateBlockedLanguage(languageState)) {
- return false;
- }
-
- if (!cr.isChromeOS) {
- return true;
- }
-
- // If this is the only enabled language that is supported by all enabled
- // component IMEs, it cannot be disabled because we need those IMEs.
- const otherInputMethodsEnabled =
- this.languages.enabled.some(function(otherLanguageState) {
- const otherLanguageCode = otherLanguageState.language.code;
- if (otherLanguageCode == languageState.language.code) {
- return false;
- }
- const inputMethods =
- this.languageInputMethods_.get(otherLanguageCode);
- return inputMethods && inputMethods.some(function(inputMethod) {
- return this.isComponentIme(inputMethod) &&
- this.supportedInputMethodMap_.get(inputMethod.id).enabled;
- }, this);
- }, this);
- return otherInputMethodsEnabled;
- },
-
- /**
- * @param {!chrome.languageSettingsPrivate.Language} language
- * @return {boolean} true if the given language can be enabled
- */
- canEnableLanguage(language) {
- return !(
- this.isLanguageEnabled(language.code) ||
- language.isProhibitedLanguage ||
- this.isLanguageCodeForArcIme(language.code) /* internal use only */);
- },
-
- /**
- * Moves the language in the list of enabled languages either up (toward the
- * front of the list) or down (toward the back).
- * @param {string} languageCode
- * @param {boolean} upDirection True if we need to move up, false if we
- * need to move down
- */
- moveLanguage: function(languageCode, upDirection) {
- if (!CrSettingsPrefs.isInitialized) {
- return;
- }
-
- if (upDirection) {
- this.languageSettingsPrivate_.moveLanguage(languageCode, MoveType.UP);
- } else {
- this.languageSettingsPrivate_.moveLanguage(languageCode, MoveType.DOWN);
- }
- },
-
- /**
- * Moves the language directly to the front of the list of enabled languages.
- * @param {string} languageCode
- */
- moveLanguageToFront: function(languageCode) {
- if (!CrSettingsPrefs.isInitialized) {
- return;
- }
-
- this.languageSettingsPrivate_.moveLanguage(languageCode, MoveType.TOP);
- },
-
- /**
- * Enables translate for the given language by removing the translate
- * language from the blocked languages preference.
- * @param {string} languageCode
- */
- enableTranslateLanguage: function(languageCode) {
- this.languageSettingsPrivate_.setEnableTranslationForLanguage(
- languageCode, true);
- },
-
- /**
- * Disables translate for the given language by adding the translate
- * language to the blocked languages preference.
- * @param {string} languageCode
- */
- disableTranslateLanguage: function(languageCode) {
- this.languageSettingsPrivate_.setEnableTranslationForLanguage(
- languageCode, false);
- },
-
- /**
- * Enables or disables spell check for the given language.
- * @param {string} languageCode
- * @param {boolean} enable
- */
- toggleSpellCheck: function(languageCode, enable) {
- if (!this.languages) {
- return;
- }
-
- if (enable) {
- const spellCheckPref = this.getPref('spellcheck.dictionaries');
- this.appendPrefListItem('spellcheck.dictionaries', languageCode);
- } else {
- this.deletePrefListItem('spellcheck.dictionaries', languageCode);
- }
- },
-
- /**
- * Converts the language code for translate. There are some differences
- * between the language set the Translate server uses and that for
- * Accept-Language.
- * @param {string} languageCode
- * @return {string} The converted language code.
- */
- convertLanguageCodeForTranslate: function(languageCode) {
- if (languageCode in kLanguageCodeToTranslateCode) {
- return kLanguageCodeToTranslateCode[languageCode];
- }
-
- const main = languageCode.split('-')[0];
- if (main == 'zh') {
- // In Translate, general Chinese is not used, and the sub code is
- // necessary as a language code for the Translate server.
- return languageCode;
- }
- if (main in kTranslateLanguageSynonyms) {
- return kTranslateLanguageSynonyms[main];
- }
-
- return main;
- },
-
- /**
- * Given a language code, returns just the base language. E.g., converts
- * 'en-GB' to 'en'.
- * @param {string} languageCode
- * @return {string}
- */
- getLanguageCodeWithoutRegion: function(languageCode) {
- // The Norwegian languages fall under the 'no' macrolanguage.
- if (languageCode == 'nb' || languageCode == 'nn') {
- return 'no';
- }
-
- // Match the characters before the hyphen.
- const result = languageCode.match(/^([^-]+)-?/);
- assert(result.length == 2);
- return result[1];
- },
-
- /**
- * @param {string} languageCode
- * @return {!chrome.languageSettingsPrivate.Language|undefined}
- */
- getLanguage: function(languageCode) {
- // If a languageCode is not found, try language without location.
- return this.supportedLanguageMap_.get(languageCode) ||
- this.supportedLanguageMap_.get(
- this.getLanguageCodeWithoutRegion(languageCode));
- },
-
- /**
- * Retries downloading the dictionary for |languageCode|.
- * @param {string} languageCode
- */
- retryDownloadDictionary: function(languageCode) {
- this.languageSettingsPrivate_.retryDownloadDictionary(languageCode);
- },
-
- // <if expr="chromeos">
- /** @param {string} id */
- addInputMethod: function(id) {
- if (!this.supportedInputMethodMap_.has(id)) {
- return;
- }
- this.languageSettingsPrivate_.addInputMethod(id);
- },
-
- /** @param {string} id */
- removeInputMethod: function(id) {
- if (!this.supportedInputMethodMap_.has(id)) {
- return;
- }
- this.languageSettingsPrivate_.removeInputMethod(id);
- },
-
- /** @param {string} id */
- setCurrentInputMethod: function(id) {
- this.inputMethodPrivate_.setCurrentInputMethod(id);
- },
-
- /**
- * @param {string} languageCode
- * @return {!Array<!chrome.languageSettingsPrivate.InputMethod>}
- */
- getInputMethodsForLanguage: function(languageCode) {
- return this.languageInputMethods_.get(languageCode) || [];
- },
-
- /**
- * @param {!chrome.languageSettingsPrivate.InputMethod} inputMethod
- * @return {boolean}
- */
- isComponentIme: function(inputMethod) {
- return inputMethod.id.startsWith('_comp_');
- },
-
- /** @param {string} id Input method ID. */
- openInputMethodOptions: function(id) {
- this.inputMethodPrivate_.openOptionsPage(id);
- },
-
- /** @param {string} id New current input method ID. */
- onInputMethodChanged_: function(id) {
- this.set('languages.inputMethods.currentId', id);
- },
-
- /** @param {string} id Added input method ID. */
- onInputMethodAdded_: function(id) {
- this.updateSupportedInputMethods_();
- },
-
- /** @param {string} id Removed input method ID. */
- onInputMethodRemoved_: function(id) {
- this.updateSupportedInputMethods_();
- },
- // </if>
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.html b/chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.html
deleted file mode 100644
index d0002574d59..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="languages_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.js b/chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.js
deleted file mode 100644
index e23dd250069..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages_browser_proxy.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the languages section
- * to interact with the browser.
- */
-
-cr.define('settings', function() {
- /** @interface */
- class LanguagesBrowserProxy {
- // <if expr="chromeos or is_win">
- /**
- * Sets the prospective UI language to the chosen language. This won't
- * affect the actual UI language until a restart.
- * @param {string} languageCode
- */
- setProspectiveUILanguage(languageCode) {}
-
- /** @return {!Promise<string>} */
- getProspectiveUILanguage() {}
-
- // </if>
-
- /** @return {!LanguageSettingsPrivate} */
- getLanguageSettingsPrivate() {}
-
- // <if expr="chromeos">
- /** @return {!InputMethodPrivate} */
- getInputMethodPrivate() {}
- // </if>
- }
-
- /**
- * @implements {settings.LanguagesBrowserProxy}
- */
- class LanguagesBrowserProxyImpl {
- // <if expr="chromeos or is_win">
- /** @override */
- setProspectiveUILanguage(languageCode) {
- chrome.send('setProspectiveUILanguage', [languageCode]);
- }
-
- /** @override */
- getProspectiveUILanguage() {
- return cr.sendWithPromise('getProspectiveUILanguage');
- }
-
- // </if>
-
- /** @override */
- getLanguageSettingsPrivate() {
- return /** @type {!LanguageSettingsPrivate} */ (
- chrome.languageSettingsPrivate);
- }
-
- // <if expr="chromeos">
- /** @override */
- getInputMethodPrivate() {
- return /** @type {!InputMethodPrivate} */ (chrome.inputMethodPrivate);
- }
- // </if>
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(LanguagesBrowserProxyImpl);
-
- return {
- LanguagesBrowserProxy: LanguagesBrowserProxy,
- LanguagesBrowserProxyImpl: LanguagesBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_page.html b/chromium/chrome/browser/resources/settings/languages_page/languages_page.html
deleted file mode 100644
index 2cc7d7d74b3..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages_page.html
+++ /dev/null
@@ -1,495 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="add_languages_dialog.html">
-<link rel="import" href="languages.html">
-<link rel="import" href="../controls/controlled_radio_button.html">
-<link rel="import" href="../controls/settings_radio_group.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../lifetime_browser_proxy.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<if expr="not is_macosx">
-<link rel="import" href="edit_dictionary_page.html">
-</if>
-
-<if expr="chromeos">
-<link rel="import" href="manage_input_methods_page.html">
-</if>
-
-<dom-module id="settings-languages-page">
- <template>
- <style include="settings-shared action-link">
- #languagesCollapse .list-item.selected {
- min-height: var(--settings-row-two-line-min-height);
- }
-
- .explain-selected {
- color: var(--google-green-refresh-700);
- font-weight: initial;
- margin-top: 4px;
- }
-
- @media (prefers-color-scheme: dark) {
- .explain-selected {
- color: var(--google-green-refresh-300);
- }
- }
-
- cr-action-menu.complex .dropdown-item {
- min-height: 36px;
- }
-
- cr-action-menu:not(.complex) hr {
- display: none;
- }
-
- cr-action-menu.complex hr {
- /* Override user-agent border and margin. */
- border: none;
- /* TODO(michaelpg): Update to whatever variable is used for the darker,
- * full-width separators: crbug.com/649547. */
- border-top: var(--cr-separator-line);
- margin: 6px 0 0 0;
- }
-
- cr-checkbox.dropdown-item {
- --cr-action-menu-disabled-item-opacity: 0.38;
- margin-inline-start: 0;
- }
-
- .icon-external {
- /* The negative margin messes up the outline border. These are in an
- indented list so this looks fine until moved: crbug.com/708286. */
- margin-inline-end: 0;
- }
-
- #uiLanguageItem:focus,
- #offerTranslations:focus {
- background-color: transparent;
- }
-
- #uiLanguageItem span {
- line-height: 20px;
- }
-
- #uiLanguageItem cr-policy-indicator {
- float: right;
- margin-inline-start: 20px;
- }
-
- .name-with-error-list {
- padding: 14px 0;
- }
-
- .name-with-error-list div {
- /* TODO(dbeam): does this need to differ from --settings-error-color? */
- color: var(--google-red-500);
- margin-top: 8px;
- }
-
- @media (prefers-color-scheme: dark) {
- .name-with-error-list div {
- color: var(--settings-error-color);
- }
- }
-
- iron-icon[icon='cr:error'] {
- @apply --cr-icon-height-width;
- --iron-icon-fill-color: var(--settings-error-color);
- margin-inline-end: 8px;
- }
-
- .name-with-error-list[disabled] {
- pointer-events: none;
- }
-
- iron-icon.policy {
- margin-inline-start: 10px;
- }
-
- cr-policy-pref-indicator {
- margin-inline-end: var(--settings-controlled-by-spacing);
- }
-
- .spell-check-radio-group {
- padding-bottom: var(--cr-section-vertical-padding);
- }
-
- .spell-check-radio-button {
- --cr-radio-button-label-spacing: calc(
- var(--cr-section-indent-width) - var(--cr-radio-button-size));
- }
-
- .spell-check-radio-button.enhanced {
- align-items: start;
- /* Align with just the first line of text */
- --cr-radio-button-disc-margin-block-start: calc(
- (1.5em - var(--cr-radio-button-size)) / 2);
- }
-
- .enhanced-spellcheck-description {
- padding-inline-end: 150px;
- }
-
- .spell-check-languages .list-item:last-of-type {
- border-bottom: var(--cr-separator-line);
- }
-
- div.list-item.non-target .target-info {
- display: none;
- }
-
- /* Any .target.target-info following another .target element needs to
- be hidden. We only want to show the _first_ .target-info of the list.
- This is a way to do a :first-of-class selector. */
- div.list-item.target ~ div.list-item.target .target-info {
- display: none;
- }
-
- #restartButton {
- margin-inline-start: var(--settings-controlled-by-spacing);
- }
-
- .external-wrapper {
- display: flex;
- }
- </style>
- <settings-languages languages="{{languages}}" prefs="{{prefs}}"
- language-helper="{{languageHelper}}">
- </settings-languages>
- <settings-animated-pages id="pages" section="languages"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <cr-expand-button
- alt="$i18n{languagesExpandA11yLabel}"
- class$="settings-box first [[getLanguageListTwoLine_()]]"
- expanded="{{languagesOpened_}}">
- <div>$i18n{languagesListTitle}</div>
-<if expr="chromeos or is_win">
- <div class="secondary">
- [[getProspectiveUILanguageName_(languages.prospectiveUILanguage)]]
- </div>
-</if>
- </cr-expand-button>
- <iron-collapse id="languagesCollapse" opened="[[languagesOpened_]]">
- <span class="settings-box first"
- hidden="[[isHelpTextHidden_(languages.enabled.*)]]">
- <span>$i18n{orderLanguagesInstructions}</span>
-<if expr="chromeos">
- <a href="$i18n{languagesLearnMoreURL}" target="_blank">
- $i18n{learnMore}
- </a>
-</if>
- </span>
- <div class="list-frame vertical-list">
- <template is="dom-repeat" items="[[languages.enabled]]">
- <div class$="list-item [[getLanguageItemClass_(
- item.language.code, language.prospectiveUILanguage)]]
- [[isTranslationTarget_(item.language.code,
- languages.translateTarget)]]">
- <div class="start">
- <div title="[[item.language.nativeDisplayName]]">
- [[item.language.displayName]]
- </div>
- <div class="target-info secondary">
- $i18n{translateTargetLabel}
- </div>
-<if expr="chromeos or is_win">
- <div class="explain-selected"
- hidden="[[!isProspectiveUILanguage_(
- item.language.code,
- languages.prospectiveUILanguage)]]">
- $i18n{isDisplayedInThisLanguage}
- </div>
-</if> <!-- chromeos or is_win -->
- </div>
-<if expr="chromeos or is_win">
- <template is="dom-if" if="[[isRestartRequired_(
- item.language.code, languages.prospectiveUILanguage)]]"
- restamp>
- <cr-button id="restartButton" on-click="onRestartTap_">
- $i18n{restart}
- </cr-button>
- </template>
-</if> <!-- chromeos or is_win -->
- <cr-icon-button class="icon-more-vert"
- title="$i18n{moreActions}" id="more-[[item.language.code]]"
- on-click="onDotsTap_"></cr-icon-button>
- </div>
- </template>
- <div class="list-item">
- <a is="action-link" class="list-button" id="addLanguages"
- disabled="[[!canEnableSomeSupportedLanguage_(languages)]]"
- on-click="onAddLanguagesTap_">
- $i18n{addLanguages}
- </a>
- </div>
- </div>
- <settings-toggle-button id="offerTranslateOtherLanguages"
- class="first" pref="{{prefs.translate.enabled}}"
- label="$i18n{offerToEnableTranslate}">
- </settings-toggle-button>
- </iron-collapse>
-<if expr="chromeos">
- <cr-expand-button
- hidden="[[!pageVisibility.inputMethodsList]]"
- alt="$i18n{inputMethodsExpandA11yLabel}"
- class="settings-box two-line"
- expanded="{{inputMethodsOpened_}}"
- id="manageInputMethodsSubpageTrigger">
- <div>$i18n{inputMethodsListTitle}</div>
- <div class="secondary">
- [[getInputMethodName_(languages.inputMethods.currentId)]]
- </div>
- </cr-expand-button>
- <iron-collapse id="inputMethodsCollapse"
- hidden="[[!pageVisibility.inputMethodsList]]"
- opened="[[inputMethodsOpened_]]">
- <div class="list-frame vertical-list">
- <template is="dom-repeat"
- items="[[languages.inputMethods.enabled]]">
- <div class$="list-item [[getInputMethodItemClass_(
- item.id, languages.inputMethods.currentId)]]"
- on-click="onInputMethodTap_" on-keypress="onInputMethodTap_"
- actionable tabindex="0">
- <div class="start">
- <div>[[item.displayName]]</div>
- <div class="explain-selected"
- hidden="[[!isCurrentInputMethod_(
- item.id, languages.inputMethods.currentId)]]">
- $i18n{inputMethodEnabled}
- </div>
- </div>
- <div class="external-wrapper" hidden="[[!item.hasOptionsPage]]">
- <div class="separator"></div>
- <cr-icon-button class="icon-external"
- on-click="onInputMethodOptionsTap_"></cr-icon-button>
- </div>
- </div>
- </template>
- <cr-link-row class="hr list-item" id="manageInputMethods"
- on-click="onManageInputMethodsTap_"
- label="$i18n{manageInputMethods}"></cr-link-row>
- </div>
- <settings-toggle-button
- pref="{{prefs.settings.language.ime_menu_activated}}"
- label="$i18n{showImeMenu}">
- </settings-toggle-button>
- </iron-collapse>
-</if>
-
- <settings-toggle-button
- id="enableSpellcheckingToggle"
- label="$i18n{spellCheckTitle}"
- sub-label="[[getSpellCheckSubLabel_(spellCheckLanguages_)]]"
- pref="{{prefs.browser.enable_spellchecking}}"
- disabled="[[!spellCheckLanguages_.length]]">
- </settings-toggle-button>
-<if expr="_google_chrome or not is_macosx">
- <iron-collapse id="spellCheckCollapse"
- opened="[[prefs.browser.enable_spellchecking.value]]">
-<if expr="_google_chrome">
- <div class="settings-box continuation spell-check-radio-group">
- <settings-radio-group
- pref="{{prefs.spellcheck.use_spelling_service}}">
- <controlled-radio-button
- class="spell-check-radio-button"
- id="spellingServiceDisable"
- label="$i18n{spellCheckBasicLabel}"
- name="false"
- pref="[[prefs.spellcheck.use_spelling_service]]">
- </controlled-radio-button>
- <controlled-radio-button
- class="spell-check-radio-button enhanced"
- id="spellingServiceEnable"
- label="$i18n{spellCheckEnhancedLabel}"
- name="true"
- pref="[[prefs.spellcheck.use_spelling_service]]">
- <div class="secondary enhanced-spellcheck-description">
- $i18n{spellCheckEnhancedDescription}
- </div>
- </controlled-radio-button>
- </settings-radio-group>
- </div>
-</if> <!-- _google_chrome -->
-<if expr="not is_macosx">
- <div id="spellCheckLanguagesList"
- hidden="[[hideSpellCheckLanguages_]]">
- <div class="settings-box continuation">
- $i18n{spellCheckLanguagesListTitle}
- </div>
- <div class="list-frame vertical-list spell-check-languages">
- <template is="dom-repeat" items="[[spellCheckLanguages_]]">
- <div class="list-item">
- <div class="start name-with-error-list"
- on-click="onSpellCheckNameClick_" actionable
- disabled$="[[isSpellCheckNameClickDisabled_(item,
- item.*)]]">
- [[item.language.displayName]]
- <div hidden="[[!errorsGreaterThan_(
- item.downloadDictionaryFailureCount, 0)]]">
- <iron-icon icon="cr:error"></iron-icon>
- $i18n{languagesDictionaryDownloadError}
- </div>
- <div hidden="[[!errorsGreaterThan_(
- item.downloadDictionaryFailureCount, 1)]]">
- $i18n{languagesDictionaryDownloadErrorHelp}
- </div>
- </div>
- <cr-button on-click="onRetryDictionaryDownloadClick_"
- hidden="[[!errorsGreaterThan_(
- item.downloadDictionaryFailureCount, 0)]]">
- $i18n{retry}
- </cr-button>
- <template is="dom-if" if="[[!item.isManaged]]">
- <cr-toggle on-change="onSpellCheckLanguageChange_"
- disabled="[[!item.language.supportsSpellcheck]]"
- checked="[[item.spellCheckEnabled]]"
- aria-label$="[[item.language.displayName]]"
- hidden="[[errorsGreaterThan_(
- item.downloadDictionaryFailureCount, 0)]]">
- </cr-toggle>
- </template>
- <template is="dom-if" if="[[item.isManaged]]">
- <cr-policy-pref-indicator
- pref="[[getIndicatorPrefForManagedSpellcheckLanguage_(
- item.spellCheckEnabled)]]"
- hidden="[[errorsGreaterThan_(
- item.downloadDictionaryFailureCount, 0)]]">
- </cr-policy-pref-indicator>
- <cr-toggle disabled="true"
- checked="[[item.spellCheckEnabled]]"
- aria-label$="[[item.language.displayName]]"
- hidden="[[errorsGreaterThan_(
- item.downloadDictionaryFailureCount, 0)]]">
- </cr-toggle>
- </template>
- </div>
- </template>
- </div>
- </div>
- <cr-link-row on-click="onEditDictionaryTap_"
- id="spellCheckSubpageTrigger"
- label="$i18n{manageSpellCheck}">
- </cr-link-row>
-</if> <!-- not is_macosx -->
- </iron-collapse>
-</if> <!-- _google_chrome or not is_macosx -->
- <cr-lazy-render id="menu">
- <template>
- <cr-action-menu
-<if expr="chromeos or is_win">
- on-close="onCloseMenu_"
-</if>
- class$="[[getMenuClass_(prefs.translate.enabled.value)]]">
-<if expr="chromeos or is_win">
- <cr-checkbox id="uiLanguageItem"
- class="dropdown-item"
- checked="[[isProspectiveUILanguage_(
- detailLanguage_.language.code,
- languages.prospectiveUILanguage)]]"
- on-change="onUILanguageChange_"
- disabled="[[disableUILanguageCheckbox_(
- detailLanguage_, languages.prospectiveUILanguage)]]">
- <span>$i18n{displayInThisLanguage}</span>
- <iron-icon class="policy" icon="cr20:domain" hidden$="[[
- !detailLanguage_.language.isProhibitedLanguage]]">
- </iron-icon>
- </cr-checkbox>
-</if> <!-- chromeos or is_win -->
- <cr-checkbox id="offerTranslations"
- class="dropdown-item"
- checked="[[detailLanguage_.translateEnabled]]"
- on-change="onTranslateCheckboxChange_"
- hidden="[[!prefs.translate.enabled.value]]"
- disabled="[[disableTranslateCheckbox_(
- detailLanguage_, languages.translateTarget)]]">
- $i18n{offerToTranslateInThisLanguage}
- </cr-checkbox>
- <hr hidden="[[!shouldShowDialogSeparator_(languages.enabled.*)]]">
- <button class="dropdown-item" role="menuitem"
- on-click="onMoveToTopTap_"
- hidden="[[isNthLanguage_(
- 0, detailLanguage_, languages.enabled.*)]]">
- $i18n{moveToTop}
- </button>
- <button class="dropdown-item" role="menuitem"
- on-click="onMoveUpTap_"
- hidden="[[!showMoveUp_(detailLanguage_,
- languages.enabled.*)]]">
- $i18n{moveUp}
- </button>
- <button class="dropdown-item" role="menuitem"
- on-click="onMoveDownTap_"
- hidden="[[!showMoveDown_(
- detailLanguage_, languages.enabled.*)]]">
- $i18n{moveDown}
- </button>
- <button class="dropdown-item" role="menuitem"
- on-click="onRemoveLanguageTap_"
- hidden="[[!detailLanguage_.removable]]">
- $i18n{removeLanguage}
- </button>
- </cr-action-menu>
- </template>
- </cr-lazy-render>
- </div>
-<if expr="chromeos">
- <template is="dom-if" route-path="/inputMethods">
- <settings-subpage
- hidden="[[!pageVisibility.manageInputMethods]]"
- associated-control="[[$$('#manageInputMethodsSubpageTrigger')]]"
- page-title="$i18n{manageInputMethodsPageTitle}">
- <settings-manage-input-methods-page languages="{{languages}}"
- language-helper="[[languageHelper]]"
- prefs="{{prefs}}">
- </settings-manage-input-methods-page>
- </settings-subpage>
- </template>
-</if>
-<if expr="not is_macosx">
- <template is="dom-if" route-path="/editDictionary"
- no-search="[[!prefs.browser.enable_spellchecking.value]]">
- <settings-subpage
- associated-control="[[$$('#spellCheckSubpageTrigger')]]"
- page-title="$i18n{editDictionaryPageTitle}"
- no-search$="[[!prefs.browser.enable_spellchecking.value]]">
- <settings-edit-dictionary-page></settings-edit-dictionary-page>
- </settings-subpage>
- </template>
-</if>
- </settings-animated-pages>
- <template is="dom-if" if="[[showAddLanguagesDialog_]]" restamp>
- <settings-add-languages-dialog languages="{{languages}}"
- language-helper="[[languageHelper]]"
- on-close="onAddLanguagesDialogClose_">
- </settings-add-languages-dialog>
- </template>
- </template>
- <script src="languages_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_page.js b/chromium/chrome/browser/resources/settings/languages_page/languages_page.js
deleted file mode 100644
index 657239a5971..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages_page.js
+++ /dev/null
@@ -1,870 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-languages-page' is the settings page
- * for language and input method settings.
- */
-cr.exportPath('settings');
-
-/**
- * @type {number} Millisecond delay that can be used when closing an action
- * menu to keep it briefly on-screen.
- */
-settings.kMenuCloseDelay = 100;
-
-/**
- * Name of the language setting is shown uma histogram.
- * @type {string}
- */
-const LANGUAGE_SETTING_IS_SHOWN_UMA_NAME = 'Translate.LanguageSettingsIsShown';
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-languages-page',
-
- behaviors: [
- I18nBehavior,
- PrefsBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Read-only reference to the languages model provided by the
- * 'settings-languages' instance.
- * @type {!LanguagesModel|undefined}
- */
- languages: {
- type: Object,
- notify: true,
- },
-
- /** @type {!LanguageHelper} */
- languageHelper: Object,
-
- // <if expr="not is_macosx">
- /** @private */
- spellCheckLanguages_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /** @private {string|undefined} */
- languageSyncedWithBrowserEnableSpellchecking_: String,
- // </if>
-
- /**
- * The language to display the details for.
- * @type {!LanguageState|undefined}
- * @private
- */
- detailLanguage_: Object,
-
- /** @private */
- hideSpellCheckLanguages_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Whether the language settings list is opened.
- * @private
- */
- languagesOpened_: {
- type: Boolean,
- observer: 'onLanguagesOpenedChanged_',
- },
-
- /** @private */
- showAddLanguagesDialog_: Boolean,
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- // <if expr="not is_macosx">
- if (settings.routes.EDIT_DICTIONARY) {
- map.set(
- settings.routes.EDIT_DICTIONARY.path,
- '#spellCheckSubpageTrigger');
- }
- // </if>
- // <if expr="chromeos">
- if (settings.routes.INPUT_METHODS) {
- map.set(settings.routes.INPUT_METHODS.path, '#manageInputMethods');
- }
- // </if>
- return map;
- },
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!LanguagesPageVisibility}
- */
- pageVisibility: Object,
-
- // <if expr="chromeos">
- /** @private */
- isGuest_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isGuest');
- },
- },
- // </if>
- },
-
- // <if expr="not is_macosx">
- observers: [
- 'updateSpellcheckLanguages_(languages.enabled.*, ' +
- 'languages.forcedSpellCheckLanguages.*)',
- 'updateSpellcheckEnabled_(prefs.browser.enable_spellchecking.*)',
- ],
- // </if>
-
- // <if expr="chromeos or is_win">
- /** @private {boolean} */
- isChangeInProgress_: false,
- // </if>
-
- // <if expr="not is_macosx">
- /**
- * Checks if there are any errors downloading the spell check dictionary. This
- * is used for showing/hiding error messages, spell check toggle and retry.
- * button.
- * @param {number} downloadDictionaryFailureCount
- * @param {number} threshold
- * @return {boolean}
- * @private
- */
- errorsGreaterThan_: function(downloadDictionaryFailureCount, threshold) {
- return downloadDictionaryFailureCount > threshold;
- },
- // </if>
-
- /**
- * Stamps and opens the Add Languages dialog, registering a listener to
- * disable the dialog's dom-if again on close.
- * @param {!Event} e
- * @private
- */
- onAddLanguagesTap_: function(e) {
- e.preventDefault();
- this.showAddLanguagesDialog_ = true;
- },
-
- /** @private */
- onAddLanguagesDialogClose_: function() {
- this.showAddLanguagesDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$.addLanguages));
- },
-
- /**
- * Checks if there are supported languages that are not enabled but can be
- * enabled.
- * @param {LanguagesModel|undefined} languages
- * @return {boolean} True if there is at least one available language.
- * @private
- */
- canEnableSomeSupportedLanguage_: function(languages) {
- return languages == undefined || languages.supported.some(language => {
- return this.languageHelper.canEnableLanguage(language);
- });
- },
-
- /**
- * Used to determine whether to show the separator between checkbox settings
- * and move buttons in the dialog menu.
- * @return {boolean} True if there is currently more than one selected
- * language.
- * @private
- */
- shouldShowDialogSeparator_: function() {
- // <if expr="chromeos">
- if (this.isGuest_) {
- return false;
- }
- // </if>
- return this.languages != undefined && this.languages.enabled.length > 1;
- },
-
- /**
- * Used to determine which "Move" buttons to show for ordering enabled
- * languages.
- * @param {number} n
- * @return {boolean} True if |language| is at the |n|th index in the list of
- * enabled languages.
- * @private
- */
- isNthLanguage_: function(n) {
- if (this.languages == undefined || this.detailLanguage_ == undefined) {
- return false;
- }
-
- if (n >= this.languages.enabled.length) {
- return false;
- }
-
- const compareLanguage = assert(this.languages.enabled[n]);
- return this.detailLanguage_.language == compareLanguage.language;
- },
-
- /**
- * @return {boolean} True if the "Move to top" option for |language| should be
- * visible.
- * @private
- */
- showMoveUp_: function() {
- // "Move up" is a no-op for the top language, and redundant with
- // "Move to top" for the 2nd language.
- return !this.isNthLanguage_(0) && !this.isNthLanguage_(1);
- },
-
- /**
- * @return {boolean} True if the "Move down" option for |language| should be
- * visible.
- * @private
- */
- showMoveDown_: function() {
- return this.languages != undefined &&
- !this.isNthLanguage_(this.languages.enabled.length - 1);
- },
-
- /**
- * @param {!Object} change Polymer change object for languages.enabled.*.
- * @return {boolean} True if there are less than 2 languages.
- */
- isHelpTextHidden_: function(change) {
- return this.languages != undefined && this.languages.enabled.length <= 1;
- },
-
- /**
- * @param {string} languageCode The language code identifying a language.
- * @param {string} translateTarget The target language.
- * @return {string} 'target' if |languageCode| matches the target language,
- 'non-target' otherwise.
- */
- isTranslationTarget_: function(languageCode, translateTarget) {
- if (this.languageHelper.convertLanguageCodeForTranslate(languageCode) ==
- translateTarget) {
- return 'target';
- } else {
- return 'non-target';
- }
- },
-
- // <if expr="chromeos">
- /**
- * Applies Chrome OS session tweaks to the menu.
- * @param {!CrActionMenuElement} menu
- * @private
- */
- tweakMenuForCrOS_: function(menu) {
- // In a CrOS multi-user session, the primary user controls the UI language.
- // TODO(michaelpg): The language selection should not be hidden, but should
- // show a policy indicator. crbug.com/648498
- if (this.isSecondaryUser_()) {
- menu.querySelector('#uiLanguageItem').hidden = true;
- }
-
- // The UI language choice doesn't persist for guests.
- if (this.isGuest_) {
- menu.querySelector('#uiLanguageItem').hidden = true;
- }
- },
-
- /**
- * Opens the Manage Input Methods page.
- * @private
- */
- onManageInputMethodsTap_: function() {
- settings.navigateTo(settings.routes.INPUT_METHODS);
- },
-
- /**
- * Handler for tap and <Enter> events on an input method on the main page,
- * which sets it as the current input method.
- * @param {!{model: !{item: !chrome.languageSettingsPrivate.InputMethod},
- * target: !{tagName: string},
- * type: string,
- * key: (string|undefined)}} e
- */
- onInputMethodTap_: function(e) {
- // Taps on the button are handled in onInputMethodOptionsTap_.
- // TODO(dschuyler): The row has two operations that are not clearly
- // delineated. crbug.com/740691
- if (e.target.tagName == 'CR-ICON-BUTTON') {
- return;
- }
-
- // Ignore key presses other than <Enter>.
- if (e.type == 'keypress' && e.key != 'Enter') {
- return;
- }
-
- // Set the input method.
- this.languageHelper.setCurrentInputMethod(e.model.item.id);
- },
-
- /**
- * Opens the input method extension's options page in a new tab (or focuses
- * an existing instance of the IME's options).
- * @param {!{model: !{item: chrome.languageSettingsPrivate.InputMethod}}} e
- * @private
- */
- onInputMethodOptionsTap_: function(e) {
- this.languageHelper.openInputMethodOptions(e.model.item.id);
- },
-
- /**
- * @param {string} id The input method ID.
- * @param {string} currentId The ID of the currently enabled input method.
- * @return {boolean} True if the IDs match.
- * @private
- */
- isCurrentInputMethod_: function(id, currentId) {
- assert(cr.isChromeOS);
- return id == currentId;
- },
-
- /**
- * @param {string} id The input method ID.
- * @param {string} currentId The ID of the currently enabled input method.
- * @return {string} The class for the input method item.
- * @private
- */
- getInputMethodItemClass_: function(id, currentId) {
- assert(cr.isChromeOS);
- return this.isCurrentInputMethod_(id, currentId) ? 'selected' : '';
- },
-
- getInputMethodName_: function(id) {
- assert(cr.isChromeOS);
- const inputMethod =
- this.languages.inputMethods.enabled.find(function(inputMethod) {
- return inputMethod.id == id;
- });
- return inputMethod ? inputMethod.displayName : '';
- },
-
- // </if>
-
- // <if expr="chromeos or is_win">
- /**
- * @return {boolean} True for a secondary user in a multi-profile session.
- * @private
- */
- isSecondaryUser_: function() {
- return cr.isChromeOS && loadTimeData.getBoolean('isSecondaryUser');
- },
-
- /**
- * @param {string} languageCode The language code identifying a language.
- * @param {string} prospectiveUILanguage The prospective UI language.
- * @return {boolean} True if the prospective UI language is set to
- * |languageCode| but requires a restart to take effect.
- * @private
- */
- isRestartRequired_: function(languageCode, prospectiveUILanguage) {
- return prospectiveUILanguage == languageCode &&
- this.languageHelper.requiresRestart();
- },
-
- /** @private */
- onCloseMenu_() {
- if (!this.isChangeInProgress_) {
- return;
- }
- Polymer.dom.flush();
- this.isChangeInProgress_ = false;
- const restartButton = this.$$('#restartButton');
- if (!restartButton) {
- return;
- }
- cr.ui.focusWithoutInk(restartButton);
- },
-
- /**
- * @param {!LanguageState} languageState
- * @param {string} prospectiveUILanguage The chosen UI language.
- * @return {boolean} True if the given language cannot be set as the
- * prospective UI language by the user.
- * @private
- */
- disableUILanguageCheckbox_: function(languageState, prospectiveUILanguage) {
- if (this.detailLanguage_ === undefined) {
- return true;
- }
-
- // UI language setting belongs to the primary user.
- if (this.isSecondaryUser_()) {
- return true;
- }
-
- // If the language cannot be a UI language, we can't set it as the
- // prospective UI language.
- if (!languageState.language.supportsUI) {
- return true;
- }
-
- // Unchecking the currently chosen language doesn't make much sense.
- if (languageState.language.code == prospectiveUILanguage) {
- return true;
- }
-
- // Check if the language is prohibited by the current "AllowedLanguages"
- // policy.
- if (languageState.language.isProhibitedLanguage) {
- return true;
- }
-
- // Otherwise, the prospective language can be changed to this language.
- return false;
- },
-
- /**
- * Handler for changes to the UI language checkbox.
- * @param {!{target: !Element}} e
- * @private
- */
- onUILanguageChange_: function(e) {
- // We don't support unchecking this checkbox. TODO(michaelpg): Ask for a
- // simpler widget.
- assert(e.target.checked);
- this.isChangeInProgress_ = true;
- this.languageHelper.setProspectiveUILanguage(
- this.detailLanguage_.language.code);
- this.languageHelper.moveLanguageToFront(this.detailLanguage_.language.code);
-
- this.closeMenuSoon_();
- },
-
- /**
- * Checks whether the prospective UI language (the pref that indicates what
- * language to use in Chrome) matches the current language. This pref is used
- * only on Chrome OS and Windows; we don't control the UI language elsewhere.
- * @param {string} languageCode The language code identifying a language.
- * @param {string} prospectiveUILanguage The prospective UI language.
- * @return {boolean} True if the given language matches the prospective UI
- * pref (which may be different from the actual UI language).
- * @private
- */
- isProspectiveUILanguage_: function(languageCode, prospectiveUILanguage) {
- return languageCode == prospectiveUILanguage;
- },
-
- /**
- * @param {string} prospectiveUILanguage
- * @return {string}
- * @private
- */
- getProspectiveUILanguageName_: function(prospectiveUILanguage) {
- return this.languageHelper.getLanguage(prospectiveUILanguage).displayName;
- },
-
- /**
- * Handler for the restart button.
- * @private
- */
- onRestartTap_: function() {
- // <if expr="chromeos">
- settings.LifetimeBrowserProxyImpl.getInstance().signOutAndRestart();
- // </if>
- // <if expr="is_win">
- settings.LifetimeBrowserProxyImpl.getInstance().restart();
- // </if>
- },
- // </if>
-
- /**
- * @param {!LanguageState|undefined} languageState
- * @param {string} targetLanguageCode The default translate target language.
- * @return {boolean} True if the translate checkbox should be disabled.
- * @private
- */
- disableTranslateCheckbox_: function(languageState, targetLanguageCode) {
- if (languageState == undefined || languageState.language == undefined ||
- !languageState.language.supportsTranslate) {
- return true;
- }
-
- if (this.languageHelper.isOnlyTranslateBlockedLanguage(languageState)) {
- return true;
- }
-
- return this.languageHelper.convertLanguageCodeForTranslate(
- languageState.language.code) == targetLanguageCode;
- },
-
- /**
- * Handler for changes to the translate checkbox.
- * @param {!{target: !Element}} e
- * @private
- */
- onTranslateCheckboxChange_: function(e) {
- if (e.target.checked) {
- this.languageHelper.enableTranslateLanguage(
- this.detailLanguage_.language.code);
- } else {
- this.languageHelper.disableTranslateLanguage(
- this.detailLanguage_.language.code);
- }
- this.closeMenuSoon_();
- },
-
- /**
- * Returns "complex" if the menu includes checkboxes, which should change the
- * spacing of items and show a separator in the menu.
- * @param {boolean} translateEnabled
- * @return {string}
- */
- getMenuClass_: function(translateEnabled) {
- if (translateEnabled || cr.isChromeOS || cr.isWindows) {
- return 'complex';
- }
- return '';
- },
-
- /**
- * Moves the language to the top of the list.
- * @private
- */
- onMoveToTopTap_: function() {
- /** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
- this.languageHelper.moveLanguageToFront(this.detailLanguage_.language.code);
- },
-
- /**
- * Moves the language up in the list.
- * @private
- */
- onMoveUpTap_: function() {
- /** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
- this.languageHelper.moveLanguage(
- this.detailLanguage_.language.code, true /* upDirection */);
- },
-
- /**
- * Moves the language down in the list.
- * @private
- */
- onMoveDownTap_: function() {
- /** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
- this.languageHelper.moveLanguage(
- this.detailLanguage_.language.code, false /* upDirection */);
- },
-
- /**
- * Disables the language.
- * @private
- */
- onRemoveLanguageTap_: function() {
- /** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
- this.languageHelper.disableLanguage(this.detailLanguage_.language.code);
- },
-
- /**
- * @return {string}
- * @private
- */
- getLanguageListTwoLine_: function() {
- return cr.isChromeOS || cr.isWindows ? 'two-line' : '';
- },
-
- // <if expr="not is_macosx">
- /**
- * Returns the value to use as the |pref| attribute for the policy indicator
- * of spellcheck languages, based on whether or not the language is enabled.
- * @param {boolean} isEnabled Whether the language is enabled or not.
- */
- getIndicatorPrefForManagedSpellcheckLanguage_(isEnabled) {
- return isEnabled ?
- this.get('spellcheck.forced_dictionaries', this.prefs) :
- this.get('spellcheck.blacklisted_dictionaries', this.prefs);
- },
-
- /**
- * Returns an array of enabled languages, plus spellcheck languages that are
- * force-enabled by policy.
- * @return {!Array<!LanguageState|!ForcedLanguageState>}
- * @private
- */
- getSpellCheckLanguages_: function() {
- const supportedSpellcheckLanguages =
- /** @type {!Array<!LanguageState|!ForcedLanguageState>} */ (
- this.languages.enabled.filter(
- (item) => item.language.supportsSpellcheck));
- const supportedSpellcheckLanguagesSet =
- new Set(supportedSpellcheckLanguages.map(x => x.language.code));
-
- this.languages.forcedSpellCheckLanguages.forEach(forcedLanguage => {
- if (!supportedSpellcheckLanguagesSet.has(forcedLanguage.language.code)) {
- supportedSpellcheckLanguages.push(forcedLanguage);
- }
- });
-
- return supportedSpellcheckLanguages;
- },
-
- /** @private */
- updateSpellcheckLanguages_: function() {
- if (this.languages == undefined) {
- return;
- }
-
- this.set('spellCheckLanguages_', this.getSpellCheckLanguages_());
-
- // Notify Polymer of subproperties that might have changed on the items in
- // the spellCheckLanguages_ array, to make sure the UI updates. Polymer
- // would otherwise not notice the changes in the subproperties, as some of
- // them are references to those from |this.languages.enabled|. It would be
- // possible to |this.linkPaths()| objects from |this.languages.enabled| to
- // |this.spellCheckLanguages_|, but that would require complex housekeeping
- // to |this.unlinkPaths()| as |this.languages.enabled| changes.
- for (let i = 0; i < this.spellCheckLanguages_.length; i++) {
- this.notifyPath(`spellCheckLanguages_.${i}.isManaged`);
- this.notifyPath(`spellCheckLanguages_.${i}.spellCheckEnabled`);
- this.notifyPath(
- `spellCheckLanguages_.${i}.downloadDictionaryFailureCount`);
- }
-
- if (this.spellCheckLanguages_.length === 0) {
- // If there are no supported spell check languages, automatically turn
- // off spell check to indicate no spell check will happen.
- this.setPrefValue('browser.enable_spellchecking', false);
- }
-
- if (this.spellCheckLanguages_.length === 1) {
- const singleLanguage = this.spellCheckLanguages_[0];
-
- // Hide list of spell check languages if there is only 1 language
- // and we don't need to display any errors for that language
- this.hideSpellCheckLanguages_ = !singleLanguage.isManaged &&
- singleLanguage.downloadDictionaryFailureCount === 0;
-
- // Turn off spell check if spell check for the 1 remaining language is off
- if (!singleLanguage.spellCheckEnabled) {
- this.setPrefValue('browser.enable_spellchecking', false);
- this.languageSyncedWithBrowserEnableSpellchecking_ =
- singleLanguage.language.code;
- }
-
- // Undo the sync if spell check appeared as turned off for the language
- // because a download was still in progress. This only occurs when
- // Settings is loaded for the very first time and dictionaries have not
- // been downloaded yet.
- if (this.languageSyncedWithBrowserEnableSpellchecking_ ===
- singleLanguage.language.code &&
- singleLanguage.spellCheckEnabled) {
- this.setPrefValue('browser.enable_spellchecking', true);
- }
- } else {
- this.hideSpellCheckLanguages_ = false;
- this.languageSyncedWithBrowserEnableSpellchecking_ = undefined;
- }
- },
-
- /** @private */
- updateSpellcheckEnabled_: function() {
- if (this.prefs == undefined) {
- return;
- }
-
- // If there is only 1 language, we hide the list of languages so users
- // are unable to toggle on/off spell check specifically for the 1 language.
- // Therefore, we need to treat the toggle for `browser.enable_spellchecking`
- // as the toggle for the 1 language as well.
- if (this.spellCheckLanguages_.length === 1) {
- this.languageHelper.toggleSpellCheck(
- this.spellCheckLanguages_[0].language.code,
- !!this.getPref('browser.enable_spellchecking').value);
- }
-
- // <if expr="_google_chrome">
- // When spell check is disabled, automatically disable using the spelling
- // service. This resets the spell check option to 'Use basic spell check'
- // when spell check is turned off. This check is in an observer so that it
- // can also correct any users who land on the Settings page and happen
- // to have spelling service enabled but spell check disabled.
- if (!this.getPref('browser.enable_spellchecking').value) {
- this.setPrefValue('spellcheck.use_spelling_service', false);
- }
- // </if>
- },
-
- /**
- * Opens the Custom Dictionary page.
- * @private
- */
- onEditDictionaryTap_: function() {
- settings.navigateTo(settings.routes.EDIT_DICTIONARY);
- },
-
- /**
- * Handler for enabling or disabling spell check for a specific language.
- * @param {!{target: Element, model: !{item: !LanguageState}}} e
- */
- onSpellCheckLanguageChange_: function(e) {
- const item = e.model.item;
- if (!item.language.supportsSpellcheck) {
- return;
- }
-
- this.languageHelper.toggleSpellCheck(
- item.language.code, !item.spellCheckEnabled);
- },
-
- /**
- * Handler to initiate another attempt at downloading the spell check
- * dictionary for a specified language.
- * @param {!{target: Element, model: !{item: !LanguageState}}} e
- */
- onRetryDictionaryDownloadClick_: function(e) {
- assert(this.errorsGreaterThan_(
- e.model.item.downloadDictionaryFailureCount, 0));
- this.languageHelper.retryDownloadDictionary(e.model.item.language.code);
- },
-
- /**
- * Handler for clicking on the name of the language. The action taken must
- * match the control that is available.
- * @param {!{target: Element, model: !{item: !LanguageState}}} e
- */
- onSpellCheckNameClick_: function(e) {
- assert(!this.isSpellCheckNameClickDisabled_(e.model.item));
- this.onSpellCheckLanguageChange_(e);
- },
-
- /**
- * Name only supports clicking when language is not managed, supports
- * spellcheck, and the dictionary has been downloaded with no errors.
- * @param {!LanguageState|!ForcedLanguageState} item
- * @return {boolean}
- * @private
- */
- isSpellCheckNameClickDisabled_: function(item) {
- return item.isManaged || !item.language.supportsSpellcheck ||
- item.downloadDictionaryFailureCount > 0;
- },
- // </if>
-
- /**
- * Returns either the "selected" class, if the language matches the
- * prospective UI language, or an empty string. Languages can only be
- * selected on Chrome OS and Windows.
- * @param {string} languageCode The language code identifying a language.
- * @param {string} prospectiveUILanguage The prospective UI language.
- * @return {string} The class name for the language item.
- * @private
- */
- getLanguageItemClass_: function(languageCode, prospectiveUILanguage) {
- if ((cr.isChromeOS || cr.isWindows) &&
- languageCode == prospectiveUILanguage) {
- return 'selected';
- }
- return '';
- },
-
- /**
- * @return {string|undefined}
- * @private
- */
- getSpellCheckSubLabel_: function() {
- // <if expr="not is_macosx">
- if (this.spellCheckLanguages_.length === 0) {
- return this.i18n('spellCheckDisabledReason');
- }
- // </if>
-
- return undefined;
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onDotsTap_: function(e) {
- // Set a copy of the LanguageState object since it is not data-bound to the
- // languages model directly.
- this.detailLanguage_ = /** @type {!LanguageState} */ (Object.assign(
- {},
- /** @type {!{model: !{item: !LanguageState}}} */ (e).model.item));
-
- // Ensure the template has been stamped.
- let menu = /** @type {?CrActionMenuElement} */ (this.$.menu.getIfExists());
- if (!menu) {
- menu = /** @type {!CrActionMenuElement} */ (this.$.menu.get());
- // <if expr="chromeos">
- this.tweakMenuForCrOS_(menu);
- // </if>
- }
-
- menu.showAt(/** @type {!Element} */ (e.target));
- },
-
- /**
- * @param {boolean} newVal The new value of languagesOpened_.
- * @param {boolean} oldVal The old value of languagesOpened_.
- * @private
- */
- onLanguagesOpenedChanged_: function(newVal, oldVal) {
- if (!oldVal && newVal) {
- chrome.send(
- 'metricsHandler:recordBooleanHistogram',
- [LANGUAGE_SETTING_IS_SHOWN_UMA_NAME, true]);
- }
- },
-
- /**
- * Closes the shared action menu after a short delay, so when a checkbox is
- * clicked it can be seen to change state before disappearing.
- * @private
- */
- closeMenuSoon_: function() {
- const menu = /** @type {!CrActionMenuElement} */ (this.$.menu.get());
- setTimeout(function() {
- if (menu.open) {
- menu.close();
- }
- }, settings.kMenuCloseDelay);
- },
-
- /**
- * Toggles the expand button within the element being listened to.
- * @param {!Event} e
- * @private
- */
- toggleExpandButton_: function(e) {
- // The expand button handles toggling itself.
- const expandButtonTag = 'CR-EXPAND-BUTTON';
- if (e.target.tagName == expandButtonTag) {
- return;
- }
-
- if (!e.currentTarget.hasAttribute('actionable')) {
- return;
- }
-
- /** @type {!CrExpandButtonElement} */
- const expandButton = e.currentTarget.querySelector(expandButtonTag);
- assert(expandButton);
- expandButton.expanded = !expandButton.expanded;
- cr.ui.focusWithoutInk(expandButton);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_types.js b/chromium/chrome/browser/resources/settings/languages_page/languages_types.js
deleted file mode 100644
index d8350852624..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/languages_types.js
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Closure typedefs for dictionaries and interfaces used by
- * language settings.
- */
-
-/**
- * Settings and state for a particular enabled language.
- * @typedef {{
- * language: !chrome.languageSettingsPrivate.Language,
- * removable: boolean,
- * spellCheckEnabled: boolean,
- * translateEnabled: boolean,
- * isManaged: boolean,
- * downloadDictionaryFailureCount: number,
- * downloadDictionaryStatus:
- * ?chrome.languageSettingsPrivate.SpellcheckDictionaryStatus,
- * }}
- */
-let LanguageState;
-
-/**
- * Settings and state for a policy-enforced spellcheck language.
- * @typedef {{
- * language: !chrome.languageSettingsPrivate.Language,
- * isManaged: boolean,
- * downloadDictionaryFailureCount: number,
- * downloadDictionaryStatus:
- * ?chrome.languageSettingsPrivate.SpellcheckDictionaryStatus,
- * }}
- */
-let ForcedLanguageState;
-
-/**
- * Input method data to expose to consumers (Chrome OS only).
- * supported: an array of supported input methods set once at initialization.
- * enabled: an array of the currently enabled input methods.
- * currentId: ID of the currently active input method.
- * @typedef {{
- * supported: !Array<!chrome.languageSettingsPrivate.InputMethod>,
- * enabled: !Array<!chrome.languageSettingsPrivate.InputMethod>,
- * currentId: string,
- * }}
- */
-let InputMethodsModel;
-
-/**
- * Languages data to expose to consumers.
- * supported: an array of languages, ordered alphabetically, set once
- * at initialization.
- * enabled: an array of enabled language states, ordered by preference.
- * translateTarget: the default language to translate into.
- * prospectiveUILanguage: the "prospective" UI language, i.e., the one to be
- * used on next restart. Matches the current UI language preference unless
- * the user has chosen a different language without restarting. May differ
- * from the actually used language (navigator.language). Chrome OS and
- * Windows only.
- * inputMethods: the InputMethodsModel (Chrome OS only).
- * forcedSpellCheckLanguages: an array of spellcheck languages that are not in
- * |enabled|.
- * @typedef {{
- * supported: !Array<!chrome.languageSettingsPrivate.Language>,
- * enabled: !Array<!LanguageState>,
- * translateTarget: string,
- * prospectiveUILanguage: (string|undefined),
- * inputMethods: (!InputMethodsModel|undefined),
- * forcedSpellCheckLanguages: !Array<!ForcedLanguageState>,
- * }}
- */
-let LanguagesModel;
-
-/**
- * Helper methods for reading and writing language settings.
- * @interface
- */
-class LanguageHelper {
- /** @return {!Promise} */
- whenReady() {}
-
- // <if expr="chromeos or is_win">
- /**
- * Sets the prospective UI language to the chosen language. This won't affect
- * the actual UI language until a restart.
- * @param {string} languageCode
- */
- setProspectiveUILanguage(languageCode) {}
-
- /**
- * True if the prospective UI language has been changed.
- * @return {boolean}
- */
- requiresRestart() {}
-
- // </if>
-
- /**
- * @return {string} The language code for ARC IMEs.
- */
- getArcImeLanguageCode() {}
-
- /**
- * @param {string} languageCode
- * @return {boolean}
- */
- isLanguageCodeForArcIme(languageCode) {}
-
- /**
- * @param {string} languageCode
- * @return {boolean}
- */
- isLanguageEnabled(languageCode) {}
-
- /**
- * Enables the language, making it available for spell check and input.
- * @param {string} languageCode
- */
- enableLanguage(languageCode) {}
-
- /**
- * Disables the language.
- * @param {string} languageCode
- */
- disableLanguage(languageCode) {}
-
- /**
- * Returns true iff provided languageState is the only blocked language.
- * @param {!LanguageState} languageState
- * @return {boolean}
- */
- isOnlyTranslateBlockedLanguage(languageState) {}
-
- /**
- * Returns true iff provided languageState can be disabled.
- * @param {!LanguageState} languageState
- * @return {boolean}
- */
- canDisableLanguage(languageState) {}
-
- /**
- * @param {!chrome.languageSettingsPrivate.Language} language
- * @return {boolean} true if the given language can be enabled
- */
- canEnableLanguage(language) {}
-
- /**
- * Moves the language in the list of enabled languages by the given offset.
- * @param {string} languageCode
- * @param {boolean} upDirection True if we need to move toward the front,
- * false if we need to move toward the back.
- */
- moveLanguage(languageCode, upDirection) {}
-
- /**
- * Moves the language directly to the front of the list of enabled languages.
- * @param {string} languageCode
- */
- moveLanguageToFront(languageCode) {}
-
- /**
- * Enables translate for the given language by removing the translate
- * language from the blocked languages preference.
- * @param {string} languageCode
- */
- enableTranslateLanguage(languageCode) {}
-
- /**
- * Disables translate for the given language by adding the translate
- * language to the blocked languages preference.
- * @param {string} languageCode
- */
- disableTranslateLanguage(languageCode) {}
-
- /**
- * Enables or disables spell check for the given language.
- * @param {string} languageCode
- * @param {boolean} enable
- */
- toggleSpellCheck(languageCode, enable) {}
-
- /**
- * Converts the language code for translate. There are some differences
- * between the language set the Translate server uses and that for
- * Accept-Language.
- * @param {string} languageCode
- * @return {string} The converted language code.
- */
- convertLanguageCodeForTranslate(languageCode) {}
-
- /**
- * Given a language code, returns just the base language. E.g., converts
- * 'en-GB' to 'en'.
- * @param {string} languageCode
- * @return {string}
- */
- getLanguageCodeWithoutRegion(languageCode) {}
-
- /**
- * @param {string} languageCode
- * @return {!chrome.languageSettingsPrivate.Language|undefined}
- */
- getLanguage(languageCode) {}
-
- /** @param {string} languageCode */
- retryDownloadDictionary(languageCode) {}
-
- // <if expr="chromeos">
- /** @param {string} id */
- addInputMethod(id) {}
-
- /** @param {string} id */
- removeInputMethod(id) {}
-
- /** @param {string} id */
- setCurrentInputMethod(id) {}
-
- /**
- * @param {string} languageCode
- * @return {!Array<!chrome.languageSettingsPrivate.InputMethod>}
- */
- getInputMethodsForLanguage(languageCode) {}
-
- /**
- * @param {!chrome.languageSettingsPrivate.InputMethod} inputMethod
- * @return {boolean}
- */
- isComponentIme(inputMethod) {}
-
- /** @param {string} id Input method ID. */
- openInputMethodOptions(id) {}
- // </if>
-}
diff --git a/chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.html b/chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.html
deleted file mode 100644
index 96a23def8a8..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-manage-input-methods-page">
- <template>
- <style include="settings-shared"></style>
- <div class="settings-box continuation"
- hidden$="[[!inputMethodsLimitedByPolicy_(
- prefs.settings.language.allowed_input_methods)]]">
- <iron-icon class="policy" icon="cr20:domain"></iron-icon>
- <div>$i18n{inputMethodsManagedbyPolicy}</div>
- </div>
- <div class="settings-box first block">
- <template is="dom-repeat" items="[[languageList_]]">
- <h2>[[item.language.displayName]]</h2>
- <div class="list-frame vertical-list">
- <template is="dom-repeat" items="[[item.inputMethods]]">
- <div class="list-item">
- <div class="language-name">
- <cr-checkbox checked="[[item.enabled]]"
- on-change="onCheckboxChange_"
- disabled="[[!enableInputMethodCheckbox_(
- item, languages.inputMethods.enabled.*)]]">
- <span>[[item.displayName]]</span>
- </cr-checkbox>
- </div>
- </div>
- </template>
- </div>
- </template>
- </div>
- </template>
- <script src="manage_input_methods_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.js b/chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.js
deleted file mode 100644
index b2b717581f7..00000000000
--- a/chromium/chrome/browser/resources/settings/languages_page/manage_input_methods_page.js
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-manage-input-methods-page' is a sub-page for enabling
- * and disabling input methods. Input methods are grouped by base languages to
- * avoid showing duplicate or ambiguous input methods.
- *
- * TODO(hsuregan): Move to OS settings.
- */
-Polymer({
- is: 'settings-manage-input-methods-page',
-
- properties: {
- /** @type {!LanguagesModel|undefined} */
- languages: {
- type: Object,
- notify: true,
- },
-
- /** @type {!LanguageHelper} */
- languageHelper: Object,
-
- /**
- * List of enabled languages with the input methods to show.
- * @private {!Array<
- * !{language: !chrome.languageSettingsPrivate.Language,
- * inputMethods: !Array<!chrome.languageSettingsPrivate.InputMethod>
- * }>}
- */
- languageList_: {
- type: Array,
- value: function() {
- return [];
- },
- },
- },
-
- observers: [
- 'availableInputMethodsChanged_(languages.enabled.*,' +
- 'languages.inputMethods.supported.*)',
- 'enabledInputMethodsChanged_(languages.inputMethods.enabled.*)',
- ],
-
- /** @private */
- availableInputMethodsChanged_: function() {
- this.populateLanguageList_();
- },
-
- /** @private */
- enabledInputMethodsChanged_: function() {
- this.populateLanguageList_();
- },
-
- /**
- * Handler for an input method checkbox.
- * @param {!{model: !{item: chrome.languageSettingsPrivate.InputMethod},
- * target: !Element}} e
- * @private
- */
- onCheckboxChange_: function(e) {
- // TODO(michaelpg): Show confirmation dialog for 3rd-party IMEs.
- const id = e.model.item.id;
- if (e.target.checked) {
- this.languageHelper.addInputMethod(id);
- } else {
- this.languageHelper.removeInputMethod(id);
- }
- },
-
- /**
- * Returns true if the input method can be added/removed.
- * @param {!chrome.languageSettingsPrivate.InputMethod} targetInputMethod
- * @param {!Object} change Polymer change object (provided in the HTML so this
- * gets called whenever languages.inputMethods.enabled.* changes).
- * @return {boolean}
- * @private
- */
- enableInputMethodCheckbox_: function(targetInputMethod, change) {
- if (targetInputMethod.isProhibitedByPolicy) {
- return false;
- }
-
- if (!targetInputMethod.enabled) {
- return true;
- }
-
- // Third-party IMEs can always be removed.
- if (!this.languageHelper.isComponentIme(targetInputMethod)) {
- return true;
- }
-
- // Can be removed as long as there is another component IME.
- return this.languages.inputMethods.enabled.some(function(inputMethod) {
- return inputMethod != targetInputMethod &&
- this.languageHelper.isComponentIme(inputMethod);
- }, this);
- },
-
- /**
- * Creates the list of languages and their input methods as the data source
- * for the view.
- * @private
- */
- populateLanguageList_: function() {
- const languageList = [];
-
- // Languages that have already been listed further up.
- const /** !Set<string> */ usedLanguages = new Set();
-
- // Add languages in preference order. However, if there are multiple
- // enabled variants of the same base language, group them all as the base
- // language instead of showing each variant individually. This prevents us
- // from displaying duplicate input methods under different variants.
- for (let i = 0; i < this.languages.enabled.length; i++) {
- const languageState = this.languages.enabled[i];
- // Skip the language if we have already included it or its base language.
- if (usedLanguages.has(languageState.language.code)) {
- continue;
- }
- const baseLanguageCode = this.languageHelper.getLanguageCodeWithoutRegion(
- languageState.language.code);
- if (usedLanguages.has(baseLanguageCode)) {
- continue;
- }
-
- // Find the other languages further down in the preferred languages list
- // which also use this language's base language code.
- const languageFamilyCodes = [languageState.language.code];
- for (let j = i + 1; j < this.languages.enabled.length; j++) {
- const otherCode = this.languages.enabled[j].language.code;
- if (this.languageHelper.getLanguageCodeWithoutRegion(otherCode) ==
- baseLanguageCode) {
- languageFamilyCodes.push(this.languages.enabled[j].language.code);
- }
- }
-
- const combinedInputMethods =
- this.getInputMethodsForLanguages(languageFamilyCodes);
-
- // Skip the language if it has no new input methods.
- if (!combinedInputMethods.length) {
- continue;
- }
-
- // Add the language or base language.
- let displayLanguage = languageState.language;
- if (languageFamilyCodes.length > 1) {
- const baseLanguage = this.languageHelper.getLanguage(baseLanguageCode);
- if (baseLanguage) {
- displayLanguage = baseLanguage;
- }
- }
- languageList.push({
- language: displayLanguage,
- inputMethods: combinedInputMethods,
- });
- for (let k = 0; k < languageFamilyCodes.length; k++) {
- usedLanguages.add(languageFamilyCodes[k]);
- }
- }
-
- // Add ARC IMEs to the bottom if any.
- const arcInputMethods = this.getInputMethodsForLanguages(
- [this.languageHelper.getArcImeLanguageCode()]);
- if (arcInputMethods.length) {
- languageList.push({
- language: this.languageHelper.getLanguage(
- this.languageHelper.getArcImeLanguageCode()),
- inputMethods: arcInputMethods,
- });
- }
-
- this.languageList_ = languageList;
- this.notifyInputMethodsChanged_();
- },
-
- /**
- * Returns the input methods that support any of the given languages.
- * @param {!Array<string>} languageCodes
- * @return {!Array<!chrome.languageSettingsPrivate.InputMethod>}
- * @private
- */
- getInputMethodsForLanguages: function(languageCodes) {
- // Input methods that have already been listed for this language.
- const /** !Set<string> */ usedInputMethods = new Set();
- /** @type {!Array<chrome.languageSettingsPrivate.InputMethod>} */
- const combinedInputMethods = [];
- for (let i = 0; i < languageCodes.length; i++) {
- const inputMethods =
- this.languageHelper.getInputMethodsForLanguage(languageCodes[i]);
- // Get the language's unused input methods and mark them as used.
- const newInputMethods = inputMethods.filter(function(inputMethod) {
- if (usedInputMethods.has(inputMethod.id)) {
- return false;
- }
- usedInputMethods.add(inputMethod.id);
- return true;
- });
- [].push.apply(combinedInputMethods, newInputMethods);
- }
- return combinedInputMethods;
- },
-
- // TODO(Polymer/polymer#3603): We have to notify Polymer of properties that
- // may have changed on nested objects, even when the outer property itself
- // is set to a new array.
- // TODO(michaelpg): Test this behavior.
- /** @private */
- notifyInputMethodsChanged_: function() {
- for (let i = 0; i < this.languageList_.length; i++) {
- for (let j = 0; j < this.languageList_[i].inputMethods.length; j++) {
- this.notifyPath(
- 'languageList_.' + i + '.inputMethods.' + j + '.enabled',
- this.languageList_[i].inputMethods[j].enabled);
- }
- }
- },
-
- /**
- * @param {Object} allowedInputMethods
- * @return {boolean}
- * @private
- */
- inputMethodsLimitedByPolicy_: function(allowedInputMethods) {
- return !!allowedInputMethods && allowedInputMethods.value.length > 0;
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/lazy_load.html b/chromium/chrome/browser/resources/settings/lazy_load.html
deleted file mode 100644
index 24e38535c00..00000000000
--- a/chromium/chrome/browser/resources/settings/lazy_load.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<html>
-<head></head>
-<body>
- <link rel="import" href="a11y_page/a11y_page.html">
- <link rel="import" href="downloads_page/downloads_page.html">
- <link rel="import" href="languages_page/languages_page.html">
- <link rel="import" href="printing_page/printing_page.html">
- <link rel="import" href="privacy_page/privacy_page.html">
- <link rel="import" href="reset_page/reset_page.html">
-
- <if expr="chromeos">
- <link rel="import" href="date_time_page/date_time_page.html">
- </if>
-
- <if expr="not chromeos">
- <link rel="import" href="system_page/system_page.html">
- </if>
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/settings/lifetime_browser_proxy.html b/chromium/chrome/browser/resources/settings/lifetime_browser_proxy.html
deleted file mode 100644
index 8cccd3283b7..00000000000
--- a/chromium/chrome/browser/resources/settings/lifetime_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="lifetime_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/lifetime_browser_proxy.js b/chromium/chrome/browser/resources/settings/lifetime_browser_proxy.js
deleted file mode 100644
index 396539708ff..00000000000
--- a/chromium/chrome/browser/resources/settings/lifetime_browser_proxy.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class LifetimeBrowserProxy {
- // Triggers a browser restart.
- restart() {}
-
- // Triggers a browser relaunch.
- relaunch() {}
-
- // <if expr="chromeos">
- // First signs out current user and then performs a restart.
- signOutAndRestart() {}
-
- /**
- * Triggers a factory reset. The parameter indicates whether to install a
- * TPM firmware update (if available) after the reset.
- *
- * @param {boolean} requestTpmFirmwareUpdate
- */
- factoryReset(requestTpmFirmwareUpdate) {}
- // </if>
- }
-
- /**
- * @implements {settings.LifetimeBrowserProxy}
- */
- class LifetimeBrowserProxyImpl {
- /** @override */
- restart() {
- chrome.send('restart');
- }
-
- /** @override */
- relaunch() {
- chrome.send('relaunch');
- }
-
- // <if expr="chromeos">
- /** @override */
- signOutAndRestart() {
- chrome.send('signOutAndRestart');
- }
-
- /** @override */
- factoryReset(requestTpmFirmwareUpdate) {
- chrome.send('factoryReset', [requestTpmFirmwareUpdate]);
- }
- // </if>
- }
-
- cr.addSingletonGetter(LifetimeBrowserProxyImpl);
-
- return {
- LifetimeBrowserProxy: LifetimeBrowserProxy,
- LifetimeBrowserProxyImpl: LifetimeBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/manifest.json b/chromium/chrome/browser/resources/settings/manifest.json
deleted file mode 100644
index acbaa3c103e..00000000000
--- a/chromium/chrome/browser/resources/settings/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "$i18nRaw{name}",
- "display": "standalone",
- "icons": [
- {
- "src": "icon-192.png",
- "sizes": "192x192",
- "type": "image/png"
- }
- ],
- "start_url": "/",
- "theme_color": "#254FAE"
-}
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/OWNERS b/chromium/chrome/browser/resources/settings/multidevice_page/OWNERS
deleted file mode 100644
index d3caaf80842..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-file://chromeos/components/multidevice/OWNERS
-
-# COMPONENT: UI>Multidevice
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.html
deleted file mode 100644
index fee74ac6ed2..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="multidevice_constants.html">
-<script src="multidevice_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js
deleted file mode 100644
index 5410aac5a20..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * An object containing messages for web permissisions origin
- * and the messages multidevice feature state.
- *
- * @typedef {{origin: string,
- * enabled: boolean}}
- */
-settings.AndroidSmsInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class MultiDeviceBrowserProxy {
- showMultiDeviceSetupDialog() {}
-
- /** @return {!Promise<!MultiDevicePageContentData>} */
- getPageContentData() {}
-
- /**
- * @param {!settings.MultiDeviceFeature} feature The feature whose state
- * should be set.
- * @param {boolean} enabled Whether the feature should be turned off or on.
- * @param {string=} opt_authToken Proof that the user is authenticated.
- * Needed to enable Smart Lock, and Better Together Suite if the Smart
- * Lock user pref is enabled.
- * @return {!Promise<boolean>} Whether the operation was successful.
- */
- setFeatureEnabledState(feature, enabled, opt_authToken) {}
-
- removeHostDevice() {}
-
- retryPendingHostSetup() {}
-
- /**
- * Called when the "Set Up" button is clicked to open the Android Messages
- * PWA.
- */
- setUpAndroidSms() {}
-
- /**
- * Returns the value of the preference controlling whether Smart Lock may be
- * used to sign-in the user (as opposed to unlocking the screen).
- * @return {!Promise<boolean>}
- */
- getSmartLockSignInEnabled() {}
-
- /**
- * Sets the value of the preference controlling whether Smart Lock may be
- * used to sign-in the user (as opposed to unlocking the screen).
- * @param {boolean} enabled
- * @param {string=} opt_authToken Authentication token used to restrict
- * edit access to the Smart Lock sign-in pref.
- */
- setSmartLockSignInEnabled(enabled, opt_authToken) {}
-
- /**
- * Returns the value of the preference controlling whether Smart Lock
- * sign-in is allowed.
- * @return {!Promise<boolean>}
- */
- getSmartLockSignInAllowed() {}
-
- /**
- * Returns android messages info with messages feature state
- * and messages for web permissions origin.
- * @return {!Promise<!settings.AndroidSmsInfo>} Android SMS Info
- */
- getAndroidSmsInfo() {}
- }
-
- /**
- * @implements {settings.MultiDeviceBrowserProxy}
- */
- class MultiDeviceBrowserProxyImpl {
- /** @override */
- showMultiDeviceSetupDialog() {
- chrome.send('showMultiDeviceSetupDialog');
- }
-
- /** @override */
- getPageContentData() {
- return cr.sendWithPromise('getPageContentData');
- }
-
- /** @override */
- setFeatureEnabledState(feature, enabled, opt_authToken) {
- return cr.sendWithPromise(
- 'setFeatureEnabledState', feature, enabled, opt_authToken);
- }
-
- /** @override */
- removeHostDevice() {
- chrome.send('removeHostDevice');
- }
-
- /** @override */
- retryPendingHostSetup() {
- chrome.send('retryPendingHostSetup');
- }
-
- /** @override */
- setUpAndroidSms() {
- chrome.send('setUpAndroidSms');
- }
-
- /** @override */
- getSmartLockSignInEnabled() {
- return cr.sendWithPromise('getSmartLockSignInEnabled');
- }
-
- /** @override */
- setSmartLockSignInEnabled(enabled, opt_authToken) {
- chrome.send('setSmartLockSignInEnabled', [enabled, opt_authToken]);
- }
-
- /** @override */
- getSmartLockSignInAllowed() {
- return cr.sendWithPromise('getSmartLockSignInAllowed');
- }
-
- /** @override */
- getAndroidSmsInfo() {
- return cr.sendWithPromise('getAndroidSmsInfo');
- }
- }
-
- cr.addSingletonGetter(MultiDeviceBrowserProxyImpl);
-
- return {
- MultiDeviceBrowserProxy: MultiDeviceBrowserProxy,
- MultiDeviceBrowserProxyImpl: MultiDeviceBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.html
deleted file mode 100644
index 9ef15ac02e5..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="multidevice_constants.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js
deleted file mode 100644
index f60c837c8b2..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /**
- * The possible statuses of hosts on the logged in account that determine the
- * page content. Note that this is based on (and must include an analog of
- * all values in) the HostStatus enum in
- * services/multidevice_setup/public/mojom/multidevice_setup.mojom.
- * @enum {number}
- */
- MultiDeviceSettingsMode = {
- NO_ELIGIBLE_HOSTS: 0,
- NO_HOST_SET: 1,
- HOST_SET_WAITING_FOR_SERVER: 2,
- HOST_SET_WAITING_FOR_VERIFICATION: 3,
- HOST_SET_VERIFIED: 4,
- };
-
- /**
- * Enum of MultiDevice features. Note that this is copied from (and must
- * include an analog of all values in) the Feature enum in
- * //chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.
- * @enum {number}
- */
- MultiDeviceFeature = {
- BETTER_TOGETHER_SUITE: 0,
- INSTANT_TETHERING: 1,
- MESSAGES: 2,
- SMART_LOCK: 3,
- };
-
- /**
- * Possible states of MultiDevice features. Note that this is copied from (and
- * must include an analog of all values in) the FeatureState enum in
- * //chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.
- * @enum {number}
- */
- MultiDeviceFeatureState = {
- PROHIBITED_BY_POLICY: 0,
- DISABLED_BY_USER: 1,
- ENABLED_BY_USER: 2,
- NOT_SUPPORTED_BY_CHROMEBOOK: 3,
- NOT_SUPPORTED_BY_PHONE: 4,
- UNAVAILABLE_NO_VERIFIED_HOST: 5,
- UNAVAILABLE_INSUFFICIENT_SECURITY: 6,
- UNAVAILABLE_SUITE_DISABLED: 7,
- FURTHER_SETUP_REQUIRED: 8,
- };
-
- return {
- MultiDeviceSettingsMode: MultiDeviceSettingsMode,
- MultiDeviceFeature: MultiDeviceFeature,
- MultiDeviceFeatureState: MultiDeviceFeatureState,
- };
-});
-
-/**
- * Container for the initial data that the page requires in order to display
- * the correct content. It is also used for receiving status updates during
- * use. Note that the host device may be verified (enabled or disabled),
- * awaiting verification, or it may have failed setup because it was not able
- * to connect to the server.
- *
- * For each MultiDevice feature (including the "suite" feature, which acts as a
- * gatekeeper for the others), the corresponding *State property is an enum
- * containing the data necessary to display it. Note that hostDeviceName should
- * be undefined if and only if no host has been set up, regardless of whether
- * there are potential hosts on the account.
- *
- * @typedef {{
- * mode: !settings.MultiDeviceSettingsMode,
- * hostDeviceName: (string|undefined),
- * betterTogetherState: !settings.MultiDeviceFeatureState,
- * instantTetheringState: !settings.MultiDeviceFeatureState,
- * messagesState: !settings.MultiDeviceFeatureState,
- * smartLockState: !settings.MultiDeviceFeatureState,
- * isAndroidSmsPairingComplete: boolean
- * }}
- */
-let MultiDevicePageContentData;
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.html
deleted file mode 100644
index adc498c1f7d..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<!-- Anyone using this behavior might be using the referenced icons. -->
-<link rel="import" href="../chromeos/os_icons.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="multidevice_constants.html">
-<script src="multidevice_feature_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.js
deleted file mode 100644
index 727822ca512..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_behavior.js
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer behavior for dealing with MultiDevice features. It is
- * intended to facilitate passing data between elements in the MultiDevice page
- * cleanly and concisely. It includes some constants and utility methods.
- */
-cr.exportPath('settings');
-
-/** @polymerBehavior */
-const MultiDeviceFeatureBehaviorImpl = {
- properties: {
- /** @type {!MultiDevicePageContentData} */
- pageContentData: Object,
-
- /**
- * Enum defined in multidevice_constants.js.
- * @type {Object<string, number>}
- */
- MultiDeviceFeature: {
- type: Object,
- value: settings.MultiDeviceFeature,
- },
- },
-
- /**
- * Whether the gatekeeper pref for the whole Better Together feature suite is
- * on.
- * @return {boolean}
- */
- isSuiteOn: function() {
- return !!this.pageContentData &&
- this.pageContentData.betterTogetherState ===
- settings.MultiDeviceFeatureState.ENABLED_BY_USER;
- },
-
- /**
- * Whether the gatekeeper pref for the whole Better Together feature suite is
- * allowed by policy.
- * @return {boolean}
- */
- isSuiteAllowedByPolicy: function() {
- return !!this.pageContentData &&
- this.pageContentData.betterTogetherState !==
- settings.MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
- },
-
- /**
- * Whether an individual feature is allowed by policy.
- * @param {!settings.MultiDeviceFeature} feature
- * @return {boolean}
- */
- isFeatureAllowedByPolicy: function(feature) {
- return this.getFeatureState(feature) !==
- settings.MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
- },
-
- /**
- * @param {!settings.MultiDeviceFeature} feature
- * @return {boolean}
- */
- isFeatureSupported: function(feature) {
- return ![settings.MultiDeviceFeatureState.NOT_SUPPORTED_BY_CHROMEBOOK,
- settings.MultiDeviceFeatureState.NOT_SUPPORTED_BY_PHONE,
- ].includes(this.getFeatureState(feature));
- },
-
- /**
- * Whether the user is prevented from attempted to change a given feature. In
- * the UI this corresponds to a disabled toggle.
- * @param {!settings.MultiDeviceFeature} feature
- * @return {boolean}
- */
- isFeatureStateEditable: function(feature) {
- // The suite is off and the toggle corresponds to an individual feature
- // (as opposed to the full suite).
- if (feature !== settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE &&
- !this.isSuiteOn()) {
- return false;
- }
-
- return [
- settings.MultiDeviceFeatureState.DISABLED_BY_USER,
- settings.MultiDeviceFeatureState.ENABLED_BY_USER
- ].includes(this.getFeatureState(feature));
- },
-
- /**
- * The localized string representing the name of the feature.
- * @param {!settings.MultiDeviceFeature} feature
- * @return {string}
- */
- getFeatureName: function(feature) {
- switch (feature) {
- case settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE:
- return this.i18n('multideviceSetupItemHeading');
- case settings.MultiDeviceFeature.INSTANT_TETHERING:
- return this.i18n('multideviceInstantTetheringItemTitle');
- case settings.MultiDeviceFeature.MESSAGES:
- return this.i18n('multideviceAndroidMessagesItemTitle');
- case settings.MultiDeviceFeature.SMART_LOCK:
- return this.i18n('multideviceSmartLockItemTitle');
- default:
- return '';
- }
- },
-
- /**
- * The full icon name used provided by the containing iron-iconset-svg
- * (i.e. [iron-iconset-svg name]:[SVG <g> tag id]) for a given feature.
- * @param {!settings.MultiDeviceFeature} feature
- * @return {string}
- */
- getIconName: function(feature) {
- switch (feature) {
- case settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE:
- return 'os-settings:multidevice-better-together-suite';
- case settings.MultiDeviceFeature.MESSAGES:
- return 'os-settings:multidevice-messages';
- case settings.MultiDeviceFeature.SMART_LOCK:
- return 'os-settings:multidevice-smart-lock';
- default:
- return '';
- }
- },
-
- /**
- * The localized string providing a description or useful status information
- * concerning a given feature.
- * @param {!settings.MultiDeviceFeature} feature
- * @return {string}
- */
- getFeatureSummaryHtml: function(feature) {
- switch (feature) {
- case settings.MultiDeviceFeature.SMART_LOCK:
- return this.i18nAdvanced('multideviceSmartLockItemSummary');
- case settings.MultiDeviceFeature.INSTANT_TETHERING:
- return this.i18nAdvanced('multideviceInstantTetheringItemSummary');
- case settings.MultiDeviceFeature.MESSAGES:
- return this.i18nAdvanced('multideviceAndroidMessagesItemSummary');
- default:
- return '';
- }
- },
-
- /**
- * Extracts the MultiDeviceFeatureState enum value describing the given
- * feature from this.pageContentData. Returns null if the feature is not
- * an accepted value (e.g. testing fake).
- * @param {!settings.MultiDeviceFeature} feature
- * @return {?settings.MultiDeviceFeatureState}
- */
- getFeatureState: function(feature) {
- if (!this.pageContentData) {
- return null;
- }
-
- switch (feature) {
- case settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE:
- return this.pageContentData.betterTogetherState;
- case settings.MultiDeviceFeature.INSTANT_TETHERING:
- return this.pageContentData.instantTetheringState;
- case settings.MultiDeviceFeature.MESSAGES:
- return this.pageContentData.messagesState;
- case settings.MultiDeviceFeature.SMART_LOCK:
- return this.pageContentData.smartLockState;
- default:
- return null;
- }
- },
-
- /**
- * Whether a host phone has been set by the user (not necessarily verified).
- * @return {boolean}
- */
- isHostSet: function() {
- return [
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
- settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED,
- ].includes(this.pageContentData.mode);
- },
-};
-
-/** @polymerBehavior */
-const MultiDeviceFeatureBehavior = [
- I18nBehavior,
- MultiDeviceFeatureBehaviorImpl,
-];
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.html
deleted file mode 100644
index b4d69033c1e..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.html
+++ /dev/null
@@ -1,83 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-
-<dom-module id="settings-multidevice-feature-item">
- <template>
- <style include="settings-shared">
- #card {
- border-top: var(--cr-separator-line);
- border-top-style: var(--feature-item-border-top-style, solid);
- padding: var(--feature-item-row-padding);
- }
-
- iron-icon {
- padding: 2px;
- }
-
- cr-policy-indicator {
- padding: 0 var(--cr-controlled-by-spacing);
- }
-
- .link-wrapper {
- align-items: center;
- display: flex;
- flex-grow: 1;
- }
- </style>
- <div id="card" class="settings-box two-line">
- <div class="link-wrapper" actionable
- actionable$="[[hasSubpageClickHandler_(
- feature, pageContentData, subpageRoute)]]"
- on-click="handleItemClick_">
- <slot name="icon">
- <iron-icon icon="[[getIconName(feature)]]"></iron-icon>
- </slot>
- <div id="item-text-container" class="middle">
- <div id="featureName">[[getFeatureName(feature)]]</div>
- <div class="secondary"
- id="featureSecondary"
- inner-h-t-m-l="[[getFeatureSummaryHtml(feature)]]">
- </div>
- </div>
- <template is="dom-if"
- if="[[hasSubpageClickHandler_(
- feature, pageContentData, subpageRoute)]]"
- restamp>
- <cr-icon-button class="subpage-arrow" aria-labelledby="featureName"
- aria-describedby="featureSecondary"></cr-icon-button>
- </template>
- </div>
- <template is="dom-if"
- if="[[hasSubpageClickHandler_(
- feature, pageContentData, subpageRoute)]]"
- restamp>
- <div class="separator"></div>
- </template>
-
- <template is="dom-if"
- if="[[!isFeatureAllowedByPolicy(feature, pageContentData)]]"
- restamp>
- <cr-policy-indicator indicator-type="userPolicy"></cr-policy-indicator>
- </template>
- <slot name="feature-controller">
- <!-- This settings-multidevice-feature-toggle is the default controller.
- If an element with slot="feature-controller" is attached, it will
- replace this one. -->
- <settings-multidevice-feature-toggle feature="[[feature]]"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-feature-toggle>
- </slot>
- </div>
- </template>
- <script src="multidevice_feature_item.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.js
deleted file mode 100644
index 1b8b003a81c..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_item.js
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Item for an individual multidevice feature. These features appear in the
- * multidevice subpage to allow the user to individually toggle them as long as
- * the phone is enabled as a multidevice host. The feature items contain basic
- * information relevant to the individual feature, such as a route to the
- * feature's autonomous page if there is one.
- */
-cr.exportPath('settings');
-
-Polymer({
- is: 'settings-multidevice-feature-item',
-
- behaviors: [MultiDeviceFeatureBehavior],
-
- properties: {
- /** @type {!settings.MultiDeviceFeature} */
- feature: Number,
-
- /**
- * If it is truthy, the item should be actionable and clicking on it should
- * navigate to the provided route. Otherwise, the item is simply not
- * actionable.
- * @type {settings.Route|undefined}
- */
- subpageRoute: Object,
-
- /**
- * URLSearchParams for subpage route. No param is provided if it is
- * undefined.
- * @type {URLSearchParams|undefined}
- */
- subpageRouteUrlSearchParams: Object,
- },
-
- /**
- * @return {boolean}
- * @private
- */
- hasSubpageClickHandler_: function() {
- return !!this.subpageRoute && this.isFeatureAllowedByPolicy(this.feature);
- },
-
- /** @private */
- handleItemClick_: function(event) {
- if (!this.hasSubpageClickHandler_()) {
- return;
- }
-
- // We do not navigate away if the click was on a link.
- if (event.path[0].tagName === 'A') {
- event.stopPropagation();
- return;
- }
-
- // Remove the search term when navigating to avoid potentially having any
- // visible search term reappear at a later time. See
- // https://crbug.com/989119.
- settings.navigateTo(
- this.subpageRoute, this.subpageRouteUrlSearchParams,
- true /* opt_removeSearch */);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.html
deleted file mode 100644
index 79ac7d51fc0..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-
-<dom-module id="settings-multidevice-feature-toggle">
- <template>
- <cr-toggle id="toggle"
- aria-label$="[[getFeatureName(feature)]]"
- checked="{{checked_}}"
- disabled="[[!isFeatureStateEditable(feature, pageContentData)]]"
- on-change="onChange_">
- </cr-toggle>
- </template>
- <script src="multidevice_feature_toggle.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.js
deleted file mode 100644
index 83350e86cb8..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_feature_toggle.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * A toggle button specially suited for the MultiDevice Settings UI use-case.
- * Instead of changing on clicks, it requests a pref change from the
- * MultiDevice service and/or triggers a password check to grab an auth token
- * for the user. It also receives real time updates on feature states and
- * reflects them in the toggle status.
- */
-Polymer({
- is: 'settings-multidevice-feature-toggle',
-
- behaviors: [MultiDeviceFeatureBehavior],
-
- properties: {
- /** @type {!settings.MultiDeviceFeature} */
- feature: Number,
-
- /** @private {boolean} */
- checked_: Boolean,
- },
-
- listeners: {
- 'click': 'onDisabledInnerToggleClick_',
- },
-
- // Note that, although this.feature does not change throughout the element's
- // lifecycle, it must be listed as an observer dependency to ensure that
- // this.feature is defined by the time of the observer's first call.
- observers: ['resetChecked_(feature, pageContentData)'],
-
- /**
- * Because MultiDevice prefs are only meant to be controlled via the
- * MultiDevice mojo service, we need the cr-toggle to appear not to change
- * when pressed. This method resets it before a change is visible to the
- * user.
- * @private
- */
- resetChecked_: function() {
- this.checked_ = this.getFeatureState(this.feature) ===
- settings.MultiDeviceFeatureState.ENABLED_BY_USER;
- },
-
- /**
- * This handles the edge case in which the inner toggle (i.e., the cr-toggle)
- * is disabled. For context, the cr-toggle element naturally stops clicks
- * from propagating as long as its disabled attribute is false. However, if
- * the cr-toggle's disabled attribute is set to true, its pointer-event CSS
- * property is set to 'none' automatically. Thus, if the cr-toggle is clicked
- * while it is disabled, the click event targets the parent element directly
- * instead of propagating through the cr-toggle. This handler prevents such a
- * click from unintentionally bubbling up the tree.
- * @private
- */
- onDisabledInnerToggleClick_: function(event) {
- event.stopPropagation();
- },
-
- /**
- * Callback for clicking on the toggle. It attempts to toggle the feature's
- * status if the user is allowed.
- * @private
- */
- onChange_: function() {
- this.resetChecked_();
-
- // Pass the negation of |this.checked_|: this indicates that if the toggle
- // is checked, the intent is for it to be unchecked, and vice versa.
- this.fire(
- 'feature-toggle-clicked',
- {feature: this.feature, enabled: !this.checked_});
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.html
deleted file mode 100644
index f318ff45b63..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../controls/password_prompt_dialog.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-<link rel="import" href="multidevice_smartlock_subpage.html">
-<link rel="import" href="multidevice_subpage.html">
-
-<dom-module id="settings-multidevice-page">
- <template>
- <style include="settings-shared">
- cr-policy-indicator {
- padding: 0 var(--cr-controlled-by-spacing);
- }
-
- .link-wrapper {
- align-items: center;
- display: flex;
- flex-grow: 1;
- }
- </style>
- <settings-animated-pages id="pages" section="multidevice"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <div id="multidevice-item" class="settings-box two-line">
- <div class="link-wrapper" actionable
- actionable$="[[doesClickOpenSubpage_(pageContentData)]]"
- on-click="handleItemClick_">
- <template is="dom-if" if="[[isHostSet(pageContentData)]]" restamp>
- <iron-icon icon=
- "[[getIconName(MultiDeviceFeature.BETTER_TOGETHER_SUITE)]]">
- </iron-icon>
- </template>
- <div class$=
- "[[getMultiDeviceItemLabelBlockCssClass_(pageContentData)]]
- settings-box-text">
- <div id="multidevice-label">
- [[getLabelText_(pageContentData)]]
- </div>
- <div id="multideviceSubLabel" class="secondary"
- inner-h-t-m-l="[[getSubLabelInnerHtml_(pageContentData)]]">
- </div>
- </div>
- <template is="dom-if"
- if="[[doesClickOpenSubpage_(pageContentData)]]"
- restamp>
- <cr-icon-button class="subpage-arrow"
- aria-labelledby="multidevice-label"
- aria-describedby="multideviceSubLabel"></cr-icon-button>
- </template>
- </div>
- <template is="dom-if"
- if="[[!isSuiteAllowedByPolicy(pageContentData)]]"
- restamp>
- <cr-policy-indicator indicator-type="userPolicy">
- </cr-policy-indicator>
- <settings-multidevice-feature-toggle
- feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-feature-toggle>
- </template>
- <template is="dom-if"
- if="[[shouldShowSeparatorAndSubpageArrow_(pageContentData)]]"
- restamp>
- <div class="separator"></div>
- </template>
- <template is="dom-if"
- if="[[shouldShowButton_(pageContentData)]]"
- restamp>
- <cr-button on-click="handleButtonClick_">
- [[getButtonText_(pageContentData)]]
- </cr-button>
- </template>
- <template is="dom-if"
- if="[[shouldShowToggle_(pageContentData)]]"
- restamp>
- <settings-multidevice-feature-toggle
- feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-feature-toggle>
- </template>
- </div>
- </div>
- <template is="dom-if" route-path="/multidevice/features" restamp>
- <settings-subpage associated-control="[[$$('#multidevice-item')]]"
- page-title="[[getLabelText_(pageContentData)]]">
- <settings-multidevice-subpage
- page-content-data="[[pageContentData]]">
- </settings-multidevice-subpage>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/multidevice/features/smartLock"
- restamp>
- <settings-subpage
- associated-control="[[$$('#multidevice-item')]]"
- page-title="$i18n{easyUnlockSectionTitle}">
- <settings-multidevice-smartlock-subpage
- prefs="{{prefs}}"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-smartlock-subpage>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- <template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
- <settings-password-prompt-dialog id="multidevicePasswordPrompt"
- auth-token="{{authToken_}}">
- </settings-password-prompt-dialog>
- </template>
- </template>
- <script src="multidevice_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
deleted file mode 100644
index 51e8c6746b8..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
+++ /dev/null
@@ -1,369 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Settings page for managing MultiDevice features.
- */
-cr.exportPath('settings');
-
-Polymer({
- is: 'settings-multidevice-page',
-
- behaviors: [
- settings.RouteObserverBehavior,
- MultiDeviceFeatureBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /** Preferences state. */
- prefs: {type: Object},
-
- /**
- * A Map specifying which element should be focused when exiting a subpage.
- * The key of the map holds a settings.Route path, and the value holds a
- * query selector that identifies the desired element.
- * @private {!Map<string, string>}
- */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.MULTIDEVICE_FEATURES) {
- map.set(
- settings.routes.MULTIDEVICE_FEATURES.path,
- '#multidevice-item .subpage-arrow');
- }
- return map;
- },
- },
-
- /**
- * Authentication token provided by password-prompt-dialog.
- * @private {string}
- */
- authToken_: {
- type: String,
- value: '',
- },
-
- /**
- * Feature which the user has requested to be enabled but could not be
- * enabled immediately because authentication (i.e., entering a password) is
- * required. This value is initialized to null, is set when the password
- * dialog is opened, and is reset to null again once the password dialog is
- * closed.
- * @private {?settings.MultiDeviceFeature}
- */
- featureToBeEnabledOnceAuthenticated_: {
- type: Number,
- value: null,
- },
-
- /** @private {boolean} */
- showPasswordPromptDialog_: {
- type: Boolean,
- value: false,
- },
- },
-
- listeners: {
- 'close': 'onDialogClose_',
- 'feature-toggle-clicked': 'onFeatureToggleClicked_',
- 'forget-device-requested': 'onForgetDeviceRequested_',
- },
-
- /** @private {?settings.MultiDeviceBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
-
- this.addWebUIListener(
- 'settings.updateMultidevicePageContentData',
- this.onPageContentDataChanged_.bind(this));
-
- this.browserProxy_.getPageContentData().then(
- this.onPageContentDataChanged_.bind(this));
- },
-
- /**
- * Overridden from settings.RouteObserverBehavior.
- * @protected
- */
- currentRouteChanged: function() {
- this.leaveNestedPageIfNoHostIsSet_();
- },
-
- /**
- * CSS class for the <div> containing all the text in the multidevice-item
- * <div>, i.e. the label and sublabel. If the host is set, the Better Together
- * icon appears so before the text (i.e. text div is 'middle' class).
- * @return {string}
- * @private
- */
- getMultiDeviceItemLabelBlockCssClass_: function() {
- return this.isHostSet() ? 'middle' : 'start';
- },
-
- /**
- * @return {string} Translated item label.
- * @private
- */
- getLabelText_: function() {
- return this.pageContentData.hostDeviceName ||
- this.i18n('multideviceSetupItemHeading');
- },
-
- /**
- * @return {string} Translated sublabel with a "learn more" link.
- * @private
- */
- getSubLabelInnerHtml_: function() {
- if (!this.isSuiteAllowedByPolicy()) {
- return this.i18nAdvanced('multideviceSetupSummary');
- }
- switch (this.pageContentData.mode) {
- case settings.MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS:
- return this.i18nAdvanced('multideviceNoHostText');
- case settings.MultiDeviceSettingsMode.NO_HOST_SET:
- return this.i18nAdvanced('multideviceSetupSummary');
- case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
- // Intentional fall-through.
- case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
- return this.i18nAdvanced('multideviceVerificationText');
- default:
- return this.isSuiteOn() ? this.i18n('multideviceEnabled') :
- this.i18n('multideviceDisabled');
- }
- },
-
- /**
- * @return {string} Translated button text.
- * @private
- */
- getButtonText_: function() {
- switch (this.pageContentData.mode) {
- case settings.MultiDeviceSettingsMode.NO_HOST_SET:
- return this.i18n('multideviceSetupButton');
- case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
- // Intentional fall-through.
- case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
- return this.i18n('multideviceVerifyButton');
- default:
- return '';
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowButton_: function() {
- return [
- settings.MultiDeviceSettingsMode.NO_HOST_SET,
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
- ].includes(this.pageContentData.mode);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowToggle_: function() {
- return this.pageContentData.mode ===
- settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED;
- },
-
- /**
- * Whether to show the separator bar and, if the state calls for a chevron
- * (a.k.a. subpage arrow) routing to the subpage, the chevron.
- * @return {boolean}
- * @private
- */
- shouldShowSeparatorAndSubpageArrow_: function() {
- return this.pageContentData.mode !==
- settings.MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- doesClickOpenSubpage_: function() {
- return this.isHostSet();
- },
-
- /** @private */
- handleItemClick_: function(event) {
- // We do not open the subpage if the click was on a link.
- if (event.path[0].tagName === 'A') {
- event.stopPropagation();
- return;
- }
-
- if (!this.isHostSet()) {
- return;
- }
-
- settings.navigateTo(settings.routes.MULTIDEVICE_FEATURES);
- },
-
- /** @private */
- handleButtonClick_: function(event) {
- event.stopPropagation();
- switch (this.pageContentData.mode) {
- case settings.MultiDeviceSettingsMode.NO_HOST_SET:
- this.browserProxy_.showMultiDeviceSetupDialog();
- return;
- case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
- // Intentional fall-through.
- case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
- // If this device is waiting for action on the server or the host
- // device, clicking the button should trigger this action.
- this.browserProxy_.retryPendingHostSetup();
- }
- },
-
- /** @private */
- openPasswordPromptDialog_: function() {
- this.showPasswordPromptDialog_ = true;
- },
-
- onDialogClose_: function(event) {
- event.stopPropagation();
- if (event.path.some(
- element => element.id === 'multidevicePasswordPrompt')) {
- this.onPasswordPromptDialogClose_();
- }
- },
-
- /** @private */
- onPasswordPromptDialogClose_: function() {
- // The password prompt should only be shown when there is a feature waiting
- // to be enabled.
- assert(this.featureToBeEnabledOnceAuthenticated_ !== null);
-
- // If |this.authToken_| is set when the dialog has been closed, this means
- // that the user entered the correct password into the dialog. Thus, send
- // all pending features to be enabled.
- if (this.authToken_) {
- this.browserProxy_.setFeatureEnabledState(
- this.featureToBeEnabledOnceAuthenticated_, true /* enabled */,
- this.authToken_);
-
- // Reset |this.authToken_| now that it has been used. This ensures that
- // users cannot keep an old auth token and reuse it on an subsequent
- // request.
- this.authToken_ = '';
- }
-
- // Either the feature was enabled above or the user canceled the request by
- // clicking "Cancel" on the password dialog. Thus, there is no longer a need
- // to track any pending feature.
- this.featureToBeEnabledOnceAuthenticated_ = null;
-
- // Remove the password prompt dialog from the DOM.
- this.showPasswordPromptDialog_ = false;
- },
-
- /**
- * Attempt to enable the provided feature. If not authenticated (i.e.,
- * |authToken_| is invalid), display the password prompt to begin the
- * authentication process.
- *
- * @param {!CustomEvent<!{
- * feature: !settings.MultiDeviceFeature,
- * enabled: boolean
- * }>} event
- * @private
- */
- onFeatureToggleClicked_: function(event) {
- const feature = event.detail.feature;
- const enabled = event.detail.enabled;
-
- // Disabling any feature does not require authentication, and enable some
- // features does not require authentication.
- if (!enabled || !this.isAuthenticationRequiredToEnable_(feature)) {
- this.browserProxy_.setFeatureEnabledState(feature, enabled);
- return;
- }
-
- // If the feature required authentication to be enabled, open the password
- // prompt dialog. This is required every time the user enables a security-
- // sensitive feature (i.e., use of stale auth tokens is not acceptable).
- this.featureToBeEnabledOnceAuthenticated_ = feature;
- this.openPasswordPromptDialog_();
- },
-
- /**
- * @param {!settings.MultiDeviceFeature} feature The feature to enable.
- * @return {boolean} Whether authentication is required to enable the feature.
- * @private
- */
- isAuthenticationRequiredToEnable_: function(feature) {
- // Enabling SmartLock always requires authentication.
- if (feature == settings.MultiDeviceFeature.SMART_LOCK) {
- return true;
- }
-
- // Enabling any feature besides SmartLock and the Better Together suite does
- // not require authentication.
- if (feature != settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE) {
- return false;
- }
-
- const smartLockState =
- this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK);
-
- // If the user is enabling the Better Together suite and this change would
- // result in SmartLock being implicitly enabled, authentication is required.
- // SmartLock is implicitly enabled if it is only currently not enabled due
- // to the suite being disabled or due to the SmartLock host device not
- // having a lock screen set.
- return smartLockState ==
- settings.MultiDeviceFeatureState.UNAVAILABLE_SUITE_DISABLED ||
- smartLockState ==
- settings.MultiDeviceFeatureState.UNAVAILABLE_INSUFFICIENT_SECURITY;
- },
-
- /** @private */
- onForgetDeviceRequested_: function() {
- this.browserProxy_.removeHostDevice();
- settings.navigateTo(settings.routes.MULTIDEVICE);
- },
-
- /**
- * Checks if the user is in a nested page without a host set and, if so,
- * navigates them back to the main page.
- * @private
- */
- leaveNestedPageIfNoHostIsSet_: function() {
- // Wait for data to arrive.
- if (!this.pageContentData) {
- return;
- }
-
- // If the user gets to the a nested page without a host (e.g. by clicking a
- // stale 'existing user' notifications after forgetting their host) we
- // direct them back to the main settings page.
- if (settings.routes.MULTIDEVICE != settings.getCurrentRoute() &&
- settings.routes.MULTIDEVICE.contains(settings.getCurrentRoute()) &&
- !this.isHostSet()) {
- settings.navigateTo(settings.routes.MULTIDEVICE);
- }
- },
-
- /**
- * @param {!MultiDevicePageContentData} newData
- * @private
- */
- onPageContentDataChanged_: function(newData) {
- this.pageContentData = newData;
- this.leaveNestedPageIfNoHostIsSet_();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.html
deleted file mode 100644
index c1f52cdc294..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<!-- TODO(jhawkins): This is copy/pasted from controlled_radio_button. Figure
- out how to refactor such that a common UI/behavior may be shared. The only
- difference is how the controlling preference is read. -->
-<dom-module id="multidevice-radio-button">
- <template>
- <style include="settings-shared cr-radio-button-style">
- :host([disabled]) {
- opacity: 1;
- }
-
- /* Disc and label should be transluscent, but not the policy indicator. */
- :host([disabled]) .disc-wrapper,
- :host([disabled]) #labelWrapper {
- opacity: var(--cr-disabled-opacity);
- }
-
- cr-policy-pref-indicator {
- margin-inline-start: var(--settings-controlled-by-spacing);
- /* Enable pointer events for the indicator so :hover works. Disable
- * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
- pointer-events: all;
- }
- </style>
-
- <div
- aria-checked$="[[getAriaChecked_(checked)]]"
- aria-disabled$="[[getAriaDisabled_(disabled)]]"
- aria-labelledby="labelWrapper"
- class="disc-wrapper"
- id="button"
- role="radio"
- tabindex="0"
- on-keydown="onInputKeydown_">
- <div class="disc-border"></div>
- <div class="disc"></div>
- </div>
-
- <div id="labelWrapper">
- <span>[[label]]</span>
- </div>
-
- <template is="dom-if" if="[[disabled]]" restamp>
- <cr-policy-indicator
- indicator-type="userPolicy"
- icon-aria-label="[[label]]"
- on-click="onIndicatorTap_">
- </cr-policy-indicator>
- </template>
-
- </template>
- <script src="multidevice_radio_button.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.js
deleted file mode 100644
index e5d121758f1..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_radio_button.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-Polymer({
- is: 'multidevice-radio-button',
-
- behaviors: [
- CrRadioButtonBehavior,
- ],
-
- /**
- * Prevents on-click handles on the control from being activated when the
- * indicator is clicked.
- * @param {!Event} e The click event.
- * @private
- */
- onIndicatorTap_: function(e) {
- e.preventDefault();
- e.stopPropagation();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html
deleted file mode 100644
index 8a150439064..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html
+++ /dev/null
@@ -1,65 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-<link rel="import" href="multidevice_radio_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-multidevice-smartlock-subpage">
- <template>
- <style include="settings-shared"></style>
- <div class="settings-box first">
- <!-- TODO(jhawkins): Remove this status text and move the toggle into
- the subpage header section. -->
- <div class="start">
- <template is="dom-if" if="[[smartLockEnabled_]]" restamp>
- $i18n{multideviceEnabled}
- </template>
- <template is="dom-if" if="[[!smartLockEnabled_]]" restamp>
- $i18n{multideviceDisabled}
- </template>
- </div>
- <settings-multidevice-feature-toggle
- feature="[[MultiDeviceFeature.SMART_LOCK]]"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-feature-toggle>
- </div>
- <iron-collapse opened="[[smartLockEnabled_]]">
- <div class="settings-box first line-only">
- <h2 class="start first">
- $i18n{multideviceSmartLockOptions}
- </h2>
- </div>
- <div class="list-frame">
- <cr-radio-group
- selected="[[smartLockSignInEnabled_]]"
- selectable-elements="multidevice-radio-button"
- disabled="[[!smartLockSignInAllowed_]]"
- on-selected-changed="onSmartLockSignInEnabledChanged_">
- <multidevice-radio-button
- name="disabled"
- class="list-item underbar"
- label="$i18n{easyUnlockUnlockDeviceOnly}">
- </multidevice-radio-button>
- <multidevice-radio-button
- name="enabled"
- class="list-item"
- label="$i18n{easyUnlockUnlockDeviceAndAllowSignin}">
- </multidevice-radio-button>
- </cr-radio-group>
- </div>
- </iron-collapse>
- <template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
- <settings-password-prompt-dialog id="smartLockSignInPasswordPrompt"
- on-close="onEnableSignInDialogClose_" auth-token="{{authToken_}}">
- </settings-password-prompt-dialog>
- </template>
- </template>
- <script src="multidevice_smartlock_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js
deleted file mode 100644
index 2dd6976e916..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Subpage of settings-multidevice-feature for managing the Smart Lock feature.
- */
-cr.exportPath('settings');
-
-cr.define('settings', function() {
- /**
- * The state of the preference controlling Smart Lock's ability to sign-in the
- * user.
- * @enum {string}
- */
- SignInEnabledState = {
- ENABLED: 'enabled',
- DISABLED: 'disabled',
- };
-
- /** @const {string} */
- SmartLockSignInEnabledPrefName = 'proximity_auth.is_chromeos_login_enabled';
-
- return {
- SignInEnabledState: SignInEnabledState,
- SmartLockSignInEnabledPrefName: SmartLockSignInEnabledPrefName,
- };
-});
-
-Polymer({
- is: 'settings-multidevice-smartlock-subpage',
-
- behaviors: [
- MultiDeviceFeatureBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /** @type {?SettingsRoutes} */
- routes: {
- type: Object,
- value: settings.routes,
- },
-
- /**
- * True if Smart Lock is enabled.
- * @private
- */
- smartLockEnabled_: {
- type: Boolean,
- computed: 'computeIsSmartLockEnabled_(pageContentData)',
- },
-
- /**
- * Whether Smart Lock may be used to sign-in the user (as opposed to only
- * being able to unlock the user's screen).
- * @private {!settings.SignInEnabledState}
- */
- smartLockSignInEnabled_: {
- type: Object,
- value: settings.SignInEnabledState.DISABLED,
- },
-
- /**
- * True if the user is allowed to enable Smart Lock sign-in.
- * @private
- */
- smartLockSignInAllowed_: {
- type: Boolean,
- value: true,
- },
-
- /** @private */
- showPasswordPromptDialog_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Authentication token provided by password-prompt-dialog.
- * @private {string}
- */
- authToken_: {
- type: String,
- value: '',
- },
- },
-
- /** @private {?settings.MultiDeviceBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
-
- this.addWebUIListener(
- 'smart-lock-signin-enabled-changed',
- this.updateSmartLockSignInEnabled_.bind(this));
-
- this.addWebUIListener(
- 'smart-lock-signin-allowed-changed',
- this.updateSmartLockSignInAllowed_.bind(this));
-
- this.browserProxy_.getSmartLockSignInEnabled().then(enabled => {
- this.updateSmartLockSignInEnabled_(enabled);
- });
-
- this.browserProxy_.getSmartLockSignInAllowed().then(allowed => {
- this.updateSmartLockSignInAllowed_(allowed);
- });
- },
-
- /**
- * Returns true if Smart Lock is an enabled feature.
- * @return {boolean}
- * @private
- */
- computeIsSmartLockEnabled_: function() {
- return !!this.pageContentData &&
- this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK) ==
- settings.MultiDeviceFeatureState.ENABLED_BY_USER;
- },
-
- /**
- * Updates the state of the Smart Lock 'sign-in enabled' toggle.
- * @private
- */
- updateSmartLockSignInEnabled_: function(enabled) {
- this.smartLockSignInEnabled_ = enabled ?
- settings.SignInEnabledState.ENABLED :
- settings.SignInEnabledState.DISABLED;
- },
-
- /**
- * Updates the Smart Lock 'sign-in enabled' toggle such that disallowing
- * sign-in disables the toggle.
- * @private
- */
- updateSmartLockSignInAllowed_: function(allowed) {
- this.smartLockSignInAllowed_ = allowed;
- },
-
- /** @private */
- openPasswordPromptDialog_: function() {
- this.showPasswordPromptDialog_ = true;
- },
-
- /**
- * Sets the Smart Lock 'sign-in enabled' pref based on the value of the
- * radio group representing the pref.
- * @private
- */
- onSmartLockSignInEnabledChanged_: function() {
- const radioGroup = this.$$('cr-radio-group');
- const enabled = radioGroup.selected == settings.SignInEnabledState.ENABLED;
-
- if (!enabled) {
- // No authentication check is required to disable.
- this.browserProxy_.setSmartLockSignInEnabled(false /* enabled */);
- return;
- }
-
- // Toggle the enabled state back to disabled, as authentication may not
- // succeed. The toggle state updates automatically by the pref listener.
- radioGroup.selected = settings.SignInEnabledState.DISABLED;
- this.openPasswordPromptDialog_();
- },
-
- /**
- * Updates the state of the password dialog controller flag when the UI
- * element closes.
- * @private
- */
- onEnableSignInDialogClose_: function() {
- this.showPasswordPromptDialog_ = false;
-
- // If |this.authToken_| is set when the dialog has been closed, this means
- // that the user entered the correct password into the dialog when
- // attempting to enable SignIn with Smart Lock.
- if (this.authToken_ !== '') {
- this.browserProxy_.setSmartLockSignInEnabled(
- true /* enabled */, this.authToken_);
- }
-
- // Always require password entry if re-enabling SignIn with Smart Lock.
- this.authToken_ = '';
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
deleted file mode 100644
index 55a72bcb020..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
+++ /dev/null
@@ -1,131 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-<link rel="import" href="multidevice_tether_item.html">
-
-<dom-module id="settings-multidevice-subpage">
- <template>
- <style include="settings-shared iron-flex">
- settings-multidevice-feature-item,
- settings-multidevice-tether-item {
- --feature-item-row-padding: 0;
- }
-
- settings-multidevice-feature-item:first-of-type {
- --feature-item-border-top-style: none;
- }
-
- #feature-items-container {
- @apply --settings-list-frame-padding;
- }
- </style>
- <div class="settings-box first">
- <div id="status-text-container"
- class="start"
- enabled$="[[isSuiteOn(pageContentData)]]"
- inner-h-t-m-l="[[getStatusInnerHtml_(pageContentData)]]">
- </div>
- <template is="dom-if" if="[[shouldShowVerifyButton_(pageContentData)]]"
- restamp>
- <cr-button on-click="handleVerifyButtonClick_">
- $i18n{multideviceVerifyButton}
- </cr-button>
- </template>
- <template is="dom-if" if="[[shouldShowSuiteToggle_(pageContentData)]]"
- restamp>
- <settings-multidevice-feature-toggle
- feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-feature-toggle>
- </template>
- </div>
- <template is="dom-if"
- if="[[shouldShowIndividualFeatures_(pageContentData)]]"
- restamp>
- <div id="feature-items-container">
- <template is="dom-if"
- if="[[isFeatureSupported(
- MultiDeviceFeature.SMART_LOCK, pageContentData)]]"
- restamp>
- <settings-multidevice-feature-item id="smartLockItem"
- feature="[[MultiDeviceFeature.SMART_LOCK]]"
- page-content-data="[[pageContentData]]"
- subpage-route="[[routes.SMART_LOCK]]">
- </settings-multidevice-feature-item>
- </template>
- <template is="dom-if"
- if="[[isFeatureSupported(
- MultiDeviceFeature.INSTANT_TETHERING, pageContentData)]]"
- restamp>
- <settings-multidevice-tether-item id="instantTetheringItem"
- page-content-data="[[pageContentData]]">
- </settings-multidevice-tether-item>
- </template>
- <template is="dom-if"
- if="[[isFeatureSupported(
- MultiDeviceFeature.MESSAGES, pageContentData)]]"
- restamp>
- <settings-multidevice-feature-item id="messagesItem"
- feature="[[MultiDeviceFeature.MESSAGES]]"
- page-content-data="[[pageContentData]]">
- <template is="dom-if"
- if="[[doesAndroidMessagesRequireSetUp_(pageContentData)]]"
- restamp>
- <cr-button disabled$="[[isAndroidMessagesSetupButtonDisabled_(
- pageContentData)]]"
- on-click="handleAndroidMessagesButtonClick_"
- slot="feature-controller">
- $i18n{multideviceSetupButton}
- </cr-button>
- </template>
- </settings-multidevice-feature-item>
- </template>
- </div>
- </template>
- <div class="settings-box two-line">
- <div id="forget-device-label" class="start">
- $i18n{multideviceForgetDevice}
- <div class="secondary">
- $i18n{multideviceForgetDeviceSummary}
- </div>
- </div>
- <cr-button on-click="handleForgetDeviceClick_"
- aria-labelledby="forgetDeviceLabel">
- $i18n{multideviceForgetDeviceDisconnect}
- </cr-button>
- </div>
- <cr-dialog id="forgetDeviceDialog">
- <div slot="title">$i18n{multideviceForgetDevice}</div>
- <div slot="body">
- <div class="first">
- $i18n{multideviceForgetDeviceDialogMessage}
- </div>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button"
- on-click="onForgetDeviceDialogCancelClick_">
- $i18n{cancel}
- </cr-button>
- <cr-button id="confirmButton"
- class="action-button"
- on-click="onForgetDeviceDialogConfirmClick_">
- $i18n{multideviceForgetDeviceDisconnect}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="multidevice_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js
deleted file mode 100644
index cb8f1dc6368..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Subpage of settings-multidevice-page for managing multidevice features
- * individually and for forgetting a host.
- */
-cr.exportPath('settings');
-
-Polymer({
- is: 'settings-multidevice-subpage',
-
- behaviors: [MultiDeviceFeatureBehavior],
-
- properties: {
- /**
- * Alias for allowing Polymer bindings to settings.routes.
- * @type {?SettingsRoutes}
- */
- routes: {
- type: Object,
- value: settings.routes,
- },
- },
-
- /** @private {?settings.MultiDeviceBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
- },
-
- /** @private */
- handleVerifyButtonClick_: function(event) {
- this.browserProxy_.retryPendingHostSetup();
- },
-
- /** @private */
- handleAndroidMessagesButtonClick_: function() {
- this.browserProxy_.setUpAndroidSms();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowIndividualFeatures_: function() {
- return this.pageContentData.mode ===
- settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowVerifyButton_: function() {
- return [
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
- ].includes(this.pageContentData.mode);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowSuiteToggle_: function() {
- return this.pageContentData.mode ===
- settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED;
- },
-
- /** @private */
- handleForgetDeviceClick_: function() {
- this.$.forgetDeviceDialog.showModal();
- },
-
- /** @private */
- onForgetDeviceDialogCancelClick_: function() {
- this.$.forgetDeviceDialog.close();
- },
-
- /** @private */
- onForgetDeviceDialogConfirmClick_: function() {
- this.fire('forget-device-requested');
- this.$.forgetDeviceDialog.close();
- },
-
- /**
- * @return {string}
- * @private
- */
- getStatusInnerHtml_: function() {
- if ([
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
- settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
- ].includes(this.pageContentData.mode)) {
- return this.i18nAdvanced('multideviceVerificationText');
- }
- return this.isSuiteOn() ? this.i18n('multideviceEnabled') :
- this.i18n('multideviceDisabled');
- },
-
- /**
- * @return {boolean}
- * @private
- */
- doesAndroidMessagesRequireSetUp_: function() {
- return this.getFeatureState(settings.MultiDeviceFeature.MESSAGES) ===
- settings.MultiDeviceFeatureState.FURTHER_SETUP_REQUIRED;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isAndroidMessagesSetupButtonDisabled_: function() {
- const messagesFeatureState =
- this.getFeatureState(settings.MultiDeviceFeature.MESSAGES);
- return !this.isSuiteOn() ||
- messagesFeatureState ===
- settings.MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html
deleted file mode 100644
index 4809023c3b1..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_icon.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-
-<dom-module id="settings-multidevice-tether-item">
- <style include="settings-shared">
- </style>
- <template>
- <settings-multidevice-feature-item page-content-data="[[pageContentData]]"
- feature="[[MultiDeviceFeature.INSTANT_TETHERING]]"
- subpage-route="[[routes.INTERNET_NETWORKS]]"
- subpage-route-url-search-params=
- "[[getTetherNetworkUrlSearchParams_()]]">
- <cr-network-icon slot="icon"
- show-technology-badge="[[showTechnologyBadge_]]"
- network-state="[[activeNetworkState_]]"
- device-state="[[deviceState_]]">
- </cr-network-icon>
- </settings-multidevice-feature-item>
- </template>
- <script src="multidevice_tether_item.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js b/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js
deleted file mode 100644
index d9ab5429c4c..00000000000
--- a/chromium/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * This element provides a layer between the settings-multidevice-subpage
- * element and the internet_page folder's network-summary-item. It is
- * responsible for loading initial tethering network data from the
- * networkConfig mojo API as well as updating the data in real time. It
- * serves a role comparable to the internet_page's network-summary element.
- */
-
-Polymer({
- is: 'settings-multidevice-tether-item',
-
- behaviors: [
- CrNetworkListenerBehavior,
- MultiDeviceFeatureBehavior,
- ],
-
- properties: {
- /**
- * The device state for tethering.
- * @private {?OncMojo.DeviceStateProperties|undefined}
- */
- deviceState_: Object,
-
- /**
- * The network state for a potential tethering host phone. Note that there
- * is at most one because only one MultiDevice host phone is allowed on an
- * account at a given time.
- * @private {?OncMojo.NetworkStateProperties|undefined}
- */
- activeNetworkState_: Object,
-
- /**
- * Alias for allowing Polymer bindings to settings.routes.
- * @type {?SettingsRoutes}
- */
- routes: {
- type: Object,
- value: settings.routes,
- },
-
- /**
- * Whether to show technology badge on mobile network icon.
- * @private
- */
- showTechnologyBadge_: {
- type: Boolean,
- value: function() {
- return loadTimeData.valueExists('showTechnologyBadge') &&
- loadTimeData.getBoolean('showTechnologyBadge');
- }
- },
- },
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @override */
- created: function() {
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /** @override */
- attached: function() {
- this.updateTetherDeviceState_();
- this.updateTetherNetworkState_();
- },
-
- /**
- * CrosNetworkConfigObserver impl
- * Note that any change to leading to a new active network will also trigger
- * onNetworkStateListChanged, triggering updateTetherNetworkState_ and
- * rendering this callback redundant. As a result, we return early if the
- * active network is not changed.
- * @param {!Array<chromeos.networkConfig.mojom.NetworkStateProperties>}
- * networks
- * @private
- */
- onActiveNetworksChanged: function(networks) {
- const guid = this.activeNetworkState_.guid;
- if (!networks.find(network => network.guid == guid)) {
- return;
- }
- this.networkConfig_.getNetworkState(guid).then(response => {
- if (response.result) {
- this.activeNetworkState_ = response.result;
- }
- });
- },
-
- /** CrosNetworkConfigObserver impl */
- onNetworkStateListChanged: function() {
- this.updateTetherNetworkState_();
- },
-
- /** CrosNetworkConfigObserver impl */
- onDeviceStateListChanged: function() {
- this.updateTetherDeviceState_();
- },
-
- /**
- * Retrieves device states (OncMojo.DeviceStateProperties) and sets
- * this.deviceState_ to the retrieved Tether device state (or undefined if
- * there is none). Note that crosNetworkConfig.getDeviceStateList retrieves at
- * most one device per NetworkType so there will be at most one Tether device
- * state.
- * @private
- */
- updateTetherDeviceState_: function() {
- this.networkConfig_.getDeviceStateList().then(response => {
- const kTether = chromeos.networkConfig.mojom.NetworkType.kTether;
- const deviceStates = response.result;
- const deviceState =
- deviceStates.find(deviceState => deviceState.type == kTether);
- this.deviceState_ = deviceState || {
- deviceState: chromeos.networkConfig.mojom.DeviceStateType.kDisabled,
- managedNetworkAvailable: false,
- scanning: false,
- simAbsent: false,
- type: kTether,
- };
- });
- },
-
- /**
- * Retrieves all Instant Tethering network states
- * (OncMojo.NetworkStateProperties). Note that there is at most one because
- * only one host is allowed on an account at a given time. Then it sets
- * this.activeNetworkState_ to that network if there is one or a dummy object
- * with an empty string for a GUID otherwise.
- * @private
- */
- updateTetherNetworkState_: function() {
- const kTether = chromeos.networkConfig.mojom.NetworkType.kTether;
- const filter = {
- filter: chromeos.networkConfig.mojom.FilterType.kVisible,
- limit: 1,
- networkType: kTether,
- };
- this.networkConfig_.getNetworkStateList(filter).then(response => {
- const networks = response.result;
- this.activeNetworkState_ =
- networks[0] || OncMojo.getDefaultNetworkState(kTether);
- });
- },
-
- /**
- * Returns an array containing the active network state if there is one
- * (note that if there is not GUID will be falsy). Returns an empty array
- * otherwise.
- * @return {!Array<chromeos.networkConfig.mojom.NetworkStateProperties>}
- * @private
- */
- getNetworkStateList_: function() {
- return this.activeNetworkState_.guid ? [this.activeNetworkState_] : [];
- },
-
- /**
- * @return {!URLSearchParams}
- * @private
- */
- getTetherNetworkUrlSearchParams_: function() {
- return new URLSearchParams('type=Tether');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.html b/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.html
deleted file mode 100644
index 2ea2bbd2c25..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="on_startup_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.js b/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.js
deleted file mode 100644
index 96fe49b399e..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_browser_proxy.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @typedef {{id: string, name: string, canBeDisabled: boolean}} */
-let NtpExtension;
-
-cr.define('settings', function() {
- /** @interface */
- class OnStartupBrowserProxy {
- /** @return {!Promise<?NtpExtension>} */
- getNtpExtension() {}
- }
-
- /**
- * @implements {settings.OnStartupBrowserProxy}
- */
- class OnStartupBrowserProxyImpl {
- /** @override */
- getNtpExtension() {
- return cr.sendWithPromise('getNtpExtension');
- }
- }
-
- cr.addSingletonGetter(OnStartupBrowserProxyImpl);
-
- return {
- OnStartupBrowserProxy: OnStartupBrowserProxy,
- OnStartupBrowserProxyImpl: OnStartupBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.html b/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.html
deleted file mode 100644
index 1d7e8883129..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/controlled_radio_button.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_radio_group.html">
-<link rel="import" href="on_startup_browser_proxy.html">
-<link rel="import" href="startup_urls_page.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-on-startup-page">
- <template>
- <style include="settings-shared">
- .block {
- display: block;
- }
- </style>
- <div class="settings-box block first">
- <settings-radio-group id="onStartupRadioGroup"
- pref="{{prefs.session.restore_on_startup}}"
- group-aria-label="$i18n{onStartup}">
- <controlled-radio-button name="[[prefValues_.OPEN_NEW_TAB]]"
- pref="[[prefs.session.restore_on_startup]]"
- label="$i18n{onStartupOpenNewTab}"
- no-extension-indicator>
- </controlled-radio-button>
- <template is="dom-if" if="[[ntpExtension_]]">
- <extension-controlled-indicator
- extension-id="[[ntpExtension_.id]]"
- extension-name="[[ntpExtension_.name]]"
- extension-can-be-disabled="[[ntpExtension_.canBeDisabled]]">
- </extension-controlled-indicator>
- </template>
- <controlled-radio-button name="[[prefValues_.CONTINUE]]"
- pref="[[prefs.session.restore_on_startup]]"
- label="$i18n{onStartupContinue}">
- </controlled-radio-button>
- <controlled-radio-button name="[[prefValues_.OPEN_SPECIFIC]]"
- pref="[[prefs.session.restore_on_startup]]"
- label="$i18n{onStartupOpenSpecific}">
- </controlled-radio-button>
- </settings-radio-group>
- </div>
- <template is="dom-if"
- if="[[showStartupUrls_(prefs.session.restore_on_startup.value)]]">
- <settings-startup-urls-page prefs="[[prefs]]">
- </settings-startup-urls-page>
- </template>
- </template>
- <script src="on_startup_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.js b/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.js
deleted file mode 100644
index aff7303d079..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/on_startup_page.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-on-startup-page' is a settings page.
- */
-Polymer({
- is: 'settings-on-startup-page',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private {?NtpExtension} */
- ntpExtension_: Object,
-
- /**
- * Enum values for the 'session.restore_on_startup' preference.
- * @private {!Object<string, number>}
- */
- prefValues_: {
- readOnly: true,
- type: Object,
- value: {
- CONTINUE: 1,
- OPEN_NEW_TAB: 5,
- OPEN_SPECIFIC: 4,
- },
- },
- },
-
- /** @override */
- attached: function() {
- const updateNtpExtension = ntpExtension => {
- // Note that |ntpExtension| is empty if there is no NTP extension.
- this.ntpExtension_ = ntpExtension;
- };
- settings.OnStartupBrowserProxyImpl.getInstance().getNtpExtension().then(
- updateNtpExtension);
- this.addWebUIListener('update-ntp-extension', updateNtpExtension);
- },
-
- /**
- * Determine whether to show the user defined startup pages.
- * @param {number} restoreOnStartup Enum value from prefValues_.
- * @return {boolean} Whether the open specific pages is selected.
- * @private
- */
- showStartupUrls_: function(restoreOnStartup) {
- return restoreOnStartup == this.prefValues_.OPEN_SPECIFIC;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.html b/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.html
deleted file mode 100644
index e6583006957..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="startup_urls_page_browser_proxy.html">
-
-<dom-module id="settings-startup-url-dialog">
- <template>
- <style include="settings-shared"></style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">[[dialogTitle_]]</div>
- <div slot="body">
- <cr-input id="url" label="$i18n{onStartupSiteUrl}"
- value="{{url_}}" on-input="validate_" spellcheck="false"
- maxlength="[[urlLimit_]]" invalid="[[hasError_(error_)]]" autofocus
- error-message="[[errorMessage_('$i18nPolymer{onStartupInvalidUrl}',
- '$i18nPolymer{onStartupUrlTooLong}', error_)]]">
- </cr-input>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_"
- id="cancel">$i18n{cancel}</cr-button>
- <cr-button id="actionButton" class="action-button"
- on-click="onActionButtonTap_">[[actionButtonText_]]</cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="startup_url_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.js b/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.js
deleted file mode 100644
index 3502af22b07..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_dialog.js
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-/**
- * Describe the current URL input error status.
- * @enum {number}
- */
-const UrlInputError = {
- NONE: 0,
- INVALID_URL: 1,
- TOO_LONG: 2,
-};
-
-/**
- * @fileoverview 'settings-startup-url-dialog' is a component for adding
- * or editing a startup URL entry.
- */
-Polymer({
- is: 'settings-startup-url-dialog',
-
- properties: {
- /** @private {UrlInputError} */
- error_: {
- type: Number,
- value: UrlInputError.NONE,
- },
-
- /** @private */
- url_: String,
-
- /** @private */
- urlLimit_: {
- readOnly: true,
- type: Number,
- value: 100 * 1024, // 100 KB.
- },
-
- /**
- * If specified the dialog acts as an "Edit page" dialog, otherwise as an
- * "Add new page" dialog.
- * @type {?StartupPageInfo}
- */
- model: Object,
-
- /** @private */
- dialogTitle_: String,
-
- /** @private */
- actionButtonText_: String,
- },
-
- /** @private {!settings.SearchEnginesBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.browserProxy_ = settings.StartupUrlsPageBrowserProxyImpl.getInstance();
-
- if (this.model) {
- this.dialogTitle_ = loadTimeData.getString('onStartupEditPage');
- this.actionButtonText_ = loadTimeData.getString('save');
- this.$.actionButton.disabled = false;
- // Pre-populate the input field.
- this.url_ = this.model.url;
- } else {
- this.dialogTitle_ = loadTimeData.getString('onStartupAddNewPage');
- this.actionButtonText_ = loadTimeData.getString('add');
- this.$.actionButton.disabled = true;
- }
- this.$.dialog.showModal();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- hasError_: function() {
- return this.error_ != UrlInputError.NONE;
- },
-
- /**
- * @param {string} invalidUrl
- * @param {string} tooLong
- * @return {string}
- * @private
- */
- errorMessage_: function(invalidUrl, tooLong) {
- return ['', invalidUrl, tooLong][this.error_];
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onActionButtonTap_: function() {
- const whenDone = this.model ?
- this.browserProxy_.editStartupPage(this.model.modelIndex, this.url_) :
- this.browserProxy_.addStartupPage(this.url_);
-
- whenDone.then(success => {
- if (success) {
- this.$.dialog.close();
- }
- // If the URL was invalid, there is nothing to do, just leave the dialog
- // open and let the user fix the URL or cancel.
- });
- },
-
- /** @private */
- validate_: function() {
- if (this.url_.length == 0) {
- this.$.actionButton.disabled = true;
- this.error_ = UrlInputError.NONE;
- return;
- }
- if (this.url_.length >= this.urlLimit_) {
- this.$.actionButton.disabled = true;
- this.error_ = UrlInputError.TOO_LONG;
- return;
- }
- this.browserProxy_.validateStartupPage(this.url_).then(isValid => {
- this.$.actionButton.disabled = !isValid;
- this.error_ = isValid ? UrlInputError.NONE : UrlInputError.INVALID_URL;
- });
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.html b/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.html
deleted file mode 100644
index cd75b9639ab..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="startup_urls_page_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-
-<dom-module id="settings-startup-url-entry">
- <template>
- <style include="settings-shared">
- .hide-overflow {
- overflow: hidden;
- }
- </style>
- <div class="list-item" focus-row-container>
- <site-favicon url="[[model.url]]"></site-favicon>
- <div class="middle hide-overflow">
- <div class="text-elide">[[model.title]]</div>
- <div class="text-elide secondary">[[model.url]]</div>
- </div>
- <template is="dom-if" if="[[editable]]">
- <cr-icon-button class="icon-more-vert" id="dots" on-click="onDotsTap_"
- title="$i18n{moreActions}" focus-row-control focus-type="menu">
- </cr-icon-button>
- <cr-lazy-render id="menu">
- <template>
- <cr-action-menu>
- <button class="dropdown-item" on-click="onEditTap_">
- $i18n{edit}
- </button>
- <button class="dropdown-item" id="remove"
- on-click="onRemoveTap_">
- $i18n{onStartupRemove}
- </button>
- </cr-action-menu>
- </template>
- </cr-lazy-render>
- </template>
- </div>
- </template>
- <script src="startup_url_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.js b/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.js
deleted file mode 100644
index c3c9e3d646a..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_url_entry.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview settings-startup-url-entry represents a UI component that
- * displays a URL that is loaded during startup. It includes a menu that allows
- * the user to edit/remove the entry.
- */
-
-cr.exportPath('settings');
-
-/**
- * The name of the event fired from this element when the "Edit" option is
- * clicked.
- * @type {string}
- */
-settings.EDIT_STARTUP_URL_EVENT = 'edit-startup-url';
-
-Polymer({
- is: 'settings-startup-url-entry',
-
- behaviors: [cr.ui.FocusRowBehavior],
-
- properties: {
- editable: {
- type: Boolean,
- reflectToAttribute: true,
- },
-
- /** @type {!StartupPageInfo} */
- model: Object,
- },
-
- /** @private */
- onRemoveTap_: function() {
- this.$$('cr-action-menu').close();
- settings.StartupUrlsPageBrowserProxyImpl.getInstance().removeStartupPage(
- this.model.modelIndex);
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onEditTap_: function(e) {
- e.preventDefault();
- this.$$('cr-action-menu').close();
- this.fire(settings.EDIT_STARTUP_URL_EVENT, {
- model: this.model,
- anchor: this.$$('#dots'),
- });
- },
-
- /** @private */
- onDotsTap_: function() {
- const actionMenu =
- /** @type {!CrActionMenuElement} */ (this.$$('#menu').get());
- actionMenu.showAt(assert(this.$$('#dots')));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.html b/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.html
deleted file mode 100644
index cb74a00ae97..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="startup_url_dialog.html">
-<link rel="import" href="startup_url_entry.html">
-<link rel="import" href="startup_urls_page_browser_proxy.html">
-
-<dom-module id="settings-startup-urls-page">
- <template>
- <style include="settings-shared action-link iron-flex">
- .list-frame {
- @apply --settings-list-frame-padding;
- }
-
- .list-frame > div {
- border-top: var(--cr-separator-line);
- }
-
- #outer {
- @apply --settings-list-frame-padding;
- max-height: 355px; /** Enough height to show six entries. */
- }
-
- #container settings-startup-url-entry {
- cursor: default;
- }
- </style>
- <div id="outer" class="layout vertical flex">
- <div id="container" class="scroll-container" scrollable>
- <iron-list items="[[startupPages_]]" scroll-target="container"
- preserve-focus risk-selection class="cr-separators">
- <template>
- <settings-startup-url-entry model="[[item]]" first$="[[!index]]"
- tabindex$="[[tabIndex]]" iron-list-tab-index="[[tabIndex]]"
- last-focused="{{lastFocused_}}" list-blurred="{{listBlurred_}}"
- editable="[[shouldAllowUrlsEdit_(
- prefs.session.startup_urls.enforcement)]]">
- </settings-startup-url-entry>
- </template>
- </iron-list>
- </div>
- </div>
- <div class="list-frame">
- <template is="dom-if" if="[[shouldAllowUrlsEdit_(
- prefs.session.startup_urls.enforcement)]]" restamp>
- <div class="list-item" id="addPage">
- <a is="action-link" class="list-button" on-click="onAddPageTap_">
- $i18n{onStartupAddNewPage}
- </a>
- </div>
- <div class="list-item" id="useCurrentPages">
- <a is="action-link" class="list-button"
- on-click="onUseCurrentPagesTap_">
- $i18n{onStartupUseCurrent}
- </a>
- </div>
- </template>
- <template is="dom-if" if="[[prefs.session.startup_urls.extensionId]]"
- restamp>
- <extension-controlled-indicator
- extension-id="[[prefs.session.startup_urls.extensionId]]"
- extension-name="[[prefs.session.startup_urls.controlledByName]]"
- extension-can-be-disabled="[[
- prefs.session.startup_urls.extensionCanBeDisabled]]">
- </extension-controlled-indicator>
- </template>
- </div>
- <template is="dom-if" if="[[showStartupUrlDialog_]]" restamp>
- <settings-startup-url-dialog model="[[startupUrlDialogModel_]]"
- on-close="destroyUrlDialog_">
- </settings-startup-url-dialog>
- </template>
- </template>
- <script src="startup_urls_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js b/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js
deleted file mode 100644
index 901bae68a5d..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-startup-urls-page' is the settings page
- * containing the urls that will be opened when chrome is started.
- */
-
-Polymer({
- is: 'settings-startup-urls-page',
-
- behaviors: [CrScrollableBehavior, WebUIListenerBehavior],
-
- properties: {
- prefs: Object,
-
- /**
- * Pages to load upon browser startup.
- * @private {!Array<!StartupPageInfo>}
- */
- startupPages_: Array,
-
- /** @private */
- showStartupUrlDialog_: Boolean,
-
- /** @private {?StartupPageInfo} */
- startupUrlDialogModel_: Object,
-
- /** @private {Object}*/
- lastFocused_: Object,
-
- /** @private */
- listBlurred_: Boolean,
- },
-
- /** @private {?settings.StartupUrlsPageBrowserProxy} */
- browserProxy_: null,
-
- /**
- * The element to return focus to, when the startup-url-dialog is closed.
- * @private {?HTMLElement}
- */
- startupUrlDialogAnchor_: null,
-
- /** @override */
- attached: function() {
- this.browserProxy_ = settings.StartupUrlsPageBrowserProxyImpl.getInstance();
- this.addWebUIListener('update-startup-pages', startupPages => {
- // If an "edit" URL dialog was open, close it, because the underlying page
- // might have just been removed (and model indices have changed anyway).
- if (this.startupUrlDialogModel_) {
- this.destroyUrlDialog_();
- }
- this.startupPages_ = startupPages;
- this.updateScrollableContents();
- });
- this.browserProxy_.loadStartupPages();
-
- this.addEventListener(settings.EDIT_STARTUP_URL_EVENT, event => {
- this.startupUrlDialogModel_ = event.detail.model;
- this.startupUrlDialogAnchor_ = event.detail.anchor;
- this.showStartupUrlDialog_ = true;
- event.stopPropagation();
- });
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onAddPageTap_: function(e) {
- e.preventDefault();
- this.showStartupUrlDialog_ = true;
- this.startupUrlDialogAnchor_ =
- /** @type {!HTMLElement} */ (this.$$('#addPage a[is=action-link]'));
- },
-
- /** @private */
- destroyUrlDialog_: function() {
- this.showStartupUrlDialog_ = false;
- this.startupUrlDialogModel_ = null;
- if (this.startupUrlDialogAnchor_) {
- cr.ui.focusWithoutInk(assert(this.startupUrlDialogAnchor_));
- this.startupUrlDialogAnchor_ = null;
- }
- },
-
- /** @private */
- onUseCurrentPagesTap_: function() {
- this.browserProxy_.useCurrentPages();
- },
-
- /**
- * @return {boolean} Whether "Add new page" and "Use current pages" are
- * allowed.
- * @private
- */
- shouldAllowUrlsEdit_: function() {
- return this.get('prefs.session.startup_urls.enforcement') !=
- chrome.settingsPrivate.Enforcement.ENFORCED;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.html b/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.html
deleted file mode 100644
index 73c53f0545f..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<link rel="href" src="chrome://resources/html/assert.html">
-<link rel="href" src="chrome://resources/html/cr.html">
-<link rel="href" src="chrome://resources/html/promise_resolver.html">
-<script src="startup_urls_page_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.js b/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.js
deleted file mode 100644
index df50bb96cde..00000000000
--- a/chromium/chrome/browser/resources/settings/on_startup_page/startup_urls_page_browser_proxy.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{
- * modelIndex: number,
- * title: string,
- * tooltip: string,
- * url: string
- * }}
- */
-let StartupPageInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class StartupUrlsPageBrowserProxy {
- loadStartupPages() {}
- useCurrentPages() {}
-
- /**
- * @param {string} url
- * @return {!Promise<boolean>} Whether the URL is valid.
- */
- validateStartupPage(url) {}
-
- /**
- * @param {string} url
- * @return {!Promise<boolean>} Whether the URL was actually added, or
- * ignored because it was invalid.
- */
- addStartupPage(url) {}
-
- /**
- * @param {number} modelIndex
- * @param {string} url
- * @return {!Promise<boolean>} Whether the URL was actually edited, or
- * ignored because it was invalid.
- */
- editStartupPage(modelIndex, url) {}
-
- /** @param {number} index */
- removeStartupPage(index) {}
- }
-
- /**
- * @implements {settings.StartupUrlsPageBrowserProxy}
- */
- class StartupUrlsPageBrowserProxyImpl {
- /** @override */
- loadStartupPages() {
- chrome.send('onStartupPrefsPageLoad');
- }
-
- /** @override */
- useCurrentPages() {
- chrome.send('setStartupPagesToCurrentPages');
- }
-
- /** @override */
- validateStartupPage(url) {
- return cr.sendWithPromise('validateStartupPage', url);
- }
-
- /** @override */
- addStartupPage(url) {
- return cr.sendWithPromise('addStartupPage', url);
- }
-
- /** @override */
- editStartupPage(modelIndex, url) {
- return cr.sendWithPromise('editStartupPage', modelIndex, url);
- }
-
- /** @override */
- removeStartupPage(index) {
- chrome.send('removeStartupPage', [index]);
- }
- }
-
- cr.addSingletonGetter(StartupUrlsPageBrowserProxyImpl);
-
- return {
- StartupUrlsPageBrowserProxy: StartupUrlsPageBrowserProxy,
- StartupUrlsPageBrowserProxyImpl: StartupUrlsPageBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/open_window_proxy.html b/chromium/chrome/browser/resources/settings/open_window_proxy.html
deleted file mode 100644
index 14336c432cb..00000000000
--- a/chromium/chrome/browser/resources/settings/open_window_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="open_window_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/open_window_proxy.js b/chromium/chrome/browser/resources/settings/open_window_proxy.js
deleted file mode 100644
index 5aab78cd5d2..00000000000
--- a/chromium/chrome/browser/resources/settings/open_window_proxy.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used to open a URL in a new tab.
- * the browser.
- */
-
-cr.exportPath('settings');
-
-cr.define('settings', function() {
- /** @interface */
- class OpenWindowProxy {
- /**
- * Opens the specified URL in a new tab.
- * @param {string} url
- */
- openURL(url) {}
- }
-
- /** @implements {settings.OpenWindowProxy} */
- class OpenWindowProxyImpl {
- /** @override */
- openURL(url) {
- window.open(url);
- }
- }
-
- cr.addSingletonGetter(OpenWindowProxyImpl);
-
- return {
- OpenWindowProxy: OpenWindowProxy,
- OpenWindowProxyImpl: OpenWindowProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/os_settings_manifest.json b/chromium/chrome/browser/resources/settings/os_settings_manifest.json
deleted file mode 100644
index adbabe2f7bb..00000000000
--- a/chromium/chrome/browser/resources/settings/os_settings_manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "$i18nRaw{name}",
- "display": "standalone",
- "icons": [
- {
- "src": "icon-192.png",
- "sizes": "192x192",
- "type": "image/png"
- }
- ],
- "start_url": "/",
- "theme_color": "#fff"
-}
diff --git a/chromium/chrome/browser/resources/settings/page_visibility.html b/chromium/chrome/browser/resources/settings/page_visibility.html
deleted file mode 100644
index 8ea50773be0..00000000000
--- a/chromium/chrome/browser/resources/settings/page_visibility.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-
-<script src="page_visibility.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/page_visibility.js b/chromium/chrome/browser/resources/settings/page_visibility.js
deleted file mode 100644
index 6096033fafd..00000000000
--- a/chromium/chrome/browser/resources/settings/page_visibility.js
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Specifies page visibility based on incognito status, Chrome OS guest mode,
- * and whether or not to include OS settings. Once the Chrome OS SplitSettings
- * project is completed this can be changed to only consider incognito and
- * guest mode. https://crbug.com/950007
- * @typedef {{
- * a11y: (boolean|undefined|A11yPageVisibility),
- * advancedSettings: (boolean|undefined),
- * appearance: (boolean|undefined|AppearancePageVisibility),
- * autofill: (boolean|undefined),
- * bluetooth: (boolean|undefined),
- * dateTime: (boolean|undefined),
- * defaultBrowser: (boolean|undefined),
- * device: (boolean|undefined),
- * downloads: (boolean|undefined|DownloadsPageVisibility),
- * internet: (boolean|undefined),
- * languages: (boolean|undefined|LanguagesPageVisibility),
- * multidevice: (boolean|undefined),
- * onStartup: (boolean|undefined),
- * people: (boolean|undefined|PeoplePageVisibility),
- * printing: (boolean|undefined),
- * privacy: (boolean|undefined|PrivacyPageVisibility),
- * reset:(boolean|undefined|ResetPageVisibility),
- * }}
- */
-let PageVisibility;
-
-/**
- * @typedef {{
- * webstoreLink: boolean,
- * }}
- */
-let A11yPageVisibility;
-
-/**
- * TODO(crbug.com/950007): Remove setWallpaper after SplitSettings launch.
- * @typedef {{
- * bookmarksBar: boolean,
- * homeButton: boolean,
- * pageZoom: boolean,
- * setTheme: boolean,
- * setWallpaper: boolean,
- * }}
- */
-let AppearancePageVisibility;
-
-/**
- * @typedef {{
- * googleDrive: boolean,
- * smbShares: boolean,
- * }}
- */
-let DownloadsPageVisibility;
-
-/**
- * @typedef {{
- * googleAccounts: boolean,
- * kerberosAccounts: boolean,
- * lockScreen: boolean,
- * manageUsers: boolean,
- * }}
- */
-let PeoplePageVisibility;
-
-/**
- * @typedef {{
- * contentProtectionAttestation: boolean,
- * networkPrediction: boolean,
- * searchPrediction: boolean,
- * wakeOnWifi: boolean,
- * }}
- */
-let PrivacyPageVisibility;
-
-/**
- * @typedef {{
- * powerwash: boolean,
- * }}
- */
-let ResetPageVisibility;
-
-/**
- * @typedef {{
- * manageInputMethods: boolean,
- * inputMethodsList: boolean,
- * }}
- */
-let LanguagesPageVisibility;
-
-cr.define('settings', function() {
- /**
- * Dictionary defining page visibility.
- * @type {!PageVisibility}
- */
- let pageVisibility;
-
- const showOSSettings = loadTimeData.getBoolean('showOSSettings');
- const isAccountManagerEnabled =
- loadTimeData.valueExists('isAccountManagerEnabled') &&
- loadTimeData.getBoolean('isAccountManagerEnabled');
- const isKerberosEnabled = loadTimeData.valueExists('isKerberosEnabled') &&
- loadTimeData.getBoolean('isKerberosEnabled');
-
- if (loadTimeData.getBoolean('isGuest')) {
- // "if not chromeos" and "if chromeos" in two completely separate blocks
- // to work around closure compiler.
- // <if expr="not chromeos">
- pageVisibility = {
- autofill: false,
- people: false,
- onStartup: false,
- reset: false,
- appearance: false,
- defaultBrowser: false,
- advancedSettings: false,
- extensions: false,
- printing: false,
- languages: false,
- };
- // </if>
- // <if expr="chromeos">
- pageVisibility = {
- internet: showOSSettings,
- bluetooth: showOSSettings,
- multidevice: false,
- autofill: false,
- people: false,
- onStartup: false,
- reset: false,
- appearance: {
- setWallpaper: false,
- setTheme: false,
- homeButton: false,
- bookmarksBar: false,
- pageZoom: false,
- },
- device: showOSSettings,
- advancedSettings: true,
- dateTime: showOSSettings,
- privacy: {
- contentProtectionAttestation: showOSSettings,
- searchPrediction: false,
- networkPrediction: false,
- wakeOnWifi: showOSSettings,
- },
- downloads: {
- googleDrive: false,
- smbShares: false,
- },
- a11y: {
- webstoreLink: showOSSettings,
- },
- extensions: false,
- printing: true,
- languages: {
- manageInputMethods: showOSSettings,
- inputMethodsList: showOSSettings,
- },
- };
- // </if>
- } else {
- // All pages are visible when not in chromeos. Since polymer only notifies
- // after a property is set.
- // <if expr="chromeos">
- pageVisibility = {
- internet: showOSSettings,
- bluetooth: showOSSettings,
- multidevice: showOSSettings,
- autofill: true,
- people: {
- lockScreen: showOSSettings,
- kerberosAccounts: showOSSettings && isKerberosEnabled,
- googleAccounts: showOSSettings && isAccountManagerEnabled,
- manageUsers: showOSSettings,
- },
- onStartup: true,
- reset: {
- powerwash: showOSSettings,
- },
- appearance: {
- setWallpaper: showOSSettings,
- setTheme: true,
- homeButton: true,
- bookmarksBar: true,
- pageZoom: true,
- },
- device: showOSSettings,
- advancedSettings: true,
- dateTime: showOSSettings,
- privacy: {
- contentProtectionAttestation: showOSSettings,
- searchPrediction: true,
- networkPrediction: true,
- wakeOnWifi: showOSSettings,
- },
- downloads: {
- googleDrive: showOSSettings,
- smbShares: showOSSettings,
- },
- a11y: {
- webstoreLink: showOSSettings,
- },
- extensions: true,
- printing: true,
- languages: {
- manageInputMethods: showOSSettings,
- inputMethodsList: showOSSettings,
- },
- };
- // </if>
- }
-
- return {pageVisibility: pageVisibility};
-});
diff --git a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.html b/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.html
deleted file mode 100644
index 9e5cdfc8400..00000000000
--- a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="parental_controls_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.js b/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.js
deleted file mode 100644
index 45c48b39a07..00000000000
--- a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_browser_proxy.js
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Browser Proxy for Parental Controls functions.
- */
-
-cr.define('parental_controls', function() {
- /** @interface */
- class BrowserProxy {
- /**
- * Shows the Add Supervsion dialog.
- */
- showAddSupervisionDialog() {}
-
- /**
- * Launches an app that shows the Family Link Settings. Depending
- * on whether the Family Link Helper app is available, this might
- * launch the app, or take some kind of backup/default action.
- */
- launchFamilyLinkSettings() {}
- }
-
- /** @implements {parental_controls.BrowserProxy} */
- class BrowserProxyImpl {
- /** @override */
- showAddSupervisionDialog() {
- chrome.send('showAddSupervisionDialog');
- }
-
- /** @override */
- launchFamilyLinkSettings() {
- chrome.send('launchFamilyLinkSettings');
- }
- }
-
- cr.addSingletonGetter(BrowserProxyImpl);
-
- return {
- BrowserProxy: BrowserProxy,
- BrowserProxyImpl: BrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.html b/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.html
deleted file mode 100644
index 3a55afc70c6..00000000000
--- a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="parental_controls_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-parental-controls-page">
- <template>
- <style include="settings-shared">
- cr-link-row {
- --cr-section-padding: 0;
- }
- </style>
- <div id="parental-controls-item" class="settings-box two-line">
- <template is="dom-if" if="[[isChild_]]">
- <cr-link-row on-click="handleFamilyLinkButtonClick_"
- label="$i18n{parentalControlsPageTitle}"
- sub-label="$i18n{parentalControlsPageViewSettingsLabel}"
- external>
- <iron-icon icon="cr20:kite" aria-hidden="true"></iron-icon>
- </cr-link-row>
- </template>
- <template is="dom-if" if="[[!isChild_]]">
- <div class="start settings-box-text">
- <div>
- $i18n{parentalControlsPageTitle}
- </div>
- <div class="secondary">
- [[getSetupLabelText_(online_)]]
- </div>
- </div>
- <div class="separator"></div>
- <cr-button on-click="handleSetupButtonClick_" disabled$="[[!online_]]">
- $i18n{parentalControlsSetUpButtonLabel}
- </cr-button>
- </template>
- </div>
- </template>
- <script src="parental_controls_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.js b/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.js
deleted file mode 100644
index 85c58b62dff..00000000000
--- a/chromium/chrome/browser/resources/settings/parental_controls_page/parental_controls_page.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Settings page for managing Parental Controls features.
- */
-
-Polymer({
- is: 'settings-parental-controls-page',
-
- behaviors: [
- I18nBehavior,
- ],
-
- properties: {
- /** @private */
- isChild_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isChild');
- }
- },
-
- /** @private */
- online_: {
- type: Boolean,
- value: function() {
- return navigator.onLine;
- }
- },
- },
-
- /** @override */
- created: function() {
- this.browserProxy_ = parental_controls.BrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- // Set up online/offline listeners.
- window.addEventListener('offline', this.onOffline_.bind(this));
- window.addEventListener('online', this.onOnline_.bind(this));
- },
-
- /**
- * Updates the UI when the device goes offline.
- * @private
- */
- onOffline_: function() {
- this.online_ = false;
- },
-
- /**
- * Updates the UI when the device comes online.
- * @private
- */
- onOnline_: function() {
- this.online_ = true;
- },
-
- /**
- * @return {string} Returns the string to display in the main
- * description area for non-child users.
- * @private
- */
- getSetupLabelText_: function(online) {
- if (online) {
- return this.i18n('parentalControlsPageSetUpLabel');
- } else {
- return this.i18n('parentalControlsPageConnectToInternetLabel');
- }
- },
-
- /** @private */
- handleSetupButtonClick_: function(event) {
- event.stopPropagation();
- this.browserProxy_.showAddSupervisionDialog();
- },
-
- /** @private */
- handleFamilyLinkButtonClick_: function(event) {
- event.stopPropagation();
- this.browserProxy_.launchFamilyLinkSettings();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/account_manager.html b/chromium/chrome/browser/resources/settings/people_page/account_manager.html
deleted file mode 100644
index f5d9d8bc29f..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/account_manager.html
+++ /dev/null
@@ -1,207 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_tooltip_icon.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="account_manager_browser_proxy.html">
-
-<dom-module id="settings-account-manager">
- <template>
- <style include="settings-shared iron-flex iron-flex-alignment">
- :host {
- --add-account-margin-top: 16px;
- }
-
- .profile-icon {
- background: center / cover no-repeat;
- border-radius: 20px;
- flex-shrink: 0;
- height: 40px;
- width: 40px;
- }
-
- .account-list-item {
- padding-inline-end: 8px;
- padding-inline-start: 0;
- }
-
- .account-manager-description {
- /* Up to 70 characters. */
- max-width: 35em;
- }
-
- #outer {
- margin-inline-end: var(--cr-section-padding);
- margin-inline-start: 60px;
- }
-
- .secondary-accounts-policy-indicator {
- margin-inline-end: 12px;
- }
-
- .settings-box.user-message {
- align-items: flex-end;
- }
-
- #account-list-header > h2 {
- padding-bottom: 12px;
- padding-top: 12px;
- }
-
- cr-policy-indicator {
- margin-inline-end: 1em;
- margin-top: var(--add-account-margin-top);
- }
-
- #add-account-button {
- margin-bottom: 12px;
- margin-top: 12px;
- }
-
- #add-account-icon {
- -webkit-mask-image: url(chrome://resources/images/add.svg);
- background-color: currentColor;
- height: 24px;
- margin-inline-end: 0.5em;
- width: 24px;
- }
-
- .signed-out-text {
- color: var(--google-red-600);
- }
-
- .error-badge {
- background: url(chrome://resources/images/error_badge.svg)
- center / cover no-repeat;
- display: block;
- height: 20px;
- left: 60%;
- position: relative;
- top: 60%;
- width: 20px;
- }
-
- :host-context([dir='rtl']) .error-badge {
- left: auto;
- right: 60%;
- }
-
- .management-status {
- color: var(--cr-secondary-text-color);
- }
-
- .tooltip-primary-account {
- margin-inline-end: 12px;
- margin-inline-start: 12px;
- }
- </style>
-
- <div class="settings-box first">
- <span class="account-manager-description">
- $i18n{accountManagerDescription}
- <a href="$i18nRaw{accountManagerLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </span>
- </div>
-
- <div id="settings-box-user-message" class="settings-box first user-message"
- hidden="[[isSecondaryGoogleAccountSigninAllowed_()]]">
- <cr-policy-pref-indicator class="secondary-accounts-policy-indicator"
- pref=
- "[[prefs.account_manager.secondary_google_account_signin_allowed]]">
- </cr-policy-pref-indicator>
- <div id="user-message-text" class="secondary">
- [[getSecondaryAccountsDisabledUserMessage_()]]
- </div>
- </div>
-
- <div class="settings-box first">
- <div id="account-list-header" class="flex">
- <h2>$i18n{accountListHeader}</h2>
- </div>
- <cr-button disabled="[[!isSecondaryGoogleAccountSigninAllowed_()]]"
- id="add-account-button" on-tap="addAccount_">
- <div id="add-account-icon"></div>
- $i18n{addAccountLabel}
- </cr-button>
- </div>
-
- <div id="outer" class="layout vertical nowrap">
- <template is="dom-repeat" id="account-list" items="[[accounts_]]">
- <div class="settings-box account-list-item">
-
- <div class="profile-icon"
- style="background-image: [[getIconImageSet_(item.pic)]]">
- <template is="dom-if" if="[[!item.isSignedIn]]">
- <span class="error-badge"></span>
- </template>
- </div>
-
- <div class="middle two-line no-min-width">
- <div class="flex text-elide">
- <!-- If account is signed in, display the full name -->
- <template is="dom-if" if="[[item.isSignedIn]]">
- <span>[[item.fullName]]</span>
- </template>
- <!-- Else, display a re-authentication message -->
- <template is="dom-if" if="[[!item.isSignedIn]]">
- <span class="signed-out-text">
- [[getAccountManagerSignedOutName_(item.unmigrated)]]
- </span>
- </template>
-
- <div class="secondary">[[item.email]]</div>
- </div>
- </div>
-
- <template is="dom-if"
- if="[[shouldShowReauthenticationButton_(item)]]">
- <cr-button title="[[getAccountManagerSignedOutTitle_(item)]]"
- class="reauth-button" on-click="onReauthenticationTap_">
- [[getAccountManagerSignedOutLabel_(item.unmigrated)]]
- </cr-button>
- </template>
-
- <!-- If this is the Device Account, display the management status -->
- <template is="dom-if" if="[[item.isDeviceAccount]]">
- <cr-tooltip-icon icon-class="cr:info-outline"
- class="tooltip-primary-account"
- tooltip-text="$i18n{accountManagerPrimaryAccountTooltip}"
- icon-aria-label="$i18n{accountManagerPrimaryAccountTooltip}">
- </cr-tooltip-icon>
- <span class="management-status">
- [[getManagementLabel_(item)]]
- </span>
- </template>
- <!-- Else, display a hamburger menu for removing the account -->
- <template is="dom-if" if="[[!item.isDeviceAccount]]">
- <cr-icon-button class="icon-more-vert"
- title="[[getMoreActionsTitle_(item)]]"
- on-click="onAccountActionsMenuButtonTap_">
- </cr-icon-button>
- </template>
- </div>
- </template>
-
- <div class="clear settings-box"></div>
-
- <cr-action-menu>
- <button class="dropdown-item" on-click="onRemoveAccountTap_">
- $i18n{removeAccountLabel}
- </button>
- </cr-action-menu>
- </div>
- </template>
- <script src="account_manager.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/account_manager.js b/chromium/chrome/browser/resources/settings/people_page/account_manager.js
deleted file mode 100644
index e5cecdcd17f..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/account_manager.js
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-account-manager' is the settings subpage containing controls to
- * list, add and delete Secondary Google Accounts.
- */
-
-Polymer({
- is: 'settings-account-manager',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /**
- * List of Accounts.
- * @type {!Array<settings.Account>}
- */
- accounts_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The targeted account for menu operations.
- * @private {?settings.Account}
- */
- actionMenuAccount_: Object,
- },
-
- /** @private {?settings.AccountManagerBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.addWebUIListener('accounts-changed', this.refreshAccounts_.bind(this));
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.AccountManagerBrowserProxyImpl.getInstance();
- this.refreshAccounts_();
- },
-
- /**
- * @param {!settings.Route} newRoute
- * @param {settings.Route} oldRoute
- */
- currentRouteChanged: function(newRoute, oldRoute) {
- if (newRoute == settings.routes.ACCOUNT_MANAGER) {
- this.browserProxy_.showWelcomeDialogIfRequired();
- }
- },
-
- /**
- * @return {boolean} True if secondary account sign-ins are allowed, false
- * otherwise.
- * @private
- */
- isSecondaryGoogleAccountSigninAllowed_: function() {
- return loadTimeData.getBoolean('secondaryGoogleAccountSigninAllowed');
- },
-
- /**
- * @return {string} 'Secondary Accounts disabled' message depending on
- * account type
- * @private
- */
- getSecondaryAccountsDisabledUserMessage_: function() {
- return loadTimeData.getBoolean('isChild')
- ? this.i18n('accountManagerSecondaryAccountsDisabledChildText')
- : this.i18n('accountManagerSecondaryAccountsDisabledText');
- },
-
- /**
- * @param {string} iconUrl
- * @return {string} A CSS image-set for multiple scale factors.
- * @private
- */
- getIconImageSet_: function(iconUrl) {
- return cr.icon.getImage(iconUrl);
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- addAccount_: function(event) {
- this.browserProxy_.addAccount();
- },
-
- /**
- * @param {!settings.Account} account
- * @return {boolean} True if the account reauthentication button should be
- * shown, false otherwise.
- * @private
- */
- shouldShowReauthenticationButton_: function(account) {
- // Device account re-authentication cannot be handled in-session, primarily
- // because the user may have changed their password (leading to an LST
- // invalidation) and we do not have a mechanism to change the cryptohome
- // password in-session.
- return !account.isDeviceAccount && !account.isSignedIn;
- },
-
- /**
- * @param {!settings.Account} account
- * @return {string} An appropriate management status label. e.g.
- * "Primary account" for unmanaged accounts, "Managed by <Domain>"
- * for Enterprise managed accounts etc.
- * @private
- */
- getManagementLabel_: function(account) {
- if (account.organization) {
- return this.i18n('accountManagerManagedLabel', account.organization);
- }
-
- return this.i18n('accountManagerUnmanagedLabel');
- },
-
- /**
- * @param {boolean} unmigrated
- * @private
- */
- getAccountManagerSignedOutName_: function(unmigrated) {
- return this.i18n(unmigrated ? 'accountManagerUnmigratedAccountName'
- : 'accountManagerSignedOutAccountName');
- },
-
- /**
- * @param {boolean} unmigrated
- * @private
- */
- getAccountManagerSignedOutLabel_: function(unmigrated) {
- return this.i18n(unmigrated ? 'accountManagerMigrationLabel'
- : 'accountManagerReauthenticationLabel');
- },
-
-
- /**
- * @param {!settings.Account} account
- * @private
- */
- getAccountManagerSignedOutTitle_: function(account) {
- const label = account.unmigrated ? 'accountManagerMigrationTooltip'
- : 'accountManagerReauthenticationTooltip';
- return loadTimeData.getStringF(label, account.email);
- },
-
- /**
- * @param {!settings.Account} account
- * @private
- */
- getMoreActionsTitle_: function(account) {
- return loadTimeData.getStringF('accountManagerMoreActionsTooltip',
- account.email);
- },
-
- /**
- * @param {!CustomEvent<!{model: !{item: !settings.Account}}>} event
- * @private
- */
- onReauthenticationTap_: function(event) {
- if (event.model.item.unmigrated) {
- this.browserProxy_.migrateAccount(event.model.item.email);
- } else {
- this.browserProxy_.reauthenticateAccount(event.model.item.email);
- }
- },
-
- /**
- * @private
- */
- refreshAccounts_: function() {
- this.browserProxy_.getAccounts().then(accounts => {
- this.set('accounts_', accounts);
- });
- },
-
- /**
- * Opens the Account actions menu.
- * @param {!{model: !{item: settings.Account}, target: !Element}} event
- * @private
- */
- onAccountActionsMenuButtonTap_: function(event) {
- this.actionMenuAccount_ = event.model.item;
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
- .showAt(event.target);
- },
-
- /**
- * Closes action menu and resets action menu model.
- * @private
- */
- closeActionMenu_: function() {
- this.$$('cr-action-menu').close();
- this.actionMenuAccount_ = null;
- },
-
- /**
- * Removes the account being pointed to by |this.actionMenuAccount_|.
- * @private
- */
- onRemoveAccountTap_: function() {
- this.browserProxy_.removeAccount(
- /** @type {?settings.Account} */ (this.actionMenuAccount_));
- this.closeActionMenu_();
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.html
deleted file mode 100644
index 7228eadf8dc..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="account_manager_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js
deleted file mode 100644
index b08b02268b5..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Google Accounts" subsection of
- * the "People" section of Settings, to interact with the browser. Chrome OS
- * only.
- */
-cr.exportPath('settings');
-
-/**
- * Information for an account managed by Chrome OS AccountManager.
- * @typedef {{
- * id: string,
- * accountType: number,
- * isDeviceAccount: boolean,
- * isSignedIn: boolean,
- * unmigrated: boolean,
- * fullName: string,
- * email: string,
- * pic: string,
- * organization: (string|undefined),
- * }}
- */
-settings.Account;
-
-cr.define('settings', function() {
- /** @interface */
- class AccountManagerBrowserProxy {
- /**
- * Returns a Promise for the list of GAIA accounts held in AccountManager.
- * @return {!Promise<!Array<settings.Account>>}
- */
- getAccounts() {}
-
- /**
- * Triggers the 'Add account' flow.
- */
- addAccount() {}
-
- /**
- * Triggers the re-authentication flow for the account pointed to by
- * |account_email|.
- * @param {string} account_email
- */
- reauthenticateAccount(account_email) {}
-
- /**
- * Triggers the migration dialog for the account pointed to by
- * |account_email|.
- * @param {string} account_email
- */
- migrateAccount(account_email) {}
-
- /**
- * Removes |account| from Account Manager.
- * @param {?settings.Account} account
- */
- removeAccount(account) {}
-
- /**
- * Displays the Account Manager welcome dialog if required.
- */
- showWelcomeDialogIfRequired() {}
- }
-
- /**
- * @implements {settings.AccountManagerBrowserProxy}
- */
- class AccountManagerBrowserProxyImpl {
- /** @override */
- getAccounts() {
- return cr.sendWithPromise('getAccounts');
- }
-
- /** @override */
- addAccount() {
- chrome.send('addAccount');
- }
-
- /** @override */
- reauthenticateAccount(account_email) {
- chrome.send('reauthenticateAccount', [account_email]);
- }
-
- /** @override */
- migrateAccount(account_email) {
- chrome.send('migrateAccount', [account_email]);
- }
-
- /** @override */
- removeAccount(account) {
- chrome.send('removeAccount', [account]);
- }
-
- /** @override */
- showWelcomeDialogIfRequired() {
- chrome.send('showWelcomeDialogIfRequired');
- }
- }
-
- cr.addSingletonGetter(AccountManagerBrowserProxyImpl);
-
- return {
- AccountManagerBrowserProxy: AccountManagerBrowserProxy,
- AccountManagerBrowserProxyImpl: AccountManagerBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/change_picture.html b/chromium/chrome/browser/resources/settings/people_page/change_picture.html
deleted file mode 100644
index 1dc7a5d0d42..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/change_picture.html
+++ /dev/null
@@ -1,118 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_list.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_pane.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_types.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="change_picture_browser_proxy.html">
-
-<dom-module id="settings-change-picture">
- <template>
- <style>
- :host {
- --cr-toolbar-height: 56px;
- /* #headerLine height + padding */
- --cr-settings-header-height: calc(62px + 1.34em);
- --title-height: 2em;
- --title-padding: 16px;
- display: block;
- min-height: 328px;
- }
-
- #title {
- height: var(--title-height);
- margin-inline-start: 20px;
- padding-top: var(--title-padding);
- }
-
- #container {
- align-items: flex-start;
- display: flex;
- margin-inline-start: 20px;
- position: absolute;
- top: calc(var(--cr-settings-header-height) +
- var(--title-padding) +
- var(--title-height));
- user-select: none;
- }
-
- #picturePane {
- --cr-picture-image-size: 192px;
- flex-shrink: 0;
- height: 288px;
- margin-inline-end: 24px;
- margin-top: 6px;
- position: relative;
- width: 288px;
- }
-
- #authorCredit {
- color: var(--paper-grey-500);
- display: flex;
- flex-direction: column;
- margin-top: 20px;
- }
-
- #pictureList {
- /* TODO(reveman): Find a way to have height align to viewport
- without using fixed position. */
- height: calc(100vh -
- var(--cr-toolbar-height) -
- var(--cr-settings-header-height) -
- var(--title-padding) -
- var(--title-height));
- margin-inline-end: 16px;
- margin-top: 0;
- min-height: 332px;
- overflow-x: hidden;
- overflow-y: auto;
- position: relative;
- }
-
- </style>
- <div id="title">$i18n{changePicturePageDescription}</div>
- <div id="container">
- <div>
- <cr-picture-pane id="picturePane"
- camera-present="[[cameraPresent_]]",
- image-src="[[getImageSrc_(selectedItem_)]]"
- image-type="[[getImageType_(selectedItem_)]]"
- discard-image-label="$i18n{discardPhoto}"
- preview-alt-text="$i18n{previewAltText}"
- take-photo-label="$i18n{takePhoto}"
- capture-video-label="$i18n{captureVideo}"
- switch-mode-to-camera-label="$i18n{switchModeToCamera}"
- switch-mode-to-video-label="$i18n{switchModeToVideo}"
- camera-video-mode-enabled="[[cameraVideoModeEnabled_]]"
- on-keys-pressed="onCameraPaneKeysPressed_">
- </cr-picture-pane>
- <div id="authorCredit"
- hidden="[[!isAuthorCreditShown_(selectedItem_)]]">
- [[getAuthorCredit_(selectedItem_, defaultImages_)]]
- <a href="[[getAuthorWebsite_(selectedItem_, defaultImages_)]]"
- target="_blank">
- [[getAuthorWebsite_(selectedItem_, defaultImages_)]]
- </a>
- </div>
- </div>
- <cr-picture-list id="pictureList"
- hidden="[[!defaultImages_]]"
- camera-present="[[cameraPresent_]]"
- default-images="[[getDefaultImages_(defaultImages_,
- firstDefaultImageIndex_)]]"
- selected-item="{{selectedItem_}}"
- choose-file-label="$i18n{chooseFile}"
- old-image-label="$i18n{oldPhoto}"
- profile-image-label="$i18n{profilePhoto}"
- take-photo-label="$i18n{takePhoto}"
- capture-video-label="$i18n{captureVideo}">
- </cr-picture-list>
- </div>
- </template>
- <script src="change_picture.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/change_picture.js b/chromium/chrome/browser/resources/settings/people_page/change_picture.js
deleted file mode 100644
index 8dd70529250..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/change_picture.js
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-change-picture' is the settings subpage containing controls to
- * edit a ChromeOS user's picture.
- */
-Polymer({
- is: 'settings-change-picture',
-
- behaviors: [
- settings.RouteObserverBehavior,
- I18nBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * True if the user has a plugged-in webcam.
- * @private {boolean}
- */
- cameraPresent_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The currently selected item. This property is bound to the iron-selector
- * and never directly assigned. This may be undefined momentarily as
- * the selection changes due to iron-selector implementation details.
- * @private {?CrPicture.ImageElement}
- */
- selectedItem_: {
- type: Object,
- value: null,
- },
-
- /**
- * The active set of default user images.
- * @private {?Array<!settings.DefaultImage>}
- */
- defaultImages_: {
- type: Object,
- value: null,
- },
-
- /**
- * The index of the first default image to use in the selection list.
- * @private
- */
- firstDefaultImageIndex_: Number,
-
- /**
- * True when camera video mode is enabled.
- * @private {boolean}
- */
- cameraVideoModeEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('changePictureVideoModeEnabled');
- },
- readOnly: true,
- },
- },
-
- listeners: {
- 'discard-image': 'onDiscardImage_',
- 'image-activate': 'onImageActivate_',
- 'focus-action': 'onFocusAction_',
- 'photo-taken': 'onPhotoTaken_',
- 'switch-mode': 'onSwitchMode_',
- },
-
- /** @private {?settings.ChangePictureBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?CrPictureListElement} */
- pictureList_: null,
-
- /** @private {boolean} */
- oldImagePending_: false,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.ChangePictureBrowserProxyImpl.getInstance();
- this.pictureList_ =
- /** @type {CrPictureListElement} */ (this.$.pictureList);
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'default-images-changed', this.receiveDefaultImages_.bind(this));
- this.addWebUIListener(
- 'selected-image-changed', this.receiveSelectedImage_.bind(this));
- this.addWebUIListener(
- 'old-image-changed', this.receiveOldImage_.bind(this));
- this.addWebUIListener(
- 'profile-image-changed', this.receiveProfileImage_.bind(this));
- this.addWebUIListener(
- 'camera-presence-changed', this.receiveCameraPresence_.bind(this));
- },
-
-
- /** @protected */
- currentRouteChanged: function(newRoute) {
- if (newRoute == settings.routes.CHANGE_PICTURE) {
- this.browserProxy_.initialize();
- this.browserProxy_.requestSelectedImage();
- this.pictureList_.setFocus();
- } else {
- // Ensure we deactivate the camera when we navigate away.
- this.selectedItem_ = null;
- }
- },
-
- /**
- * Handler for the 'default-images-changed' event.
- * @param {{first: number, images: !Array<!settings.DefaultImage>}} info
- * @private
- */
- receiveDefaultImages_: function(info) {
- this.defaultImages_ = info.images;
- this.firstDefaultImageIndex_ = info.first;
- },
-
- /**
- * Handler for the 'selected-image-changed' event. Is only called with
- * default images.
- * @param {string} imageUrl
- * @private
- */
- receiveSelectedImage_: function(imageUrl) {
- this.pictureList_.setSelectedImageUrl(imageUrl);
- },
-
- /**
- * Handler for the 'old-image-changed' event. The Old image is any selected
- * non-profile and non-default image. It can be from the camera, a file, or a
- * deprecated default image. When this method is called, the Old image
- * becomes the selected image.
- * @param {!{url: string, index: number}} imageInfo
- * @private
- */
- receiveOldImage_: function(imageInfo) {
- this.oldImagePending_ = false;
- this.pictureList_.setOldImageUrl(imageInfo.url, imageInfo.index);
- },
-
- /**
- * Handler for the 'profile-image-changed' event.
- * @param {string} imageUrl
- * @param {boolean} selected
- * @private
- */
- receiveProfileImage_: function(imageUrl, selected) {
- this.pictureList_.setProfileImageUrl(imageUrl, selected);
- },
-
- /**
- * Handler for the 'camera-presence-changed' event.
- * @param {boolean} cameraPresent
- * @private
- */
- receiveCameraPresence_: function(cameraPresent) {
- this.cameraPresent_ = cameraPresent;
- },
-
- /**
- * Selects an image element.
- * @param {!CrPicture.ImageElement} image
- * @private
- */
- selectImage_: function(image) {
- switch (image.dataset.type) {
- case CrPicture.SelectionTypes.CAMERA:
- /** CrPicturePaneElement */ (this.$.picturePane).takePhoto();
- break;
- case CrPicture.SelectionTypes.FILE:
- this.browserProxy_.chooseFile();
- break;
- case CrPicture.SelectionTypes.PROFILE:
- this.browserProxy_.selectProfileImage();
- break;
- case CrPicture.SelectionTypes.OLD:
- const imageIndex = image.dataset.imageIndex;
- if (imageIndex !== undefined && imageIndex >= 0 && image.src) {
- this.browserProxy_.selectDefaultImage(image.dataset.url);
- } else {
- this.browserProxy_.selectOldImage();
- }
- break;
- case CrPicture.SelectionTypes.DEFAULT:
- this.browserProxy_.selectDefaultImage(image.dataset.url);
- break;
- default:
- assertNotReached('Selected unknown image type');
- }
- },
-
- /**
- * Handler for when an image is activated.
- * @param {!CustomEvent<!CrPicture.ImageElement>} event
- * @private
- */
- onImageActivate_: function(event) {
- this.selectImage_(event.detail);
- },
-
- /** Focus the action button in the picture pane. */
- onFocusAction_: function() {
- /** CrPicturePaneElement */ (this.$.picturePane).focusActionButton();
- },
-
- /**
- * @param {!CustomEvent<{photoDataUrl: string}>} event
- * @private
- */
- onPhotoTaken_: function(event) {
- this.oldImagePending_ = true;
- this.browserProxy_.photoTaken(event.detail.photoDataUrl);
- this.pictureList_.setOldImageUrl(event.detail.photoDataUrl);
- this.pictureList_.setFocus();
- announceAccessibleMessage(
- loadTimeData.getString('photoCaptureAccessibleText'));
- },
-
- /**
- * @param {!CustomEvent<boolean>} event
- * @private
- */
- onSwitchMode_: function(event) {
- const videomode = event.detail;
- announceAccessibleMessage(this.i18n(
- videomode ? 'videoModeAccessibleText' : 'photoModeAccessibleText'));
- },
-
- /**
- * Callback the iron-a11y-keys "keys-pressed" event bubbles up from the
- * cr-camera-pane.
- * @param {!CustomEvent<!{key: string, keyboardEvent: Object}>} event
- * @private
- */
- onCameraPaneKeysPressed_(event) {
- this.$.pictureList.focus();
- this.$.pictureList.onKeysPressed(event);
- },
-
- /** @private */
- onDiscardImage_: function() {
- // Prevent image from being discarded if old image is pending.
- if (this.oldImagePending_) {
- return;
- }
- this.pictureList_.setOldImageUrl(CrPicture.kDefaultImageUrl);
- // Revert to profile image as we don't know what last used default image is.
- this.browserProxy_.selectProfileImage();
- announceAccessibleMessage(this.i18n('photoDiscardAccessibleText'));
- },
-
- /**
- * @param {CrPicture.ImageElement} selectedItem
- * @return {string}
- * @private
- */
- getImageSrc_: function(selectedItem) {
- return (selectedItem && selectedItem.dataset.url) || '';
- },
-
- /**
- * @param {CrPicture.ImageElement} selectedItem
- * @return {string}
- * @private
- */
- getImageType_: function(selectedItem) {
- return (selectedItem && selectedItem.dataset.type) ||
- CrPicture.SelectionTypes.NONE;
- },
-
- /**
- * @param {!Array<!settings.DefaultImage>} defaultImages
- * @param {number} firstDefaultImageIndex
- * @return {!Array<!settings.DefaultImage>}
- * @private
- */
- getDefaultImages_(defaultImages, firstDefaultImageIndex) {
- return defaultImages ? defaultImages.slice(firstDefaultImageIndex) : [];
- },
-
- /**
- * @param {CrPicture.ImageElement} selectedItem
- * @return {boolean} True if the author credit text is shown.
- * @private
- */
- isAuthorCreditShown_: function(selectedItem) {
- return !!selectedItem &&
- (selectedItem.dataset.type == CrPicture.SelectionTypes.DEFAULT ||
- (selectedItem.dataset.imageIndex !== undefined &&
- selectedItem.dataset.imageIndex >= 0));
- },
-
- /**
- * @param {!CrPicture.ImageElement} selectedItem
- * @param {!Array<!settings.DefaultImage>} defaultImages
- * @return {string} The author name for the selected default image. An empty
- * string is returned if there is no valid author name.
- * @private
- */
- getAuthorCredit_: function(selectedItem, defaultImages) {
- const index = selectedItem ? selectedItem.dataset.imageIndex : undefined;
- if (index === undefined || index < 0 || index >= defaultImages.length) {
- return '';
- }
- const author = defaultImages[index].author;
- return author ? this.i18n('authorCreditText', author) : '';
- },
-
- /**
- * @param {!CrPicture.ImageElement} selectedItem
- * @param {!Array<!settings.DefaultImage>} defaultImages
- * @return {string} The author name for the selected default image. An empty
- * string is returned if there is no valid author name.
- * @private
- */
- getAuthorWebsite_: function(selectedItem, defaultImages) {
- const index = selectedItem ? selectedItem.dataset.imageIndex : undefined;
- if (index === undefined || index < 0 || index >= defaultImages.length) {
- return '';
- }
- return defaultImages[index].website || '';
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.html
deleted file mode 100644
index 6a983e4ff9a..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="change_picture_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.js
deleted file mode 100644
index 3cd333e51bd..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/change_picture_browser_proxy.js
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Change Picture" subpage of
- * the People section to interact with the browser. ChromeOS only.
- */
-cr.exportPath('settings');
-
-/**
- * An object describing a default image.
- * @typedef {{
- * author: (string|undefined),
- * index: number,
- * title: (string|undefined),
- * url: string,
- * website: (string|undefined)
- * }}
- */
-settings.DefaultImage;
-
-cr.define('settings', function() {
- /** @interface */
- class ChangePictureBrowserProxy {
- /**
- * Retrieves the initial set of default images, profile image, etc. As a
- * response, the C++ sends these WebUIListener events:
- * 'default-images-changed', 'profile-image-changed', 'old-image-changed',
- * and 'selected-image-changed'
- */
- initialize() {}
-
- /**
- * Sets the user image to one of the default images. As a response, the C++
- * sends the 'default-images-changed' WebUIListener event.
- * @param {string} imageUrl
- */
- selectDefaultImage(imageUrl) {}
-
- /**
- * Sets the user image to the 'old' image. As a response, the C++ sends the
- * 'old-image-changed' WebUIListener event.
- */
- selectOldImage() {}
-
- /**
- * Sets the user image to the profile image. As a response, the C++ sends
- * the 'profile-image-changed' WebUIListener event.
- */
- selectProfileImage() {}
-
- /**
- * Provides the taken photo as a data URL to the C++ and sets the user
- * image to the 'old' image. As a response, the C++ sends the
- * 'old-image-changed' WebUIListener event.
- * @param {string} photoDataUrl
- */
- photoTaken(photoDataUrl) {}
-
- /**
- * Requests a file chooser to select a new user image. No response is
- * expected.
- */
- chooseFile() {}
-
- /** Requests the currently selected image. */
- requestSelectedImage() {}
- }
-
- /**
- * @implements {settings.ChangePictureBrowserProxy}
- */
- class ChangePictureBrowserProxyImpl {
- /** @override */
- initialize() {
- chrome.send('onChangePicturePageInitialized');
- }
-
- /** @override */
- selectDefaultImage(imageUrl) {
- chrome.send('selectImage', [imageUrl, 'default']);
- }
-
- /** @override */
- selectOldImage() {
- chrome.send('selectImage', ['', 'old']);
- }
-
- /** @override */
- selectProfileImage() {
- chrome.send('selectImage', ['', 'profile']);
- }
-
- /** @override */
- photoTaken(photoDataUrl) {
- chrome.send('photoTaken', [photoDataUrl]);
- }
-
- /** @override */
- chooseFile() {
- chrome.send('chooseFile');
- }
-
- /** @override */
- requestSelectedImage() {
- chrome.send('requestSelectedImage');
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(ChangePictureBrowserProxyImpl);
-
- return {
- ChangePictureBrowserProxy: ChangePictureBrowserProxy,
- ChangePictureBrowserProxyImpl: ChangePictureBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.html
deleted file mode 100644
index 1e90fb88668..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="fingerprint_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.js
deleted file mode 100644
index 8479d9ce8e1..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/fingerprint_browser_proxy.js
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * @enum {number}
- * These values must be kept in sync with the values in
- * third_party/cros_system_api/dbus/service_constants.h.
- */
-settings.FingerprintResultType = {
- SUCCESS: 0,
- PARTIAL: 1,
- INSUFFICIENT: 2,
- SENSOR_DIRTY: 3,
- TOO_SLOW: 4,
- TOO_FAST: 5,
- IMMOBILE: 6,
-};
-
-/**
- * An object describing a attempt from the fingerprint hardware. The structure
- * of this data must be kept in sync with C++ FingerprintHandler.
- * @typedef {{
- * result: settings.FingerprintResultType,
- * indexes: !Array<number>,
- * }}
- */
-settings.FingerprintAttempt;
-
-/**
- * An object describing a scan from the fingerprint hardware. The structure of
- * this data must be kept in sync with C++ FingerprintHandler.
- * @typedef {{
- * result: settings.FingerprintResultType,
- * isComplete: boolean,
- * percentComplete: number,
- * }}
- */
-settings.FingerprintScan;
-
-/**
- * An object describing the necessary info to display on the fingerprint
- * settings. The structure of this data must be kept in sync with
- * C++ FingerprintHandler.
- * @typedef {{
- * fingerprintsList: !Array<string>,
- * isMaxed: boolean,
- * }}
- */
-settings.FingerprintInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class FingerprintBrowserProxy {
- /**
- * @return {!Promise<!settings.FingerprintInfo>}
- */
- getFingerprintsList() {}
-
- /**
- * @return {!Promise<number>}
- */
- getNumFingerprints() {}
-
- /**
- * @param {string} authToken
- */
- startEnroll(authToken) {}
-
- cancelCurrentEnroll() {}
-
- /**
- * @param {number} index
- * @return {!Promise<string>}
- */
- getEnrollmentLabel(index) {}
-
- /**
- * @param {number} index
- * @return {!Promise<boolean>}
- */
- removeEnrollment(index) {}
-
- /**
- * @param {number} index
- * @param {string} newLabel
- * @return {!Promise<boolean>}
- */
- changeEnrollmentLabel(index, newLabel) {}
-
- startAuthentication() {}
- endCurrentAuthentication() {}
-
- /**
- * TODO(sammiequon): Temporary function to let the handler know when a
- * completed scan has been sent via click on the setup fingerprint dialog.
- * Remove this when real scans are implemented.
- */
- fakeScanComplete() {}
- }
-
- /**
- * @implements {settings.FingerprintBrowserProxy}
- */
- class FingerprintBrowserProxyImpl {
- /** @override */
- getFingerprintsList() {
- return cr.sendWithPromise('getFingerprintsList');
- }
-
- /** @override */
- getNumFingerprints() {
- return cr.sendWithPromise('getNumFingerprints');
- }
-
- /** @override */
- startEnroll(authToken) {
- chrome.send('startEnroll', [authToken]);
- }
-
- /** @override */
- cancelCurrentEnroll() {
- chrome.send('cancelCurrentEnroll');
- }
-
- /** @override */
- getEnrollmentLabel(index) {
- return cr.sendWithPromise('getEnrollmentLabel');
- }
-
- /** @override */
- removeEnrollment(index) {
- return cr.sendWithPromise('removeEnrollment', index);
- }
-
- /** @override */
- changeEnrollmentLabel(index, newLabel) {
- return cr.sendWithPromise('changeEnrollmentLabel', index, newLabel);
- }
-
- /** @override */
- startAuthentication() {
- chrome.send('startAuthentication');
- }
-
- /** @override */
- endCurrentAuthentication() {
- chrome.send('endCurrentAuthentication');
- }
-
- /** @override */
- fakeScanComplete() {
- chrome.send('fakeScanComplete');
- }
- }
-
- cr.addSingletonGetter(FingerprintBrowserProxyImpl);
-
- return {
- FingerprintBrowserProxy: FingerprintBrowserProxy,
- FingerprintBrowserProxyImpl: FingerprintBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html b/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html
deleted file mode 100644
index 37db70a1b32..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-ripple/paper-ripple.html">
-<link rel="import" href="fingerprint_browser_proxy.html">
-<link rel="import" href="setup_fingerprint_dialog.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-fingerprint-list">
- <template>
- <style include="settings-shared">
- .add-link {
- background-color: transparent;
- color: var(--google-blue-500);
- }
-
- .add-link[disabled] {
- color: var(--google-grey-500);
- }
-
- .body {
- @apply --settings-list-frame-padding;
- }
-
- .list-item {
- background-color: white;
- }
-
- cr-input {
- --cr-input-error-display: none;
- }
-
- paper-ripple {
- color: var(--google-grey-refresh-700);
- }
- </style>
-
- <h2 class="settings-box">$i18n{lockScreenRegisteredFingerprints}</h2>
- <div class="body layout vertical">
- <iron-list id="fingerprintsList" items="[[fingerprints_]]">
- <template>
- <div class="list-item">
- <paper-ripple noink></paper-ripple>
- <cr-input value="{{item}}" on-change="onFingerprintLabelChanged_">
- </cr-input>
- <cr-icon-button class="icon-delete-gray"
- on-click="onFingerprintDeleteTapped_"
- aria-label$="[[getButtonAriaLabel_(item)]]"></cr-icon-button>
- </div>
- </template>
- </iron-list>
- <div class="continuation">
- <cr-button id="addFingerprint" class="add-link action-button"
- on-click="openAddFingerprintDialog_">
- $i18n{lockScreenAddFingerprint}
- </cr-button>
- </div>
- </div>
- <i class="settings-box continuation">$i18n{lockScreenFingerprintWarning}</i>
-
- <template is="dom-if" if="[[showSetupFingerprintDialog_]]" restamp>
- <settings-setup-fingerprint-dialog
- auth-token="[[authToken]]"
- on-add-fingerprint="updateFingerprintsList_"
- on-close="onSetupFingerprintDialogClose_"
- allow-add-another-finger="[[allowAddAnotherFinger_]]">
- </settings-setup-fingerprint-dialog>
- </template>
- </template>
- <script src="fingerprint_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.js b/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.js
deleted file mode 100644
index 657e219e3dd..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.js
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-/**
- * The duration in ms of a background flash when a user touches the fingerprint
- * sensor on this page.
- * @type {number}
- */
-const FLASH_DURATION_MS = 500;
-
-Polymer({
- is: 'settings-fingerprint-list',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /**
- * Authentication token provided by settings-people-page.
- */
- authToken: {
- type: String,
- value: '',
- },
-
- /**
- * The list of fingerprint objects.
- * @private {!Array<string>}
- */
- fingerprints_: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- /** @private */
- showSetupFingerprintDialog_: Boolean,
-
- /**
- * Whether add another finger is allowed.
- * @type {boolean}
- * @private
- */
- allowAddAnotherFinger_: {
- type: Boolean,
- value: true,
- },
- },
-
- /** @private {?settings.FingerprintBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'on-fingerprint-attempt-received', this.onAttemptReceived_.bind(this));
- this.addWebUIListener('on-screen-locked', this.onScreenLocked_.bind(this));
- this.browserProxy_ = settings.FingerprintBrowserProxyImpl.getInstance();
- this.browserProxy_.startAuthentication();
- this.updateFingerprintsList_();
- },
-
- /** @override */
- detached: function() {
- this.browserProxy_.endCurrentAuthentication();
- },
-
- /**
- * Overridden from settings.RouteObserverBehavior.
- * @param {!settings.Route} newRoute
- * @param {!settings.Route} oldRoute
- * @protected
- */
- currentRouteChanged: function(newRoute, oldRoute) {
- if (newRoute != settings.routes.FINGERPRINT) {
- if (this.browserProxy_) {
- this.browserProxy_.endCurrentAuthentication();
- }
- } else if (oldRoute == settings.routes.LOCK_SCREEN) {
- // Start fingerprint authentication when going from LOCK_SCREEN to
- // FINGERPRINT page.
- this.browserProxy_.startAuthentication();
- }
- },
-
- /**
- * Sends a ripple when the user taps the sensor with a registered fingerprint.
- * @param {!settings.FingerprintAttempt} fingerprintAttempt
- * @private
- */
- onAttemptReceived_: function(fingerprintAttempt) {
- /** @type {NodeList<!HTMLElement>} */ const listItems =
- this.$.fingerprintsList.querySelectorAll('.list-item');
- /** @type {Array<number>} */ const filteredIndexes =
- fingerprintAttempt.indexes.filter(function(index) {
- return index >= 0 && index < listItems.length;
- });
-
- // Flash the background and produce a ripple for each list item that
- // corresponds to the attempted finger.
- filteredIndexes.forEach(function(index) {
- const listItem = listItems[index];
- const ripple = listItem.querySelector('paper-ripple');
-
- // Activate the ripple.
- if (ripple) {
- ripple.simulatedRipple();
- }
-
- // Flash the background.
- listItem.animate(
- [
- {backgroundColor: ['var(--google-grey-300)']},
- {backgroundColor: ['white']}
- ],
- FLASH_DURATION_MS);
- });
- },
-
- /** @private */
- updateFingerprintsList_: function() {
- this.browserProxy_.getFingerprintsList().then(
- this.onFingerprintsChanged_.bind(this));
- },
-
- /**
- * @param {!settings.FingerprintInfo} fingerprintInfo
- * @private
- */
- onFingerprintsChanged_: function(fingerprintInfo) {
- // Update iron-list.
- this.fingerprints_ = fingerprintInfo.fingerprintsList.slice();
- this.$$('.action-button').disabled = fingerprintInfo.isMaxed;
- this.allowAddAnotherFinger_ = !fingerprintInfo.isMaxed;
- },
-
- /**
- * Deletes a fingerprint from |fingerprints_|.
- * @param {!{model: !{index: !number}}} e
- * @private
- */
- onFingerprintDeleteTapped_: function(e) {
- this.browserProxy_.removeEnrollment(e.model.index).then(success => {
- if (success) {
- this.updateFingerprintsList_();
- }
- });
- },
-
- /**
- * @param {!{model: !{index: !number, item: !string}}} e
- * @private
- */
- onFingerprintLabelChanged_: function(e) {
- this.browserProxy_.changeEnrollmentLabel(e.model.index, e.model.item)
- .then(success => {
- if (success) {
- this.updateFingerprintsList_();
- }
- });
- },
-
- /**
- * Opens the setup fingerprint dialog.
- * @private
- */
- openAddFingerprintDialog_: function() {
- this.showSetupFingerprintDialog_ = true;
- },
-
- /** @private */
- onSetupFingerprintDialogClose_: function() {
- this.showSetupFingerprintDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$$('#addFingerprint')));
- this.browserProxy_.startAuthentication();
- },
-
- /**
- * Close the setup fingerprint dialog when the screen is unlocked.
- * @param {boolean} screenIsLocked
- * @private
- */
- onScreenLocked_: function(screenIsLocked) {
- if (!screenIsLocked &&
- settings.getCurrentRoute() == settings.routes.FINGERPRINT) {
- this.onSetupFingerprintDialogClose_();
- }
- },
-
- /**
- * @param {string} item
- * @return {string}
- * @private
- */
- getButtonAriaLabel_: function(item) {
- return this.i18n('lockScreenDeleteFingerprintLabel', item);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.html
deleted file mode 100644
index d49dd93b022..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="import_data_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.js
deleted file mode 100644
index 47c80a28a9d..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/import_data_browser_proxy.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the the Import Data dialog to allow
- * users to import data (like bookmarks) from other web browsers.
- */
-cr.exportPath('settings');
-
-/**
- * An object describing a source browser profile that may be imported.
- * The structure of this data must be kept in sync with C++ ImportDataHandler.
- * @typedef {{
- * name: string,
- * index: number,
- * history: boolean,
- * favorites: boolean,
- * passwords: boolean,
- * search: boolean,
- * autofillFormData: boolean,
- * }}
- */
-settings.BrowserProfile;
-
-/**
- * @enum {string}
- * These string values must be kept in sync with the C++ ImportDataHandler.
- */
-settings.ImportDataStatus = {
- INITIAL: 'initial',
- IN_PROGRESS: 'inProgress',
- SUCCEEDED: 'succeeded',
- FAILED: 'failed',
-};
-
-cr.define('settings', function() {
- /** @interface */
- class ImportDataBrowserProxy {
- /**
- * Returns the source profiles available for importing from other browsers.
- * @return {!Promise<!Array<!settings.BrowserProfile>>}
- */
- initializeImportDialog() {}
-
- /**
- * Starts importing data for the specified source browser profile. The C++
- * responds with the 'import-data-status-changed' WebUIListener event.
- * @param {number} sourceBrowserProfileIndex
- */
- importData(sourceBrowserProfileIndex) {}
-
- /**
- * Prompts the user to choose a bookmarks file to import bookmarks from.
- */
- importFromBookmarksFile() {}
- }
-
- /**
- * @implements {settings.ImportDataBrowserProxy}
- */
- class ImportDataBrowserProxyImpl {
- /** @override */
- initializeImportDialog() {
- return cr.sendWithPromise('initializeImportDialog');
- }
-
- /** @override */
- importData(sourceBrowserProfileIndex) {
- chrome.send('importData', [sourceBrowserProfileIndex]);
- }
-
- /** @override */
- importFromBookmarksFile() {
- chrome.send('importFromBookmarksFile');
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(ImportDataBrowserProxyImpl);
-
- return {
- ImportDataBrowserProxy: ImportDataBrowserProxy,
- ImportDataBrowserProxyImpl: ImportDataBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.html b/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.html
deleted file mode 100644
index 9eb241a1aba..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.html
+++ /dev/null
@@ -1,130 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../controls/settings_checkbox.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="import_data_browser_proxy.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-import-data-dialog">
- <template>
- <style include="settings-shared md-select">
- .description {
- align-items: center;
- display: flex;
- margin-top: 16px;
- }
-
- paper-spinner-lite {
- margin: 0 8px;
- }
-
- #successIcon {
- fill: var(--cr-checked-color);
- height: 80px;
- margin: auto;
- width: 100%;
- }
-
- /* Prevent focus-outline to be chopped by the bottom of dialog body. */
- .md-select {
- margin-top: 2px;
- }
-
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}"
- ignore-popstate>
- <div slot="title">$i18n{importTitle}</div>
- <div slot="body">
- <div hidden$="[[!hasImportStatus_(
- importStatusEnum_.SUCCEEDED, importStatus_)]]">
- <iron-icon id="successIcon" icon="settings:check-circle">
- </iron-icon>
- <div hidden$="[[!prefs.import_dialog_bookmarks.value]]">
- <div class="description">$i18n{importSuccess}</div>
- <settings-toggle-button class="first"
- label="$i18n{showBookmarksBar}"
- pref="{{prefs.bookmark_bar.show_on_all_tabs}}">
- </settings-toggle-button>
- </div>
- </div>
-
- <div hidden$="[[hasImportStatus_(
- importStatusEnum_.SUCCEEDED, importStatus_)]]">
- <select id="browserSelect" class="md-select"
- aria-label="$i18n{importFromLabel}"
- on-change="onBrowserProfileSelectionChange_">
- <template is="dom-repeat" items="[[browserProfiles_]]">
- <option value="[[item.index]]">[[item.name]]</option>
- </template>
- </select>
- <div class="description">$i18n{importDescription}</div>
- <settings-checkbox
- hidden="[[!selected_.history]]"
- pref="{{prefs.import_dialog_history}}"
- label="$i18n{importHistory}">
- </settings-checkbox>
- <settings-checkbox
- hidden="[[!selected_.favorites]]"
- pref="{{prefs.import_dialog_bookmarks}}"
- label="$i18n{importFavorites}">
- </settings-checkbox>
- <settings-checkbox
- hidden="[[!selected_.passwords]]"
- pref="{{prefs.import_dialog_saved_passwords}}"
- label="$i18n{importPasswords}">
- </settings-checkbox>
- <settings-checkbox
- hidden="[[!selected_.search]]"
- pref="{{prefs.import_dialog_search_engine}}"
- label="$i18n{importSearch}">
- </settings-checkbox>
- <settings-checkbox
- hidden="[[!selected_.autofillFormData]]"
- pref="{{prefs.import_dialog_autofill_form_data}}"
- label="$i18n{importAutofillFormData}">
- </settings-checkbox>
- </div>
- </div>
- <div slot="button-container">
- <paper-spinner-lite
- active="[[hasImportStatus_(
- importStatusEnum_.IN_PROGRESS, importStatus_)]]"
- hidden="[[hasImportStatus_(
- importStatusEnum_.SUCCEEDED, importStatus_)]]">
- </paper-spinner-lite>
- <cr-button id="cancel" class="cancel-button"
- hidden="[[hasImportStatus_(
- importStatusEnum_.SUCCEEDED, importStatus_)]]"
- disabled="[[hasImportStatus_(
- importStatusEnum_.IN_PROGRESS, importStatus_)]]"
- on-click="closeDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button id="import" class="action-button"
- hidden="[[hasImportStatus_(
- importStatusEnum_.SUCCEEDED, importStatus_)]]"
- disabled="[[shouldDisableImport_(
- importStatus_, noImportDataTypeSelected_)]]"
- on-click="onActionButtonTap_">
- [[getActionButtonText_(selected_)]]
- </cr-button>
-
- <cr-button id="done" class="action-button"
- hidden$="[[!hasImportStatus_(
- importStatusEnum_.SUCCEEDED, importStatus_)]]"
- on-click="closeDialog_">$i18n{done}</cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="import_data_dialog.js"></script>
-:</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.js b/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.js
deleted file mode 100644
index 4192db5cdfb..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.js
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-import-data-dialog' is a component for importing
- * bookmarks and other data from other sources.
- */
-Polymer({
- is: 'settings-import-data-dialog',
-
- behaviors: [I18nBehavior, WebUIListenerBehavior, PrefsBehavior],
-
- properties: {
- /** @private {!Array<!settings.BrowserProfile>} */
- browserProfiles_: Array,
-
- /** @private {!settings.BrowserProfile} */
- selected_: Object,
-
- /**
- * Whether none of the import data categories is selected.
- * @private
- */
- noImportDataTypeSelected_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- importStatus_: {
- type: String,
- value: settings.ImportDataStatus.INITIAL,
- },
-
- /**
- * Mirroring the enum so that it can be used from HTML bindings.
- * @private
- */
- importStatusEnum_: {
- type: Object,
- value: settings.ImportDataStatus,
- },
- },
-
- observers: [
- 'prefsChanged_(selected_, prefs.*)',
- ],
-
- /** @private {?settings.ImportDataBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.browserProxy_ = settings.ImportDataBrowserProxyImpl.getInstance();
- this.browserProxy_.initializeImportDialog().then(data => {
- this.browserProfiles_ = data;
- this.selected_ = this.browserProfiles_[0];
-
- // Show the dialog only after the browser profiles data is populated
- // to avoid UI flicker.
- this.$.dialog.showModal();
- });
-
- this.addWebUIListener('import-data-status-changed', importStatus => {
- this.importStatus_ = importStatus;
- if (this.hasImportStatus_(settings.ImportDataStatus.FAILED)) {
- this.closeDialog_();
- }
- });
- },
-
- /** @private */
- prefsChanged_: function() {
- if (this.selected_ == undefined || this.prefs == undefined) {
- return;
- }
-
- this.noImportDataTypeSelected_ =
- !(this.getPref('import_dialog_history').value &&
- this.selected_.history) &&
- !(this.getPref('import_dialog_bookmarks').value &&
- this.selected_.favorites) &&
- !(this.getPref('import_dialog_saved_passwords').value &&
- this.selected_.passwords) &&
- !(this.getPref('import_dialog_search_engine').value &&
- this.selected_.search) &&
- !(this.getPref('import_dialog_autofill_form_data').value &&
- this.selected_.autofillFormData);
- },
-
- /**
- * @param {!settings.ImportDataStatus} status
- * @return {boolean} Whether |status| is the current status.
- * @private
- */
- hasImportStatus_: function(status) {
- return this.importStatus_ == status;
- },
-
- /** @private */
- isImportFromFileSelected_: function() {
- // The last entry in |browserProfiles_| always refers to dummy profile for
- // importing from a bookmarks file.
- return this.selected_.index == this.browserProfiles_.length - 1;
- },
-
- /**
- * @return {string}
- * @private
- */
- getActionButtonText_: function() {
- return this.i18n(
- this.isImportFromFileSelected_() ? 'importChooseFile' : 'importCommit');
- },
-
- /** @private */
- onBrowserProfileSelectionChange_: function() {
- this.selected_ = this.browserProfiles_[this.$.browserSelect.selectedIndex];
- },
-
- /** @private */
- onActionButtonTap_: function() {
- if (this.isImportFromFileSelected_()) {
- this.browserProxy_.importFromBookmarksFile();
- } else {
- this.browserProxy_.importData(this.$.browserSelect.selectedIndex);
- }
- },
-
- /** @private */
- closeDialog_: function() {
- this.$.dialog.close();
- },
-
- /**
- * @return {boolean} Whether the import button should be disabled.
- * @private
- */
- shouldDisableImport_: function() {
- return this.hasImportStatus_(settings.ImportDataStatus.IN_PROGRESS) ||
- this.noImportDataTypeSelected_;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.html b/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.html
deleted file mode 100644
index 66f41e87954..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.html
+++ /dev/null
@@ -1,184 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="kerberos_accounts_browser_proxy.html">
-<link rel="import" href="kerberos_add_account_dialog.html">
-
-<dom-module id="settings-kerberos-accounts">
- <template>
- <style include="settings-shared iron-flex iron-flex-alignment">
- .account-icon {
- background: center no-repeat;
- border-radius: 20px;
- flex-shrink: 0;
- height: 40px;
- width: 40px;
- }
-
- #outer {
- margin-inline-end: var(--cr-section-padding);
- margin-inline-start: 60px;
- }
-
- #account-list-header {
- padding-bottom: 1em;
- }
-
- .account-toolbar,
- #remove-account-policy-indicator {
- margin-inline-start: 1em;
- }
-
- #add-account-policy-indicator {
- margin-inline-end: 1em;
- }
-
- #add-account-icon {
- -webkit-mask-image: url(chrome://resources/images/add.svg);
- background-color: currentColor;
- height: 24px;
- margin-inline-end: 0.5em;
- width: 24px;
- }
-
- #remove-account-container {
- align-items: center;
- display: flex;
- }
-
- .error-badge {
- left: 60%;
- position: relative;
- top: 60%;
- }
-
- .warning {
- color: red;
- }
-
- :host-context([dir='rtl']) .error-badge {
- left: auto;
- right: 60%;
- }
- </style>
-
- <div class="settings-box first">
- <h2 class="first">
- <!-- Inner div needed to get spacing right in front of 'Learn more'. -->
- <div inner-h-t-m-l="[[i18nAdvanced('kerberosAccountsDescription')]]">
- </div>
- </h2>
- </div>
-
- <div class="settings-box first">
- <div id="account-list-header" class="flex">
- <h2>$i18n{kerberosAccountsListHeader}</h2>
- </div>
- <template is="dom-if" if="[[!addAccountsAllowed_]]">
- <cr-policy-indicator id="add-account-policy-indicator"
- indicator-type="userPolicy">
- </cr-policy-indicator>
- </template>
- <cr-button id="add-account-button" on-click="onAddAccountClick_"
- disabled="[[!addAccountsAllowed_]]">
- <div id="add-account-icon"></div>
- $i18n{kerberosAccountsAddAccountLabel}
- </cr-button>
- </div>
-
- <div id="outer" class="layout vertical nowrap">
- <template is="dom-repeat" id="account-list" items="[[accounts_]]">
- <div class="settings-box account-list-item">
-
- <div class="account-icon"
- style="background-image: [[getIconImageSet_(item.pic)]]">
- <img class="error-badge" hidden$="[[item.isSignedIn]]"
- src="chrome://resources/images/error_badge.svg">
- </div>
-
- <div class="middle two-line no-min-width">
- <div class="flex text-elide">
- <span>[[item.principalName]]</span>
-
- <div class="secondary signed-in" hidden$="[[!item.isSignedIn]]">
- [[i18n('kerberosAccountsSignedIn', item.validForDuration)]]
- </div>
- <div class="secondary warning signed-out"
- hidden$="[[item.isSignedIn]]">
- $i18n{kerberosAccountsSignedOut}
- </div>
- </div>
- </div>
-
- <div class="secondary account-toolbar active-indicator"
- hidden$="[[!item.isActive]]">
- $i18n{kerberosAccountsTicketActive}
- </div>
-
- <cr-button class="account-toolbar reauth-button"
- hidden$="[[item.isSignedIn]]"
- on-click="onReauthenticationClick_">
- $i18n{kerberosAccountsReauthenticationLabel}
- </cr-button>
-
- <template is="dom-if" if="[[item.isManaged]]">
- <cr-policy-indicator indicator-type="userPolicy"
- class="account-toolbar account-policy-indicator">
- </cr-policy-indicator>
- </template>
-
- <!-- Hamburger menu -->
- <cr-icon-button class="icon-more-vert more-actions"
- title="$i18n{moreActions}"
- on-click="onAccountActionsMenuButtonClick_">
- </cr-icon-button>
- </div>
- </template>
-
- <div class="clear settings-box"></div>
-
- <cr-action-menu>
- <button class="dropdown-item" on-click="onRefreshNowClick_">
- $i18n{kerberosAccountsRefreshNowLabel}
- </button>
- <button class="dropdown-item" on-click="onSetAsActiveAccountClick_">
- $i18n{kerberosAccountsSetAsActiveAccountLabel}
- </button>
- <button class="dropdown-item" on-click="onRemoveAccountClick_"
- disabled="[[selectedAccount_.isManaged]]">
- <div id="remove-account-container">
- $i18n{kerberosAccountsRemoveAccountLabel}
- <template is="dom-if" if="[[selectedAccount_.isManaged]]">
- <cr-policy-indicator id="remove-account-policy-indicator"
- indicator-type="userPolicy">
- </cr-policy-indicator>
- </template>
- </div>
- </button>
- </cr-action-menu>
- </div>
-
- <cr-toast id="account-toast" duration="3000">
- <!-- Gets displayed with black font without div and id :-/ -->
- <div id="account-toast-label">[[accountToastText_]]</div>
- </cr-toast>
-
- <template is="dom-if" if="[[showAddAccountDialog_]]" restamp>
- <kerberos-add-account-dialog preset-account="[[selectedAccount_]]"
- on-close="onAddAccountDialogClosed_">
- </kerberos-add-account-dialog>
- </template>
- </template>
- <script src="kerberos_accounts.js"></script>
-</dom-module> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.js b/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.js
deleted file mode 100644
index 4f45f060ed1..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts.js
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-kerberos-accounts' is the settings subpage containing controls to
- * list, add and delete Kerberos Accounts.
- */
-
-'use strict';
-
-Polymer({
- is: 'settings-kerberos-accounts',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * List of Accounts.
- * @private {!Array<!settings.KerberosAccount>}
- */
- accounts_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The targeted account for menu and other operations.
- * @private {?settings.KerberosAccount}
- */
- selectedAccount_: Object,
-
- /** @private */
- showAddAccountDialog_: Boolean,
-
- /** @private */
- addAccountsAllowed_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('kerberosAddAccountsAllowed');
- },
- },
-
- /** @private */
- accountToastText_: {
- type: String,
- value: '',
- },
- },
-
- /** @private {?settings.KerberosAccountsBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'kerberos-accounts-changed', this.refreshAccounts_.bind(this));
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_ =
- settings.KerberosAccountsBrowserProxyImpl.getInstance();
-
- // Grab account list and - when done - pop up the reauthentication dialog if
- // there is a kerberos_reauth param.
- this.refreshAccounts_().then(() => {
- const queryParams = settings.getQueryParameters();
- const reauthPrincipal = queryParams.get('kerberos_reauth');
- const reauthAccount = this.accounts_.find(account => {
- return account.principalName == reauthPrincipal;
- });
- if (reauthAccount) {
- this.selectedAccount_ = reauthAccount;
- this.showAddAccountDialog_ = true;
- }
- });
- },
-
- /**
- * @param {string} iconUrl
- * @return {string} A CSS image-set for multiple scale factors.
- * @private
- */
- getIconImageSet_: function(iconUrl) {
- return cr.icon.getImage(iconUrl);
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onAddAccountClick_: function(event) {
- this.selectedAccount_ = null;
- this.showAddAccountDialog_ = true;
- },
-
- /**
- * @param {!CustomEvent<!{model: !{item: !settings.Account}}>} event
- * @private
- */
- onReauthenticationClick_: function(event) {
- this.selectedAccount_ = event.model.item;
- this.showAddAccountDialog_ = true;
- },
-
- /** @private */
- onAddAccountDialogClosed_: function() {
- if (this.$$('kerberos-add-account-dialog').accountWasRefreshed) {
- this.showToast_('kerberosAccountsAccountRefreshedTip');
- }
-
- this.showAddAccountDialog_ = false;
-
- // In case it was opened by the 'Refresh now' action menu.
- this.closeActionMenu_();
- },
-
- /**
- * @return {!Promise}
- * @private
- */
- refreshAccounts_: function() {
- return this.browserProxy_.getAccounts().then(accounts => {
- this.accounts_ = accounts;
- });
- },
-
- /**
- * Opens the Account actions menu.
- * @param {!{model: !{item: !settings.KerberosAccount}, target: !Element}}
- * event
- * @private
- */
- onAccountActionsMenuButtonClick_: function(event) {
- this.selectedAccount_ = event.model.item;
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
- .showAt(event.target);
- },
-
- /**
- * Closes action menu and resets action menu model.
- * @private
- */
- closeActionMenu_: function() {
- this.$$('cr-action-menu').close();
- this.selectedAccount_ = null;
- },
-
- /**
- * Removes |this.selectedAccount_|.
- * @private
- */
- onRemoveAccountClick_: function() {
- this.browserProxy_
- .removeAccount(
- /** @type {!settings.KerberosAccount} */ (this.selectedAccount_))
- .then(error => {
- if (error == settings.KerberosErrorType.kNone) {
- this.showToast_('kerberosAccountsAccountRemovedTip');
- } else {
- console.error('Unexpected error removing account: ' + error);
- }
- });
- this.closeActionMenu_();
- },
-
- /**
- * Sets |this.selectedAccount_| as active Kerberos account.
- * @private
- */
- onSetAsActiveAccountClick_: function() {
- this.browserProxy_.setAsActiveAccount(
- /** @type {!settings.KerberosAccount} */ (this.selectedAccount_));
- this.closeActionMenu_();
- },
-
- /**
- * Opens the reauth dialog for |this.selectedAccount_|.
- * @private
- */
- onRefreshNowClick_: function() {
- this.showAddAccountDialog_ = true;
- },
-
- /**
- * Pops up a toast with localized text |label|.
- * @param {string} label Name of the localized label string.
- * @private
- */
- showToast_: function(label) {
- this.accountToastText_ = this.i18n(label);
- this.$$('#account-toast').show();
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.html
deleted file mode 100644
index c74b570b089..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="kerberos_accounts_browser_proxy.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js
deleted file mode 100644
index 89d27cd38aa..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Kerberos Accounts" subsection of
- * the "People" section of Settings, to interact with the browser. Chrome OS
- * only.
- */
-cr.exportPath('settings');
-
-/**
- * Information for a Chrome OS Kerberos account.
- * @typedef {{
- * principalName: string,
- * config: string,
- * isSignedIn: boolean,
- * isActive: boolean,
- * isManaged: boolean,
- * passwordWasRemembered: boolean,
- * pic: string,
- * validForDuration: string
- * }}
- */
-settings.KerberosAccount;
-
-/**
- * @typedef {{
- * error: !settings.KerberosErrorType,
- * errorInfo: !{
- * code: !settings.KerberosConfigErrorCode,
- * lineIndex: (number|undefined)
- * }
- * }}
- */
-settings.ValidateKerberosConfigResult;
-
-cr.define('settings', function() {
- /**
- * @enum {number}
- * These values must be kept in sync with the ErrorType enum in
- * third_party/cros_system_api/dbus/kerberos/kerberos_service.proto.
- */
- const KerberosErrorType = {
- kNone: 0,
- kUnknown: 1,
- kDBusFailure: 2,
- kNetworkProblem: 3,
- kUnknownKrb5Error: 4,
- kBadPrincipal: 5,
- kBadPassword: 6,
- kPasswordExpired: 7,
- kPasswordRejected: 8,
- kNoCredentialsCacheFound: 9,
- kKerberosTicketExpired: 10,
- kKdcDoesNotSupportEncryptionType: 11,
- kContactingKdcFailed: 12,
- kParseRequestFailed: 13,
- kLocalIo: 14,
- kUnknownPrincipalName: 15,
- kDuplicatePrincipalName: 16,
- kInProgress: 17,
- kParsePrincipalFailed: 18,
- kBadConfig: 19,
- kJailFailure: 20,
- };
-
- /**
- * @enum {number}
- * Error codes for config validation.
- * These values must be kept in sync with the KerberosConfigErrorCode enum in
- * third_party/cros_system_api/dbus/kerberos/kerberos_service.proto.
- */
- const KerberosConfigErrorCode = {
- kNone: 0,
- kSectionNestedInGroup: 1,
- kSectionSyntax: 2,
- kExpectedOpeningCurlyBrace: 3,
- kExtraCurlyBrace: 4,
- kRelationSyntax: 5,
- kKeyNotSupported: 6,
- kSectionNotSupported: 7,
- kKrb5FailedToParse: 8,
- };
-
- /** @interface */
- class KerberosAccountsBrowserProxy {
- /**
- * Returns a Promise for the list of Kerberos accounts held in the kerberosd
- * system daemon.
- * @return {!Promise<!Array<!settings.KerberosAccount>>}
- */
- getAccounts() {}
-
- /**
- * Attempts to add a new (or update an existing) Kerberos account.
- * @param {string} principalName Kerberos principal (user@realm.com).
- * @param {string} password Account password.
- * @param {boolean} rememberPassword Whether to store the password.
- * @param {string} config Kerberos configuration.
- * @param {boolean} allowExisting Whether existing accounts may be updated.
- * @return {!Promise<!settings.KerberosErrorType>}
- */
- addAccount(
- principalName, password, rememberPassword, config, allowExisting) {}
-
- /**
- * Removes |account| from the set of Kerberos accounts.
- * @param {!settings.KerberosAccount} account
- * @return {!Promise<!settings.KerberosErrorType>}
- */
- removeAccount(account) {}
-
- /**
- * Validates |krb5conf| by making sure that it does not contain syntax
- * errors or disallowed configuration options.
- * @param {string} krb5Conf Kerberos configuration data (krb5.conf)
- * @return {!Promise<!settings.ValidateKerberosConfigResult>}
- */
- validateConfig(krb5Conf) {}
-
- /**
- * Sets |account| as currently active account. Kerberos credentials are
- * consumed from this account.
- * @param {!settings.KerberosAccount} account
- */
- setAsActiveAccount(account) {}
- }
-
- /**
- * @implements {settings.KerberosAccountsBrowserProxy}
- */
- class KerberosAccountsBrowserProxyImpl {
- /** @override */
- getAccounts() {
- return cr.sendWithPromise('getKerberosAccounts');
- }
-
- /** @override */
- addAccount(
- principalName, password, rememberPassword, config, allowExisting) {
- return cr.sendWithPromise(
- 'addKerberosAccount', principalName, password, rememberPassword,
- config, allowExisting);
- }
-
- /** @override */
- removeAccount(account) {
- return cr.sendWithPromise('removeKerberosAccount', account.principalName);
- }
-
- /** @override */
- validateConfig(krb5conf) {
- return cr.sendWithPromise('validateKerberosConfig', krb5conf);
- }
-
- /** @override */
- setAsActiveAccount(account) {
- chrome.send('setAsActiveKerberosAccount', [account.principalName]);
- }
- }
-
- cr.addSingletonGetter(KerberosAccountsBrowserProxyImpl);
-
- return {
- KerberosErrorType: KerberosErrorType,
- KerberosConfigErrorCode: KerberosConfigErrorCode,
- KerberosAccountsBrowserProxy: KerberosAccountsBrowserProxy,
- KerberosAccountsBrowserProxyImpl: KerberosAccountsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html b/chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html
deleted file mode 100644
index 45f8f74a85a..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html
+++ /dev/null
@@ -1,168 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../controls/settings_textarea.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="kerberos_accounts_browser_proxy.html">
-
-<dom-module id="kerberos-add-account-dialog">
- <template>
- <style include="settings-shared action-link">
- #advancedConfigDesc {
- align-items: center;
- display: flex;
- }
-
- #advancedConfigPolicyIndicator {
- margin-inline-start: 1em;
- }
-
- #credentials {
- margin-top: 16px;
- }
-
- #credentials > *:not(:last-child) {
- margin-bottom: var(--cr-form-field-bottom-spacing);
- }
-
- #general-error-container {
- display: flex;
- height: 56px;
- }
-
- #config-error-container {
- display: flex;
- height: 40px;
- margin-top: 16px;
- }
-
- #general-error-message,
- #config-error-message {
- color: var(--settings-error-color);
- }
-
- iron-icon[icon='cr:error'] {
- fill: var(--settings-error-color);
- margin-inline-end: 8px;
- }
-
- #rememberPasswordContainer {
- align-items: center;
- display: flex;
- margin-bottom: 32px;
- }
-
- #rememberPassword {
- margin-inline-end: 1em;
- }
- </style>
-
- <cr-dialog id="addDialog" hidden="[[showAdvancedConfig_]]">
- <div slot="title">[[title_]]</div>
-
- <div slot="body" spellcheck="false">
- <h2 class="start first">
- $i18n{addKerberosAccountDescription}
- </h2>
-
- <div id="general-error-container"
- hidden="[[!showError_(generalErrorText_)]]">
- <iron-icon id="error-icon" icon="cr:error"></iron-icon>
- <div id="general-error-message">[[generalErrorText_]]</div>
- </div>
-
- <div id="credentials">
- <cr-input id="username" label="$i18n{kerberosUsername}"
- value="{{username_}}" invalid="[[showError_(usernameErrorText_)]]"
- placeholder="user@example.com"
- error-message="[[usernameErrorText_]]">
- </cr-input>
-
- <cr-input id="password" type="password"
- label="$i18n{kerberosPassword}" value="{{password_}}"
- invalid="[[showError_(passwordErrorText_)]]"
- error-message="[[passwordErrorText_]]"
- on-input="onPasswordInput_">
- </cr-input>
-
- <div id="rememberPasswordContainer">
- <cr-checkbox id="rememberPassword" checked="{{rememberPassword_}}"
- disabled="[[!rememberPasswordEnabled_]]">
- $i18n{addKerberosAccountRememberPassword}
- </cr-checkbox>
- <template is="dom-if" if="[[!rememberPasswordEnabled_]]">
- <cr-policy-indicator id="rememberPasswordPolicyIndicator"
- indicator-type="userPolicy">
- </cr-policy-indicator>
- </template>
- </div>
-
- <a is="action-link" id="advancedConfigButton"
- on-click="onAdvancedConfigClick_">
- $i18n{kerberosAccountsAdvancedConfigLabel}
- </a>
- </div>
- </div>
-
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancel_" id="cancel">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onAdd_"
- disabled="[[inProgress_]]">
- [[actionButtonLabel_]]
- </cr-button>
- </div>
- </cr-dialog>
-
- <template is="dom-if" if="[[showAdvancedConfig_]]" restamp>
- <cr-dialog id="advancedConfigDialog" on-close="onAdvancedConfigClose_">
- <div slot="title">$i18n{kerberosAdvancedConfigTitle}</div>
-
- <div slot="body">
- <h2 class="first" id="advancedConfigDesc">
- $i18n{kerberosAdvancedConfigDesc}
- <template is="dom-if" if="[[isManaged_]]">
- <cr-policy-indicator id="advancedConfigPolicyIndicator"
- indicator-type="userPolicy">
- </cr-policy-indicator>
- </template>
- </h2>
-
- <settings-textarea id="config" value="{{editableConfig_}}" rows=12
- spellcheck="false" disabled="[[isManaged_]]">
- </settings-textarea>
-
- <div id="config-error-container"
- hidden="[[!showError_(configErrorText_)]]">
- <iron-icon id="error-icon" icon="cr:error"></iron-icon>
- <div id="config-error-message">[[configErrorText_]]</div>
- </div>
- </div>
-
- <div slot="button-container">
- <cr-button class="cancel-button"
- on-click="onAdvancedConfigCancel_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onAdvancedConfigSave_"
- disabled="[[inProgress_]]">
- $i18n{save}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- </template>
- <script src="kerberos_add_account_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js b/chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js
deleted file mode 100644
index 5bb16f8daf9..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'kerberos-add-account-dialog' is an element to add Kerberos accounts.
- */
-
-Polymer({
- is: 'kerberos-add-account-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * If set, some fields are preset from this account (like username or
- * whether to remember the password).
- * @type {?settings.KerberosAccount}
- */
- presetAccount: Object,
-
- /**
- * Whether an existing |presetAccount| was successfully authenticated.
- * Always false if |presetAccount| is null (new accounts).
- */
- accountWasRefreshed: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- username_: {
- type: String,
- value: '',
- },
-
- /** @private */
- password_: {
- type: String,
- value: '',
- },
-
- /**
- * Current configuration in the Advanced Config dialog. Propagates to
- * |config| only if 'Save' button is pressed.
- * @private {string}
- */
- editableConfig_: {
- type: String,
- value: '',
- },
-
- /** @private */
- rememberPassword_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- generalErrorText_: {
- type: String,
- value: '',
- },
-
- /** @private */
- usernameErrorText_: {
- type: String,
- value: '',
- },
-
- /** @private */
- passwordErrorText_: {
- type: String,
- value: '',
- },
-
- /** @private */
- configErrorText_: {
- type: String,
- value: '',
- },
-
- /** @private */
- inProgress_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isManaged_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showAdvancedConfig_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- rememberPasswordEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('kerberosRememberPasswordEnabled');
- },
- },
- },
-
- /** @private {boolean} */
- useRememberedPassword_: false,
-
- /** @private {string} */
- config_: '',
-
- /** @private {string} */
- title_: '',
-
- /** @private {string} */
- actionButtonLabel_: '',
-
- /** @private {?settings.KerberosAccountsBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ =
- settings.KerberosAccountsBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.$.addDialog.showModal();
-
- if (this.presetAccount) {
- // Refresh an existing account.
- this.title_ = this.i18n('refreshKerberosAccount');
- this.actionButtonLabel_ =
- this.i18n('addKerberosAccountRefreshButtonLabel');
-
- // Preset username and make UI read-only.
- // Note: At least the focus() part needs to be after showModal.
- this.username_ = this.presetAccount.principalName;
- this.isManaged_ = this.presetAccount.isManaged;
- this.$.username.readonly = true;
- this.$.password.focus();
-
- if (this.presetAccount.passwordWasRemembered &&
- this.rememberPasswordEnabled_) {
- // The daemon knows the user's password, so prefill the password field
- // with some string (Chrome does not know the actual password for
- // security reasons). If the user does not change it, an empty password
- // is sent to the daemon, which is interpreted as "use remembered
- // password". Also, keep remembering the password by default.
- const FAKE_PASSWORD = 'xxxxxxxx';
- this.password_ = FAKE_PASSWORD;
- this.rememberPassword_ = true;
- this.useRememberedPassword_ = true;
- }
-
- this.config_ = this.presetAccount.config;
- } else {
- // Add a new Kerberos account.
- this.title_ = this.i18n('addKerberosAccount');
- this.actionButtonLabel_ = this.i18n('add');
-
- // Set a default configuration.
- this.config_ = loadTimeData.getString('defaultKerberosConfig');
- }
- },
-
- /** @private */
- onCancel_: function() {
- this.$.addDialog.cancel();
- },
-
- /** @private */
- onAdd_: function() {
- assert(!this.inProgress_);
- this.inProgress_ = true;
-
- // Keep the general error, wiping it might cause the error to disappear and
- // immediately reappear, causing 2 resizings of the dialog.
- this.usernameErrorText_ = '';
- this.passwordErrorText_ = '';
-
- // An empty password triggers the Kerberos daemon to use the remembered one.
- const passwordToSubmit = this.useRememberedPassword_ ? '' : this.password_;
-
- // For new accounts (no preset), bail if the account already exists.
- const allowExisting = !!this.presetAccount;
-
- this.browserProxy_
- .addAccount(
- this.username_, passwordToSubmit, this.rememberPassword_,
- this.config_, allowExisting)
- .then(error => {
- this.inProgress_ = false;
-
- // Success case. Close dialog.
- if (error == settings.KerberosErrorType.kNone) {
- this.accountWasRefreshed = this.presetAccount != null;
- this.$.addDialog.close();
- return;
- }
-
- // Triggers the UI to update error messages.
- this.updateErrorMessages_(error);
- });
- },
-
- /** @private */
- onPasswordInput_: function() {
- // On first input, don't reuse the remembered password, but submit the
- // changed one.
- this.useRememberedPassword_ = false;
- },
-
- /** @private */
- onAdvancedConfigClick_: function() {
- // Keep a copy of the config in case the user cancels.
- this.editableConfig_ = this.config_;
- this.showAdvancedConfig_ = true;
- Polymer.dom.flush();
- this.$$('#advancedConfigDialog').showModal();
- },
-
- /** @private */
- onAdvancedConfigCancel_: function() {
- this.configErrorText_ = '';
- this.showAdvancedConfig_ = false;
- this.$$('#advancedConfigDialog').cancel();
- },
-
- /** @private */
- onAdvancedConfigSave_: function() {
- assert(!this.inProgress_);
- this.inProgress_ = true;
-
- this.browserProxy_.validateConfig(this.editableConfig_).then(result => {
- this.inProgress_ = false;
-
- // Success case. Close dialog.
- if (result.error == settings.KerberosErrorType.kNone) {
- this.showAdvancedConfig_ = false;
- this.config_ = this.editableConfig_;
- this.configErrorText_ = '';
- this.$$('#advancedConfigDialog').close();
- return;
- }
-
- // Triggers the UI to update error messages.
- this.updateConfigErrorMessage_(result);
- });
- },
-
- onAdvancedConfigClose_: function(event) {
- // Note: 'Esc' doesn't trigger onAdvancedConfigCancel_() and some tests
- // that trigger onAdvancedConfigCancel_() don't trigger this for some
- // reason, hence this is needed here and above.
- this.showAdvancedConfig_ = false;
-
- // Since this is a sub-dialog, prevent event from bubbling up. Otherwise,
- // it might cause the add-dialog to be closed.
- event.stopPropagation();
- },
-
- /**
- * @param {!settings.KerberosErrorType} error Current error enum
- * @private
- */
- updateErrorMessages_: function(error) {
- this.generalErrorText_ = '';
- this.usernameErrorText_ = '';
- this.passwordErrorText_ = '';
-
- switch (error) {
- case settings.KerberosErrorType.kNone:
- break;
-
- case settings.KerberosErrorType.kNetworkProblem:
- this.generalErrorText_ = this.i18n('kerberosErrorNetworkProblem');
- break;
- case settings.KerberosErrorType.kParsePrincipalFailed:
- this.usernameErrorText_ = this.i18n('kerberosErrorUsernameInvalid');
- break;
- case settings.KerberosErrorType.kBadPrincipal:
- this.usernameErrorText_ = this.i18n('kerberosErrorUsernameUnknown');
- break;
- case settings.KerberosErrorType.kDuplicatePrincipalName:
- this.usernameErrorText_ =
- this.i18n('kerberosErrorDuplicatePrincipalName');
- break;
- case settings.KerberosErrorType.kContactingKdcFailed:
- this.usernameErrorText_ = this.i18n('kerberosErrorContactingServer');
- break;
-
- case settings.KerberosErrorType.kBadPassword:
- this.passwordErrorText_ = this.i18n('kerberosErrorPasswordInvalid');
- break;
- case settings.KerberosErrorType.kPasswordExpired:
- this.passwordErrorText_ = this.i18n('kerberosErrorPasswordExpired');
- break;
-
- case settings.KerberosErrorType.kKdcDoesNotSupportEncryptionType:
- this.generalErrorText_ = this.i18n('kerberosErrorKdcEncType');
- break;
- default:
- this.generalErrorText_ =
- this.i18n('kerberosErrorGeneral', error.toString());
- }
- },
-
- /**
- * @param {!settings.ValidateKerberosConfigResult} result Result from a
- * validateKerberosConfig() call.
- * @private
- */
- updateConfigErrorMessage_: function(result) {
- // There should be an error at this point.
- assert(result.error != settings.KerberosErrorType.kNone);
-
- // Only handle kBadConfig here. Display generic error otherwise. Should only
- // occur if something is wrong with D-Bus, but nothing user-induced.
- if (result.error != settings.KerberosErrorType.kBadConfig) {
- this.configErrorText_ =
- this.i18n('kerberosErrorGeneral', result.error.toString());
- return;
- }
-
- let errorLine = '';
-
- // Don't fall for the classical blunder 0 == false.
- if (result.errorInfo.lineIndex != undefined) {
- const textArea = this.$$('#config').shadowRoot.querySelector('#input');
- errorLine = this.selectAndScrollTo_(textArea, result.errorInfo.lineIndex);
- }
-
- // If kBadConfig, the error code should be set.
- assert(result.errorInfo.code != settings.KerberosConfigErrorCode.kNone);
- this.configErrorText_ =
- this.getConfigErrorString_(result.errorInfo.code, errorLine);
- },
-
- /**
- * @param {!settings.KerberosConfigErrorCode} code Error code
- * @param {string} errorLine Line where the error occurred
- * @return {string} Localized error string that corresponds to code
- * @private
- */
- getConfigErrorString_: function(code, errorLine) {
- switch (code) {
- case settings.KerberosConfigErrorCode.kSectionNestedInGroup:
- return this.i18n('kerberosConfigErrorSectionNestedInGroup', errorLine);
- case settings.KerberosConfigErrorCode.kSectionSyntax:
- return this.i18n('kerberosConfigErrorSectionSyntax', errorLine);
- case settings.KerberosConfigErrorCode.kExpectedOpeningCurlyBrace:
- return this.i18n(
- 'kerberosConfigErrorExpectedOpeningCurlyBrace', errorLine);
- case settings.KerberosConfigErrorCode.kExtraCurlyBrace:
- return this.i18n('kerberosConfigErrorExtraCurlyBrace', errorLine);
- case settings.KerberosConfigErrorCode.kRelationSyntax:
- return this.i18n('kerberosConfigErrorRelationSyntax', errorLine);
- case settings.KerberosConfigErrorCode.kKeyNotSupported:
- return this.i18n('kerberosConfigErrorKeyNotSupported', errorLine);
- case settings.KerberosConfigErrorCode.kSectionNotSupported:
- return this.i18n('kerberosConfigErrorSectionNotSupported', errorLine);
- case settings.KerberosConfigErrorCode.kKrb5FailedToParse:
- // Note: This error doesn't have an error line.
- return this.i18n('kerberosConfigErrorKrb5FailedToParse');
- default:
- assertNotReached();
- }
- },
-
- /**
- * Selects a line in a text area and scrolls to it.
- * @param {!Element} textArea A textarea element
- * @param {number} lineIndex 0-based index of the line to select
- * @return {string} The line at lineIndex.
- * @private
- */
- selectAndScrollTo_: function(textArea, lineIndex) {
- const lines = textArea.value.split('\n');
- assert(lineIndex >= 0 && lineIndex < lines.length);
-
- // Compute selection position in characters.
- let startPos = 0;
- for (let i = 0; i < lineIndex; i++) {
- startPos += lines[i].length + 1;
- }
-
- // Ignore starting and trailing whitespace for the selection.
- const trimmedLine = lines[lineIndex].trim();
- startPos += lines[lineIndex].indexOf(trimmedLine);
- const endPos = startPos + trimmedLine.length;
-
- // Set selection.
- textArea.focus();
- textArea.setSelectionRange(startPos, endPos);
-
- // Scroll to center the selected line.
- const lineHeight = textArea.clientHeight / textArea.rows;
- const firstLine = Math.max(0, lineIndex - textArea.rows / 2);
- textArea.scrollTop = lineHeight * firstLine;
-
- return lines[lineIndex];
- },
-
- /**
- * Whether an error element should be shown.
- * Note that !! is not supported in Polymer bindings.
- * @param {?string} errorText Error text to be displayed. Empty if no error.
- * @return {boolean} True iff errorText is not empty.
- * @private
- */
- showError_: function(errorText) {
- return !!errorText;
- }
-}); \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_screen.html b/chromium/chrome/browser/resources/settings/people_page/lock_screen.html
deleted file mode 100644
index 56fa177dc1d..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/lock_screen.html
+++ /dev/null
@@ -1,175 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="fingerprint_browser_proxy.html">
-<link rel="import" href="lock_state_behavior.html">
-<link rel="import" href="lock_screen_password_prompt_dialog.html">
-<link rel="import" href="setup_pin_dialog.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-lock-screen">
- <template>
- <style include="settings-shared">
- cr-policy-indicator {
- margin-inline-start: auto;
- /* Align the indicator with the h2 that it is associated with. */
- padding-bottom: 12px;
- padding-top: 24px;
- }
-
- #lockOptionsDiv {
- display: block;
- }
-
- #pinPasswordDiv {
- display: flex;
- }
-
- #pinPasswordLabel {
- flex: 1;
- }
-
- #pinPasswordLabel,
- #pinPasswordSecondaryActionDiv {
- margin: auto;
- }
-
- .underbar {
- border-bottom: var(--cr-separator-line);
- }
-
- #unlockType[disabled] {
- opacity: var(--settings-disabled-opacity);
- pointer-events: none;
- }
- </style>
-
- <div>
- <settings-toggle-button id="enableLockScreen" class="first"
- pref="{{prefs.settings.enable_screen_lock}}"
- on-settings-boolean-control-change="onScreenLockChange_" no-set-pref
- label="$i18n{enableScreenlock}">
- </settings-toggle-button>
-
- <template is="dom-if" if="[[quickUnlockEnabled_]]">
- <div id="lockOptionsDiv">
- <div class="settings-box">
- <h2>[[selectLockScreenOptionsString(hasPinLogin)]]</h2>
- <template is="dom-if" if="[[quickUnlockDisabledByPolicy_]]">
- <cr-policy-indicator indicator-type="userPolicy">
- </cr-policy-indicator>
- </template>
- </div>
- <div class="list-frame" >
- <cr-radio-group id="unlockType"
- disabled$="[[quickUnlockDisabledByPolicy_]]"
- selected="{{selectedUnlockType}}">
- <cr-radio-button name="password" class="list-item underbar">
- <div class="start">
- $i18n{lockScreenPasswordOnly}
- </div>
- </cr-radio-button>
- <cr-radio-button name="pin+password" class="list-item">
- <div id="pinPasswordDiv">
- <div id="pinPasswordLabel">
- $i18n{lockScreenPinOrPassword}
- </div>
- <template is="dom-if"
- if="[[showConfigurePinButton_(selectedUnlockType)]]">
- <div class="separator"></div>
- <div id="pinPasswordSecondaryActionDiv"
- class="secondary-action">
- <!-- Use stop-keyboard-event-propagation to prevent
- triggering this when focused after closing the
- dialog. -->
- <cr-button id="setupPinButton" on-click="onConfigurePin_"
- stop-keyboard-event-propagation>
- [[getSetupPinText_(hasPin)]]
- </cr-button>
- </div>
- </template>
- </div>
- </cr-radio-button>
- </cr-radio-group>
- </div>
- </div>
- </template>
-
- <template is="dom-if" if="[[lockScreenNotificationsEnabled_]]">
- <h2 class="settings-box">
- $i18n{lockScreenNotificationTitle}
- </h2>
- <div class="list-frame">
- <settings-radio-group
- pref="{{prefs.ash.message_center.lock_screen_mode}}">
- <template is="dom-if"
- if="[[lockScreenHideSensitiveNotificationSupported_]]">
- <cr-radio-button name="hideSensitive" class="list-item underbar"
- pref="[[prefs.ash.message_center.lock_screen_mode]]"
- label="$i18n{lockScreenNotificationHideSensitive}">
- </cr-radio-button>
- </template>
- <cr-radio-button name="show" class="list-item underbar"
- pref="[[prefs.ash.message_center.lock_screen_mode]]"
- label="$i18n{lockScreenNotificationShow}">
- </cr-radio-button>
- <cr-radio-button name="hide" class="list-item"
- pref="[[prefs.ash.message_center.lock_screen_mode]]"
- label="$i18n{lockScreenNotificationHide}">
- </cr-radio-button>
- </settings-radio-group>
- </div>
- </template>
-
- <template is="dom-if" if="[[fingerprintUnlockEnabled_]]">
- <div id="fingerprintDiv" class="settings-box two-line">
- <div class="start">
- $i18n{lockScreenEditFingerprints}
- <div class="secondary" id="lockScreenEditFingerprintsSecondary">
- [[getDescriptionText_(numFingerprints_)]]
- </div>
- </div>
- <div class="separator"></div>
- <div class="secondary-action">
- <cr-button on-click="onEditFingerprints_"
- aria-label="$i18n{lockScreenEditFingerprints}"
- aria-descibedby="lockScreenEditFingerprintsSecondary">
- $i18n{lockScreenSetupFingerprintButton}
- </cr-button>
- </div>
- </div>
- </template>
-
- <template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
- <settings-lock-screen-password-prompt-dialog
- id="lockScreenPasswordPrompt"
- on-close="onPasswordPromptDialogClose_" set-modes="{{setModes_}}"
- auth-token="{{authToken}}">
- </settings-lock-screen-password-prompt-dialog>
- </template>
-
- <template is="dom-if" if="[[showSetupPinDialog_]]" restamp>
- <settings-setup-pin-dialog id="setupPin" set-modes="[[setModes_]]"
- on-close="onSetupPinDialogClose_">
- </settings-setup-pin-dialog>
- </template>
- </div>
- </template>
-
- <script src="lock_screen.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_screen.js b/chromium/chrome/browser/resources/settings/people_page/lock_screen.js
deleted file mode 100644
index f0584fee7e4..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/lock_screen.js
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-lock-screen' allows the user to change how they unlock their
- * device.
- *
- * Example:
- *
- * <settings-lock-screen
- * prefs="{{prefs}}">
- * </settings-lock-screen>
- */
-
-Polymer({
- is: 'settings-lock-screen',
-
- behaviors: [
- I18nBehavior,
- LockStateBehavior,
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /** Preferences state. */
- prefs: {type: Object},
-
- /**
- * setModes_ is a partially applied function that stores the current auth
- * token. It's defined only when the user has entered a valid password.
- * @type {Object|undefined}
- * @private
- */
- setModes_: {
- type: Object,
- observer: 'onSetModesChanged_',
- },
-
- /**
- * Authentication token provided by lock-screen-password-prompt-dialog.
- */
- authToken: {
- type: String,
- value: '',
- notify: true,
- },
-
- /**
- * writeUma_ is a function that handles writing uma stats. It may be
- * overridden for tests.
- *
- * @type {Function}
- * @private
- */
- writeUma_: {
- type: Object,
- value: function() {
- return settings.recordLockScreenProgress;
- },
- },
-
- /**
- * True if quick unlock settings should be displayed on this machine.
- * @private
- */
- quickUnlockEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('quickUnlockEnabled');
- },
- readOnly: true,
- },
-
- /**
- * True if quick unlock settings are disabled by policy.
- * @private
- */
- quickUnlockDisabledByPolicy_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('quickUnlockDisabledByPolicy');
- },
- readOnly: true,
- },
-
- /**
- * True if fingerprint unlock settings should be displayed on this machine.
- * @private
- */
- fingerprintUnlockEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('fingerprintUnlockEnabled');
- },
- readOnly: true,
- },
-
- /** @private */
- numFingerprints_: {
- type: Number,
- value: 0,
- },
-
- /**
- * Whether notifications on the lock screen are enable by the feature flag.
- * @private
- */
- lockScreenNotificationsEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('lockScreenNotificationsEnabled');
- },
- readOnly: true,
- },
-
- /**
- * Whether the "hide sensitive notification" option on the lock screen can
- * be enable by the feature flag.
- * @private
- */
- lockScreenHideSensitiveNotificationSupported_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean(
- 'lockScreenHideSensitiveNotificationsSupported');
- },
- readOnly: true,
- },
-
- /** @private */
- showPasswordPromptDialog_: Boolean,
-
- /** @private */
- showSetupPinDialog_: Boolean,
- },
-
- /** @private {?settings.FingerprintBrowserProxy} */
- fingerprintBrowserProxy_: null,
-
- /** selectedUnlockType is defined in LockStateBehavior. */
- observers: ['selectedUnlockTypeChanged_(selectedUnlockType)'],
-
- /** @override */
- attached: function() {
- if (this.shouldAskForPassword_(settings.getCurrentRoute())) {
- this.openPasswordPromptDialog_();
- }
-
- this.fingerprintBrowserProxy_ =
- settings.FingerprintBrowserProxyImpl.getInstance();
- this.updateNumFingerprints_();
- },
-
- /**
- * Overridden from settings.RouteObserverBehavior.
- * @param {!settings.Route} newRoute
- * @param {!settings.Route} oldRoute
- * @protected
- */
- currentRouteChanged: function(newRoute, oldRoute) {
- if (newRoute == settings.routes.LOCK_SCREEN) {
- this.updateUnlockType();
- this.updateNumFingerprints_();
- }
-
- if (this.shouldAskForPassword_(newRoute)) {
- this.openPasswordPromptDialog_();
- } else if (
- newRoute != settings.routes.FINGERPRINT &&
- oldRoute != settings.routes.FINGERPRINT) {
- // If the user navigated away from the lock screen settings page they will
- // have to re-enter their password. An exception is if they are navigating
- // to or from the fingerprint subpage.
- this.setModes_ = undefined;
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onScreenLockChange_: function(event) {
- const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
- if (!this.authToken) {
- console.error('Screen lock changed with expired token.');
- target.checked = !target.checked;
- return;
- }
- this.setLockScreenEnabled(this.authToken, target.checked);
- },
-
- /**
- * Called when the unlock type has changed.
- * @param {!string} selected The current unlock type.
- * @private
- */
- selectedUnlockTypeChanged_: function(selected) {
- if (selected == LockScreenUnlockType.VALUE_PENDING) {
- return;
- }
-
- if (selected != LockScreenUnlockType.PIN_PASSWORD && this.setModes_) {
- this.setModes_.call(null, [], [], function(result) {
- assert(result, 'Failed to clear quick unlock modes');
- if (!result) {
- console.error('Failed to clear quick unlock modes');
- }
- });
- }
- },
-
- /** @private */
- onSetModesChanged_: function() {
- if (this.shouldAskForPassword_(settings.getCurrentRoute())) {
- this.showSetupPinDialog_ = false;
- this.openPasswordPromptDialog_();
- }
- },
-
- /** @private */
- openPasswordPromptDialog_: function() {
- this.showPasswordPromptDialog_ = true;
- },
-
- /** @private */
- onPasswordPromptDialogClose_: function() {
- this.showPasswordPromptDialog_ = false;
- if (!this.setModes_) {
- settings.navigateToPreviousRoute();
- } else if (!this.$$('#unlockType').disabled) {
- cr.ui.focusWithoutInk(assert(this.$$('#unlockType')));
- } else {
- cr.ui.focusWithoutInk(assert(this.$$('#enableLockScreen')));
- }
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onConfigurePin_: function(e) {
- e.preventDefault();
- this.writeUma_(LockScreenProgress.CHOOSE_PIN_OR_PASSWORD);
- this.showSetupPinDialog_ = true;
- },
-
- /** @private */
- onSetupPinDialogClose_: function() {
- this.showSetupPinDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$$('#setupPinButton')));
- },
-
- /**
- * Returns true if the setup pin section should be shown.
- * @param {!string} selectedUnlockType The current unlock type. Used to let
- * Polymer know about the dependency.
- * @private
- */
- showConfigurePinButton_: function(selectedUnlockType) {
- return selectedUnlockType === LockScreenUnlockType.PIN_PASSWORD;
- },
-
- /**
- * @param {boolean} hasPin
- * @private
- */
- getSetupPinText_: function(hasPin) {
- if (hasPin) {
- return this.i18n('lockScreenChangePinButton');
- }
- return this.i18n('lockScreenSetupPinButton');
- },
-
- /** @private */
- getDescriptionText_: function() {
- if (this.numFingerprints_ > 0) {
- return this.i18n(
- 'lockScreenNumberFingerprints', this.numFingerprints_.toString());
- }
-
- return this.i18n('lockScreenEditFingerprintsDescription');
- },
-
- /** @private */
- onEditFingerprints_: function() {
- settings.navigateTo(settings.routes.FINGERPRINT);
- },
-
- /**
- * @param {!settings.Route} route
- * @return {boolean} Whether the password dialog should be shown.
- * @private
- */
- shouldAskForPassword_: function(route) {
- return route == settings.routes.LOCK_SCREEN && !this.setModes_;
- },
-
- /** @private */
- updateNumFingerprints_: function() {
- if (this.fingerprintUnlockEnabled_ && this.fingerprintBrowserProxy_) {
- this.fingerprintBrowserProxy_.getNumFingerprints().then(
- numFingerprints => {
- this.numFingerprints_ = numFingerprints;
- });
- }
- },
-
- /**
- * Looks up the translation id, which depends on PIN login support.
- * @param {boolean} hasPinLogin
- * @private
- */
- selectLockScreenOptionsString(hasPinLogin) {
- if (hasPinLogin) {
- return this.i18n('lockScreenOptionsLoginLock');
- }
- return this.i18n('lockScreenOptionsLock');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.html b/chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.html
deleted file mode 100644
index 086183af7b5..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html">
-<link rel="import" href="../controls/password_prompt_dialog.html">
-<link rel="import" href="lock_state_behavior.html">
-
-<dom-module id="settings-lock-screen-password-prompt-dialog">
- <template>
- <settings-password-prompt-dialog
- id="passwordPrompt"
- password-prompt-text="[[selectPasswordPromptEnterPasswordString_(hasPinLogin)]]"
- auth-token="{{authToken}}">
- </settings-password-prompt-dialog>
- </template>
- <script src="lock_screen_password_prompt_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.js b/chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.js
deleted file mode 100644
index 0da0789b4b4..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/lock_screen_password_prompt_dialog.js
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- *
- * 'settings-lock-screen-password-prompt-dialog' displays a password prompt to
- * the user. Clients can determine if the user has authenticated if either the
- * |authToken| string or |setModes| closure are present.
- *
- * The setModes binding is a wrapper around chrome.quickUnlockPrivate.setModes
- * which has a prebound account password. The account password by itself is not
- * available for other elements to access.
- *
- * Example:
- *
- * <settings-lock-screen-password-prompt-dialog
- * id="lockScreenPasswordPrompt"
- * set-modes="[[setModes]]">
- * </settings-lock-screen-password-prompt-dialog>
- */
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-lock-screen-password-prompt-dialog',
-
- behaviors: [
- LockStateBehavior,
- ],
-
- properties: {
- /**
- * Authentication token returned by quickUnlockPrivate.getAuthToken. Should
- * be passed to API calls which require authentication.
- * @type {string}
- */
- authToken: {
- type: String,
- notify: true,
- observer: 'authTokenChanged_',
- },
-
- /**
- * A wrapper around chrome.quickUnlockPrivate.setModes with the account
- * password already supplied. If this is null, the authentication screen
- * needs to be redisplayed. This property will be cleared after the timeout
- * returned by quickUnlockPrivate.getAuthToken.
- * @type {?Function}
- */
- setModes: {
- type: Object,
- notify: true,
- },
-
- /**
- * writeUma_ is a function that handles writing uma stats. It may be
- * overridden for tests.
- *
- * @type {Function}
- * @private
- */
- writeUma_: {
- type: Object,
- value: function() {
- return settings.recordLockScreenProgress;
- }
- },
- },
-
- /** @override */
- attached: function() {
- this.writeUma_(LockScreenProgress.START_SCREEN_LOCK);
- },
-
- /**
- * Called when the authToken changes. If the authToken is valid, that
- * indicates the user authenticated successfully.
- * @param {String} authToken
- * @private
- */
- authTokenChanged_: function(authToken) {
- if (!this.authToken) {
- this.setModes = null;
- return;
- }
-
- // The user successfully authenticated.
- this.writeUma_(LockScreenProgress.ENTER_PASSWORD_CORRECTLY);
-
- this.setModes = (modes, credentials, onComplete) => {
- this.quickUnlockPrivate.setModes(
- this.authToken, modes, credentials, () => {
- let result = true;
- if (chrome.runtime.lastError) {
- console.error(
- 'setModes failed: ' + chrome.runtime.lastError.message);
- result = false;
- }
- onComplete(result);
- });
- };
- },
-
- /**
- * Looks up the translation id, which depends on PIN login support.
- * @param {boolean} hasPinLogin
- * @return {string}
- * @private
- */
- selectPasswordPromptEnterPasswordString_: function(hasPinLogin) {
- if (hasPinLogin) {
- return this.i18n('passwordPromptEnterPasswordLoginLock');
- }
- return this.i18n('passwordPromptEnterPasswordLock');
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.html b/chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.html
deleted file mode 100644
index 52dd5119bd0..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-
-<script src="lock_state_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.js b/chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.js
deleted file mode 100644
index 8ef0b549f2f..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/lock_state_behavior.js
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Contains utilities that help identify the current way that the lock screen
- * will be displayed.
- */
-
-/** @enum {string} */
-const LockScreenUnlockType = {
- VALUE_PENDING: 'value_pending',
- PASSWORD: 'password',
- PIN_PASSWORD: 'pin+password'
-};
-
-/**
- * Determining if the device supports PIN sign-in takes time, as it may require
- * a cryptohome call. This means incorrect strings may be shown for a brief
- * period, and updating them causes UI flicker.
- *
- * Cache the value since the behavior is instantiated multiple times. Caching
- * is safe because PIN login support depends only on hardware capabilities. The
- * value does not change after discovered.
- *
- * @type {boolean|undefined}
- */
-let cachedHasPinLogin = undefined;
-
-/** @polymerBehavior */
-const LockStateBehaviorImpl = {
- properties: {
- /**
- * The currently selected unlock type.
- * @type {!LockScreenUnlockType}
- */
- selectedUnlockType:
- {type: String, notify: true, value: LockScreenUnlockType.VALUE_PENDING},
-
- /**
- * True/false if there is a PIN set; undefined if the computation is still
- * pending. This is a separate value from selectedUnlockType because the UI
- * can change the selectedUnlockType before setting up a PIN.
- * @type {boolean|undefined}
- */
- hasPin: {type: Boolean, notify: true},
-
- /**
- * True if the PIN backend supports signin. undefined iff the value is still
- * resolving.
- * @type {boolean|undefined}
- */
- hasPinLogin: {type: Boolean, notify: true},
-
- /**
- * Interface for chrome.quickUnlockPrivate calls. May be overridden by
- * tests.
- * @type {QuickUnlockPrivate}
- */
- quickUnlockPrivate: {type: Object, value: chrome.quickUnlockPrivate},
- },
-
- /** @override */
- attached: function() {
- this.boundOnActiveModesChanged_ = this.updateUnlockType.bind(this);
- this.quickUnlockPrivate.onActiveModesChanged.addListener(
- this.boundOnActiveModesChanged_);
-
- // See comment on |cachedHasPinLogin| declaration.
- if (cachedHasPinLogin === undefined) {
- this.addWebUIListener(
- 'pin-login-available-changed',
- this.handlePinLoginAvailableChanged_.bind(this));
- chrome.send('RequestPinLoginState');
- } else {
- this.hasPinLogin = cachedHasPinLogin;
- }
-
- this.updateUnlockType();
- },
-
- /** @override */
- detached: function() {
- this.quickUnlockPrivate.onActiveModesChanged.removeListener(
- this.boundOnActiveModesChanged_);
- },
-
- /**
- * Updates the selected unlock type radio group. This function will get called
- * after preferences are initialized, after the quick unlock mode has been
- * changed, and after the lockscreen preference has changed.
- */
- updateUnlockType: function() {
- this.quickUnlockPrivate.getActiveModes(modes => {
- if (modes.includes(chrome.quickUnlockPrivate.QuickUnlockMode.PIN)) {
- this.hasPin = true;
- this.selectedUnlockType = LockScreenUnlockType.PIN_PASSWORD;
- } else {
- this.hasPin = false;
- this.selectedUnlockType = LockScreenUnlockType.PASSWORD;
- }
- });
- },
-
- /** Sets the lock screen enabled state. */
- setLockScreenEnabled(authToken, enabled) {
- this.quickUnlockPrivate.setLockScreenEnabled(authToken, enabled);
- },
-
- /**
- * Handler for when the pin login available state has been updated.
- * @private
- */
- handlePinLoginAvailableChanged_: function(isAvailable) {
- this.hasPinLogin = isAvailable;
- cachedHasPinLogin = this.hasPinLogin;
- },
-};
-
-/** @polymerBehavior */
-const LockStateBehavior =
- [I18nBehavior, WebUIListenerBehavior, LockStateBehaviorImpl]; \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/people_page/manage_profile.html b/chromium/chrome/browser/resources/settings/people_page/manage_profile.html
deleted file mode 100644
index c2f444c6013..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/manage_profile.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/shadow.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="manage_profile_browser_proxy.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-manage-profile">
- <template>
- <style include="settings-shared">
- cr-input {
- --cr-input-error-display: none;
- }
-
- #selector {
- margin: 16px 48px;
- }
- </style>
- <div class="settings-box first">
- <cr-input id="name" value="[[profileName]]" pattern=".*\S.*"
- on-change="onProfileNameChanged_" on-keydown="onProfileNameKeydown_"
- disabled="[[isProfileNameDisabled_(syncStatus)]]" maxlength="500"
- auto-validate required spellcheck="false">
- </cr-input>
- </div>
- <template is="dom-if" if="[[isProfileShortcutSettingVisible_]]">
- <div class="settings-box first">
- <div id="showShortcutLabel" class="start">$i18n{showShortcutLabel}</div>
- <cr-toggle id="hasShortcutToggle"
- checked="{{hasProfileShortcut_}}"
- on-change="onHasProfileShortcutChange_"
- aria-labelledby="showShortcutLabel">
- </cr-toggle>
- </div>
- </template>
- <cr-profile-avatar-selector id="selector" avatars="[[availableIcons]]"
- selected-avatar="{{profileAvatar_}}" ignore-modified-key-events>
- </cr-profile-avatar-selector>
- </template>
- <script src="manage_profile.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/manage_profile.js b/chromium/chrome/browser/resources/settings/people_page/manage_profile.js
deleted file mode 100644
index 0f45b9044cf..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/manage_profile.js
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-manage-profile' is the settings subpage containing controls to
- * edit a profile's name, icon, and desktop shortcut.
- */
-Polymer({
- is: 'settings-manage-profile',
-
- behaviors: [WebUIListenerBehavior, settings.RouteObserverBehavior],
-
- properties: {
- /**
- * The newly selected avatar. Populated only if the user manually changes
- * the avatar selection. The observer ensures that the changes are
- * propagated to the C++.
- * @private
- */
- profileAvatar_: {
- type: Object,
- observer: 'profileAvatarChanged_',
- },
-
- /**
- * The current profile name.
- */
- profileName: String,
-
- /**
- * True if the current profile has a shortcut.
- */
- hasProfileShortcut_: Boolean,
-
- /**
- * The available icons for selection.
- * @type {!Array<!AvatarIcon>}
- */
- availableIcons: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The current sync status.
- * @type {?settings.SyncStatus}
- */
- syncStatus: Object,
-
- /**
- * True if the profile shortcuts feature is enabled.
- */
- isProfileShortcutSettingVisible_: Boolean,
- },
-
- /** @private {?settings.ManageProfileBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.ManageProfileBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- const setIcons = icons => {
- this.availableIcons = icons;
- };
-
- this.addWebUIListener('available-icons-changed', setIcons);
- this.browserProxy_.getAvailableIcons().then(setIcons);
- },
-
- /** @protected */
- currentRouteChanged: function() {
- if (settings.getCurrentRoute() == settings.routes.MANAGE_PROFILE) {
- if (this.profileName) {
- this.$.name.value = this.profileName;
- }
- if (loadTimeData.getBoolean('profileShortcutsEnabled')) {
- this.browserProxy_.getProfileShortcutStatus().then(status => {
- if (status == ProfileShortcutStatus.PROFILE_SHORTCUT_SETTING_HIDDEN) {
- this.isProfileShortcutSettingVisible_ = false;
- return;
- }
-
- this.isProfileShortcutSettingVisible_ = true;
- this.hasProfileShortcut_ =
- status == ProfileShortcutStatus.PROFILE_SHORTCUT_FOUND;
- });
- }
- }
- },
-
- /**
- * Handler for when the profile name field is changed, then blurred.
- * @param {!Event} event
- * @private
- */
- onProfileNameChanged_: function(event) {
- if (event.target.invalid) {
- return;
- }
-
- this.browserProxy_.setProfileName(event.target.value);
- },
-
- /**
- * Handler for profile name keydowns.
- * @param {!Event} event
- * @private
- */
- onProfileNameKeydown_: function(event) {
- if (event.key == 'Escape') {
- event.target.value = this.profileName;
- event.target.blur();
- }
- },
-
- /**
- * Handler for when the profile avatar is changed by the user.
- * @private
- */
- profileAvatarChanged_: function() {
- if (this.profileAvatar_.isGaiaAvatar) {
- this.browserProxy_.setProfileIconToGaiaAvatar();
- } else {
- this.browserProxy_.setProfileIconToDefaultAvatar(this.profileAvatar_.url);
- }
- },
-
- /**
- * @param {?settings.SyncStatus} syncStatus
- * @return {boolean} Whether the profile name field is disabled.
- * @private
- */
- isProfileNameDisabled_: function(syncStatus) {
- return !!syncStatus.supervisedUser && !syncStatus.childUser;
- },
-
- /**
- * Handler for when the profile shortcut toggle is changed.
- * @param {!Event} event
- * @private
- */
- onHasProfileShortcutChange_: function(event) {
- if (this.hasProfileShortcut_) {
- this.browserProxy_.addProfileShortcut();
- } else {
- this.browserProxy_.removeProfileShortcut();
- }
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.html
deleted file mode 100644
index 6e30760f0cd..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="manage_profile_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.js
deleted file mode 100644
index 4acf227e676..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/manage_profile_browser_proxy.js
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Manage Profile" subpage of
- * the People section to interact with the browser. Chrome Browser only.
- */
-
-/**
- * Contains the possible profile shortcut statuses. These strings must be kept
- * in sync with the C++ Manage Profile handler.
- * @enum {string}
- */
-const ProfileShortcutStatus = {
- PROFILE_SHORTCUT_SETTING_HIDDEN: 'profileShortcutSettingHidden',
- PROFILE_SHORTCUT_NOT_FOUND: 'profileShortcutNotFound',
- PROFILE_SHORTCUT_FOUND: 'profileShortcutFound',
-};
-
-cr.define('settings', function() {
- /** @interface */
- class ManageProfileBrowserProxy {
- /**
- * Gets the available profile icons to choose from.
- * @return {!Promise<!Array<!AvatarIcon>>}
- */
- getAvailableIcons() {}
-
- /**
- * Sets the profile's icon to the GAIA avatar.
- */
- setProfileIconToGaiaAvatar() {}
-
- /**
- * Sets the profile's icon to one of the default avatars.
- * @param {string} iconUrl The new profile URL.
- */
- setProfileIconToDefaultAvatar(iconUrl) {}
-
- /**
- * Sets the profile's name.
- * @param {string} name The new profile name.
- */
- setProfileName(name) {}
-
- /**
- * Returns whether the current profile has a shortcut.
- * @return {!Promise<ProfileShortcutStatus>}
- */
- getProfileShortcutStatus() {}
-
- /**
- * Adds a shortcut for the current profile.
- */
- addProfileShortcut() {}
-
- /**
- * Removes the shortcut of the current profile.
- */
- removeProfileShortcut() {}
- }
-
- /**
- * @implements {settings.ManageProfileBrowserProxy}
- */
- class ManageProfileBrowserProxyImpl {
- /** @override */
- getAvailableIcons() {
- return cr.sendWithPromise('getAvailableIcons');
- }
-
- /** @override */
- setProfileIconToGaiaAvatar() {
- chrome.send('setProfileIconToGaiaAvatar');
- }
-
- /** @override */
- setProfileIconToDefaultAvatar(iconUrl) {
- chrome.send('setProfileIconToDefaultAvatar', [iconUrl]);
- }
-
- /** @override */
- setProfileName(name) {
- chrome.send('setProfileName', [name]);
- }
-
- /** @override */
- getProfileShortcutStatus() {
- return cr.sendWithPromise('requestProfileShortcutStatus');
- }
-
- /** @override */
- addProfileShortcut() {
- chrome.send('addProfileShortcut');
- }
-
- /** @override */
- removeProfileShortcut() {
- chrome.send('removeProfileShortcut');
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(ManageProfileBrowserProxyImpl);
-
- return {
- ManageProfileBrowserProxy: ManageProfileBrowserProxy,
- ManageProfileBrowserProxyImpl: ManageProfileBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/people_page.html b/chromium/chrome/browser/resources/settings/people_page/people_page.html
deleted file mode 100644
index d6891e8da93..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/people_page.html
+++ /dev/null
@@ -1,428 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="profile_info_browser_proxy.html">
-<link rel="import" href="sync_browser_proxy.html">
-<link rel="import" href="sync_page.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../open_window_proxy.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="signout_dialog.html">
-<link rel="import" href="sync_controls.html">
-
-<if expr="chromeos">
-<link rel="import" href="account_manager.html">
-<link rel="import" href="account_manager_browser_proxy.html">
-<link rel="import" href="change_picture.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_png_behavior.html">
-<link rel="import" href="fingerprint_list.html">
-<link rel="import" href="kerberos_accounts.html">
-<link rel="import" href="lock_screen.html">
-<link rel="import" href="lock_state_behavior.html">
-<link rel="import" href="users_page.html">
-<link rel="import" href="../parental_controls_page/parental_controls_page.html">
-</if>
-<if expr="not chromeos">
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
-<link rel="import" href="sync_account_control.html">
-<link rel="import" href="import_data_dialog.html">
-<link rel="import" href="manage_profile.html">
-</if>
-
-<dom-module id="settings-people-page">
- <template>
- <style include="settings-shared iron-flex">
- setting-box.middle {
- /* Per spec, middle text is indented 20px in this section. */
- margin-inline-start: 20px;
- }
-
- .sync-row {
- align-items: center;
- flex: auto;
- }
-
- #profile-icon {
- background: center / cover no-repeat;
- border-radius: 20px;
- flex-shrink: 0;
- height: 40px;
- width: 40px;
- }
-
- #sync-status.no-error {
- --cr-link-row-start-icon-color: var(--google-green-refresh-700);
- }
-
- @media (prefers-color-scheme: dark) {
- #sync-status.no-error {
- --cr-link-row-start-icon-color: var(--google-green-refresh-300);
- }
- }
-
- #sync-setup {
- --cr-secondary-text: {
- color: var(--settings-error-color);
- }
- }
-
- #sync-status[actionable].auth-error {
- --cr-link-row-start-icon-color: var(--google-blue-500);
- }
-
- @media (prefers-color-scheme: dark) {
- #sync-status[actionable].auth-error {
- --cr-link-row-start-icon-color: var(--google-blue-refresh-300);
- }
- }
-
- #sync-status:not([actionable]) .subpage-arrow {
- display: none;
- }
-
- cr-link-row {
- --cr-link-row-icon-width: 40px;
- border-top: var(--cr-separator-line);
- }
-
- #sync-status[actionable].sync-error {
- --cr-link-row-start-icon-color: var(--settings-error-color);
- --cr-secondary-text: {
- color: var(--settings-error-color);
- }
- }
-
- .icon-container {
- display: flex;
- flex-shrink: 0;
- justify-content: center;
- width: 40px;
- }
-
-<if expr="not chromeos">
- #toast {
- left: 0;
- z-index: 1;
- }
-
- :host-context([dir='rtl']) #toast {
- left: auto;
- right: 0;
- }
-
- settings-sync-account-control[showing-promo]::part(banner) {
- /* Make the banner image stay within setting-section's card border
- radius. */
- border-top-left-radius: var(--cr-card-border-radius);
- border-top-right-radius: var(--cr-card-border-radius);
- }
-
- settings-sync-account-control[showing-promo]::part(title) {
- font-size: 1.1rem;
- line-height: 1.625rem;
- }
-</if>
- </style>
- <settings-animated-pages id="pages" section="people"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
-<if expr="not chromeos">
- <template is="dom-if" if="[[shouldShowSyncAccountControl_(diceEnabled_,
- syncStatus.syncSystemEnabled, syncStatus.signinAllowed)]]">
- <settings-sync-account-control
- unified-consent-enabled="[[unifiedConsentEnabled_]]"
- sync-status="[[syncStatus]]"
- promo-label-with-account="$i18n{peopleSignInPrompt}"
- promo-label-with-no-account="$i18n{peopleSignInPrompt}"
- promo-secondary-label-with-account=
- "$i18n{peopleSignInPromptSecondaryWithAccount}"
- promo-secondary-label-with-no-account=
- "$i18n{peopleSignInPromptSecondaryWithNoAccount}">
- </settings-sync-account-control>
- </template>
- <template is="dom-if" if="[[!diceEnabled_]]">
-</if>
- <div id="picture-subpage-trigger" class="settings-box first two-line">
- <template is="dom-if" if="[[syncStatus]]">
- <div id="profile-icon" on-click="onProfileTap_"
- actionable$="[[isProfileActionable_]]"
- style="background-image: [[getIconImageSet_(
- profileIconUrl_)]]">
- </div>
- <div class="middle two-line no-min-width"
- id="profile-row"
- on-click="onProfileTap_"
- actionable$="[[isProfileActionable_]]">
- <div class="flex text-elide settings-box-text">
- <span id="profile-name">[[profileName_]]</span>
- <div class="secondary" hidden="[[!syncStatus.signedIn]]">
- [[syncStatus.signedInUsername]]
- </div>
- </div>
-<if expr="not chromeos">
- <cr-icon-button class="subpage-arrow"
- aria-label="$i18n{editPerson}"
- aria-describedby="profile-name"></cr-icon-button>
-</if>
-<if expr="chromeos">
- <cr-icon-button class$="[[profileRowIconClass_]]"
- id="profile-subpage-arrow"
- hidden="[[!isProfileActionable_]]"
- aria-label$="[[profileRowIconAriaLabel_]]"
- aria-describedby="profile-name"></cr-icon-button>
-</if>
- </div>
-<if expr="not chromeos">
- <template is="dom-if" if="[[showSignin_(syncStatus)]]">
- <div class="separator"></div>
- <cr-button class="action-button" on-click="onSigninTap_"
- disabled="[[syncStatus.firstSetupInProgress]]">
- $i18n{syncSignin}
- </cr-button>
- </template>
- <template is="dom-if" if="[[syncStatus.signedIn]]">
- <div class="separator"></div>
- <cr-button id="disconnectButton" on-click="onDisconnectTap_"
- disabled="[[syncStatus.firstSetupInProgress]]">
- $i18n{syncDisconnect}
- </cr-button>
- </template>
-</if>
- </template>
- </div>
-<if expr="not chromeos">
- </template> <!-- if="[[!diceEnabled_]]" -->
-</if>
-
- <template is="dom-if" if="[[!syncStatus.signedIn]]">
-<if expr="not chromeos">
- <template is="dom-if" if="[[!diceEnabled_]]">
-</if>
- <div class="settings-box two-line" id="sync-overview"
- hidden="[[!syncStatus.signinAllowed]]">
- <div class="start settings-box-text">
- $i18n{syncOverview}
- <a target="_blank" href="$i18n{syncLearnMoreUrl}">
- $i18n{learnMore}
- </a>
- </div>
- </div>
-<if expr="not chromeos">
- </template> <!-- if="[[!diceEnabled_]]" -->
-</if>
- </template>
-
- <template is="dom-if"
- if="[[isPreUnifiedConsentAdvancedSyncSettingsVisible_(
- syncStatus, unifiedConsentEnabled_)]]">
- <cr-link-row id="sync-status"
- class$="[[getSyncStatusClass_(syncStatus)]]"
- label="$i18n{syncAndNonPersonalizedServices}"
- sub-label="[[getSyncAndGoogleServicesSubtext_(syncStatus)]]"
- on-click="onSyncTap_" start-icon="[[getSyncIcon_(syncStatus)]]"
- actionable$="[[isSyncStatusActionable_(syncStatus)]]">
- </cr-link-row>
- </template>
-
- <template is="dom-if" if="[[unifiedConsentEnabled_]]">
- <cr-link-row id="sync-setup"
- label="$i18n{syncAndNonPersonalizedServices}"
- sub-label="[[getSyncAndGoogleServicesSubtext_(syncStatus)]]"
- on-click="onSyncTap_">
- </cr-link-row>
- </template>
-
-<if expr="not chromeos">
- <template is="dom-if" if="[[diceEnabled_]]">
- <cr-link-row id="manage-google-account"
- label="$i18n{manageGoogleAccount}"
- hidden="[[!shouldShowGoogleAccount_]]"
- on-click="openGoogleAccount_" external></cr-link-row>
-
- <div class="settings-box" id="edit-profile" on-click="onProfileTap_"
- actionable>
- <div class="start settings-box-text">
- $i18n{profileNameAndPicture}
- </div>
- <cr-icon-button class="subpage-arrow"
- aria-label="$i18n{editPerson}"></cr-icon-button>
- </div>
- </template>
-</if>
-
-<if expr="chromeos">
- <cr-link-row id="lock-screen-subpage-trigger" class="hr"
- on-click="onConfigureLockTap_"
- label="[[selectLockScreenTitleString(hasPinLogin)]]"
- sub-label="[[getPasswordState_(hasPin,
- prefs.settings.enable_screen_lock.value)]]"
- hidden="[[!pageVisibility.people.lockScreen]]"></cr-link-row>
-
- <template is="dom-if" if="[[pageVisibility.people.googleAccounts]]">
- <cr-link-row id="account-manager-subpage-trigger" class="hr"
- on-click="onAccountManagerTap_"
- label="$i18n{accountManagerSubMenuLabel}"></cr-link-row>
- </template>
- <template is="dom-if" if="[[showParentalControls_]]">
- <settings-parental-controls-page>
- </settings-parental-controls-page>
- </template>
- <cr-link-row id="manage-other-people-subpage-trigger"
- label="$i18n{manageOtherPeople}" on-click="onManageOtherPeople_"
- hidden="[[!pageVisibility.people.manageUsers]]">
- </cr-link-row>
-
- <template is="dom-if" if="[[pageVisibility.people.kerberosAccounts]]">
- <cr-link-row id="kerberos-accounts-subpage-trigger" class="hr"
- on-click="onKerberosAccountsTap_"
- label="$i18n{kerberosAccountsSubMenuLabel}">
- <cr-policy-indicator indicator-type="userPolicy">
- </cr-policy-indicator>
- </cr-link-row>
- </template>
-</if>
-
-<if expr="not chromeos">
- <cr-link-row id="importDataDialogTrigger"
- label="$i18n{importTitle}"
- on-click="onImportDataTap_"></cr-link-row>
-</if>
-
- </div>
- <template is="dom-if" route-path="/syncSetup"
- no-search="[[!isAdvancedSyncSettingsSearchable_(
- syncStatus, unifiedConsentEnabled_)]]">
- <settings-subpage
- associated-control="[[getAdvancedSyncSettingsAssociatedControl_(
- unifiedConsentEnabled_)]]"
- page-title="$i18n{syncPageTitle}"
- learn-more-url="$i18n{syncAndGoogleServicesLearnMoreURL}"
- no-search$="[[!isAdvancedSyncSettingsSearchable_(syncStatus,
- unifiedConsentEnabled_)]]">
- <settings-sync-page
-<if expr="not chromeos">
- dice-enabled="[[diceEnabled_]]"
-</if>
- unified-consent-enabled="[[unifiedConsentEnabled_]]"
- sync-status="[[syncStatus]]" prefs="{{prefs}}"
- page-visibility="[[pageVisibility.privacy]]">
- </settings-sync-page>
- </settings-subpage>
- </template>
-
- <template is="dom-if" if="[[unifiedConsentEnabled_]]">
- <template is="dom-if" route-path="/syncSetup/advanced">
- <settings-subpage page-title="$i18n{syncAdvancedPageTitle}"
- associated-control="[[$$('#sync-setup')]]"
- learn-more-url="$i18n{syncAndGoogleServicesLearnMoreURL}">
- <settings-sync-controls sync-status="[[syncStatus]]">
- </settings-sync-controls>
- </settings-subpage>
- </template>
- </template>
-
-<if expr="chromeos">
- <template is="dom-if" if="[[pageVisibility.people.lockScreen]]">
- <template is="dom-if" route-path="/lockScreen">
- <settings-subpage
- page-title="[[selectLockScreenTitleString(hasPinLogin)]]"
- associated-control="[[$$('#lock-screen-subpage-trigger')]]">
- <settings-lock-screen
- prefs="{{prefs}}" auth-token="{{authToken_}}">
- </settings-lock-screen>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[fingerprintUnlockEnabled_]]">
- <template is="dom-if" route-path="/lockScreen/fingerprint" no-search>
- <settings-subpage page-title="$i18n{lockScreenFingerprintTitle}">
- <settings-fingerprint-list auth-token="[[authToken_]]">
- </settings-fingerprint-list>
- </settings-subpage>
- </template>
- </template>
- </template>
- <template is="dom-if" if="[[pageVisibility.people.manageUsers]]">
- <template is="dom-if" route-path="/accounts">
- <settings-subpage
- associated-control=
- "[[$$('#manage-other-people-subpage-trigger')]]"
- page-title="$i18n{manageOtherPeople}">
- <settings-users-page prefs="{{prefs}}">
- </settings-users-page>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" route-path="/changePicture">
- <settings-subpage
- associated-control="[[$$('#picture-subpage-trigger')]]"
- page-title="$i18n{changePictureTitle}">
- <settings-change-picture></settings-change-picture>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[pageVisibility.people.googleAccounts]]">
- <template is="dom-if" route-path="/accountManager">
- <settings-subpage
- associated-control="[[$$('#account-manager-subpage-trigger')]]"
- page-title="$i18n{accountManagerPageTitle}">
- <settings-account-manager></settings-account-manager>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" if="[[pageVisibility.people.kerberosAccounts]]">
- <template is="dom-if" route-path="/kerberosAccounts">
- <settings-subpage
- associated-control="[[$$('#kerberos-accounts-subpage-trigger')]]"
- page-title="$i18n{kerberosAccountsPageTitle}">
- <settings-kerberos-accounts></settings-kerberos-accounts>
- </settings-subpage>
- </template>
- </template>
-</if>
-<if expr="not chromeos">
- <template is="dom-if" route-path="/manageProfile">
- <settings-subpage
- associated-control="[[getEditPersonAssocControl_(diceEnabled_)]]"
- page-title="$i18n{editPerson}">
- <settings-manage-profile profile-name="[[profileName_]]"
- sync-status="[[syncStatus]]">
- </settings-manage-profile>
- </settings-subpage>
- </template>
-</if>
- </settings-animated-pages>
-
- <template is="dom-if" if="[[showSignoutDialog_]]" restamp>
- <settings-signout-dialog sync-status="[[syncStatus]]"
- on-close="onDisconnectDialogClosed_">
- </settings-signout-dialog>
- </template>
-
- <template is="dom-if" if="[[showImportDataDialog_]]" restamp>
- <settings-import-data-dialog prefs="{{prefs}}"
- on-close="onImportDataDialogClosed_">
- </settings-import-data-dialog>
- </template>
-<if expr="not chromeos">
- <cr-toast duration="3000" id="toast">
- <span>$i18n{syncSettingsSavedToast}</span>
- </cr-toast>
-</if>
- </template>
- <script src="people_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/people_page.js b/chromium/chrome/browser/resources/settings/people_page/people_page.js
deleted file mode 100644
index ec7591d62f6..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/people_page.js
+++ /dev/null
@@ -1,699 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-people-page' is the settings page containing sign-in settings.
- */
-Polymer({
- is: 'settings-people-page',
-
- behaviors: [
- settings.RouteObserverBehavior, I18nBehavior, WebUIListenerBehavior,
- // <if expr="chromeos">
- CrPngBehavior, LockStateBehavior,
- // </if>
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- // <if expr="not chromeos">
- /**
- * This flag is used to conditionally show a set of new sign-in UIs to the
- * profiles that have been migrated to be consistent with the web sign-ins.
- * TODO(tangltom): In the future when all profiles are completely migrated,
- * this should be removed, and UIs hidden behind it should become default.
- * @private
- */
- diceEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('diceEnabled');
- },
- },
- // </if>
-
- /**
- * This flag is used to conditionally show a set of sync UIs to the
- * profiles that have been migrated to have a unified consent flow.
- * TODO(tangltom): In the future when all profiles are completely migrated,
- * this should be removed, and UIs hidden behind it should become default.
- * @private
- */
- unifiedConsentEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('unifiedConsentEnabled');
- },
- },
-
- // <if expr="not chromeos">
- /**
- * Stored accounts to the system, supplied by SyncBrowserProxy.
- * @type {?Array<!settings.StoredAccount>}
- */
- storedAccounts: Object,
- // </if>
-
- /**
- * The current sync status, supplied by SyncBrowserProxy.
- * @type {?settings.SyncStatus}
- */
- syncStatus: Object,
-
- /**
- * Dictionary defining page visibility.
- * @type {!PageVisibility}
- */
- pageVisibility: Object,
-
- /**
- * Authentication token provided by settings-lock-screen.
- * @private
- */
- authToken_: {
- type: String,
- value: '',
- },
-
- /**
- * The currently selected profile icon URL. May be a data URL.
- * @private
- */
- profileIconUrl_: String,
-
- /**
- * Whether the profile row is clickable. The behavior depends on the
- * platform.
- * @private
- */
- isProfileActionable_: {
- type: Boolean,
- value: function() {
- if (!cr.isChromeOS) {
- // Opens profile manager.
- return true;
- }
- if (loadTimeData.getBoolean('showOSSettings')) {
- // Pre-SplitSettings opens change picture.
- return true;
- }
- // Post-SplitSettings links out to account manager if it is available.
- return loadTimeData.getBoolean('isAccountManagerEnabled');
- },
- readOnly: true,
- },
-
- /**
- * The current profile name.
- * @private
- */
- profileName_: String,
-
- // <if expr="chromeos">
- /** @private {string} */
- profileRowIconClass_: {
- type: String,
- value: function() {
- if (loadTimeData.getBoolean('showOSSettings')) {
- // Pre-SplitSettings links internally to the change picture subpage.
- return 'subpage-arrow';
- } else {
- // Post-SplitSettings links externally to account manager. If account
- // manager isn't available the icon will be hidden.
- return 'icon-external';
- }
- },
- readOnly: true,
- },
-
- /** @private {string} */
- profileRowIconAriaLabel_: {
- type: String,
- value: function() {
- if (loadTimeData.getBoolean('showOSSettings')) {
- // Pre-SplitSettings.
- return this.i18n('changePictureTitle');
- } else {
- // Post-SplitSettings. If account manager isn't available the icon
- // will be hidden so the label doesn't matter.
- return this.i18n('accountManagerSubMenuLabel');
- }
- },
- readOnly: true,
- },
- // </if>
-
- // <if expr="not chromeos">
- /** @private {boolean} */
- shouldShowGoogleAccount_: {
- type: Boolean,
- value: false,
- computed: 'computeShouldShowGoogleAccount_(storedAccounts, syncStatus,' +
- 'storedAccounts.length, syncStatus.signedIn, syncStatus.hasError)',
- },
-
- /** @private */
- showImportDataDialog_: {
- type: Boolean,
- value: false,
- },
- // </if>
-
- /** @private */
- showSignoutDialog_: Boolean,
-
- // <if expr="chromeos">
- /**
- * True if fingerprint settings should be displayed on this machine.
- * @private
- */
- fingerprintUnlockEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('fingerprintUnlockEnabled');
- },
- readOnly: true,
- },
-
- /** @private */
- showParentalControls_: {
- type: Boolean,
- value: function() {
- return loadTimeData.valueExists('showParentalControls') &&
- loadTimeData.getBoolean('showParentalControls');
- },
- },
- // </if>
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.SYNC) {
- map.set(
- settings.routes.SYNC.path,
- loadTimeData.getBoolean('unifiedConsentEnabled') ?
- '#sync-setup' :
- '#sync-status .subpage-arrow');
- }
- // <if expr="not chromeos">
- if (settings.routes.MANAGE_PROFILE) {
- map.set(
- settings.routes.MANAGE_PROFILE.path,
- loadTimeData.getBoolean('diceEnabled') ?
- '#edit-profile .subpage-arrow' :
- '#picture-subpage-trigger .subpage-arrow');
- }
- // </if>
- // <if expr="chromeos">
- if (settings.routes.CHANGE_PICTURE) {
- map.set(
- settings.routes.CHANGE_PICTURE.path,
- '#picture-subpage-trigger .subpage-arrow');
- }
- if (settings.routes.LOCK_SCREEN) {
- map.set(
- settings.routes.LOCK_SCREEN.path, '#lock-screen-subpage-trigger');
- }
- if (settings.routes.ACCOUNTS) {
- map.set(
- settings.routes.ACCOUNTS.path,
- '#manage-other-people-subpage-trigger');
- }
- if (settings.routes.ACCOUNT_MANAGER) {
- map.set(
- settings.routes.ACCOUNT_MANAGER.path,
- '#account-manager-subpage-trigger');
- }
- if (settings.routes.KERBEROS_ACCOUNTS) {
- map.set(
- settings.routes.KERBEROS_ACCOUNTS.path,
- '#kerberos-accounts-subpage-trigger');
- }
- // </if>
- return map;
- },
- },
- },
-
- /** @private {?settings.SyncBrowserProxy} */
- syncBrowserProxy_: null,
-
- /** @override */
- attached: function() {
- let useProfileNameAndIcon = true;
- // <if expr="chromeos">
- if (!loadTimeData.getBoolean('showOSSettings') &&
- loadTimeData.getBoolean('isAccountManagerEnabled')) {
- // If this is SplitSettings and we have the Google Account manager,
- // prefer the GAIA name and icon.
- useProfileNameAndIcon = false;
- this.addWebUIListener(
- 'accounts-changed', this.updateAccounts_.bind(this));
- this.updateAccounts_();
- }
- // </if>
- if (useProfileNameAndIcon) {
- settings.ProfileInfoBrowserProxyImpl.getInstance().getProfileInfo().then(
- this.handleProfileInfo_.bind(this));
- this.addWebUIListener(
- 'profile-info-changed', this.handleProfileInfo_.bind(this));
- }
-
- this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
- this.syncBrowserProxy_.getSyncStatus().then(
- this.handleSyncStatus_.bind(this));
- this.addWebUIListener(
- 'sync-status-changed', this.handleSyncStatus_.bind(this));
-
- // <if expr="not chromeos">
- const handleStoredAccounts = accounts => {
- this.storedAccounts = accounts;
- };
- this.syncBrowserProxy_.getStoredAccounts().then(handleStoredAccounts);
- this.addWebUIListener('stored-accounts-updated', handleStoredAccounts);
-
- this.addWebUIListener('sync-settings-saved', () => {
- /** @type {!CrToastElement} */ (this.$.toast).show();
- });
- // </if>
- },
-
- /** @protected */
- currentRouteChanged: function() {
- this.showImportDataDialog_ =
- settings.getCurrentRoute() == settings.routes.IMPORT_DATA;
-
- if (settings.getCurrentRoute() == settings.routes.SIGN_OUT) {
- // If the sync status has not been fetched yet, optimistically display
- // the sign-out dialog. There is another check when the sync status is
- // fetched. The dialog will be closed when the user is not signed in.
- if (this.syncStatus && !this.syncStatus.signedIn) {
- settings.navigateToPreviousRoute();
- } else {
- this.showSignoutDialog_ = true;
- }
- }
- },
-
- /**
- * @return {!Element}
- * @private
- */
- getEditPersonAssocControl_: function() {
- return this.diceEnabled_ ? assert(this.$$('#edit-profile')) :
- assert(this.$$('#picture-subpage-trigger'));
- },
-
- // <if expr="chromeos">
- /** @private */
- getPasswordState_: function(hasPin, enableScreenLock) {
- if (!enableScreenLock) {
- return this.i18n('lockScreenNone');
- }
- if (hasPin) {
- return this.i18n('lockScreenPinOrPassword');
- }
- return this.i18n('lockScreenPasswordOnly');
- },
- // </if>
-
- /**
- * @return {string}
- * @private
- */
- getSyncAndGoogleServicesSubtext_: function() {
- if (this.syncStatus && this.syncStatus.hasError &&
- this.syncStatus.statusText) {
- return this.syncStatus.statusText;
- }
- return '';
- },
-
- /**
- * Handler for when the profile's icon and name is updated.
- * @private
- * @param {!settings.ProfileInfo} info
- */
- handleProfileInfo_: function(info) {
- this.profileName_ = info.name;
- /**
- * Extract first frame from image by creating a single frame PNG using
- * url as input if base64 encoded and potentially animated.
- */
- // <if expr="chromeos">
- if (info.iconUrl.startsWith('data:image/png;base64')) {
- this.profileIconUrl_ =
- CrPngBehavior.convertImageSequenceToPng([info.iconUrl]);
- return;
- }
- // </if>
-
- this.profileIconUrl_ = info.iconUrl;
- },
-
- // <if expr="chromeos">
- /**
- * @private
- * @suppress {checkTypes} The types only exists in Chrome OS builds, but
- * Closure doesn't understand the <if> above.
- */
- updateAccounts_: async function() {
- const /** @type {!Array<{settings.Account}>} */ accounts =
- await settings.AccountManagerBrowserProxyImpl.getInstance()
- .getAccounts();
- // The user might not have any GAIA accounts (e.g. guest mode, Kerberos,
- // Active Directory). In these cases the profile row is hidden, so there's
- // nothing to do.
- if (accounts.length == 0) {
- return;
- }
- this.profileName_ = accounts[0].fullName;
- this.profileIconUrl_ = accounts[0].pic;
- },
- // </if>
-
- /**
- * Handler for when the sync state is pushed from the browser.
- * @param {?settings.SyncStatus} syncStatus
- * @private
- */
- handleSyncStatus_: function(syncStatus) {
- // Sign-in impressions should be recorded only if the sign-in promo is
- // shown. They should be recorder only once, the first time
- // |this.syncStatus| is set.
- const shouldRecordSigninImpression =
- !this.syncStatus && syncStatus && this.showSignin_(syncStatus);
-
- this.syncStatus = syncStatus;
-
- if (shouldRecordSigninImpression && !this.shouldShowSyncAccountControl_()) {
- // SyncAccountControl records the impressions user actions.
- chrome.metricsPrivate.recordUserAction('Signin_Impression_FromSettings');
- }
- },
-
- // <if expr="not chromeos">
- /**
- * @return {boolean}
- * @private
- */
- computeShouldShowGoogleAccount_: function() {
- if (this.storedAccounts === undefined || this.syncStatus === undefined) {
- return false;
- }
-
- return (this.storedAccounts.length > 0 || !!this.syncStatus.signedIn) &&
- !this.syncStatus.hasError;
- },
- // </if>
-
- /** @private */
- onProfileTap_: function() {
- // <if expr="chromeos">
- if (loadTimeData.getBoolean('showOSSettings')) {
- // Pre-SplitSettings.
- settings.navigateTo(settings.routes.CHANGE_PICTURE);
- } else if (loadTimeData.getBoolean('isAccountManagerEnabled')) {
- // Post-SplitSettings. The browser C++ code loads OS settings in a window.
- // Don't use window.open() because that creates an extra empty tab.
- window.location.href = 'chrome://os-settings/accountManager';
- }
- // </if>
- // <if expr="not chromeos">
- settings.navigateTo(settings.routes.MANAGE_PROFILE);
- // </if>
- },
-
- /** @private */
- onSigninTap_: function() {
- this.syncBrowserProxy_.startSignIn();
- },
-
- /** @private */
- onDisconnectDialogClosed_: function(e) {
- this.showSignoutDialog_ = false;
- // <if expr="not chromeos">
- if (!this.diceEnabled_) {
- // If DICE-enabled, this button won't exist here.
- cr.ui.focusWithoutInk(assert(this.$$('#disconnectButton')));
- }
- // </if>
-
- // <if expr="chromeos">
- cr.ui.focusWithoutInk(assert(this.$$('#disconnectButton')));
- // </if>
-
- if (settings.getCurrentRoute() == settings.routes.SIGN_OUT) {
- settings.navigateToPreviousRoute();
- }
- },
-
- /** @private */
- onDisconnectTap_: function() {
- settings.navigateTo(settings.routes.SIGN_OUT);
- },
-
- /** @private */
- onSyncTap_: function() {
- // When unified-consent is enabled, users can go to sync subpage regardless
- // of sync status.
- if (this.unifiedConsentEnabled_) {
- settings.navigateTo(settings.routes.SYNC);
- return;
- }
-
- // TODO(crbug.com/862983): Remove this code once UnifiedConsent is rolled
- // out to 100%.
- assert(this.syncStatus.signedIn);
- assert(this.syncStatus.syncSystemEnabled);
-
- if (!this.isSyncStatusActionable_(this.syncStatus)) {
- return;
- }
-
- switch (this.syncStatus.statusAction) {
- case settings.StatusAction.REAUTHENTICATE:
- this.syncBrowserProxy_.startSignIn();
- break;
- case settings.StatusAction.SIGNOUT_AND_SIGNIN:
- // <if expr="chromeos">
- this.syncBrowserProxy_.attemptUserExit();
- // </if>
- // <if expr="not chromeos">
- if (this.syncStatus.domain) {
- settings.navigateTo(settings.routes.SIGN_OUT);
- } else {
- // Silently sign the user out without deleting their profile and
- // prompt them to sign back in.
- this.syncBrowserProxy_.signOut(false);
- this.syncBrowserProxy_.startSignIn();
- }
- // </if>
- break;
- case settings.StatusAction.UPGRADE_CLIENT:
- settings.navigateTo(settings.routes.ABOUT);
- break;
- case settings.StatusAction.ENTER_PASSPHRASE:
- case settings.StatusAction.CONFIRM_SYNC_SETTINGS:
- case settings.StatusAction.NO_ACTION:
- default:
- settings.navigateTo(settings.routes.SYNC);
- }
- },
-
- // <if expr="chromeos">
- /**
- * @param {!Event} e
- * @private
- */
- onConfigureLockTap_: function(e) {
- // Navigating to the lock screen will always open the password prompt
- // dialog, so prevent the end of the tap event to focus what is underneath
- // it, which takes focus from the dialog.
- e.preventDefault();
- settings.navigateTo(settings.routes.LOCK_SCREEN);
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onAccountManagerTap_: function(e) {
- settings.navigateTo(settings.routes.ACCOUNT_MANAGER);
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onKerberosAccountsTap_: function(e) {
- settings.navigateTo(settings.routes.KERBEROS_ACCOUNTS);
- },
-
- /** @private */
- onManageOtherPeople_: function() {
- settings.navigateTo(settings.routes.ACCOUNTS);
- },
- // </if>
-
- // <if expr="not chromeos">
- /** @private */
- onImportDataTap_: function() {
- settings.navigateTo(settings.routes.IMPORT_DATA);
- },
-
- /** @private */
- onImportDataDialogClosed_: function() {
- settings.navigateToPreviousRoute();
- cr.ui.focusWithoutInk(assert(this.$.importDataDialogTrigger));
- },
-
- /**
- * Open URL for managing your Google Account.
- * @private
- */
- openGoogleAccount_: function() {
- settings.OpenWindowProxyImpl.getInstance().openURL(
- loadTimeData.getString('googleAccountUrl'));
- chrome.metricsPrivate.recordUserAction('ManageGoogleAccount_Clicked');
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowSyncAccountControl_: function() {
- if (this.syncStatus == undefined) {
- return false;
- }
-
- return this.diceEnabled_ && !!this.syncStatus.syncSystemEnabled &&
- !!this.syncStatus.signinAllowed;
- },
- // </if>
-
- /**
- * @private
- * @param {?settings.SyncStatus} syncStatus
- * @return {boolean}
- */
- isPreUnifiedConsentAdvancedSyncSettingsVisible_: function(syncStatus) {
- return !!syncStatus && !!syncStatus.signedIn &&
- !!syncStatus.syncSystemEnabled && !this.unifiedConsentEnabled_;
- },
-
- /**
- * @private
- * @param {?settings.SyncStatus} syncStatus
- * @return {boolean}
- */
- isAdvancedSyncSettingsSearchable_: function(syncStatus) {
- return this.isPreUnifiedConsentAdvancedSyncSettingsVisible_(syncStatus) ||
- !!this.unifiedConsentEnabled_;
- },
-
- /**
- * @private
- * @return {Element|null}
- */
- getAdvancedSyncSettingsAssociatedControl_: function() {
- return this.unifiedConsentEnabled_ ? this.$$('#sync-setup') :
- this.$$('#sync-status');
- },
-
- /**
- * @private
- * @param {?settings.SyncStatus} syncStatus
- * @return {boolean} Whether an action can be taken with the sync status. sync
- * status is actionable if sync is not managed and if there is a sync
- * error, there is an action associated with it.
- */
- isSyncStatusActionable_: function(syncStatus) {
- return !!syncStatus && !syncStatus.managed &&
- (!syncStatus.hasError ||
- syncStatus.statusAction != settings.StatusAction.NO_ACTION);
- },
-
- /**
- * @private
- * @param {?settings.SyncStatus} syncStatus
- * @return {string}
- */
- getSyncIcon_: function(syncStatus) {
- if (!syncStatus) {
- return '';
- }
-
- let syncIcon = 'cr:sync';
-
- if (syncStatus.hasError) {
- syncIcon = 'settings:sync-problem';
- }
-
- // Override the icon to the disabled icon if sync is managed.
- if (syncStatus.managed ||
- syncStatus.statusAction == settings.StatusAction.REAUTHENTICATE) {
- syncIcon = 'settings:sync-disabled';
- }
-
- return syncIcon;
- },
-
- /**
- * @private
- * @param {?settings.SyncStatus} syncStatus
- * @return {string} The class name for the sync status row.
- */
- getSyncStatusClass_: function(syncStatus) {
- if (syncStatus && syncStatus.hasError) {
- // Most of the time re-authenticate states are caused by intentional user
- // action, so they will be displayed differently as other errors.
- return syncStatus.statusAction == settings.StatusAction.REAUTHENTICATE ?
- 'auth-error' :
- 'sync-error';
- }
-
- return 'no-error';
- },
-
- /**
- * @param {string} iconUrl
- * @return {string} A CSS image-set for multiple scale factors.
- * @private
- */
- getIconImageSet_: function(iconUrl) {
- return cr.icon.getImage(iconUrl);
- },
-
- /**
- * @param {!settings.SyncStatus} syncStatus
- * @return {boolean} Whether to show the "Sign in to Chrome" button.
- * @private
- */
- showSignin_: function(syncStatus) {
- return !!syncStatus.signinAllowed && !syncStatus.signedIn;
- },
-
- /**
- * Looks up the translation id, which depends on PIN login support.
- * @param {boolean} hasPinLogin
- * @private
- */
- selectLockScreenTitleString(hasPinLogin) {
- if (hasPinLogin) {
- return this.i18n('lockScreenTitleLoginLock');
- }
- return this.i18n('lockScreenTitleLock');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.html
deleted file mode 100644
index 76290dddea4..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="profile_info_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.js
deleted file mode 100644
index 65808a60de6..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/profile_info_browser_proxy.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the the People section to get the
- * profile info, which consists of the profile name and icon. Used for both
- * Chrome browser and ChromeOS.
- */
-cr.exportPath('settings');
-
-/**
- * An object describing the profile.
- * @typedef {{
- * name: string,
- * iconUrl: string
- * }}
- */
-settings.ProfileInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class ProfileInfoBrowserProxy {
- /**
- * Returns a Promise for the profile info.
- * @return {!Promise<!settings.ProfileInfo>}
- */
- getProfileInfo() {}
-
- /**
- * Requests the profile stats count. The result is returned by the
- * 'profile-stats-count-ready' WebUI listener event.
- */
- getProfileStatsCount() {}
- }
-
- /**
- * @implements {ProfileInfoBrowserProxy}
- */
- class ProfileInfoBrowserProxyImpl {
- /** @override */
- getProfileInfo() {
- return cr.sendWithPromise('getProfileInfo');
- }
-
- /** @override */
- getProfileStatsCount() {
- chrome.send('getProfileStatsCount');
- }
- }
-
- cr.addSingletonGetter(ProfileInfoBrowserProxyImpl);
-
- return {
- ProfileInfoBrowserProxyImpl: ProfileInfoBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/settings_icon_camera_alt.svg b/chromium/chrome/browser/resources/settings/people_page/settings_icon_camera_alt.svg
deleted file mode 100644
index dbe522a6b85..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/settings_icon_camera_alt.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#FFF"><circle cx="12" cy="12" r="3.2"/><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/></svg> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html b/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
deleted file mode 100644
index 298e790f814..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
+++ /dev/null
@@ -1,105 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/chromeos/cr_lottie/cr_lottie.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="fingerprint_browser_proxy.html">
-
-<dom-module id="settings-setup-fingerprint-dialog">
- <template>
- <style include="settings-shared">
- #dialog::part(dialog) {
- min-width: 500px;
- width: 500px;
- }
-
- .fingerprint-scanner-laptop-bottom-right {
- background:
- url(chrome://theme/IDR_LOGIN_FINGERPRINT_SCANNER_LAPTOP_BOTTOM_RIGHT_ANIMATION);
- }
-
- .fingerprint-scanner-laptop-top-right {
- background:
- url(chrome://theme/IDR_LOGIN_FINGERPRINT_SCANNER_LAPTOP_TOP_RIGHT_ANIMATION);
- }
-
- #scannerLocation {
- background-position: center;
- background-repeat: no-repeat;
- background-size: 298px 205px;
- height: 240px;
- }
-
- #scannerLocationLottie {
- height: 220px;
- padding: 10px 0;
- }
-
- #messageDiv {
- height: 20px;
- }
-
- /* Use this instead of hidden so that the dialog does not resize when the
- message appears or disappears. */
- #messageDiv[invisible] {
- visibility: hidden;
- }
-
- #closeButton {
- margin-inline-start: 5px;
- }
- </style>
-
- <cr-dialog id="dialog" on-close="close"
- close-text="$i18n{close}">
- <div slot="title">$i18n{configureFingerprintTitle}</div>
- <div slot="body">
- <div id="messageDiv"
- invisible$="[[!getInstructionMessage_(step_, problemMessage_)]]"
- aria-live="polite">
- <span>[[getInstructionMessage_(step_, problemMessage_)]]</span>
- </div>
- <template is="dom-if" if="[[shouldUseLottieAnimation_]]">
- <div id="scannerLocationLottie"
- hidden="[[!showScannerLocation_(step_)]]" aria-live="polite"
- aria-label="$i18n{configureFingerprintScannerStepAriaLabel}">
- <cr-lottie animation-url="finger_print.json" autoplay>
- </cr-lottie>
- </div>
- </template>
- <template is="dom-if" if="[[!shouldUseLottieAnimation_]]">
- <div id="scannerLocation" hidden="[[!showScannerLocation_(step_)]]"
- class$="[[fingerprintScannerAnimationClass_]]"
- aria-label="$i18n{configureFingerprintScannerStepAriaLabel}"
- aria-live="polite" >
- </div>
- </template>
- <cr-fingerprint-progress-arc id="arc" circle-radius="100"
- hidden="[[!showArc_(step_)]]">
- </cr-fingerprint-progress-arc>
- </div>
- <div slot="button-container">
- <cr-button id="addAnotherButton" on-click="onAddAnotherFingerprint_"
- hidden$="[[hideAddAnother_(step_, allowAddAnotherFinger)]]">
- $i18n{configureFingerprintAddAnotherButton}
- </cr-button>
-
- <cr-button id="closeButton"
- class$="[[getCloseButtonClass_(step_)]]" on-click="onClose_">
- [[getCloseButtonText_(step_)]]
- </cr-button>
- </div>
- </cr-dialog>
- </template>
-
- <script src="setup_fingerprint_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js b/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
deleted file mode 100644
index d0d6d0ec42b..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
+++ /dev/null
@@ -1,370 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * The steps in the fingerprint setup flow.
- * @enum {number}
- */
-settings.FingerprintSetupStep = {
- LOCATE_SCANNER: 1, // The user needs to locate the scanner.
- MOVE_FINGER: 2, // The user needs to move finger around the scanner.
- READY: 3 // The scanner has read the fingerprint successfully.
-};
-
-/**
- * Fingerprint sensor locations corresponding to the FingerprintLocation
- * enumerators in
- * /chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
- * @enum {number}
- */
-settings.FingerprintLocation = {
- TABLET_POWER_BUTTON: 0,
- KEYBOARD_TOP_RIGHT: 1,
- KEYBOARD_BOTTOM_RIGHT: 2,
-};
-
-(function() {
-
-/**
- * The amount of milliseconds after a successful but not completed scan before a
- * message shows up telling the user to scan their finger again.
- * @type {number}
- */
-const SHOW_TAP_SENSOR_MESSAGE_DELAY_MS = 2000;
-
-Polymer({
- is: 'settings-setup-fingerprint-dialog',
-
- behaviors: [I18nBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * Whether add another finger is allowed.
- * @type {boolean}
- */
- allowAddAnotherFinger: {
- type: Boolean,
- value: true,
- },
-
- /**
- * Authentication token provided by settings-fingerprint-list
- */
- authToken: {
- type: String,
- value: '',
- },
- /**
- * The problem message to display.
- * @private
- */
- problemMessage_: {
- type: String,
- value: '',
- },
-
- /**
- * The setup phase we are on.
- * @type {!settings.FingerprintSetupStep}
- * @private
- */
- step_: {type: Number, value: settings.FingerprintSetupStep.LOCATE_SCANNER},
-
- /**
- * The percentage of completion that has been received during setup.
- * This is used to approximate the progress of the setup.
- * The value within [0, 100] represents the percent of enrollment
- * completion.
- * @type {number}
- * @private
- */
- percentComplete_: {
- type: Number,
- value: 0,
- observer: 'onProgressChanged_',
- },
-
- /**
- * This is used to display right animation for fingerprint sensor.
- * @private {string}
- */
- fingerprintScannerAnimationClass_: {
- type: String,
- value: function() {
- if (!loadTimeData.getBoolean('fingerprintUnlockEnabled')) {
- return '';
- }
- const fingerprintLocation =
- loadTimeData.getInteger('fingerprintReaderLocation');
- switch (fingerprintLocation) {
- case settings.FingerprintLocation.TABLET_POWER_BUTTON:
- return '';
- case settings.FingerprintLocation.KEYBOARD_TOP_RIGHT:
- return 'fingerprint-scanner-laptop-top-right';
- case settings.FingerprintLocation.KEYBOARD_BOTTOM_RIGHT:
- return 'fingerprint-scanner-laptop-bottom-right';
- }
- assertNotReached();
- },
- readOnly: true,
- },
-
- /**
- * True lottie animation file should be used instead of a png animation
- * image sequence.
- * @private {boolean}
- */
- shouldUseLottieAnimation_: {
- type: Boolean,
- value: function() {
- if (!loadTimeData.getBoolean('fingerprintUnlockEnabled')) {
- return false;
- }
-
- const fingerprintLocation =
- loadTimeData.getInteger('fingerprintReaderLocation');
- const isTabletPowerButton =
- settings.FingerprintLocation.TABLET_POWER_BUTTON ==
- fingerprintLocation;
- return isTabletPowerButton;
- },
- readOnly: true,
- }
- },
-
- /**
- * A message shows after the user has not scanned a finger during setup. This
- * is the set timeout id.
- * @type {number}
- * @private
- */
- tapSensorMessageTimeoutId_: 0,
-
- /** @private {?settings.FingerprintBrowserProxy}*/
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'on-fingerprint-scan-received', this.onScanReceived_.bind(this));
- this.browserProxy_ = settings.FingerprintBrowserProxyImpl.getInstance();
-
- this.$.arc.reset();
- this.browserProxy_.startEnroll(this.authToken);
- this.$.dialog.showModal();
- },
-
- /**
- * Closes the dialog.
- */
- close: function() {
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
-
- // Note: Reset resets |step_| back to the default, so handle anything that
- // checks |step_| before resetting.
- if (this.step_ != settings.FingerprintSetupStep.READY) {
- this.browserProxy_.cancelCurrentEnroll();
- }
-
- this.reset_();
- },
-
- /** private */
- clearSensorMessageTimeout_: function() {
- if (this.tapSensorMessageTimeoutId_ != 0) {
- clearTimeout(this.tapSensorMessageTimeoutId_);
- this.tapSensorMessageTimeoutId_ = 0;
- }
- },
-
- /**
- * Resets the dialog to its start state. Call this when the dialog gets
- * closed.
- * @private
- */
- reset_: function() {
- this.step_ = settings.FingerprintSetupStep.LOCATE_SCANNER;
- this.percentComplete_ = 0;
- this.clearSensorMessageTimeout_();
- },
-
- /**
- * Closes the dialog.
- * @private
- */
- onClose_: function() {
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
- },
-
- /**
- * Advances steps, shows problems and animates the progress as needed based on
- * scan results.
- * @param {!settings.FingerprintScan} scan
- * @private
- */
- onScanReceived_: function(scan) {
- switch (this.step_) {
- case settings.FingerprintSetupStep.LOCATE_SCANNER:
- this.$.arc.reset();
- this.step_ = settings.FingerprintSetupStep.MOVE_FINGER;
- this.percentComplete_ = scan.percentComplete;
- this.setProblem_(scan.result);
- break;
- case settings.FingerprintSetupStep.MOVE_FINGER:
- if (scan.isComplete) {
- this.problemMessage_ = '';
- this.step_ = settings.FingerprintSetupStep.READY;
- this.clearSensorMessageTimeout_();
- this.fire('add-fingerprint');
- } else {
- this.setProblem_(scan.result);
- }
- this.percentComplete_ = scan.percentComplete;
- break;
- case settings.FingerprintSetupStep.READY:
- break;
- default:
- assertNotReached();
- break;
- }
- },
-
- /**
- * Sets the instructions based on which phase of the fingerprint setup we are
- * on.
- * @param {!settings.FingerprintSetupStep} step The current step the
- * fingerprint setup is on.
- * @param {string} problemMessage Message for the scan result.
- * @private
- */
- getInstructionMessage_: function(step, problemMessage) {
- switch (step) {
- case settings.FingerprintSetupStep.LOCATE_SCANNER:
- return this.i18n('configureFingerprintInstructionLocateScannerStep');
- case settings.FingerprintSetupStep.MOVE_FINGER:
- return problemMessage;
- case settings.FingerprintSetupStep.READY:
- return this.i18n('configureFingerprintInstructionReadyStep');
- }
- assertNotReached();
- },
-
- /**
- * Set the problem message based on the result from the fingerprint scanner.
- * @param {!settings.FingerprintResultType} scanResult The result the
- * fingerprint scanner gives.
- * @private
- */
- setProblem_: function(scanResult) {
- this.clearSensorMessageTimeout_();
- switch (scanResult) {
- case settings.FingerprintResultType.SUCCESS:
- this.problemMessage_ = '';
- this.tapSensorMessageTimeoutId_ = setTimeout(() => {
- this.problemMessage_ = this.i18n('configureFingerprintLiftFinger');
- }, SHOW_TAP_SENSOR_MESSAGE_DELAY_MS);
- break;
- case settings.FingerprintResultType.PARTIAL:
- case settings.FingerprintResultType.INSUFFICIENT:
- case settings.FingerprintResultType.SENSOR_DIRTY:
- case settings.FingerprintResultType.TOO_SLOW:
- case settings.FingerprintResultType.TOO_FAST:
- this.problemMessage_ = this.i18n('configureFingerprintTryAgain');
- break;
- case settings.FingerprintResultType.IMMOBILE:
- this.problemMessage_ = this.i18n('configureFingerprintImmobile');
- break;
- default:
- assertNotReached();
- break;
- }
- },
-
- /**
- * Displays the text of the close button based on which phase of the
- * fingerprint setup we are on.
- * @param {!settings.FingerprintSetupStep} step The current step the
- * fingerprint setup is on.
- * @private
- */
- getCloseButtonText_: function(step) {
- if (step == settings.FingerprintSetupStep.READY) {
- return this.i18n('done');
- }
-
- return this.i18n('cancel');
- },
-
- /**
- * @param {!settings.FingerprintSetupStep} step
- * @private
- */
- getCloseButtonClass_: function(step) {
- if (step == settings.FingerprintSetupStep.READY) {
- return 'action-button';
- }
-
- return 'cancel-button';
- },
-
- /**
- * @param {!settings.FingerprintSetupStep} step
- * @param {boolean} allowAddAnotherFinger
- * @private
- */
- hideAddAnother_: function(step, allowAddAnotherFinger) {
- return step != settings.FingerprintSetupStep.READY ||
- !allowAddAnotherFinger;
- },
-
- /**
- * Enrolls the finished fingerprint and sets the dialog back to step one to
- * prepare to enroll another fingerprint.
- * @private
- */
- onAddAnotherFingerprint_: function() {
- this.reset_();
- this.$.arc.reset();
- this.step_ = settings.FingerprintSetupStep.MOVE_FINGER;
- this.browserProxy_.startEnroll(this.authToken);
- },
-
- /**
- * Whether scanner location should be shown at the current step.
- * @private
- */
- showScannerLocation_: function() {
- return this.step_ == settings.FingerprintSetupStep.LOCATE_SCANNER;
- },
-
- /**
- * Whether fingerprint progress circle should be shown at the current step.
- * @private
- */
- showArc_: function() {
- return this.step_ == settings.FingerprintSetupStep.MOVE_FINGER ||
- this.step_ == settings.FingerprintSetupStep.READY;
- },
-
- /**
- * Observer for percentComplete_.
- * @private
- */
- onProgressChanged_: function(newValue, oldValue) {
- // Start a new enrollment, so reset all enrollment related states.
- if (newValue === 0) {
- this.$.arc.reset();
- return;
- }
-
- this.$.arc.setProgress(oldValue, newValue, newValue === 100);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.html b/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.html
deleted file mode 100644
index 093bace2c14..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-setup-pin-dialog">
- <template>
- <style include="settings-shared">
- #pinKeyboardDiv {
- justify-content: center;
- };
-
- #pinKeyboard {
- --cr-input-placeholder-letter-spacing: normal;
- }
- </style>
- <cr-dialog id="dialog" on-close="close"
- close-text="$i18n{close}">
- <div slot="title">[[getTitleMessage_(isConfirmStep_)]]</div>
- <div slot="body">
- <!-- PIN keyboard -->
- <div id="pinKeyboardDiv" class="settings-box continuation">
- <setup-pin-keyboard id="pinKeyboard"
- enable-submit="{{enableSubmit_}}"
- is-confirm-step="{{isConfirmStep_}}"
- on-pin-submit="onPinSubmit_"
- on-set-pin-done="onSetPinDone_"
- set-modes="{{setModes}}"
- quick-unlock-private="[[quickUnlockPrivate]]"
- write-uma="[[writeUma_]]"
- enable-placeholder>
- </setup-pin-keyboard>
- </div>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onPinSubmit_"
- disabled$="[[!enableSubmit_]]">
- <span>[[getContinueMessage_(isConfirmStep_)]]</span>
- </cr-button>
- </div>
- </cr-dialog>
- </template>
-
- <script src="setup_pin_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.js b/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.js
deleted file mode 100644
index 83e44e8ed17..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.js
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-setup-pin-dialog' is the settings page for choosing a PIN.
- *
- * Example:
- * * <settings-setup-pin-dialog set-modes="[[quickUnlockSetModes]]">
- * </settings-setup-pin-dialog>
- */
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'settings-setup-pin-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * Reflects property set in password_prompt_dialog.js.
- * @type {?Object}
- */
- setModes: {
- type: Object,
- notify: true,
- },
-
- /**
- * Should the step-specific submit button be displayed?
- * @private
- */
- enableSubmit_: Boolean,
-
- /**
- * The current step/subpage we are on.
- * @private
- */
- isConfirmStep_: {type: Boolean, value: false},
-
- /**
- * Interface for chrome.quickUnlockPrivate calls. May be overridden by
- * tests.
- * @private
- */
- quickUnlockPrivate: {type: Object, value: chrome.quickUnlockPrivate},
-
- /**
- * writeUma is a function that handles writing uma stats. It may be
- * overridden for tests.
- *
- * @type {Function}
- * @private
- */
- writeUma_: {
- type: Object,
- value: function() {
- return () => {};
- }
- },
- },
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- this.$.pinKeyboard.focus();
- },
-
- close: function() {
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
-
- this.$.pinKeyboard.resetState();
- },
-
-
- /** @private */
- onCancelTap_: function() {
- this.$.pinKeyboard.resetState();
- this.$.dialog.close();
- },
-
- /** @private */
- onPinSubmit_: function() {
- this.$.pinKeyboard.doSubmit();
- },
-
-
- /** @private */
- onSetPinDone_: function() {
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
- },
-
- /**
- * @private
- * @param {boolean} isConfirmStep
- * @return {string}
- */
- getTitleMessage_: function(isConfirmStep) {
- return this.i18n(
- isConfirmStep ? 'configurePinConfirmPinTitle' :
- 'configurePinChoosePinTitle');
- },
-
- /**
- * @private
- * @param {boolean} isConfirmStep
- * @return {string}
- */
- getContinueMessage_: function(isConfirmStep) {
- return this.i18n(isConfirmStep ? 'confirm' : 'continue');
- },
-});
-
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/signout_dialog.html b/chromium/chrome/browser/resources/settings/people_page/signout_dialog.html
deleted file mode 100644
index 74a1ee48418..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/signout_dialog.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="profile_info_browser_proxy.html">
-<link rel="import" href="sync_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-signout-dialog">
- <template>
- <style include="settings-shared">
- #dialog [slot=footer] .settings-box {
- --cr-section-padding: 0;
- }
-
- .delete-profile-warning {
- padding-bottom: 10px;
- padding-inline-end: var(--cr-section-padding);
- /* In order to line up with the checkbox text. */
- padding-inline-start: var(--cr-section-indent-padding);
- padding-top: 10px;
- }
-
- #wideFooter {
- /* Override the cr-dialog footer padding. */
- padding: 16px 0;
- }
-
- #dialog-body {
- /* Add space for the link focus ring. See https://crbug.com/916939. */
- padding-bottom: 2px;
- }
- </style>
-
- <cr-dialog id="dialog" ignore-enter-key close-text="$i18n{close}">
- <div slot="title">$i18n{syncDisconnectTitle}</div>
- <div id="dialog-body" slot="body">
- <div inner-h-t-m-l="[[
- getDisconnectExplanationHtml_(syncStatus.domain)]]">
- </div>
- </div>
- <div slot="button-container">
- <cr-button id="disconnectCancel" class="cancel-button"
- on-click="onDisconnectCancel_" >
- $i18n{cancel}
- </cr-button>
- <cr-button id="disconnectConfirm" class="action-button"
- hidden="[[syncStatus.domain]]" on-click="onDisconnectConfirm_">
- $i18n{syncDisconnect}
- </cr-button>
- <cr-button id="disconnectManagedProfileConfirm"
- class="action-button" hidden="[[!syncStatus.domain]]"
- on-click="onDisconnectConfirm_">
- $i18n{syncDisconnectConfirm}
- </cr-button>
- </div>
-<if expr="(not chromeos and is_posix) or is_win or is_macosx">
- <template is="dom-if" if="[[!syncStatus.domain]]">
- <div id="wideFooter" slot="footer">
- <div class="settings-box first">
- <cr-checkbox id="deleteProfile" class="start"
- checked="{{deleteProfile_}}">
- $i18n{syncDisconnectDeleteProfile}
- </cr-checkbox>
- <cr-expand-button expanded="{{deleteProfileWarningVisible_}}"
- alt="$i18n{deleteProfileWarningExpandA11yLabel}">
- </cr-expand-button>
- </div>
- <iron-collapse opened="[[deleteProfileWarningVisible_]]">
- <div class="delete-profile-warning">
- [[deleteProfileWarning_]]
- </div>
- </iron-collapse>
- </div>
- </template>
-</if>
- </cr-dialog>
- </template>
- <script src="signout_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/signout_dialog.js b/chromium/chrome/browser/resources/settings/people_page/signout_dialog.js
deleted file mode 100644
index e9ea0fe55e2..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/signout_dialog.js
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-signout-dialog' is a dialog that allows the
- * user to turn off sync and sign out of Chromium.
- */
-Polymer({
- is: 'settings-signout-dialog',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /**
- * The current sync status, supplied by the parent.
- * @type {?settings.SyncStatus}
- */
- syncStatus: {
- type: Object,
- observer: 'syncStatusChanged_',
- },
-
- /**
- * True if the checkbox to delete the profile has been checked.
- * @private
- */
- deleteProfile_: Boolean,
-
- /**
- * True if the profile deletion warning is visible.
- * @private
- */
- deleteProfileWarningVisible_: Boolean,
-
- /**
- * The profile deletion warning. The message indicates the number of
- * profile stats that will be deleted if a non-zero count for the profile
- * stats is returned from the browser.
- * @private
- */
- deleteProfileWarning_: String,
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'profile-stats-count-ready', this.handleProfileStatsCount_.bind(this));
- // <if expr="not chromeos">
- settings.ProfileInfoBrowserProxyImpl.getInstance().getProfileStatsCount();
- // </if>
- this.async(() => {
- this.$.dialog.showModal();
- });
- },
-
- /**
- * Returns true when the user selected 'Confirm'.
- * @return {boolean}
- */
- wasConfirmed: function() {
- return this.$.dialog.getNative().returnValue == 'success';
- },
-
- /**
- * Handler for when the profile stats count is pushed from the browser.
- * @param {number} count
- * @private
- */
- handleProfileStatsCount_: function(count) {
- const username = this.syncStatus.signedInUsername || '';
- if (count == 0) {
- this.deleteProfileWarning_ = loadTimeData.getStringF(
- 'deleteProfileWarningWithoutCounts', username);
- } else if (count == 1) {
- this.deleteProfileWarning_ = loadTimeData.getStringF(
- 'deleteProfileWarningWithCountsSingular', username);
- } else {
- this.deleteProfileWarning_ = loadTimeData.getStringF(
- 'deleteProfileWarningWithCountsPlural', count, username);
- }
- },
-
- /**
- * Polymer observer for syncStatus.
- * @private
- */
- syncStatusChanged_: function() {
- if (!this.syncStatus.signedIn && this.$.dialog.open) {
- this.$.dialog.close();
- }
- },
-
- /**
- * @private
- * @param {string} domain
- * @return {string}
- */
- getDisconnectExplanationHtml_: function(domain) {
- // <if expr="not chromeos">
- if (domain) {
- return loadTimeData.getStringF(
- 'syncDisconnectManagedProfileExplanation',
- '<span id="managed-by-domain-name">' + domain + '</span>');
- }
- // </if>
- return loadTimeData.getString('syncDisconnectExplanation');
- },
-
- /** @private */
- onDisconnectCancel_: function() {
- this.$.dialog.cancel();
- },
-
- /** @private */
- onDisconnectConfirm_: function() {
- this.$.dialog.close();
- const deleteProfile = !!this.syncStatus.domain || this.deleteProfile_;
- settings.SyncBrowserProxyImpl.getInstance().signOut(deleteProfile);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_account_control.html b/chromium/chrome/browser/resources/settings/people_page/sync_account_control.html
deleted file mode 100644
index e299214c6b6..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_account_control.html
+++ /dev/null
@@ -1,245 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="profile_info_browser_proxy.html">
-<link rel="import" href="sync_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-sync-account-control">
- <template>
- <style include="settings-shared">
- :host {
- --shown-avatar-size: 40px;
- --sync-icon-border-size: 2px;
- --sync-icon-size: 16px;
- }
-
- setting-box.middle {
- /* Per spec, middle text is indented 20px in this section. */
- margin-inline-start: 20px;
- }
-
- .account-icon {
- border-radius: 20px;
- flex-shrink: 0;
- height: var(--shown-avatar-size);
- width: var(--shown-avatar-size);
- }
-
- .account-icon.small {
- height: 20px;
- width: 20px;
- }
-
- #menu .dropdown-item {
- padding: 12px;
- }
-
- #menu .dropdown-item span {
- margin-inline-start: 8px;
- }
-
- .flex {
- display: flex;
- flex: 1;
- flex-direction: column;
- }
-
- #avatar-container {
- height: var(--shown-avatar-size);
- position: relative;
- }
-
- #sync-icon-container {
- align-items: center;
- background: var(--google-green-700);
- border: var(--sync-icon-border-size) solid white;
- border-radius: 50%;
- display: flex;
- height: var(--sync-icon-size);
- position: absolute;
- right: -6px;
- top: calc(var(--shown-avatar-size) - var(--sync-icon-size) -
- var(--sync-icon-border-size));
- width: var(--sync-icon-size);
- }
-
- :host-context([dir='rtl']) #sync-icon-container {
- left: -6px;
- right: initial;
- }
-
- @media (prefers-color-scheme: dark) {
- #sync-icon-container {
- background: var(--google-green-refresh-300);
- border-color: var(--google-grey-900);
- }
- }
-
- #sync-icon-container.sync-problem {
- background: var(--settings-error-color);
- }
-
- #sync-icon-container.sync-paused {
- background: var(--google-blue-500);
- }
-
- @media (prefers-color-scheme: dark) {
- #sync-icon-container.sync-paused {
- background: var(--google-blue-refresh-300);
- }
- }
-
- #sync-icon-container.sync-disabled {
- background: var(--google-grey-400);
- }
-
- @media (prefers-color-scheme: dark) {
- #sync-icon-container.sync-disabled {
- background: var(--google-grey-refresh-500);
- }
- }
-
- #sync-icon-container iron-icon {
- fill: white; /* Same in light and dark modes. */
- height: 12px;
- margin: auto;
- width: 12px;
- }
-
- #sign-in {
- margin: auto 8px;
- min-width: 100px;
- }
-
- #banner {
- background: url(../images/sync_banner.svg) no-repeat;
- background-size: 100% auto;
- display: none;
- padding-top: calc(120 / 680 * 100%); /* Keep background ratio. */
- }
-
- @media (prefers-color-scheme: dark) {
- #banner {
- background-image: url(../images/sync_banner_dark.svg);
- }
- }
-
- :host([showing-promo]) #banner {
- display: block;
- }
- </style>
- <div id="banner" hidden="[[syncStatus.signedIn]]" part="banner"></div>
- <div class$="settings-box first
- [[getPromoHeaderClass_(subLabel_)]]"
- id="promo-header" hidden="[[syncStatus.signedIn]]">
- <div class="start settings-box-text">
- <div id="promo-title" part="title">
- [[getLabel_(promoLabelWithAccount,
- promoLabelWithNoAccount, shownAccount_)]]
- </div>
- <div class="secondary">[[subLabel_]]</div>
- </div>
- <cr-button class="action-button" on-click="onSigninTap_"
- disabled="[[syncStatus.firstSetupInProgress]]" id="sign-in"
- hidden="[[shouldShowAvatarRow_]]">
- $i18n{peopleSignIn}
- </cr-button>
- </div>
- <template is="dom-if" if="[[shouldShowAvatarRow_]]">
- <div class="settings-box first two-line" id="avatar-row">
- <div id="avatar-container">
- <img class="account-icon" alt=""
- src="[[getAccountImageSrc_(shownAccount_.avatarImage)]]">
- <div id="sync-icon-container" hidden="[[!syncStatus.signedIn]]"
- class$="[[getSyncIconStyle_(
- syncStatus.hasError, syncStatus.statusAction,
- syncStatus.disabled)]]">
- <iron-icon icon$="[[getSyncIcon_(
- syncStatus.hasError, syncStatus.statusAction,
- syncStatus.disabled)]]"></iron-icon>
- </div>
- </div>
- <div class="middle two-line no-min-width">
- <div class="flex text-elide settings-box-text" id="user-info">
- <span>
- [[getAvatarRowTitle_(shownAccount_.fullName,
- '$i18nPolymer{syncNotWorking}', '$i18nPolymer{syncPaused}',
- '$i18nPolymer{syncDisabled}', syncStatus.hasError,
- syncStatus.statusAction, syncStatus.disabled)]]
- </span>
- <div class="secondary">
- [[getAccountLabel_(
- '$i18nPolymer{syncingTo}', shownAccount_.email,
- syncStatus.hasError, syncStatus.signedIn,
- syncStatus.disabled, syncStatus.firstSetupInProgress,
- unifiedConsentEnabled)]]
- </div>
- </div>
- </div>
- <cr-icon-button class="icon-arrow-dropdown"
- hidden="[[syncStatus.signedIn]]" on-click="onMenuButtonTap_"
- id="dropdown-arrow" aria-label="$i18n{useAnotherAccount}">
- </cr-icon-button>
- <div class="separator" hidden="[[syncStatus.signedIn]]"></div>
- <cr-button id="sync-button" class="action-button"
- hidden="[[syncStatus.signedIn]]" on-click="onSyncButtonTap_"
- disabled="[[syncStatus.firstSetupInProgress]]">
- $i18n{peopleSignIn}
- </cr-button>
- <cr-button id="turn-off"
- hidden="[[!shouldShowTurnOffButton_(syncStatus.signedIn,
- showSetupButtons_)]]"
- on-click="onTurnOffButtonTap_"
- disabled="[[syncStatus.firstSetupInProgress]]">
- $i18n{turnOffSync}
- </cr-button>
- <cr-button id="sync-error-button" class="action-button"
- hidden="[[!shouldShowErrorActionButton_(syncStatus,
- showSetupButtons_)]]"
- on-click="onErrorButtonTap_"
- disabled="[[syncStatus.firstSetupInProgress]]">
- [[syncStatus.statusActionText]]
- </cr-button>
- <div id="setup-buttons" hidden="[[!showSetupButtons_]]">
- <cr-button on-click="onSetupCancel_">$i18n{cancel}</cr-button>
- <cr-button class="action-button" on-click="onSetupConfirm_">
- $i18n{confirm}
- </cr-button>
- </div>
- </div>
- <template is="dom-if" if="[[!syncStatus.signedIn]]" restamp>
- <cr-action-menu id="menu" auto-reposition>
- <template is="dom-repeat" items="[[storedAccounts_]]">
- <button class="dropdown-item" on-click="onAccountTap_">
- <img class="account-icon small" alt=""
- src="[[getAccountImageSrc_(item.avatarImage)]]">
- <span>[[item.email]]</span>
- </button>
- </template>
- <button class="dropdown-item" on-click="onSigninTap_"
- disabled="[[syncStatus.firstSetupInProgress]]" id="sign-in-item">
- <img class="account-icon small" alt=""
- src="chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE">
- <span>$i18n{useAnotherAccount}</span>
- </button>
- <button class="dropdown-item" on-click="onSignoutTap_"
- disabled="[[syncStatus.firstSetupInProgress]]" id="sign-out-item">
- <iron-icon icon="settings:exit-to-app"></iron-icon>
- <span>$i18n{peopleSignOut}</span>
- </button>
- </cr-action-menu>
- </template>
- </template>
- </template>
- <script src="sync_account_control.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_account_control.js b/chromium/chrome/browser/resources/settings/people_page/sync_account_control.js
deleted file mode 100644
index eb455364aff..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_account_control.js
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-/**
- * @fileoverview
- * 'settings-sync-account-section' is the settings page containing sign-in
- * settings.
- */
-cr.exportPath('settings');
-
-/** @const {number} */
-settings.MAX_SIGNIN_PROMO_IMPRESSION = 10;
-
-Polymer({
- is: 'settings-sync-account-control',
- behaviors: [WebUIListenerBehavior],
- properties: {
- /**
- * The current sync status, supplied by parent element.
- * @type {!settings.SyncStatus}
- */
- syncStatus: Object,
-
- // String to be used as a title when the promo has an account.
- promoLabelWithAccount: String,
-
- // String to be used as title of the promo has no account.
- promoLabelWithNoAccount: String,
-
- // String to be used as a subtitle when the promo has an account.
- promoSecondaryLabelWithAccount: String,
-
- // String to be used as subtitle of the promo has no account.
- promoSecondaryLabelWithNoAccount: String,
-
- /**
- * Proxy variable for syncStatus.signedIn to shield observer from being
- * triggered multiple times whenever syncStatus changes.
- * @private {boolean}
- */
- signedIn_: {
- type: Boolean,
- computed: 'computeSignedIn_(syncStatus.signedIn)',
- observer: 'onSignedInChanged_',
- },
-
- /** @private {!Array<!settings.StoredAccount>} */
- storedAccounts_: Object,
-
- /** @private {?settings.StoredAccount} */
- shownAccount_: Object,
-
- showingPromo: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
-
- // This property should be set by the parent only and should not change
- // after the element is created.
- embeddedInSubpage: {
- type: Boolean,
- reflectToAttribute: true,
- },
-
- // This property should be set by the parent only and should not change
- // after the element is created.
- hideButtons: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
-
- /** @private {boolean} */
- shouldShowAvatarRow_: {
- type: Boolean,
- value: false,
- computed: 'computeShouldShowAvatarRow_(storedAccounts_, syncStatus,' +
- 'storedAccounts_.length, syncStatus.signedIn)',
- observer: 'onShouldShowAvatarRowChange_',
- },
-
- /** @private */
- subLabel_: {
- type: String,
- computed: 'computeSubLabel_(promoSecondaryLabelWithAccount,' +
- 'promoSecondaryLabelWithNoAccount, shownAccount_)',
- },
-
- unifiedConsentEnabled: Boolean,
-
- /** @private */
- showSetupButtons_: {
- type: Boolean,
- computed: 'computeShowSetupButtons_(unifiedConsentEnabled,' +
- 'hideButtons, syncStatus.firstSetupInProgress)',
- },
- },
-
- observers: [
- 'onShownAccountShouldChange_(storedAccounts_, syncStatus)',
- ],
-
- /** @private {?settings.SyncBrowserProxy} */
- syncBrowserProxy_: null,
-
- created: function() {
- this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.syncBrowserProxy_.getStoredAccounts().then(
- this.handleStoredAccounts_.bind(this));
- this.addWebUIListener(
- 'stored-accounts-updated', this.handleStoredAccounts_.bind(this));
- },
-
- /**
- * Records the following user actions:
- * - Signin_Impression_FromSettings and
- * - Signin_ImpressionWithAccount_FromSettings
- * - Signin_ImpressionWithNoAccount_FromSettings
- * @private
- */
- recordImpressionUserActions_: function() {
- assert(!this.syncStatus.signedIn);
- assert(this.shownAccount_ !== undefined);
-
- chrome.metricsPrivate.recordUserAction('Signin_Impression_FromSettings');
- if (this.shownAccount_) {
- chrome.metricsPrivate.recordUserAction(
- 'Signin_ImpressionWithAccount_FromSettings');
- } else {
- chrome.metricsPrivate.recordUserAction(
- 'Signin_ImpressionWithNoAccount_FromSettings');
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeSignedIn_: function() {
- return !!this.syncStatus && !!this.syncStatus.signedIn;
- },
-
- /** @private */
- onSignedInChanged_: function() {
- if (this.embeddedInSubpage) {
- this.showingPromo = true;
- return;
- }
-
- if (!this.showingPromo && !this.syncStatus.signedIn &&
- this.syncBrowserProxy_.getPromoImpressionCount() <
- settings.MAX_SIGNIN_PROMO_IMPRESSION) {
- this.showingPromo = true;
- this.syncBrowserProxy_.incrementPromoImpressionCount();
- } else {
- // Turn off the promo if the user is signed in.
- this.showingPromo = false;
- }
- if (!this.syncStatus.signedIn && this.shownAccount_ !== undefined) {
- this.recordImpressionUserActions_();
- }
- },
-
- /**
- * @param {string} labelWithAccount
- * @param {string} labelWithNoAccount
- * @return {string}
- * @private
- */
- getLabel_: function(labelWithAccount, labelWithNoAccount) {
- return this.shownAccount_ ? labelWithAccount : labelWithNoAccount;
- },
-
- /**
- * @return {string}
- * @private
- */
- computeSubLabel_: function() {
- return this.getLabel_(this.promoSecondaryLabelWithAccount,
- this.promoSecondaryLabelWithNoAccount);
- },
-
- /**
- * @return {string}
- * @private
- */
- getPromoHeaderClass_: function() {
- return this.subLabel_ ? 'two-line' : '';
- },
-
- /**
- * @param {string} label
- * @param {string} name
- * @return {string}
- * @private
- */
- getSubstituteLabel_: function(label, name) {
- return loadTimeData.substituteString(label, name);
- },
-
- /**
- * @param {string} label
- * @param {string} account
- * @return {string}
- * @private
- */
- getAccountLabel_: function(label, account) {
- if (this.unifiedConsentEnabled && this.syncStatus.firstSetupInProgress) {
- return this.syncStatus.statusText || account;
- }
- return this.syncStatus.signedIn && !this.syncStatus.hasError &&
- !this.syncStatus.disabled ?
- loadTimeData.substituteString(label, account) :
- account;
- },
-
- /**
- * @param {?string} image
- * @return {string}
- * @private
- */
- getAccountImageSrc_: function(image) {
- // image can be undefined if the account has not set an avatar photo.
- return image || 'chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE';
- },
-
- /**
- * Returns the class of the sync icon.
- * @return {string}
- * @private
- */
- getSyncIconStyle_: function() {
- if (this.syncStatus.hasUnrecoverableError) {
- return 'sync-problem';
- }
- if (this.syncStatus.hasError) {
- return this.syncStatus.statusAction ==
- settings.StatusAction.REAUTHENTICATE ?
- 'sync-paused' :
- 'sync-problem';
- }
- if (this.syncStatus.disabled) {
- return 'sync-disabled';
- }
- return 'sync';
- },
-
- /**
- * Returned value must match one of iron-icon's settings:(*) icon name.
- * @return {string}
- * @private
- */
- getSyncIcon_: function() {
- switch (this.getSyncIconStyle_()) {
- case 'sync-problem':
- return 'settings:sync-problem';
- case 'sync-paused':
- return 'settings:sync-disabled';
- default:
- return 'cr:sync';
- }
- },
-
- /**
- * @return {string}
- * @private
- */
- getAvatarRowTitle_: function(
- accountName, syncErrorLabel, authErrorLabel, disabledLabel) {
- switch (this.getSyncIconStyle_()) {
- case 'sync-problem':
- return syncErrorLabel;
- case 'sync-paused':
- return authErrorLabel;
- case 'sync-disabled':
- return disabledLabel;
- default:
- return accountName;
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowTurnOffButton_: function() {
- return !this.hideButtons && !this.showSetupButtons_ &&
- !!this.syncStatus.signedIn;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowErrorActionButton_: function() {
- if (this.embeddedInSubpage &&
- this.syncStatus.statusAction ==
- settings.StatusAction.ENTER_PASSPHRASE) {
- // In a subpage the passphrase button is not required.
- return false;
- }
- return !this.hideButtons && !this.showSetupButtons_ &&
- !!this.syncStatus.signedIn && !!this.syncStatus.hasError &&
- this.syncStatus.statusAction != settings.StatusAction.NO_ACTION;
- },
-
- /**
- * @param {!Array<!settings.StoredAccount>} accounts
- * @private
- */
- handleStoredAccounts_: function(accounts) {
- this.storedAccounts_ = accounts;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeShouldShowAvatarRow_: function() {
- if (this.storedAccounts_ === undefined || this.syncStatus === undefined) {
- return false;
- }
-
- return this.syncStatus.signedIn || this.storedAccounts_.length > 0;
- },
-
- /** @private */
- onErrorButtonTap_: function() {
- switch (this.syncStatus.statusAction) {
- case settings.StatusAction.REAUTHENTICATE:
- this.syncBrowserProxy_.startSignIn();
- break;
- case settings.StatusAction.SIGNOUT_AND_SIGNIN:
- if (this.syncStatus.domain) {
- settings.navigateTo(settings.routes.SIGN_OUT);
- } else {
- // Silently sign the user out without deleting their profile and
- // prompt them to sign back in.
- this.syncBrowserProxy_.signOut(false);
- this.syncBrowserProxy_.startSignIn();
- }
- break;
- case settings.StatusAction.UPGRADE_CLIENT:
- settings.navigateTo(settings.routes.ABOUT);
- break;
- case settings.StatusAction.ENTER_PASSPHRASE:
- case settings.StatusAction.CONFIRM_SYNC_SETTINGS:
- default:
- settings.navigateTo(settings.routes.SYNC);
- }
- },
-
- /** @private */
- onSigninTap_: function() {
- this.syncBrowserProxy_.startSignIn();
- // Need to close here since one menu item also triggers this function.
- if (this.$$('#menu')) {
- /** @type {!CrActionMenuElement} */ (this.$$('#menu')).close();
- }
- },
-
- /** @private */
- onSignoutTap_: function() {
- this.syncBrowserProxy_.signOut(false /* deleteProfile */);
- /** @type {!CrActionMenuElement} */ (this.$$('#menu')).close();
- },
-
- /** @private */
- onSyncButtonTap_: function() {
- assert(this.shownAccount_);
- assert(this.storedAccounts_.length > 0);
- const isDefaultPromoAccount =
- (this.shownAccount_.email == this.storedAccounts_[0].email);
-
- this.syncBrowserProxy_.startSyncingWithEmail(
- this.shownAccount_.email, isDefaultPromoAccount);
- },
-
- /** @private */
- onTurnOffButtonTap_: function() {
- /* This will route to people_page's disconnect dialog. */
- settings.navigateTo(settings.routes.SIGN_OUT);
- },
-
- /** @private */
- onMenuButtonTap_: function() {
- const actionMenu =
- /** @type {!CrActionMenuElement} */ (this.$$('#menu'));
- actionMenu.showAt(assert(this.$$('#dropdown-arrow')));
- },
-
- /** @private */
- onShouldShowAvatarRowChange_: function() {
- // Close dropdown when avatar-row hides, so if it appears again, the menu
- // won't be open by default.
- const actionMenu = this.$$('#menu');
- if (!this.shouldShowAvatarRow_ && actionMenu && actionMenu.open) {
- actionMenu.close();
- }
- },
-
- /**
- * @param {!{model:
- * !{item: !settings.StoredAccount},
- * }} e
- * @private
- */
- onAccountTap_: function(e) {
- this.shownAccount_ = e.model.item;
- /** @type {!CrActionMenuElement} */ (this.$$('#menu')).close();
- },
-
- /** @private */
- onShownAccountShouldChange_: function() {
- if (this.storedAccounts_ === undefined || this.syncStatus === undefined) {
- return;
- }
-
- if (this.syncStatus.signedIn) {
- for (let i = 0; i < this.storedAccounts_.length; i++) {
- if (this.storedAccounts_[i].email == this.syncStatus.signedInUsername) {
- this.shownAccount_ = this.storedAccounts_[i];
- return;
- }
- }
- } else {
- const firstStoredAccount =
- (this.storedAccounts_.length > 0) ? this.storedAccounts_[0] : null;
-
- // Sign-in impressions should be recorded in the following cases:
- // 1. When the promo is first shown, i.e. when |shownAccount_| is
- // initialized;
- // 2. When the impression account state changes, i.e. promo impression
- // state changes (WithAccount -> WithNoAccount) or
- // (WithNoAccount -> WithAccount).
- const shouldRecordImpression = (this.shownAccount_ === undefined) ||
- (!this.shownAccount_ && firstStoredAccount) ||
- (this.shownAccount_ && !firstStoredAccount);
-
- this.shownAccount_ = firstStoredAccount;
-
- if (shouldRecordImpression) {
- this.recordImpressionUserActions_();
- }
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeShowSetupButtons_: function() {
- return !this.hideButtons && !!this.unifiedConsentEnabled &&
- !!this.syncStatus.firstSetupInProgress;
- },
-
- /** @private */
- onSetupCancel_: function() {
- this.fire('sync-setup-done', false);
- },
-
- /** @private */
- onSetupConfirm_: function() {
- this.fire('sync-setup-done', true);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.html b/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.html
deleted file mode 100644
index 1770408e3bc..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="sync_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.js b/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
deleted file mode 100644
index 340398c2a47..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the the People section to get the
- * status of the sync backend and user preferences on what data to sync. Used
- * for both Chrome browser and ChromeOS.
- */
-cr.exportPath('settings');
-
-/**
- * @typedef {{fullName: (string|undefined),
- * givenName: (string|undefined),
- * email: string,
- * avatarImage: (string|undefined)}}
- * @see chrome/browser/ui/webui/settings/people_handler.cc
- */
-settings.StoredAccount;
-
-/**
- * @typedef {{childUser: (boolean|undefined),
- * disabled: (boolean|undefined),
- * domain: (string|undefined),
- * hasError: (boolean|undefined),
- * hasUnrecoverableError: (boolean|undefined),
- * managed: (boolean|undefined),
- * firstSetupInProgress: (boolean|undefined),
- * signedIn: (boolean|undefined),
- * signedInUsername: (string|undefined),
- * signinAllowed: (boolean|undefined),
- * statusAction: (!settings.StatusAction),
- * statusActionText: (string|undefined),
- * statusText: (string|undefined),
- * supervisedUser: (boolean|undefined),
- * syncSystemEnabled: (boolean|undefined)}}
- * @see chrome/browser/ui/webui/settings/people_handler.cc
- */
-settings.SyncStatus;
-
-
-/**
- * Must be kept in sync with the return values of getSyncErrorAction in
- * chrome/browser/ui/webui/settings/people_handler.cc
- * @enum {string}
- */
-settings.StatusAction = {
- NO_ACTION: 'noAction', // No action to take.
- REAUTHENTICATE: 'reauthenticate', // User needs to reauthenticate.
- SIGNOUT_AND_SIGNIN:
- 'signOutAndSignIn', // User needs to sign out and sign in.
- UPGRADE_CLIENT: 'upgradeClient', // User needs to upgrade the client.
- ENTER_PASSPHRASE: 'enterPassphrase', // User needs to enter passphrase.
- CONFIRM_SYNC_SETTINGS:
- 'confirmSyncSettings', // User needs to confirm sync settings.
-};
-
-/**
- * The state of sync. This is the data structure sent back and forth between
- * C++ and JS. Its naming and structure is not optimal, but changing it would
- * require changes to the C++ handler, which is already functional.
- * @typedef {{
- * appsEnforced: boolean,
- * appsRegistered: boolean,
- * appsSynced: boolean,
- * autofillEnforced: boolean,
- * autofillRegistered: boolean,
- * autofillSynced: boolean,
- * bookmarksEnforced: boolean,
- * bookmarksRegistered: boolean,
- * bookmarksSynced: boolean,
- * encryptAllData: boolean,
- * encryptAllDataAllowed: boolean,
- * enterPassphraseBody: (string|undefined),
- * extensionsEnforced: boolean,
- * extensionsRegistered: boolean,
- * extensionsSynced: boolean,
- * fullEncryptionBody: string,
- * passphrase: (string|undefined),
- * passphraseRequired: boolean,
- * passwordsEnforced: boolean,
- * passwordsRegistered: boolean,
- * passwordsSynced: boolean,
- * paymentsIntegrationEnabled: boolean,
- * preferencesEnforced: boolean,
- * preferencesRegistered: boolean,
- * preferencesSynced: boolean,
- * setNewPassphrase: (boolean|undefined),
- * syncAllDataTypes: boolean,
- * tabsEnforced: boolean,
- * tabsRegistered: boolean,
- * tabsSynced: boolean,
- * themesEnforced: boolean,
- * themesRegistered: boolean,
- * themesSynced: boolean,
- * typedUrlsEnforced: boolean,
- * typedUrlsRegistered: boolean,
- * typedUrlsSynced: boolean,
- * wifiConfigurationsEnforced: boolean,
- * wifiConfigurationsRegistered: boolean,
- * wifiConfigurationsSynced: boolean,
- * }}
- */
-settings.SyncPrefs;
-
-/**
- * @enum {string}
- */
-settings.PageStatus = {
- SPINNER: 'spinner', // Before the page has loaded.
- CONFIGURE: 'configure', // Preferences ready to be configured.
- TIMEOUT: 'timeout', // Preferences loading has timed out.
- DONE: 'done', // Sync subpage can be closed now.
- PASSPHRASE_FAILED: 'passphraseFailed', // Error in the passphrase.
-};
-
-cr.define('settings', function() {
- /**
- * Key to be used with localStorage.
- * @type {string}
- */
- const PROMO_IMPRESSION_COUNT_KEY = 'signin-promo-count';
-
- /** @interface */
- class SyncBrowserProxy {
- // <if expr="not chromeos">
- /**
- * Starts the signin process for the user. Does nothing if the user is
- * already signed in.
- */
- startSignIn() {}
-
- /**
- * Signs out the signed-in user.
- * @param {boolean} deleteProfile
- */
- signOut(deleteProfile) {}
-
- /**
- * Invalidates the Sync token without signing the user out.
- */
- pauseSync() {}
-
- /**
- * @return {number} the number of times the sync account promo was shown.
- */
- getPromoImpressionCount() {}
-
- /**
- * Increment the number of times the sync account promo was shown.
- */
- incrementPromoImpressionCount() {}
-
- // </if>
-
- // <if expr="chromeos">
- /**
- * Signs the user out.
- */
- attemptUserExit() {}
-
- // </if>
-
- /**
- * Gets the current sync status.
- * @return {!Promise<!settings.SyncStatus>}
- */
- getSyncStatus() {}
-
- /**
- * Gets a list of stored accounts.
- * @return {!Promise<!Array<!settings.StoredAccount>>}
- */
- getStoredAccounts() {}
-
- /**
- * Function to invoke when the sync page has been navigated to. This
- * registers the UI as the "active" sync UI so that if the user tries to
- * open another sync UI, this one will be shown instead.
- */
- didNavigateToSyncPage() {}
-
- /**
- * Function to invoke when leaving the sync page so that the C++ layer can
- * be notified that the sync UI is no longer open.
- * @param {boolean} didAbort
- */
- didNavigateAwayFromSyncPage(didAbort) {}
-
- /**
- * Sets which types of data to sync.
- * @param {!settings.SyncPrefs} syncPrefs
- * @return {!Promise<!settings.PageStatus>}
- */
- setSyncDatatypes(syncPrefs) {}
-
- /**
- * Sets the sync encryption options.
- * @param {!settings.SyncPrefs} syncPrefs
- * @return {!Promise<!settings.PageStatus>}
- */
- setSyncEncryption(syncPrefs) {}
-
- /**
- * Start syncing with an account, specified by its email.
- * |isDefaultPromoAccount| is true if |email| is the email of the default
- * account displayed in the promo.
- * @param {string} email
- * @param {boolean} isDefaultPromoAccount
- */
- startSyncingWithEmail(email, isDefaultPromoAccount) {}
-
- /**
- * Opens the Google Activity Controls url in a new tab.
- */
- openActivityControlsUrl() {}
-
- /**
- * Function to dispatch event sync-prefs-changed even without a change.
- * This is used to decide whether we should show the link to password
- * manager in passwords section on page load.
- */
- sendSyncPrefsChanged() {}
- }
-
- /**
- * @implements {settings.SyncBrowserProxy}
- */
- class SyncBrowserProxyImpl {
- // <if expr="not chromeos">
- /** @override */
- startSignIn() {
- chrome.send('SyncSetupStartSignIn');
- }
-
- /** @override */
- signOut(deleteProfile) {
- chrome.send('SyncSetupSignout', [deleteProfile]);
- }
-
- /** @override */
- pauseSync() {
- chrome.send('SyncSetupPauseSync');
- }
-
- /** @override */
- getPromoImpressionCount() {
- return parseInt(
- window.localStorage.getItem(PROMO_IMPRESSION_COUNT_KEY), 10) ||
- 0;
- }
-
- /** @override */
- incrementPromoImpressionCount() {
- window.localStorage.setItem(
- PROMO_IMPRESSION_COUNT_KEY,
- (this.getPromoImpressionCount() + 1).toString());
- }
-
- // </if>
- // <if expr="chromeos">
- /** @override */
- attemptUserExit() {
- return chrome.send('AttemptUserExit');
- }
- // </if>
-
- /** @override */
- getSyncStatus() {
- return cr.sendWithPromise('SyncSetupGetSyncStatus');
- }
-
- /** @override */
- getStoredAccounts() {
- return cr.sendWithPromise('SyncSetupGetStoredAccounts');
- }
-
- /** @override */
- didNavigateToSyncPage() {
- chrome.send('SyncSetupShowSetupUI');
- }
-
- /** @override */
- didNavigateAwayFromSyncPage(didAbort) {
- chrome.send('SyncSetupDidClosePage', [didAbort]);
- }
-
- /** @override */
- setSyncDatatypes(syncPrefs) {
- return cr.sendWithPromise(
- 'SyncSetupSetDatatypes', JSON.stringify(syncPrefs));
- }
-
- /** @override */
- setSyncEncryption(syncPrefs) {
- return cr.sendWithPromise(
- 'SyncSetupSetEncryption', JSON.stringify(syncPrefs));
- }
-
- /** @override */
- startSyncingWithEmail(email, isDefaultPromoAccount) {
- chrome.send(
- 'SyncSetupStartSyncingWithEmail', [email, isDefaultPromoAccount]);
- }
-
- /** @override */
- openActivityControlsUrl() {
- chrome.metricsPrivate.recordUserAction(
- 'Signin_AccountSettings_GoogleActivityControlsClicked');
- }
-
- /** @override */
- sendSyncPrefsChanged() {
- chrome.send('SyncPrefsDispatch');
- }
- }
-
- cr.addSingletonGetter(SyncBrowserProxyImpl);
-
- return {
- SyncBrowserProxy: SyncBrowserProxy,
- SyncBrowserProxyImpl: SyncBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_controls.html b/chromium/chrome/browser/resources/settings/people_page/sync_controls.html
deleted file mode 100644
index 85151dcf0e4..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_controls.html
+++ /dev/null
@@ -1,185 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="sync_browser_proxy.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-sync-controls">
- <template>
- <style include="settings-shared">
- #sync-data-types .list-item:not([hidden]) ~ .list-item:not([hidden]) {
- border-top: var(--cr-separator-line);
- }
-
- .list-item {
- display: flex;
- }
-
- .list-item > div {
- flex: 1;
- }
- </style>
- <div class="settings-box first">
- <div id="syncEverythingCheckboxLabel" class="start">
- $i18n{syncEverythingCheckboxLabel}
- </div>
- <cr-toggle id="syncAllDataTypesControl"
- checked="{{syncPrefs.syncAllDataTypes}}"
- on-change="onSyncAllDataTypesChanged_"
- aria-labelledby="syncEverythingCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-frame" id="sync-data-types">
- <div class="list-item" hidden="[[!syncPrefs.appsRegistered]]">
- <div id="appCheckboxLabel">
- $i18n{appCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.appsSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.appsEnforced)]]"
- aria-labelledby="appCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.bookmarksRegistered]]">
- <div id="bookmarksCheckboxLabel">
- $i18n{bookmarksCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.bookmarksSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.bookmarksEnforced)]]"
- aria-labelledby="bookmarksCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.extensionsRegistered]]">
- <div id="extensionsCheckboxLabel">
- $i18n{extensionsCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.extensionsSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.extensionsEnforced)]]"
- aria-labelledby="extensionsCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.typedUrlsRegistered]]">
- <div id="historyCheckboxLabel">
- $i18n{historyCheckboxLabel}
- </div>
- <!-- TypedUrls has a special on-change handler to deal with user
- events. -->
- <cr-toggle id="historyToggle"
- checked="{{syncPrefs.typedUrlsSynced}}"
- on-change="onTypedUrlsDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.typedUrlsEnforced)]]"
- aria-labelledby="historyCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.preferencesRegistered]]">
- <div id="settingsCheckboxLabel">
- $i18n{settingsCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.preferencesSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes,
- syncPrefs.preferencesEnforced)]]"
- aria-labelledby="settingsCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.themesRegistered]]">
- <div id="themesAndWallpapersCheckboxLabel">
- $i18n{themesAndWallpapersCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.themesSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.themesEnforced)]]"
- aria-labelledby="themesAndWallpapersCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.tabsRegistered]]">
- <div id="openTabsCheckboxLabel">
- $i18n{openTabsCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.tabsSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.tabsEnforced)]]"
- aria-labelledby="openTabsCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.passwordsRegistered]]">
- <div id="passwordsCheckboxLabel">
- $i18n{passwordsCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.passwordsSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.passwordsEnforced)]]"
- aria-labelledby="passwordsCheckboxLabel">
- </cr-toggle>
- </div>
-
-<if expr="chromeos">
- <div class="list-item"
- hidden="[[!syncPrefs.wifiConfigurationsRegistered]]">
- <div id="wifiConfigurationsCheckboxLabel">
- $i18n{wifiConfigurationsCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.wifiConfigurationsSynced}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes,
- syncPrefs.wifiConfigurationsEnforced)]]"
- aria-labelledby="wifiConfigurationsCheckboxLabel">
- </cr-toggle>
- </div>
-</if>
-
- <div class="list-item" hidden="[[!syncPrefs.autofillRegistered]]">
- <div id="autofillCheckboxLabel">
- $i18n{autofillCheckboxLabel}
- </div>
- <!-- Autofill has a special on-change handler to deal with
- Payments integration. -->
- <cr-toggle checked="{{syncPrefs.autofillSynced}}"
- on-change="onAutofillDataTypeChanged_"
- disabled="[[shouldSyncCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.autofillEnforced)]]"
- aria-labelledby="autofillCheckboxLabel">
- </cr-toggle>
- </div>
-
- <div class="list-item" hidden="[[!syncPrefs.autofillRegistered]]">
- <!-- The Payments integration checkbox is a special case in many
- ways. It's visible only if autofill is registered. It's
- disabled and unchecked if autofill is unchecked.-->
- <div>
- $i18n{enablePaymentsIntegrationCheckboxLabel}
- </div>
- <cr-toggle checked="{{syncPrefs.paymentsIntegrationEnabled}}"
- on-change="onSingleSyncDataTypeChanged_"
- disabled="[[shouldPaymentsCheckboxBeDisabled_(
- syncPrefs.syncAllDataTypes, syncPrefs.autofillSynced)]]"
- aria-label="$i18n{enablePaymentsIntegrationCheckboxLabel}">
- </cr-toggle>
- </div>
- </div>
- </template>
- <script src="sync_controls.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_controls.js b/chromium/chrome/browser/resources/settings/people_page/sync_controls.js
deleted file mode 100644
index 452cc5eeaad..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_controls.js
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-/**
- * Names of the individual data type properties to be cached from
- * settings.SyncPrefs when the user checks 'Sync All'.
- * @type {!Array<string>}
- */
-const SyncPrefsIndividualDataTypes = [
- 'appsSynced',
- 'extensionsSynced',
- 'preferencesSynced',
- 'autofillSynced',
- 'typedUrlsSynced',
- 'themesSynced',
- 'bookmarksSynced',
- 'passwordsSynced',
- 'tabsSynced',
- 'paymentsIntegrationEnabled',
-];
-
-/**
- * @fileoverview
- * 'settings-sync-controls' contains all sync data type controls.
- */
-Polymer({
- is: 'settings-sync-controls',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- hidden: {
- type: Boolean,
- value: false,
- computed: 'syncControlsHidden_(' +
- 'syncStatus.signedIn, syncStatus.disabled, syncStatus.hasError)',
- reflectToAttribute: true,
- },
-
- /**
- * The current sync preferences, supplied by SyncBrowserProxy.
- * @type {settings.SyncPrefs|undefined}
- */
- syncPrefs: Object,
-
- /**
- * The current sync status, supplied by the parent.
- * @type {settings.SyncStatus}
- */
- syncStatus: {
- type: Object,
- observer: 'syncStatusChanged_',
- },
- },
-
- /** @private {?settings.SyncBrowserProxy} */
- browserProxy_: null,
-
- /**
- * Caches the individually selected synced data types. This is used to
- * be able to restore the selections after checking and unchecking Sync All.
- * @private {?Object}
- */
- cachedSyncPrefs_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'sync-prefs-changed', this.handleSyncPrefsChanged_.bind(this));
-
- if (settings.getCurrentRoute() == settings.routes.SYNC_ADVANCED) {
- this.browserProxy_.didNavigateToSyncPage();
- }
- },
-
- /**
- * Handler for when the sync preferences are updated.
- * @private
- */
- handleSyncPrefsChanged_: function(syncPrefs) {
- this.syncPrefs = syncPrefs;
-
- // If autofill is not registered or synced, force Payments integration off.
- if (!this.syncPrefs.autofillRegistered || !this.syncPrefs.autofillSynced) {
- this.set('syncPrefs.paymentsIntegrationEnabled', false);
- }
- },
-
- /**
- * Handler for when the sync all data types checkbox is changed.
- * @param {!Event} event
- * @private
- */
- onSyncAllDataTypesChanged_: function(event) {
- if (event.target.checked) {
- this.set('syncPrefs.syncAllDataTypes', true);
-
- // Cache the previously selected preference before checking every box.
- this.cachedSyncPrefs_ = {};
- for (const dataType of SyncPrefsIndividualDataTypes) {
- // These are all booleans, so this shallow copy is sufficient.
- this.cachedSyncPrefs_[dataType] = this.syncPrefs[dataType];
-
- this.set(['syncPrefs', dataType], true);
- }
- } else if (this.cachedSyncPrefs_) {
- // Restore the previously selected preference.
- for (const dataType of SyncPrefsIndividualDataTypes) {
- this.set(['syncPrefs', dataType], this.cachedSyncPrefs_[dataType]);
- }
- }
-
- this.onSingleSyncDataTypeChanged_();
- },
-
- /**
- * Handler for when any sync data type checkbox is changed (except autofill).
- * @private
- */
- onSingleSyncDataTypeChanged_: function() {
- assert(this.syncPrefs);
- this.browserProxy_.setSyncDatatypes(this.syncPrefs);
- },
-
- /**
- * Handler for when the autofill data type checkbox is changed.
- * @private
- */
- onAutofillDataTypeChanged_: function() {
- this.set(
- 'syncPrefs.paymentsIntegrationEnabled', this.syncPrefs.autofillSynced);
-
- this.onSingleSyncDataTypeChanged_();
- },
-
- /**
- * Handler for when the autofill data type checkbox is changed.
- * @private
- */
- onTypedUrlsDataTypeChanged_: function() {
- this.onSingleSyncDataTypeChanged_();
- },
-
- /**
- * @param {boolean} syncAllDataTypes
- * @param {boolean} enforced
- * @return {boolean} Whether the sync checkbox should be disabled.
- */
- shouldSyncCheckboxBeDisabled_: function(syncAllDataTypes, enforced) {
- return syncAllDataTypes || enforced;
- },
-
- /**
- * @param {boolean} syncAllDataTypes
- * @param {boolean} autofillSynced
- * @return {boolean} Whether the sync checkbox should be disabled.
- */
- shouldPaymentsCheckboxBeDisabled_: function(
- syncAllDataTypes, autofillSynced) {
- return syncAllDataTypes || !autofillSynced;
- },
-
- /** @private */
- syncStatusChanged_: function() {
- if (settings.getCurrentRoute() == settings.routes.SYNC_ADVANCED &&
- this.syncControlsHidden_()) {
- settings.navigateTo(settings.routes.SYNC);
- }
- },
-
- /**
- * @return {boolean} Whether the sync controls are hidden.
- * @private
- */
- syncControlsHidden_: function() {
- if (!this.syncStatus) {
- // Show sync controls by default.
- return false;
- }
-
- if (!this.syncStatus.signedIn || this.syncStatus.disabled) {
- return true;
- }
-
- return !!this.syncStatus.hasError &&
- this.syncStatus.statusAction !== settings.StatusAction.ENTER_PASSPHRASE;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_page.html b/chromium/chrome/browser/resources/settings/people_page/sync_page.html
deleted file mode 100644
index 51b1cf194c8..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_page.html
+++ /dev/null
@@ -1,362 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="sync_browser_proxy.html">
-<link rel="import" href="sync_controls.html">
-<link rel="import" href="../privacy_page/personalization_options.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<if expr="not chromeos">
-<link rel="import" href="sync_account_control.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
-</if>
-
-<dom-module id="settings-sync-page">
- <template>
- <style include="cr-shared-style settings-shared iron-flex">
- #sync-separator {
- border-bottom: var(--cr-separator-line);
- }
-
- #create-password-box {
- /* In order to line up with the encryption radio box text. */
- margin-inline-start: var(--cr-section-indent-width);
- }
-
- #create-password-box {
- margin-bottom: 1em;
- }
-
- #create-password-box .list-item {
- margin-bottom: var(--cr-form-field-bottom-spacing);
- }
-
- cr-input {
- --cr-input-width: var(--settings-input-max-width);
- }
-
- #existingPassphrase {
- border-bottom: var(--cr-separator-line);
- border-top: var(--cr-separator-line);
- /* This particular list frame is not indented. */
- padding-inline-start: var(--cr-section-padding);
- }
-
- #submitExistingPassphrase {
- /* The submit button for the existing passphrase is on the same line. */
- margin-inline-start: 16px;
- }
-
- #passphraseRecoverHint {
- align-items: center;
- }
-
- #other-sync-items {
- padding-bottom: 8px;
- }
-
- #other-sync-items > .list-item:not(.first) {
- border-top: var(--cr-separator-line);
- }
-
- .passphrase-reset-icon {
- margin-inline-end: 8px;
- }
-
- #disabled-by-admin-icon {
- text-align: center;
- width: 40px;
- }
-
-<if expr="not chromeos">
- #toast {
- left: 0;
- z-index: 1;
- }
-
- :host-context([dir='rtl']) #toast {
- left: auto;
- right: 0;
- }
-</if>
- </style>
-<if expr="not chromeos">
- <template is="dom-if" if="[[shouldShowSyncAccountControl_(
- unifiedConsentEnabled, syncStatus.syncSystemEnabled,
- syncStatus.signinAllowed)]]">
- <settings-sync-account-control embedded-in-subpage
- unified-consent-enabled="[[unifiedConsentEnabled]]"
- sync-status="[[syncStatus]]"
- promo-label-with-account=
- "$i18n{peopleSignInPromptSecondaryWithAccount}"
- promo-label-with-no-account=
- "$i18n{peopleSignInPromptSecondaryWithNoAccount}"
- on-sync-setup-done="onSyncSetupDone_">
- </settings-sync-account-control>
- </template>
-</if>
- <div class="settings-box first" hidden="[[!syncDisabledByAdmin_]]">
- <iron-icon id="disabled-by-admin-icon" icon="cr20:domain"></iron-icon>
- <div class="middle settings-box-text">
- $i18n{syncDisabledByAdministrator}
- </div>
- </div>
-
- <template is="dom-if" if="[[shouldShowExistingPassphraseBelowAccount_(
- unifiedConsentEnabled, syncPrefs.passphraseRequired)]]"
- on-dom-change="focusPassphraseInput_">
- <div id="existingPassphrase" class="list-frame">
- <div id="existingPassphraseTitle" class="list-item">
- <div class="start settings-box-text">
- <div>$i18n{existingPassphraseTitle}</div>
- <div class="secondary"
- inner-h-t-m-l="[[syncPrefs.enterPassphraseBody]]">
- </div>
- </div>
- </div>
- <div id="existingPassphraseContainer" class="list-item">
- <cr-input id="existingPassphraseInput" type="password"
- value="{{existingPassphrase_}}"
- placeholder="$i18n{passphrasePlaceholder}"
- error-message="$i18n{incorrectPassphraseError}"
- on-keypress="onSubmitExistingPassphraseTap_">
- <cr-button id="submitExistingPassphrase" slot="suffix"
- on-click="onSubmitExistingPassphraseTap_"
- class="action-button" disabled="[[!existingPassphrase_]]">
- $i18n{submitPassphraseButton}
- </cr-button>
- </cr-input>
- </div>
- <div id="passphraseRecoverHint" class="list-item">
- <div class="settings-box-text">$i18nRaw{passphraseRecover}</div>
- </div>
- </div>
- </template>
-
- <div id="sync-separator" hidden="[[!syncSectionDisabled_]]"></div>
-
- <div id="sync-section" hidden="[[syncSectionDisabled_]]">
- <div class="settings-box first" hidden="[[!unifiedConsentEnabled]]">
- <h2 class="cr-title-text start">$i18n{sync}</h2>
- </div>
-
- <div id="[[pages_.SPINNER]]" class="settings-box first settings-box-text"
- hidden$="[[!isStatus_(pages_.SPINNER, pageStatus_)]]">
- $i18n{syncLoading}
- </div>
- <div id="[[pages_.TIMEOUT]]" class="settings-box first settings-box-text"
- hidden$="[[!isStatus_(pages_.TIMEOUT, pageStatus_)]]">
- $i18n{syncTimeout}
- </div>
- <div id="[[pages_.CONFIGURE]]"
- hidden$="[[!isStatus_(pages_.CONFIGURE, pageStatus_)]]">
- <!-- TODO(http://crbug.com/862983) Remove this section once the Unified
- Consent feature is launched. -->
- <template is="dom-if" if="[[shouldShowExistingPassphraseInSyncSection_(
- unifiedConsentEnabled, syncPrefs.passphraseRequired)]]"
- on-dom-change="focusPassphraseInput_">
- <div id="existingPassphrase" class="list-frame">
- <div class="list-item">
- <div class="settings-box-text"
- inner-h-t-m-l="[[syncPrefs.enterPassphraseBody]]">
- </div>
- </div>
- <div id="existingPassphraseContainer" class="list-item">
- <cr-input id="existingPassphraseInput" type="password"
- value="{{existingPassphrase_}}"
- placeholder="$i18n{passphrasePlaceholder}"
- error-message="$i18n{incorrectPassphraseError}"
- on-keypress="onSubmitExistingPassphraseTap_">
- <cr-button id="submitExistingPassphrase" slot="suffix"
- on-click="onSubmitExistingPassphraseTap_"
- class="action-button" disabled="[[!existingPassphrase_]]">
- $i18n{submitPassphraseButton}
- </cr-button>
- </cr-input>
- </div>
- <div id="passphraseRecoverHint" class="list-item">
- <span>$i18nRaw{passphraseRecover}</span>
- </div>
- </div>
- </template>
-
- <template is="dom-if" if="[[!unifiedConsentEnabled]]">
- <settings-sync-controls sync-status="[[syncStatus]]">
- </settings-sync-controls>
- </template>
-
- <div id="other-sync-items"
- class$="[[getListFrameClass_(unifiedConsentEnabled)]]">
-
- <template is="dom-if" if="[[unifiedConsentEnabled]]">
- <div id="sync-advanced-row" class="list-item first"
- on-click="onSyncAdvancedTap_" actionable>
- <div class="start">
- $i18n{syncAdvancedPageTitle}
- </div>
- <cr-icon-button class="subpage-arrow"
- aria-label="$i18n{syncAdvancedPageTitle}"></cr-icon-button>
- </div>
- </template>
-
- <template is="dom-if"
- if="[[shouldShowDriveSuggest_(unifiedConsentEnabled)]]" restamp>
- <settings-toggle-button
- class$="[[getListItemClass_(unifiedConsentEnabled)]]"
- pref="{{prefs.documentsuggest.enabled}}"
- label="$i18n{driveSuggestPref}"
- sub-label="$i18n{driveSuggestPrefDesc}">
- </settings-toggle-button>
- </template>
-
- <a class$="inherit-color no-outline
- [[getListItemClass_(unifiedConsentEnabled)]]" tabindex="-1"
- target="_blank" href="$i18n{activityControlsUrl}"
- on-click="onActivityControlsTap_">
- <div class="start settings-box-text">
- $i18n{personalizeGoogleServicesTitle}
- </div>
- <cr-icon-button actionable class="icon-external"></cr-icon-button>
- </a>
-
- <a id="syncDashboardLink"
- class$="inherit-color no-outline
- [[getListItemClass_(unifiedConsentEnabled)]]"
- tabindex="-1" target="_blank" href="$i18n{syncDashboardUrl}"
- hidden="[[syncStatus.supervisedUser]]">
- <div class="start settings-box-text">
- $i18n{manageSyncedDataTitle}
- </div>
- <cr-icon-button actionable class="icon-external"></cr-icon-button>
- </a>
-
- <div id="encryptionDescription"
- hidden="[[syncPrefs.passphraseRequired]]"
- class$="single-column settings-box-text
- [[getPassphraseHintLines_(syncPrefs.encryptAllData)]]
- [[getListItemClass_(unifiedConsentEnabled)]]">
- $i18n{encryptionOptionsTitle}
- <div class="secondary">
- $i18n{syncDataEncryptedText}
- <div hidden="[[!syncPrefs.encryptAllData]]">
- <iron-icon icon="cr:info-outline"
- class="passphrase-reset-icon">
- </iron-icon>
- $i18nRaw{passphraseResetHintEncryption}
- </div>
- </div>
- </div>
-
- <div id="encryptionRadioGroupContainer" class="list-frame"
- hidden="[[syncPrefs.passphraseRequired]]">
- <cr-radio-group
- id="encryptionRadioGroup"
- selected="[[selectedEncryptionRadio_(syncPrefs)]]"
- on-selected-changed="onEncryptionRadioSelectionChanged_"
- disabled$="[[disableEncryptionOptions_]]">
- <cr-radio-button name="encrypt-with-google" class="list-item"
- aria-label="$i18n{encryptWithGoogleCredentialsLabel}"
- disabled$="[[disableEncryptionOptions_]]">
- $i18n{encryptWithGoogleCredentialsLabel}
- </cr-radio-button>
- <cr-radio-button name="encrypt-with-passphrase" class="list-item"
- disabled$="[[disableEncryptionOptions_]]">
- <span hidden="[[!syncPrefs.fullEncryptionBody]]">
- [[syncPrefs.fullEncryptionBody]]
- </span>
- <span on-click="onLearnMoreTap_"
- hidden="[[syncPrefs.fullEncryptionBody]]">
- $i18nRaw{encryptWithSyncPassphraseLabel}
- </span>
- </cr-radio-button>
- </cr-radio-group>
- </div>
-
- <template is="dom-if" if="[[creatingNewPassphrase_]]">
- <div class="list-frame">
- <div id="create-password-box"
- on-keypress="onSaveNewPassphraseTap_">
- <div class="list-item">
- <span>$i18nRaw{passphraseExplanationText}</span>
- </div>
- <cr-input id="passphraseInput" type="password"
- value="{{passphrase_}}"
- placeholder="$i18n{passphrasePlaceholder}"
- error-message="$i18n{emptyPassphraseError}">
- </cr-input>
- <cr-input id="passphraseConfirmationInput" type="password"
- value="{{confirmation_}}"
- placeholder="$i18n{passphraseConfirmationPlaceholder}"
- error-message="$i18n{mismatchedPassphraseError}">
- </cr-input>
- <cr-button id="saveNewPassphrase"
- on-click="onSaveNewPassphraseTap_" class="action-button"
- disabled="[[!isSaveNewPassphraseEnabled_(passphrase_,
- confirmation_)]]">
- $i18n{save}
- </cr-button>
- </div>
- </div>
- </template>
-
- </div>
- </div>
- </div>
-
- <template is="dom-if" if="[[unifiedConsentEnabled]]">
- <div class="settings-box first">
- <h2 class="cr-title-text">
- $i18n{nonPersonalizedServicesSectionLabel}
- </h2>
- </div>
- <settings-personalization-options class="list-frame" prefs="{{prefs}}"
- page-visibility="[[pageVisibility]]" sync-status="[[syncStatus]]"
- unified-consent-enabled="[[unifiedConsentEnabled]]">
- </settings-personalization-options>
- </template>
-
-<if expr="not chromeos">
- <template is="dom-if" if="[[showSetupCancelDialog_]]" restamp>
- <cr-dialog id="setupCancelDialog" on-close="onSetupCancelDialogClose_"
- ignore-popstate>
- <div slot="title">$i18n{syncSetupCancelDialogTitle}</div>
- <div slot="body">$i18n{syncSetupCancelDialogBody}</div>
- <div slot="button-container">
- <cr-button class="cancel-button"
- on-click="onSetupCancelDialogBack_">
- $i18n{back}
- </cr-button>
- <cr-button class="action-button"
- on-click="onSetupCancelDialogConfirm_">
- $i18n{cancelSync}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
-
- <template is="dom-if" if="[[!unifiedConsentEnabled]]">
- <cr-toast id="toast" open="[[syncStatus.firstSetupInProgress]]">
- <div>$i18n{syncWillStart}</div>
- <cr-button on-click="onSyncSetupCancel_">
- $i18n{cancelSync}
- </cr-button>
- </cr-toast>
- </template>
-</if>
- </template>
- <script src="sync_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_page.js b/chromium/chrome/browser/resources/settings/people_page/sync_page.js
deleted file mode 100644
index e65b7efd44b..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/sync_page.js
+++ /dev/null
@@ -1,710 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-/**
- * Names of the radio buttons which allow the user to choose their encryption
- * mechanism.
- * @enum {string}
- */
-const RadioButtonNames = {
- ENCRYPT_WITH_GOOGLE: 'encrypt-with-google',
- ENCRYPT_WITH_PASSPHRASE: 'encrypt-with-passphrase',
-};
-
-/**
- * @fileoverview
- * 'settings-sync-page' is the settings page containing sync settings.
- */
-Polymer({
- is: 'settings-sync-page',
-
- behaviors: [
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private */
- pages_: {
- type: Object,
- value: settings.PageStatus,
- readOnly: true,
- },
-
- /**
- * The current page status. Defaults to |CONFIGURE| such that the searching
- * algorithm can search useful content when the page is not visible to the
- * user.
- * @private {?settings.PageStatus}
- */
- pageStatus_: {
- type: String,
- value: settings.PageStatus.CONFIGURE,
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!PrivacyPageVisibility}
- */
- pageVisibility: Object,
-
- /**
- * The current sync preferences, supplied by SyncBrowserProxy.
- * @type {settings.SyncPrefs|undefined}
- */
- syncPrefs: {
- type: Object,
- },
-
- /** @type {settings.SyncStatus} */
- syncStatus: {
- type: Object,
- },
-
- /**
- * Whether the "create passphrase" inputs should be shown. These inputs
- * give the user the opportunity to use a custom passphrase instead of
- * authenticating with their Google credentials.
- * @private
- */
- creatingNewPassphrase_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The passphrase input field value.
- * @private
- */
- passphrase_: {
- type: String,
- value: '',
- },
-
- /**
- * The passphrase confirmation input field value.
- * @private
- */
- confirmation_: {
- type: String,
- value: '',
- },
-
- /**
- * The existing passphrase input field value.
- * @private
- */
- existingPassphrase_: {
- type: String,
- value: '',
- },
-
- /** @private */
- signedIn_: {
- type: Boolean,
- value: true,
- computed: 'computeSignedIn_(syncStatus.signedIn)',
- },
-
- /** @private */
- syncDisabledByAdmin_: {
- type: Boolean,
- value: false,
- computed: 'computeSyncDisabledByAdmin_(syncStatus.managed)',
- },
-
- /** @private */
- syncSectionDisabled_: {
- type: Boolean,
- value: false,
- computed: 'computeSyncSectionDisabled_(' +
- 'unifiedConsentEnabled, syncStatus.signedIn, syncStatus.disabled, ' +
- 'syncStatus.hasError, syncStatus.statusAction)',
- },
-
- // <if expr="not chromeos">
- diceEnabled: Boolean,
- // </if>
-
- unifiedConsentEnabled: {
- type: Boolean,
- observer: 'initializeDidAbort_',
- },
-
- /** @private */
- showSetupCancelDialog_: {
- type: Boolean,
- value: false,
- },
-
- disableEncryptionOptions_: {
- type: Boolean,
- computed: 'computeDisableEncryptionOptions_(' +
- 'syncPrefs, syncStatus)',
- },
- },
-
- /** @private {?settings.SyncBrowserProxy} */
- browserProxy_: null,
-
- /**
- * If unified consent is enabled, the beforeunload callback is used to
- * show the 'Leave site' dialog. This makes sure that the user has the chance
- * to go back and confirm the sync opt-in before leaving.
- *
- * If unified consent is disabled, the beforeunload callback is used
- * to confirm the sync setup before leaving the opt-in flow.
- *
- * This property is non-null if the user is currently navigated on the sync
- * settings route.
- *
- * @private {?Function}
- */
- beforeunloadCallback_: null,
-
- /**
- * If unified consent is enabled, the unload callback is used to cancel the
- * sync setup when the user hits the browser back button after arriving on the
- * page.
- * Note: Cases like closing the tab or reloading don't need to be handled,
- * because they are already caught in |PeopleHandler::~PeopleHandler|
- * from the C++ code.
- *
- * @private {?Function}
- */
- unloadCallback_: null,
-
- /**
- * Whether the initial layout for collapsible sections has been computed. It
- * is computed only once, the first time the sync status is updated.
- * @private {boolean}
- */
- collapsibleSectionsInitialized_: false,
-
- /**
- * Whether the user decided to abort sync. When unified consent is enabled,
- * this is initialized to true.
- * @private {boolean}
- */
- didAbort_: false,
-
- /**
- * Whether the user confirmed the cancellation of sync.
- * @private {boolean}
- */
- setupCancelConfirmed_: false,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'page-status-changed', this.handlePageStatusChanged_.bind(this));
- this.addWebUIListener(
- 'sync-prefs-changed', this.handleSyncPrefsChanged_.bind(this));
-
- if (settings.getCurrentRoute() == settings.routes.SYNC) {
- this.onNavigateToPage_();
- }
- },
-
- /** @override */
- detached: function() {
- if (settings.routes.SYNC.contains(settings.getCurrentRoute())) {
- this.onNavigateAwayFromPage_();
- }
-
- if (this.beforeunloadCallback_) {
- window.removeEventListener('beforeunload', this.beforeunloadCallback_);
- this.beforeunloadCallback_ = null;
- }
- if (this.unloadCallback_) {
- window.removeEventListener('unload', this.unloadCallback_);
- this.unloadCallback_ = null;
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeSignedIn_: function() {
- return !!this.syncStatus.signedIn;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeSyncSectionDisabled_: function() {
- return !!this.unifiedConsentEnabled && this.syncStatus !== undefined &&
- (!this.syncStatus.signedIn || !!this.syncStatus.disabled ||
- (!!this.syncStatus.hasError &&
- this.syncStatus.statusAction !==
- settings.StatusAction.ENTER_PASSPHRASE));
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeSyncDisabledByAdmin_: function() {
- return this.syncStatus != undefined && !!this.syncStatus.managed;
- },
-
- /** @private */
- onSetupCancelDialogBack_: function() {
- this.$$('#setupCancelDialog').cancel();
- chrome.metricsPrivate.recordUserAction(
- 'Signin_Signin_CancelCancelAdvancedSyncSettings');
- },
-
- /** @private */
- onSetupCancelDialogConfirm_: function() {
- this.setupCancelConfirmed_ = true;
- this.$$('#setupCancelDialog').close();
- settings.navigateTo(settings.routes.BASIC);
- chrome.metricsPrivate.recordUserAction(
- 'Signin_Signin_ConfirmCancelAdvancedSyncSettings');
- },
-
- /** @private */
- onSetupCancelDialogClose_: function() {
- this.showSetupCancelDialog_ = false;
- },
-
- /** @protected */
- currentRouteChanged: function() {
- if (settings.getCurrentRoute() == settings.routes.SYNC) {
- this.onNavigateToPage_();
- return;
- }
-
- if (settings.routes.SYNC.contains(settings.getCurrentRoute())) {
- return;
- }
-
- const searchParams = settings.getQueryParameters().get('search');
- if (searchParams) {
- // User navigated away via searching. Cancel sync without showing
- // confirmation dialog.
- this.onNavigateAwayFromPage_();
- return;
- }
-
- const userActionCancelsSetup = this.syncStatus &&
- this.syncStatus.firstSetupInProgress && this.didAbort_;
- if (this.unifiedConsentEnabled && userActionCancelsSetup &&
- !this.setupCancelConfirmed_) {
- chrome.metricsPrivate.recordUserAction(
- 'Signin_Signin_BackOnAdvancedSyncSettings');
- // Show the 'Cancel sync?' dialog.
- // Yield so that other |currentRouteChanged| observers are called,
- // before triggering another navigation (and another round of observers
- // firing). Triggering navigation from within an observer leads to some
- // undefined behavior and runtime errors.
- requestAnimationFrame(() => {
- settings.navigateTo(settings.routes.SYNC);
- this.showSetupCancelDialog_ = true;
- // Flush to make sure that the setup cancel dialog is attached.
- Polymer.dom.flush();
- this.$$('#setupCancelDialog').showModal();
- });
- return;
- }
-
- // Reset variable.
- this.setupCancelConfirmed_ = false;
-
- this.onNavigateAwayFromPage_();
- },
-
- /**
- * @param {!settings.PageStatus} expectedPageStatus
- * @return {boolean}
- * @private
- */
- isStatus_: function(expectedPageStatus) {
- return expectedPageStatus == this.pageStatus_;
- },
-
- /** @private */
- onNavigateToPage_: function() {
- assert(settings.getCurrentRoute() == settings.routes.SYNC);
-
- if (this.beforeunloadCallback_) {
- return;
- }
-
- this.collapsibleSectionsInitialized_ = false;
-
- // Display loading page until the settings have been retrieved.
- this.pageStatus_ = settings.PageStatus.SPINNER;
-
- this.browserProxy_.didNavigateToSyncPage();
-
- if (this.unifiedConsentEnabled) {
- this.beforeunloadCallback_ = event => {
- // When the user tries to leave the sync setup, show the 'Leave site'
- // dialog.
- if (this.unifiedConsentEnabled && this.syncStatus &&
- this.syncStatus.firstSetupInProgress) {
- event.preventDefault();
- event.returnValue = '';
-
- chrome.metricsPrivate.recordUserAction(
- 'Signin_Signin_AbortAdvancedSyncSettings');
- }
- };
- window.addEventListener('beforeunload', this.beforeunloadCallback_);
-
- this.unloadCallback_ = this.onNavigateAwayFromPage_.bind(this);
- window.addEventListener('unload', this.unloadCallback_);
- } else {
- this.beforeunloadCallback_ = this.onNavigateAwayFromPage_.bind(this);
- window.addEventListener('beforeunload', this.beforeunloadCallback_);
- }
- },
-
- /** @private */
- onNavigateAwayFromPage_: function() {
- if (!this.beforeunloadCallback_) {
- return;
- }
-
- // Reset the status to CONFIGURE such that the searching algorithm can
- // search useful content when the page is not visible to the user.
- this.pageStatus_ = settings.PageStatus.CONFIGURE;
-
- this.browserProxy_.didNavigateAwayFromSyncPage(this.didAbort_);
- this.initializeDidAbort_();
-
- window.removeEventListener('beforeunload', this.beforeunloadCallback_);
- this.beforeunloadCallback_ = null;
-
- if (this.unloadCallback_) {
- window.removeEventListener('unload', this.unloadCallback_);
- this.unloadCallback_ = null;
- }
- },
-
- /**
- * Handler for when the sync preferences are updated.
- * @private
- */
- handleSyncPrefsChanged_: function(syncPrefs) {
- this.syncPrefs = syncPrefs;
- this.pageStatus_ = settings.PageStatus.CONFIGURE;
-
- // Hide the new passphrase box if (a) full data encryption is enabled,
- // (b) encrypting all data is not allowed (so far, only applies to
- // supervised accounts), or (c) the user is a supervised account.
- if (this.syncPrefs.encryptAllData ||
- !this.syncPrefs.encryptAllDataAllowed ||
- (this.syncStatus && this.syncStatus.supervisedUser)) {
- this.creatingNewPassphrase_ = false;
- }
- },
-
- /** @private */
- onActivityControlsTap_: function() {
- this.browserProxy_.openActivityControlsUrl();
- },
-
- /**
- * @param {string} passphrase The passphrase input field value
- * @param {string} confirmation The passphrase confirmation input field value.
- * @return {boolean} Whether the passphrase save button should be enabled.
- * @private
- */
- isSaveNewPassphraseEnabled_: function(passphrase, confirmation) {
- return passphrase !== '' && confirmation !== '';
- },
-
- /**
- * Sends the newly created custom sync passphrase to the browser.
- * @private
- * @param {!Event} e
- */
- onSaveNewPassphraseTap_: function(e) {
- assert(this.creatingNewPassphrase_);
-
- // Ignore events on irrelevant elements or with irrelevant keys.
- if (e.target.tagName != 'CR-BUTTON' && e.target.tagName != 'CR-INPUT') {
- return;
- }
- if (e.type == 'keypress' && e.key != 'Enter') {
- return;
- }
-
- // If a new password has been entered but it is invalid, do not send the
- // sync state to the API.
- if (!this.validateCreatedPassphrases_()) {
- return;
- }
-
- this.syncPrefs.encryptAllData = true;
- this.syncPrefs.setNewPassphrase = true;
- this.syncPrefs.passphrase = this.passphrase_;
-
- this.browserProxy_.setSyncEncryption(this.syncPrefs)
- .then(this.handlePageStatusChanged_.bind(this));
- },
-
- /**
- * Sends the user-entered existing password to re-enable sync.
- * @private
- * @param {!Event} e
- */
- onSubmitExistingPassphraseTap_: function(e) {
- if (e.type == 'keypress' && e.key != 'Enter') {
- return;
- }
-
- assert(!this.creatingNewPassphrase_);
-
- this.syncPrefs.setNewPassphrase = false;
-
- this.syncPrefs.passphrase = this.existingPassphrase_;
- this.existingPassphrase_ = '';
-
- this.browserProxy_.setSyncEncryption(this.syncPrefs)
- .then(this.handlePageStatusChanged_.bind(this));
- },
-
- /**
- * Called when the page status updates.
- * @param {!settings.PageStatus} pageStatus
- * @private
- */
- handlePageStatusChanged_: function(pageStatus) {
- switch (pageStatus) {
- case settings.PageStatus.SPINNER:
- case settings.PageStatus.TIMEOUT:
- case settings.PageStatus.CONFIGURE:
- this.pageStatus_ = pageStatus;
- return;
- case settings.PageStatus.DONE:
- if (settings.getCurrentRoute() == settings.routes.SYNC) {
- settings.navigateTo(settings.routes.PEOPLE);
- }
- return;
- case settings.PageStatus.PASSPHRASE_FAILED:
- if (this.pageStatus_ == this.pages_.CONFIGURE && this.syncPrefs &&
- this.syncPrefs.passphraseRequired) {
- const passphraseInput = /** @type {!CrInputElement} */ (
- this.$$('#existingPassphraseInput'));
- passphraseInput.invalid = true;
- passphraseInput.focusInput();
- }
- return;
- }
-
- assertNotReached();
- },
-
- /**
- * Called when the encryption
- * @param {!Event} event
- * @private
- */
- onEncryptionRadioSelectionChanged_: function(event) {
- this.creatingNewPassphrase_ =
- event.detail.value == RadioButtonNames.ENCRYPT_WITH_PASSPHRASE;
- },
-
- /**
- * Computed binding returning the selected encryption radio button.
- * @private
- */
- selectedEncryptionRadio_: function() {
- return this.syncPrefs.encryptAllData || this.creatingNewPassphrase_ ?
- RadioButtonNames.ENCRYPT_WITH_PASSPHRASE :
- RadioButtonNames.ENCRYPT_WITH_GOOGLE;
- },
-
- /**
- * Checks the supplied passphrases to ensure that they are not empty and that
- * they match each other. Additionally, displays error UI if they are invalid.
- * @return {boolean} Whether the check was successful (i.e., that the
- * passphrases were valid).
- * @private
- */
- validateCreatedPassphrases_: function() {
- const emptyPassphrase = !this.passphrase_;
- const mismatchedPassphrase = this.passphrase_ != this.confirmation_;
-
- this.$$('#passphraseInput').invalid = emptyPassphrase;
- this.$$('#passphraseConfirmationInput').invalid =
- !emptyPassphrase && mismatchedPassphrase;
-
- return !emptyPassphrase && !mismatchedPassphrase;
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onLearnMoreTap_: function(event) {
- if (event.target.tagName == 'A') {
- // Stop the propagation of events, so that clicking on links inside
- // checkboxes or radio buttons won't change the value.
- event.stopPropagation();
- }
- },
-
- /**
- * When unified-consent enabled, the non-toggle items on the bottom of sync
- * section should be wrapped with 'list-frame' in order to be indented
- * correctly.
- * @return {string}
- * @private
- */
- getListFrameClass_: function() {
- return this.unifiedConsentEnabled ? 'list-frame' : '';
- },
-
- /**
- * When unified-consent enabled, the non-toggle items on the bottom of sync
- * section will be wrapped with 'list-frame', and should have the 'list-item'
- * instead of 'settings-box' in order to be indented correctly.
- * @return {string}
- * @private
- */
- getListItemClass_: function() {
- return this.unifiedConsentEnabled ? 'list-item' : 'settings-box';
- },
-
- /**
- * When there is a sync passphrase, some items have an additional line for the
- * passphrase reset hint, making them three lines rather than two.
- * @return {string}
- * @private
- */
- getPassphraseHintLines_: function() {
- return this.syncPrefs.encryptAllData ? 'three-line' : 'two-line';
- },
-
- // <if expr="not chromeos">
- /**
- * @return {boolean}
- * @private
- */
- shouldShowSyncAccountControl_: function() {
- return !!this.unifiedConsentEnabled && this.syncStatus !== undefined &&
- !!this.syncStatus.syncSystemEnabled && !!this.syncStatus.signinAllowed;
- },
- // </if>
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowExistingPassphraseBelowAccount_: function() {
- return !!this.unifiedConsentEnabled && this.syncPrefs !== undefined &&
- !!this.syncPrefs.passphraseRequired;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowExistingPassphraseInSyncSection_: function() {
- return !this.unifiedConsentEnabled && this.syncPrefs !== undefined &&
- !!this.syncPrefs.passphraseRequired;
- },
-
- /**
- * Whether we should disable the radio buttons that allow choosing the
- * encryption options for Sync.
- * We disable the buttons if:
- * (a) full data encryption is enabled, or,
- * (b) full data encryption is not allowed (so far, only applies to
- * supervised accounts), or,
- * (c) the user is a supervised account.
- * @return {boolean}
- * @private
- */
- computeDisableEncryptionOptions_: function() {
- return !!(
- (this.syncPrefs &&
- (this.syncPrefs.encryptAllData ||
- !this.syncPrefs.encryptAllDataAllowed)) ||
- (this.syncStatus && this.syncStatus.supervisedUser));
- },
-
- /** @private */
- onSyncAdvancedTap_: function() {
- settings.navigateTo(settings.routes.SYNC_ADVANCED);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowDriveSuggest_: function() {
- return loadTimeData.getBoolean('driveSuggestAvailable') &&
- !this.unifiedConsentEnabled;
- },
-
- /**
- * Used when unified consent is disabled.
- * @private
- */
- onSyncSetupCancel_: function() {
- this.didAbort_ = true;
- settings.navigateTo(settings.routes.BASIC);
- },
-
- /** @private */
- initializeDidAbort_: function() {
- this.didAbort_ = !!this.unifiedConsentEnabled;
- },
-
- /**
- * @param {!CustomEvent<boolean>} e The event passed from
- * settings-sync-account-control.
- * @private
- */
- onSyncSetupDone_: function(e) {
- if (e.detail) {
- this.didAbort_ = false;
- chrome.metricsPrivate.recordUserAction(
- 'Signin_Signin_ConfirmAdvancedSyncSettings');
- } else {
- this.setupCancelConfirmed_ = true;
- chrome.metricsPrivate.recordUserAction(
- 'Signin_Signin_CancelAdvancedSyncSettings');
- }
- settings.navigateTo(settings.routes.BASIC);
- },
-
- /**
- * Focuses the passphrase input element if it is available and the page is
- * visible.
- * @private
- */
- focusPassphraseInput_: function() {
- const passphraseInput =
- /** @type {!CrInputElement} */ (this.$$('#existingPassphraseInput'));
- if (passphraseInput && settings.getCurrentRoute() == settings.routes.SYNC) {
- passphraseInput.focus();
- }
- },
-});
-
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/user_list.html b/chromium/chrome/browser/resources/settings/people_page/user_list.html
deleted file mode 100644
index dbc871abac2..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/user_list.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-user-list">
- <template>
- <style include="settings-shared iron-flex">
- .user-list {
- /* 4 users (the extra 1px is to account for the border-bottom) */
- max-height: calc(4 * (var(--settings-row-two-line-min-height) + 1px));
- overflow-y: auto;
- }
-
- .user {
- border-bottom: var(--cr-separator-line);
- }
-
- .user-icon {
- background-position: center;
- background-repeat: no-repeat;
- background-size: cover;
- border-radius: 20px;
- flex-shrink: 0;
- height: 40px;
- width: 40px;
- }
-
- .user-info {
- padding-inline-start: 20px;
- }
-
- :host([disabled]) .user-list {
- opacity: var(--cr-disabled-opacity);
- overflow: auto;
- }
- </style>
- <div class="user-list" scrollable>
- <template is="dom-repeat" items="[[users_]]">
- <div class="user layout horizontal center two-line">
- <img class="user-icon" src="[[getProfilePictureUrl_(item)]]">
- <div class="flex layout vertical user-info no-min-width">
- <div class="text-elide" title="[[getTooltip_(item)]]">
- [[getUserName_(item)]]
- </div>
- <div class="secondary text-elide" title="[[item.displayEmail]]"
- hidden$="[[!shouldShowEmail_(item)]]">
- [[item.displayEmail]]
- </div>
- </div>
- <cr-icon-button class="icon-clear"
- hidden="[[shouldHideCloseButton_(disabled, item.isOwner)]]"
- title="$i18n{removeUserTooltip}" on-click="removeUser_">
- </cr-icon-button>
- </div>
- </template>
- </div>
- </template>
- <script src="user_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/user_list.js b/chromium/chrome/browser/resources/settings/people_page/user_list.js
deleted file mode 100644
index 066f71f607c..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/user_list.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-user-list' shows a list of users whitelisted on this Chrome OS
- * device.
- *
- * Example:
- *
- * <settings-user-list prefs="{{prefs}}">
- * </settings-user-list>
- */
-Polymer({
- is: 'settings-user-list',
-
- behaviors: [
- CrScrollableBehavior,
- I18nBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- /**
- * Current list of whitelisted users.
- * @private {!Array<!chrome.usersPrivate.User>}
- */
- users_: {
- type: Array,
- value: function() {
- return [];
- },
- notify: true
- },
-
- /**
- * Whether the user list is disabled, i.e. that no modifications can be
- * made.
- * @type {boolean}
- */
- disabled: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- }
- },
-
- /** @override */
- ready: function() {
- chrome.settingsPrivate.onPrefsChanged.addListener(prefs => {
- prefs.forEach(function(pref) {
- if (pref.key == 'cros.accounts.users') {
- chrome.usersPrivate.getWhitelistedUsers(users => {
- this.setUsers_(users);
- });
- }
- }, this);
- });
- },
-
- /** @protected */
- currentRouteChanged: function() {
- if (settings.getCurrentRoute() == settings.routes.ACCOUNTS) {
- chrome.usersPrivate.getWhitelistedUsers(users => {
- this.setUsers_(users);
- });
- }
- },
-
- /**
- * @param {!chrome.usersPrivate.User} user
- * @return {string}
- * @private
- */
- getUserName_: function(user) {
- return user.isOwner ? this.i18n('deviceOwnerLabel', user.name) : user.name;
- },
-
- /**
- * Helper function that sorts and sets the given list of whitelisted users.
- * @param {!Array<!chrome.usersPrivate.User>} users List of whitelisted users.
- */
- setUsers_: function(users) {
- this.users_ = users;
- this.users_.sort(function(a, b) {
- if (a.isOwner != b.isOwner) {
- return b.isOwner ? 1 : -1;
- } else {
- return -1;
- }
- });
- this.requestUpdateScroll();
- },
-
- /**
- * @private
- * @param {!{model: !{item: !chrome.usersPrivate.User}}} e
- */
- removeUser_: function(e) {
- chrome.usersPrivate.removeWhitelistedUser(
- e.model.item.email, /* callback */ function() {});
- },
-
- /** @private */
- shouldHideCloseButton_: function(disabled, isUserOwner) {
- return disabled || isUserOwner;
- },
-
- /**
- * @param {chrome.usersPrivate.User} user
- * @private
- */
- getProfilePictureUrl_: function(user) {
- return 'chrome://userimage/' + user.email + '?id=' + Date.now() +
- '&frame=0';
- },
-
- /**
- * @param {chrome.usersPrivate.User} user
- * @private
- */
- shouldShowEmail_: function(user) {
- return !user.isSupervised && user.name != user.displayEmail;
- },
-
- /**
- * Use this function to prevent tooltips from displaying for user names. We
- * only want to display tooltips for email addresses.
- * @param {chrome.usersPrivate.User} user
- * @private
- */
- getTooltip_: function(user) {
- return !this.shouldShowEmail_(user) ? user.displayEmail : '';
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.html b/chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.html
deleted file mode 100644
index cbcbc0b24d5..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-users-add-user-dialog">
- <template>
- <style include="settings-shared">
- cr-dialog::part(dialog) {
- width: 320px;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{addUsers}</div>
- <div slot="body">
- <cr-input id="addUserInput" label="$i18n{addUsersEmail}"
- invalid="[[shouldShowError_(errorCode_)]]"
- on-value-changed="onInput_"
- error-message="[[getErrorString_(errorCode_)]]" autofocus>
- </cr-input>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button on-click="addUser_" class="action-button"
- disabled$="[[!canAddUser_(isEmail_, isEmpty_)]]">
- $i18n{add}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="users_add_user_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.js b/chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.js
deleted file mode 100644
index a68a908b4b6..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/users_add_user_dialog.js
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-users-add-user-dialog' is the dialog shown for adding new allowed
- * users to a ChromeOS device.
- */
-(function() {
-
-/**
- * Regular expression for adding a user where the string provided is just
- * the part before the "@".
- * Email alias only, assuming it's a gmail address.
- * e.g. 'john'
- * @type {!RegExp}
- */
-const NAME_ONLY_REGEX =
- new RegExp('^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)\\s*$');
-
-/**
- * Regular expression for adding a user where the string provided is a full
- * email address.
- * e.g. 'john@chromium.org'
- * @type {!RegExp}
- */
-const EMAIL_REGEX = new RegExp(
- '^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)@' +
- '([A-Za-z0-9\-]{2,63}\\..+)\\s*$');
-
-/** @enum {number} */
-const UserAddError = {
- NO_ERROR: 0,
- INVALID_EMAIL: 1,
- USER_EXISTS: 2,
-};
-
-Polymer({
- is: 'settings-users-add-user-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /** @private */
- errorCode_: {
- type: Number,
- value: UserAddError.NO_ERROR,
- },
-
- /** @private */
- isEmail_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isEmpty_: {
- type: Boolean,
- value: true,
- },
- },
-
- usersPrivate_: chrome.usersPrivate,
-
- open: function() {
- this.$.addUserInput.value = '';
- this.onInput_();
- this.$.dialog.showModal();
- // Set to valid initially since the user has not typed anything yet.
- this.$.addUserInput.invalid = false;
- },
-
- /** @private */
- addUser_: function() {
- // May be submitted by the Enter key even if the input value is invalid.
- if (this.$.addUserInput.disabled) {
- return;
- }
-
- const input = this.$.addUserInput.value;
-
- const nameOnlyMatches = NAME_ONLY_REGEX.exec(input);
- let userEmail;
- if (nameOnlyMatches) {
- userEmail = nameOnlyMatches[1] + '@gmail.com';
- } else {
- const emailMatches = EMAIL_REGEX.exec(input);
- // Assuming the input validated, one of these two must match.
- assert(emailMatches);
- userEmail = emailMatches[1] + '@' + emailMatches[2];
- }
-
- this.usersPrivate_.isWhitelistedUser(userEmail, doesUserExist => {
- if (doesUserExist) {
- // This user email had been saved previously
- this.errorCode_ = UserAddError.USER_EXISTS;
- return;
- }
-
- this.$.dialog.close();
- this.usersPrivate_.addWhitelistedUser(
- userEmail,
- /* callback */ function(success) {});
-
- this.$.addUserInput.value = '';
- });
- },
-
- /**
- * @return {boolean}
- * @private
- */
- canAddUser_: function() {
- return this.isEmail_ && !this.isEmpty_;
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.cancel();
- },
-
- /** @private */
- onInput_: function() {
- const input = this.$.addUserInput.value;
- this.isEmail_ = NAME_ONLY_REGEX.test(input) || EMAIL_REGEX.test(input);
- this.isEmpty_ = input.length == 0;
-
- if (!this.isEmail_ && !this.isEmpty_) {
- this.errorCode_ = UserAddError.INVALID_EMAIL;
- return;
- }
-
- this.errorCode_ = UserAddError.NO_ERROR;
- },
-
- /**
- * @private
- * @return {boolean}
- */
- shouldShowError_: function() {
- return this.errorCode_ != UserAddError.NO_ERROR;
- },
-
- /**
- * @private
- * @return {string}
- */
- getErrorString_: function(errorCode_) {
- if (errorCode_ == UserAddError.USER_EXISTS) {
- return this.i18n('userExistsError');
- }
- //TODO errorString for UserAddError.INVALID_EMAIL crbug/1007481
-
- return '';
- },
-});
-
-})();
diff --git a/chromium/chrome/browser/resources/settings/people_page/users_page.html b/chromium/chrome/browser/resources/settings/people_page/users_page.html
deleted file mode 100644
index 3b94c83970a..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/users_page.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="user_list.html">
-<link rel="import" href="users_add_user_dialog.html">
-
-<dom-module id="settings-users-page">
- <template>
- <style include="settings-shared action-link">
- .settings-box:first-of-type {
- border-top: none;
- }
-
- #add-user-button {
- /* Add user button must be lined up with the start of users' names. */
- margin-inline-start: var(--cr-section-indent-padding);
- }
-
- .block {
- display: block;
- }
-
- #header {
- padding-inline-start: 20px;
- }
- </style>
- <div class="settings-box" hidden$="[[!isWhitelistManaged_]]">
- $i18n{settingsManagedLabel}
- </div>
- <div class="settings-box"
- hidden$="[[shouldHideModifiedByOwnerLabel_(
- isWhitelistManaged_, isOwner_)]]">
- $i18n{usersModifiedByOwnerLabel}
- </div>
- <settings-toggle-button class="first"
- pref="{{prefs.cros.accounts.allowBWSI}}"
- label="$i18n{guestBrowsingLabel}"
- disabled="[[isEditingDisabled_(isOwner_, isWhitelistManaged_)]]">
- </settings-toggle-button>
- <settings-toggle-button class="continuation"
- pref="{{prefs.cros.accounts.showUserNamesOnSignIn}}"
- label="$i18n{showOnSigninLabel}"
- disabled="[[isEditingDisabled_(isOwner_, isWhitelistManaged_)]]">
- </settings-toggle-button>
- <settings-toggle-button class="continuation"
- pref="{{prefs.cros.accounts.allowGuest}}"
- id="restrictSignIn"
- label="$i18n{restrictSigninLabel}"
- disabled="[[isEditingDisabled_(isOwner_, isWhitelistManaged_)]]"
- inverted>
- </settings-toggle-button>
- <template is="dom-if"
- if="[[isEditingUsersEnabled_(isOwner_, isWhitelistManaged_,
- prefs.cros.accounts.allowGuest.value, isChild_)]]">
- <div class="list-frame" >
- <settings-user-list prefs="[[prefs]]">
- </settings-user-list>
- <div id="add-user-button" class="list-item">
- <a is="action-link" class="list-button" on-click="openAddUserDialog_">
- $i18n{addUsers}
- </a>
- </div>
- </div>
- </template>
- <settings-users-add-user-dialog id="addUserDialog"
- on-close="onAddUserDialogClose_">
- </settings-users-add-user-dialog>
- </template>
- <script src="users_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/people_page/users_page.js b/chromium/chrome/browser/resources/settings/people_page/users_page.js
deleted file mode 100644
index 4a3ff206056..00000000000
--- a/chromium/chrome/browser/resources/settings/people_page/users_page.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-users-page' is the settings page for managing user accounts on
- * the device.
- */
-Polymer({
- is: 'settings-users-page',
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private */
- isOwner_: {
- type: Boolean,
- value: true,
- },
-
- /** @private */
- isWhitelistManaged_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- isChild_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isSupervised');
- },
- },
- },
-
- /** @override */
- created: function() {
- chrome.usersPrivate.getCurrentUser(user => {
- this.isOwner_ = user.isOwner;
- });
-
- chrome.usersPrivate.isWhitelistManaged(isWhitelistManaged => {
- this.isWhitelistManaged_ = isWhitelistManaged;
- });
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- openAddUserDialog_: function(e) {
- e.preventDefault();
- this.$.addUserDialog.open();
- },
-
- /** @private */
- onAddUserDialogClose_: function() {
- cr.ui.focusWithoutInk(assert(this.$$('#add-user-button a')));
- },
-
- /**
- * @param {boolean} isOwner
- * @param {boolean} isWhitelistManaged
- * @private
- * @return {boolean}
- */
- isEditingDisabled_: function(isOwner, isWhitelistManaged) {
- return !isOwner || isWhitelistManaged;
- },
-
- /**
- * @param {boolean} isOwner
- * @param {boolean} isWhitelistManaged
- * @param {boolean} allowGuest
- * @param {boolean} isChild
- * @private
- * @return {boolean}
- */
- isEditingUsersEnabled_: function(
- isOwner, isWhitelistManaged, allowGuest, isChild) {
- return isOwner && !isWhitelistManaged && !allowGuest && !isChild;
- },
-
- /** @return {boolean} */
- shouldHideModifiedByOwnerLabel_: function() {
- return this.isWhitelistManaged_ || this.isOwner_;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.html b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.html
deleted file mode 100644
index 1a1f299df7f..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="plugin_vm_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.js b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.js
deleted file mode 100644
index fa73bb1a92d..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_browser_proxy.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used by the Plugin VM section
- * to manage the Plugin VM.
- */
-cr.define('settings', function() {
- /** @interface */
- class PluginVmBrowserProxy {
- /**
- * @param {!Array<string>} paths Paths to sanitze.
- * @return {!Promise<!Array<string>>} Text to display in UI.
- */
- getPluginVmSharedPathsDisplayText(paths) {}
-
- /**
- * @param {string} vmName VM to stop sharing path with.
- * @param {string} path Path to stop sharing.
- */
- removePluginVmSharedPath(vmName, path) {}
- }
-
- /** @implements {settings.PluginVmBrowserProxy} */
- class PluginVmBrowserProxyImpl {
- /** @override */
- getPluginVmSharedPathsDisplayText(paths) {
- return cr.sendWithPromise('getPluginVmSharedPathsDisplayText', paths);
- }
-
- /** @override */
- removePluginVmSharedPath(vmName, path) {
- chrome.send('removePluginVmSharedPath', [vmName, path]);
- }
- }
-
- // The singleton instance_ can be replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(PluginVmBrowserProxyImpl);
-
- return {
- PluginVmBrowserProxy: PluginVmBrowserProxy,
- PluginVmBrowserProxyImpl: PluginVmBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.html b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.html
deleted file mode 100644
index 7c0844dfdff..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="plugin_vm_shared_paths.html">
-<link rel="import" href="plugin_vm_subpage.html">
-
-<dom-module id="settings-plugin-vm-page">
- <template>
- <style include="settings-shared"></style>
-
- <settings-animated-pages id="pages" section="pluginVm"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <cr-link-row id="plugin-vm"
- label="$i18n{pluginVmPageLabel}"
- sub-label="$i18n{pluginVmPageSubtext}"
- on-click="onSubpageClick_"></cr-link-row>
- </div>
-
- <template is="dom-if" route-path="/pluginVm/details">
- <settings-subpage
- associated-control="[[$$('#plugin-vm')]]"
- page-title="$i18n{pluginVmPageLabel}">
- <settings-plugin-vm-subpage prefs="{{prefs}}">
- </settings-plugin-vm-subpage>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/pluginVm/sharedPaths">
- <settings-subpage
- associated-control="[[$$('#plugin-vm')]]"
- page-title="$i18n{pluginVmSharedPaths}">
- <settings-plugin-vm-shared-paths prefs="{{prefs}}">
- </settings-plugin-vm-shared-paths>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- </template>
- <script src="plugin_vm_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.js b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.js
deleted file mode 100644
index a0f79eb112d..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_page.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'plugin-vm-page' is the settings page for Plugin VM.
- */
-
-Polymer({
- is: 'settings-plugin-vm-page',
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.PLUGIN_VM_DETAILS) {
- map.set(settings.routes.PLUGIN_VM_DETAILS.path, '#plugin-vm');
- }
- if (settings.routes.PLUGIN_VM_SHARED_PATHS) {
- map.set(settings.routes.PLUGIN_VM_SHARED_PATHS.path, '#plugin-vm');
- }
- return map;
- },
- },
- },
-
- /** @private */
- onSubpageClick_: function(event) {
- settings.navigateTo(settings.routes.PLUGIN_VM_DETAILS);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.html b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.html
deleted file mode 100644
index 25e651ce204..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="plugin_vm_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-plugin-vm-shared-paths">
- <template>
- <style include="settings-shared"></style>
- <div class="settings-box first">
- <div>
- $i18n{pluginVmSharedPathsInstructionsAdd}
- <span id="pluginVmInstructionsRemove"
- hidden="[[!sharedPaths_.length]]">
- $i18n{pluginVmSharedPathsInstructionsRemove}
- </span>
- </div>
- </div>
- <div class="settings-box continuation">
- <h2 class="start">$i18n{pluginVmSharedPathsListHeading}</h2>
- </div>
- <div class="list-frame vertical-list">
- <template is="dom-repeat" items="[[sharedPaths_]]">
- <div class="list-item">
- <div class="start">[[item.pathDisplayText]]</div>
- <cr-icon-button class="icon-clear"
- on-click="onRemoveSharedPathClick_"
- title="$i18n{pluginVmSharedPathsRemoveSharing}"></cr-icon-button>
- </div>
- </template>
- </div>
- </template>
- <script src="plugin_vm_shared_paths.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.js b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.js
deleted file mode 100644
index ffb0697a9ed..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_shared_paths.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'plugin-vm-shared-paths' is the settings shared paths subpage for Plugin VM.
- */
-
-(function() {
-
-/**
- * The Plugin VM is named 'PvmDefault'.
- * https://cs.chromium.org/chromium/src/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h?q=kPluginVmName
- * @type {string}
- */
-const PLUGIN_VM = 'PvmDefault';
-
-Polymer({
- is: 'settings-plugin-vm-shared-paths',
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * The shared path string suitable for display in the UI.
- * @private {Array<!{path: string, pathDisplayText: string}>}
- */
- sharedPaths_: Array,
- },
-
- observers: [
- 'onPluginVmSharedPathsChanged_(prefs.guest_os.paths_shared_to_vms.value)'
- ],
-
- /**
- * @param {!Object<!Array<string>>} paths
- * @private
- */
- onPluginVmSharedPathsChanged_: function(paths) {
- const vmPaths = [];
- for (const path in paths) {
- const vms = paths[path];
- if (vms.includes(PLUGIN_VM)) {
- vmPaths.push(path);
- }
- }
- settings.PluginVmBrowserProxyImpl.getInstance()
- .getPluginVmSharedPathsDisplayText(vmPaths)
- .then(text => {
- this.sharedPaths_ = vmPaths.map(
- (path, i) => ({path: path, pathDisplayText: text[i]}));
- });
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onRemoveSharedPathClick_: function(event) {
- settings.PluginVmBrowserProxyImpl.getInstance().removePluginVmSharedPath(
- PLUGIN_VM, event.model.item.path);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.html b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.html
deleted file mode 100644
index e8bdfa6ed65..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-
-<dom-module id="settings-plugin-vm-subpage">
- <template>
- <style include="settings-shared"></style>
- <div id="plugin-vm-shared-paths" class="settings-box first"
- on-click="onSharedPathsClick_" actionable>
- <div class="start">$i18n{pluginVmSharedPaths}</div>
- <cr-icon-button class="subpage-arrow"
- aria-label="$i18n{pluginVmSharedPaths}"></cr-icon-button>
- </div>
- <!-- TODO(timloh): Wire this up to a pref. -->
- <settings-toggle-button label="$i18n{pluginVmPrinterAccess}">
- </settings-toggle-button>
- </template>
- <script src="plugin_vm_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.js b/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.js
deleted file mode 100644
index 47f27372a89..00000000000
--- a/chromium/chrome/browser/resources/settings/plugin_vm_page/plugin_vm_subpage.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'plugin-vm-subpage' is the settings subpage for managing Plugin VM.
- */
-
-Polymer({
- is: 'settings-plugin-vm-subpage',
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
- },
-
- /** @private */
- onSharedPathsClick_: function() {
- settings.navigateTo(settings.routes.PLUGIN_VM_SHARED_PATHS);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/prefs/pref_util.html b/chromium/chrome/browser/resources/settings/prefs/pref_util.html
deleted file mode 100644
index 8ebccbc7499..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/pref_util.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="pref_util.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/prefs/pref_util.js b/chromium/chrome/browser/resources/settings/prefs/pref_util.js
deleted file mode 100644
index fd5a45a3ce8..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/pref_util.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Utility functions to help use prefs in Polymer controls. */
-
-// TODO(michaelpg): converge with other WebUI on capitalization. This is
-// consistent with Settings, but WebUI uses lower.underscore_case.
-cr.define('Settings.PrefUtil', function() {
- /**
- * Converts a string value to a type corresponding to the given preference.
- * @param {string} value
- * @param {!chrome.settingsPrivate.PrefObject} pref
- * @return {boolean|number|string|undefined}
- */
- function stringToPrefValue(value, pref) {
- switch (pref.type) {
- case chrome.settingsPrivate.PrefType.BOOLEAN:
- return value == 'true';
- case chrome.settingsPrivate.PrefType.NUMBER:
- const n = parseFloat(value);
- if (isNaN(n)) {
- console.error(
- 'Argument to stringToPrefValue for number pref ' +
- 'was unparsable: ' + value);
- return undefined;
- }
- return n;
- case chrome.settingsPrivate.PrefType.STRING:
- case chrome.settingsPrivate.PrefType.URL:
- return value;
- default:
- assertNotReached('No conversion from string to ' + pref.type + ' pref');
- }
- }
-
- /**
- * Returns the value of the pref as a string.
- * @param {!chrome.settingsPrivate.PrefObject} pref
- * @return {string}
- */
- function prefToString(pref) {
- switch (pref.type) {
- case chrome.settingsPrivate.PrefType.BOOLEAN:
- case chrome.settingsPrivate.PrefType.NUMBER:
- return pref.value.toString();
- case chrome.settingsPrivate.PrefType.STRING:
- case chrome.settingsPrivate.PrefType.URL:
- return /** @type {string} */ (pref.value);
- default:
- assertNotReached('No conversion from ' + pref.type + ' pref to string');
- }
- }
- return {
- stringToPrefValue: stringToPrefValue,
- prefToString: prefToString,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs.html b/chromium/chrome/browser/resources/settings/prefs/prefs.html
deleted file mode 100644
index ca69b76351f..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/prefs.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="prefs_types.html">
-
-<script src="prefs.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs.js b/chromium/chrome/browser/resources/settings/prefs/prefs.js
deleted file mode 100644
index 55626d18fae..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/prefs.js
+++ /dev/null
@@ -1,335 +0,0 @@
-/* Copyright 2015 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-/**
- * @fileoverview
- * 'settings-prefs' exposes a singleton model of Chrome settings and
- * preferences, which listens to changes to Chrome prefs whitelisted in
- * chrome.settingsPrivate. When changing prefs in this element's 'prefs'
- * property via the UI, the singleton model tries to set those preferences in
- * Chrome. Whether or not the calls to settingsPrivate.setPref succeed, 'prefs'
- * is eventually consistent with the Chrome pref store.
- */
-
-(function() {
-'use strict';
-
-/**
- * Checks whether two values are recursively equal. Only compares serializable
- * data (primitives, serializable arrays and serializable objects).
- * @param {*} val1 Value to compare.
- * @param {*} val2 Value to compare with val1.
- * @return {boolean} True if the values are recursively equal.
- */
-function deepEqual(val1, val2) {
- if (val1 === val2) {
- return true;
- }
-
- if (Array.isArray(val1) || Array.isArray(val2)) {
- if (!Array.isArray(val1) || !Array.isArray(val2)) {
- return false;
- }
- return arraysEqual(
- /** @type {!Array} */ (val1),
- /** @type {!Array} */ (val2));
- }
-
- if (val1 instanceof Object && val2 instanceof Object) {
- return objectsEqual(val1, val2);
- }
-
- return false;
-}
-
-/**
- * @param {!Array} arr1
- * @param {!Array} arr2
- * @return {boolean} True if the arrays are recursively equal.
- */
-function arraysEqual(arr1, arr2) {
- if (arr1.length != arr2.length) {
- return false;
- }
-
- for (let i = 0; i < arr1.length; i++) {
- if (!deepEqual(arr1[i], arr2[i])) {
- return false;
- }
- }
-
- return true;
-}
-
-/**
- * @param {!Object} obj1
- * @param {!Object} obj2
- * @return {boolean} True if the objects are recursively equal.
- */
-function objectsEqual(obj1, obj2) {
- const keys1 = Object.keys(obj1);
- const keys2 = Object.keys(obj2);
- if (keys1.length != keys2.length) {
- return false;
- }
-
- for (let i = 0; i < keys1.length; i++) {
- const key = keys1[i];
- if (!deepEqual(obj1[key], obj2[key])) {
- return false;
- }
- }
-
- return true;
-}
-
-/**
- * Returns a recursive copy of the value.
- * @param {*} val Value to copy. Should be a primitive or only contain
- * serializable data (primitives, serializable arrays and
- * serializable objects).
- * @return {*} A deep copy of the value.
- */
-function deepCopy(val) {
- if (!(val instanceof Object)) {
- return val;
- }
- return Array.isArray(val) ? deepCopyArray(/** @type {!Array} */ (val)) :
- deepCopyObject(val);
-}
-
-/**
- * @param {!Array} arr
- * @return {!Array} Deep copy of the array.
- */
-function deepCopyArray(arr) {
- const copy = [];
- for (let i = 0; i < arr.length; i++) {
- copy.push(deepCopy(arr[i]));
- }
- return copy;
-}
-
-/**
- * @param {!Object} obj
- * @return {!Object} Deep copy of the object.
- */
-function deepCopyObject(obj) {
- const copy = {};
- const keys = Object.keys(obj);
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- copy[key] = deepCopy(obj[key]);
- }
- return copy;
-}
-
-Polymer({
- is: 'settings-prefs',
-
- properties: {
- /**
- * Object containing all preferences, for use by Polymer controls.
- * @type {Object|undefined}
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Map of pref keys to values representing the state of the Chrome
- * pref store as of the last update from the API.
- * @type {Object<*>}
- * @private
- */
- lastPrefValues_: {
- type: Object,
- value: function() {
- return {};
- },
- },
- },
-
- observers: [
- 'prefsChanged_(prefs.*)',
- ],
-
- /** @type {SettingsPrivate} */
- settingsApi_: /** @type {SettingsPrivate} */ (chrome.settingsPrivate),
-
- /** @override */
- created: function() {
- if (!CrSettingsPrefs.deferInitialization) {
- this.initialize();
- }
- },
-
- /** @override */
- detached: function() {
- CrSettingsPrefs.resetForTesting();
- },
-
- /**
- * @param {SettingsPrivate=} opt_settingsApi SettingsPrivate implementation
- * to use (chrome.settingsPrivate by default).
- */
- initialize: function(opt_settingsApi) {
- // Only initialize once (or after resetForTesting() is called).
- if (this.initialized_) {
- return;
- }
- this.initialized_ = true;
-
- if (opt_settingsApi) {
- this.settingsApi_ = opt_settingsApi;
- }
-
- /** @private {function(!Array<!chrome.settingsPrivate.PrefObject>)} */
- this.boundPrefsChanged_ = this.onSettingsPrivatePrefsChanged_.bind(this);
- this.settingsApi_.onPrefsChanged.addListener(this.boundPrefsChanged_);
- this.settingsApi_.getAllPrefs(
- this.onSettingsPrivatePrefsFetched_.bind(this));
- },
-
- /**
- * @param {!{path: string}} e
- * @private
- */
- prefsChanged_: function(e) {
- // |prefs| can be directly set or unset in tests.
- if (!CrSettingsPrefs.isInitialized || e.path == 'prefs') {
- return;
- }
-
- const key = this.getPrefKeyFromPath_(e.path);
- const prefStoreValue = this.lastPrefValues_[key];
-
- const prefObj = /** @type {chrome.settingsPrivate.PrefObject} */ (
- this.get(key, this.prefs));
-
- // If settingsPrivate already has this value, ignore it. (Otherwise,
- // a change event from settingsPrivate could make us call
- // settingsPrivate.setPref and potentially trigger an IPC loop.)
- if (!deepEqual(prefStoreValue, prefObj.value)) {
- this.settingsApi_.setPref(
- key, prefObj.value,
- /* pageId */ '',
- /* callback */ this.setPrefCallback_.bind(this, key));
- }
- },
-
- /**
- * Called when prefs in the underlying Chrome pref store are changed.
- * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs
- * The prefs that changed.
- * @private
- */
- onSettingsPrivatePrefsChanged_: function(prefs) {
- if (CrSettingsPrefs.isInitialized) {
- this.updatePrefs_(prefs);
- }
- },
-
- /**
- * Called when prefs are fetched from settingsPrivate.
- * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs
- * @private
- */
- onSettingsPrivatePrefsFetched_: function(prefs) {
- this.updatePrefs_(prefs);
- CrSettingsPrefs.setInitialized();
- },
-
- /**
- * Checks the result of calling settingsPrivate.setPref.
- * @param {string} key The key used in the call to setPref.
- * @param {boolean} success True if setting the pref succeeded.
- * @private
- */
- setPrefCallback_: function(key, success) {
- if (!success) {
- this.refresh(key);
- }
- },
-
- /**
- * Get the current pref value from chrome.settingsPrivate to ensure the UI
- * stays up to date.
- * @param {string} key
- */
- refresh: function(key) {
- this.settingsApi_.getPref(key, pref => {
- this.updatePrefs_([pref]);
- });
- },
-
- /**
- * Updates the prefs model with the given prefs.
- * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs
- * @private
- */
- updatePrefs_: function(newPrefs) {
- // Use the existing prefs object or create it.
- const prefs = this.prefs || {};
- newPrefs.forEach(function(newPrefObj) {
- // Use the PrefObject from settingsPrivate to create a copy in
- // lastPrefValues_ at the pref's key.
- this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value);
-
- if (!deepEqual(this.get(newPrefObj.key, prefs), newPrefObj)) {
- // Add the pref to |prefs|.
- cr.exportPath(newPrefObj.key, newPrefObj, prefs);
- // If this.prefs already exists, notify listeners of the change.
- if (prefs == this.prefs) {
- this.notifyPath('prefs.' + newPrefObj.key, newPrefObj);
- }
- }
- }, this);
- if (!this.prefs) {
- this.prefs = prefs;
- }
- },
-
- /**
- * Given a 'property-changed' path, returns the key of the preference the
- * path refers to. E.g., if the path of the changed property is
- * 'prefs.search.suggest_enabled.value', the key of the pref that changed is
- * 'search.suggest_enabled'.
- * @param {string} path
- * @return {string}
- * @private
- */
- getPrefKeyFromPath_: function(path) {
- // Skip the first token, which refers to the member variable (this.prefs).
- const parts = path.split('.');
- assert(parts.shift() == 'prefs', 'Path doesn\'t begin with \'prefs\'');
-
- for (let i = 1; i <= parts.length; i++) {
- const key = parts.slice(0, i).join('.');
- // The lastPrefValues_ keys match the pref keys.
- if (this.lastPrefValues_.hasOwnProperty(key)) {
- return key;
- }
- }
- return '';
- },
-
- /**
- * Resets the element so it can be re-initialized with a new prefs state.
- */
- resetForTesting: function() {
- if (!this.initialized_) {
- return;
- }
- this.prefs = undefined;
- this.lastPrefValues_ = {};
- this.initialized_ = false;
- // Remove the listener added in initialize().
- this.settingsApi_.onPrefsChanged.removeListener(this.boundPrefsChanged_);
- this.settingsApi_ =
- /** @type {SettingsPrivate} */ (chrome.settingsPrivate);
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs_behavior.html b/chromium/chrome/browser/resources/settings/prefs/prefs_behavior.html
deleted file mode 100644
index 31dc603f202..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/prefs_behavior.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="prefs_behavior.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs_behavior.js b/chromium/chrome/browser/resources/settings/prefs/prefs_behavior.js
deleted file mode 100644
index f9ba9d80076..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/prefs_behavior.js
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Common prefs behavior.
- */
-
-/** @polymerBehavior */
-const PrefsBehavior = {
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
- },
-
- /**
- * Gets the pref at the given prefPath. Throws if the pref is not found.
- * @param {string} prefPath
- * @return {!chrome.settingsPrivate.PrefObject}
- * @protected
- */
- getPref: function(prefPath) {
- const pref = /** @type {!chrome.settingsPrivate.PrefObject} */ (
- this.get(prefPath, this.prefs));
- assert(typeof pref != 'undefined', 'Pref is missing: ' + prefPath);
- return pref;
- },
-
- /**
- * Sets the value of the pref at the given prefPath. Throws if the pref is not
- * found.
- * @param {string} prefPath
- * @param {*} value
- * @protected
- */
- setPrefValue: function(prefPath, value) {
- this.getPref(prefPath); // Ensures we throw if the pref is not found.
- this.set('prefs.' + prefPath + '.value', value);
- },
-
- /**
- * Appends the item to the pref list at the given key if the item is not
- * already in the list. Asserts if the pref itself is not found or is not an
- * Array type.
- * @param {string} key
- * @param {*} item
- * @protected
- */
- appendPrefListItem: function(key, item) {
- const pref = this.getPref(key);
- assert(pref && pref.type == chrome.settingsPrivate.PrefType.LIST);
- if (pref.value.indexOf(item) == -1) {
- this.push('prefs.' + key + '.value', item);
- }
- },
-
- /**
- * Deletes the given item from the pref at the given key if the item is found.
- * Asserts if the pref itself is not found or is not an Array type.
- * @param {string} key
- * @param {*} item
- * @protected
- */
- deletePrefListItem: function(key, item) {
- assert(this.getPref(key).type == chrome.settingsPrivate.PrefType.LIST);
- this.arrayDelete('prefs.' + key + '.value', item);
- },
-};
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs_types.html b/chromium/chrome/browser/resources/settings/prefs/prefs_types.html
deleted file mode 100644
index be2c1ca075a..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/prefs_types.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="prefs_types.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs_types.js b/chromium/chrome/browser/resources/settings/prefs/prefs_types.js
deleted file mode 100644
index a26242af638..00000000000
--- a/chromium/chrome/browser/resources/settings/prefs/prefs_types.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Types for CrSettingsPrefsElement.
- */
-
-/**
- * Global state for prefs status.
- */
-const CrSettingsPrefs = (function() {
- const CrSettingsPrefsInternal = {
- /**
- * Resolves the CrSettingsPrefs.initialized promise.
- */
- setInitialized: function() {
- /** @public {boolean} */
- CrSettingsPrefsInternal.isInitialized = true;
- CrSettingsPrefsInternal.resolve_();
- },
-
- /**
- * Restores state for testing.
- */
- resetForTesting: function() {
- CrSettingsPrefsInternal.setup_();
- },
-
- /**
- * Whether to defer initialization. Used in testing to prevent premature
- * initialization when intending to fake the settings API.
- * @type {boolean}
- */
- deferInitialization: false,
-
- /**
- * Called to set up the promise and resolve methods.
- * @private
- */
- setup_: function() {
- CrSettingsPrefsInternal.isInitialized = false;
- /**
- * Promise to be resolved when all settings have been initialized.
- * @type {!Promise}
- */
- CrSettingsPrefsInternal.initialized = new Promise(function(resolve) {
- CrSettingsPrefsInternal.resolve_ = resolve;
- });
- },
- };
-
- CrSettingsPrefsInternal.setup_();
-
- return CrSettingsPrefsInternal;
-})();
diff --git a/chromium/chrome/browser/resources/settings/printing_page/OWNERS b/chromium/chrome/browser/resources/settings/printing_page/OWNERS
deleted file mode 100644
index 1100e9746d7..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-khorimoto@chromium.org
-stevenjb@chromium.org
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cloud_printers.html b/chromium/chrome/browser/resources/settings/printing_page/cloud_printers.html
deleted file mode 100644
index 21bcb4b9989..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cloud_printers.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-cloud-printers">
- <template>
- <style include="settings-shared"></style>
- <div class="settings-box first">
- <span>
- $i18n{printingCloudPrintLearnMoreLabel}
- <a href="$i18n{printingCloudPrintLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </span>
- </div>
- <settings-toggle-button
- pref="{{prefs.local_discovery.notifications_enabled}}"
- label="$i18n{printingNotificationsLabel}">
- </settings-toggle-button>
- <a class="settings-box inherit-color no-outline" tabindex="-1"
- target="_blank" href="$i18n{devicesUrl}">
- <div class="start">
- $i18n{printingManageCloudPrintDevices}
- </div>
- <cr-icon-button actionable class="icon-external"
- aria-label="$i18n{printingManageCloudPrintDevices}"></cr-icon-button>
- </a>
- </template>
- <script src="cloud_printers.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cloud_printers.js b/chromium/chrome/browser/resources/settings/printing_page/cloud_printers.js
deleted file mode 100644
index b7dcbd2a9b7..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cloud_printers.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cloud-printers' is a component for showing Google
- * Cloud Printer settings subpage (chrome://settings/cloudPrinters).
- */
-// TODO(xdai): Rename it to 'settings-cloud-printers-page'.
-Polymer({
- is: 'settings-cloud-printers',
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html b/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
deleted file mode 100644
index f19b1047748..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
+++ /dev/null
@@ -1,288 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="cups_add_printer_dialog_elements.html">
-<link rel="import" href="cups_printer_dialog_util.html">
-<link rel="import" href="cups_printer_shared_css.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-
-<dom-module id="add-printer-discovery-dialog">
- <template>
- <style include="cr-shared-style cups-printer-shared">
- add-printer-list {
- max-height: 310px;
- overflow-y: auto;
- position: absolute;
- width: 100%;
- }
-
- #searchSpinner {
- position: absolute;
- top: 80%;
- }
-
- #searchSpinner paper-spinner-lite {
- --paper-spinner-stroke-width: 2px;
- height: 15px;
- margin-inline-end: 3px;
- margin-inline-start: 20px;
- width: 15px;
- }
- </style>
- <add-printer-dialog>
- <div slot="dialog-title">$i18n{addPrintersNearbyTitle}</div>
- <div slot="dialog-body">
- <add-printer-list printers="[[discoveredPrinters]]"
- selected-printer="{{selectedPrinter}}">
- </add-printer-list>
- <div class="center" id="noPrinterMessage"
- hidden="[[discoveredPrinters.length]]">
- $i18n{noPrinterNearbyMessage}
- </div>
- <div id="searchSpinner" hidden="[[!discovering_]]">
- <paper-spinner-lite active="[[discovering_]]"></paper-spinner-lite>
- <span>$i18n{searchingNearbyPrinters}</span>
- </div>
- </div>
- <div slot="dialog-buttons">
- <div> <!-- Left group -->
- <cr-button id="manuallyAddPrinterButton"
- on-click="switchToManualAddDialog_">
- $i18n{manuallyAddPrinterButtonText}
- </cr-button>
- </div>
- <div> <!-- Right group -->
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" id="addPrinterButton"
- disabled="[[!canAddPrinter_(selectedPrinter)]]"
- on-click="switchToConfiguringDialog_">
- $i18n{addPrinterButtonText}
- </cr-button>
- </div>
- </div>
- </add-printer-dialog>
- </template>
-</dom-module>
-
-<dom-module id="add-printer-manually-dialog">
- <template>
- <style include="cups-printer-shared"></style>
- <add-printer-dialog>
- <div slot="dialog-title">
- $i18n{addPrintersManuallyTitle}
- <div id="general-error-container" hidden="[[!errorText_]]">
- <div id="general-error">
- <iron-icon id="general-error-icon" icon="cr:warning"></iron-icon>
- <div id="general-error-message">
- [[errorText_]]
- </div>
- </div>
- </div>
- </div>
- <div slot="dialog-body">
- <div class="settings-box first two-line">
- <cr-input class="printer-name-input" autofocus
- id="printerNameInput" value="{{newPrinter.printerName}}"
- label="$i18n{printerName}" maxlength=64>
- </cr-input>
- </div>
- <div class="settings-box two-line">
- <cr-input id="printerAddressInput" label="$i18n{printerAddress}"
- value="{{newPrinter.printerAddress}}" maxlength=63
- error-message="$i18n{ippPrinterUnreachable}">
- </cr-input>
- </div>
- <div class="settings-box two-line">
- <div class="start">
- <div id="printerProtocol" class="cr-form-field-label">
- $i18n{printerProtocol}
- </div>
- <div class="secondary">
- <select class="md-select" aria-labelledby="printerProtocol"
- value="[[newPrinter.printerProtocol]]"
- on-change="onProtocolChange_">
- <option value="ipp">$i18n{printerProtocolIpp}</option>
- <option value="ipps">$i18n{printerProtocolIpps}</option>
- <option value="http">$i18n{printerProtocolHttp}</option>
- <option value="https">$i18n{printerProtocolHttps}</option>
- <option value="socket">$i18n{printerProtocolAppSocket}
- </option>
- <option value="lpd">$i18n{printerProtocolLpd}</option>
- </select>
- </div>
- </div>
- </div>
- <div class="settings-box two-line">
- <cr-input label="$i18n{printerQueue}"
- value="{{newPrinter.printerQueue}}" maxlength=64>
- </cr-input>
- </div>
- </div>
- <div slot="dialog-buttons">
- <div> <!-- Left group -->
- <cr-button on-click="switchToDiscoveryDialog_"
- hidden="[[enableUpdatedUi]]">
- $i18n{discoverPrintersButtonText}
- </cr-button>
- </div>
- <div> <!-- Right group -->
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button id="addPrinterButton" class="action-button"
- on-click="addPressed_"
- disabled="[[!canAddPrinter_(newPrinter.*,
- addPrinterInProgress_)]]">
- $i18n{addPrinterButtonText}
- </cr-button>
- </div>
- </div>
- </add-printer-dialog>
- </template>
-</dom-module>
-
-<dom-module id="add-printer-manufacturer-model-dialog">
- <template>
- <style include="cups-printer-shared">
- .subtext {
- margin-bottom: 10px;
- padding-inline-end: 20px;
- padding-inline-start: 20px;
- }
- </style>
- <add-printer-dialog>
- <div slot="dialog-title">$i18n{manufacturerAndModelDialogTitle}
- <div id="general-error-container" hidden="[[!errorText_]]">
- <div id="general-error">
- <iron-icon id="general-error-icon" icon="cr:warning"></iron-icon>
- <div id="general-error-message">
- [[errorText_]]
- </div>
- </div>
- </div>
- </div>
- <div slot="dialog-body">
- <div class="subtext" id="makeModelTextInfo">
- <span>[[getManufacturerAndModelSubtext_(activePrinter.*)]]
- </span>
- <a href="$i18n{printingCUPSPrintLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </div>
- <div class="settings-box two-line">
- <cr-searchable-drop-down id="manufacturerDropdown"
- items="[[manufacturerList]]"
- label="$i18n{printerManufacturer}"
- value="{{activePrinter.ppdManufacturer}}">
- </cr-searchable-drop-down>
- </div>
- <div class="settings-box two-line">
- <cr-searchable-drop-down id="modelDropdown"
- items="[[modelList]]"
- label="$i18n{printerModel}"
- value="{{activePrinter.ppdModel}}">
- </cr-searchable-drop-down>
- </div>
- <div id="ppdLabel" class="cr-form-field-label">
- <span>$i18n{selectDriver}</span>
- <a href="$i18n{printingCUPSPrintPpdLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </div>
- <div class="settings-box two-line">
- <cr-input class="browse-file-input" readonly value="[[newUserPPD_]]"
- aria-labelledby="ppdLabel" invalid="[[invalidPPD_]]"
- error-message="$i18n{selectDriverErrorMessage}" tabindex="-1">
- </cr-input>
- <cr-button class="browse-button" on-click="onBrowseFile_">
- $i18n{selectDriverButtonText}
- </cr-button>
- </div>
- <div class="eula" id="eulaUrl" hidden="[[!eulaUrl_]]">
- <a href="[[eulaUrl_]]" target="_blank">$i18n{printerEulaNotice}</a>
- </div>
- </div>
- <div slot="dialog-buttons">
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" id="addPrinterButton"
- disabled="[[!canAddPrinter_(activePrinter.ppdManufacturer,
- activePrinter.ppdModel,
- activePrinter.printerPPDPath,
- addPrinterInProgress_)]]"
- on-click="addPrinter_">
- $i18n{addPrinterButtonText}
- </cr-button>
- </div>
- </add-printer-dialog>
- </template>
-</dom-module>
-
-<dom-module id="add-printer-configuring-dialog">
- <template>
- <style include="cups-printer-shared">
- [slot='dialog-body'] {
- padding-top: 140px;
- text-align: center;
- }
- </style>
- <add-printer-dialog>
- <div slot="dialog-title">[[dialogTitle]]</div>
- <div slot="dialog-body">
- <paper-spinner-lite active></paper-spinner-lite>
- <div id="configuringMessage">$i18n{printerConfiguringMessage}</div>
- </div>
- <div slot="dialog-buttons">
- <cr-button class="cancel-button" on-click="onCloseConfiguringTap_">
- $i18n{close}
- </cr-button>
- </div>
- </add-printer-dialog>
- </template>
-</dom-module>
-
-<dom-module id="settings-cups-add-printer-dialog">
- <template>
- <style include="settings-shared"></style>
-
- <!-- Printer Discovery Dialog -->
- <template is="dom-if" if="[[showDiscoveryDialog_]]" restamp>
- <add-printer-discovery-dialog selected-printer="{{newPrinter}}">
- </add-printer-discovery-dialog>
- </template>
-
- <!-- Manually Add Printer Dialog -->
- <template is="dom-if" if="[[showManuallyAddDialog_]]" restamp>
- <add-printer-manually-dialog new-printer="{{newPrinter}}"
- enable-updated-ui="[[enableUpdatedUi]]">
- </add-printer-manually-dialog>
- </template>
-
- <!-- Configuring Printer Dialog -->
- <template is="dom-if" if="[[showConfiguringDialog_]]" restamp>
- <add-printer-configuring-dialog
- printer-name="[[newPrinter.printerName]]"
- dialog-title="[[configuringDialogTitle]]">
- </add-printer-configuring-dialog>
- </template>
-
- <!-- Manufacturer and Model Dialog -->
- <template is="dom-if" if="[[showManufacturerDialog_]]" restamp>
- <add-printer-manufacturer-model-dialog active-printer="{{newPrinter}}">
- </add-printer-manufacturer-model-dialog>
- </template>
-
- </template>
- <script src="cups_add_printer_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js b/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
deleted file mode 100644
index 74f6940dfbb..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
+++ /dev/null
@@ -1,808 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cups-add-printer-dialog' includes multiple dialogs to
- * set up a new CUPS printer.
- * Subdialogs include:
- * - 'add-printer-discovery-dialog' is a dialog showing discovered printers on
- * the network that are available for setup.
- * - 'add-printer-manually-dialog' is a dialog in which user can manually enter
- * the information to set up a new printer.
- * - 'add-printer-configuring-dialog' is the configuring-in-progress dialog.
- * - 'add-printer-manufacturer-model-dialog' is a dialog in which the user can
- * manually select the manufacture and model of the new printer.
- */
-
-/**
- * Different dialogs in add printer flow.
- * @enum {string}
- */
-const AddPrinterDialogs = {
- DISCOVERY: 'add-printer-discovery-dialog',
- MANUALLY: 'add-printer-manually-dialog',
- CONFIGURING: 'add-printer-configuring-dialog',
- MANUFACTURER: 'add-printer-manufacturer-model-dialog',
-};
-
-/**
- * The maximum height of the discovered printers list when the searching spinner
- * is not showing.
- * @type {number}
- */
-const kPrinterListFullHeight = 350;
-
-/**
- * Return a reset CupsPrinterInfo object.
- * @return {!CupsPrinterInfo}
- */
-function getEmptyPrinter_() {
- return {
- ppdManufacturer: '',
- ppdModel: '',
- printerAddress: '',
- printerDescription: '',
- printerId: '',
- printerManufacturer: '',
- printerModel: '',
- printerMakeAndModel: '',
- printerName: '',
- printerPPDPath: '',
- printerPpdReference: {
- userSuppliedPpdUrl: '',
- effectiveMakeAndModel: '',
- autoconf: false,
- },
- printerPpdReferenceResolved: false,
- printerProtocol: 'ipp',
- printerQueue: 'ipp/print',
- printerStatus: '',
- };
-}
-
-Polymer({
- is: 'add-printer-discovery-dialog',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /** @type {!Array<!CupsPrinterInfo>|undefined} */
- discoveredPrinters: {
- type: Array,
- value: () => [],
- },
-
- /** @type {!CupsPrinterInfo} */
- selectedPrinter: {
- type: Object,
- notify: true,
- },
-
- discovering_: {
- type: Boolean,
- value: true,
- },
-
- /**
- * TODO(jimmyxgong): Remove this feature flag conditional once feature
- * is launched.
- * @private
- */
- enableUpdatedUi: Boolean,
- },
-
- /** @override */
- ready: function() {
- if (this.enableUpdatedUi) {
- return;
- }
-
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .startDiscoveringPrinters();
- this.addWebUIListener(
- 'on-nearby-printers-changed', this.onNearbyPrintersChanged_.bind(this));
- this.addWebUIListener(
- 'on-printer-discovery-done', this.onPrinterDiscoveryDone_.bind(this));
- },
-
- close: function() {
- this.$$('add-printer-dialog').close();
- },
-
- /**
- * @param {!Array<!CupsPrinterInfo>} automaticPrinters
- * @param {!Array<!CupsPrinterInfo>} discoveredPrinters
- * @private
- */
- onNearbyPrintersChanged_: function(automaticPrinters, discoveredPrinters) {
- this.discovering_ = true;
- this.set(
- 'discoveredPrinters', automaticPrinters.concat(discoveredPrinters));
- },
-
- /** @private */
- onPrinterDiscoveryDone_: function() {
- this.discovering_ = false;
- this.$$('add-printer-list').style.maxHeight = kPrinterListFullHeight + 'px';
-
- if (!this.discoveredPrinters.length) {
- this.selectedPrinter = getEmptyPrinter_();
- this.fire('no-detected-printer');
- }
- },
-
- /** @private */
- stopDiscoveringPrinters_: function() {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .stopDiscoveringPrinters();
- this.discovering_ = false;
- },
-
- /** @private */
- switchToManualAddDialog_: function() {
- // We're abandoning discovery in favor of manual specification, so
- // drop the selection if one exists.
- this.selectedPrinter = getEmptyPrinter_();
- this.close();
- this.fire('open-manually-add-printer-dialog');
-
- if (this.enableUpdatedUi) {
- return;
- }
-
- this.stopDiscoveringPrinters_();
- },
-
- /** @private */
- onCancelTap_: function() {
- this.close();
-
- if (this.enableUpdatedUi) {
- return;
- }
-
- this.stopDiscoveringPrinters_();
- },
-
- /** @private */
- switchToConfiguringDialog_: function() {
- this.close();
- this.fire('open-configuring-printer-dialog');
-
- if (this.enableUpdatedUi) {
- return;
- }
-
- this.stopDiscoveringPrinters_();
- },
-
- /**
- * @param {?CupsPrinterInfo} selectedPrinter
- * @return {boolean} Whether the add printer button is enabled.
- * @private
- */
- canAddPrinter_: function(selectedPrinter) {
- return !!selectedPrinter && !!selectedPrinter.printerName;
- },
-});
-
-Polymer({
- is: 'add-printer-manually-dialog',
-
- properties: {
- /** @type {!CupsPrinterInfo} */
- newPrinter: {type: Object, notify: true, value: getEmptyPrinter_},
-
- /** @private */
- addPrinterInProgress_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The error text to be displayed on the dialog.
- * @private
- */
- errorText_: {
- type: String,
- value: '',
- },
-
- /**
- * TODO(jimmyxgong): Remove this feature flag conditional once feature
- * is launched.
- * @private
- */
- enableUpdatedUi: Boolean,
- },
-
- observers: [
- 'printerInfoChanged_(newPrinter.*)',
- ],
-
- /** @private */
- switchToDiscoveryDialog_: function() {
- this.newPrinter = getEmptyPrinter_();
- this.$$('add-printer-dialog').close();
- this.fire('open-discovery-printers-dialog');
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$$('add-printer-dialog').close();
- },
-
- /**
- * Handler for addCupsPrinter success.
- * @param {!PrinterSetupResult} result
- * @private
- * */
- onAddPrinterSucceeded_: function(result) {
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: result, printerName: this.newPrinter.printerName});
- this.$$('add-printer-dialog').close();
- },
-
- /**
- * Handler for addCupsPrinter failure.
- * @param {*} result
- * @private
- * */
- onAddPrinterFailed_: function(result) {
- this.errorText_ = settings.printing.getErrorText(
- /** @type {PrinterSetupResult} */ (result));
- },
-
- /**
- * Handler for getPrinterInfo success.
- * @param {!PrinterMakeModel} info
- * @private
- * */
- onPrinterFound_: function(info) {
- const newPrinter =
- /** @type {CupsPrinterInfo} */ (Object.assign({}, this.newPrinter));
-
- newPrinter.printerManufacturer = info.manufacturer;
- newPrinter.printerModel = info.model;
- newPrinter.printerMakeAndModel = info.makeAndModel;
- newPrinter.printerPpdReference.userSuppliedPpdUrl =
- info.ppdRefUserSuppliedPpdUrl;
- newPrinter.printerPpdReference.effectiveMakeAndModel =
- info.ppdRefEffectiveMakeAndModel;
- newPrinter.printerPpdReference.autoconf = info.autoconf;
- newPrinter.printerPpdReferenceResolved = info.ppdReferenceResolved;
-
- this.newPrinter = newPrinter;
-
-
- // Add the printer if it's configurable. Otherwise, forward to the
- // manufacturer dialog.
- if (this.newPrinter.printerPpdReferenceResolved) {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .addCupsPrinter(this.newPrinter)
- .then(
- this.onAddPrinterSucceeded_.bind(this),
- this.onAddPrinterFailed_.bind(this));
- } else {
- this.$$('add-printer-dialog').close();
- this.fire('open-manufacturer-model-dialog');
- }
- },
-
- /**
- * Handler for getPrinterInfo failure.
- * @param {*} result a PrinterSetupResult with an error code indicating why
- * getPrinterInfo failed.
- * @private
- */
- infoFailed_: function(result) {
- this.addPrinterInProgress_ = false;
- if (result == PrinterSetupResult.PRINTER_UNREACHABLE) {
- this.$.printerAddressInput.invalid = true;
- return;
- }
- this.errorText_ = settings.printing.getErrorText(
- /** @type {PrinterSetupResult} */ (result));
- },
-
- /** @private */
- addPressed_: function() {
- this.addPrinterInProgress_ = true;
-
- if (this.newPrinter.printerProtocol == 'ipp' ||
- this.newPrinter.printerProtocol == 'ipps') {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getPrinterInfo(this.newPrinter)
- .then(this.onPrinterFound_.bind(this), this.infoFailed_.bind(this));
- } else {
- this.$$('add-printer-dialog').close();
- this.fire('open-manufacturer-model-dialog');
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onProtocolChange_: function(event) {
- this.set('newPrinter.printerProtocol', event.target.value);
- },
-
- /**
- * @return {boolean} Whether the add printer button is enabled.
- * @private
- */
- canAddPrinter_: function() {
- return !this.addPrinterInProgress_ &&
- settings.printing.isNameAndAddressValid(this.newPrinter);
- },
-
- /** @private */
- printerInfoChanged_: function() {
- this.$.printerAddressInput.invalid = false;
- this.errorText_ = '';
- },
-
-});
-
-Polymer({
- is: 'add-printer-manufacturer-model-dialog',
-
- properties: {
- /** @type {!CupsPrinterInfo} */
- activePrinter: {
- type: Object,
- notify: true,
- },
-
- /** @type {?Array<string>} */
- manufacturerList: Array,
-
- /** @type {?Array<string>} */
- modelList: Array,
-
- /**
- * Whether the user selected PPD file is valid.
- * @private
- */
- invalidPPD_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The base name of a newly selected PPD file.
- * @private
- */
- newUserPPD_: String,
-
- /**
- * The URL to a printer's EULA.
- * @private
- */
- eulaUrl_: {
- type: String,
- value: '',
- },
-
- /** @private */
- addPrinterInProgress_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The error text to be displayed on the dialog.
- * @private
- */
- errorText_: {
- type: String,
- value: '',
- },
- },
-
- observers: [
- 'selectedManufacturerChanged_(activePrinter.ppdManufacturer)',
- 'selectedModelChanged_(activePrinter.ppdModel)',
- ],
-
- /** @override */
- attached: function() {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterManufacturersList()
- .then(this.manufacturerListChanged_.bind(this));
- },
-
- close: function() {
- this.$$('add-printer-dialog').close();
- },
-
- /**
- * Handler for addCupsPrinter success.
- * @param {!PrinterSetupResult} result
- * @private
- * */
- onPrinterAddedSucceeded_: function(result) {
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: result, printerName: this.activePrinter.printerName});
- this.close();
- },
-
- /**
- * Handler for addCupsPrinter failure.
- * @param {*} result
- * @private
- * */
- onPrinterAddedFailed_: function(result) {
- this.addPrinterInProgress_ = false;
- this.errorText_ = settings.printing.getErrorText(
- /** @type {PrinterSetupResult} */ (result));
- },
-
- /**
- * If the printer is a nearby printer, return make + model with the subtext.
- * Otherwise, return printer name.
- * @return {string} The additional information subtext of the manufacturer and
- * model dialog.
- * @private
- */
- getManufacturerAndModelSubtext_: function() {
- if (this.activePrinter.printerMakeAndModel) {
- return loadTimeData.getStringF(
- 'manufacturerAndModelAdditionalInformation',
- this.activePrinter.printerMakeAndModel);
- }
- return loadTimeData.getStringF(
- 'manufacturerAndModelAdditionalInformation',
- this.activePrinter.printerName);
- },
-
- /**
- * @param {string} manufacturer The manufacturer for which we are retrieving
- * models.
- * @private
- */
- selectedManufacturerChanged_: function(manufacturer) {
- // Reset model if manufacturer is changed.
- this.set('activePrinter.ppdModel', '');
- this.modelList = [];
- if (manufacturer && manufacturer.length != 0) {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterModelsList(manufacturer)
- .then(this.modelListChanged_.bind(this));
- }
- },
-
- /**
- * Attempts to get the EULA Url if the selected printer has one.
- * @private
- */
- selectedModelChanged_: function() {
- this.errorText_ = '';
- if (!this.activePrinter.ppdManufacturer || !this.activePrinter.ppdModel) {
- // Do not check for an EULA unless both |ppdManufacturer| and |ppdModel|
- // are set. Set |eulaUrl_| to be empty in this case.
- this.onGetEulaUrlCompleted_('' /* eulaUrl */);
- return;
- }
-
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getEulaUrl(
- this.activePrinter.ppdManufacturer, this.activePrinter.ppdModel)
- .then(this.onGetEulaUrlCompleted_.bind(this));
- },
-
- /**
- * @param {string} eulaUrl The URL for the printer's EULA.
- * @private
- */
- onGetEulaUrlCompleted_: function(eulaUrl) {
- this.eulaUrl_ = eulaUrl;
- },
-
- /**
- * @param {!ManufacturersInfo} manufacturersInfo
- * @private
- */
- manufacturerListChanged_: function(manufacturersInfo) {
- if (!manufacturersInfo.success) {
- return;
- }
- this.manufacturerList = manufacturersInfo.manufacturers;
- if (this.activePrinter.ppdManufacturer.length != 0) {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterModelsList(this.activePrinter.ppdManufacturer)
- .then(this.modelListChanged_.bind(this));
- }
- },
-
- /**
- * @param {!ModelsInfo} modelsInfo
- * @private
- */
- modelListChanged_: function(modelsInfo) {
- if (modelsInfo.success) {
- this.modelList = modelsInfo.models;
- }
- },
-
- /** @private */
- onBrowseFile_: function() {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterPPDPath()
- .then(this.printerPPDPathChanged_.bind(this));
- },
-
- /**
- * @param {string} path The full path to the selected PPD file
- * @private
- */
- printerPPDPathChanged_: function(path) {
- this.set('activePrinter.printerPPDPath', path);
- this.invalidPPD_ = !path;
- this.newUserPPD_ = settings.printing.getBaseName(path);
- },
-
- /** @private */
- onCancelTap_: function() {
- this.close();
- settings.CupsPrintersBrowserProxyImpl.getInstance().cancelPrinterSetUp(
- this.activePrinter);
- },
-
- /** @private */
- addPrinter_: function() {
- this.addPrinterInProgress_ = true;
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .addCupsPrinter(this.activePrinter)
- .then(
- this.onPrinterAddedSucceeded_.bind(this),
- this.onPrinterAddedFailed_.bind(this));
- },
-
- /**
- * @param {string} ppdManufacturer
- * @param {string} ppdModel
- * @param {string} printerPPDPath
- * @return {boolean} Whether we have enough information to set up the printer
- * @private
- */
- canAddPrinter_: function(ppdManufacturer, ppdModel, printerPPDPath) {
- return !this.addPrinterInProgress_ &&
- settings.printing.isPPDInfoValid(
- ppdManufacturer, ppdModel, printerPPDPath);
- },
-});
-
-Polymer({
- is: 'add-printer-configuring-dialog',
-
- properties: {
- printerName: String,
- dialogTitle: String,
- },
-
- /** @override */
- attached: function() {
- this.$.configuringMessage.textContent =
- loadTimeData.getStringF('printerConfiguringMessage', this.printerName);
- },
-
- /** @private */
- onCloseConfiguringTap_: function() {
- this.close();
- },
-
- close: function() {
- this.$$('add-printer-dialog').close();
- },
-});
-
-Polymer({
- is: 'settings-cups-add-printer-dialog',
-
- properties: {
- /** @type {!CupsPrinterInfo} */
- newPrinter: {
- type: Object,
- },
-
- configuringDialogTitle: String,
-
- /** @private {string} */
- previousDialog_: String,
-
- /** @private {string} */
- currentDialog_: String,
-
- /** @private {boolean} */
- showDiscoveryDialog_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {boolean} */
- showManuallyAddDialog_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {boolean} */
- showConfiguringDialog_: {
- type: Boolean,
- value: false,
- },
-
- /** @private {boolean} */
- showManufacturerDialog_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * TODO(jimmyxgong): Remove this feature flag conditional once feature
- * is launched.
- * @private
- */
- enableUpdatedUi: Boolean,
- },
-
- listeners: {
- 'open-manually-add-printer-dialog': 'openManuallyAddPrinterDialog_',
- 'open-configuring-printer-dialog': 'openConfiguringPrinterDialog_',
- 'open-discovery-printers-dialog': 'openDiscoveryPrintersDialog_',
- 'open-manufacturer-model-dialog':
- 'openManufacturerModelDialogForCurrentPrinter_',
- 'no-detected-printer': 'onNoDetectedPrinter_',
- },
-
- /** Opens the Add printer discovery dialog. */
- open: function() {
- this.resetData_();
- if (this.enableUpdatedUi) {
- // The updated UI will remove the discovery dialog. Open the manual
- // dialog by default.
- this.switchDialog_(
- '', AddPrinterDialogs.MANUALLY, 'showManuallyAddDialog_');
- } else {
- this.switchDialog_(
- '', AddPrinterDialogs.DISCOVERY, 'showDiscoveryDialog_');
- }
- },
-
- /**
- * Reset all the printer data in the Add printer flow.
- * @private
- */
- resetData_: function() {
- if (this.newPrinter) {
- this.newPrinter = getEmptyPrinter_();
- }
- },
-
- /** @private */
- openManuallyAddPrinterDialog_: function() {
- this.switchDialog_(
- this.currentDialog_, AddPrinterDialogs.MANUALLY,
- 'showManuallyAddDialog_');
- },
-
- /** @private */
- openDiscoveryPrintersDialog_: function() {
- this.switchDialog_(
- this.currentDialog_, AddPrinterDialogs.DISCOVERY,
- 'showDiscoveryDialog_');
- },
-
- /** @private */
- switchToManufacturerDialog_: function() {
- this.$$('add-printer-configuring-dialog').close();
- this.openManufacturerModelDialogForCurrentPrinter_();
- },
-
- /** @private */
- openConfiguringPrinterDialog_: function() {
- this.switchDialog_(
- this.currentDialog_, AddPrinterDialogs.CONFIGURING,
- 'showConfiguringDialog_');
- if (this.previousDialog_ == AddPrinterDialogs.DISCOVERY) {
- this.configuringDialogTitle =
- loadTimeData.getString('addPrintersNearbyTitle');
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .addDiscoveredPrinter(this.newPrinter.printerId)
- .then(
- this.onAddingDiscoveredPrinterSucceeded_.bind(this),
- this.manuallyAddDiscoveredPrinter_.bind(this));
- } else {
- assertNotReached('Opening configuring dialog from invalid place');
- }
- },
-
- /** @private */
- openManufacturerModelDialogForCurrentPrinter_: function() {
- this.switchDialog_(
- this.currentDialog_, AddPrinterDialogs.MANUFACTURER,
- 'showManufacturerDialog_');
- },
-
- /** @param {!CupsPrinterInfo} printer */
- openManufacturerModelDialogForSpecifiedPrinter: function(printer) {
- this.newPrinter = printer;
- this.switchDialog_(
- '', AddPrinterDialogs.MANUFACTURER, 'showManufacturerDialog_');
- },
-
- /** @private */
- onNoDetectedPrinter_: function() {
- // If there is no detected printer, automatically open manually-add-printer
- // dialog only when the user opens the discovery-dialog through the
- // "ADD PRINTER" button.
- if (!this.previousDialog_) {
- this.$$('add-printer-discovery-dialog').close();
- this.newPrinter = getEmptyPrinter_();
- this.openManuallyAddPrinterDialog_();
- }
- },
-
- /**
- * Switch dialog from |fromDialog| to |toDialog|.
- * @param {string} fromDialog
- * @param {string} toDialog
- * @param {string} domIfBooleanName The name of the boolean variable
- * corresponding to the |toDialog|.
- * @private
- */
- switchDialog_: function(fromDialog, toDialog, domIfBooleanName) {
- this.previousDialog_ = fromDialog;
- this.currentDialog_ = toDialog;
-
- this.set(domIfBooleanName, true);
- this.async(function() {
- const dialog = this.$$(toDialog);
- dialog.addEventListener('close', () => {
- this.set(domIfBooleanName, false);
- });
- });
- },
-
- /**
- * Handler for addDiscoveredPrinter.
- * @param {!PrinterSetupResult} result
- * @private
- * */
- onAddingDiscoveredPrinterSucceeded_: function(result) {
- this.$$('add-printer-configuring-dialog').close();
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: result, printerName: this.newPrinter.printerName});
- },
-
- /**
- * Use the given printer as the starting point for a user-driven
- * add of a printer. This is called if we can't automatically configure
- * the printer, and need more information from the user.
- *
- * @param {*} printer
- * @private
- */
- manuallyAddDiscoveredPrinter_: function(printer) {
- this.newPrinter = /** @type {CupsPrinterInfo} */ (printer);
- this.switchToManufacturerDialog_();
- },
-
- /**
- * @param {boolean} success
- * @param {string} printerName
- * @private
- */
- onAddPrinter_: function(success, printerName) {
- // 'on-add-cups-printer' event might be triggered by editing an existing
- // printer, in which case there is no configuring dialog.
- if (this.$$('add-printer-configuring-dialog')) {
- this.$$('add-printer-configuring-dialog').close();
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.html b/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.html
deleted file mode 100644
index f7ed42a8229..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="cups_printer_dialog_util.html">
-<link rel="import" href="cups_printer_shared_css.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-
-<dom-module id="add-printer-list">
- <template>
- <style include="cups-printer-shared">
- .list-item {
- padding: 0 20px;
- }
- </style>
- <div>
- <array-selector id="arraySelector" items="[[printers]]"
- selected="{{selectedPrinter}}">
- </array-selector>
- <template is="dom-repeat" items="[[printers]]" sort="sort_">
- <button class="list-item" on-click="onSelect_">
- [[item.printerName]]
- </button>
- </template>
- </div>
- </template>
-</dom-module>
-
-<dom-module id="add-printer-dialog">
- <template>
- <style include="settings-shared">
- #dialog {
- /* Force a bottom border regardless of scroll state. */
- --cr-dialog-body-border-bottom: 1px solid var(--paper-grey-300);
- }
- #dialog [slot=body] {
- height: 350px;
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
- </style>
-
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">
- <slot name="dialog-title"></slot>
- </div>
- <div slot="body">
- <slot name="dialog-body"></slot>
- </div>
- <div slot="button-container">
- <slot name="dialog-buttons"></slot>
- </div>
- </cr-dialog>
- </template>
- <script src="cups_add_printer_dialog_elements.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.js b/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.js
deleted file mode 100644
index 724c9cc9f46..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_elements.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** 'add-printers-list' is the list of discovered printers. */
-Polymer({
- is: 'add-printer-list',
-
- properties: {
- /** @type {!Array<!CupsPrinterInfo>} */
- printers: {
- type: Array,
- notify: true,
- },
-
- /** @type {!CupsPrinterInfo} */
- selectedPrinter: {
- type: Object,
- notify: true,
- },
- },
-
- /**
- * @param {{model:Object}} event
- * @private
- */
- onSelect_: function(event) {
- this.selectedPrinter = event.model.item;
- },
-
- /**
- * @param {!CupsPrinterInfo} first
- * @param {!CupsPrinterInfo} second
- * @return {number} The result of the comparison.
- * @private
- */
- sort_: function(first, second) {
- return settings.printing.alphabeticalSort(first, second);
- },
-});
-
-/** 'add-printer-dialog' is the template of the Add Printer dialog. */
-Polymer({
- is: 'add-printer-dialog',
-
- /** @private */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- close: function() {
- this.$.dialog.close();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html b/chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
deleted file mode 100644
index ab4137cfd86..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
+++ /dev/null
@@ -1,161 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/mojo_interface_provider.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="cups_add_printer_dialog_elements.html">
-<link rel="import" href="cups_printer_dialog_util.html">
-<link rel="import" href="cups_printer_shared_css.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-
-<dom-module id="settings-cups-edit-printer-dialog">
- <template>
- <style include="cr-shared-style cups-printer-shared"></style>
- <add-printer-dialog>
- <div slot="dialog-title">$i18n{editPrinterDialogTitle}
- <div id="general-error-container" hidden="[[!errorText_]]">
- <div id="general-error">
- <iron-icon id="general-error-icon" icon="cr:warning"></iron-icon>
- <div id="general-error-message">
- [[errorText_]]
- </div>
- </div>
- </div>
- </div>
- <div slot="dialog-body" scrollable>
- <div class="settings-box first two-line">
- <cr-input class="printer-name-input" autofocus
- id="printerName"
- value="{{pendingPrinter_.printerName}}"
- on-input="onPrinterInfoChange_"
- label="$i18n{printerName}"
- maxlength=64>
- </cr-input>
- </div>
- <div class="settings-box two-line">
- <cr-input label="$i18n{printerAddress}"
- id="printerAddress"
- on-input="onPrinterInfoChange_"
- value="{{pendingPrinter_.printerAddress}}"
- disabled="[[!networkProtocolActive_]]"
- maxlength=63
- readonly="[[!isOnline_]]">
- </cr-input>
- </div>
- <div class="settings-box two-line">
- <div class="start">
- <div id="printerProtocol" class="cr-form-field-label">
- $i18n{printerProtocol}
- </div>
- <div class="secondary">
- <select class="md-select" aria-labelledby="printerProtocol"
- value="[[pendingPrinter_.printerProtocol]]"
- on-change="onProtocolChange_"
- disabled="[[!protocolSelectEnabled(isOnline_,
- networkProtocolActive_)]]">
- <option value="ipp">
- $i18n{printerProtocolIpp}
- </option>
- <option value="ipps">
- $i18n{printerProtocolIpps}
- </option>
- <option value="http"
- disabled="[[isAutoconfPrinter_(pendingPrinter_.*)]]">
- $i18n{printerProtocolHttp}
- </option>
- <option value="https"
- disabled="[[isAutoconfPrinter_(pendingPrinter_.*)]]">
- $i18n{printerProtocolHttps}
- </option>
- <option value="socket"
- disabled="[[isAutoconfPrinter_(pendingPrinter_.*)]]">
- $i18n{printerProtocolAppSocket}
- </option>
- <option value="lpd"
- disabled="[[isAutoconfPrinter_(pendingPrinter_.*)]]">
- $i18n{printerProtocolLpd}
- </option>
- <option value="usb" disabled="[[networkProtocolActive_]]">
- $i18n{printerProtocolUsb}
- </option>
- <option value="ippusb" disabled="[[networkProtocolActive_]]">
- $i18n{printerProtocolIppUsb}
- </option>
- </select>
- </div>
- </div>
- </div>
- <div class="settings-box two-line">
- <cr-input id="printerQueue" label="$i18n{printerQueue}"
- value="{{pendingPrinter_.printerQueue}}"
- on-input="onPrinterInfoChange_"
- maxlength=64
- disabled="[[!networkProtocolActive_]]"
- readonly="[[!isOnline_]]">
- </cr-input>
- </div>
- <div class="settings-box two-line">
- <cr-input label="$i18n{printerURI}" readonly
- value="[[getPrinterURI_(pendingPrinter_)]]">
- </cr-input>
- </div>
- <template id="makeAndModelSection" is="dom-if"
- if="[[!isAutoconfPrinter_(pendingPrinter_.*)]]">
- <div class="settings-box two-line">
- <cr-searchable-drop-down items="[[manufacturerList]]"
- id="printerPPDManufacturer"
- label="$i18n{printerManufacturer}"
- value="{{pendingPrinter_.ppdManufacturer}}"
- readonly="[[!isOnline_]]">
- </cr-searchable-drop-down>
- </div>
- <div class="settings-box two-line">
- <cr-searchable-drop-down items="[[modelList]]"
- id="printerPPDModel"
- label="$i18n{printerModel}"
- value="{{pendingPrinter_.ppdModel}}"
- readonly="[[!isOnline_]]">
- </cr-searchable-drop-down>
- </div>
- <div id="ppdLabel" class="cr-form-field-label">
- <span>$i18n{selectDriver}</span>
- <a href="$i18n{printingCUPSPrintPpdLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </div>
- <div class="settings-box two-line">
- <cr-input class="browse-file-input" readonly tabindex="-1"
- value="[[userPPD_]]" aria-labelledby="ppdLabel"
- error-message="$i18n{selectDriverErrorMessage}"
- invalid="[[invalidPPD_]]">
- </cr-input>
- <cr-button class="browse-button" on-click="onBrowseFile_"
- disabled="[[!isOnline_]]">
- $i18n{selectDriverButtonText}
- </cr-button>
- </div>
- <div class="eula" id="eulaUrl" hidden="[[!eulaUrl_]]">
- <a href="[[eulaUrl_]]" target="_blank">$i18n{printerEulaNotice}</a>
- </div>
- </template>
- </div>
- <div slot="dialog-buttons">
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onSaveTap_"
- disabled="[[!canSavePrinter_(pendingPrinter_.*,
- printerInfoChanged_, isOnline_)]]">
- $i18n{editPrinterButtonText}
- </cr-button>
- </div>
- </add-printer-dialog>
- </template>
- <script src="cups_edit_printer_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js b/chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js
deleted file mode 100644
index 6b2ceffcc92..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js
+++ /dev/null
@@ -1,464 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cups-edit-printer-dialog' is a dialog to edit the
- * existing printer's information and re-configure it.
- */
-
-Polymer({
- is: 'settings-cups-edit-printer-dialog',
-
- behaviors: [
- CrScrollableBehavior,
- CrNetworkListenerBehavior,
- ],
-
- properties: {
- /**
- * The currently saved printer.
- * @type {CupsPrinterInfo}
- */
- activePrinter: Object,
-
- /**
- * Printer that holds the modified changes to activePrinter and only
- * applies these changes when the save button is clicked.
- * @type {CupsPrinterInfo}
- */
- pendingPrinter_: Object,
-
- /**
- * If the printer needs to be re-configured.
- * @private {boolean}
- */
- needsReconfigured_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The current PPD in use by the printer.
- * @private
- */
- userPPD_: String,
-
-
- /**
- * Tracks whether the dialog is fully initialized. This is required because
- * the dialog isn't fully initialized until Model and Manufacturer are set.
- * Allows us to ignore changes made to these fields until initialization is
- * complete.
- * @private
- */
- arePrinterFieldsInitialized_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * If the printer info has changed since loading this dialog. This will
- * only track the freeform input fields, since the other fields contain
- * input selected from dropdown menus.
- * @private
- */
- printerInfoChanged_: {
- type: Boolean,
- value: false,
- },
-
- networkProtocolActive_: {
- type: Boolean,
- computed: 'isNetworkProtocol_(pendingPrinter_.printerProtocol)',
- },
-
- /** @type {?Array<string>} */
- manufacturerList: Array,
-
- /** @type {?Array<string>} */
- modelList: Array,
-
- /**
- * Whether the user selected PPD file is valid.
- * @private
- */
- invalidPPD_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The base name of a newly selected PPD file.
- * @private
- */
- newUserPPD_: String,
-
- /**
- * The URL to a printer's EULA.
- * @private
- */
- eulaUrl_: {
- type: String,
- value: '',
- },
-
- /** @private */
- isOnline_: {
- type: Boolean,
- value: true,
- },
-
- /**
- * The error text to be displayed on the dialog.
- * @private
- */
- errorText_: {
- type: String,
- value: '',
- },
- },
-
- observers: [
- 'printerPathChanged_(pendingPrinter_.*)',
- 'selectedEditManufacturerChanged_(pendingPrinter_.ppdManufacturer)',
- 'onModelChanged_(pendingPrinter_.ppdModel)',
- ],
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @override */
- created: function() {
- this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- },
-
- /** @override */
- attached: function() {
- // Create a copy of activePrinter so that we can modify its fields.
- this.pendingPrinter_ = /** @type{CupsPrinterInfo} */
- (Object.assign({}, this.activePrinter));
-
- this.refreshNetworks_();
-
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getPrinterPpdManufacturerAndModel(this.pendingPrinter_.printerId)
- .then(
- this.onGetPrinterPpdManufacturerAndModel_.bind(this),
- this.onGetPrinterPpdManufacturerAndModelFailed_.bind(this));
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterManufacturersList()
- .then(this.manufacturerListChanged_.bind(this));
- this.userPPD_ =
- settings.printing.getBaseName(this.pendingPrinter_.printerPPDPath);
- },
-
- /**
- * CrosNetworkConfigObserver impl
- * @param {!Array<chromeos.networkConfig.mojom.NetworkStateProperties>}
- * networks
- * @private
- */
- onActiveNetworksChanged: function(networks) {
- this.isOnline_ = networks.some(function(network) {
- return OncMojo.connectionStateIsConnected(network.connectionState);
- });
- },
-
- /**
- * @param {!{path: string, value: string}} change
- * @private
- */
- printerPathChanged_: function(change) {
- if (change.path != 'pendingPrinter_.printerName') {
- this.needsReconfigured_ = true;
- }
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onProtocolChange_: function(event) {
- this.set('pendingPrinter_.printerProtocol', event.target.value);
- this.onPrinterInfoChange_();
- },
-
- /** @private */
- onPrinterInfoChange_: function() {
- this.printerInfoChanged_ = true;
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$$('add-printer-dialog').close();
- },
-
- /**
- * Handler for update|reconfigureCupsPrinter success.
- * @param {!PrinterSetupResult} result
- * @private
- */
- onPrinterEditSucceeded_: function(result) {
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: result, printerName: this.activePrinter.printerName});
- this.$$('add-printer-dialog').close();
- },
-
- /**
- * Handler for update|reconfigureCupsPrinter failure.
- * @param {*} result
- * @private
- */
- onPrinterEditFailed_: function(result) {
- this.errorText_ = settings.printing.getErrorText(
- /** @type {PrinterSetupResult} */ (result));
- },
-
- /** @private */
- onSaveTap_: function() {
- this.updateActivePrinter_();
- if (!this.needsReconfigured_ || !this.isOnline_) {
- // If we don't need to reconfigure or we are offline, just update the
- // printer name.
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .updateCupsPrinter(
- this.activePrinter.printerId, this.activePrinter.printerName)
- .then(
- this.onPrinterEditSucceeded_.bind(this),
- this.onPrinterEditFailed_.bind(this));
- } else {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .reconfigureCupsPrinter(this.activePrinter)
- .then(
- this.onPrinterEditSucceeded_.bind(this),
- this.onPrinterEditFailed_.bind(this));
- }
- },
-
- /**
- * @param {!CupsPrinterInfo} printer
- * @return {string} The printer's URI that displays in the UI
- * @private
- */
- getPrinterURI_: function(printer) {
- if (!printer) {
- return '';
- } else if (
- printer.printerProtocol && printer.printerAddress &&
- printer.printerQueue) {
- return printer.printerProtocol + '://' + printer.printerAddress + '/' +
- printer.printerQueue;
- } else if (printer.printerProtocol && printer.printerAddress) {
- return printer.printerProtocol + '://' + printer.printerAddress;
- } else {
- return '';
- }
- },
-
- /**
- * Handler for getPrinterPpdManufacturerAndModel() success case.
- * @param {!PrinterPpdMakeModel} info
- * @private
- */
- onGetPrinterPpdManufacturerAndModel_: function(info) {
- this.set('pendingPrinter_.ppdManufacturer', info.ppdManufacturer);
- this.set('pendingPrinter_.ppdModel', info.ppdModel);
-
- // |needsReconfigured_| needs to reset to false after |ppdManufacturer| and
- // |ppdModel| are initialized to their correct values.
- this.needsReconfigured_ = false;
- },
-
- /**
- * Handler for getPrinterPpdManufacturerAndModel() failure case.
- * @private
- */
- onGetPrinterPpdManufacturerAndModelFailed_: function() {
- this.needsReconfigured_ = false;
- },
-
- /**
- * @param {string} protocol
- * @return {boolean} Whether |protocol| is a network protocol
- * @private
- */
- isNetworkProtocol_: function(protocol) {
- return settings.printing.isNetworkProtocol(protocol);
- },
-
- /**
- * @return {boolean} Whether the current printer was auto configured.
- * @private
- */
- isAutoconfPrinter_: function() {
- return this.pendingPrinter_.printerPpdReference.autoconf;
- },
-
- /**
- * @return {boolean} Whether the Save button is enabled.
- * @private
- */
- canSavePrinter_: function() {
- return this.printerInfoChanged_ &&
- (this.isPrinterConfigured_() || !this.isOnline_);
- },
-
- /**
- * @param {string} manufacturer The manufacturer for which we are retrieving
- * models.
- * @private
- */
- selectedEditManufacturerChanged_: function(manufacturer) {
- // Reset model if manufacturer is changed.
- this.set('pendingPrinter_.ppdModel', '');
- this.modelList = [];
- if (!!manufacturer && manufacturer.length != 0) {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterModelsList(manufacturer)
- .then(this.modelListChanged_.bind(this));
- }
- },
-
- /**
- * Sets printerInfoChanged_ to true to show that the model has changed. Also
- * attempts to get the EULA Url if the selected printer has one.
- * @private
- */
- onModelChanged_: function() {
- if (this.arePrinterFieldsInitialized_) {
- this.printerInfoChanged_ = true;
- }
-
- if (!this.pendingPrinter_.ppdManufacturer ||
- !this.pendingPrinter_.ppdModel) {
- // Do not check for an EULA unless both |ppdManufacturer| and |ppdModel|
- // are set. Set |eulaUrl_| to be empty in this case.
- this.onGetEulaUrlCompleted_('' /* eulaUrl */);
- return;
- }
-
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getEulaUrl(
- this.pendingPrinter_.ppdManufacturer, this.pendingPrinter_.ppdModel)
- .then(this.onGetEulaUrlCompleted_.bind(this));
- },
-
- /**
- * @param {string} eulaUrl The URL for the printer's EULA.
- * @private
- */
- onGetEulaUrlCompleted_: function(eulaUrl) {
- this.eulaUrl_ = eulaUrl;
- },
-
- /** @private */
- onBrowseFile_: function() {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterPPDPath()
- .then(this.printerPPDPathChanged_.bind(this));
- },
-
- /**
- * @param {!ManufacturersInfo} manufacturersInfo
- * @private
- */
- manufacturerListChanged_: function(manufacturersInfo) {
- if (!manufacturersInfo.success) {
- return;
- }
- this.manufacturerList = manufacturersInfo.manufacturers;
- if (this.pendingPrinter_.ppdManufacturer.length != 0) {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrinterModelsList(this.pendingPrinter_.ppdManufacturer)
- .then(this.modelListChanged_.bind(this));
- }
- },
-
- /**
- * @param {!ModelsInfo} modelsInfo
- * @private
- */
- modelListChanged_: function(modelsInfo) {
- if (modelsInfo.success) {
- this.modelList = modelsInfo.models;
- // ModelListChanged_ is the final step of initializing pendingPrinter.
- this.arePrinterFieldsInitialized_ = true;
- }
- },
-
- /**
- * @param {string} path The full path to the selected PPD file
- * @private
- */
- printerPPDPathChanged_: function(path) {
- this.set('pendingPrinter_.printerPPDPath', path);
- this.invalidPPD_ = !path;
- if (!this.invalidPPD_) {
- // A new valid PPD file should be treated as a saveable change.
- this.onPrinterInfoChange_();
- }
- this.userPPD_ = settings.printing.getBaseName(path);
- },
-
- /**
- * Returns true if the printer has valid name, address, and valid PPD or was
- * auto-configured.
- * @return {boolean}
- * @private
- */
- isPrinterConfigured_: function() {
- return settings.printing.isNameAndAddressValid(this.pendingPrinter_) &&
- (this.isAutoconfPrinter_() ||
- settings.printing.isPPDInfoValid(
- this.pendingPrinter_.ppdManufacturer,
- this.pendingPrinter_.ppdModel,
- this.pendingPrinter_.printerPPDPath));
- },
-
- /**
- * Helper function to copy over modified fields to activePrinter.
- * @private
- */
- updateActivePrinter_: function() {
- if (!this.isOnline_) {
- // If we are not online, only copy over the printerName.
- this.activePrinter.printerName = this.pendingPrinter_.printerName;
- return;
- }
-
- this.activePrinter =
- /** @type{CupsPrinterInfo} */ (Object.assign({}, this.pendingPrinter_));
- // Set ppdModel since there is an observer that clears ppdmodel's value when
- // ppdManufacturer changes.
- this.activePrinter.ppdModel = this.pendingPrinter_.ppdModel;
- },
-
- /**
- * Callback function when networks change.
- * @private
- */
- refreshNetworks_: function() {
- this.networkConfig_
- .getNetworkStateList({
- filter: chromeos.networkConfig.mojom.FilterType.kActive,
- networkType: chromeos.networkConfig.mojom.NetworkType.kAll,
- limit: chromeos.networkConfig.mojom.NO_LIMIT,
- })
- .then((responseParams) => {
- this.onActiveNetworksChanged(responseParams.result);
- });
- },
-
- /**
- * Returns true if the printer protocol select field should be enabled.
- * @return {boolean}
- * @private
- */
- protocolSelectEnabled: function() {
- return this.isOnline_ && this.networkProtocolActive_;
- },
-
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html b/chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html
deleted file mode 100644
index da0573be5ea..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="cups_printer_types.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-<link rel="import" href="cups_printers_entry_list_behavior.html">
-<link rel="import" href="cups_printers_entry.html">
-<link rel="import" href="cups_printers_entry_manager.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-cups-nearby-printers">
- <template>
- <style include="cups-printer-shared">
- :host {
- display: flex;
- flex-direction: column;
- }
-
- #no-search-results {
- margin-top: 20px;
- }
- </style>
-
- <iron-list class="list-frame vertical-list flex-auto" id="printerEntryList"
- items="[[filteredPrinters_]]">
- <template>
- <settings-cups-printers-entry printer-entry="[[item]]">
- </settings-cups-printers-entry>
- </template>
- </iron-list>
- <div id="no-search-results"
- hidden="[[!showNoSearchResultsMessage_(searchTerm,
- filteredPrinters_.*)]]">
- $i18n{noSearchResults}
- </div>
- </template>
- <script src="cups_nearby_printers.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js b/chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js
deleted file mode 100644
index f4020c2e390..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cups-nearby-printers' is a list container for
- * Nearby Printers.
- */
-Polymer({
- is: 'settings-cups-nearby-printers',
-
- // ListPropertyUpdateBehavior is used in CupsPrintersEntryListBehavior.
- behaviors: [
- CupsPrintersEntryListBehavior,
- ListPropertyUpdateBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * Search term for filtering |nearbyPrinters|.
- * @type {string}
- */
- searchTerm: {
- type: String,
- value: '',
- },
-
- /** @type {?CupsPrinterInfo} */
- activePrinter: {
- type: Object,
- notify: true,
- },
-
- /**
- * @type {number}
- * @private
- */
- activePrinterListEntryIndex_: {
- type: Number,
- value: -1,
- },
-
- /**
- * List of printers filtered through a search term.
- * @type {!Array<!PrinterListEntry>}
- * @private
- */
- filteredPrinters_: {
- type: Array,
- value: () => [],
- },
- },
-
- listeners: {
- 'add-automatic-printer': 'onAddAutomaticPrinter_',
- 'query-discovered-printer': 'onQueryDiscoveredPrinter_',
- },
-
- observers: [
- 'onSearchOrPrintersChanged_(nearbyPrinters.*, searchTerm)'
- ],
-
- /**
- * Redoes the search whenever |searchTerm| or |nearbyPrinters| changes.
- * @private
- */
- onSearchOrPrintersChanged_: function() {
- if (!this.nearbyPrinters) {
- return;
- }
- // Filter printers through |searchTerm|. If |searchTerm| is empty,
- // |filteredPrinters_| is just |nearbyPrinters|.
- const updatedPrinters = this.searchTerm ?
- this.nearbyPrinters.filter(
- item => settings.printing.matchesSearchTerm(
- item.printerInfo,this.searchTerm)) :
- this.nearbyPrinters.slice();
-
- updatedPrinters.sort(settings.printing.sortPrinters);
-
- this.updateList(
- 'filteredPrinters_', printer => printer.printerInfo.printerId,
- updatedPrinters);
- },
-
- /**
- * @param {!CustomEvent<{item: !PrinterListEntry}>} e
- * @private
- */
- onAddAutomaticPrinter_: function(e) {
- const item = e.detail.item;
- this.setActivePrinter_(item);
-
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .addDiscoveredPrinter(item.printerInfo.printerId)
- .then(
- this.onAddNearbyPrintersSucceeded_.bind(this,
- item.printerInfo.printerName),
- this.onAddNearbyPrinterFailed_.bind(this));
- },
-
- /**
- * @param {!CustomEvent<{item: !PrinterListEntry}>} e
- * @private
- */
- onQueryDiscoveredPrinter_: function(e) {
- const item = e.detail.item;
- this.setActivePrinter_(item);
-
- // This is a workaround to ensure type safety on the params of the casted
- // function. We do this because the closure compiler does not work well with
- // rejected js promises.
- const queryDiscoveredPrinterFailed = /** @type {!Function}) */(
- this.onQueryDiscoveredPrinterFailed_.bind(this));
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .addDiscoveredPrinter(item.printerInfo.printerId)
- .then(
- this.onQueryDiscoveredPrinterSucceeded_.bind(this,
- item.printerInfo.printerName),
- queryDiscoveredPrinterFailed);
- },
-
- /**
- * Retrieves the index of |item| in |nearbyPrinters_| and sets that printer as
- * the active printer.
- * @param {!PrinterListEntry} item
- * @private
- */
- setActivePrinter_: function(item) {
- this.activePrinterListEntryIndex_ = this.nearbyPrinters.findIndex(
- printer => printer.printerInfo.printerId == item.printerInfo.printerId);
-
- this.activePrinter =
- this.get(['nearbyPrinters', this.activePrinterListEntryIndex_])
- .printerInfo;
- },
-
- /**
- * Handler for addDiscoveredPrinter success.
- * @param {string} printerName
- * @param {!PrinterSetupResult} result
- * @private
- */
- onAddNearbyPrintersSucceeded_: function(printerName, result) {
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: result, printerName: printerName});
- },
-
- /**
- * Handler for addDiscoveredPrinter failure.
- * @param {*} printer
- * @private
- */
- onAddNearbyPrinterFailed_: function(printer) {
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: PrinterSetupResult.PRINTER_UNREACHABLE,
- printerName: printer.printerName});
- },
-
- /**
- * Handler for queryDiscoveredPrinter success.
- * @param {string} printerName
- * @param {!PrinterSetupResult} result
- * @private
- */
- onQueryDiscoveredPrinterSucceeded_: function(printerName, result) {
- this.fire(
- 'show-cups-printer-toast',
- {resultCode: result, printerName: printerName});
- },
-
- /**
- * Handler for queryDiscoveredPrinter failure.
- * @param {!CupsPrinterInfo} printer
- * @private
- */
- onQueryDiscoveredPrinterFailed_: function(printer) {
- this.fire('open-manufacturer-model-dialog-for-specified-printer',
- {item: /** @type {CupsPrinterInfo} */(printer)});
- },
-
- /**
- * @return {boolean} Returns true if the no search message should be visible.
- * @private
- */
- showNoSearchResultsMessage_: function() {
- return !!this.searchTerm && !this.filteredPrinters_.length;
- }
-}); \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html
deleted file mode 100644
index 061146b255c..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="cups_printer_dialog_util.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.js
deleted file mode 100644
index 46b1be62226..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.js
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Utility functions that are used in Cups printer setup dialogs.
- */
-
-cr.define('settings.printing', function() {
- /**
- * @param {string} protocol
- * @return {boolean} Whether |protocol| is a network protocol
- */
- function isNetworkProtocol(protocol) {
- return ['ipp', 'ipps', 'http', 'https', 'socket', 'lpd'].includes(protocol);
- }
-
- /**
- * Returns true if the printer's name and address is valid. This function
- * uses regular expressions to determine whether the provided printer name
- * and address are valid. Address can be either an ipv4/6 address or a
- * hostname followed by an optional port.
- * NOTE: The regular expression for hostnames will allow hostnames that are
- * over 255 characters.
- * @param {CupsPrinterInfo} printer
- * @return {boolean}
- */
- function isNameAndAddressValid(printer) {
- if (!printer) {
- return false;
- }
-
- const name = printer.printerName;
- const address = printer.printerAddress;
-
- if (!isNetworkProtocol(printer.printerProtocol) && !!name) {
- // We do not need to verify the address of a non-network printer.
- return true;
- }
-
- if (!name || !address) {
- return false;
- }
-
- const hostnamePrefix = '([a-z\\d]|[a-z\\d][a-z\\d\\-]{0,61}[a-z\\d])';
-
- // Matches an arbitrary number of 'prefix patterns' which are separated by a
- // dot.
- const hostnameSuffix = `(\\.${hostnamePrefix})*`;
-
- // Matches an optional port at the end of the address.
- const portNumber = '(:\\d+)?';
-
- const ipv6Full = '(([a-f\\d]){1,4}(:(:)?([a-f\\d]){1,4}){1,7})';
-
- // Special cases for addresses using a shorthand notation.
- const ipv6Prefix = '(::([a-f\\d]){1,4})';
- const ipv6Suffix = '(([a-f\\d]){1,4}::)';
- const ipv6Combined = `(${ipv6Full}|${ipv6Prefix}|${ipv6Suffix})`;
- const ipv6WithPort = `(\\[${ipv6Combined}\\]${portNumber})`;
-
- // Matches valid hostnames and ipv4 addresses.
- const hostnameRegex =
- new RegExp(`^${hostnamePrefix}${hostnameSuffix}${portNumber}$`, 'i');
-
- // Matches valid ipv6 addresses.
- const ipv6AddressRegex =
- new RegExp(`^(${ipv6Combined}|${ipv6WithPort})$`, 'i');
-
- const invalidIpv6Regex = new RegExp('.*::.*::.*');
-
- return hostnameRegex.test(address) ||
- (ipv6AddressRegex.test(address) && !invalidIpv6Regex.test(address));
- }
-
- /**
- * Returns true if the printer's manufacturer and model or ppd path is valid.
- * @param {string} manufacturer
- * @param {string} model
- * @param {string} ppdPath
- * @return {boolean}
- */
- function isPPDInfoValid(manufacturer, model, ppdPath) {
- return !!((manufacturer && model) || ppdPath);
- }
-
- /**
- * Returns the base name of a filepath.
- * @param {string} path The full path of the file
- * @return {string} The base name of the file
- */
- function getBaseName(path) {
- if (path && path.length > 0) {
- return path.substring(path.lastIndexOf('/') + 1);
- }
- return '';
- }
-
- /**
- * A function used for sorting printer names based on the current locale's
- * collation order.
- * @param {!CupsPrinterInfo} first
- * @param {!CupsPrinterInfo} second
- * @return {number} The result of the comparison.
- */
- function alphabeticalSort(first, second) {
- return first.printerName.toLocaleLowerCase().localeCompare(
- second.printerName.toLocaleLowerCase());
- }
-
- /**
- * Return the error string corresponding to the result code.
- * @param {!PrinterSetupResult} result
- * @return {string}
- */
- function getErrorText(result) {
- switch (result) {
- case PrinterSetupResult.FATAL_ERROR:
- return loadTimeData.getString('printerAddedFatalErrorMessage');
- case PrinterSetupResult.PRINTER_UNREACHABLE:
- return loadTimeData.getString('printerAddedUnreachableMessage');
- case PrinterSetupResult.DBUS_ERROR:
- // Simply return a generic error message as this error should only
- // occur when a call to Dbus fails which isn't meaningful to the user.
- return loadTimeData.getString('printerAddedFailedMessage');
- case PrinterSetupResult.NATIVE_PRINTERS_NOT_ALLOWED:
- return loadTimeData.getString(
- 'printerAddedNativePrintersNotAllowedMessage');
- case PrinterSetupResult.INVALID_PRINTER_UPDATE:
- return loadTimeData.getString('editPrinterInvalidPrinterUpdate');
- case PrinterSetupResult.PPD_TOO_LARGE:
- return loadTimeData.getString('printerAddedPpdTooLargeMessage');
- case PrinterSetupResult.INVALID_PPD:
- return loadTimeData.getString('printerAddedInvalidPpdMessage');
- case PrinterSetupResult.PPD_NOT_FOUND:
- return loadTimeData.getString('printerAddedPpdNotFoundMessage');
- case PrinterSetupResult.PPD_UNRETRIEVABLE:
- return loadTimeData.getString('printerAddedPpdUnretrievableMessage');
- default:
- assertNotReached();
- }
- }
-
- /**
- * We sort by printer type, which is based off of a maintained list in
- * cups_printers_types.js. If the types are the same, we sort alphabetically.
- * @param {!PrinterListEntry} first
- * @param {!PrinterListEntry} second
- * @return {number}
- */
- function sortPrinters(first, second) {
- if (first.printerType == second.printerType) {
- return settings.printing.alphabeticalSort(
- first.printerInfo, second.printerInfo);
- }
-
- return first.printerType - second.printerType;
- }
-
- /**
- * @param {!CupsPrinterInfo} printer
- * @param {string} searchTerm
- * @return {boolean} True if the printer has |searchTerm| in its name.
- */
- function matchesSearchTerm(printer, searchTerm) {
- return printer.printerName.toLowerCase().includes(searchTerm.toLowerCase());
- }
-
- /**
- * @param {!PrinterListEntry} first
- * @param {!PrinterListEntry} second
- * @return {boolean}
- */
- function arePrinterIdsEqual(first, second) {
- return first.printerInfo.printerId == second.printerInfo.printerId;
- }
-
- return {
- isNetworkProtocol: isNetworkProtocol,
- isNameAndAddressValid: isNameAndAddressValid,
- isPPDInfoValid: isPPDInfoValid,
- getBaseName: getBaseName,
- alphabeticalSort: alphabeticalSort,
- getErrorText: getErrorText,
- sortPrinters: sortPrinters,
- matchesSearchTerm: matchesSearchTerm,
- arePrinterIdsEqual: arePrinterIdsEqual,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_shared_css.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printer_shared_css.html
deleted file mode 100644
index 68ee364dca0..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_shared_css.html
+++ /dev/null
@@ -1,122 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<!-- Common styles for CUPS printer settings. -->
-<dom-module id="cups-printer-shared">
- <template>
- <style include="settings-shared md-select">
- [slot='dialog-body'] .settings-box {
- border-top: none;
- margin-bottom: 10px;
- }
-
- [slot='dialog-body'] .settings-box.two-line {
- align-items: flex-start;
- }
-
- [slot='dialog-body'] .settings-box cr-input {
- width: 100%;
- }
-
- /* The only input that has error-message are at the end, so we don't
- need to allocate space for error-message under each input
- for consistency. */
- [slot='dialog-body'] .settings-box cr-input:not([error-message]) {
- --cr-input-error-display: none;
- }
-
- [slot='dialog-body'] .settings-box .md-select {
- width: 100%;
- }
-
- [slot='dialog-body'] .settings-box .printer-name-input {
- width: 100%;
- }
-
- [slot='dialog-body'] .settings-box .browse-file-input {
- width: auto;
- --cr-input-width: 383px;
- }
-
- [slot='dialog-body'] .settings-box .browse-button {
- margin-bottom: 8px;
- margin-inline-start: 12px;
- }
-
- [slot='dialog-body'] .last {
- margin-top: 20px;
- }
-
- [slot='dialog-body'] .center {
- display: flex;
- justify-content: center;
- position: absolute;
- top: 50%;
- width: 100%;
- }
-
- [slot='dialog-body'] .eula {
- padding-inline-start: 20px;
- }
-
- [slot='dialog-buttons'] {
- display: flex;
- justify-content: space-between;
- width: 100%;
- }
-
- .flex-auto {
- flex: 1 1 auto;
- }
-
- .list-item {
- background: none;
- border: none;
- box-sizing: border-box;
- color: var(--paper-grey-800);
- cursor: pointer;
- font: inherit;
- min-height: 32px;
- padding: 0 24px;
- text-align: start;
- width: 100%;
- }
-
- .list-item:focus {
- background-color: var(--paper-grey-300);
- outline: none;
- }
-
- #search input[type='search'] {
- font: inherit;
- }
-
- #ppdLabel {
- padding-inline-start: 20px;
- }
-
- #general-error-container {
- height: 20px;
- margin-top: 10px;
- }
-
- #general-error-icon {
- --iron-icon-fill-color: var(--google-red-600);
- }
-
- #general-error-message {
- color: var(--google-red-600);
- display: inline-block;
- font-size: 10px;
- margin-inline-start: 5px;
- }
-
- #no-search-results {
- text-align: center;
- }
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.html
deleted file mode 100644
index 59964ef38b5..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="cups_printers_browser_proxy.html">
-<script src="cups_printer_types.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.js
deleted file mode 100644
index d2367b61c3b..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printer_types.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{
- * printerInfo: !CupsPrinterInfo,
- * printerType: number,
- * }}
- */
-let PrinterListEntry;
-
-/**
- * @enum {number}
- * These values correspond to the different types of printers available. Refer
- * to cups_printer_management.md for more information about the different
- * categories of printers.
- *
- * The types are numbered in desired sorting order for display.
- */
-const PrinterType = {
- SAVED: 0,
- AUTOMATIC: 1,
- DISCOVERED: 2,
-}; \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html
deleted file mode 100644
index c5b866a04bc..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html
+++ /dev/null
@@ -1,207 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/mojo_interface_provider.html">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/chromeos/onc_mojo.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="cups_add_printer_dialog.html">
-<link rel="import" href="cups_edit_printer_dialog.html">
-<link rel="import" href="cups_printer_shared_css.html">
-<link rel="import" href="cups_printers_entry_manager.html">
-<link rel="import" href="cups_printers_list.html">
-<link rel="import" href="cups_saved_printers.html">
-<link rel="import" href="cups_nearby_printers.html">
-<link rel="import" href="../route.html">
-
-<dom-module id="settings-cups-printers">
- <template>
- <style include="cups-printer-shared action-link iron-flex
- iron-flex-alignment">
- .custom-list-item {
- border-bottom: var(--cr-separator-line);
- min-height: var(--settings-row-min-height);
- }
-
- .padded {
- padding: 20px;
- }
-
- #addPrinterSection {
- padding-inline-start:
- var(--settings-list-frame-padding_-_padding-inline-start);
- padding-inline-end:
- var(--settings-list-frame-padding_-_padding-inline-end);
- }
-
- #addPrinterText {
- flex: 1;
- }
-
- #addManualPrinterIcon {
- --iron-icon-fill-color: rgb(138, 180, 248);
- --cr-icon-button-margin-end: 0;
- }
-
- #cloudOffIcon {
- --iron-icon-fill-color: rgb(95, 99, 104);
- margin-top: 10px;
- }
-
- #connectionMessage {
- padding-inline-start: 20px;
- }
-
- #noConnectivityContentContainer {
- border-bottom: var(--cr-separator-line);
- padding-inline-start: 20px;
- }
-
- #noSearchResultsMessage {
- color: var(--md-loading-message-color);
- font-size: 16px;
- font-weight: 500;
- margin-top: 80px;
- text-align: center;
- }
-
- #savedPrintersContainer {
- border-bottom: var(--cr-separator-line);
- }
-
- :host(:not([can-add-printer])) #addPrinterSection,
- :host(:not([can-add-printer])) #nearbyPrinters {
- opacity: var(--cr-disabled-opacity);
- pointer-events: none;
- }
- </style>
-
- <template is="dom-if" if="[[!enableUpdatedUi_]]">
- <div class="settings-box first">
- <div class="start">
- <span>$i18n{cupsPrintersLearnMoreLabel}</span>
- <a href="$i18n{printingCUPSPrintLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- <div class="secondary" hidden="[[canAddPrinter]]">
- $i18n{requireNetworkMessage}
- </div>
- </div>
- <template is="dom-if" if="[[!addPrinterButtonActive_(canAddPrinter,
- prefs.native_printing.user_native_printers_allowed.value)]]">
- <cr-policy-pref-indicator
- pref="[[prefs.native_printing.user_native_printers_allowed]]"
- icon-aria-label="$i18n{printingPageTitle}">
- </cr-policy-pref-indicator>
- </template>
- <cr-button class="action-button" id="addPrinter"
- on-click="onAddPrinterTap_"
- disabled="[[!addPrinterButtonActive_(canAddPrinter,
- prefs.native_printing.user_native_printers_allowed.value)]]">
- $i18n{addCupsPrinter}
- </cr-button>
- </div>
- <settings-cups-printers-list printers="{{printers}}"
- active-printer="{{activePrinter}}"
- search-term="[[searchTerm]]">
- </settings-cups-printers-list>
-
- <div id="noSearchResultsMessage"
- hidden="[[!showNoSearchResultsMessage_(searchTerm)]]">
- $i18n{noSearchResults}
- </div>
- </template>
-
- <template is="dom-if" if="[[enableUpdatedUi_]]">
- <template is="dom-if" if="[[!canAddPrinter]]">
- <div id="noConnectivityContentContainer"
- class="layout horizontal padded">
- <iron-icon id="cloudOffIcon" icon="settings20:cloud-off"></iron-icon>
- <div id="connectionMessage" class="layout vertical">
- <div>$i18n{noInternetConnection}</div>
- <div class="secondary">$i18n{checkNetworkAndTryAgain}</div>
- </div>
- </div>
- </template>
-
- <template is="dom-if"
- if="[[doesAccountHaveSavedPrinters_(savedPrinters_)]]" restamp>
- <div id="savedPrintersContainer">
- <div class="settings-box first">
- <div class="start">
- <span>$i18n{savedPrintersTitle}</span>
- </div>
- </div>
-
- <settings-cups-saved-printers id="savedPrinters"
- active-printer="{{activePrinter}}"
- search-term="[[searchTerm]]">
- </settings-cups-saved-printers>
- </div>
- </template>
-
- <div class="padded first" id="nearbyPrinters">
- <div>$i18n{nearbyPrintersListTitle}</div>
- <span class="secondary">
- $i18n{nearbyPrintersListDescription}
- </span>
- <a href="$i18n{printingCUPSPrintLearnMoreUrl}"
- target="_blank">
- $i18n{learnMore}
- </a>
- <template is="dom-if" if="[[!addPrinterButtonActive_(canAddPrinter,
- prefs.native_printing.user_native_printers_allowed.value)]]">
- <cr-policy-pref-indicator
- pref="[[prefs.native_printing.user_native_printers_allowed]]"
- icon-aria-label="$i18n{printingPageTitle}">
- </cr-policy-pref-indicator>
- </template>
- </div>
-
- <div id="addPrinterSection">
- <div class="layout horizontal center custom-list-item">
- <div id="addPrinterText">$i18n{addCupsPrinter}</div>
- <cr-icon-button class="action-button" id="addManualPrinterIcon"
- iron-icon="settings20:printer-add"
- on-click="onAddPrinterTap_"
- disabled="[[!addPrinterButtonActive_(canAddPrinter,
- prefs.native_printing.user_native_printers_allowed.value)]]"
- title="$i18n{addCupsPrinter}">
- </cr-icon-button>
- </div>
- </div>
- <template is="dom-if" if="[[canAddPrinter]]" restamp>
- <settings-cups-nearby-printers search-term="[[searchTerm]]"
- active-printer="{{activePrinter}}">
- </settings-cups-nearby-printers>
- </template>
- </template>
-
- <settings-cups-add-printer-dialog id="addPrinterDialog"
- on-close="onAddPrinterDialogClose_"
- enable-updated-ui="[[enableUpdatedUi_]]">
- </settings-cups-add-printer-dialog>
-
- <template is="dom-if" if="[[showCupsEditPrinterDialog_]]" restamp>
- <settings-cups-edit-printer-dialog id="editPrinterDialog"
- active-printer="{{activePrinter}}"
- on-close="onEditPrinterDialogClose_">
- </settings-cups-edit-printer-dialog>
- </template>
-
- <cr-toast id="errorToast" duration="3000" role="alert">
- <div class="error-message" id="addPrinterDoneMessage">
- [[addPrinterResultText_]]
- </div>
- </cr-toast>
- </template>
- <script src="cups_printers.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js
deleted file mode 100644
index ece618460ad..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cups-printers' is a component for showing CUPS
- * Printer settings subpage (chrome://settings/cupsPrinters). It is used to
- * set up legacy & non-CloudPrint printers on ChromeOS by leveraging CUPS (the
- * unix printing system) and the many open source drivers built for CUPS.
- */
-// TODO(xdai): Rename it to 'settings-cups-printers-page'.
-Polymer({
- is: 'settings-cups-printers',
-
- behaviors: [
- CrNetworkListenerBehavior,
- settings.RouteObserverBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /** @type {!Array<!CupsPrinterInfo>} */
- printers: {
- type: Array,
- notify: true,
- },
-
- prefs: Object,
-
- /** @type {?CupsPrinterInfo} */
- activePrinter: {
- type: Object,
- notify: true,
- },
-
- searchTerm: {
- type: String,
- },
-
- /** This is also used as an attribute for css styling. */
- canAddPrinter: {
- type: Boolean,
- reflectToAttribute: true,
- },
-
- /**
- * @type {!Array<!PrinterListEntry>}
- * @private
- */
- savedPrinters_: {
- type: Array,
- value: () => [],
- },
-
- /** @private */
- showCupsEditPrinterDialog_: Boolean,
-
- /**@private */
- addPrinterResultText_: String,
-
- /**
- * TODO(jimmyxgong): Remove this feature flag conditional once feature
- * is launched.
- * @private
- */
- enableUpdatedUi_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('updatedCupsPrintersUiEnabled');
- },
- },
- },
-
- listeners: {
- 'edit-cups-printer-details': 'onShowCupsEditPrinterDialog_',
- 'show-cups-printer-toast': 'openResultToast_',
- 'open-manufacturer-model-dialog-for-specified-printer':
- 'openManufacturerModelDialogForSpecifiedPrinter_',
- },
-
- /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
- networkConfig_: null,
-
- /** @private {settings.printing.CupsPrintersEntryManager} */
- entryManager_: null,
-
- /** @override */
- created: function() {
- this.networkConfig_ =
- network_config.MojoInterfaceProviderImpl.getInstance()
- .getMojoServiceRemote();
- this.entryManager_ =
- settings.printing.CupsPrintersEntryManager.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.networkConfig_
- .getNetworkStateList({
- filter: chromeos.networkConfig.mojom.FilterType.kActive,
- networkType: chromeos.networkConfig.mojom.NetworkType.kAll,
- limit: chromeos.networkConfig.mojom.NO_LIMIT,
- })
- .then((responseParams) => {
- this.onActiveNetworksChanged(responseParams.result);
- });
-
- if (this.enableUpdatedUi_) {
- return;
- }
- },
-
- /** @override */
- ready: function() {
- this.updateCupsPrintersList_();
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- if (route != settings.routes.CUPS_PRINTERS) {
- cr.removeWebUIListener('on-printers-changed');
- this.entryManager_.removeWebUIListeners();
- return;
- }
-
- this.entryManager_.addWebUIListeners();
- cr.addWebUIListener(
- 'on-printers-changed', this.onPrintersChanged_.bind(this));
- this.updateCupsPrintersList_();
- },
-
- /**
- * CrosNetworkConfigObserver impl
- * @param {!Array<chromeos.networkConfig.mojom.NetworkStateProperties>}
- * networks
- * @private
- */
- onActiveNetworksChanged: function(networks) {
- this.canAddPrinter = networks.some(function(network) {
- // Note: Check for kOnline rather than using
- // OncMojo.connectionStateIsConnected() since the latter could return true
- // for networks without connectivity (e.g., captive portals).
- return network.connectionState ==
- chromeos.networkConfig.mojom.ConnectionStateType.kOnline;
- });
- },
-
- /**
- * @param {!CustomEvent<!{
- * resultCode: PrinterSetupResult,
- * printerName: string
- * }>} event
- * @private
- */
- openResultToast_: function(event) {
- const printerName = event.detail.printerName;
- switch (event.detail.resultCode) {
- case PrinterSetupResult.SUCCESS:
- this.addPrinterResultText_ =
- loadTimeData.getStringF('printerAddedSuccessfulMessage',
- printerName);
- break;
- case PrinterSetupResult.EDIT_SUCCESS:
- this.addPrinterResultText_ =
- loadTimeData.getStringF('printerEditedSuccessfulMessage',
- printerName);
- break;
- case PrinterSetupResult.PRINTER_UNREACHABLE:
- if (this.enableUpdatedUi_) {
- this.addPrinterResultText_ =
- loadTimeData.getStringF('printerUnavailableMessage', printerName);
- break;
- }
- default:
- assertNotReached();
- }
-
- this.$.errorToast.show();
- },
-
- /**
- * @param {!CustomEvent<{item: !CupsPrinterInfo}>} e
- * @private
- */
- openManufacturerModelDialogForSpecifiedPrinter_: function(e) {
- const item = e.detail.item;
- this.$.addPrinterDialog
- .openManufacturerModelDialogForSpecifiedPrinter(item);
- },
-
- /** @private */
- updateCupsPrintersList_: function() {
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .getCupsPrintersList()
- .then(this.onPrintersChanged_.bind(this));
- },
-
- /**
- * @param {!CupsPrintersList} cupsPrintersList
- * @private
- */
- onPrintersChanged_: function(cupsPrintersList) {
- if (this.enableUpdatedUi_) {
- this.savedPrinters_ = cupsPrintersList.printerList.map(
- printer => /** @type {!PrinterListEntry} */({
- printerInfo: printer,
- printerType: PrinterType.SAVED}));
- this.entryManager_.setSavedPrintersList(this.savedPrinters_);
- } else {
- this.printers = cupsPrintersList.printerList;
- }
- },
-
- /** @private */
- onAddPrinterTap_: function() {
- this.$.addPrinterDialog.open();
- },
-
- /** @private */
- onAddPrinterDialogClose_: function() {
- cr.ui.focusWithoutInk(assert(
- this.enableUpdatedUi_ ? this.$$('#addManualPrinterIcon')
- : this.$$('#addPrinter')));
- },
-
- /** @private */
- onShowCupsEditPrinterDialog_: function() {
- this.showCupsEditPrinterDialog_ = true;
- },
-
- /** @private */
- onEditPrinterDialogClose_: function() {
- this.showCupsEditPrinterDialog_ = false;
- },
-
- /**
- * @param {string} searchTerm
- * @return {boolean} If the 'no-search-results-found' string should be shown.
- * @private
- */
- showNoSearchResultsMessage_: function(searchTerm) {
- if (!searchTerm || !this.printers.length) {
- return false;
- }
- searchTerm = searchTerm.toLowerCase();
- return !this.printers.some(printer => {
- return printer.printerName.toLowerCase().includes(searchTerm);
- });
- },
-
- /**
- * @param {boolean} connectedToNetwork Whether the device is connected to
- a network.
- * @param {boolean} userNativePrintersAllowed Whether users are allowed to
- configure their own native printers.
- * @return {boolean} Whether the 'Add Printer' button is active.
- * @private
- */
- addPrinterButtonActive_: function(
- connectedToNetwork, userNativePrintersAllowed) {
- return connectedToNetwork && userNativePrintersAllowed;
- },
-
- /**
- * @return {boolean} Whether |savedPrinters_| is empty.
- * @private
- */
- doesAccountHaveSavedPrinters_: function() {
- return !!this.savedPrinters_.length;
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.html
deleted file mode 100644
index 0c0f54fc8ca..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="cups_printers_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js
deleted file mode 100644
index 9f35ce694f5..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "CUPS printing" section to
- * interact with the browser. Used only on Chrome OS.
- */
-
-/**
- * @typedef {{
- * ppdManufacturer: string,
- * ppdModel: string,
- * printerAddress: string,
- * printerDescription: string,
- * printerId: string,
- * printerManufacturer: string,
- * printerModel: string,
- * printerMakeAndModel: string,
- * printerName: string,
- * printerPPDPath: string,
- * printerPpdReference: {
- * userSuppliedPpdUrl: string,
- * effectiveMakeAndModel: string,
- * autoconf: boolean,
- * },
- * printerPpdReferenceResolved: boolean,
- * printerProtocol: string,
- * printerQueue: string,
- * printerStatus: string,
- * }}
- *
- * Note: |printerPPDPath| refers to a PPD retrieved from the user at the
- * add-printer-manufacturer-model-dialog. |printerPpdReference| refers to either
- * information retrieved from the printer or resolved via ppd_provider.
- */
-let CupsPrinterInfo;
-
-/**
- * @typedef {{
- * printerList: !Array<!CupsPrinterInfo>,
- * }}
- */
-let CupsPrintersList;
-
-/**
- * @typedef {{
- * success: boolean,
- * manufacturers: Array<string>
- * }}
- */
-let ManufacturersInfo;
-
-/**
- * @typedef {{
- * success: boolean,
- * models: Array<string>
- * }}
- */
-let ModelsInfo;
-
-/**
- * @typedef {{
- * manufacturer: string,
- * model: string,
- * makeAndModel: string,
- * autoconf: boolean,
- * ppdRefUserSuppliedPpdUrl: string,
- * ppdRefEffectiveMakeAndModel: string,
- * ppdReferenceResolved: boolean
- * }}
- */
-let PrinterMakeModel;
-
-/**
- * @typedef {{
- * ppdManufacturer: string,
- * ppdModel: string
- * }}
- */
-let PrinterPpdMakeModel;
-
-/**
- * @enum {number}
- * These values must be kept in sync with the PrinterSetupResult enum in
- * chrome/browser/chromeos/printing/printer_configurer.h.
- */
-const PrinterSetupResult = {
- FATAL_ERROR: 0,
- SUCCESS: 1,
- PRINTER_UNREACHABLE: 2,
- DBUS_ERROR: 3,
- NATIVE_PRINTERS_NOT_ALLOWED: 4,
- INVALID_PRINTER_UPDATE: 5,
- COMPONENT_UNAVAILAVLE: 6,
- EDIT_SUCCESS: 7,
- PPD_TOO_LARGE: 10,
- INVALID_PPD: 11,
- PPD_NOT_FOUND: 12,
- PPD_UNRETRIEVABLE: 13,
- DBUS_NO_REPLY: 64,
- DBUS_TIMEOUT: 65,
-};
-
-/**
- * @typedef {{
- * message: string
- * }}
- */
-let QueryFailure;
-
-cr.define('settings', function() {
- /** @interface */
- class CupsPrintersBrowserProxy {
- /**
- * @return {!Promise<!CupsPrintersList>}
- */
- getCupsPrintersList() {}
-
- /**
- * @param {string} printerId
- * @param {string} printerName
- * @return {!Promise<!PrinterSetupResult>}
- */
- updateCupsPrinter(printerId, printerName) {}
-
- /**
- * @param {string} printerId
- * @param {string} printerName
- */
- removeCupsPrinter(printerId, printerName) {}
-
- /**
- * @return {!Promise<string>} The full path of the printer PPD file.
- */
- getCupsPrinterPPDPath() {}
-
- /**
- * @param {!CupsPrinterInfo} newPrinter
- * @return {!Promise<!PrinterSetupResult>}
- */
- addCupsPrinter(newPrinter) {}
-
- /**
- * @param {!CupsPrinterInfo} printer
- * @return {!Promise<!PrinterSetupResult>}
- */
- reconfigureCupsPrinter(printer) {}
-
- startDiscoveringPrinters() {}
- stopDiscoveringPrinters() {}
-
- /**
- * @return {!Promise<!ManufacturersInfo>}
- */
- getCupsPrinterManufacturersList() {}
-
- /**
- * @param {string} manufacturer
- * @return {!Promise<!ModelsInfo>}
- */
- getCupsPrinterModelsList(manufacturer) {}
-
- /**
- * @param {!CupsPrinterInfo} newPrinter
- * @return {!Promise<!PrinterMakeModel>}
- */
- getPrinterInfo(newPrinter) {}
-
- /**
- * @param {string} printerId
- * @return {!Promise<!PrinterPpdMakeModel>}
- */
- getPrinterPpdManufacturerAndModel(printerId) {}
-
- /**
- * @param{string} printerId
- * @return {!Promise<!PrinterSetupResult>}
- */
- addDiscoveredPrinter(printerId) {}
-
- /**
- * Report to the handler that setup was cancelled.
- * @param {!CupsPrinterInfo} newPrinter
- */
- cancelPrinterSetUp(newPrinter) {}
-
- /**
- * @param {string} ppdManufacturer
- * @param {string} ppdModel
- * @return {!Promise<string>} Returns the EULA URL of the printer. Returns
- * an empty string if no EULA is required.
- */
- getEulaUrl(ppdManufacturer, ppdModel) {}
- }
-
- /**
- * @implements {settings.CupsPrintersBrowserProxy}
- */
- class CupsPrintersBrowserProxyImpl {
- /** @override */
- getCupsPrintersList() {
- return cr.sendWithPromise('getCupsPrintersList');
- }
-
- /** @override */
- updateCupsPrinter(printerId, printerName) {
- return cr.sendWithPromise('updateCupsPrinter', printerId, printerName);
- }
-
- /** @override */
- removeCupsPrinter(printerId, printerName) {
- chrome.send('removeCupsPrinter', [printerId, printerName]);
- }
-
- /** @override */
- addCupsPrinter(newPrinter) {
- return cr.sendWithPromise('addCupsPrinter', newPrinter);
- }
-
- /** @override */
- reconfigureCupsPrinter(printer) {
- return cr.sendWithPromise('reconfigureCupsPrinter', printer);
- }
-
- /** @override */
- getCupsPrinterPPDPath() {
- return cr.sendWithPromise('selectPPDFile');
- }
-
- /** @override */
- startDiscoveringPrinters() {
- chrome.send('startDiscoveringPrinters');
- }
-
- /** @override */
- stopDiscoveringPrinters() {
- chrome.send('stopDiscoveringPrinters');
- }
-
- /** @override */
- getCupsPrinterManufacturersList() {
- return cr.sendWithPromise('getCupsPrinterManufacturersList');
- }
-
- /** @override */
- getCupsPrinterModelsList(manufacturer) {
- return cr.sendWithPromise('getCupsPrinterModelsList', manufacturer);
- }
-
- /** @override */
- getPrinterInfo(newPrinter) {
- return cr.sendWithPromise('getPrinterInfo', newPrinter);
- }
-
- /** @override */
- getPrinterPpdManufacturerAndModel(printerId) {
- return cr.sendWithPromise('getPrinterPpdManufacturerAndModel', printerId);
- }
-
- /** @override */
- addDiscoveredPrinter(printerId) {
- return cr.sendWithPromise('addDiscoveredPrinter', printerId);
- }
-
- /** @override */
- cancelPrinterSetUp(newPrinter) {
- chrome.send('cancelPrinterSetUp', [newPrinter]);
- }
-
- /** @override */
- getEulaUrl(ppdManufacturer, ppdModel) {
- return cr.sendWithPromise('getEulaUrl', ppdManufacturer, ppdModel);
- }
- }
-
- cr.addSingletonGetter(CupsPrintersBrowserProxyImpl);
-
- return {
- CupsPrintersBrowserProxy: CupsPrintersBrowserProxy,
- CupsPrintersBrowserProxyImpl: CupsPrintersBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.html
deleted file mode 100644
index 8d7c6b5145c..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="cups_printer_types.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-cups-printers-entry">
- <template>
- <style include="settings-shared">
- .printer-name {
- flex: 1;
- }
- </style>
- <div id="entry" class="list-item" focus-row-container>
- <div class="printer-name text-elide">
- <span id="printerName">
- [[printerEntry.printerInfo.printerName]]
- </span>
- <div id="printerSubtext" hidden="[[!subtext]]" class="secondary">
- [[subtext]]
- </div>
- </div>
- <template is="dom-if" if="[[isSavedPrinter_(printerEntry.printerType)]]">
- <cr-icon-button class="icon-more-vert" on-click="onOpenActionMenuTap_"
- title="$i18n{moreActions}">
- </cr-icon-button>
- </template>
- <template is="dom-if"
- if="[[isDiscoveredPrinter_(printerEntry.printerType)]]">
- <cr-button id="setupPrinterButton"
- on-click="onAddDiscoveredPrinterTap_">
- $i18n{setupPrinter}
- </cr-button>
- </template>
- <template is="dom-if"
- if="[[isAutomaticPrinter_(printerEntry.printerType)]]">
- <cr-button id="savePrinterButton" on-click="onAddAutomaticPrinterTap_">
- $i18n{savePrinter}
- </cr-button>
- </template>
- </div>
- </template>
- <script src="cups_printers_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.js
deleted file mode 100644
index 5734b7717ae..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry.js
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cups-printers-entry' is a component that holds a
- * printer.
- */
-Polymer({
- is: 'settings-cups-printers-entry',
-
- properties: {
- /** @type {!PrinterListEntry} */
- printerEntry: Object,
-
- /**
- * TODO(jimmyxgong): Determine how subtext should be set and what
- * information it should have.
- * The additional information subtext for a printer.
- * @type {string}
- */
- subtext: {type: String, value: ''},
- },
-
- /**
- * Fires a custom event when the menu button is clicked. Sends the details of
- * the printer and where the menu should appear.
- */
- onOpenActionMenuTap_: function(e) {
- this.fire('open-action-menu', {
- target: e.target,
- item: this.printerEntry,
- });
- },
-
- onAddDiscoveredPrinterTap_: function(e) {
- this.fire('query-discovered-printer', {item: this.printerEntry});
- },
-
- onAddAutomaticPrinterTap_: function() {
- this.fire('add-automatic-printer', {item: this.printerEntry});
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isSavedPrinter_: function() {
- return this.printerEntry.printerType == PrinterType.SAVED;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isDiscoveredPrinter_: function() {
- return this.printerEntry.printerType == PrinterType.DISCOVERED;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isAutomaticPrinter_: function() {
- return this.printerEntry.printerType == PrinterType.AUTOMATIC;
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html
deleted file mode 100644
index 6f30319498f..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="cups_printer_types.html">
-<script src="cups_printers_entry_list_behavior.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js
deleted file mode 100644
index 59588b24629..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Polymer behavior for observing CupsPrintersEntryManager events.
- * Use this behavior if you want to receive a dynamically updated list of both
- * saved and nearby printers.
- */
-
-/** @polymerBehavior */
-const CupsPrintersEntryListBehavior = {
- properties: {
- /** @private {!settings.printing.CupsPrintersEntryManager} */
- entryManager_: Object,
-
- /** @type {!Array<!PrinterListEntry>} */
- savedPrinters: {
- type: Array,
- value: () => [],
- },
-
- /** @type {!Array<!PrinterListEntry>} */
- nearbyPrinters: {
- type: Array,
- value: () => [],
- },
- },
-
- /** @override */
- created: function() {
- this.entryManager_ =
- settings.printing.CupsPrintersEntryManager.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.entryManager_.addOnSavedPrintersChangedListener(
- this.onSavedPrintersChanged_.bind(this));
- this.entryManager_.addOnNearbyPrintersChangedListener(
- this.onNearbyPrintersChanged_.bind(this));
-
- // Initialize saved and nearby printers list.
- this.onSavedPrintersChanged_(
- this.entryManager_.savedPrinters, [] /* printerAdded */,
- [] /* printerRemoved */);
- this.onNearbyPrintersChanged_(this.entryManager_.nearbyPrinters);
- },
-
- /** @override */
- detached: function() {
- this.entryManager_.removeOnSavedPrintersChangedListener(
- this.onSavedPrintersChanged_.bind(this));
- this.entryManager_.removeOnNearbyPrintersChangedListener(
- this.onNearbyPrintersChanged_.bind(this));
- },
-
- /**
- * Non-empty params indicate the applicable change to be notified.
- * @param {!Array<!PrinterListEntry>} savedPrinters
- * @param {!Array<!PrinterListEntry>} addedPrinters
- * @param {!Array<!PrinterListEntry>} removedPrinters
- * @private
- */
- onSavedPrintersChanged_: function(
- savedPrinters, addedPrinters, removedPrinters) {
- this.updateList(
- 'savedPrinters', printer => printer.printerInfo.printerId,
- savedPrinters);
-
- assert(!(addedPrinters.length && removedPrinters.length));
-
- if (addedPrinters.length) {
- this.onSavedPrintersAdded(addedPrinters);
- } else if (removedPrinters.length) {
- this.onSavedPrintersRemoved(removedPrinters);
- }
- },
-
- /**
- * @param {!Array<!PrinterListEntry>} printerList
- * @private
- */
- onNearbyPrintersChanged_: function(printerList) {
- this.updateList(
- 'nearbyPrinters', printer => printer.printerInfo.printerId,
- printerList);
- },
-
- // CupsPrintersEntryListBehavior methods. Override these in the
- // implementations.
-
- /** @param{!Array<!PrinterListEntry>} addedPrinters */
- onSavedPrintersAdded: function(addedPrinters) {},
-
- /** @param{!Array<!PrinterListEntry>} removedPrinters */
- onSavedPrintersRemoved: function(removedPrinters) {},
-}; \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html
deleted file mode 100644
index d8e4aac3867..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="cups_printer_types.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-<script src="cups_printers_entry_manager.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js
deleted file mode 100644
index 86908fbb3c3..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Function which provides the client with metadata about a change
- * to a list of saved printers. The first parameter is the updated list of
- * printers after the change, the second parameter is the newly-added printer
- * (if it exists), and the third parameter is the newly-removed printer
- * (if it exists).
- * @typedef {!function(!Array<!PrinterListEntry>, !Array<!PrinterListEntry>,
- * !Array<!PrinterListEntry>): void}
- */
-let PrintersListWithDeltasCallback;
-
-/**
- * Function which provides the client with a list that contains the nearby
- * printers list. The parameter is the updated list of printers after any
- * changes.
- * @typedef {function(!Array<!PrinterListEntry>): void}
- */
-let PrintersListCallback;
-
-cr.define('settings.printing', function() {
- /**
- * Finds the printers that are in |firstArr| but not in |secondArr|.
- * @param {!Array<!PrinterListEntry>} firstArr
- * @param {!Array<!PrinterListEntry>} secondArr
- * @return {!Array<!PrinterListEntry>}
- * @private
- */
- function findDifference_(firstArr, secondArr) {
- return firstArr.filter((firstArrEntry) => {
- return !secondArr.some(
- p => p.printerInfo.printerId == firstArrEntry.printerInfo.printerId);
- });
- }
-
- /**
- * Class for managing printer entries. Holds both Saved and Nearby printers
- * and notifies observers of any applicable changes to either printer lists.
- */
- class CupsPrintersEntryManager {
- constructor() {
- /** @private {!Array<!PrinterListEntry>} */
- this.savedPrinters_ = [];
-
- /** @private {!Array<!PrinterListEntry>} */
- this.nearbyPrinters_ = [];
-
- /** @private {!Array<PrintersListWithDeltasCallback>} */
- this.onSavedPrintersChangedListeners_ = [];
-
- /** @type {!Array<PrintersListCallback>} */
- this.onNearbyPrintersChangedListeners_ = [];
- }
-
- addWebUIListeners() {
- // TODO(1005905): Add on-printers-changed listener here once legacy code
- // is removed.
- cr.addWebUIListener(
- 'on-nearby-printers-changed', this.setNearbyPrintersList.bind(this));
- settings.CupsPrintersBrowserProxyImpl.getInstance()
- .startDiscoveringPrinters();
- }
-
- removeWebUIListeners() {
- cr.removeWebUIListener('on-nearby-printers-changed');
- }
-
- /** @return {!Array<!PrinterListEntry>} */
- get savedPrinters() {
- return this.savedPrinters_;
- }
-
- /** @return {!Array<!PrinterListEntry>} */
- get nearbyPrinters() {
- return this.nearbyPrinters_;
- }
-
- /** @param {PrintersListWithDeltasCallback} listener */
- addOnSavedPrintersChangedListener(listener) {
- this.onSavedPrintersChangedListeners_.push(listener);
- }
-
- /** @param {PrintersListWithDeltasCallback} listener */
- removeOnSavedPrintersChangedListener(listener) {
- this.onSavedPrintersChangedListeners_ =
- this.onSavedPrintersChangedListeners_.filter(lis => lis != listener);
- }
-
- /** @param {PrintersListCallback} listener */
- addOnNearbyPrintersChangedListener(listener) {
- this.onNearbyPrintersChangedListeners_.push(listener);
- }
-
- /** @param {PrintersListCallback} listener */
- removeOnNearbyPrintersChangedListener(listener) {
- this.onNearbyPrintersChangedListeners_ =
- this.onNearbyPrintersChangedListeners_.filter(lis => lis != listener);
- }
-
- /**
- * Sets the saved printers list and notifies observers of any applicable
- * changes.
- * @param {!Array<!PrinterListEntry>} printerList
- */
- setSavedPrintersList(printerList) {
- if (printerList.length > this.savedPrinters_.length) {
- const diff = findDifference_(printerList, this.savedPrinters_);
- this.savedPrinters_ = printerList;
- this.notifyOnSavedPrintersChangedListeners_(
- this.savedPrinters_, diff, [] /* printersRemoved */);
- return;
- }
-
- if (printerList.length < this.savedPrinters_.length) {
- const diff = findDifference_(this.savedPrinters_, printerList);
- this.savedPrinters_ = printerList;
- this.notifyOnSavedPrintersChangedListeners_(
- this.savedPrinters_, [] /* printersAdded */, diff);
- return;
- }
-
- this.savedPrinters_ = printerList;
- this.notifyOnSavedPrintersChangedListeners_(
- this.savedPrinters_, [] /* printersAdded */,
- [] /* printersRemoved */);
- }
-
- /**
- * Sets the nearby printers list and notifies observers of any applicable
- * changes.
- * @param {!Array<!CupsPrinterInfo>} automaticPrinters
- * @param {!Array<!CupsPrinterInfo>} discoveredPrinters
- */
- setNearbyPrintersList(automaticPrinters, discoveredPrinters) {
- if (!automaticPrinters && !discoveredPrinters) {
- return;
- }
-
- this.nearbyPrinters_ = [];
-
- for (const printer of automaticPrinters) {
- this.nearbyPrinters_.push(
- {printerInfo: printer, printerType: PrinterType.AUTOMATIC});
- }
-
- for (const printer of discoveredPrinters) {
- this.nearbyPrinters_.push(
- {printerInfo: printer, printerType: PrinterType.DISCOVERED});
- }
-
- this.notifyOnNearbyPrintersChangedListeners_();
- }
-
- /**
- * Non-empty/null fields indicate the applicable change to be notified.
- * @param {!Array<!PrinterListEntry>} savedPrinters
- * @param {!Array<!PrinterListEntry>} addedPrinter
- * @param {!Array<!PrinterListEntry>} removedPrinter
- * @private
- */
- notifyOnSavedPrintersChangedListeners_(
- savedPrinters, addedPrinter, removedPrinter) {
- this.onSavedPrintersChangedListeners_.forEach(
- listener => listener(savedPrinters, addedPrinter, removedPrinter));
- }
-
- /** @private */
- notifyOnNearbyPrintersChangedListeners_() {
- this.onNearbyPrintersChangedListeners_.forEach(
- listener => listener(this.nearbyPrinters_));
- }
- }
-
- cr.addSingletonGetter(CupsPrintersEntryManager);
-
- return {
- CupsPrintersEntryManager: CupsPrintersEntryManager,
- };
-}); \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.html
deleted file mode 100644
index c0e8208c60d..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="cups_printer_dialog_util.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-cups-printers-list">
- <template>
- <style include="settings-shared">
- .printer-name {
- flex: 1;
- }
- </style>
-
- <cr-action-menu>
- <button class="dropdown-item" on-click="onEditTap_">
- $i18n{editPrinter}
- </button>
- <button class="dropdown-item" on-click="onRemoveTap_">
- $i18n{removePrinter}
- </button>
- </cr-action-menu>
- <div class="list-frame vertical-list">
- <template is="dom-repeat" items="[[printers]]"
- filter="[[filterPrinter_(searchTerm)]]"
- sort="sort_">
- <div class="list-item">
- <div class="printer-name text-elide">[[item.printerName]]</div>
- <!--TODO(xdai): Add icon for enterprise CUPS printer. -->
- <cr-icon-button class="icon-more-vert" on-click="onOpenActionMenuTap_"
- title="$i18n{moreActions}"></cr-icon-button>
- </div>
- </template>
- </div>
- </template>
- <script src="cups_printers_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.js
deleted file mode 100644
index df422ff4ca1..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.js
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-cups-printers-list' is a component for a list of
- * CUPS printers.
- */
-Polymer({
- is: 'settings-cups-printers-list',
-
- properties: {
- /** @type {!Array<!CupsPrinterInfo>} */
- printers: {
- type: Array,
- notify: true,
- },
-
- searchTerm: {
- type: String,
- },
-
- /**
- * The model for the printer action menu.
- * @type {?CupsPrinterInfo}
- */
- activePrinter: {
- type: Object,
- notify: true,
- },
- },
-
- /** @private {settings.CupsPrintersBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.CupsPrintersBrowserProxyImpl.getInstance();
- },
-
- /**
- * @param {!{model: !{item: !CupsPrinterInfo}}} e
- * @private
- */
- onOpenActionMenuTap_: function(e) {
- this.activePrinter = e.model.item;
- const menu =
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'));
- menu.showAt(/** @type {!Element} */ (/** @type {!Event} */ (e).target));
- },
-
- /**
- * @param {{model:Object}} event
- * @private
- */
- onEditTap_: function(event) {
- // Event is caught by 'settings-cups-printers'.
- this.fire('edit-cups-printer-details');
- this.closeDropdownMenu_();
- },
-
- /**
- * @param {{model:Object}} event
- * @private
- */
- onRemoveTap_: function(event) {
- const index = this.printers.indexOf(assert(this.activePrinter));
- this.splice('printers', index, 1);
- this.browserProxy_.removeCupsPrinter(
- this.activePrinter.printerId, this.activePrinter.printerName);
- this.activePrinter = null;
- this.closeDropdownMenu_();
- },
-
- /** @private */
- closeDropdownMenu_: function() {
- const menu =
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'));
- menu.close();
- },
-
- /**
- * The filter callback function to show printers based on |searchTerm|.
- * @param {string} searchTerm
- * @private
- */
- filterPrinter_: function(searchTerm) {
- if (!searchTerm) {
- return null;
- }
- return function(printer) {
- return printer.printerName.toLowerCase().includes(
- searchTerm.toLowerCase());
- };
- },
-
- /**
- * @param {!CupsPrinterInfo} first
- * @param {!CupsPrinterInfo} second
- * @return {number} The result of the comparison.
- * @private
- */
- sort_: function(first, second) {
- return settings.printing.alphabeticalSort(first, second);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.html b/chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.html
deleted file mode 100644
index c89d0230a99..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="cups_printer_types.html">
-<link rel="import" href="cups_printers_browser_proxy.html">
-<link rel="import" href="cups_printers_entry_list_behavior.html">
-<link rel="import" href="cups_printers_entry.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-cups-saved-printers">
- <template>
- <style include="cups-printer-shared iron-flex iron-flex-alignment
- iron-flex-factors">
- :host {
- display: flex;
- flex-direction: column;
- }
-
- #no-search-results {
- margin-bottom: 20px;
- }
-
- /** Height of iron list row entry. */
- #show-more-container {
- min-height: var(--settings-row-min-height);
- }
-
- /** Border line that is the same size as a list entry's border. */
- #show-more-line-separator {
- border-bottom: var(--cr-separator-line);
- left: 60px;
- position: relative;
- right: 20px;
- width: 596px;
- }
-
- #show-more-icon {
- --cr-icon-button-margin-end: 0;
- }
-
- #show-more-text {
- flex: 1;
- }
- </style>
-
- <cr-action-menu>
- <button id="editButton" class="dropdown-item" on-click="onEditTap_">
- $i18n{editPrinter}
- </button>
- <button id="removeButton" class="dropdown-item" on-click="onRemoveTap_">
- $i18n{removePrinter}
- </button>
- </cr-action-menu>
-
- <iron-list class="list-frame vertical-list flex-auto" id="printerEntryList"
- items="[[filteredPrinters_]]">
- <template>
- <settings-cups-printers-entry printer-entry="[[item]]">
- </settings-cups-printers-entry>
- </template>
- </iron-list>
- <template is="dom-if" id="show-more-button-section"
- if="[[shouldPrinterListBeCollapsed_(searchTerm, savedPrinters.*,
- newPrinters_.*, hasShowMoreBeenTapped_)]]" restamp>
- <div id="show-more-line-separator"></div>
- <div class="list-frame layout horizontal" id="show-more-container">
- <div id="show-more-text">$i18n{showMorePrinters}</div>
- <cr-icon-button class="action-button" id="show-more-icon"
- iron-icon="cr:expand-more"
- on-click="onShowMoreTap_"
- title=$i18n{showMorePrinters}>
- </cr-icon-button>
- </div>
- </template>
-
- <div id="no-search-results"
- hidden="[[!showNoSearchResultsMessage_(searchTerm,
- filteredPrinters_.*)]]">
- $i18n{noSearchResults}
- </div>
- </template>
- <script src="cups_saved_printers.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.js b/chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.js
deleted file mode 100644
index 948bc0526ab..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/cups_saved_printers.js
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-
-// If the Show more button is visible, the minimum number of printers we show
-// is 3.
-const kMinVisiblePrinters = 3;
-
-/**
- * Move a printer's position in |printerArr| from |fromIndex| to |toIndex|.
- * @param {!Array<!PrinterListEntry>} printerArr
- * @param {number} fromIndex
- * @param {number} toIndex
- */
-function moveEntryInPrinters(printerArr, fromIndex, toIndex) {
- const element = printerArr[fromIndex];
- printerArr.splice(fromIndex, 1);
- printerArr.splice(toIndex, 0, element);
-}
-
-/**
- * @fileoverview 'settings-cups-saved-printers' is a list container for Saved
- * Printers.
- */
-Polymer({
- is: 'settings-cups-saved-printers',
-
- // ListPropertyUpdateBehavior is used in CupsPrintersEntryListBehavior.
- behaviors: [
- CupsPrintersEntryListBehavior,
- ListPropertyUpdateBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * Search term for filtering |savedPrinters|.
- * @type {string}
- */
- searchTerm: {
- type: String,
- value: '',
- },
-
- /** @type {?CupsPrinterInfo} */
- activePrinter: {
- type: Object,
- notify: true,
- },
-
- /**
- * @type {number}
- * @private
- */
- activePrinterListEntryIndex_: {
- type: Number,
- value: -1,
- },
-
- /**
- * List of printers filtered through a search term.
- * @type {!Array<!PrinterListEntry>}
- * @private
- */
- filteredPrinters_: {
- type: Array,
- value: () => [],
- },
-
- /**
- * Array of new PrinterListEntry's that were added during this session.
- * @type {!Array<!PrinterListEntry>}
- * @private
- */
- newPrinters_: {
- type: Array,
- value: () => [],
- },
-
- /**
- * Keeps track of whether the user has tapped the Show more button. A search
- * term will expand the collapsed list, so we need to keep track of whether
- * the list expanded because of a search term or because the user tapped on
- * the Show more button.
- * @private
- */
- hasShowMoreBeenTapped_: {
- type: Boolean,
- value: false,
- },
- },
-
- listeners: {
- 'open-action-menu': 'onOpenActionMenu_',
- },
-
- observers: [
- 'onSearchOrPrintersChanged_(savedPrinters.*, searchTerm,' +
- 'hasShowMoreBeenTapped_, newPrinters_.*)'
- ],
-
- /** @private {settings.CupsPrintersBrowserProxy} */
- browserProxy_: null,
-
- /**
- * The number of printers we display if hidden printers are allowed.
- * kMinVisiblePrinters is the default value and we never show fewer printers
- * if the Show more button is visible.
- */
- visiblePrinterCounter_: kMinVisiblePrinters,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.CupsPrintersBrowserProxyImpl.getInstance();
- },
-
- /**
- * Redoes the search whenever |searchTerm| or |savedPrinters| changes.
- * @private
- */
- onSearchOrPrintersChanged_: function() {
- if (!this.savedPrinters) {
- return;
- }
-
- const updatedPrinters = this.getVisiblePrinters_();
-
- this.updateList(
- 'filteredPrinters_', printer => printer.printerInfo.printerId,
- updatedPrinters);
- },
-
- /**
- * @param {!CustomEvent<{target: !HTMLElement, item: !PrinterListEntry}>} e
- * @private
- */
- onOpenActionMenu_: function(e) {
- const item = /** @type {!PrinterListEntry} */(e.detail.item);
- this.activePrinterListEntryIndex_ = this.savedPrinters.findIndex(
- printer => printer.printerInfo.printerId == item.printerInfo.printerId);
- this.activePrinter =
- this.get(['savedPrinters', this.activePrinterListEntryIndex_])
- .printerInfo;
-
- const target = /** @type {!HTMLElement} */ (e.detail.target);
- this.$$('cr-action-menu').showAt(target);
- },
-
- /** @private */
- onEditTap_: function() {
- // Event is caught by 'settings-cups-printers'.
- this.fire('edit-cups-printer-details');
- this.closeActionMenu_();
- },
-
- /** @private */
- onRemoveTap_: function() {
- this.browserProxy_.removeCupsPrinter(
- this.activePrinter.printerId, this.activePrinter.printerName);
- this.activePrinter = null;
- this.activeListEntryIndex_ = -1;
- this.closeActionMenu_();
- },
-
- /** @private */
- onShowMoreTap_: function() {
- this.hasShowMoreBeenTapped_ = true;
- },
-
- /**
- * Gets the printers to be shown in the UI. These printers are filtered
- * by the search term, alphabetically sorted (if applicable), and are the
- * printers not hidden by the Show more section.
- * @return {!Array<!PrinterListEntry>} Returns only the visible printers.
- * @private
- */
- getVisiblePrinters_: function() {
- // Filter printers through |searchTerm|. If |searchTerm| is empty,
- // |filteredPrinters_| is just |savedPrinters|.
- const updatedPrinters = this.searchTerm ?
- this.savedPrinters.filter(
- item => settings.printing.matchesSearchTerm(
- item.printerInfo, this.searchTerm)) :
- this.savedPrinters.slice();
-
- updatedPrinters.sort(settings.printing.sortPrinters);
-
- this.moveNewlyAddedPrinters_(updatedPrinters, 0 /* toIndex */);
-
- if (this.shouldPrinterListBeCollapsed_()) {
- // If the Show more button is visible, we only display the first
- // N < |visiblePrinterCounter_| printers and the rest are hidden.
- return updatedPrinters.filter(
- (printer, idx) => idx < this.visiblePrinterCounter_);
- }
- return updatedPrinters;
- },
-
- /** @private */
- closeActionMenu_: function() {
- this.$$('cr-action-menu').close();
- },
-
- /**
- * @return {boolean} Returns true if the no search message should be visible.
- * @private
- */
- showNoSearchResultsMessage_: function() {
- return !!this.searchTerm && !this.filteredPrinters_.length;
- },
-
- /** @param{!Array<!PrinterListEntry>} addedPrinters */
- onSavedPrintersAdded: function(addedPrinters) {
- const currArr = this.newPrinters_.slice();
- for (const printer of addedPrinters) {
- this.visiblePrinterCounter_++;
- currArr.push(printer);
- }
-
- this.set('newPrinters_', currArr);
- },
-
- /** @param{!Array<!PrinterListEntry>} removedPrinters */
- onSavedPrintersRemoved: function(removedPrinters) {
- const currArr = this.newPrinters_.slice();
- for (const printer of removedPrinters) {
- const newPrinterRemovedIdx = currArr.findIndex(
- p => p.printerInfo.printerId == printer.printerInfo.printerId);
- // If the removed printer is a recently added printer, remove it from
- // |currArr|.
- if (newPrinterRemovedIdx > -1) {
- currArr.splice(newPrinterRemovedIdx, 1);
- }
-
- this.visiblePrinterCounter_ = Math.max(
- kMinVisiblePrinters, --this.visiblePrinterCounter_);
- }
-
- this.set('newPrinters_', currArr);
- },
-
- /**
- * Keeps track of whether the Show more button should be visible which means
- * that the printer list is collapsed. There are two ways a collapsed list
- * may be expanded: the Show more button is tapped or if there is a search
- * term.
- * @return {boolean} True if the printer list should be collapsed.
- * @private
- */
- shouldPrinterListBeCollapsed_: function() {
- // If |searchTerm| is set, never collapse the list.
- if (this.searchTerm) {
- return false;
- }
-
- // If |hasShowMoreBeenTapped_| is set to true, never collapse the list.
- if (this.hasShowMoreBeenTapped_) {
- return false;
- }
-
- // If the total number of saved printers does not exceed the number of
- // visible printers, there is no need for the list to be collapsed.
- if (this.savedPrinters.length - this.visiblePrinterCounter_ < 1) {
- return false;
- }
-
- return true;
- },
-
- /**
- * Moves printers that are in |newPrinters_| to position |toIndex| of
- * |printerArr|. This moves all recently added printers to the top of the
- * printer list.
- * @param {!Array<!PrinterListEntry>} printerArr
- * @param {number} toIndex
- * @private
- */
- moveNewlyAddedPrinters_: function(printerArr, toIndex) {
- if (!this.newPrinters_.length) {
- return;
- }
-
- // We have newly added printers, move them to the top of the list.
- for (const printer of this.newPrinters_) {
- const idx = printerArr.findIndex(
- p => p.printerInfo.printerId == printer.printerInfo.printerId);
- if (idx > -1) {
- moveEntryInPrinters(printerArr, idx, toIndex);
- }
- }
- }
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.html b/chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.html
deleted file mode 100644
index 8e6d297ef56..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="printing_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.js b/chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.js
deleted file mode 100644
index 83b8536e836..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/printing_browser_proxy.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the Chrome printing section to
- * interact with the browser. Used on operating system that is not Chrome OS.
- */
-
-cr.define('settings', function() {
- /** @interface */
- class PrintingBrowserProxy {
- /**
- * Open the native print system dialog.
- */
- openSystemPrintDialog() {}
- }
-
- /**
- * @implements {settings.PrintingBrowserProxy}
- */
- class PrintingBrowserProxyImpl {
- /** @override */
- openSystemPrintDialog() {
- chrome.send('openSystemPrintDialog');
- }
- }
-
- cr.addSingletonGetter(PrintingBrowserProxyImpl);
-
- return {
- PrintingBrowserProxy: PrintingBrowserProxy,
- PrintingBrowserProxyImpl: PrintingBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/printing_page/printing_page.html b/chromium/chrome/browser/resources/settings/printing_page/printing_page.html
deleted file mode 100644
index ca572d61d8d..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/printing_page.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="cloud_printers.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<if expr="chromeos">
-<link rel="import" href="cups_printers.html">
-</if>
-<if expr="not chromeos">
-<link rel="import" href="printing_browser_proxy.html">
-</if>
-
-<dom-module id="settings-printing-page">
- <template>
- <style include="settings-shared"></style>
- <settings-animated-pages id="pages" section="printing"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
-<if expr="chromeos">
- <cr-link-row hidden$="[[!showCupsPrinters_]]"
- id="cupsPrinters" label="$i18n{cupsPrintersTitle}"
- on-click="onTapCupsPrinters_"></cr-link-row>
-</if>
-<if expr="not chromeos">
- <cr-link-row label="$i18n{localPrintersTitle}"
- on-click="onTapLocalPrinters_" external></cr-link-row>
-</if>
- <cr-link-row class="hr" id="cloudPrinters"
- label="$i18n{cloudPrintersTitle}" on-click="onTapCloudPrinters_">
- </cr-link-row>
- </div>
-<if expr="chromeos">
- <template is="dom-if" route-path="/cupsPrinters">
- <settings-subpage
- associated-control="[[$$('#cupsPrinters')]]"
- page-title="$i18n{cupsPrintersTitle}"
- search-label="$i18n{searchLabel}"
- search-term="{{searchTerm}}">
- <settings-cups-printers search-term="{{searchTerm}}"
- prefs="[[prefs]]">
- </settings-cups-printers>
- </settings-subpage>
- </template>
-</if>
- <template is="dom-if" route-path="/cloudPrinters">
- <settings-subpage
- associated-control="[[$$('#cloudPrinters')]]"
- page-title="$i18n{cloudPrintersTitle}">
- <settings-cloud-printers prefs="{{prefs}}">
- </settings-cloud-printers>
- </settings-subpage>
- </template>
- </settings-animated-pages>
- </template>
- <script src="printing_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/printing_page/printing_page.js b/chromium/chrome/browser/resources/settings/printing_page/printing_page.js
deleted file mode 100644
index 678364bd05a..00000000000
--- a/chromium/chrome/browser/resources/settings/printing_page/printing_page.js
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-Polymer({
- is: 'settings-printing-page',
-
- properties: {
- /** Preferences state. */
- prefs: {
- type: Object,
- notify: true,
- },
-
- searchTerm: {
- type: String,
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- if (settings.routes.CLOUD_PRINTERS) {
- map.set(settings.routes.CLOUD_PRINTERS.path, '#cloudPrinters');
- }
- // <if expr="chromeos">
- if (settings.routes.CUPS_PRINTERS) {
- map.set(settings.routes.CUPS_PRINTERS.path, '#cupsPrinters');
- }
- // </if>
- return map;
- },
- },
-
- // <if expr="chromeos">
- /**
- * TODO(crbug.com/950007): Remove when SplitSettings is the default because
- * CUPS printers will exist only in the OS settings page.
- * @private
- */
- showCupsPrinters_: {
- type: Boolean,
- value: () => loadTimeData.getBoolean('showOSSettings'),
- }
- // </if>
- },
-
- // <if expr="chromeos">
- /** @private */
- onTapCupsPrinters_: function() {
- settings.navigateTo(settings.routes.CUPS_PRINTERS);
- },
- // </if>
-
- // <if expr="not chromeos">
- onTapLocalPrinters_: function() {
- settings.PrintingBrowserProxyImpl.getInstance().openSystemPrintDialog();
- },
- // </if>
-
- /** @private */
- onTapCloudPrinters_: function() {
- settings.navigateTo(settings.routes.CLOUD_PRINTERS);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/personalization_options.html b/chromium/chrome/browser/resources/settings/privacy_page/personalization_options.html
deleted file mode 100644
index 34d663a378f..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/personalization_options.html
+++ /dev/null
@@ -1,113 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../lifetime_browser_proxy.html">
-<link rel="import" href="../people_page/sync_browser_proxy.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="privacy_page_browser_proxy.html">
-
-<!--
- * Note: If a synced pref is added/removed from this file, the method
- * |GetSyncedServicePrefNames| in
- * chrome/browser/unified_consent/unified_consent_service_factory.cc has to be
- * updated accordingly.
--->
-<dom-module id="settings-personalization-options">
- <template>
- <style include="settings-shared">
- :host(.list-frame) settings-toggle-button,
- :host(.list-frame) .settings-box {
- padding-inline-end: 0;
- padding-inline-start: 0;
- }
-
- :host(.list-frame) settings-toggle-button:first-of-type {
- border-top: none;
- }
- </style>
- <settings-toggle-button hidden="[[!pageVisibility.searchPrediction]]"
- pref="{{prefs.search.suggest_enabled}}"
- label="$i18n{searchSuggestPref}"
- sub-label="$i18n{searchSuggestPrefDesc}">
- </settings-toggle-button>
- <settings-toggle-button pref="{{prefs.alternate_error_pages.enabled}}"
- label="$i18n{linkDoctorPref}"
- sub-label="$i18n{linkDoctorPrefDesc}">
- </settings-toggle-button>
- <settings-toggle-button pref="{{prefs.safebrowsing.enabled}}"
- label="$i18n{safeBrowsingEnableProtection}"
- sub-label="$i18n{safeBrowsingEnableProtectionDesc}">
- </settings-toggle-button>
- <settings-toggle-button id="passwordsLeakDetectionCheckbox"
- pref="{{prefs.profile.password_manager_leak_detection}}"
- checked="[[getCheckedLeakDetection_(
- userSignedIn_, passwordsLeakDetectionAvailable_)]]"
- label="$i18n{passwordsLeakDetectionLabel}"
- sub-label="[[getPasswordsLeakDetectionSubLabel_(
- userSignedIn_, passwordsLeakDetectionAvailable_)]]"
- hidden$="[[!passwordsLeakDetectionEnabled_]]"
- disabled="[[getDisabledLeakDetection_(
- userSignedIn_, prefs.*)]]">
- </settings-toggle-button>
- <settings-toggle-button
- pref="{{prefs.safebrowsing.scout_reporting_enabled}}"
- checked="[[getCheckedExtendedSafeBrowsing_(prefs.*)]]"
- label="$i18n{safeBrowsingEnableExtendedReporting}"
- sub-label="$i18n{safeBrowsingEnableExtendedReportingDesc}"
- disabled="[[getDisabledExtendedSafeBrowsing_(prefs.*)]]">
- </settings-toggle-button>
-<if expr="_google_chrome">
-<if expr="chromeos">
- <settings-toggle-button pref="{{prefs.cros.metrics.reportingEnabled}}"
- label="$i18n{enableLogging}"
- sub-label="$i18n{enableLoggingDesc}">
- </settings-toggle-button>
-</if><!-- chromeos -->
-<if expr="not chromeos">
- <settings-toggle-button id="metricsReportingControl"
- pref="[[metricsReportingPref_]]" label="$i18n{enableLogging}"
- sub-label="$i18n{enableLoggingDesc}" no-set-pref
- on-settings-boolean-control-change="onMetricsReportingChange_">
- <template is="dom-if" if="[[showRestart_]]" restamp>
- <cr-button on-click="onRestartTap_" id="restart"
- slot="more-actions">
- $i18n{restart}
- </cr-button>
- </template>
- </settings-toggle-button>
-</if><!-- not chromeos -->
-</if><!-- _google_chrome -->
- <template is="dom-if" if="[[unifiedConsentEnabled]]">
- <settings-toggle-button
- pref="{{prefs.url_keyed_anonymized_data_collection.enabled}}"
- label="$i18n{urlKeyedAnonymizedDataCollection}"
- sub-label="$i18n{urlKeyedAnonymizedDataCollectionDesc}">
- </settings-toggle-button>
- </template>
-<if expr="_google_chrome">
- <settings-toggle-button id="spellCheckControl"
- pref="{{prefs.spellcheck.use_spelling_service}}"
- on-settings-boolean-control-change="onUseSpellingServiceToggle_"
- label="$i18n{spellingPref}"
- sub-label="$i18n{spellingDescription}"
- hidden="[[!showSpellCheckControl_(prefs.spellcheck.dictionaries)]]">
- </settings-toggle-button>
-</if><!-- _google_chrome -->
- <template is="dom-if" if="[[shouldShowDriveSuggest_(unifiedConsentEnabled,
- syncStatus, syncStatus.signedIn, syncStatus.statusAction)]]" restamp>
- <settings-toggle-button id="driveSuggestControl"
- pref="{{prefs.documentsuggest.enabled}}"
- label="$i18n{driveSuggestPref}"
- sub-label="$i18n{driveSuggestPrefDesc}">
- </settings-toggle-button>
- </template>
- </template>
- <script src="personalization_options.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/personalization_options.js b/chromium/chrome/browser/resources/settings/privacy_page/personalization_options.js
deleted file mode 100644
index 5d066a1dee6..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/personalization_options.js
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'personalization-options' contains several toggles related to
- * personalizations.
- */
-(function() {
-
-Polymer({
- is: 'settings-personalization-options',
-
- behaviors: [
- I18nBehavior,
- PrefsBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!PrivacyPageVisibility}
- */
- pageVisibility: Object,
-
- unifiedConsentEnabled: Boolean,
-
- /** @type {settings.SyncStatus} */
- syncStatus: Object,
-
- // <if expr="not chromeos">
- /** @private {Array<!settings.StoredAccount>} */
- storedAccounts_: Object,
- // </if>
-
- /** @private */
- userSignedIn_: {
- type: Boolean,
- computed: 'computeUserSignedIn_(syncStatus, storedAccounts_)',
- },
-
- /** @private */
- passwordsLeakDetectionAvailable_: {
- type: Boolean,
- computed: 'computePasswordsLeakDetectionAvailable_(prefs.*)',
- },
-
- /** @private */
- passwordsLeakDetectionEnabled_: {
- type: Boolean,
- value: loadTimeData.getBoolean('passwordsLeakDetectionEnabled'),
- },
-
- // <if expr="_google_chrome and not chromeos">
- // TODO(dbeam): make a virtual.* pref namespace and set/get this normally
- // (but handled differently in C++).
- /** @private {chrome.settingsPrivate.PrefObject} */
- metricsReportingPref_: {
- type: Object,
- value: function() {
- // TODO(dbeam): this is basically only to appease PrefControlBehavior.
- // Maybe add a no-validate attribute instead? This makes little sense.
- return /** @type {chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
-
- /** @private */
- showRestart_: Boolean,
- // </if>
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.PrivacyPageBrowserProxyImpl.getInstance();
-
- // <if expr="_google_chrome and not chromeos">
- const setMetricsReportingPref = this.setMetricsReportingPref_.bind(this);
- this.addWebUIListener('metrics-reporting-change', setMetricsReportingPref);
- this.browserProxy_.getMetricsReporting().then(setMetricsReportingPref);
- // </if>
- // <if expr="not chromeos">
- const storedAccountsChanged = storedAccounts => this.storedAccounts_ =
- storedAccounts;
- const syncBrowserProxy = settings.SyncBrowserProxyImpl.getInstance();
- syncBrowserProxy.getStoredAccounts().then(storedAccountsChanged);
- this.addWebUIListener('stored-accounts-updated', storedAccountsChanged);
- // </if>
-
- // Even though we already set checked="[[getCheckedLeakDetection_(...)]]"
- // in the DOM, this might be overridden within prefValueChanged_ of
- // SettingsBooleanControlBehaviorImpl which gets invoked once we navigate to
- // sync_page.html. Re-computing the checked value here once fixes this
- // problem.
- this.$.passwordsLeakDetectionCheckbox.checked =
- this.getCheckedLeakDetection_();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeUserSignedIn_: function() {
- return (!!this.syncStatus && !!this.syncStatus.signedIn) ?
- !this.syncStatus.hasError :
- (!!this.storedAccounts_ && this.storedAccounts_.length > 0);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computePasswordsLeakDetectionAvailable_: function() {
- return !!this.getPref('profile.password_manager_leak_detection').value &&
- !!this.getPref('safebrowsing.enabled').value;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- getCheckedLeakDetection_: function() {
- return this.userSignedIn_ && this.passwordsLeakDetectionAvailable_;
- },
-
- /**
- * @return {string}
- * @private
- */
- getPasswordsLeakDetectionSubLabel_: function() {
- if (!this.userSignedIn_ && this.passwordsLeakDetectionAvailable_) {
- return this.i18n('passwordsLeakDetectionSignedOutEnabledDescription');
- }
- return '';
- },
-
- /**
- * @return {boolean}
- * @private
- */
- getDisabledLeakDetection_: function() {
- return !this.userSignedIn_ || !this.getPref('safebrowsing.enabled').value;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- getCheckedExtendedSafeBrowsing_: function() {
- return !!this.getPref('safebrowsing.enabled').value &&
- !!this.getPref('safebrowsing.scout_reporting_enabled').value;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- getDisabledExtendedSafeBrowsing_: function() {
- return !this.getPref('safebrowsing.enabled').value;
- },
-
- // <if expr="_google_chrome and not chromeos">
- /** @private */
- onMetricsReportingChange_: function() {
- const enabled = this.$.metricsReportingControl.checked;
- this.browserProxy_.setMetricsReportingEnabled(enabled);
- },
-
- /**
- * @param {!MetricsReporting} metricsReporting
- * @private
- */
- setMetricsReportingPref_: function(metricsReporting) {
- const hadPreviousPref = this.metricsReportingPref_.value !== undefined;
- const pref = {
- key: '',
- type: chrome.settingsPrivate.PrefType.BOOLEAN,
- value: metricsReporting.enabled,
- };
- if (metricsReporting.managed) {
- pref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
- pref.controlledBy = chrome.settingsPrivate.ControlledBy.USER_POLICY;
- }
-
- // Ignore the next change because it will happen when we set the pref.
- this.metricsReportingPref_ = pref;
-
- // TODO(dbeam): remember whether metrics reporting was enabled when Chrome
- // started.
- if (metricsReporting.managed) {
- this.showRestart_ = false;
- } else if (hadPreviousPref) {
- this.showRestart_ = true;
- }
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onRestartTap_: function(e) {
- e.stopPropagation();
- settings.LifetimeBrowserProxyImpl.getInstance().restart();
- },
- // </if>
-
- // <if expr="_google_chrome">
- /**
- * @param {!Event} event
- * @private
- */
- onUseSpellingServiceToggle_: function(event) {
- // If turning on using the spelling service, automatically turn on
- // spellcheck so that the spelling service can run.
- if (event.target.checked) {
- this.setPrefValue('browser.enable_spellchecking', true);
- }
- },
- // </if>
-
- /**
- * @return {boolean}
- * @private
- */
- showSpellCheckControl_: function() {
- return !this.unifiedConsentEnabled ||
- (!!this.prefs.spellcheck &&
- /** @type {!Array<string>} */
- (this.prefs.spellcheck.dictionaries.value).length > 0);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldShowDriveSuggest_: function() {
- return loadTimeData.getBoolean('driveSuggestAvailable') &&
- !!this.unifiedConsentEnabled && !!this.syncStatus &&
- !!this.syncStatus.signedIn &&
- this.syncStatus.statusAction !== settings.StatusAction.REAUTHENTICATE;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html
deleted file mode 100644
index 9b9618c956d..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ /dev/null
@@ -1,722 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="security_keys_subpage.html">
-<link rel="import" href="../clear_browsing_data_dialog/clear_browsing_data_dialog.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../lifetime_browser_proxy.html">
-<link rel="import" href="../people_page/sync_browser_proxy.html">
-<link rel="import" href="../people_page/signout_dialog.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_settings/all_sites.html">
-<link rel="import" href="../site_settings/category_default_setting.html">
-<link rel="import" href="../site_settings/category_setting_exceptions.html">
-<link rel="import" href="../site_settings/chooser_exception_list.html">
-<link rel="import" href="../site_settings/constants.html">
-<link rel="import" href="../site_settings/media_picker.html">
-<link rel="import" href="../site_settings/pdf_documents.html">
-<link rel="import" href="../site_settings/protocol_handlers.html">
-<link rel="import" href="../site_settings/site_data_details_subpage.html">
-<link rel="import" href="../site_settings/site_data.html">
-<link rel="import" href="../site_settings/site_details.html">
-<link rel="import" href="../site_settings/zoom_levels.html">
-<link rel="import" href="../site_settings_page/site_settings_page.html">
-
-<if expr="not chromeos">
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
-</if>
-<if expr="use_nss_certs">
- <link rel="import" href="chrome://resources/cr_components/certificate_manager/certificate_manager.html">
-</if>
-<link rel="import" href="privacy_page_browser_proxy.html">
-<link rel="import" href="personalization_options.html">
-
-<dom-module id="settings-privacy-page">
- <template>
- <style include="settings-shared">
-<if expr="not chromeos">
- #toast {
- left: 0;
- z-index: 1;
- }
-
- :host-context([dir='rtl']) #toast {
- left: auto;
- right: 0;
- }
-</if>
- </style>
- <template is="dom-if" if="[[showClearBrowsingDataDialog_]]" restamp>
- <settings-clear-browsing-data-dialog prefs="{{prefs}}"
- on-close="onDialogClosed_">
- </settings-clear-browsing-data-dialog>
- </template>
- <template id="doNotTrackDialogIf" is="dom-if" if="[[showDoNotTrackDialog_]]"
- on-dom-change="onDoNotTrackDomChange_">
- <cr-dialog id="confirmDoNotTrackDialog"
- close-text="$i18n{close}" on-cancel="onDoNotTrackDialogCancel_"
- on-close="onDoNotTrackDialogClosed_">
- <div slot="title">$i18n{doNotTrackDialogTitle}</div>
- <div slot="body">$i18nRaw{doNotTrackDialogMessage}</div>
- <div slot="button-container">
- <cr-button class="cancel-button"
- on-click="onDoNotTrackDialogCancel_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button"
- on-click="onDoNotTrackDialogConfirm_">
- $i18n{confirm}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <settings-animated-pages id="pages" section="privacy"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <template is="dom-if" if="[[!unifiedConsentEnabled_]]">
- <div class="settings-box first">
- <p class="privacy-explanation">
- $i18nRaw{improveBrowsingExperience}
- </p>
- </div>
- </template>
- <cr-link-row label="$i18n{syncAndNonPersonalizedServices}"
- sub-label="$i18n{syncAndGoogleServicesPrivacyDescription}"
- on-click="onSyncAndGoogleServicesClick_"
- hidden="[[!unifiedConsentEnabled_]]"></cr-link-row>
-<if expr="not chromeos">
- <settings-toggle-button id="signinAllowedToggle"
- pref="{{prefs.signin.allowed_on_next_startup}}"
- label="$i18n{signinAllowedTitle}"
- sub-label="$i18n{signinAllowedDescription}"
- on-settings-boolean-control-change="onSigninAllowedChange_"
- no-set-pref>
- </settings-toggle-button>
-</if><!-- not chromeos -->
- <template is="dom-if" if="[[!unifiedConsentEnabled_]]">
- <settings-personalization-options prefs="{{prefs}}"
- page-visibility="[[pageVisibility]]"
- unified-consent-enabled="[[unifiedConsentEnabled_]]">
- </settings-personalization-options>
- </template>
- <settings-toggle-button id="doNotTrack"
- pref="{{prefs.enable_do_not_track}}" label="$i18n{doNotTrack}"
- on-settings-boolean-control-change="onDoNotTrackChange_"
- no-set-pref>
- </settings-toggle-button>
- <settings-toggle-button id="canMakePaymentToggle"
- aria-label="$i18n{canMakePaymentToggleLabel}"
- label="$i18n{canMakePaymentToggleLabel}"
- pref="{{prefs.payments.can_make_payment_enabled}}">
- </settings-toggle-button>
- <settings-toggle-button hidden="[[!pageVisibility.networkPrediction]]"
- pref="{{prefs.net.network_prediction_options}}"
- label="$i18n{networkPredictionEnabled}"
- sub-label="$i18n{networkPredictionEnabledDesc}"
- numeric-unchecked-value="[[networkPredictionUncheckedValue_]]">
- </settings-toggle-button>
-<if expr="chromeos">
- <settings-toggle-button
- hidden="[[!pageVisibility.contentProtectionAttestation]]"
- pref="{{prefs.cros.device.attestation_for_content_protection_enabled}}"
- label="$i18n{enableContentProtectionAttestation}">
- </settings-toggle-button>
- <settings-toggle-button
- hidden="[[!pageVisibility.wakeOnWifi]]"
- pref="{{prefs.settings.internet.wake_on_wifi_darkconnect}}"
- label="$i18n{wakeOnWifi}">
- </settings-toggle-button>
-</if>
-<if expr="use_nss_certs or is_win or is_macosx">
- <cr-link-row id="manageCertificates"
- class="hr"
-<if expr="not use_nss_certs">
- external
-</if>
- label="$i18n{manageCertificates}"
- sub-label="$i18n{manageCertificatesDescription}"
- on-click="onManageCertificatesTap_"></cr-link-row>
-</if>
- <template is="dom-if" if="[[enableSecurityKeysSubpage_]]">
- <cr-link-row id="security-keys-subpage-trigger"
- class="hr"
- label="$i18n{securityKeysTitle}"
- sub-label="$i18n{securityKeysDesc}"
- on-click="onSecurityKeysTap_"></cr-link-row>
- </template>
- <cr-link-row id="site-settings-subpage-trigger"
- class="hr"
- label="$i18n{siteSettings}"
- sub-label="$i18n{siteSettingsDescription}"
- on-click="onSiteSettingsTap_"></cr-link-row>
- <cr-link-row id="clearBrowsingData"
- class="hr"
- label="$i18n{clearBrowsingData}"
- sub-label="$i18n{clearBrowsingDataDescription}"
- on-click="onClearBrowsingDataTap_"></cr-link-row>
- </div>
-
-<if expr="use_nss_certs">
- <template is="dom-if" route-path="/certificates">
- <settings-subpage
- associated-control="[[$$('#manageCertificates')]]"
- page-title="$i18n{manageCertificates}">
- <certificate-manager></certificate-manager>
- </settings-subpage>
- </template>
-</if>
-
- <template is="dom-if" if="[[enableSecurityKeysSubpage_]]">
- <template is="dom-if" route-path="/securityKeys">
- <settings-subpage
- associated-control="[[$$('#security-keys-subpage-trigger')]]"
- page-title="$i18n{securityKeysTitle}">
- <security-keys-subpage></security-keys-subpage>
- </settings-subpage>
- </template>
- </template>
-
- <template is="dom-if" route-path="/content">
- <settings-subpage
- associated-control="[[$$('#site-settings-subpage-trigger')]]"
- id="site-settings"
- page-title="$i18n{siteSettings}"
- learn-more-url="$i18n{exceptionsLearnMoreURL}">
- <settings-site-settings-page focus-config="[[focusConfig_]]">
- </settings-site-settings-page>
- </settings-subpage>
- </template>
-
- <template is="dom-if" route-path="/content/all" no-search>
- <settings-subpage page-title="$i18n{siteSettingsAllSites}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <all-sites filter="[[searchFilter_]]"></all-sites>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/automaticDownloads" no-search>
- <settings-subpage page-title="$i18n{siteSettingsAutomaticDownloads}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsAutoDownloadBlock}"
- toggle-on-label="$i18n{siteSettingsAutoDownloadAskRecommended}"
- category="{{ContentSettingsTypes.AUTOMATIC_DOWNLOADS}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.AUTOMATIC_DOWNLOADS}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/backgroundSync" no-search>
- <settings-subpage page-title="$i18n{siteSettingsBackgroundSync}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsBackgroundSyncBlocked}"
- toggle-on-label=
- "$i18n{siteSettingsAllowRecentlyClosedSitesRecommended}"
- category="{{ContentSettingsTypes.BACKGROUND_SYNC}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.BACKGROUND_SYNC}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/camera" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryCamera}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <media-picker label="$i18n{siteSettingsCameraLabel}" type="camera">
- </media-picker>
- <category-default-setting category="{{ContentSettingsTypes.CAMERA}}"
- toggle-off-label="$i18n{siteSettingsBlocked}"
- toggle-on-label=
- "$i18n{siteSettingsAskBeforeAccessingRecommended}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.CAMERA}}" read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/cookies" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryCookies}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting category="{{ContentSettingsTypes.COOKIES}}"
- toggle-off-label="$i18n{siteSettingsBlocked}"
- toggle-on-label="$i18n{siteSettingsCookiesAllowedRecommended}"
- sub-option-label="$i18n{deleteDataPostSession}">
- </category-default-setting>
- <settings-toggle-button
- pref="{{prefs.profile.block_third_party_cookies}}"
- label="$i18n{thirdPartyCookie}"
- sub-label="$i18n{thirdPartyCookieSublabel}">
- </settings-toggle-button>
- <cr-link-row id="site-data-trigger" class="hr"
- on-click="onSiteDataTap_" label="$i18n{siteSettingsCookieLink}">
- </cr-link-row>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.COOKIES}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/siteData" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCookieHeader}"
- search-label="$i18n{siteSettingsCookieSearch}"
- search-term="{{siteDataFilter_}}">
- <site-data filter="[[siteDataFilter_]]"
- focus-config="[[focusConfig_]]">
- </site-data>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/images" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryImages}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting category="{{ContentSettingsTypes.IMAGES}}"
- toggle-off-label="$i18n{siteSettingsDontShowImages}"
- toggle-on-label="$i18n{siteSettingsShowAllRecommended}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.IMAGES}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[enableInsecureContentContentSetting_]]">
- <template is="dom-if" route-path="/content/insecureContent" no-search>
- <settings-subpage
- page-title="$i18n{siteSettingsCategoryInsecureContent}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <div class="settings-box first">
- $i18n{siteSettingsInsecureContentBlock}
- </div>
- <category-setting-exceptions
- category="[[ContentSettingsTypes.MIXEDSCRIPT]]"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" route-path="/content/location" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryLocation}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsBlocked}"
- toggle-on-label="$i18n{siteSettingsAskBeforeAccessingRecommended}"
- category="{{ContentSettingsTypes.GEOLOCATION}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.GEOLOCATION}}" read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/handlers" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryHandlers}">
- <protocol-handlers
- toggle-off-label="$i18n{siteSettingsHandlersBlocked}"
- toggle-on-label="$i18n{siteSettingsHandlersAskRecommended}">
- </protocol-handlers>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/zoomLevels" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryZoomLevels}">
- <zoom-levels></zoom-levels>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/pdfDocuments" no-search>
- <settings-subpage page-title="$i18n{siteSettingsPdfDocuments}">
- <settings-pdf-documents prefs="{{prefs}}"></settings-pdf-documents>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/javascript" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryJavascript}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsBlocked}"
- toggle-on-label="$i18n{siteSettingsAllowedRecommended}"
- category="{{ContentSettingsTypes.JAVASCRIPT}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.JAVASCRIPT}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/sound" no-search>
- <settings-subpage page-title="$i18n{siteSettingsSound}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsSoundBlock}"
- toggle-on-label="$i18n{siteSettingsSoundAllowRecommended}"
- category="{{ContentSettingsTypes.SOUND}}">
- </category-default-setting>
- <settings-toggle-button
- id="block-autoplay-setting"
- label="$i18n{siteSettingsBlockAutoplaySetting}"
- pref="{{blockAutoplayStatus_.pref}}"
- disabled="[[!blockAutoplayStatus_.enabled]]"
- hidden="[[!enableBlockAutoplayContentSetting_]]"
- on-settings-boolean-control-change="onBlockAutoplayToggleChange_"
- no-set-pref>
- </settings-toggle-button>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.SOUND}}"
- block-header="$i18n{siteSettingsBlockSound}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/microphone" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryMicrophone}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <media-picker label="$i18n{siteSettingsMicrophoneLabel}" type="mic">
- </media-picker>
- <category-default-setting category="{{ContentSettingsTypes.MIC}}"
- toggle-off-label="$i18n{siteSettingsBlocked}"
- toggle-on-label=
- "$i18n{siteSettingsAskBeforeAccessingRecommended}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.MIC}}" read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/sensors" no-search>
- <settings-subpage page-title="$i18n{siteSettingsSensors}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsSensorsBlock}"
- toggle-on-label="$i18n{siteSettingsSensorsAllow}"
- category="{{ContentSettingsTypes.SENSORS}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.SENSORS}}" read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/notifications" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryNotifications}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsBlocked}"
- toggle-on-label="$i18n{siteSettingsAskBeforeSendingRecommended}"
- category="{{ContentSettingsTypes.NOTIFICATIONS}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.NOTIFICATIONS}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/flash" no-search>
- <settings-subpage page-title="$i18n{siteSettingsFlash}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <a class="settings-box first no-outline" tabindex="-1"
- target="_blank">
- $i18n{siteSettingsFlashPermissionsEphemeral}
- </a>
- <category-default-setting category="{{ContentSettingsTypes.PLUGINS}}"
- toggle-off-label="$i18n{siteSettingsFlashBlockRecommended}"
- toggle-on-label="$i18n{siteSettingsFlashAskFirst}">
- </category-default-setting>
-<if expr="chromeos">
- <a class="settings-box inherit-color no-outline" tabindex="-1"
- target="_blank"
- href="https://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager07.html">
- <div class="start">$i18n{adobeFlashStorage}</div>
- <cr-icon-button actionable class="icon-external"
- aria-label="$i18n{adobeFlashStorage}"></cr-icon-button>
- </a>
-</if>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.PLUGINS}}"
- block-header="$i18n{siteSettingsBlock}" read-only-list
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/popups" no-search>
- <settings-subpage page-title="$i18n{siteSettingsCategoryPopups}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting category="{{ContentSettingsTypes.POPUPS}}"
- toggle-off-label="$i18n{siteSettingsBlockedRecommended}"
- toggle-on-label="$i18n{siteSettingsAllowed}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.POPUPS}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[enableSafeBrowsingSubresourceFilter_]]"
- no-search>
- <template is="dom-if" route-path="/content/ads" no-search>
- <settings-subpage page-title="$i18n{siteSettingsAds}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- category="{{ContentSettingsTypes.ADS}}"
- toggle-off-label="$i18n{siteSettingsAdsBlockRecommended}"
- toggle-on-label="$i18n{siteSettingsAllowed}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.ADS}}"
- read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" route-path="/content/unsandboxedPlugins" no-search>
- <settings-subpage page-title="$i18n{siteSettingsUnsandboxedPlugins}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsUnsandboxedPluginsBlock}"
- toggle-on-label=
- "$i18n{siteSettingsUnsandboxedPluginsAskRecommended}"
- category="{{ContentSettingsTypes.UNSANDBOXED_PLUGINS}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.UNSANDBOXED_PLUGINS}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/midiDevices" no-search>
- <settings-subpage page-title="$i18n{siteSettingsMidiDevices}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsMidiDevicesBlock}"
- toggle-on-label=
- "$i18n{siteSettingsMidiDevicesAskRecommended}"
- category="{{ContentSettingsTypes.MIDI_DEVICES}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.MIDI_DEVICES}}" read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/usbDevices" no-search>
- <settings-subpage page-title="$i18n{siteSettingsUsbDevices}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsUsbDevicesBlock}"
- toggle-on-label=
- "$i18n{siteSettingsUsbDevicesAskRecommended}"
- category="{{ContentSettingsTypes.USB_DEVICES}}">
- </category-default-setting>
- <chooser-exception-list
- category="{{ContentSettingsTypes.USB_DEVICES}}"
- chooser-type="{{ChooserType.USB_DEVICES}}">
- </chooser-exception-list>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
- <template is="dom-if" route-path="/content/serialPorts" no-search>
- <settings-subpage page-title="$i18n{siteSettingsSerialPorts}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsSerialPortsBlock}"
- toggle-on-label=
- "$i18n{siteSettingsSerialPortsAskRecommended}"
- category="{{ContentSettingsTypes.SERIAL_PORTS}}">
- </category-default-setting>
- <chooser-exception-list
- category="{{ContentSettingsTypes.SERIAL_PORTS}}"
- chooser-type="{{ChooserType.SERIAL_PORTS}}">
- </chooser-exception-list>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" if="[[enableNativeFileSystemWriteContentSetting_]]">
- <template is="dom-if" route-path="/content/filesystem" no-search>
- <settings-subpage
- page-title="$i18n{siteSettingsNativeFileSystemWrite}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsNativeFileSystemWriteBlock}"
- toggle-on-label=
- "$i18n{siteSettingsNativeFileSystemWriteAskRecommended}"
- category="{{ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE}}"
- read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" route-path="/content/siteDetails" no-search>
- <settings-subpage page-title="[[pageTitle]]">
- <site-details
- page-title="{{pageTitle}}"
- block-autoplay-enabled="[[blockAutoplayStatus_.pref.value]]">
- </site-details>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/cookies/detail" no-search>
- <settings-subpage page-title="[[pageTitle]]">
- <cr-button slot="subpage-title-extra"
- on-click="onRemoveAllCookiesFromSite_">
- $i18n{siteSettingsCookieRemoveAll}
- </cr-button>
- <site-data-details-subpage page-title="{{pageTitle}}">
- </site-data-details-subpage>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/protectedContent" no-search>
-<if expr="not chromeos">
- <settings-subpage page-title="$i18n{siteSettingsProtectedContent}">
-</if>
-<if expr="chromeos">
- <settings-subpage page-title="$i18n{siteSettingsProtectedContent}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
-</if>
- <settings-toggle-button class="first"
- pref="{{prefs.webkit.webprefs.encrypted_media_enabled}}"
- label="[[getProtectedContentLabel_(
- prefs.webkit.webprefs.encrypted_media_enabled.value)]]"
- disabled$="[[isGuest_]]">
- </settings-toggle-button>
-<if expr="chromeos or is_win">
- <div class="settings-box first two-line">
- $i18n{siteSettingsProtectedContentIdentifiersExplanation}
- </div>
- <settings-toggle-button class="first"
- pref="{{prefs.settings.privacy.drm_enabled}}"
- label="[[getProtectedContentIdentifiersLabel_(
- prefs.settings.privacy.drm_enabled.value)]]"
- disabled$="[[isGuest_]]">
- </settings-toggle-button>
-</if>
-<if expr="chromeos">
- <template is="dom-if"
- if="[[prefs.settings.privacy.drm_enabled.value]]">
- <category-setting-exceptions
- category="{{ContentSettingsTypes.PROTECTED_CONTENT}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </template>
-</if>
- </settings-subpage>
- </template>
- <template is="dom-if" route-path="/content/clipboard" no-search>
- <settings-subpage page-title="$i18n{siteSettingsClipboard}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsClipboardBlock}"
- toggle-on-label="$i18n{siteSettingsClipboardAskRecommended}"
- category="{{ContentSettingsTypes.CLIPBOARD}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.CLIPBOARD}}"
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[enablePaymentHandlerContentSetting_]]">
- <template is="dom-if" route-path="/content/paymentHandler" no-search>
- <settings-subpage page-title="$i18n{siteSettingsPaymentHandler}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsPaymentHandlerBlock}"
- toggle-on-label="$i18n{siteSettingsPaymentHandlerAllowRecommended}"
- category="[[ContentSettingsTypes.PAYMENT_HANDLER]]">
- </category-default-setting>
- <category-setting-exceptions
- category="[[ContentSettingsTypes.PAYMENT_HANDLER]]"
- block-header="$i18n{siteSettingsBlocked}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- </template>
- <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
- <template is="dom-if" route-path="/content/bluetoothScanning" no-search>
- <settings-subpage page-title="$i18n{siteSettingsBluetoothScanning}"
- search-label="$i18n{siteSettingsAllSitesSearch}"
- search-term="{{searchFilter_}}">
- <category-default-setting
- toggle-off-label="$i18n{siteSettingsBluetoothScanningBlock}"
- toggle-on-label=
- "$i18n{siteSettingsBluetoothScanningAskRecommended}"
- category="{{ContentSettingsTypes.BLUETOOTH_SCANNING}}">
- </category-default-setting>
- <category-setting-exceptions
- category="{{ContentSettingsTypes.BLUETOOTH_SCANNING}}"
- read-only-list
- block-header="$i18n{siteSettingsBlock}"
- search-filter="[[searchFilter_]]">
- </category-setting-exceptions>
- </settings-subpage>
- </template>
- </template>
- </settings-animated-pages>
-
- <template is="dom-if" if="[[showSignoutDialog_]]" restamp>
- <settings-signout-dialog sync-status="[[syncStatus]]"
- on-close="onSignoutDialogClosed_">
- </settings-signout-dialog>
- </template>
-
-<if expr="not chromeos">
- <cr-toast id="toast" open="[[showRestart_]]">
- <div>$i18n{restartToApplyChanges}</div>
- <cr-button on-click="onRestartTap_">
- $i18n{restart}
- </cr-button>
- </cr-toast>
-</if>
- </template>
- <script src="privacy_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js
deleted file mode 100644
index a7b63fd525e..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js
+++ /dev/null
@@ -1,439 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{
- * enabled: boolean,
- * pref: !chrome.settingsPrivate.PrefObject
- * }}
- */
-let BlockAutoplayStatus;
-
-/**
- * @fileoverview
- * 'settings-privacy-page' is the settings page containing privacy and
- * security settings.
- */
-(function() {
-
-/**
- * Must be kept in sync with the C++ enum of the same name.
- * @enum {number}
- */
-const NetworkPredictionOptions = {
- ALWAYS: 0,
- WIFI_ONLY: 1,
- NEVER: 2,
- DEFAULT: 1,
-};
-
-Polymer({
- is: 'settings-privacy-page',
-
- behaviors: [
- settings.RouteObserverBehavior,
- I18nBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- /**
- * The current sync status, supplied by SyncBrowserProxy.
- * @type {?settings.SyncStatus}
- */
- syncStatus: Object,
-
- /**
- * Dictionary defining page visibility.
- * @type {!PrivacyPageVisibility}
- */
- pageVisibility: Object,
-
- /** @private */
- isGuest_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isGuest');
- }
- },
-
- /** @private */
- showClearBrowsingDataDialog_: Boolean,
-
- /** @private */
- showDoNotTrackDialog_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Used for HTML bindings. This is defined as a property rather than within
- * the ready callback, because the value needs to be available before
- * local DOM initialization - otherwise, the toggle has unexpected behavior.
- * @private
- */
- networkPredictionUncheckedValue_: {
- type: Number,
- value: NetworkPredictionOptions.NEVER,
- },
-
- /** @private */
- enableSafeBrowsingSubresourceFilter_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableSafeBrowsingSubresourceFilter');
- }
- },
-
- /** @private */
- enableBlockAutoplayContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableBlockAutoplayContentSetting');
- }
- },
-
- /** @private {BlockAutoplayStatus} */
- blockAutoplayStatus_: {
- type: Object,
- value: function() {
- return /** @type {BlockAutoplayStatus} */ ({});
- }
- },
-
- /** @private */
- enablePaymentHandlerContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enablePaymentHandlerContentSetting');
- }
- },
-
- /** @private */
- enableExperimentalWebPlatformFeatures_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures');
- },
- },
-
- /** @private */
- enableSecurityKeysSubpage_: {
- type: Boolean,
- readOnly: true,
- value: function() {
- return loadTimeData.getBoolean('enableSecurityKeysSubpage');
- }
- },
-
- /** @private */
- enableInsecureContentContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableInsecureContentContentSetting');
- }
- },
-
- /** @private */
- enableNativeFileSystemWriteContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean(
- 'enableNativeFileSystemWriteContentSetting');
- }
- },
-
- /** @private {!Map<string, string>} */
- focusConfig_: {
- type: Object,
- value: function() {
- const map = new Map();
- // <if expr="use_nss_certs">
- if (settings.routes.CERTIFICATES) {
- map.set(settings.routes.CERTIFICATES.path, '#manageCertificates');
- }
- // </if>
- if (settings.routes.SITE_SETTINGS) {
- map.set(
- settings.routes.SITE_SETTINGS.path,
- '#site-settings-subpage-trigger');
- }
-
- if (settings.routes.SITE_SETTINGS_SITE_DATA) {
- map.set(
- settings.routes.SITE_SETTINGS_SITE_DATA.path,
- '#site-data-trigger');
- }
-
- if (settings.routes.SECURITY_KEYS) {
- map.set(
- settings.routes.SECURITY_KEYS.path,
- '#security-keys-subpage-trigger');
- }
- return map;
- },
- },
-
- /**
- * This flag is used to conditionally show a set of sync UIs to the
- * profiles that have been migrated to have a unified consent flow.
- * TODO(tangltom): In the future when all profiles are completely migrated,
- * this should be removed, and UIs hidden behind it should become default.
- * @private
- */
- unifiedConsentEnabled_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('unifiedConsentEnabled');
- },
- },
-
- // <if expr="not chromeos">
- /** @private */
- showRestart_: Boolean,
- // </if>
-
- /** @private */
- showSignoutDialog_: Boolean,
-
- /** @private */
- searchFilter_: String,
- },
-
- /** @override */
- ready: function() {
- this.ContentSettingsTypes = settings.ContentSettingsTypes;
- this.ChooserType = settings.ChooserType;
-
- this.browserProxy_ = settings.PrivacyPageBrowserProxyImpl.getInstance();
-
- this.onBlockAutoplayStatusChanged_({
- pref: /** @type {chrome.settingsPrivate.PrefObject} */ ({value: false}),
- enabled: false
- });
-
- this.addWebUIListener(
- 'onBlockAutoplayStatusChanged',
- this.onBlockAutoplayStatusChanged_.bind(this));
-
- settings.SyncBrowserProxyImpl.getInstance().getSyncStatus().then(
- this.handleSyncStatus_.bind(this));
- this.addWebUIListener(
- 'sync-status-changed', this.handleSyncStatus_.bind(this));
- },
-
- /**
- * Handler for when the sync state is pushed from the browser.
- * @param {?settings.SyncStatus} syncStatus
- * @private
- */
- handleSyncStatus_: function(syncStatus) {
- this.syncStatus = syncStatus;
- },
-
- /** @protected */
- currentRouteChanged: function() {
- this.showClearBrowsingDataDialog_ =
- settings.getCurrentRoute() == settings.routes.CLEAR_BROWSER_DATA;
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onDoNotTrackDomChange_: function(event) {
- if (this.showDoNotTrackDialog_) {
- this.maybeShowDoNotTrackDialog_();
- }
- },
-
- /**
- * Called when the block autoplay status changes.
- * @param {BlockAutoplayStatus} autoplayStatus
- * @private
- */
- onBlockAutoplayStatusChanged_: function(autoplayStatus) {
- this.blockAutoplayStatus_ = autoplayStatus;
- },
-
- /**
- * Updates the block autoplay pref when the toggle is changed.
- * @param {!Event} event
- * @private
- */
- onBlockAutoplayToggleChange_: function(event) {
- const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
- this.browserProxy_.setBlockAutoplayEnabled(target.checked);
- },
-
- /**
- * Handles the change event for the do-not-track toggle. Shows a
- * confirmation dialog when enabling the setting.
- * @param {!Event} event
- * @private
- */
- onDoNotTrackChange_: function(event) {
- const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
- if (!target.checked) {
- // Always allow disabling the pref.
- target.sendPrefChange();
- return;
- }
- this.showDoNotTrackDialog_ = true;
- // If the dialog has already been stamped, show it. Otherwise it will be
- // shown in onDomChange_.
- this.maybeShowDoNotTrackDialog_();
- },
-
- /** @private */
- maybeShowDoNotTrackDialog_: function() {
- const dialog = this.$$('#confirmDoNotTrackDialog');
- if (dialog && !dialog.open) {
- dialog.showModal();
- }
- },
-
- /** @private */
- closeDoNotTrackDialog_: function() {
- this.$$('#confirmDoNotTrackDialog').close();
- this.showDoNotTrackDialog_ = false;
- },
-
- /** @private */
- onDoNotTrackDialogClosed_: function() {
- cr.ui.focusWithoutInk(this.$.doNotTrack);
- },
-
- /**
- * Handles the shared proxy confirmation dialog 'Confirm' button.
- * @private
- */
- onDoNotTrackDialogConfirm_: function() {
- /** @type {!SettingsToggleButtonElement} */ (this.$.doNotTrack)
- .sendPrefChange();
- this.closeDoNotTrackDialog_();
- },
-
- /**
- * Handles the shared proxy confirmation dialog 'Cancel' button or a cancel
- * event.
- * @private
- */
- onDoNotTrackDialogCancel_: function() {
- /** @type {!SettingsToggleButtonElement} */ (this.$.doNotTrack)
- .resetToPrefValue();
- this.closeDoNotTrackDialog_();
- },
-
- /** @private */
- onManageCertificatesTap_: function() {
- // <if expr="use_nss_certs">
- settings.navigateTo(settings.routes.CERTIFICATES);
- // </if>
- // <if expr="is_win or is_macosx">
- this.browserProxy_.showManageSSLCertificates();
- // </if>
- },
-
- /** @private */
- onSyncAndGoogleServicesClick_: function() {
- // Navigate to sync page, and remove (privacy related) search text to
- // avoid the sync page from being hidden.
- settings.navigateTo(settings.routes.SYNC, null, true);
- },
-
- /**
- * This is a workaround to connect the remove all button to the subpage.
- * @private
- */
- onRemoveAllCookiesFromSite_: function() {
- const node = /** @type {?SiteDataDetailsSubpageElement} */ (
- this.$$('site-data-details-subpage'));
- if (node) {
- node.removeAll();
- }
- },
-
- /** @private */
- onSiteDataTap_: function() {
- settings.navigateTo(settings.routes.SITE_SETTINGS_SITE_DATA);
- },
-
- /** @private */
- onSiteSettingsTap_: function() {
- settings.navigateTo(settings.routes.SITE_SETTINGS);
- },
-
- /** @private */
- onClearBrowsingDataTap_: function() {
- settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA);
- },
-
- /** @private */
- onDialogClosed_: function() {
- settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA.parent);
- cr.ui.focusWithoutInk(assert(this.$.clearBrowsingData));
- },
-
- /** @private */
- onSecurityKeysTap_: function() {
- settings.navigateTo(settings.routes.SECURITY_KEYS);
- },
-
- /** @private */
- getProtectedContentLabel_: function(value) {
- return value ? this.i18n('siteSettingsProtectedContentEnable') :
- this.i18n('siteSettingsBlocked');
- },
-
- /** @private */
- getProtectedContentIdentifiersLabel_: function(value) {
- return value ? this.i18n('siteSettingsProtectedContentEnableIdentifiers') :
- this.i18n('siteSettingsBlocked');
- },
-
- /** @private */
- onSigninAllowedChange_: function() {
- if (this.syncStatus.signedIn && !this.$.signinAllowedToggle.checked) {
- // Switch the toggle back on and show the signout dialog.
- this.$.signinAllowedToggle.checked = true;
- this.showSignoutDialog_ = true;
- } else {
- /** @type {!SettingsToggleButtonElement} */ (this.$.signinAllowedToggle)
- .sendPrefChange();
- this.showRestart_ = true;
- }
- },
-
- /** @private */
- onSignoutDialogClosed_: function() {
- if (/** @type {!SettingsSignoutDialogElement} */ (
- this.$$('settings-signout-dialog'))
- .wasConfirmed()) {
- this.$.signinAllowedToggle.checked = false;
- /** @type {!SettingsToggleButtonElement} */ (this.$.signinAllowedToggle)
- .sendPrefChange();
- this.showRestart_ = true;
- }
- this.showSignoutDialog_ = false;
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onRestartTap_: function(e) {
- e.stopPropagation();
- settings.LifetimeBrowserProxyImpl.getInstance().restart();
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.html b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.html
deleted file mode 100644
index f8934cedd26..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="privacy_page_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js
deleted file mode 100644
index 0cfa4c59e78..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Handles interprocess communication for the privacy page. */
-
-/** @typedef {{enabled: boolean, managed: boolean}} */
-let MetricsReporting;
-
-cr.define('settings', function() {
- /** @interface */
- class PrivacyPageBrowserProxy {
- // <if expr="_google_chrome and not chromeos">
- /** @return {!Promise<!MetricsReporting>} */
- getMetricsReporting() {}
-
- /** @param {boolean} enabled */
- setMetricsReportingEnabled(enabled) {}
-
- // </if>
-
- // <if expr="is_win or is_macosx">
- /** Invokes the native certificate manager (used by win and mac). */
- showManageSSLCertificates() {}
-
- // </if>
-
- /** @param {boolean} enabled */
- setBlockAutoplayEnabled(enabled) {}
- }
-
- /**
- * @implements {settings.PrivacyPageBrowserProxy}
- */
- class PrivacyPageBrowserProxyImpl {
- // <if expr="_google_chrome and not chromeos">
- /** @override */
- getMetricsReporting() {
- return cr.sendWithPromise('getMetricsReporting');
- }
-
- /** @override */
- setMetricsReportingEnabled(enabled) {
- chrome.send('setMetricsReportingEnabled', [enabled]);
- }
-
- // </if>
-
- /** @override */
- setBlockAutoplayEnabled(enabled) {
- chrome.send('setBlockAutoplayEnabled', [enabled]);
- }
-
- // <if expr="is_win or is_macosx">
- /** @override */
- showManageSSLCertificates() {
- chrome.send('showManageSSLCertificates');
- }
- // </if>
- }
-
- cr.addSingletonGetter(PrivacyPageBrowserProxyImpl);
-
- return {
- PrivacyPageBrowserProxy: PrivacyPageBrowserProxy,
- PrivacyPageBrowserProxyImpl: PrivacyPageBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html
deleted file mode 100644
index e1d92c209a0..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html
+++ /dev/null
@@ -1,152 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="security_keys_browser_proxy.html">
-<link rel="import" href="security_keys_pin_field.html">
-
-<dom-module id="settings-security-keys-bio-enroll-dialog">
- <template>
- <style include="settings-shared">
- #header {
- display: flex;
- }
-
- iron-icon {
- padding-inline-end: 12px;
- }
-
- .list-item .name {
- word-break: break-word;
- }
-
- .name {
- flex: 3;
- }
-
- .icon-placeholder {
- width: var(--cr-icon-ripple-size);
- }
-
- #outer {
- padding: 0 var(--cr-section-padding);
- }
- </style>
-
- <cr-dialog id="dialog" close-text="$i18n{cancel}" ignore-popstate
- on-close="onDialogClosed_">
- <div slot="title">$i18n{securityKeysBioEnrollmentDialogTitle}</div>
-
- <div slot="body">
- <iron-pages attr-for-selected="id" selected="[[dialogPage_]]"
- on-iron-select="onIronSelect_">
- <div id="initial">
- <p>$i18n{securityKeysBioEnrollmentTouch}</p>
- <paper-spinner-lite style="padding-bottom: 16px;" active>
- </paper-spinner-lite>
- </div>
-
- <div id="pinPrompt">
- <p>$i18n{securityKeysBioEnrollmentPinPrompt}</p>
- <settings-security-keys-pin-field
- id="pin" label="$i18n{securityKeysPIN}">
- </settings-security-keys-pin-field>
- </div>
-
- <div id="enrollments">
- <div class="settings-box first">
- <h2 class="start">$i18n{securityKeysBioEnrollmentLabel}</h2>
- <cr-button id="addButton" on-click="addButtonClick_"
- class="secondary-button header-aligned-button">
- $i18n{add}
- </cr-button>
- </div>
-
-
- <div id="outer">
- <div id="header" class="list-item column-header"
- hidden="[[!hasSome_(enrollments_)]]">
- <div class="name">
- $i18n{securityKeysBioEnrollmentNameLabel}
- </div>
- <div class="icon-placeholder"></div>
- </div>
-
- <div id="container" hidden="[[!hasSome_(enrollments_)]]">
- <iron-list id="enrollmentList" items="[[enrollments_]]"
- class="cr-separators">
- <template>
- <div class="list-item">
- <iron-icon icon="cr-fingerprint-icon:enrollment-done">
- </iron-icon>
- <div class="name" aria-label="[[item.name]]">
- [[item.name]]
- </div>
- <cr-icon-button class="icon-clear"
- aria-label="i18n{securityKeysBioEnrollmentDelete}"
- on-click="deleteEnrollment_"
- disabled="[[deleteInProgress_]]">
- </cr-icon-button>
- </div>
- </template>
- </iron-list>
- </div>
-
- <p hidden="[[hasSome_(enrollments_)]]">
- $i18n{securityKeysBioEnrollmentNoEnrollments}
- </p>
- </div>
- </div>
-
- <div id="enroll">
- <p>$i18n{securityKeysBioEnrollmentEnrollingLabel}</p>
- <cr-fingerprint-progress-arc id="arc"></cr-fingerprint-progress-arc>
- </div>
-
- <div id="chooseName">
- <p>$i18n{securityKeysBioEnrollmentChooseName}</p>
- <cr-input type="text" id="enrollmentName"
- value="{{recentEnrollmentName_}}"
- label="$i18n{securityKeysBioEnrollmentNameLabel}"
- on-input="onEnrollmentNameInput_"
- spellcheck="false">
- </cr-input>
- </div>
-
- <div id="error">[[errorMsg_]]</div>
- </iron-pages>
- </div>
-
- <div slot="button-container">
- <cr-button id="cancelButton" class="cancel-button" on-click="cancel_"
- hidden="[[!cancelButtonVisible_]]"
- disabled="[[cancelButtonDisabled_]]">
- $i18n{cancel}
- </cr-button>
- <cr-button id="confirmButton" class="action-button"
- hidden="[[!confirmButtonVisible_]]"
- disabled="[[confirmButtonDisabled_]]"
- on-click="confirmButtonClick_">
- $i18n{continue}
- </cr-button>
- <cr-button id="doneButton" class="action-button"
- on-click="done_" hidden="[[!doneButtonVisible_]]">
- $i18n{done}
- </cr-button>
- </div>
- </cr-dialog>
-
- </template>
- <script src="security_keys_bio_enroll_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js
deleted file mode 100644
index 43253425f32..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-/**
- * @fileoverview 'settings-security-keys-bio-enroll-dialog' is a dialog for
- * listing, adding, renaming, and deleting biometric enrollments stored on a
- * security key.
- */
-Polymer({
- is: 'settings-security-keys-bio-enroll-dialog',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /** @private */
- cancelButtonDisabled_: Boolean,
-
- /** @private */
- cancelButtonVisible_: Boolean,
-
- /** @private */
- confirmButtonDisabled_: Boolean,
-
- /** @private */
- confirmButtonVisible_: Boolean,
-
- /** @private */
- deleteInProgress_: Boolean,
-
- /**
- * The ID of the element currently shown in the dialog.
- * @private
- */
- dialogPage_: {
- type: String,
- value: 'initial',
- observer: 'dialogPageChanged_',
- },
-
- /** @private */
- doneButtonVisible_: Boolean,
-
- /**
- * The list of enrollments displayed.
- * @private {!Array<!Enrollment>}
- */
- enrollments_: Array,
-
- /** @private */
- recentEnrollmentName_: String,
- },
-
- /** @private {?settings.SecurityKeysBioEnrollProxyImpl} */
- browserProxy_: null,
-
- /** @private {number} */
- maxSamples_: -1,
-
- /** @private {string} */
- recentEnrollmentId_: '',
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- this.addWebUIListener(
- 'security-keys-bio-enroll-error', this.onError_.bind(this));
- this.addWebUIListener(
- 'security-keys-bio-enroll-status', this.onEnrolling_.bind(this));
- this.browserProxy_ = settings.SecurityKeysBioEnrollProxyImpl.getInstance();
- this.browserProxy_.startBioEnroll().then(() => {
- this.collectPIN_();
- });
- },
-
- /** @private */
- collectPIN_: function() {
- this.dialogPage_ = 'pinPrompt';
- this.$.pin.focus();
- },
-
- /**
- * @private
- * @param {string} error
- */
- onError_: function(error) {
- this.errorMsg_ = error;
- this.dialogPage_ = 'error';
- },
-
- /** @private */
- submitPIN_: function() {
- if (!this.$.pin.validate()) {
- this.confirmButtonDisabled_ = false;
- return;
- }
- this.browserProxy_.providePIN(this.$.pin.value).then(retries => {
- this.confirmButtonDisabled_ = false;
- if (retries != null) {
- this.$.pin.showIncorrectPINError(retries);
- return;
- }
- this.showEnrollmentsPage_();
- });
- },
-
- /**
- * @private
- * @param {!Array<!Enrollment>} enrollments
- */
- onEnrollments_: function(enrollments) {
- this.enrollments_ = enrollments;
- this.$.enrollmentList.fire('iron-resize');
- this.dialogPage_ = 'enrollments';
- },
-
- /** @private */
- dialogPageChanged_: function() {
- switch (this.dialogPage_) {
- case 'initial':
- this.cancelButtonVisible_ = true;
- this.cancelButtonDisabled = false;
- this.confirmButtonVisible_ = false;
- this.doneButtonVisible_ = false;
- break;
- case 'pinPrompt':
- this.cancelButtonVisible_ = true;
- this.cancelButtonDisabled = false;
- this.confirmButtonVisible_ = true;
- this.confirmButtonDisabled_ = false;
- this.doneButtonVisible_ = false;
- break;
- case 'enrollments':
- this.cancelButtonVisible_ = false;
- this.confirmButtonVisible_ = false;
- this.doneButtonVisible_ = true;
- break;
- case 'enroll':
- this.cancelButtonVisible_ = true;
- this.cancelButtonDisabled = false;
- this.confirmButtonVisible_ = false;
- this.doneButtonVisible_ = false;
- break;
- case 'chooseName':
- this.cancelButtonVisible_ = false;
- this.confirmButtonVisible_ = true;
- this.confirmButtonDisabled_ = !this.recentEnrollmentName_.length;
- this.doneButtonVisible_ = false;
- this.$.enrollmentName.focus();
- break;
- case 'error':
- this.cancelButtonVisible_ = false;
- this.confirmButtonVisible_ = false;
- this.doneButtonVisible_ = true;
- break;
- default:
- assertNotReached();
- }
- this.fire('bio-enroll-dialog-ready-for-testing');
- },
-
- /** @private */
- addButtonClick_: function() {
- assert(this.dialogPage_ == 'enrollments');
-
- this.maxSamples_ = -1; // Reset maxSamples_ before enrolling starts.
- this.$.arc.reset();
-
- this.recentEnrollmentId_ = '';
- this.recentEnrollmentName_ = '';
-
- this.dialogPage_ = 'enroll';
-
- this.browserProxy_.startEnrolling().then(response => {
- this.onEnrolling_(response);
- });
- },
-
- /**
- * @private
- * @param {!EnrollmentStatus} response
- */
- onEnrolling_: function(response) {
- if (response.code == Ctap2Status.ERR_KEEPALIVE_CANCEL) {
- this.showEnrollmentsPage_();
- return;
- }
-
- if (this.maxSamples_ == -1 && response.status != null) {
- if (response.status == 0) {
- // If the first sample is valid, remaining is one less than max samples
- // required.
- this.maxSamples_ = response.remaining + 1;
- } else {
- // If the first sample failed for any reason (timed out, key full, etc),
- // the remaining number of samples is the max samples required.
- this.maxSamples_ = response.remaining;
- }
- }
- // If 0 samples remain, the enrollment has finished in some state.
- // Currently not checking response['code'] for an error.
- this.$.arc.setProgress(
- 100 - (100 * (response.remaining + 1) / this.maxSamples_),
- 100 - (100 * response.remaining / this.maxSamples_),
- response.remaining == 0);
- if (response.remaining == 0) {
- assert(response.enrollment);
- this.recentEnrollmentId_ = response.enrollment.id;
- this.recentEnrollmentName_ = response.enrollment.name;
- this.cancelButtonVisible_ = false;
- this.confirmButtonVisible_ = true;
- this.confirmButtonDisabled_ = false;
- this.$.confirmButton.focus();
- }
- this.fire('bio-enroll-dialog-ready-for-testing');
- },
-
- /** @private */
- confirmButtonClick_: function() {
- // Disable |confirmButton| while PIN verification or template enumeration is
- // pending. Resetting |dialogPage_| will re-enable it.
- this.confirmButtonDisabled_ = true;
- switch (this.dialogPage_) {
- case 'pinPrompt':
- this.submitPIN_();
- break;
- case 'enroll':
- assert(!!this.recentEnrollmentId_.length);
- this.dialogPage_ = 'chooseName';
- break;
- case 'chooseName':
- this.browserProxy_
- .renameEnrollment(
- this.recentEnrollmentId_, this.recentEnrollmentName_)
- .then(enrollments => {
- this.onEnrollments_(enrollments);
- });
- break;
- default:
- assertNotReached();
- }
- },
-
- /** @private */
- showEnrollmentsPage_: function() {
- this.browserProxy_.enumerateEnrollments().then(enrollments => {
- this.onEnrollments_(enrollments);
- });
- },
-
- /** @private */
- cancel_: function() {
- if (this.dialogPage_ == 'enroll') {
- // Cancel an ongoing enrollment. Will cause the pending
- // enumerateEnrollments() promise to be resolved and proceed to the
- // enrollments page.
- this.cancelButtonDisabled_ = true;
- this.browserProxy_.cancelEnrollment();
- } else {
- // On any other screen, simply close the dialog.
- this.done_();
- }
- },
-
- /** @private */
- done_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onDialogClosed_: function() {
- this.browserProxy_.close();
- },
-
- /**
- * @private
- * @param {!Event} e
- */
- onIronSelect_: function(e) {
- // Prevent this event from bubbling since it is unnecessarily triggering the
- // listener within settings-animated-pages.
- e.stopPropagation();
- },
-
- /**
- * @private
- * @param {?Array} list
- * @return {boolean} true if the list exists and has items.
- */
- hasSome_: function(list) {
- return !!(list && list.length);
- },
-
- /**
- * @private
- * @param {!DomRepeatEvent} event
- */
- deleteEnrollment_: function(event) {
- if (this.deleteInProgress_) {
- return;
- }
- this.deleteInProgress_ = true;
- const enrollment = this.enrollments_[event.model.index];
- this.browserProxy_.deleteEnrollment(enrollment.id).then(enrollments => {
- this.deleteInProgress_ = false;
- this.onEnrollments_(enrollments);
- });
- },
-
- /** @private */
- onEnrollmentNameInput_: function() {
- this.confirmButtonDisabled_ = !this.recentEnrollmentName_.length;
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html
deleted file mode 100644
index a4a3f9871df..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="security_keys_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js
deleted file mode 100644
index ba01514b632..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js
+++ /dev/null
@@ -1,363 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * Ctap2Status contains a subset of CTAP2 status codes. See
- * device::CtapDeviceResponseCode for the full list.
- * @enum{number}
- */
-const Ctap2Status = {
- OK: 0x0,
- ERR_KEEPALIVE_CANCEL: 0x2D,
-};
-
-/**
- * Credential represents a CTAP2 resident credential enumerated from a security
- * key.
- *
- * id: (required) The hex encoding of the CBOR-serialized
- * PublicKeyCredentialDescriptor of the credential.
- *
- * relyingPartyId: (required) The RP ID (i.e. the site that created the
- * credential; eTLD+n)
- *
- * userName: (required) The PublicKeyCredentialUserEntity.name
- *
- * userDisplayName: (required) The PublicKeyCredentialUserEntity.display_name
- *
- * @typedef {{id: string,
- * relyingPartyId: string,
- * userName: string,
- * userDisplayName: string}}
- * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
- */
-let Credential;
-
-/**
- * EnrollmentStatus represents the current status of an enrollment suboperation,
- * where 'remaining' is the number of samples left, 'status' is the last
- * enrollment status, 'code' indicates the final CtapDeviceResponseCode of the
- * operation, and 'enrollment' contains the new Enrollment.
- *
- * For each enrollment sample, 'status' is set - when the enrollment operation
- * reaches an end state, 'code' and, if successful, 'enrollment' are set. |OK|
- * indicates successful enrollment. A code of |ERR_KEEPALIVE_CANCEL| indicates
- * user-initated cancellation.
- *
- * @typedef {{status: ?number,
- * code: ?Ctap2Status,
- * remaining: number,
- * enrollment: ?Enrollment}}
- * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
- */
-let EnrollmentStatus;
-
-/**
- * Enrollment represents a valid fingerprint template stored on a security key,
- * which can be used in a user verification request.
- *
- * @typedef {{name: string,
- * id: string}}
- * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
- */
-let Enrollment;
-
-cr.define('settings', function() {
- /** @interface */
- class SecurityKeysPINBrowserProxy {
- /**
- * Starts a PIN set/change operation by flashing all security keys. Resolves
- * with a pair of numbers. The first is one if the process has immediately
- * completed (i.e. failed). In this case the second is a CTAP error code.
- * Otherwise the process is ongoing and must be completed by calling
- * |setPIN|. In this case the second number is either the number of tries
- * remaining to correctly specify the current PIN, or else null to indicate
- * that no PIN is currently set.
- * @return {!Promise<!Array<number>>}
- */
- startSetPIN() {}
-
- /**
- * Attempts a PIN set/change operation. Resolves with a pair of numbers
- * whose meaning is the same as with |startSetPIN|. The first number will
- * always be 1 to indicate that the process has completed and thus the
- * second will be the CTAP error code.
- * @return {!Promise<!Array<number>>}
- */
- setPIN(oldPIN, newPIN) {}
-
- /** Cancels all outstanding operations. */
- close() {}
- }
-
- /** @interface */
- class SecurityKeysCredentialBrowserProxy {
- /**
- * Starts a credential management operation.
- *
- * Callers must listen to errors that can occur during the operation via a
- * 'security-keys-credential-management-error' WebListener. Values received
- * via this listener are localized error strings. When the
- * WebListener fires, the operation must be considered terminated.
- *
- * @return {!Promise} a promise that resolves when the handler is ready for
- * the authenticator PIN to be provided.
- */
- startCredentialManagement() {}
-
- /**
- * Provides a PIN for a credential management operation. The
- * startCredentialManagement() promise must have resolved before this method
- * may be called.
- * @return {!Promise<?number>} a promise that resolves with null if the PIN
- * was correct or the number of retries remaining otherwise.
- */
- providePIN(pin) {}
-
- /**
- * Enumerates credentials on the authenticator. A correct PIN must have
- * previously been supplied via providePIN() before this
- * method may be called.
- * @return {!Promise<!Array<!Credential>>}
- */
- enumerateCredentials() {}
-
- /**
- * Deletes the credentials with the given IDs from the security key.
- * @param {!Array<string>} ids
- * @return {!Promise<string>} a localized response message to display to
- * the user (on either success or error)
- */
- deleteCredentials(ids) {}
-
- /** Cancels all outstanding operations. */
- close() {}
- }
-
- /** @interface */
- class SecurityKeysResetBrowserProxy {
- /**
- * Starts a reset operation by flashing all security keys and sending a
- * reset command to the one that the user activates. Resolves with a CTAP
- * error code.
- * @return {!Promise<number>}
- */
- reset() {}
-
- /**
- * Waits for a reset operation to complete. Resolves with a CTAP error code.
- * @return {!Promise<number>}
- */
- completeReset() {}
-
- /** Cancels all outstanding operations. */
- close() {}
- }
-
- /** @interface */
- class SecurityKeysBioEnrollProxy {
- /**
- * Starts a biometric enrollment operation.
- *
- * Callers must listen to errors that can occur during this operation via a
- * 'security-keys-bio-enrollment-error' WebUIListener. Values received via
- * this listener are localized error strings. The WebListener may fire at
- * any point during the operation (enrolling, deleting, etc) and when it
- * fires, the operation must be considered terminated.
- *
- * @return {!Promise} resolves when the handler is ready for the
- * authentcation PIN to be provided.
- */
- startBioEnroll() {}
-
- /**
- * Provides a PIN for a biometric enrollment operation. The startBioEnroll()
- * Promise must have resolved before this method may be called.
- *
- * @return {!Promise<?number>} resolves with null if the PIN was correct,
- * the number of retries remaining otherwise.
- */
- providePIN(pin) {}
-
- /**
- * Enumerates enrollments on the authenticator. A correct PIN must have
- * previously been supplied via bioEnrollProvidePIN() before this method may
- * be called.
- *
- * @return {!Promise<!Array<!Enrollment>>}
- */
- enumerateEnrollments() {}
-
- /**
- * Move the operation into enrolling mode, which instructs the authenticator
- * to start sampling for touches.
- *
- * Callers must listen to status updates that will occur during this
- * suboperation via a 'security-keys-bio-enroll-status' WebListener. Values
- * received via this listener are DictionaryValues with two elements (see
- * below). When the WebListener fires, the authenticator has either timed
- * out waiting for a touch, or has successfully processed a touch. Any
- * errors will fire the 'security-keys-bio-enrollment-error' WebListener.
- *
- * @return {!Promise<!EnrollmentStatus>} resolves when the enrollment
- * operation is finished successfully.
- */
- startEnrolling() {}
-
- /**
- * Cancel an ongoing enrollment suboperation. This can safely be called at
- * any time and only has an impact when the authenticator is currently
- * sampling.
- */
- cancelEnrollment() {}
-
- /**
- * Deletes the enrollment with the given ID.
- *
- * @param {string} id
- * @return {!Promise<!Array<!Enrollment>>} The remaining enrollments.
- */
- deleteEnrollment(id) {}
-
- /**
- * Renames the enrollment with the given ID.
- *
- * @param {string} id
- * @param {string} name
- * @return {!Promise<!Array<!Enrollment>>} The updated list of enrollments.
- */
- renameEnrollment(id, name) {}
-
- /** Cancels all outstanding operations. */
- close() {}
- }
-
- /** @implements {settings.SecurityKeysPINBrowserProxy} */
- class SecurityKeysPINBrowserProxyImpl {
- /** @override */
- startSetPIN() {
- return cr.sendWithPromise('securityKeyStartSetPIN');
- }
-
- /** @override */
- setPIN(oldPIN, newPIN) {
- return cr.sendWithPromise('securityKeySetPIN', oldPIN, newPIN);
- }
-
- /** @override */
- close() {
- return chrome.send('securityKeyPINClose');
- }
- }
-
- /** @implements {settings.SecurityKeysCredentialBrowserProxy} */
- class SecurityKeysCredentialBrowserProxyImpl {
- /** @override */
- startCredentialManagement() {
- return cr.sendWithPromise('securityKeyCredentialManagementStart');
- }
-
- /** @override */
- providePIN(pin) {
- return cr.sendWithPromise('securityKeyCredentialManagementPIN', pin);
- }
-
- /** @override */
- enumerateCredentials() {
- return cr.sendWithPromise('securityKeyCredentialManagementEnumerate');
- }
-
- /** @override */
- deleteCredentials(ids) {
- return cr.sendWithPromise('securityKeyCredentialManagementDelete', ids);
- }
-
- /** @override */
- close() {
- return chrome.send('securityKeyCredentialManagementClose');
- }
- }
-
- /** @implements {settings.SecurityKeysResetBrowserProxy} */
- class SecurityKeysResetBrowserProxyImpl {
- /** @override */
- reset() {
- return cr.sendWithPromise('securityKeyReset');
- }
-
- /** @override */
- completeReset() {
- return cr.sendWithPromise('securityKeyCompleteReset');
- }
-
- /** @override */
- close() {
- return chrome.send('securityKeyResetClose');
- }
- }
-
- /** @implements {settings.SecurityKeysBioEnrollProxy} */
- class SecurityKeysBioEnrollProxyImpl {
- /** @override */
- startBioEnroll() {
- return cr.sendWithPromise('securityKeyBioEnrollStart');
- }
-
- /** @override */
- providePIN(pin) {
- return cr.sendWithPromise('securityKeyBioEnrollProvidePIN', pin);
- }
-
- /** @override */
- enumerateEnrollments() {
- return cr.sendWithPromise('securityKeyBioEnrollEnumerate');
- }
-
- /** @override */
- startEnrolling() {
- return cr.sendWithPromise('securityKeyBioEnrollStartEnrolling');
- }
-
- /** @override */
- cancelEnrollment() {
- return chrome.send('securityKeyBioEnrollCancel');
- }
-
- /** @override */
- deleteEnrollment(id) {
- return cr.sendWithPromise('securityKeyBioEnrollDelete', id);
- }
-
- /** @override */
- renameEnrollment(id, name) {
- return cr.sendWithPromise('securityKeyBioEnrollRename', id, name);
- }
-
- /** @override */
- close() {
- return chrome.send('securityKeyBioEnrollClose');
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(SecurityKeysPINBrowserProxyImpl);
- cr.addSingletonGetter(SecurityKeysCredentialBrowserProxyImpl);
- cr.addSingletonGetter(SecurityKeysResetBrowserProxyImpl);
- cr.addSingletonGetter(SecurityKeysBioEnrollProxyImpl);
-
- return {
- SecurityKeysPINBrowserProxy: SecurityKeysPINBrowserProxy,
- SecurityKeysPINBrowserProxyImpl: SecurityKeysPINBrowserProxyImpl,
- SecurityKeysCredentialBrowserProxy: SecurityKeysCredentialBrowserProxy,
- SecurityKeysCredentialBrowserProxyImpl:
- SecurityKeysCredentialBrowserProxyImpl,
- SecurityKeysResetBrowserProxy: SecurityKeysResetBrowserProxy,
- SecurityKeysResetBrowserProxyImpl: SecurityKeysResetBrowserProxyImpl,
- SecurityKeysBioEnrollProxy: SecurityKeysBioEnrollProxy,
- SecurityKeysBioEnrollProxyImpl: SecurityKeysBioEnrollProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.html
deleted file mode 100644
index 070ed2d6abb..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.html
+++ /dev/null
@@ -1,130 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="security_keys_browser_proxy.html">
-<link rel="import" href="security_keys_pin_field.html">
-
-<dom-module id="settings-security-keys-credential-management-dialog">
- <template>
- <style include="settings-shared">
- paper-spinner-lite {
- padding-bottom: 12px;
- }
-
- #header {
- display: flex;
- }
-
- .site {
- flex: 3;
- }
-
- .user {
- flex: 2;
- }
-
- .list-item .site {
- align-items: center;
- display: flex;
- }
-
- .list-item .site,
- .list-item .user {
- word-break: break-word;
- }
-
- site-favicon {
- margin-inline-end: 8px;
- min-width: 16px;
- }
-
- .checkbox-placeholder {
- width: var(--cr-icon-ripple-size);
- }
- </style>
-
- <cr-dialog id="dialog" close-text="$i18n{cancel}" ignore-popstate
- on-close="onDialogClosed_">
- <div slot="title">$i18n{securityKeysCredentialManagementDialogTitle}</div>
-
- <div slot="body">
- <iron-pages attr-for-selected="id" selected="[[dialogPage_]]"
- on-iron-select="onIronSelect_">
- <div id="initial">
- <p>$i18n{securityKeysCredentialManagementTouch}</p>
- <paper-spinner-lite active></paper-spinner-lite>
- </div>
-
- <div id="pinPrompt">
- <p>$i18nRaw{securityKeysCredentialManagementPinPrompt}</p>
- <settings-security-keys-pin-field
- id="pin" label="$i18n{securityKeysPIN}">
- </settings-security-keys-pin-field>
- </div>
-
- <div id="credentials">
- <div id="header" class="list-item column-header">
- <div class="site">$i18n{securityKeysCredentialWebsite}</div>
- <div class="user">$i18n{securityKeysCredentialUsername}</div>
- <div class="checkbox-placeholder"></div>
- </div>
-
- <div id="container">
- <iron-list id="credentialList" items="[[credentials_]]"
- class="cr-separators list-with-header">
- <template>
- <div class="list-item">
- <div class="site" aria-label="[[item.relyingPartyId]]">
- <site-favicon url="[[item.relyingPartyId]]">
- </site-favicon>
- <div>[[item.relyingPartyId]]</div>
- </div>
- <div class="user">[[formatUser_(item)]]</div>
- <cr-checkbox on-change="checkedCredentialsChanged_"
- data-id$="[[item.id]]"
- checked="[[credentialIsChecked_(item.id)]]"
- disabled="[[deleteInProgress_]]"></cr-checkbox>
- </div>
- </template>
- </iron-list>
- </div>
- </div>
-
- <div id="error">[[errorMsg_]]</div>
- </iron-pages>
- </div>
-
- <div slot="button-container">
- <cr-button id="cancelButton" class="cancel-button"
- on-click="close_" hidden="[[!cancelButtonVisible_]]">
- $i18n{cancel}
- </cr-button>
- <cr-button id="confirmButton" class="action-button"
- on-click="confirmButtonClick_"
- disabled="[[confirmButtonDisabled_]]"
- hidden="[[!confirmButtonVisible_]]">
- [[confirmButtonLabel_]]
- </cr-button>
- <cr-button id="closeButton" class="action-button"
- on-click="close_"
- hidden="[[!closeButtonVisible_]]">
- $i18n{close}
- </cr-button>
- </div>
- </cr-dialog>
-
- </template>
- <script src="security_keys_credential_management_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js
deleted file mode 100644
index 062c329e254..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-/**
- * @fileoverview 'settings-security-keys-credential-management-dialog' is a
- * dialog for viewing and erasing credentials stored on a security key.
- */
-Polymer({
- is: 'settings-security-keys-credential-management-dialog',
-
- behaviors: [
- I18nBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * The ID of the element currently shown in the dialog.
- * @private
- */
- dialogPage_: {
- type: String,
- value: 'initial',
- observer: 'dialogPageChanged_',
- },
-
- /**
- * The list of credentials displayed in the dialog.
- * @private {!Array<!Credential>}
- */
- credentials_: Array,
-
- /**
- * The message displayed on the "error" dialog page.
- * @private
- */
- errorMsg_: String,
-
- /** @private */
- cancelButtonVisible_: Boolean,
-
- /** @private */
- confirmButtonVisible_: Boolean,
-
- /** @private */
- confirmButtonDisabled_: Boolean,
-
- /** @private */
- confirmButtonLabel_: String,
-
- /** @private */
- closeButtonVisible_: Boolean,
-
- /** @private */
- deleteInProgress_: Boolean,
- },
-
- /** @private {?settings.SecurityKeysCredentialBrowserProxy} */
- browserProxy_: null,
-
- /** @private {?Set<string>} */
- checkedCredentialIds_: null,
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- this.addWebUIListener(
- 'security-keys-credential-management-finished',
- this.onError_.bind(this));
- this.checkedCredentialIds_ = new Set();
- this.browserProxy_ =
- settings.SecurityKeysCredentialBrowserProxyImpl.getInstance();
- this.browserProxy_.startCredentialManagement().then(
- this.collectPin_.bind(this));
- },
-
- /** @private */
- collectPin_: function() {
- this.dialogPage_ = 'pinPrompt';
- this.$.pin.focus();
- },
-
- /**
- * @private
- * @param {string} error
- */
- onError_: function(error) {
- this.errorMsg_ = error;
- this.dialogPage_ = 'error';
- },
-
- /** @private */
- submitPIN_: function() {
- if (!this.$.pin.validate()) {
- return;
- }
- this.confirmButtonDisabled_ = true;
- this.browserProxy_.providePIN(this.$.pin.value).then((retries) => {
- this.confirmButtonDisabled_ = false;
- if (retries != null) {
- this.$.pin.showIncorrectPINError(retries);
- this.collectPin_();
- return;
- }
- this.browserProxy_.enumerateCredentials().then(
- this.onCredentials_.bind(this));
- });
- },
-
- /**
- * @param {number} retries
- * @return {string} localized error string for an invalid PIN attempt and a
- * given number of remaining retries.
- */
- pinRetriesError_: function(retries) {
- // Warn the user if the number of retries is getting low.
- if (1 < retries && retries <= 3) {
- return this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString());
- }
- return this.i18n(
- retries == 1 ? 'securityKeysPINIncorrectRetriesSin' :
- 'securityKeysPINIncorrect');
- },
-
- /**
- * @private
- * @param {!Array<!Credential>} credentials
- */
- onCredentials_: function(credentials) {
- if (!credentials.length) {
- this.onError_(this.i18n('securityKeysCredentialManagementNoCredentials'));
- return;
- }
- this.credentials_ = credentials;
- this.$.credentialList.fire('iron-resize');
- this.dialogPage_ = 'credentials';
- },
-
- /** @private */
- dialogPageChanged_: function() {
- switch (this.dialogPage_) {
- case 'initial':
- this.cancelButtonVisible_ = true;
- this.confirmButtonVisible_ = false;
- this.closeButtonVisible_ = false;
- break;
- case 'pinPrompt':
- this.cancelButtonVisible_ = true;
- this.confirmButtonLabel_ = this.i18n('continue');
- this.confirmButtonDisabled_ = false;
- this.confirmButtonVisible_ = true;
- this.closeButtonVisible_ = false;
- break;
- case 'credentials':
- this.cancelButtonVisible_ = true;
- this.confirmButtonLabel_ =
- this.i18n('securityKeysCredentialManagementErase');
- this.confirmButtonDisabled_ = true;
- this.confirmButtonVisible_ = true;
- this.closeButtonVisible_ = false;
- break;
- case 'error':
- this.cancelButtonVisible_ = false;
- this.confirmButtonVisible_ = false;
- this.closeButtonVisible_ = true;
- break;
- default:
- assertNotReached();
- }
- this.fire('credential-management-dialog-ready-for-testing');
- },
-
- /** @private */
- confirmButtonClick_: function() {
- switch (this.dialogPage_) {
- case 'pinPrompt':
- this.submitPIN_();
- break;
- case 'credentials':
- this.deleteSelectedCredentials_();
- break;
- default:
- assertNotReached();
- }
- },
-
- /** @private */
- close_: function() {
- this.$.dialog.close();
- },
-
- /**
- * Stringifies the user entity of a Credential for display in the dialog.
- * @private
- * @param {!Credential} credential
- * @return {string}
- */
- formatUser_: function(credential) {
- if (this.isEmpty_(credential.userDisplayName)) {
- return credential.userName;
- }
- return `${credential.userDisplayName} (${credential.userName})`;
- },
-
- /** @private */
- onDialogClosed_: function() {
- this.browserProxy_.close();
- },
-
- /**
- * @private
- * @param {?string} str
- * @return {boolean} Whether this credential has been selected for removal.
- */
- isEmpty_: function(str) {
- return !str || str.length == 0;
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onIronSelect_: function(e) {
- // Prevent this event from bubbling since it is unnecessarily triggering the
- // listener within settings-animated-pages.
- e.stopPropagation();
- },
-
- /**
- * Handler for checking or unchecking a credential.
- * @param {!Event} e
- * @private
- */
- checkedCredentialsChanged_: function(e) {
- const credentialId = e.target.dataset.id;
- if (e.target.checked) {
- this.checkedCredentialIds_.add(credentialId);
- } else {
- this.checkedCredentialIds_.delete(credentialId);
- }
- this.confirmButtonDisabled_ = this.checkedCredentialIds_.size == 0;
- },
-
- /**
- * @private
- * @param {string} credentialId
- * @return {boolean} true if the checkbox for |credentialId| is checked
- */
- credentialIsChecked_: function(credentialId) {
- return this.checkedCredentialIds_.has(credentialId);
- },
-
- /** @private */
- deleteSelectedCredentials_: function() {
- assert(this.dialogPage_ == 'credentials');
- assert(this.credentials_ && this.credentials_.length > 0);
- assert(this.checkedCredentialIds_.size > 0);
-
- this.confirmButtonDisabled_ = true;
- this.deleteInProgress_ = true;
- this.browserProxy_.deleteCredentials(Array.from(this.checkedCredentialIds_))
- .then((err) => {
- this.confirmButtonDisabled_ = false;
- this.deleteInProgress_ = false;
- this.onError_(err);
- });
- },
-
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.html
deleted file mode 100644
index f185cba1363..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-security-keys-pin-field">
- <template>
- <style include="settings-shared">
- cr-input {
- display: inline-block;
- padding-inline-end: 2em;
- --cr-input-width: 8em;
- }
- </style>
-
- <cr-input id="pin" value="{{value}}" minLength="4"
- maxLength="255" spellcheck="false"
- on-input="onPINInput_"
- invalid="[[isNonEmpty_(error)]]"
- label="[[label]]" tabindex="0"
- type$="[[inputType_(inputVisible_)]]"
- error-message="[[error]]">
- <cr-icon-button slot="suffix" id="showButton"
- class$="[[showButtonClass_(inputVisible_)]]"
- title="[[showButtonTitle_(inputVisible_)]]"
- focus-row-control focus-type="showPassword"
- on-click="showButtonClick_"></cr-icon-button>
- </cr-input>
- </template>
- <script src="security_keys_pin_field.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.js
deleted file mode 100644
index 72159d830d7..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.js
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-security-keys-pin-field' is a component for entering
- * a security key PIN.
- */
-Polymer({
- is: 'settings-security-keys-pin-field',
-
- behaviors: [
- I18nBehavior,
- ],
-
- properties: {
- /** The label of the input field. */
- label: String,
-
- /** The validation error label of the input field. */
- error: {
- type: String,
- observer: 'errorChanged_',
- },
-
- /** The value of the input field. */
- value: String,
-
- /**
- * Whether the contents of the input field are visible.
- * @private
- */
- inputVisible_: Boolean,
- },
-
- /** @override */
- attached: function() {
- Polymer.RenderStatus.afterNextRender(this, function() {
- Polymer.IronA11yAnnouncer.requestAvailability();
- });
-
- this.inputVisible_ = false;
- },
-
- /** Focuses the PIN input field. */
- focus: function() {
- this.$.pin.focus();
- },
-
- /**
- * Validates the PIN and sets the validation error if it is not valid.
- * @return {boolean} True iff the PIN is valid.
- */
- validate: function() {
- const error = this.isValidPIN_(this.value);
- if (error != '') {
- this.error = error;
- return false;
- }
- return true;
- },
-
- /**
- * Sets the validation error to indicate the PIN was incorrect.
- * @param {number} retries The number of retries remaining.
- */
- showIncorrectPINError: function(retries) {
- // Warn the user if the number of retries is getting low.
- let error;
- if (1 < retries && retries <= 3) {
- error =
- this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString());
- } else if (retries == 1) {
- error = this.i18n('securityKeysPINIncorrectRetriesSin');
- } else {
- error = this.i18n('securityKeysPINIncorrect');
- }
- this.error = error;
- },
-
- /** @private */
- onPINInput_: function() {
- // Typing in the PIN box after an error makes the error message
- // disappear.
- this.error = '';
- },
-
- /**
- * Polymer helper function to detect when an error string is empty.
- * @param {string} s Arbitrary string
- * @return {boolean} True iff |s| is non-empty.
- * @private
- */
- isNonEmpty_: function(s) {
- return s != '';
- },
-
- /**
- * @return {string} The PIN-input element type.
- * @private
- */
- inputType_: function() {
- return this.inputVisible_ ? 'text' : 'password';
- },
-
- /**
- * @return {string} The class (and thus icon) to be displayed.
- * @private
- */
- showButtonClass_: function() {
- return 'icon-visibility' + (this.inputVisible_ ? '-off' : '');
- },
-
- /**
- * @return {string} The tooltip for the icon.
- * @private
- */
- showButtonTitle_: function() {
- return this.i18n(
- this.inputVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs');
- },
-
- /**
- * onClick handler for the show/hide icon.
- * @private
- */
- showButtonClick_: function() {
- this.inputVisible_ = !this.inputVisible_;
- },
-
- /**
- * @param {string} pin A candidate PIN.
- * @return {string} An error string or else '' to indicate validity.
- * @private
- */
- isValidPIN_: function(pin) {
- // The UTF-8 encoding of the PIN must be between 4
- // and 63 bytes, and the final byte cannot be zero.
- const utf8Encoded = new TextEncoder().encode(pin);
- if (utf8Encoded.length < 4) {
- return this.i18n('securityKeysPINTooShort');
- }
- if (utf8Encoded.length > 63 ||
- // If the PIN somehow has a NUL at the end then it's invalid, but this
- // is so obscure that we don't try to message it. Rather we just say
- // that it's too long because trimming the final character is the best
- // response by the user.
- utf8Encoded[utf8Encoded.length - 1] == 0) {
- return this.i18n('securityKeysPINTooLong');
- }
-
- // A PIN must contain at least four code-points. Javascript strings are
- // UCS-2 and the |length| property counts UCS-2 elements, not code-points.
- // (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.)
- // Therefore, iterate over the string (which does yield codepoints) and
- // check that four or more were seen.
- let length = 0;
- for (const codepoint of pin) {
- length++;
- }
-
- if (length < 4) {
- return this.i18n('securityKeysPINTooShort');
- }
-
- return '';
- },
-
- /** @private */
- errorChanged_: function() {
- // Make screen readers announce changes to the PIN validation error
- // label.
- this.fire('iron-announce', {text: this.error});
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.html
deleted file mode 100644
index 9313a82657b..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="security_keys_browser_proxy.html">
-
-<dom-module id="settings-security-keys-reset-dialog">
- <template>
- <style include="settings-shared">
- paper-spinner-lite {
- padding-bottom: 12px;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}" ignore-popstate
- on-close="closeDialog_">
- <div slot="title">[[title_]]</div>
- <div slot="body">
- <iron-pages attr-for-selected="id" selected="[[shown_]]"
- on-iron-select="onIronSelect_">
- <div id="initial">
- <p>$i18n{securityKeysResetStep1}</p>
- <paper-spinner-lite active></paper-spinner-lite>
- </div>
-
- <div id="noReset">
- <p>$i18n{securityKeysNoReset}</p>
- </div>
-
- <div id="resetFailed">
- <p>[[resetFailed_(errorCode_)]]</p>
- </div>
-
- <div id="reset2">
- <p>$i18n{securityKeysResetStep2}</p>
- </div>
-
- <div id="resetSuccess">
- <p>$i18n{securityKeysResetSuccess}</p>
- </div>
-
- <div id="resetNotAllowed">
- <p>$i18n{securityKeysResetNotAllowed}</p>
- </div>
- </iron-pages>
- </div>
- <div slot="button-container">
- <cr-button id="button" class$="[[maybeActionButton_(complete_)]]"
- on-click="closeDialog_">
- [[closeText_(complete_)]]
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="security_keys_reset_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js
deleted file mode 100644
index ce37c7e591c..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-security-keys-reset-dialog' is a dialog for
- * triggering factory resets of security keys.
- */
-Polymer({
- is: 'settings-security-keys-reset-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * A CTAP error code for when the specific error was not recognised.
- * @private
- */
- errorCode_: Number,
-
- /**
- * True iff the process has completed, successfully or otherwise.
- * @private
- */
- complete_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The id of an element on the page that is currently shown.
- * @private
- */
- shown_: {
- type: String,
- value: 'initial',
- },
-
- /**
- * @private
- */
- title_: String,
- },
-
- /** @private {?settings.SecurityKeysResetBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.title_ = this.i18n('securityKeysResetTitle');
- this.browserProxy_ =
- settings.SecurityKeysResetBrowserProxyImpl.getInstance();
- this.$.dialog.showModal();
-
- this.browserProxy_.reset().then(code => {
- // code is a CTAP error code. See
- // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
- if (code == 1 /* INVALID_COMMAND */) {
- this.shown_ = 'noReset';
- this.finish_();
- } else if (code != 0 /* unknown error */) {
- this.errorCode_ = code;
- this.shown_ = 'resetFailed';
- this.finish_();
- } else {
- this.title_ = this.i18n('securityKeysResetConfirmTitle');
- this.shown_ = 'reset2';
- this.browserProxy_.completeReset().then(code => {
- this.title_ = this.i18n('securityKeysResetTitle');
- if (code == 0 /* SUCCESS */) {
- this.shown_ = 'resetSuccess';
- } else if (code == 48 /* NOT_ALLOWED */) {
- this.shown_ = 'resetNotAllowed';
- } else /* unknown error */ {
- this.errorCode_ = code;
- this.shown_ = 'resetFailed';
- }
- this.finish_();
- });
- }
- });
- },
-
- /** @private */
- closeDialog_: function() {
- this.$.dialog.close();
- this.finish_();
- },
-
- /** @private */
- finish_: function() {
- if (this.complete_) {
- return;
- }
- this.complete_ = true;
- this.browserProxy_.close();
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onIronSelect_: function(e) {
- // Prevent this event from bubbling since it is unnecessarily triggering the
- // listener within settings-animated-pages.
- e.stopPropagation();
- },
-
- /**
- @param {number} code CTAP error code.
- @return {string} Contents of the error string that may be displayed
- to the user. Used automatically by Polymer.
- @private
- */
- resetFailed_: function(code) {
- if (code === null) {
- return '';
- }
- return this.i18n('securityKeysResetError', code.toString());
- },
-
- /**
- * @param {boolean} complete Whether the dialog process is complete.
- * @return {string} The label of the dialog button. Used automatically by
- * Polymer.
- * @private
- */
- closeText_: function(complete) {
- return this.i18n(complete ? 'ok' : 'cancel');
- },
-
- /**
- * @param {boolean} complete Whether the dialog process is complete.
- * @return {string} The class of the dialog button. Used automatically by
- * Polymer.
- * @private
- */
- maybeActionButton_: function(complete) {
- return complete ? 'action-button' : 'cancel-button';
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.html
deleted file mode 100644
index 94f201d1cf2..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.html
+++ /dev/null
@@ -1,141 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="security_keys_browser_proxy.html">
-
-<dom-module id="settings-security-keys-set-pin-dialog">
- <template>
- <style include="settings-shared">
- cr-input {
- display: inline-block;
- --cr-input-width: 8em;
- }
-
- #newPIN {
- padding-inline-end: 2em;
- }
-
- #newPINRow {
- display: flex;
- flex-direction: row;
- }
-
- paper-spinner-lite {
- padding-bottom: 12px;
- }
- </style>
-
- <cr-dialog id="dialog" close-text="$i18n{close}" ignore-popstate
- on-close="closeDialog_">
- <div slot="title">[[title_]]</div>
- <div slot="body">
- <iron-pages attr-for-selected="id" selected="[[shown_]]"
- on-iron-select="onIronSelect_">
- <div id="initial">
- <p>$i18n{securityKeysPINTouch}</p>
- <paper-spinner-lite active></paper-spinner-lite>
- </div>
-
- <div id="noPINSupport">
- <p>$i18n{securityKeysNoPIN}</p>
- </div>
-
- <div id="pinPrompt">
- <div id="currentPINEntry" hidden="[[!showCurrentEntry_]]">
- <p>$i18nRaw{securityKeysCurrentPINIntro}</p>
-
- <div id="currentPINRow">
- <cr-input id="currentPIN" value="{{currentPIN_}}" minLength="4"
- maxLength="255" spellcheck="false"
- on-input="onCurrentPINInput_"
- invalid="[[isNonEmpty_(currentPINError_)]]"
- label="$i18n{securityKeysCurrentPIN}" tabindex="0"
- type$="[[inputType_(pinsVisible_)]]"
- error-message="[[currentPINError_]]">
- <cr-icon-button slot="suffix" id="showPINsButton"
- class$="[[showPINsClass_(pinsVisible_)]]"
- title="[[showPINsTitle_(pinsVisible_)]]"
- focus-row-control focus-type="showPassword"
- on-click="showPINsClick_"></cr-icon-button>
- </cr-input>
-
- </div>
- </div>
-
- <p>$i18n{securityKeysNewPIN}</p>
-
- <div id="newPINRow">
- <cr-input id="newPIN" value="{{newPIN_}}" minLength="4"
- maxLength="255" spellcheck="false" on-input="onNewPINInput_"
- label="$i18n{securityKeysPIN}"
- tabindex="0" type$="[[inputType_(pinsVisible_)]]"
- invalid="[[isNonEmpty_(newPINError_)]]"
- error-message="[[newPINError_]]">
- <!-- If a show/hide icon is included in this row, this div is
- needed to ensure that the cr-input is the same height
- as the one to the right. Otherwise they don't vertically
- align -->
- <div style="height: 36px" slot="suffix"
- hidden="[[showCurrentEntry_]]"></div>
- </cr-input>
- <cr-input id="confirmPIN" value="{{confirmPIN_}}" minLength="4"
- maxLength="255" spellcheck="false"
- on-input="onConfirmPINInput_"
- label="$i18n{securityKeysConfirmPIN}" tabindex="0"
- invalid="[[isNonEmpty_(confirmPINError_)]]"
- type$="[[inputType_(pinsVisible_)]]"
- error-message="[[confirmPINError_]]">
- <cr-icon-button slot="suffix"
- class$="[[showPINsClass_(pinsVisible_)]]"
- title="[[showPINsTitle_(pinsVisible_)]]"
- hidden="[[showCurrentEntry_]]"
- focus-row-control focus-type="showPassword"
- on-click="showPINsClick_"></cr-icon-button>
- </cr-input>
- </div>
- </div>
-
- <div id="success">
- <p>$i18n{securityKeysPINSuccess}</p>
- </div>
-
- <div id="error">
- <p>[[pinFailed_(errorCode_)]]</p>
- </div>
-
- <div id="locked">
- <p>$i18n{securityKeysPINHardLock}</p>
- </div>
-
- <div id="reinsert">
- <p>$i18n{securityKeysPINSoftLock}</p>
- </div>
- </iron-pages>
- </div>
-
- <div slot="button-container">
- <cr-button id="closeButton"
- class$="[[maybeActionButton_(complete_)]]"
- on-click="closeDialog_">
- [[closeText_(complete_)]]
- </cr-button>
- <cr-button id="pinSubmit" class="action-button"
- on-click="pinSubmitNew_" disabled="[[!setPINButtonValid_]]"
- hidden="[[complete_]]">
- $i18n{securityKeysSetPINConfirm}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="security_keys_set_pin_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
deleted file mode 100644
index 813d93e51d5..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
+++ /dev/null
@@ -1,465 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-/**
- * @fileoverview 'settings-security-keys-set-pin-dialog' is a dialog for
- * setting and changing security key PINs.
- */
-Polymer({
- is: 'settings-security-keys-set-pin-dialog',
-
- behaviors: [I18nBehavior],
-
- properties: {
- /**
- * Whether the value of the current PIN textbox is a valid PIN or not.
- * @private
- */
- currentPINValid_: Boolean,
-
- /** @private */
- newPINValid_: Boolean,
-
- /** @private */
- confirmPINValid_: Boolean,
-
- /**
- * Whether the dialog is in a state where the Set PIN button should be
- * enabled. Read by Polymer.
- * @private
- */
- setPINButtonValid_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The value of the new PIN textbox. Read/write by Polymer.
- * @private
- */
- newPIN_: {
- type: String,
- value: '',
- },
-
- /** @private */
- confirmPIN_: {
- type: String,
- value: '',
- },
-
- /** @private */
- currentPIN_: {
- type: String,
- value: '',
- },
-
- /**
- * The number of PIN attempts remaining.
- * @private
- */
- retries_: Number,
-
- /**
- * A CTAP error code when we don't recognise the specific error. Read by
- * Polymer.
- * @private
- */
- errorCode_: Number,
-
- /**
- * Whether an entry for the current PIN should be displayed. (If no PIN
- * has been set then it won't be shown.)
- * @private
- */
- showCurrentEntry_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Error string to display under the current PIN entry, or empty.
- * @private
- */
- currentPINError_: {
- type: String,
- value: '',
- },
-
- /**
- * Error string to display under the new PIN entry, or empty.
- * @private
- */
- newPINError_: {
- type: String,
- value: '',
- },
-
- /**
- * Error string to display under the confirmation PIN entry, or empty.
- * @private
- */
- confirmPINError_: {
- type: String,
- value: '',
- },
-
- /**
- * Whether the dialog process has completed, successfully or otherwise.
- * @private
- */
- complete_: {
- type: Boolean,
- value: false,
- },
-
- /**
- * The id of an element on the page that is currently shown.
- * @private
- */
- shown_: {
- type: String,
- value: 'initial',
- },
-
- /**
- * Whether the contents of the PIN entries are visible, or are displayed
- * like passwords.
- * @private
- */
- pinsVisible_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- title_: String,
- },
-
- /** @private {?settings.SecurityKeysPINBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.title_ = this.i18n('securityKeysSetPINInitialTitle');
- this.browserProxy_ = settings.SecurityKeysPINBrowserProxyImpl.getInstance();
- this.$.dialog.showModal();
-
- Polymer.RenderStatus.afterNextRender(this, function() {
- Polymer.IronA11yAnnouncer.requestAvailability();
- });
-
- this.browserProxy_.startSetPIN().then(([success, errorCode]) => {
- if (success) {
- // Operation is complete. errorCode is a CTAP error code. See
- // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
- if (errorCode == 1 /* INVALID_COMMAND */) {
- this.shown_ = 'noPINSupport';
- this.finish_();
- } else if (errorCode == 52 /* temporarily locked */) {
- this.shown_ = 'reinsert';
- this.finish_();
- } else if (errorCode == 50 /* locked */) {
- this.shown_ = 'locked';
- this.finish_();
- } else {
- this.errorCode_ = errorCode;
- this.shown_ = 'error';
- this.finish_();
- }
- } else if (errorCode == 0) {
- // A device can also signal that it is locked by returning zero retries.
- this.shown_ = 'locked';
- this.finish_();
- } else {
- // Need to prompt for a pin. Initially set the text boxes to valid so
- // that they don't all appear red without the user typing anything.
- this.currentPINValid_ = true;
- this.newPINValid_ = true;
- this.confirmPINValid_ = true;
- this.setPINButtonValid_ = true;
-
- this.retries_ = errorCode;
- // retries_ may be null to indicate that there is currently no PIN set.
- let focusTarget;
- if (this.retries_ === null) {
- this.showCurrentEntry_ = false;
- focusTarget = this.$.newPIN;
- this.title_ = this.i18n('securityKeysSetPINCreateTitle');
- } else {
- this.showCurrentEntry_ = true;
- focusTarget = this.$.currentPIN;
- this.title_ = this.i18n('securityKeysSetPINChangeTitle');
- }
-
- this.shown_ = 'pinPrompt';
- // Focus cannot be set directly from within a backend callback.
- window.setTimeout(function() {
- focusTarget.focus();
- }, 0);
- this.fire('ui-ready'); // for test synchronization.
- }
- });
- },
-
- /** @private */
- closeDialog_: function() {
- this.$.dialog.close();
- this.finish_();
- },
-
- /** @private */
- finish_: function() {
- if (this.complete_) {
- return;
- }
- this.complete_ = true;
- // Setting |complete_| to true hides the |pinSubmitNew| button while it
- // has focus, which in turn causes the browser to move focus to the <body>
- // element, which in turn prevents subsequent "Enter" keystrokes to be
- // handled by cr-dialog itself. Re-focusing manually fixes this.
- this.$.dialog.focus();
- this.browserProxy_.close();
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onIronSelect_: function(e) {
- // Prevent this event from bubbling since it is unnecessarily triggering the
- // listener within settings-animated-pages.
- e.stopPropagation();
- },
-
- /** @private */
- onCurrentPINInput_: function() {
- // Typing in the current PIN box after an error makes the error message
- // disappear.
- this.currentPINError_ = '';
- },
-
- /** @private */
- onNewPINInput_: function() {
- // Typing in the new PIN box after an error makes the error message
- // disappear.
- this.newPINError_ = '';
- },
-
- /** @private */
- onConfirmPINInput_: function() {
- // Typing in the confirm PIN box after an error makes the error message
- // disappear.
- this.confirmPINError_ = '';
- },
-
- /**
- @param {string} pin A candidate PIN.
- @return {string} An error string or else '' to indicate validity.
- @private
- */
- isValidPIN_: function(pin) {
- // The UTF-8 encoding of the PIN must be between 4 and 63 bytes, and the
- // final byte cannot be zero.
- const utf8Encoded = new TextEncoder().encode(pin);
- if (utf8Encoded.length < 4) {
- return this.i18n('securityKeysPINTooShort');
- }
- if (utf8Encoded.length > 63 ||
- // If the PIN somehow has a NUL at the end then it's invalid, but this
- // is so obscure that we don't try to message it. Rather we just say
- // that it's too long because trimming the final character is the best
- // response by the user.
- utf8Encoded[utf8Encoded.length - 1] == 0) {
- return this.i18n('securityKeysPINTooLong');
- }
-
- // A PIN must contain at least four code-points. Javascript strings are
- // UCS-2 and the |length| property counts UCS-2 elements, not code-points.
- // (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.)
- // Therefore, iterate over the string (which does yield codepoints) and
- // check that four or more were seen.
- let length = 0;
- for (const codepoint of pin) {
- length++;
- }
-
- if (length < 4) {
- return this.i18n('securityKeysPINTooShort');
- }
-
- return '';
- },
-
- /**
- * @param {number} retries The number of PIN attempts remaining.
- * @return {string} The message to show under the text box.
- * @private
- */
- mismatchError_: function(retries) {
- // Warn the user if the number of retries is getting low.
- if (1 < retries && retries <= 3) {
- return this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString());
- }
- if (retries == 1) {
- return this.i18n('securityKeysPINIncorrectRetriesSin');
- }
- return this.i18n('securityKeysPINIncorrect');
- },
-
- /**
- * Called to set focus from inside a callback.
- * @private
- */
- focusOn_: function(focusTarget) {
- // Focus cannot be set directly from within a backend callback. Also,
- // directly focusing |currentPIN| doesn't always seem to work(!). Thus
- // focus something else first, which is a hack that seems to solve the
- // problem.
- let preFocusTarget = this.$.newPIN;
- if (preFocusTarget == focusTarget) {
- preFocusTarget = this.$.currentPIN;
- }
- window.setTimeout(function() {
- preFocusTarget.focus();
- focusTarget.focus();
- }, 0);
- },
-
- /**
- * Called by Polymer when the Set PIN button is activated.
- * @private
- */
- pinSubmitNew_: function() {
- if (this.showCurrentEntry_) {
- this.currentPINError_ = this.isValidPIN_(this.currentPIN_);
- if (this.currentPINError_ != '') {
- this.focusOn_(this.$.currentPIN);
- this.fire('iron-announce', {text: this.currentPINError_});
- this.fire('ui-ready'); // for test synchronization.
- return;
- }
- }
-
- this.newPINError_ = this.isValidPIN_(this.newPIN_);
- if (this.newPINError_ != '') {
- this.focusOn_(this.$.newPIN);
- this.fire('iron-announce', {text: this.newPINError_});
- this.fire('ui-ready'); // for test synchronization.
- return;
- }
-
- if (this.newPIN_ != this.confirmPIN_) {
- this.confirmPINError_ = this.i18n('securityKeysPINMismatch');
- this.focusOn_(this.$.confirmPIN);
- this.fire('iron-announce', {text: this.confirmPINError_});
- this.fire('ui-ready'); // for test synchronization.
- return;
- }
-
- this.setPINButtonValid_ = false;
- this.browserProxy_.setPIN(this.currentPIN_, this.newPIN_).then(result => {
- // This call always completes the process so result[0] is always 1.
- // result[1] is a CTAP2 error code. See
- // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
- if (result[1] == 0 /* SUCCESS */) {
- this.shown_ = 'success';
- this.finish_();
- } else if (result[1] == 52 /* temporarily locked */) {
- this.shown_ = 'reinsert';
- this.finish_();
- } else if (result[1] == 50 /* locked */) {
- this.shown_ = 'locked';
- this.finish_();
- } else if (result[1] == 49 /* PIN_INVALID */) {
- this.currentPINValid_ = false;
- this.retries_--;
- this.currentPINError_ = this.mismatchError_(this.retries_);
- this.setPINButtonValid_ = true;
- this.focusOn_(this.$.currentPIN);
- this.fire('iron-announce', {text: this.currentPINError_});
- this.fire('ui-ready'); // for test synchronization.
- } else {
- // Unknown error.
- this.errorCode_ = result[1];
- this.shown_ = 'error';
- this.finish_();
- }
- });
- },
-
- /**
- * onClick handler for the show/hide icon.
- * @private
- */
- showPINsClick_: function() {
- this.pinsVisible_ = !this.pinsVisible_;
- },
-
- /**
- * Polymer helper function to detect when an error string is empty.
- * @param {string} s Arbitrary string
- * @return {boolean} True iff |s| is non-empty.
- * @private
- */
- isNonEmpty_: function(s) {
- return s != '';
- },
-
- /**
- * Called by Polymer when |errorCode_| changes to set the error string.
- * @private
- */
- pinFailed_: function() {
- if (this.errorCode_ === null) {
- return '';
- }
- return this.i18n('securityKeysPINError', this.errorCode_.toString());
- },
-
- /**
- * @return {string} The class of the Ok / Cancel button.
- * @private
- */
- maybeActionButton_: function() {
- return this.complete_ ? 'action-button' : 'cancel-button';
- },
-
- /**
- * @return {string} The label of the Ok / Cancel button.
- * @private
- */
- closeText_: function() {
- return this.i18n(this.complete_ ? 'ok' : 'cancel');
- },
-
- /**
- * @return {string} The class (and thus icon) to be displayed.
- * @private
- */
- showPINsClass_: function() {
- return 'icon-visibility' + (this.pinsVisible_ ? '-off' : '');
- },
-
- /**
- * @return {string} The tooltip for the icon.
- * @private
- */
- showPINsTitle_: function() {
- return this.i18n(
- this.pinsVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs');
- },
-
- /**
- * @return {string} The PIN-input element type.
- * @private
- */
- inputType_: function() {
- return this.pinsVisible_ ? 'text' : 'password';
- },
-});
-})();
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html
deleted file mode 100644
index 8f242c43a58..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<link rel="import" href="security_keys_credential_management_dialog.html">
-<link rel="import" href="security_keys_bio_enroll_dialog.html">
-<link rel="import" href="security_keys_set_pin_dialog.html">
-<link rel="import" href="security_keys_reset_dialog.html">
-
-<dom-module id="security-keys-subpage">
- <template>
- <style include="settings-shared"></style>
-
- <cr-link-row
- id="setPINButton"
- label="$i18n{securityKeysSetPIN}"
- sub-label="$i18n{securityKeysSetPINDesc}"
- on-click="onSetPIN_"></cr-link-row>
- <template is="dom-if" if="[[enableCredentialManagement_]]">
- <cr-link-row
- id="credentialManagementButton"
- class="hr"
- label="$i18n{securityKeysCredentialManagementLabel}"
- sub-label="$i18n{securityKeysCredentialManagementDesc}"
- on-click="onCredentialManagement_"></cr-link-row>
- </template>
- <cr-link-row
- id="resetButton"
- class="hr"
- label="$i18n{securityKeysReset}"
- sub-label="$i18n{securityKeysResetDesc}"
- on-click="onReset_"></cr-link-row>
- <template is="dom-if" if="[[enableBioEnrollment_]]">
- <cr-link-row
- id="bioEnrollButton"
- class="hr"
- label="$i18n{securityKeysBioEnrollmentDialogTitle}"
- sub-label="$i18n{securityKeysBioEnrollmentSubpageDescription}"
- on-click="onBioEnroll_"></cr-link-row>
- </template>
-
- <template is="dom-if" if="[[showSetPINDialog_]]" restamp>
- <settings-security-keys-set-pin-dialog on-close="onSetPINDialogClosed_">
- </settings-security-keys-set-pin-dialog>
- </template>
-
- <template is="dom-if" if="[[showCredentialManagementDialog_]]" restamp>
- <settings-security-keys-credential-management-dialog
- on-close="onCredentialManagementDialogClosed_">
- </settings-security-keys-credential-management-dialog>
- </template>
-
- <template is="dom-if" if="[[showResetDialog_]]" restamp>
- <settings-security-keys-reset-dialog on-close="onResetDialogClosed_">
- </settings-security-keys-reset-dialog>
- </template>
-
- <template is="dom-if" if="[[showBioEnrollDialog_]]" restamp>
- <settings-security-keys-bio-enroll-dialog
- on-close="onBioEnrollDialogClosed_">
- </settings-security-keys-bio-enroll-dialog>
- </template>
-
- </template>
- <script src="security_keys_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.js b/chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.js
deleted file mode 100644
index 414f10740ec..00000000000
--- a/chromium/chrome/browser/resources/settings/privacy_page/security_keys_subpage.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'security-keys-subpage' is a settings subpage
- * containing operations on security keys.
- */
-Polymer({
- is: 'security-keys-subpage',
-
- properties: {
- /** @private */
- enableCredentialManagement_: {
- type: Boolean,
- readOnly: true,
- value: function() {
- return loadTimeData.getBoolean(
- 'enableSecurityKeysCredentialManagement');
- }
- },
-
- /** @private */
- enableBioEnrollment_: {
- type: Boolean,
- readOnly: true,
- value: function() {
- return loadTimeData.getBoolean('enableSecurityKeysBioEnrollment');
- }
- },
-
- /** @private */
- showSetPINDialog_: {
- type: Boolean,
- value: false,
- },
- /** @private */
- showCredentialManagementDialog_: {
- type: Boolean,
- value: false,
- },
- /** @private */
- showResetDialog_: {
- type: Boolean,
- value: false,
- },
- /** @private */
- showBioEnrollDialog_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @private */
- onSetPIN_: function() {
- this.showSetPINDialog_ = true;
- },
-
- /** @private */
- onSetPINDialogClosed_: function() {
- this.showSetPINDialog_ = false;
- cr.ui.focusWithoutInk(this.$.setPINButton);
- },
-
- /** @private */
- onCredentialManagement_: function() {
- this.showCredentialManagementDialog_ = true;
- },
-
- /** @private */
- onCredentialManagementDialogClosed_: function() {
- this.showCredentialManagementDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$$('#credentialManagementButton')));
- },
-
- /** @private */
- onReset_: function() {
- this.showResetDialog_ = true;
- },
-
- /** @private */
- onResetDialogClosed_: function() {
- this.showResetDialog_ = false;
- cr.ui.focusWithoutInk(this.$.resetButton);
- },
-
- /** @private */
- onBioEnroll_: function() {
- this.showBioEnrollDialog_ = true;
- },
-
- /** @private */
- onBioEnrollDialogClosed_: function() {
- this.showBioEnrollDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$$('#bioEnrollButton')));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html b/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html
deleted file mode 100644
index 965e35fc1e5..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="../lifetime_browser_proxy.html">
-<link rel="import" href="reset_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-powerwash-dialog">
- <template>
- <style include="settings-shared">
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}"
- ignore-enter-key>
- <div slot="title">$i18n{powerwashDialogTitle}</div>
- <div slot="body">
- <span>
- $i18n{powerwashDialogExplanation}
- <a href="$i18nRaw{powerwashLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </span>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_"
- id="cancel">$i18n{cancel}</cr-button>
- <cr-button class="action-button" id="powerwash"
- on-click="onRestartTap_">$i18n{powerwashDialogButton}</cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="powerwash_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.js b/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.js
deleted file mode 100644
index 37786342ef1..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-powerwash-dialog' is a dialog shown to request confirmation from
- * the user for a device reset (aka powerwash).
- */
-Polymer({
- is: 'settings-powerwash-dialog',
-
- properties: {
- /** @public */
- requestTpmFirmwareUpdate: {
- type: Boolean,
- value: false,
- }
- },
-
- /** @override */
- attached: function() {
- settings.ResetBrowserProxyImpl.getInstance().onPowerwashDialogShow();
- this.$.dialog.showModal();
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onRestartTap_: function() {
- settings.LifetimeBrowserProxyImpl.getInstance().factoryReset(
- this.requestTpmFirmwareUpdate);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.html b/chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.html
deleted file mode 100644
index e34f0cf57ea..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="reset_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.js b/chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.js
deleted file mode 100644
index b9d40047a73..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_browser_proxy.js
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.define('settings', function() {
- /** @interface */
- class ResetBrowserProxy {
- /**
- * @param {boolean} sendSettings Whether the user gave consent to upload
- * broken settings to Google for analysis.
- * @param {string} requestOrigin The origin of the reset request.
- * @return {!Promise} A promise firing once resetting has completed.
- */
- performResetProfileSettings(sendSettings, requestOrigin) {}
-
- /**
- * A method to be called when the reset profile dialog is hidden.
- */
- onHideResetProfileDialog() {}
-
- /**
- * A method to be called when the reset profile banner is hidden.
- */
- onHideResetProfileBanner() {}
-
- /**
- * A method to be called when the reset profile dialog is shown.
- */
- onShowResetProfileDialog() {}
-
- /**
- * Shows the settings that are about to be reset and which will be reported
- * to Google for analysis, in a new tab.
- */
- showReportedSettings() {}
-
- /**
- * Retrieves the triggered reset tool name.
- * @return {!Promise<string>} A promise firing with the tool name, once it
- * has been retrieved.
- */
- getTriggeredResetToolName() {}
-
- // <if expr="chromeos">
- /**
- * A method to be called when the reset powerwash dialog is shown.
- */
- onPowerwashDialogShow() {}
-
- /**
- * Initiates a factory reset and restarts ChromeOS.
- */
- requestFactoryResetRestart() {}
- // </if>
- }
-
- /**
- * @implements {settings.ResetBrowserProxy}
- */
- class ResetBrowserProxyImpl {
- /** @override */
- performResetProfileSettings(sendSettings, requestOrigin) {
- return cr.sendWithPromise(
- 'performResetProfileSettings', sendSettings, requestOrigin);
- }
-
- /** @override */
- onHideResetProfileDialog() {
- chrome.send('onHideResetProfileDialog');
- }
-
- /** @override */
- onHideResetProfileBanner() {
- chrome.send('onHideResetProfileBanner');
- }
-
- /** @override */
- onShowResetProfileDialog() {
- chrome.send('onShowResetProfileDialog');
- }
-
- /** @override */
- showReportedSettings() {
- cr.sendWithPromise('getReportedSettings').then(function(settings) {
- const output = settings.map(function(entry) {
- return entry.key + ': ' + entry.value.replace(/\n/g, ', ');
- });
- const win = window.open('about:blank');
- const div = win.document.createElement('div');
- div.textContent = output.join('\n');
- div.style.whiteSpace = 'pre';
- win.document.body.appendChild(div);
- });
- }
-
- /** @override */
- getTriggeredResetToolName() {
- return cr.sendWithPromise('getTriggeredResetToolName');
- }
-
- // <if expr="chromeos">
- /** @override */
- onPowerwashDialogShow() {
- chrome.send('onPowerwashDialogShow');
- }
-
- /** @override */
- requestFactoryResetRestart() {
- chrome.send('requestFactoryResetRestart');
- }
- // </if>
- }
-
- cr.addSingletonGetter(ResetBrowserProxyImpl);
-
- return {
- ResetBrowserProxy: ResetBrowserProxy,
- ResetBrowserProxyImpl: ResetBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_page.html b/chromium/chrome/browser/resources/settings/reset_page/reset_page.html
deleted file mode 100644
index b441f7bd43b..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_page.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="reset_profile_dialog.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<if expr="chromeos">
-<link rel="import" href="powerwash_dialog.html">
-</if>
-
-<if expr="_google_chrome and is_win">
-<link rel="import" href="../chrome_cleanup_page/chrome_cleanup_page.html">
-<link rel="import" href="../incompatible_applications_page/incompatible_applications_page.html">
-</if>
-
-<dom-module id="settings-reset-page">
- <template>
- <style include="settings-shared"></style>
- <settings-animated-pages id="reset-pages" section="reset">
- <div route-path="default">
- <cr-link-row id="resetProfile" label="$i18n{resetTrigger}"
- on-click="onShowResetProfileDialog_"></cr-link-row>
- <!-- Keep a single instance of reset-profile-dialog on purpose, to
- preserve state across show/hide operations. -->
- <cr-lazy-render id="resetProfileDialog">
- <template>
- <settings-reset-profile-dialog
- on-close="onResetProfileDialogClose_">
- </settings-reset-profile-dialog>
- </template>
- </cr-lazy-render>
-<if expr="chromeos">
- <cr-link-row class="hr" hidden="[[!allowPowerwash_]]" id="powerwash"
- label="$i18n{powerwashTitle}" on-click="onShowPowerwashDialog_"
- sub-label="$i18n{powerwashDescription}"></cr-link-row>
- <template is="dom-if" if="[[showPowerwashDialog_]]" restamp>
- <settings-powerwash-dialog on-close="onPowerwashDialogClose_">
- </settings-powerwash-dialog>
- </template>
-</if>
-<if expr="_google_chrome and is_win">
- <cr-link-row class="hr" id="chromeCleanupSubpageTrigger"
- label="$i18n{resetCleanupComputerTrigger}"
- on-click="onChromeCleanupTap_"></cr-link-row>
- <template is="dom-if" if="[[showIncompatibleApplications_]]" restamp>
- <cr-link-row class="hr" id="incompatibleApplicationsSubpageTrigger"
- label="$i18n{incompatibleApplicationsResetCardTitle}"
- on-click="onIncompatibleApplicationsTap_"></cr-link-row>
- </template>
-</if>
- </div>
-<if expr="_google_chrome and is_win">
- <template is="dom-if" route-path="/cleanup">
- <settings-subpage id="chromeCleanupSubpage"
- associated-control="[[$$('#chromeCleanupSubpageTrigger')]]"
- page-title="$i18n{resetCleanupComputerTrigger}"
- learn-more-url="$i18n{chromeCleanupLearnMoreUrl}">
- <settings-chrome-cleanup-page prefs="{{prefs}}">
- </settings-chrome-cleanup-page>
- </settings-subpage>
- </template>
- <template is="dom-if" if="[[showIncompatibleApplications_]]">
- <template is="dom-if" route-path="/incompatibleApplications">
- <settings-subpage id="incompatibleApplicationsSubpage"
- associated-control="[[$$('#incompatibleApplicationsSubpageTrigger')]]"
- page-title="$i18n{incompatibleApplicationsResetCardTitle}">
- <settings-incompatible-applications-page>
- </settings-incompatible-applications-page>
- </settings-subpage>
- </template>
- </template>
-</if>
- </settings-animated-pages>
- </template>
- <script src="reset_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_page.js b/chromium/chrome/browser/resources/settings/reset_page/reset_page.js
deleted file mode 100644
index 3e9654c4f0b..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_page.js
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-reset-page' is the settings page containing reset
- * settings.
- *
- * Example:
- *
- * <iron-animated-pages>
- * <settings-reset-page prefs="{{prefs}}">
- * </settings-reset-page>
- * ... other pages ...
- * </iron-animated-pages>
- */
-Polymer({
- is: 'settings-reset-page',
-
- behaviors: [settings.RouteObserverBehavior],
-
- properties: {
- /** Preferences state. */
- prefs: Object,
-
- // <if expr="chromeos">
- /**
- * Dictionary defining page visibility.
- * @type {!ResetPageVisibility}
- */
- pageVisibility: Object,
-
- /** @private */
- showPowerwashDialog_: Boolean,
-
- /** @private */
- allowPowerwash_: Boolean,
- // </if>
-
-
- // <if expr="_google_chrome and is_win">
- /** @private */
- showIncompatibleApplications_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('showIncompatibleApplications');
- },
- },
- // </if>
- },
-
- // <if expr="chromeos">
- /** @override */
- ready: function() {
- // TODO(hsuregan): Remove when OS settings migration is complete.
- this.allowPowerwash_ = loadTimeData.getBoolean('allowPowerwash') &&
- this.pageVisibility.powerwash;
- },
- // </if>
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- const lazyRender =
- /** @type {!CrLazyRenderElement} */ (this.$.resetProfileDialog);
-
- if (route == settings.routes.TRIGGERED_RESET_DIALOG ||
- route == settings.routes.RESET_DIALOG) {
- /** @type {!SettingsResetProfileDialogElement} */ (lazyRender.get())
- .show();
- } else {
- const dialog = /** @type {?SettingsResetProfileDialogElement} */ (
- lazyRender.getIfExists());
- if (dialog) {
- dialog.cancel();
- }
- }
- },
-
- /** @private */
- onShowResetProfileDialog_: function() {
- settings.navigateTo(
- settings.routes.RESET_DIALOG, new URLSearchParams('origin=userclick'));
- },
-
- /** @private */
- onResetProfileDialogClose_: function() {
- settings.navigateToPreviousRoute();
- cr.ui.focusWithoutInk(assert(this.$.resetProfile));
- },
-
- // <if expr="chromeos">
- /**
- * @param {!Event} e
- * @private
- */
- onShowPowerwashDialog_: function(e) {
- e.preventDefault();
- this.showPowerwashDialog_ = true;
- },
-
- /** @private */
- onPowerwashDialogClose_: function() {
- this.showPowerwashDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$.powerwash));
- },
- // </if>
-
- // <if expr="_google_chrome and is_win">
- /** @private */
- onChromeCleanupTap_: function() {
- settings.navigateTo(settings.routes.CHROME_CLEANUP);
- },
-
- /** @private */
- onIncompatibleApplicationsTap_: function() {
- settings.navigateTo(settings.routes.INCOMPATIBLE_APPLICATIONS);
- },
- // </if>
-
-});
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.html b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.html
deleted file mode 100644
index 57fdf7ac742..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="reset_browser_proxy.html">
-<link rel="import" href="../route.html">
-
-<dom-module id="settings-reset-profile-banner">
- <template>
- <style include="settings-shared"></style>
- <cr-dialog id="dialog" close-text="$i18n{close}" ignore-popstate>
- <div slot="title">$i18n{resetAutomatedDialogTitle}</div>
- <div slot="body">
- <span id="description">
- $i18n{resetProfileBannerDescription}
- <a id="learnMore"
- href="$i18nRaw{resetProfileBannerLearnMoreUrl}"
- target="_blank">$i18n{learnMore}</a>
- </span>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onOkTap_" id="ok">
- $i18n{ok}
- </cr-button>
- <cr-button class="action-button" on-click="onResetTap_" id="reset">
- $i18n{resetProfileBannerButton}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="reset_profile_banner.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.js b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.js
deleted file mode 100644
index 441279b76c7..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-reset-profile-banner' is the banner shown for prompting the user to
- * clear profile settings.
- */
-Polymer({
- // TODO(dpapad): Rename to settings-reset-warning-dialog.
- is: 'settings-reset-profile-banner',
-
- listeners: {
- 'cancel': 'onCancel_',
- },
-
- /** @override */
- attached: function() {
- this.$.dialog.showModal();
- },
-
- /** @private */
- onOkTap_: function() {
- this.$.dialog.cancel();
- },
-
- /** @private */
- onCancel_: function() {
- settings.ResetBrowserProxyImpl.getInstance().onHideResetProfileBanner();
- },
-
- /** @private */
- onResetTap_: function() {
- this.$.dialog.close();
- settings.navigateTo(settings.routes.RESET_DIALOG);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.html b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.html
deleted file mode 100644
index 5c5863d80ed..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="reset_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-reset-profile-dialog">
- <template>
- <style include="settings-shared action-link">
- paper-spinner-lite {
- margin: 0 8px;
- }
-
- #dialog-body {
- /* Add space for the link focus ring. See https://crbug.com/909653. */
- padding-bottom: 2px;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}"
- ignore-popstate ignore-enter-key>
- <div slot="title">
- [[getPageTitle_(isTriggered_, triggeredResetToolName_)]]
- </div>
- <div id="dialog-body" slot="body">
- <span>
- [[getExplanationText_(isTriggered_, triggeredResetToolName_)]]
- <a href="$i18nRaw{resetPageLearnMoreUrl}" target="_blank">
- $i18n{learnMore}
- </a>
- </span>
- </div>
- <div slot="button-container">
- <paper-spinner-lite id="resetSpinner" active="[[clearingInProgress_]]">
- </paper-spinner-lite>
- <cr-button class="cancel-button" on-click="onCancelTap_"
- id="cancel" disabled="[[clearingInProgress_]]">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onResetTap_"
- id="reset" disabled="[[clearingInProgress_]]">
- $i18n{resetDialogCommit}
- </cr-button>
- </div>
- <div slot="footer">
- <cr-checkbox id="sendSettings" checked>
- $i18nRaw{resetPageFeedback}</cr-checkbox>
- </div>
- </cr-dialog>
- </template>
- <script src="reset_profile_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.js b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.js
deleted file mode 100644
index 73acaf41033..00000000000
--- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.js
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- *
- * 'settings-reset-profile-dialog' is the dialog shown for clearing profile
- * settings. A triggered variant of this dialog can be shown under certain
- * circumstances. See triggered_profile_resetter.h for when the triggered
- * variant will be used.
- */
-Polymer({
- is: 'settings-reset-profile-dialog',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- // TODO(dpapad): Evaluate whether this needs to be synced across different
- // settings tabs.
-
- /** @private */
- isTriggered_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- triggeredResetToolName_: {
- type: String,
- value: '',
- },
-
- /** @private */
- resetRequestOrigin_: String,
-
- /** @private */
- clearingInProgress_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @private {?settings.ResetBrowserProxy} */
- browserProxy_: null,
-
- /**
- * @private
- * @return {string}
- */
- getExplanationText_: function() {
- if (this.isTriggered_) {
- return loadTimeData.getStringF(
- 'triggeredResetPageExplanation', this.triggeredResetToolName_);
- }
- return loadTimeData.getStringF('resetPageExplanation');
- },
-
- /**
- * @private
- * @return {string}
- */
- getPageTitle_: function() {
- if (this.isTriggered_) {
- return loadTimeData.getStringF(
- 'triggeredResetPageTitle', this.triggeredResetToolName_);
- }
- return loadTimeData.getStringF('resetDialogCommit');
- },
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.ResetBrowserProxyImpl.getInstance();
-
- this.addEventListener('cancel', () => {
- this.browserProxy_.onHideResetProfileDialog();
- });
-
- this.$$('cr-checkbox a')
- .addEventListener('click', this.onShowReportedSettingsTap_.bind(this));
- },
-
- /** @private */
- showDialog_: function() {
- if (!this.$.dialog.open) {
- this.$.dialog.showModal();
- }
- this.browserProxy_.onShowResetProfileDialog();
- },
-
- show: function() {
- this.isTriggered_ =
- settings.getCurrentRoute() == settings.routes.TRIGGERED_RESET_DIALOG;
- if (this.isTriggered_) {
- this.browserProxy_.getTriggeredResetToolName().then(name => {
- this.resetRequestOrigin_ = 'triggeredreset';
- this.triggeredResetToolName_ = name;
- this.showDialog_();
- });
- } else {
- // For the non-triggered reset dialog, a '#cct' hash indicates that the
- // reset request came from the Chrome Cleanup Tool by launching Chrome
- // with the startup URL chrome://settings/resetProfileSettings#cct.
- const origin = window.location.hash.slice(1).toLowerCase() == 'cct' ?
- 'cct' :
- settings.getQueryParameters().get('origin');
- this.resetRequestOrigin_ = origin || '';
- this.showDialog_();
- }
- },
-
- /** @private */
- onCancelTap_: function() {
- this.cancel();
- },
-
- cancel: function() {
- if (this.$.dialog.open) {
- this.$.dialog.cancel();
- }
- },
-
- /** @private */
- onResetTap_: function() {
- this.clearingInProgress_ = true;
- this.browserProxy_
- .performResetProfileSettings(
- this.$.sendSettings.checked, this.resetRequestOrigin_)
- .then(() => {
- this.clearingInProgress_ = false;
- if (this.$.dialog.open) {
- this.$.dialog.close();
- }
- this.fire('reset-done');
- });
- },
-
- /**
- * Displays the settings that will be reported in a new tab.
- * @param {!Event} e
- * @private
- */
- onShowReportedSettingsTap_: function(e) {
- this.browserProxy_.showReportedSettings();
- e.stopPropagation();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/route.html b/chromium/chrome/browser/resources/settings/route.html
deleted file mode 100644
index 4156107d5e5..00000000000
--- a/chromium/chrome/browser/resources/settings/route.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="i18n_setup.html">
-<link rel="import" href="page_visibility.html">
-
-<script src="route.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/route.js b/chromium/chrome/browser/resources/settings/route.js
deleted file mode 100644
index 091bb933e2e..00000000000
--- a/chromium/chrome/browser/resources/settings/route.js
+++ /dev/null
@@ -1,899 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Specifies all possible routes in settings.
- *
- * @typedef {{
- * ABOUT: (undefined|!settings.Route),
- * ABOUT_ABOUT: (undefined|!settings.Route),
- * ACCESSIBILITY: (undefined|!settings.Route),
- * ACCOUNTS: (undefined|!settings.Route),
- * ACCOUNT_MANAGER: (undefined|!settings.Route),
- * ADVANCED: (undefined|!settings.Route),
- * ADDRESSES: (undefined|!settings.Route),
- * APP_MANAGEMENT: (undefined|!settings.Route),
- * APP_MANAGEMENT_DETAIL: (undefined|!settings.Route),
- * APPS: (undefined|!settings.Route),
- * ANDROID_APPS: (undefined|!settings.Route),
- * ANDROID_APPS_DETAILS: (undefined|!settings.Route),
- * CROSTINI: (undefined|!settings.Route),
- * CROSTINI_DETAILS: (undefined|!settings.Route),
- * CROSTINI_EXPORT_IMPORT: (undefined|!settings.Route),
- * CROSTINI_SHARED_PATHS: (undefined|!settings.Route),
- * CROSTINI_SHARED_USB_DEVICES: (undefined|!settings.Route),
- * APPEARANCE: (undefined|!settings.Route),
- * AUTOFILL: (undefined|!settings.Route),
- * BASIC: (undefined|!settings.Route),
- * BLUETOOTH: (undefined|!settings.Route),
- * BLUETOOTH_DEVICES: (undefined|!settings.Route),
- * CAPTIONS: (undefined|!settings.Route),
- * CERTIFICATES: (undefined|!settings.Route),
- * CHANGE_PICTURE: (undefined|!settings.Route),
- * CHROME_CLEANUP: (undefined|!settings.Route),
- * CLEAR_BROWSER_DATA: (undefined|!settings.Route),
- * CLOUD_PRINTERS: (undefined|!settings.Route),
- * CUPS_PRINTERS: (undefined|!settings.Route),
- * DATETIME: (undefined|!settings.Route),
- * DATETIME_TIMEZONE_SUBPAGE: (undefined|!settings.Route),
- * DEFAULT_BROWSER: (undefined|!settings.Route),
- * DETAILED_BUILD_INFO: (undefined|!settings.Route),
- * DEVICE: (undefined|!settings.Route),
- * DISPLAY: (undefined|!settings.Route),
- * DOWNLOADS: (undefined|!settings.Route),
- * EDIT_DICTIONARY: (undefined|!settings.Route),
- * EXTERNAL_STORAGE_PREFERENCES: (undefined|!settings.Route),
- * FINGERPRINT: (undefined|!settings.Route),
- * FILES: (undefined|!settings.Route),
- * FONTS: (undefined|!settings.Route),
- * GOOGLE_ASSISTANT: (undefined|!settings.Route),
- * IMPORT_DATA: (undefined|!settings.Route),
- * INCOMPATIBLE_APPLICATIONS: (undefined|!settings.Route),
- * INPUT_METHODS: (undefined|!settings.Route),
- * INTERNET: (undefined|!settings.Route),
- * INTERNET_NETWORKS: (undefined|!settings.Route),
- * KERBEROS_ACCOUNTS: (undefined|!settings.Route),
- * KEYBOARD: (undefined|!settings.Route),
- * KNOWN_NETWORKS: (undefined|!settings.Route),
- * LANGUAGES: (undefined|!settings.Route),
- * LANGUAGES_DETAILS: (undefined|!settings.Route),
- * LOCK_SCREEN: (undefined|!settings.Route),
- * MANAGE_ACCESSIBILITY: (undefined|!settings.Route),
- * MANAGE_CAPTION_SETTINGS: (undefined|!settings.Route),
- * MANAGE_PROFILE: (undefined|!settings.Route),
- * MANAGE_SWITCH_ACCESS_SETTINGS: (undefined|!settings.Route),
- * MANAGE_TTS_SETTINGS: (undefined|!settings.Route),
- * MULTIDEVICE: (undefined|!settings.Route),
- * MULTIDEVICE_FEATURES: (undefined|!settings.Route),
- * NETWORK_DETAIL: (undefined|!settings.Route),
- * ON_STARTUP: (undefined|!settings.Route),
- * PASSWORDS: (undefined|!settings.Route),
- * PAYMENTS: (undefined|!settings.Route),
- * PEOPLE: (undefined|!settings.Route),
- * PERSONALIZATION: (undefined|!settings.Route),
- * PLUGIN_VM: (undefined|!settings.Route),
- * PLUGIN_VM_DETAILS: (undefined|!settings.Route),
- * PLUGIN_VM_SHARED_PATHS: (undefined|!settings.Route),
- * POINTERS: (undefined|!settings.Route),
- * POWER: (undefined|!settings.Route),
- * PRINTING: (undefined|!settings.Route),
- * PRIVACY: (undefined|!settings.Route),
- * RESET: (undefined|!settings.Route),
- * RESET_DIALOG: (undefined|!settings.Route),
- * SEARCH: (undefined|!settings.Route),
- * SEARCH_ENGINES: (undefined|!settings.Route),
- * SECURITY_KEYS: (undefined|!settings.Route),
- * SIGN_OUT: (undefined|!settings.Route),
- * SITE_SETTINGS: (undefined|!settings.Route),
- * SITE_SETTINGS_ADS: (undefined|!settings.Route),
- * SITE_SETTINGS_ALL: (undefined|!settings.Route),
- * SITE_SETTINGS_AUTOMATIC_DOWNLOADS: (undefined|!settings.Route),
- * SITE_SETTINGS_BACKGROUND_SYNC: (undefined|!settings.Route),
- * SITE_SETTINGS_BLUETOOTH_SCANNING: (undefined|!settings.Route),
- * SITE_SETTINGS_CAMERA: (undefined|!settings.Route),
- * SITE_SETTINGS_CLIPBOARD: (undefined|!settings.Route),
- * SITE_SETTINGS_COOKIES: (undefined|!settings.Route),
- * SITE_SETTINGS_DATA_DETAILS: (undefined|!settings.Route),
- * SITE_SETTINGS_FLASH: (undefined|!settings.Route),
- * SITE_SETTINGS_HANDLERS: (undefined|!settings.Route),
- * SITE_SETTINGS_IMAGES: (undefined|!settings.Route),
- * SITE_SETTINGS_MIXEDSCRIPT: (undefined|!settings.Route),
- * SITE_SETTINGS_JAVASCRIPT: (undefined|!settings.Route),
- * SITE_SETTINGS_SENSORS: (undefined|!settings.Route),
- * SITE_SETTINGS_SOUND: (undefined|!settings.Route),
- * SITE_SETTINGS_LOCATION: (undefined|!settings.Route),
- * SITE_SETTINGS_MICROPHONE: (undefined|!settings.Route),
- * SITE_SETTINGS_MIDI_DEVICES: (undefined|!settings.Route),
- * SITE_SETTINGS_NATIVE_FILE_SYSTEM_WRITE: (undefined|!settings.Route),
- * SITE_SETTINGS_NOTIFICATIONS: (undefined|!settings.Route),
- * SITE_SETTINGS_PAYMENT_HANDLER: (undefined|!settings.Route),
- * SITE_SETTINGS_PDF_DOCUMENTS: (undefined|!settings.Route),
- * SITE_SETTINGS_POPUPS: (undefined|!settings.Route),
- * SITE_SETTINGS_PROTECTED_CONTENT: (undefined|!settings.Route),
- * SITE_SETTINGS_SITE_DATA: (undefined|!settings.Route),
- * SITE_SETTINGS_SITE_DETAILS: (undefined|!settings.Route),
- * SITE_SETTINGS_UNSANDBOXED_PLUGINS: (undefined|!settings.Route),
- * SITE_SETTINGS_USB_DEVICES: (undefined|!settings.Route),
- * SITE_SETTINGS_SERIAL_PORTS: (undefined|!settings.Route),
- * SITE_SETTINGS_ZOOM_LEVELS: (undefined|!settings.Route),
- * SMART_LOCK: (undefined|!settings.Route),
- * SMB_SHARES: (undefined|!settings.Route),
- * STORAGE: (undefined|!settings.Route),
- * STYLUS: (undefined|!settings.Route),
- * SYNC: (undefined|!settings.Route),
- * SYNC_ADVANCED: (undefined|!settings.Route),
- * SYSTEM: (undefined|!settings.Route),
- * TRIGGERED_RESET_DIALOG: (undefined|!settings.Route),
- * }}
- */
-let SettingsRoutes;
-
-cr.define('settings', function() {
-
- /**
- * Class for navigable routes. May only be instantiated within this file.
- */
- class Route {
- /** @param {string} path */
- constructor(path) {
- /** @type {string} */
- this.path = path;
-
- /** @type {?settings.Route} */
- this.parent = null;
-
- /** @type {number} */
- this.depth = 0;
-
- /**
- * @type {boolean} Whether this route corresponds to a navigable
- * dialog. Those routes don't belong to a "section".
- */
- this.isNavigableDialog = false;
-
- // Below are all legacy properties to provide compatibility with the old
- // routing system.
-
- /** @type {string} */
- this.section = '';
- }
-
- /**
- * Returns a new Route instance that's a child of this route.
- * @param {string} path Extends this route's path if it doesn't contain a
- * leading slash.
- * @return {!settings.Route}
- * @private
- */
- createChild(path) {
- assert(path);
-
- // |path| extends this route's path if it doesn't have a leading slash.
- // If it does have a leading slash, it's just set as the new route's URL.
- const newUrl = path[0] == '/' ? path : `${this.path}/${path}`;
-
- const route = new Route(newUrl);
- route.parent = this;
- route.section = this.section;
- route.depth = this.depth + 1;
-
- return route;
- }
-
- /**
- * Returns a new Route instance that's a child section of this route.
- * TODO(tommycli): Remove once we've obsoleted the concept of sections.
- * @param {string} path
- * @param {string} section
- * @return {!settings.Route}
- * @private
- */
- createSection(path, section) {
- const route = this.createChild(path);
- route.section = section;
- return route;
- }
-
- /**
- * Returns the absolute path string for this Route, assuming this function
- * has been called from within chrome://settings.
- * @return {string}
- */
- getAbsolutePath() {
- return window.location.origin + this.path;
- }
-
- /**
- * Returns true if this route matches or is an ancestor of the parameter.
- * @param {!settings.Route} route
- * @return {boolean}
- */
- contains(route) {
- for (let r = route; r != null; r = r.parent) {
- if (this == r) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if this route is a subpage of a section.
- * @return {boolean}
- */
- isSubpage() {
- return !!this.parent && !!this.section &&
- this.parent.section == this.section;
- }
- }
-
- /**
- * @return {!SettingsRoutes} Routes that are shared between browser and OS
- * settings under the same conditions (e.g. in guest mode).
- */
- function computeCommonRoutes() {
- /** @type {!SettingsRoutes} */
- const r = {};
-
- // Root pages.
- r.BASIC = new Route('/');
- r.ABOUT = new Route('/help');
-
- r.SIGN_OUT = r.BASIC.createChild('/signOut');
- r.SIGN_OUT.isNavigableDialog = true;
-
- r.SEARCH = r.BASIC.createSection('/search', 'search');
- if (!loadTimeData.getBoolean('isGuest')) {
- r.PEOPLE = r.BASIC.createSection('/people', 'people');
- r.SYNC = r.PEOPLE.createChild('/syncSetup');
- r.SYNC_ADVANCED = r.SYNC.createChild('/syncSetup/advanced');
- }
-
- return r;
- }
-
- /**
- * Adds Route objects for each path corresponding to browser-only content.
- * @param {!SettingsRoutes} r Routes to include browser-only content.
- */
- function addBrowserSettingsRoutes(r) {
- const pageVisibility = settings.pageVisibility || {};
-
- // <if expr="not chromeos">
- r.IMPORT_DATA = r.BASIC.createChild('/importData');
- r.IMPORT_DATA.isNavigableDialog = true;
-
- if (pageVisibility.people !== false) {
- r.MANAGE_PROFILE = r.PEOPLE.createChild('/manageProfile');
- }
- // </if>
-
- if (pageVisibility.appearance !== false) {
- r.APPEARANCE = r.BASIC.createSection('/appearance', 'appearance');
- r.FONTS = r.APPEARANCE.createChild('/fonts');
- }
-
- if (pageVisibility.autofill !== false) {
- r.AUTOFILL = r.BASIC.createSection('/autofill', 'autofill');
- r.PASSWORDS = r.AUTOFILL.createChild('/passwords');
- r.PAYMENTS = r.AUTOFILL.createChild('/payments');
- r.ADDRESSES = r.AUTOFILL.createChild('/addresses');
- }
-
- if (pageVisibility.defaultBrowser !== false) {
- r.DEFAULT_BROWSER =
- r.BASIC.createSection('/defaultBrowser', 'defaultBrowser');
- }
-
- r.SEARCH_ENGINES = r.SEARCH.createChild('/searchEngines');
-
- if (pageVisibility.onStartup !== false) {
- r.ON_STARTUP = r.BASIC.createSection('/onStartup', 'onStartup');
- r.STARTUP_PAGES = r.ON_STARTUP.createChild('/startupPages');
- }
-
- // Advanced Routes
- if (pageVisibility.advancedSettings !== false) {
- r.ADVANCED = new Route('/advanced');
-
- r.CLEAR_BROWSER_DATA = r.ADVANCED.createChild('/clearBrowserData');
- r.CLEAR_BROWSER_DATA.isNavigableDialog = true;
-
- if (pageVisibility.privacy !== false) {
- r.PRIVACY = r.ADVANCED.createSection('/privacy', 'privacy');
- r.CERTIFICATES = r.PRIVACY.createChild('/certificates');
- r.SITE_SETTINGS = r.PRIVACY.createChild('/content');
- if (loadTimeData.getBoolean('enableSecurityKeysSubpage')) {
- r.SECURITY_KEYS = r.PRIVACY.createChild('/securityKeys');
- }
- }
-
- r.SITE_SETTINGS_ALL = r.SITE_SETTINGS.createChild('all');
- r.SITE_SETTINGS_SITE_DETAILS =
- r.SITE_SETTINGS_ALL.createChild('/content/siteDetails');
-
- r.SITE_SETTINGS_HANDLERS = r.SITE_SETTINGS.createChild('/handlers');
-
- // TODO(tommycli): Find a way to refactor these repetitive category
- // routes.
- r.SITE_SETTINGS_ADS = r.SITE_SETTINGS.createChild('ads');
- r.SITE_SETTINGS_AUTOMATIC_DOWNLOADS =
- r.SITE_SETTINGS.createChild('automaticDownloads');
- r.SITE_SETTINGS_BACKGROUND_SYNC =
- r.SITE_SETTINGS.createChild('backgroundSync');
- r.SITE_SETTINGS_CAMERA = r.SITE_SETTINGS.createChild('camera');
- r.SITE_SETTINGS_CLIPBOARD = r.SITE_SETTINGS.createChild('clipboard');
- r.SITE_SETTINGS_COOKIES = r.SITE_SETTINGS.createChild('cookies');
- r.SITE_SETTINGS_SITE_DATA =
- r.SITE_SETTINGS_COOKIES.createChild('/siteData');
- r.SITE_SETTINGS_DATA_DETAILS =
- r.SITE_SETTINGS_SITE_DATA.createChild('/cookies/detail');
- r.SITE_SETTINGS_IMAGES = r.SITE_SETTINGS.createChild('images');
- if (loadTimeData.getBoolean('enableInsecureContentContentSetting')) {
- r.SITE_SETTINGS_MIXEDSCRIPT =
- r.SITE_SETTINGS.createChild('insecureContent');
- }
- r.SITE_SETTINGS_JAVASCRIPT = r.SITE_SETTINGS.createChild('javascript');
- r.SITE_SETTINGS_SOUND = r.SITE_SETTINGS.createChild('sound');
- r.SITE_SETTINGS_SENSORS = r.SITE_SETTINGS.createChild('sensors');
- r.SITE_SETTINGS_LOCATION = r.SITE_SETTINGS.createChild('location');
- r.SITE_SETTINGS_MICROPHONE = r.SITE_SETTINGS.createChild('microphone');
- r.SITE_SETTINGS_NOTIFICATIONS =
- r.SITE_SETTINGS.createChild('notifications');
- r.SITE_SETTINGS_FLASH = r.SITE_SETTINGS.createChild('flash');
- r.SITE_SETTINGS_POPUPS = r.SITE_SETTINGS.createChild('popups');
- r.SITE_SETTINGS_UNSANDBOXED_PLUGINS =
- r.SITE_SETTINGS.createChild('unsandboxedPlugins');
- r.SITE_SETTINGS_MIDI_DEVICES = r.SITE_SETTINGS.createChild('midiDevices');
- r.SITE_SETTINGS_USB_DEVICES = r.SITE_SETTINGS.createChild('usbDevices');
- if (loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures')) {
- r.SITE_SETTINGS_SERIAL_PORTS =
- r.SITE_SETTINGS.createChild('serialPorts');
- }
- r.SITE_SETTINGS_ZOOM_LEVELS = r.SITE_SETTINGS.createChild('zoomLevels');
- r.SITE_SETTINGS_PDF_DOCUMENTS =
- r.SITE_SETTINGS.createChild('pdfDocuments');
- r.SITE_SETTINGS_PROTECTED_CONTENT =
- r.SITE_SETTINGS.createChild('protectedContent');
- if (loadTimeData.getBoolean('enablePaymentHandlerContentSetting')) {
- r.SITE_SETTINGS_PAYMENT_HANDLER =
- r.SITE_SETTINGS.createChild('paymentHandler');
- }
- if (loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures')) {
- r.SITE_SETTINGS_BLUETOOTH_SCANNING =
- r.SITE_SETTINGS.createChild('bluetoothScanning');
- }
- if (loadTimeData.getBoolean(
- 'enableNativeFileSystemWriteContentSetting')) {
- r.SITE_SETTINGS_NATIVE_FILE_SYSTEM_WRITE =
- r.SITE_SETTINGS.createChild('filesystem');
- }
-
- r.LANGUAGES = r.ADVANCED.createSection('/languages', 'languages');
- // <if expr="not is_macosx">
- r.EDIT_DICTIONARY = r.LANGUAGES.createChild('/editDictionary');
- // </if>
-
- if (pageVisibility.downloads !== false) {
- r.DOWNLOADS = r.ADVANCED.createSection('/downloads', 'downloads');
- }
-
- r.PRINTING = r.ADVANCED.createSection('/printing', 'printing');
- r.CLOUD_PRINTERS = r.PRINTING.createChild('/cloudPrinters');
-
- r.ACCESSIBILITY = r.ADVANCED.createSection('/accessibility', 'a11y');
-
- // <if expr="chromeos or is_linux">
- if (loadTimeData.getBoolean('enableCaptionSettings')) {
- r.CAPTIONS = r.ACCESSIBILITY.createChild('/captions');
- }
- // </if>
-
- // <if expr="is_win">
- if (loadTimeData.getBoolean('enableCaptionSettings') &&
- !loadTimeData.getBoolean('isWindows10OrNewer')) {
- r.CAPTIONS = r.ACCESSIBILITY.createChild('/captions');
- }
- // </if>
-
- // <if expr="not chromeos">
- r.SYSTEM = r.ADVANCED.createSection('/system', 'system');
- // </if>
-
- if (pageVisibility.reset !== false) {
- r.RESET = r.ADVANCED.createSection('/reset', 'reset');
- r.RESET_DIALOG = r.ADVANCED.createChild('/resetProfileSettings');
- r.RESET_DIALOG.isNavigableDialog = true;
- r.TRIGGERED_RESET_DIALOG =
- r.ADVANCED.createChild('/triggeredResetProfileSettings');
- r.TRIGGERED_RESET_DIALOG.isNavigableDialog = true;
- // <if expr="_google_chrome and is_win">
- r.CHROME_CLEANUP = r.RESET.createChild('/cleanup');
- if (loadTimeData.getBoolean('showIncompatibleApplications')) {
- r.INCOMPATIBLE_APPLICATIONS =
- r.RESET.createChild('/incompatibleApplications');
- }
- // </if>
- }
- }
- }
-
- // <if expr="chromeos">
- /**
- * Adds Route objects for each path corresponding to CrOS-only content.
- * @param {!SettingsRoutes} r Routes to include CrOS-only content.
- */
- function addOSSettingsRoutes(r) {
- r.INTERNET = r.BASIC.createSection('/internet', 'internet');
- r.INTERNET_NETWORKS = r.INTERNET.createChild('/networks');
- r.NETWORK_DETAIL = r.INTERNET.createChild('/networkDetail');
- r.KNOWN_NETWORKS = r.INTERNET.createChild('/knownNetworks');
- r.BLUETOOTH = r.BASIC.createSection('/bluetooth', 'bluetooth');
- r.BLUETOOTH_DEVICES = r.BLUETOOTH.createChild('/bluetoothDevices');
-
- r.DEVICE = r.BASIC.createSection('/device', 'device');
- r.POINTERS = r.DEVICE.createChild('/pointer-overlay');
- r.KEYBOARD = r.DEVICE.createChild('/keyboard-overlay');
- r.STYLUS = r.DEVICE.createChild('/stylus');
- r.DISPLAY = r.DEVICE.createChild('/display');
- r.STORAGE = r.DEVICE.createChild('/storage');
- r.EXTERNAL_STORAGE_PREFERENCES =
- r.DEVICE.createChild('/storage/externalStoragePreferences');
- r.POWER = r.DEVICE.createChild('/power');
-
- // "About" is the only section in About, but we still need to create the
- // route in order to show the subpage on Chrome OS.
- r.ABOUT_ABOUT = r.ABOUT.createSection('/help/about', 'about');
- r.DETAILED_BUILD_INFO = r.ABOUT_ABOUT.createChild('/help/details');
-
- if (!loadTimeData.getBoolean('isGuest')) {
- r.MULTIDEVICE = r.BASIC.createSection('/multidevice', 'multidevice');
- r.MULTIDEVICE_FEATURES =
- r.MULTIDEVICE.createChild('/multidevice/features');
- r.SMART_LOCK =
- r.MULTIDEVICE_FEATURES.createChild('/multidevice/features/smartLock');
-
- r.ACCOUNTS = r.PEOPLE.createChild('/accounts');
- r.ACCOUNT_MANAGER = r.PEOPLE.createChild('/accountManager');
- r.KERBEROS_ACCOUNTS = r.PEOPLE.createChild('/kerberosAccounts');
- r.LOCK_SCREEN = r.PEOPLE.createChild('/lockScreen');
- r.FINGERPRINT = r.LOCK_SCREEN.createChild('/lockScreen/fingerprint');
- }
-
- // Show Android Apps page in the browser if split settings is turned off.
- if (!loadTimeData.getBoolean('isOSSettings') &&
- loadTimeData.getBoolean('showOSSettings') &&
- loadTimeData.valueExists('androidAppsVisible') &&
- loadTimeData.getBoolean('androidAppsVisible')) {
- r.ANDROID_APPS = r.BASIC.createSection('/androidApps', 'androidApps');
- r.ANDROID_APPS_DETAILS =
- r.ANDROID_APPS.createChild('/androidApps/details');
- }
-
- if (loadTimeData.valueExists('showCrostini') &&
- loadTimeData.getBoolean('showCrostini')) {
- r.CROSTINI = r.BASIC.createSection('/crostini', 'crostini');
- r.CROSTINI_DETAILS = r.CROSTINI.createChild('/crostini/details');
- if (loadTimeData.valueExists('showCrostiniExportImport') &&
- loadTimeData.getBoolean('showCrostiniExportImport')) {
- r.CROSTINI_EXPORT_IMPORT =
- r.CROSTINI_DETAILS.createChild('/crostini/exportImport');
- }
- r.CROSTINI_SHARED_PATHS =
- r.CROSTINI_DETAILS.createChild('/crostini/sharedPaths');
- r.CROSTINI_SHARED_USB_DEVICES =
- r.CROSTINI_DETAILS.createChild('/crostini/sharedUsbDevices');
- }
-
- if (loadTimeData.valueExists('showPluginVm') &&
- loadTimeData.getBoolean('showPluginVm')) {
- r.PLUGIN_VM = r.BASIC.createSection('/pluginVm', 'pluginVm');
- r.PLUGIN_VM_DETAILS = r.PLUGIN_VM.createChild('/pluginVm/details');
- r.PLUGIN_VM_SHARED_PATHS =
- r.PLUGIN_VM.createChild('/pluginVm/sharedPaths');
- }
-
- r.GOOGLE_ASSISTANT = r.SEARCH.createChild('/googleAssistant');
-
- // This if/else accounts for sections that were added or refactored in
- // the settings split (crbug.com/950007) and some routes that were created
- // in browser settings conditioned on the pageVisibility constant, which is
- // being decoupled from OS Settings in the split. The 'else' block provides
- // a section-by-section comparison.
- // TODO (crbug.com/967861): Make 'if' block unconditional. Remove 'else'
- // block.
- if (loadTimeData.getBoolean('isOSSettings')) {
- r.ADVANCED = new Route('/advanced');
-
- r.PRIVACY = r.ADVANCED.createSection('/privacy', 'privacy');
-
- // Languages and input
- r.LANGUAGES = r.ADVANCED.createSection('/languages', 'languages');
- r.LANGUAGES_DETAILS = r.LANGUAGES.createChild('/languages/details');
- r.INPUT_METHODS =
- r.LANGUAGES_DETAILS.createChild('/languages/inputMethods');
-
- r.PRINTING = r.ADVANCED.createSection('/printing', 'printing');
-
- r.ACCESSIBILITY = r.ADVANCED.createSection('/accessibility', 'a11y');
-
- if (!loadTimeData.getBoolean('isGuest')) {
- // Personalization
- r.PERSONALIZATION =
- r.BASIC.createSection('/personalization', 'personalization');
- r.CHANGE_PICTURE = r.PERSONALIZATION.createChild('/changePicture');
-
- // Files (analogous to Downloads)
- r.FILES = r.ADVANCED.createSection('/files', 'files');
- r.SMB_SHARES = r.FILES.createChild('/smbShares');
- }
-
- // Reset
- if (loadTimeData.valueExists('allowPowerwash') &&
- loadTimeData.getBoolean('allowPowerwash')) {
- r.RESET = r.ADVANCED.createSection('/reset', 'reset');
- }
-
- const showAppManagement = loadTimeData.valueExists('showAppManagement') &&
- loadTimeData.getBoolean('showAppManagement');
- const showAndroidApps = loadTimeData.valueExists('androidAppsVisible') &&
- loadTimeData.getBoolean('androidAppsVisible');
- // Apps
- if (showAppManagement || showAndroidApps) {
- r.APPS = r.BASIC.createSection('/apps', 'apps');
- if (showAppManagement) {
- r.APP_MANAGEMENT = r.APPS.createChild('/app-management');
- r.APP_MANAGEMENT_DETAIL =
- r.APP_MANAGEMENT.createChild('/app-management/detail');
- }
- if (showAndroidApps) {
- r.ANDROID_APPS_DETAILS = r.APPS.createChild('/androidAppsDetails');
- }
- }
- } else {
- assert(r.ADVANCED, 'ADVANCED route should exist');
-
- assert(r.PRIVACY, 'PRIVACY route should exist');
-
- // Languages and input
- assert(r.LANGUAGES, 'LANGUAGES route should exist');
- r.INPUT_METHODS = r.LANGUAGES.createChild('/inputMethods');
-
- assert(r.PRINTING, 'PRINTING route should exist');
-
- assert(r.ACCESSIBILITY, 'ACCESSIBILITY route should exist');
-
- if (!loadTimeData.getBoolean('isGuest')) {
- // People
- r.CHANGE_PICTURE = r.PEOPLE.createChild('/changePicture');
-
- // Downloads (analogous to Files)
- assert(r.DOWNLOADS, 'DOWNLOADS route should exist');
- r.SMB_SHARES = r.DOWNLOADS.createChild('/smbShares');
-
- // Reset
- assert(r.RESET, 'RESET route should exist');
- }
-
- assert(!r.APPS, 'APPS route should not exist');
- }
-
- r.DATETIME = r.ADVANCED.createSection('/dateTime', 'dateTime');
- r.DATETIME_TIMEZONE_SUBPAGE = r.DATETIME.createChild('/dateTime/timeZone');
-
- r.CUPS_PRINTERS = r.PRINTING.createChild('/cupsPrinters');
-
- r.MANAGE_ACCESSIBILITY =
- r.ACCESSIBILITY.createChild('/manageAccessibility');
- if (loadTimeData.getBoolean('showExperimentalAccessibilitySwitchAccess')) {
- r.MANAGE_SWITCH_ACCESS_SETTINGS = r.MANAGE_ACCESSIBILITY.createChild(
- '/manageAccessibility/switchAccess');
- }
- r.MANAGE_TTS_SETTINGS =
- r.MANAGE_ACCESSIBILITY.createChild('/manageAccessibility/tts');
-
- r.MANAGE_CAPTION_SETTINGS =
- r.MANAGE_ACCESSIBILITY.createChild('/manageAccessibility/captions');
- }
- // </if>
-
- class Router {
- /** @param {!SettingsRoutes} availableRoutes */
- constructor(availableRoutes) {
- /**
- * List of available routes. This is populated taking into account current
- * state (like guest mode).
- * @private {!SettingsRoutes}
- */
- this.routes_ = availableRoutes;
-
- /**
- * The current active route. This updated is only by settings.navigateTo
- * or settings.initializeRouteFromUrl.
- * @type {!settings.Route}
- */
- this.currentRoute = /** @type {!settings.Route} */ (this.routes_.BASIC);
-
- /**
- * The current query parameters. This is updated only by
- * settings.navigateTo or settings.initializeRouteFromUrl.
- * @private {!URLSearchParams}
- */
- this.currentQueryParameters_ = new URLSearchParams();
-
- /** @private {boolean} */
- this.wasLastRouteChangePopstate_ = false;
-
- /** @private {boolean}*/
- this.initializeRouteFromUrlCalled_ = false;
- }
-
- /** @return {settings.Route} */
- getRoute(routeName) {
- return this.routes_[routeName];
- }
-
- /** @return {!SettingsRoutes} */
- getRoutes() {
- return this.routes_;
- }
-
- /**
- * Helper function to set the current route and notify all observers.
- * @param {!settings.Route} route
- * @param {!URLSearchParams} queryParameters
- * @param {boolean} isPopstate
- */
- setCurrentRoute(route, queryParameters, isPopstate) {
- this.recordMetrics(route.path);
-
- const oldRoute = this.currentRoute;
- this.currentRoute = route;
- this.currentQueryParameters_ = queryParameters;
- this.wasLastRouteChangePopstate_ = isPopstate;
- new Set(routeObservers).forEach((observer) => {
- observer.currentRouteChanged(this.currentRoute, oldRoute);
- });
- }
-
- /** @return {!settings.Route} */
- getCurrentRoute() {
- return this.currentRoute;
- }
-
- /** @return {!URLSearchParams} */
- getQueryParameters() {
- return new URLSearchParams(
- this.currentQueryParameters_); // Defensive copy.
- }
-
- /** @return {boolean} */
- lastRouteChangeWasPopstate() {
- return this.wasLastRouteChangePopstate_;
- }
-
- /**
- * @param {string} path
- * @return {?settings.Route} The matching canonical route, or null if none
- * matches.
- */
- getRouteForPath(path) {
- // Allow trailing slash in paths.
- const canonicalPath = path.replace(CANONICAL_PATH_REGEX, '$1$2');
-
- // TODO(tommycli): Use Object.values once Closure compilation supports it.
- const matchingKey =
- Object.keys(this.routes_)
- .find((key) => this.routes_[key].path == canonicalPath);
-
- return matchingKey ? this.routes_[matchingKey] : null;
- }
-
- /**
- * Navigates to a canonical route and pushes a new history entry.
- * @param {!settings.Route} route
- * @param {URLSearchParams=} opt_dynamicParameters Navigations to the same
- * URL parameters in a different order will still push to history.
- * @param {boolean=} opt_removeSearch Whether to strip the 'search' URL
- * parameter during navigation. Defaults to false.
- */
- navigateTo(route, opt_dynamicParameters, opt_removeSearch) {
- // The ADVANCED route only serves as a parent of subpages, and should not
- // be possible to navigate to it directly.
- if (route == this.routes_.ADVANCED) {
- route = /** @type {!settings.Route} */ (this.routes_.BASIC);
- }
-
- const params = opt_dynamicParameters || new URLSearchParams();
- const removeSearch = !!opt_removeSearch;
-
- const oldSearchParam = this.getQueryParameters().get('search') || '';
- const newSearchParam = params.get('search') || '';
-
- if (!removeSearch && oldSearchParam && !newSearchParam) {
- params.append('search', oldSearchParam);
- }
-
- let url = route.path;
- const queryString = params.toString();
- if (queryString) {
- url += '?' + queryString;
- }
-
- // History serializes the state, so we don't push the actual route object.
- window.history.pushState(this.currentRoute.path, '', url);
- this.setCurrentRoute(route, params, false);
- }
-
- /**
- * Navigates to the previous route if it has an equal or lesser depth.
- * If there is no previous route in history meeting those requirements,
- * this navigates to the immediate parent. This will never exit Settings.
- */
- navigateToPreviousRoute() {
- const previousRoute = window.history.state &&
- assert(this.getRouteForPath(
- /** @type {string} */ (window.history.state)));
-
- if (previousRoute && previousRoute.depth <= this.currentRoute.depth) {
- window.history.back();
- } else {
- this.navigateTo(
- this.currentRoute.parent ||
- /** @type {!settings.Route} */ (this.routes_.BASIC));
- }
- }
-
- /**
- * Initialize the route and query params from the URL.
- */
- initializeRouteFromUrl() {
- assert(!this.initializeRouteFromUrlCalled_);
- this.initializeRouteFromUrlCalled_ = true;
-
- const route = this.getRouteForPath(window.location.pathname);
-
- // Record all correct paths entered on the settings page, and
- // as all incorrect paths are routed to the main settings page,
- // record all incorrect paths as hitting the main settings page.
- this.recordMetrics(route ? route.path : this.routes_.BASIC.path);
-
- // Never allow direct navigation to ADVANCED.
- if (route && route != this.routes_.ADVANCED) {
- this.currentRoute = route;
- this.currentQueryParameters_ =
- new URLSearchParams(window.location.search);
- } else {
- window.history.replaceState(undefined, '', this.routes_.BASIC.path);
- }
- }
-
- /**
- * Make a UMA note about visiting this URL path.
- * @param {string} urlPath The url path (only).
- */
- recordMetrics(urlPath) {
- assert(!urlPath.startsWith('chrome://'));
- assert(!urlPath.startsWith('settings'));
- assert(urlPath.startsWith('/'));
- assert(!urlPath.match(/\?/g));
- chrome.metricsPrivate.recordSparseHashable(
- 'WebUI.Settings.PathVisited', urlPath);
- }
-
- resetRouteForTesting() {
- this.initializeRouteFromUrlCalled_ = false;
- this.wasLastRouteChangePopstate_ = false;
- this.currentRoute = /** @type {!settings.Route} */ (this.routes_.BASIC);
- this.currentQueryParameters_ = new URLSearchParams();
- }
- }
-
- /**
- * @return {!settings.Router} A router with at least those routes common to OS
- * and browser settings. If the window is not in OS settings (based on
- * loadTimeData) then browser specific routes are added. If the window is
- * OS settings or if Chrome OS is using a consolidated settings page for
- * OS and browser settings then OS specific routes are added.
- */
- function buildRouter() {
- const availableRoutes = computeCommonRoutes();
- const isOSSettings = loadTimeData.valueExists('isOSSettings') &&
- loadTimeData.getBoolean('isOSSettings');
- if (!isOSSettings) {
- addBrowserSettingsRoutes(availableRoutes);
- }
-
- // <if expr="chromeos">
- const showOSSettings = loadTimeData.valueExists('showOSSettings') &&
- loadTimeData.getBoolean('showOSSettings');
- if (isOSSettings || showOSSettings) {
- addOSSettingsRoutes(availableRoutes);
- }
- // </if>
- return new Router(availableRoutes);
- }
- const routerInstance = buildRouter();
-
- const routeObservers = new Set();
-
- /** @polymerBehavior */
- const RouteObserverBehavior = {
- /** @override */
- attached: function() {
- assert(!routeObservers.has(this));
- routeObservers.add(this);
-
- // Emulating Polymer data bindings, the observer is called when the
- // element starts observing the route.
- this.currentRouteChanged(routerInstance.currentRoute, undefined);
- },
-
- /** @override */
- detached: function() {
- assert(routeObservers.delete(this));
- },
-
- /**
- * @param {!settings.Route|undefined} opt_newRoute
- * @param {!settings.Route|undefined} opt_oldRoute
- */
- currentRouteChanged: function(opt_newRoute, opt_oldRoute) {
- assertNotReached();
- },
- };
-
- /**
- * Regular expression that captures the leading slash, the content and the
- * trailing slash in three different groups.
- * @type {!RegExp}
- */
- const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
-
- window.addEventListener('popstate', function(event) {
- // On pop state, do not push the state onto the window.history again.
- routerInstance.setCurrentRoute(
- /** @type {!settings.Route} */ (
- routerInstance.getRouteForPath(window.location.pathname) ||
- routerInstance.getRoutes().BASIC),
- new URLSearchParams(window.location.search), true);
- });
-
- // TODO(dpapad): Change to 'get routes() {}' in export when we fix a bug in
- // ChromePass that limits the syntax of what can be returned from cr.define().
- const routes = routerInstance.getRoutes();
-
- // TODO(dpapad): Stop exposing all those methods directly on settings.*,
- // and instead update all clients to use the singleton instance directly
- const getCurrentRoute = routerInstance.getCurrentRoute.bind(routerInstance);
- const getRouteForPath = routerInstance.getRouteForPath.bind(routerInstance);
- const initializeRouteFromUrl =
- routerInstance.initializeRouteFromUrl.bind(routerInstance);
- const resetRouteForTesting =
- routerInstance.resetRouteForTesting.bind(routerInstance);
- const getQueryParameters =
- routerInstance.getQueryParameters.bind(routerInstance);
- const lastRouteChangeWasPopstate =
- routerInstance.lastRouteChangeWasPopstate.bind(routerInstance);
- const navigateTo = routerInstance.navigateTo.bind(routerInstance);
- const navigateToPreviousRoute =
- routerInstance.navigateToPreviousRoute.bind(routerInstance);
-
- return {
- Route: Route, // The Route class definition.
- Router: Router, // The Router class definition.
- router: routerInstance, // the singleton.
- buildRouterForTesting: buildRouter,
- routes: routes,
- RouteObserverBehavior: RouteObserverBehavior,
- getRouteForPath: getRouteForPath,
- initializeRouteFromUrl: initializeRouteFromUrl,
- resetRouteForTesting: resetRouteForTesting,
- getCurrentRoute: getCurrentRoute,
- getQueryParameters: getQueryParameters,
- lastRouteChangeWasPopstate: lastRouteChangeWasPopstate,
- navigateTo: navigateTo,
- navigateToPreviousRoute: navigateToPreviousRoute,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.html b/chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.html
deleted file mode 100644
index fac0773366c..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="../extension_control_browser_proxy.html">
-<link rel="import" href="search_engine_entry_css.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-
-<dom-module id="settings-omnibox-extension-entry">
- <template>
- <style include="settings-shared search-engine-entry">
- .name-column {
- align-items: center;
- display: flex;
- flex: 3;
- word-break: break-word;
- }
-
- .keyword-column {
- flex: 7;
- }
- </style>
- <div class="list-item" focus-row-container>
- <div class="name-column">
- <site-favicon favicon-url="[[engine.iconURL]]"></site-favicon>
- <span>[[engine.displayName]]</span>
- </div>
- <div class="keyword-column">[[engine.keyword]]</div>
- <cr-icon-button class="icon-more-vert" on-click="onDotsTap_"
- title="$i18n{moreActions}" focus-row-control focus-type="menu">
- </cr-icon-button>
- <cr-action-menu>
- <button class="dropdown-item" on-click="onManageTap_"
- id="manage">
- $i18n{searchEnginesManageExtension}
- </button>
- <button class="dropdown-item" on-click="onDisableTap_"
- id="disable">
- $i18n{disable}
- </button>
- </cr-action-menu>
- </div>
- </template>
- <script src="omnibox_extension_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js b/chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js
deleted file mode 100644
index a89c03f05e1..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-omnibox-extension-entry' is a component for showing
- * an omnibox extension with its name and keyword.
- */
-Polymer({
- is: 'settings-omnibox-extension-entry',
-
- properties: {
- /** @type {!SearchEngine} */
- engine: Object,
- },
-
- behaviors: [cr.ui.FocusRowBehavior],
-
- /** @private {?settings.ExtensionControlBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ =
- settings.ExtensionControlBrowserProxyImpl.getInstance();
- },
-
- /** @private */
- onManageTap_: function() {
- this.closePopupMenu_();
- this.browserProxy_.manageExtension(this.engine.extension.id);
- },
-
- /** @private */
- onDisableTap_: function() {
- this.closePopupMenu_();
- this.browserProxy_.disableExtension(this.engine.extension.id);
- },
-
- /** @private */
- closePopupMenu_: function() {
- this.$$('cr-action-menu').close();
- },
-
- /** @private */
- onDotsTap_: function() {
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
- .showAt(assert(this.$$('cr-icon-button')), {
- anchorAlignmentY: AnchorAlignment.AFTER_END,
- });
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.html
deleted file mode 100644
index a71de9853fe..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="search_engines_browser_proxy.html">
-
-<dom-module id="settings-search-engine-dialog">
- <template>
- <style include="settings-shared">
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">[[dialogTitle_]]</div>
- <div slot="body" spellcheck="false">
- <cr-input id="searchEngine"
- label="$i18n{searchEnginesSearchEngine}"
- error-message="$i18n{notValid}"
- value="{{searchEngine_}}" on-input="validate_"
- autofocus>
- </cr-input>
- <cr-input id="keyword"
- label="$i18n{searchEnginesKeyword}"
- error-message="$i18n{notValid}"
- value="{{keyword_}}" on-focus="validate_" on-input="validate_">
- </cr-input>
- <cr-input id="queryUrl"
- label="$i18n{searchEnginesQueryURLExplanation}"
- error-message="$i18n{notValid}"
- value="{{queryUrl_}}" on-focus="validate_" on-input="validate_"
- disabled$="[[model.urlLocked]]">
- </cr-input>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="cancel_" id="cancel">
- $i18n{cancel}</cr-button>
- <cr-button id="actionButton" class="action-button"
- on-click="onActionButtonTap_">
- [[actionButtonText_]]
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="search_engine_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js
deleted file mode 100644
index 1ba1e5b6c8a..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-search-engine-dialog' is a component for adding
- * or editing a search engine entry.
- */
-Polymer({
- is: 'settings-search-engine-dialog',
-
- behaviors: [WebUIListenerBehavior],
-
- properties: {
- /**
- * The search engine to be edited. If not populated a new search engine
- * should be added.
- * @type {?SearchEngine}
- */
- model: Object,
-
- /** @private {string} */
- searchEngine_: String,
-
- /** @private {string} */
- keyword_: String,
-
- /** @private {string} */
- queryUrl_: String,
-
- /** @private {string} */
- dialogTitle_: String,
-
- /** @private {string} */
- actionButtonText_: String,
- },
-
- /** @private {settings.SearchEnginesBrowserProxy} */
- browserProxy_: null,
-
- /**
- * The |modelIndex| to use when a new search engine is added. Must match with
- * kNewSearchEngineIndex constant specified at
- * chrome/browser/ui/webui/settings/search_engines_handler.cc
- * @type {number}
- */
- DEFAULT_MODEL_INDEX: -1,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.SearchEnginesBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- if (this.model) {
- this.dialogTitle_ =
- loadTimeData.getString('searchEnginesEditSearchEngine');
- this.actionButtonText_ = loadTimeData.getString('save');
-
- // If editing an existing search engine, pre-populate the input fields.
- this.searchEngine_ = this.model.name;
- this.keyword_ = this.model.keyword;
- this.queryUrl_ = this.model.url;
- } else {
- this.dialogTitle_ =
- loadTimeData.getString('searchEnginesAddSearchEngine');
- this.actionButtonText_ = loadTimeData.getString('add');
- }
-
- this.addEventListener('cancel', () => {
- this.browserProxy_.searchEngineEditCancelled();
- });
-
- this.addWebUIListener(
- 'search-engines-changed', this.enginesChanged_.bind(this));
- },
-
- /** @override */
- attached: function() {
- this.async(this.updateActionButtonState_.bind(this));
- this.browserProxy_.searchEngineEditStarted(
- this.model ? this.model.modelIndex : this.DEFAULT_MODEL_INDEX);
- this.$.dialog.showModal();
- },
-
- /**
- * @param {!SearchEnginesInfo} searchEnginesInfo
- * @private
- */
- enginesChanged_: function(searchEnginesInfo) {
- if (this.model) {
- const engineWasRemoved = ['defaults', 'others', 'extensions'].every(
- engineType =>
- searchEnginesInfo[engineType].every(e => e.id != this.model.id));
- if (engineWasRemoved) {
- this.cancel_();
- return;
- }
- }
-
- [this.$.searchEngine, this.$.keyword, this.$.queryUrl].forEach(
- element => this.validateElement_(element));
- },
-
- /** @private */
- cancel_: function() {
- this.$.dialog.cancel();
- },
-
- /** @private */
- onActionButtonTap_: function() {
- this.browserProxy_.searchEngineEditCompleted(
- this.searchEngine_, this.keyword_, this.queryUrl_);
- this.$.dialog.close();
- },
-
- /**
- * @param {!Element} inputElement
- * @private
- */
- validateElement_: function(inputElement) {
- // If element is empty, disable the action button, but don't show the red
- // invalid message.
- if (inputElement.value == '') {
- inputElement.invalid = false;
- this.updateActionButtonState_();
- return;
- }
-
- this.browserProxy_
- .validateSearchEngineInput(inputElement.id, inputElement.value)
- .then(isValid => {
- inputElement.invalid = !isValid;
- this.updateActionButtonState_();
- });
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- validate_: function(event) {
- const inputElement = /** @type {!Element} */ (event.target);
- this.validateElement_(inputElement);
- },
-
- /** @private */
- updateActionButtonState_: function() {
- const allValid = [
- this.$.searchEngine, this.$.keyword, this.$.queryUrl
- ].every(function(inputElement) {
- return !inputElement.invalid && inputElement.value.length > 0;
- });
- this.$.actionButton.disabled = !allValid;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html
deleted file mode 100644
index 4469b4298cc..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="search_engine_entry_css.html">
-<link rel="import" href="search_engines_browser_proxy.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-
-<dom-module id="settings-search-engine-entry">
- <template>
- <style include="settings-shared search-engine-entry">
- :host([is-default]) .list-item {
- font-weight: 500;
- }
-
- #name-column {
- align-items: center;
- display: flex;
- }
-
- #name-column,
- #keyword-column {
- flex: 3;
- word-break: break-word;
- }
-
- #keyword-column > div {
- margin-inline-end: 8px;
- }
-
- #url-column {
- flex: 4;
- }
- </style>
-
- <div class="list-item" focus-row-container>
- <div id="name-column">
- <site-favicon
- favicon-url="[[engine.iconURL]]"
- url="[[engine.url]]">
- </site-favicon>
- <div>[[engine.displayName]]</div>
- </div>
- <div id="keyword-column"><div>[[engine.keyword]]</div></div>
- <div id="url-column" class="text-elide">[[engine.url]]</div>
- <cr-icon-button class="icon-more-vert" on-click="onDotsTap_"
- title="$i18n{moreActions}" focus-row-control
- focus-type="cr-menu-button"></cr-icon-button>
- <cr-action-menu>
- <button class="dropdown-item" on-click="onMakeDefaultTap_"
- hidden$="[[!engine.canBeDefault]]" id="makeDefault">
- $i18n{searchEnginesMakeDefault}
- </button>
- <button class="dropdown-item" on-click="onEditTap_"
- hidden$="[[!engine.canBeEdited]]" id="edit">
- $i18n{edit}
- </button>
- <button class="dropdown-item" on-click="onDeleteTap_"
- hidden$="[[!engine.canBeRemoved]]" id="delete">
- $i18n{searchEnginesRemoveFromList}
- </button>
- </cr-action-menu>
- </div>
- <template is="dom-if" if="[[engine.extension]]">
- <extension-controlled-indicator
- extension-id="[[engine.extension.id]]"
- extension-name="[[engine.extension.name]]"
- extension-can-be-disabled="[[engine.extension.canBeDisabled]]">
- </extension-controlled-indicator>
- </template>
- </template>
- <script src="search_engine_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
deleted file mode 100644
index 998b42503c9..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-search-engine-entry' is a component for showing a
- * search engine with its name, domain and query URL.
- */
-Polymer({
- is: 'settings-search-engine-entry',
-
- behaviors: [cr.ui.FocusRowBehavior],
-
- properties: {
- /** @type {!SearchEngine} */
- engine: Object,
-
- /** @type {boolean} */
- isDefault: {
- reflectToAttribute: true,
- type: Boolean,
- computed: 'computeIsDefault_(engine)'
- },
-
- /** @private {boolean} */
- showDots_: {
- reflectToAttribute: true,
- type: Boolean,
- computed: 'computeShowDots_(engine.canBeDefault,' +
- 'engine.canBeEdited,' +
- 'engine.canBeRemoved)',
- },
- },
-
- /** @private {settings.SearchEnginesBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.SearchEnginesBrowserProxyImpl.getInstance();
- },
-
- /** @private */
- closePopupMenu_: function() {
- this.$$('cr-action-menu').close();
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeIsDefault_: function() {
- return this.engine.default;
- },
-
- /**
- * @param {boolean} canBeDefault
- * @param {boolean} canBeEdited
- * @param {boolean} canBeRemoved
- * @return {boolean} Whether to show the dots menu.
- * @private
- */
- computeShowDots_: function(canBeDefault, canBeEdited, canBeRemoved) {
- return canBeDefault || canBeEdited || canBeRemoved;
- },
-
- /** @private */
- onDeleteTap_: function() {
- this.browserProxy_.removeSearchEngine(this.engine.modelIndex);
- this.closePopupMenu_();
- },
-
- /** @private */
- onDotsTap_: function() {
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
- .showAt(assert(this.$$('cr-icon-button')), {
- anchorAlignmentY: AnchorAlignment.AFTER_END,
- });
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onEditTap_: function(e) {
- e.preventDefault();
- this.closePopupMenu_();
- this.fire('edit-search-engine', {
- engine: this.engine,
- anchorElement: assert(this.$$('cr-icon-button')),
- });
- },
-
- /** @private */
- onMakeDefaultTap_: function() {
- this.closePopupMenu_();
- this.browserProxy_.setDefaultSearchEngine(this.engine.modelIndex);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.html
deleted file mode 100644
index 3c01e83aebf..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dom-module id="search-engine-entry">
- <template>
- <style>
- site-favicon {
- margin-inline-end: 8px;
- min-width: 16px;
- }
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.html
deleted file mode 100644
index 89d043647fc..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="search_engines_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js
deleted file mode 100644
index d8a275ec6ec..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Manage search engines" section
- * to interact with the browser.
- */
-
-/**
- * @typedef {{canBeDefault: boolean,
- * canBeEdited: boolean,
- * canBeRemoved: boolean,
- * default: boolean,
- * displayName: string,
- * extension: ({id: string,
- * name: string,
- * canBeDisabled: boolean,
- * icon: string}|undefined),
- * iconURL: (string|undefined),
- * id: number,
- * isOmniboxExtension: boolean,
- * keyword: string,
- * modelIndex: number,
- * name: string,
- * url: string,
- * urlLocked: boolean}}
- * @see chrome/browser/ui/webui/settings/search_engine_manager_handler.cc
- */
-let SearchEngine;
-
-/**
- * @typedef {{
- * defaults: !Array<!SearchEngine>,
- * others: !Array<!SearchEngine>,
- * extensions: !Array<!SearchEngine>
- * }}
- */
-let SearchEnginesInfo;
-
-cr.define('settings', function() {
- /** @interface */
- class SearchEnginesBrowserProxy {
- /** @param {number} modelIndex */
- setDefaultSearchEngine(modelIndex) {}
-
- /** @param {number} modelIndex */
- removeSearchEngine(modelIndex) {}
-
- /** @param {number} modelIndex */
- searchEngineEditStarted(modelIndex) {}
-
- searchEngineEditCancelled() {}
-
- /**
- * @param {string} searchEngine
- * @param {string} keyword
- * @param {string} queryUrl
- */
- searchEngineEditCompleted(searchEngine, keyword, queryUrl) {}
-
- /** @return {!Promise<!SearchEnginesInfo>} */
- getSearchEnginesList() {}
-
- /**
- * @param {string} fieldName
- * @param {string} fieldValue
- * @return {!Promise<boolean>}
- */
- validateSearchEngineInput(fieldName, fieldValue) {}
- }
-
- /**
- * @implements {settings.SearchEnginesBrowserProxy}
- */
- class SearchEnginesBrowserProxyImpl {
- /** @override */
- setDefaultSearchEngine(modelIndex) {
- chrome.send('setDefaultSearchEngine', [modelIndex]);
- }
-
- /** @override */
- removeSearchEngine(modelIndex) {
- chrome.send('removeSearchEngine', [modelIndex]);
- }
-
- /** @override */
- searchEngineEditStarted(modelIndex) {
- chrome.send('searchEngineEditStarted', [modelIndex]);
- }
-
- /** @override */
- searchEngineEditCancelled() {
- chrome.send('searchEngineEditCancelled');
- }
-
- /** @override */
- searchEngineEditCompleted(searchEngine, keyword, queryUrl) {
- chrome.send('searchEngineEditCompleted', [
- searchEngine,
- keyword,
- queryUrl,
- ]);
- }
-
- /** @override */
- getSearchEnginesList() {
- return cr.sendWithPromise('getSearchEnginesList');
- }
-
- /** @override */
- validateSearchEngineInput(fieldName, fieldValue) {
- return cr.sendWithPromise(
- 'validateSearchEngineInput', fieldName, fieldValue);
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(SearchEnginesBrowserProxyImpl);
-
- return {
- SearchEnginesBrowserProxy: SearchEnginesBrowserProxy,
- SearchEnginesBrowserProxyImpl: SearchEnginesBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.html
deleted file mode 100644
index 78feab02378..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="search_engine_entry.html">
-
-<dom-module id="settings-search-engines-list">
- <template>
- <style include="settings-shared">
- #headers {
- display: flex;
- padding: 10px 0;
- }
-
- #headers .name,
- #headers .keyword {
- flex: 3;
- }
-
- #headers .url {
- flex: 4;
- }
-
- #outer {
- @apply --settings-list-frame-padding;
- }
-
- settings-search-engine-entry {
- border-top: var(--cr-separator-line);
- }
-
- :host([fixed-height]) #container {
- /* Max items we show before scrolling is 6. Adding a 7th item to the
- * list will add a scroll bar to the container. */
- max-height: calc((var(--settings-row-min-height) +
- var(--cr-separator-height)) * 6);
- }
-
- .icon-placeholder {
- margin-inline-end: 0;
- margin-inline-start: var(--cr-icon-button-margin-start);
- width: var(--cr-icon-ripple-size);
- }
- </style>
- <div id="outer">
- <div id="headers" class="column-header">
- <div class="name">$i18n{searchEnginesSearchEngine}</div>
- <div class="keyword">$i18n{searchEnginesKeyword}</div>
- <div class="url">$i18n{searchEnginesQueryURL}</div>
- <div class="icon-placeholder"></div>
- </div>
- <div id="container" class="scroll-container"
- scrollable$="[[fixedHeight]]">
- <iron-list items="[[engines]]" scroll-target="[[scrollTarget]]"
- scroll-offset="[[scrollOffset]]" preserve-focus risk-selection>
- <template>
- <settings-search-engine-entry engine="[[item]]"
- tabindex$="[[tabIndex]]" iron-list-tab-index="[[tabIndex]]"
- last-focused="{{lastFocused_}}" list-blurred="{{listBlurred_}}">
- </settings-search-engine-entry>
- </template>
- </iron-list>
- </div>
- </div>
- </template>
- <script src="search_engines_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.js
deleted file mode 100644
index 12911500d9b..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_list.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-search-engines-list' is a component for showing a
- * list of search engines.
- */
-Polymer({
- is: 'settings-search-engines-list',
-
- properties: {
- /** @type {!Array<!SearchEngine>} */
- engines: Array,
-
- /**
- * The scroll target that this list should use.
- * @type {?HTMLElement}
- */
- scrollTarget: Object,
-
- /** Used to fix scrolling glitch when list is not top most element. */
- scrollOffset: Number,
-
- /** @private {Object}*/
- lastFocused_: Object,
-
- /** @private */
- listBlurred_: Boolean,
-
- fixedHeight: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html
deleted file mode 100644
index 6dc16d5cb0c..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html
+++ /dev/null
@@ -1,88 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="search_engines_browser_proxy.html">
-<link rel="import" href="search_engine_dialog.html">
-<link rel="import" href="search_engines_list.html">
-<link rel="import" href="omnibox_extension_entry.html">
-<link rel="import" href="../global_scroll_target_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-search-engines-page">
- <template>
- <style include="settings-shared action-link">
- .extension-engines,
- #noOtherEngines,
- .no-search-results {
- @apply --settings-list-frame-padding;
- }
-
- settings-omnibox-extension-entry {
- border-top: var(--cr-separator-line);
- }
- </style>
- <div class="settings-box first">
- <h2>$i18n{searchEnginesDefault}</h2>
- </div>
- <div class="no-search-results" hidden="[[matchingDefaultEngines_.length]]">
- $i18n{searchNoResults}
- </div>
- <settings-search-engines-list fixed-height
- hidden="[[!matchingDefaultEngines_.length]]"
- engines="[[matchingDefaultEngines_]]">
- </settings-search-engines-list>
- <template is="dom-if" if="[[showDialog_]]" restamp>
- <settings-search-engine-dialog model="[[dialogModel_]]"
- on-close="onCloseDialog_">
- </settings-search-engine-dialog>
- </template>
-
- <div class="settings-box first">
- <h2 class="start">$i18n{searchEnginesOther}</h2>
- <cr-button class="secondary-button header-aligned-button"
- on-click="onAddSearchEngineTap_" id="addSearchEngine">
- $i18n{add}
- </cr-button>
- </div>
- <div id="noOtherEngines" hidden="[[otherEngines.length]]">
- $i18n{searchEnginesNoOtherEngines}
- </div>
- <div class="no-search-results"
- hidden="[[!showNoResultsMessage_(
- otherEngines, matchingOtherEngines_)]]">
- $i18n{searchNoResults}
- </div>
- <settings-search-engines-list id="otherEngines"
- hidden="[[!matchingOtherEngines_.length]]"
- engines="[[matchingOtherEngines_]]"
- scroll-target="[[subpageScrollTarget]]">
- </settings-search-engines-list>
- <template is="dom-if" if="[[showExtensionsList_]]">
- <div class="settings-box first">
- <h2>$i18n{searchEnginesExtension}</h2>
- </div>
- <div class="no-search-results" hidden="[[matchingExtensions_.length]]">
- $i18n{searchNoResults}
- </div>
- <iron-list id="extensions" class="extension-engines"
- items="[[matchingExtensions_]]" preserve-focus risk-selection>
- <template>
- <settings-omnibox-extension-entry engine="[[item]]"
- tabindex$="[[tabIndex]]" iron-list-tab-index="[[tabIndex]]"
- last-focused="{{omniboxExtensionlastFocused_}}"
- list-blurred="{{omniboxExtensionListBlurred_}}">
- </settings-omnibox-extension-entry>
- </template>
- </iron-list>
- </template>
- </template>
- <script src="search_engines_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
deleted file mode 100644
index 84e5d33bad8..00000000000
--- a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-search-engines-page' is the settings page
- * containing search engines settings.
- */
-Polymer({
- is: 'settings-search-engines-page',
-
- behaviors: [settings.GlobalScrollTargetBehavior, WebUIListenerBehavior],
-
- properties: {
- /** @type {!Array<!SearchEngine>} */
- defaultEngines: Array,
-
- /** @type {!Array<!SearchEngine>} */
- otherEngines: Array,
-
- /** @type {!Array<!SearchEngine>} */
- extensions: Array,
-
- /**
- * Needed by GlobalScrollTargetBehavior.
- * @override
- */
- subpageRoute: {
- type: Object,
- value: settings.routes.SEARCH_ENGINES,
- },
-
- /** @private {boolean} */
- showExtensionsList_: {
- type: Boolean,
- computed: 'computeShowExtensionsList_(extensions)',
- },
-
- /** Filters out all search engines that do not match. */
- filter: {
- type: String,
- value: '',
- },
-
- /** @private {!Array<!SearchEngine>} */
- matchingDefaultEngines_: {
- type: Array,
- computed: 'computeMatchingEngines_(defaultEngines, filter)',
- },
-
- /** @private {!Array<!SearchEngine>} */
- matchingOtherEngines_: {
- type: Array,
- computed: 'computeMatchingEngines_(otherEngines, filter)',
- },
-
- /** @private {!Array<!SearchEngine>} */
- matchingExtensions_: {
- type: Array,
- computed: 'computeMatchingEngines_(extensions, filter)',
- },
-
- /** @private {HTMLElement} */
- omniboxExtensionlastFocused_: Object,
-
- /** @private {boolean} */
- omniboxExtensionListBlurred_: Boolean,
-
- /** @private {?SearchEngine} */
- dialogModel_: {
- type: Object,
- value: null,
- },
-
- /** @private {?HTMLElement} */
- dialogAnchorElement_: {
- type: Object,
- value: null,
- },
-
- /** @private */
- showDialog_: {
- type: Boolean,
- value: false,
- },
- },
-
- // Since the iron-list for extensions is enclosed in a dom-if, observe both
- // |extensions| and |showExtensionsList_|.
- observers: ['extensionsChanged_(extensions, showExtensionsList_)'],
-
- listeners: {
- 'edit-search-engine': 'onEditSearchEngine_',
- },
-
- /** @override */
- ready: function() {
- settings.SearchEnginesBrowserProxyImpl.getInstance()
- .getSearchEnginesList()
- .then(this.enginesChanged_.bind(this));
- this.addWebUIListener(
- 'search-engines-changed', this.enginesChanged_.bind(this));
-
- // Sets offset in iron-list that uses the page as a scrollTarget.
- Polymer.RenderStatus.afterNextRender(this, function() {
- this.$.otherEngines.scrollOffset = this.$.otherEngines.offsetTop;
- });
- },
-
- /**
- * @param {?SearchEngine} searchEngine
- * @param {!HTMLElement} anchorElement
- * @private
- */
- openDialog_: function(searchEngine, anchorElement) {
- this.dialogModel_ = searchEngine;
- this.dialogAnchorElement_ = anchorElement;
- this.showDialog_ = true;
- },
-
- /** @private */
- onCloseDialog_: function() {
- this.showDialog_ = false;
- const anchor = /** @type {!HTMLElement} */ (this.dialogAnchorElement_);
- cr.ui.focusWithoutInk(anchor);
- this.dialogModel_ = null;
- this.dialogAnchorElement_ = null;
- },
-
- /**
- * @param {!CustomEvent<!{
- * engine: !SearchEngine,
- * anchorElement: !HTMLElement
- * }>} e
- * @private
- */
- onEditSearchEngine_: function(e) {
- this.openDialog_(e.detail.engine, e.detail.anchorElement);
- },
-
- /** @private */
- extensionsChanged_: function() {
- if (this.showExtensionsList_ && this.$.extensions) {
- this.$.extensions.notifyResize();
- }
- },
-
- /**
- * @param {!SearchEnginesInfo} searchEnginesInfo
- * @private
- */
- enginesChanged_: function(searchEnginesInfo) {
- this.defaultEngines = searchEnginesInfo.defaults;
-
- // Sort |otherEngines| in alphabetical order.
- this.otherEngines = searchEnginesInfo.others.sort(
- (a, b) => a.name.toLocaleLowerCase().localeCompare(
- b.name.toLocaleLowerCase()));
-
- this.extensions = searchEnginesInfo.extensions;
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onAddSearchEngineTap_: function(e) {
- e.preventDefault();
- this.openDialog_(null, assert(this.$.addSearchEngine));
- },
-
- /** @private */
- computeShowExtensionsList_: function() {
- return this.extensions.length > 0;
- },
-
- /**
- * Filters the given list based on the currently existing filter string.
- * @param {!Array<!SearchEngine>} list
- * @return {!Array<!SearchEngine>}
- * @private
- */
- computeMatchingEngines_: function(list) {
- if (this.filter == '') {
- return list;
- }
-
- const filter = this.filter.toLowerCase();
- return list.filter(e => {
- return [e.displayName, e.name, e.keyword, e.url].some(
- term => term.toLowerCase().includes(filter));
- });
- },
-
- /**
- * @param {!Array<!SearchEngine>} list The original list.
- * @param {!Array<!SearchEngine>} filteredList The filtered list.
- * @return {boolean} Whether to show the "no results" message.
- * @private
- */
- showNoResultsMessage_: function(list, filteredList) {
- return list.length > 0 && filteredList.length == 0;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/search_page/search_page.html b/chromium/chrome/browser/resources/settings/search_page/search_page.html
deleted file mode 100644
index 109fe94a163..00000000000
--- a/chromium/chrome/browser/resources/settings/search_page/search_page.html
+++ /dev/null
@@ -1,124 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../search_engines_page/search_engines_page.html">
-<link rel="import" href="../search_engines_page/search_engines_browser_proxy.html">
-<link rel="import" href="../settings_page/settings_animated_pages.html">
-<link rel="import" href="../settings_page/settings_subpage.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<if expr="chromeos">
- <link rel="import" href="../google_assistant_page/google_assistant_page.html">
- <link rel="import" href="../google_assistant_page/google_assistant_browser_proxy.html">
-</if>
-
-<dom-module id="settings-search-page">
- <template>
- <style include="settings-shared md-select">
- #search-wrapper {
- align-items: center;
- display: flex;
- min-height: var(--settings-row-min-height);
- }
-
- iron-icon {
- padding-inline-end: 16px;
- }
-
- .indented {
- margin-inline-start: var(--cr-section-indent-width);
- }
- </style>
- <settings-animated-pages id="pages" section="search"
- focus-config="[[focusConfig_]]">
- <div route-path="default">
- <!-- Omnibox search engine -->
- <div class="settings-box first block">
- <div id="search-wrapper">
- <div id="searchExplanation" class="start settings-box-text">
- $i18nRaw{searchExplanation}
- </div>
- <template is="dom-if" if="[[isDefaultSearchControlledByPolicy_(
- prefs.default_search_provider_data.template_url_data)]]">
- <cr-policy-pref-indicator pref="[[
- prefs.default_search_provider_data.template_url_data]]">
- </cr-policy-pref-indicator>
- </template>
- <select class="md-select" on-change="onChange_"
- aria-labelledby="searchExplanation"
- disabled$="[[isDefaultSearchEngineEnforced_(
- prefs.default_search_provider_data.template_url_data)]]">
- <template is="dom-repeat" items="[[searchEngines_]]">
- <option selected="[[item.default]]">[[item.name]]</option>
- </template>
- </select>
- </div>
- <template is="dom-if"
- if="[[prefs.default_search_provider_data.template_url_data.extensionId]]">
- <extension-controlled-indicator
- extension-id="[[
- prefs.default_search_provider_data.template_url_data.extensionId]]"
- extension-name="[[
- prefs.default_search_provider_data.template_url_data.controlledByName]]"
- extension-can-be-disabled="[[
- prefs.default_search_provider_data.template_url_data.extensionCanBeDisabled]]"
- on-disable-extension="onDisableExtension_">
- </extension-controlled-indicator>
- </template>
- </div>
-
- <!-- Manage search engines -->
- <cr-link-row class="hr" id="enginesSubpageTrigger"
- label="$i18n{searchEnginesManage}"
- on-click="onManageSearchEnginesTap_"></cr-link-row>
-
-<if expr="chromeos">
- <!-- Google Assistant -->
- <template is="dom-if" if="[[isAssistantAllowed_]]">
- <cr-link-row
- class="hr"
- id="assistantSubpageTrigger"
- label="$i18n{searchGoogleAssistant}"
- sub-label="[[getAssistantEnabledDisabledLabel_(
- prefs.settings.voice_interaction.enabled.value)]]"
- on-click="onGoogleAssistantTap_">
- </cr-link-row>
- </template>
-</if>
- </div>
- <template is="dom-if" route-path="/searchEngines">
- <settings-subpage
- associated-control="[[$$('#enginesSubpageTrigger')]]"
- page-title="$i18n{searchEnginesManage}"
- search-label="$i18n{searchEnginesSearch}"
- search-term="{{searchEnginesFilter_}}">
- <settings-search-engines-page filter="[[searchEnginesFilter_]]">
- </settings-subpage>
- </template>
-<if expr="chromeos">
- <template is="dom-if" if="[[isAssistantAllowed_]]">
- <template is="dom-if" route-path="/googleAssistant">
- <settings-subpage
- associated-control="[[$$('#assistantSubpageTrigger')]]"
- page-title="$i18n{googleAssistantPageTitle}">
- <settings-google-assistant-page prefs="{{prefs}}">
- </settings-google-assistant-page>
- </settings-subpage>
- </template>
- </template>
-</if>
- </settings-animated-pages>
- </template>
- <script src="search_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_page/search_page.js b/chromium/chrome/browser/resources/settings/search_page/search_page.js
deleted file mode 100644
index 4264d2e3fcb..00000000000
--- a/chromium/chrome/browser/resources/settings/search_page/search_page.js
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-search-page' is the settings page containing search settings.
- */
-Polymer({
- is: 'settings-search-page',
-
- behaviors: [I18nBehavior],
-
- properties: {
- prefs: Object,
-
- /**
- * List of default search engines available.
- * @private {!Array<!SearchEngine>}
- */
- searchEngines_: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- /** @private Filter applied to search engines. */
- searchEnginesFilter_: String,
-
- /** @type {?Map<string, string>} */
- focusConfig_: Object,
-
- // <if expr="chromeos">
- /** @private Can be disallowed due to flag, policy, locale, etc. */
- isAssistantAllowed_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isAssistantAllowed') &&
- loadTimeData.getBoolean('showOSSettings');
- },
- },
- // </if>
- },
-
- /** @private {?settings.SearchEnginesBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- created: function() {
- this.browserProxy_ = settings.SearchEnginesBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- // Omnibox search engine
- const updateSearchEngines = searchEngines => {
- this.set('searchEngines_', searchEngines.defaults);
- };
- this.browserProxy_.getSearchEnginesList().then(updateSearchEngines);
- cr.addWebUIListener('search-engines-changed', updateSearchEngines);
-
- this.focusConfig_ = new Map();
- if (settings.routes.SEARCH_ENGINES) {
- this.focusConfig_.set(
- settings.routes.SEARCH_ENGINES.path, '#enginesSubpageTrigger');
- }
- // <if expr="chromeos">
- if (settings.routes.GOOGLE_ASSISTANT) {
- this.focusConfig_.set(
- settings.routes.GOOGLE_ASSISTANT.path,
- '#assistantSubpageTrigger .subpage-arrow');
- }
- // </if>
- },
-
- /** @private */
- onChange_: function() {
- const select = /** @type {!HTMLSelectElement} */ (this.$$('select'));
- const searchEngine = this.searchEngines_[select.selectedIndex];
- this.browserProxy_.setDefaultSearchEngine(searchEngine.modelIndex);
- },
-
- /** @private */
- onDisableExtension_: function() {
- this.fire('refresh-pref', 'default_search_provider.enabled');
- },
-
- /** @private */
- onManageSearchEnginesTap_: function() {
- settings.navigateTo(settings.routes.SEARCH_ENGINES);
- },
-
- // <if expr="chromeos">
- /** @private */
- onGoogleAssistantTap_: function() {
- assert(this.isAssistantAllowed_);
- settings.navigateTo(settings.routes.GOOGLE_ASSISTANT);
- },
- // </if>
-
- // <if expr="chromeos">
- /**
- * @param {boolean} toggleValue
- * @return {string}
- * @private
- */
- getAssistantEnabledDisabledLabel_: function(toggleValue) {
- return this.i18n(
- toggleValue ? 'searchGoogleAssistantEnabled' :
- 'searchGoogleAssistantDisabled');
- },
- // </if>
-
- /**
- * @param {!Event} event
- * @private
- */
- doNothing_: function(event) {
- event.stopPropagation();
- },
-
- /**
- * @param {!chrome.settingsPrivate.PrefObject} pref
- * @return {boolean}
- * @private
- */
- isDefaultSearchControlledByPolicy_: function(pref) {
- return pref.controlledBy == chrome.settingsPrivate.ControlledBy.USER_POLICY;
- },
-
- /**
- * @param {!chrome.settingsPrivate.PrefObject} pref
- * @return {boolean}
- * @private
- */
- isDefaultSearchEngineEnforced_: function(pref) {
- return pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/search_settings.js b/chromium/chrome/browser/resources/settings/search_settings.js
deleted file mode 100644
index 60facec8de0..00000000000
--- a/chromium/chrome/browser/resources/settings/search_settings.js
+++ /dev/null
@@ -1,601 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * A data structure used by callers to combine the results of multiple search
- * requests.
- *
- * @typedef {{
- * canceled: Boolean,
- * didFindMatches: Boolean,
- * wasClearSearch: Boolean,
- * }}
- */
-settings.SearchResult;
-
-cr.define('settings', function() {
- /**
- * A CSS attribute indicating that a node should be ignored during searching.
- * @type {string}
- */
- const SKIP_SEARCH_CSS_ATTRIBUTE = 'no-search';
-
- /**
- * List of elements types that should not be searched at all.
- * The only DOM-MODULE node is in <body> which is not searched, therefore
- * DOM-MODULE is not needed in this set.
- * @type {!Set<string>}
- */
- const IGNORED_ELEMENTS = new Set([
- 'CONTENT',
- 'CR-ACTION-MENU',
- 'CR-DIALOG',
- 'CR-ICON-BUTTON',
- 'CR-SLIDER',
- 'DIALOG',
- 'IMG',
- 'IRON-ICON',
- 'IRON-LIST',
- 'PAPER-RIPPLE',
- 'PAPER-SPINNER-LITE',
- 'SLOT',
- 'STYLE',
- 'TEMPLATE',
- ]);
-
- /**
- * Traverses the entire DOM (including Shadow DOM), finds text nodes that
- * match the given regular expression and applies the highlight UI. It also
- * ensures that <settings-section> instances become visible if any matches
- * occurred under their subtree.
- *
- * @param {!settings.SearchRequest} request
- * @param {!Node} root The root of the sub-tree to be searched
- * @private
- */
- function findAndHighlightMatches_(request, root) {
- let foundMatches = false;
- const highlights = [];
- const bubbles = [];
-
- function doSearch(node) {
- // NOTE: For subpage wrappers <template route-path="..."> when |no-search|
- // participates in a data binding:
- //
- // - Always use noSearch Polymer property, for example
- // no-search="[[foo]]"
- // - *Don't* use a no-search CSS attribute like no-search$="[[foo]]"
- //
- // The latter throws an error during the automatic Polymer 2 conversion to
- // <dom-if><template...></dom-if> syntax.
- if (node.nodeName == 'DOM-IF' && node.hasAttribute('route-path') &&
- !node.if && !node['noSearch'] &&
- !node.hasAttribute(SKIP_SEARCH_CSS_ATTRIBUTE)) {
- request.queue_.addRenderTask(new RenderTask(request, node));
- return;
- }
-
- if (IGNORED_ELEMENTS.has(node.nodeName)) {
- return;
- }
-
- if (node instanceof HTMLElement) {
- const element = /** @type {HTMLElement} */ (node);
- if (element.hasAttribute(SKIP_SEARCH_CSS_ATTRIBUTE) ||
- element.hasAttribute('hidden') || element.style.display == 'none') {
- return;
- }
- }
-
- if (node.nodeType == Node.TEXT_NODE) {
- const textContent = node.nodeValue.trim();
- if (textContent.length == 0) {
- return;
- }
-
- if (request.regExp.test(textContent)) {
- foundMatches = true;
- const bubble = revealParentSection_(node, request.rawQuery_);
- if (bubble) {
- bubbles.push(bubble);
- }
-
- // Don't highlight <select> nodes, yellow rectangles can't be
- // displayed within an <option>.
- // TODO(dpapad): highlight <select> controls with a search bubble
- // instead.
- if (node.parentNode.nodeName != 'OPTION') {
- request.addTextObserver(node);
- highlights.push(cr.search_highlight_utils.highlight(
- node, textContent.split(request.regExp)));
- }
- }
- // Returning early since TEXT_NODE nodes never have children.
- return;
- }
-
- let child = node.firstChild;
- while (child !== null) {
- // Getting a reference to the |nextSibling| before calling doSearch()
- // because |child| could be removed from the DOM within doSearch().
- const nextSibling = child.nextSibling;
- doSearch(child);
- child = nextSibling;
- }
-
- const shadowRoot = node.shadowRoot;
- if (shadowRoot) {
- doSearch(shadowRoot);
- }
- }
-
- doSearch(root);
- request.addHighlightsAndBubbles(highlights, bubbles);
- return foundMatches;
- }
-
- /**
- * Finds and makes visible the <settings-section> parent of |node|.
- * @param {!Node} node
- * @param {string} rawQuery
- * @return {?Node} The search bubble created while revealing the section, if
- * any.
- * @private
- */
- function revealParentSection_(node, rawQuery) {
- let associatedControl = null;
- // Find corresponding SETTINGS-SECTION parent and make it visible.
- let parent = node;
- while (parent.nodeName !== 'SETTINGS-SECTION') {
- parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
- parent.host :
- parent.parentNode;
- if (!parent) {
- // |node| wasn't inside a SETTINGS-SECTION.
- return null;
- }
- if (parent.nodeName == 'SETTINGS-SUBPAGE') {
- // TODO(dpapad): Cast to SettingsSubpageElement here.
- associatedControl = assert(
- parent.associatedControl,
- 'An associated control was expected for SETTINGS-SUBPAGE ' +
- parent.pageTitle + ', but was not found.');
- }
- }
- parent.hiddenBySearch = false;
-
- // Need to add the search bubble after the parent SETTINGS-SECTION has
- // become visible, otherwise |offsetWidth| returns zero.
- if (associatedControl) {
- return cr.search_highlight_utils.highlightControlWithBubble(
- associatedControl, rawQuery);
- }
- return null;
- }
-
- /** @abstract */
- class Task {
- /**
- * @param {!settings.SearchRequest} request
- * @param {!Node} node
- */
- constructor(request, node) {
- /** @protected {!settings.SearchRequest} */
- this.request = request;
-
- /** @protected {!Node} */
- this.node = node;
- }
-
- /**
- * @abstract
- * @return {!Promise}
- */
- exec() {}
- }
-
- class RenderTask extends Task {
- /**
- * A task that takes a <template is="dom-if">...</template> node
- * corresponding to a setting subpage and renders it. A
- * SearchAndHighlightTask is posted for the newly rendered subtree, once
- * rendering is done.
- *
- * @param {!settings.SearchRequest} request
- * @param {!Node} node
- */
- constructor(request, node) {
- super(request, node);
- }
-
- /** @override */
- exec() {
- const routePath = this.node.getAttribute('route-path');
-
- const content = Polymer.DomIf._contentForTemplate(
- /** @type {!HTMLTemplateElement} */ (this.node.firstElementChild));
- const subpageTemplate = content.querySelector('settings-subpage');
- subpageTemplate.setAttribute('route-path', routePath);
- assert(!this.node.if);
- this.node.if = true;
-
- return new Promise((resolve, reject) => {
- const parent = this.node.parentNode;
- parent.async(() => {
- const renderedNode =
- parent.querySelector('[route-path="' + routePath + '"]');
- // Register a SearchAndHighlightTask for the part of the DOM that was
- // just rendered.
- this.request.queue_.addSearchAndHighlightTask(
- new SearchAndHighlightTask(this.request, assert(renderedNode)));
- resolve();
- });
- });
- }
- }
-
- class SearchAndHighlightTask extends Task {
- /**
- * @param {!settings.SearchRequest} request
- * @param {!Node} node
- */
- constructor(request, node) {
- super(request, node);
- }
-
- /** @override */
- exec() {
- const foundMatches = findAndHighlightMatches_(this.request, this.node);
- this.request.updateMatches(foundMatches);
- return Promise.resolve();
- }
- }
-
- class TopLevelSearchTask extends Task {
- /**
- * @param {!settings.SearchRequest} request
- * @param {!Node} page
- */
- constructor(request, page) {
- super(request, page);
- }
-
- /** @override */
- exec() {
- const shouldSearch = this.request.regExp !== null;
- this.setSectionsVisibility_(!shouldSearch);
- if (shouldSearch) {
- const foundMatches = findAndHighlightMatches_(this.request, this.node);
- this.request.updateMatches(foundMatches);
- }
-
- return Promise.resolve();
- }
-
- /**
- * @param {boolean} visible
- * @private
- */
- setSectionsVisibility_(visible) {
- const sections = this.node.querySelectorAll('settings-section');
-
- for (let i = 0; i < sections.length; i++) {
- sections[i].hiddenBySearch = !visible;
- }
- }
- }
-
- class TaskQueue {
- /** @param {!settings.SearchRequest} request */
- constructor(request) {
- /** @private {!settings.SearchRequest} */
- this.request_ = request;
-
- /**
- * @private {{
- * high: !Array<!Task>,
- * middle: !Array<!Task>,
- * low: !Array<!Task>
- * }}
- */
- this.queues_;
- this.reset();
-
- /** @private {?Function} */
- this.onEmptyCallback_ = null;
-
- /**
- * Whether a task is currently running.
- * @private {boolean}
- */
- this.running_ = false;
- }
-
- /** Drops all tasks. */
- reset() {
- this.queues_ = {high: [], middle: [], low: []};
- }
-
- /** @param {!TopLevelSearchTask} task */
- addTopLevelSearchTask(task) {
- this.queues_.high.push(task);
- this.consumePending_();
- }
-
- /** @param {!SearchAndHighlightTask} task */
- addSearchAndHighlightTask(task) {
- this.queues_.middle.push(task);
- this.consumePending_();
- }
-
- /** @param {!RenderTask} task */
- addRenderTask(task) {
- this.queues_.low.push(task);
- this.consumePending_();
- }
-
- /**
- * Registers a callback to be called every time the queue becomes empty.
- * @param {function():void} onEmptyCallback
- */
- onEmpty(onEmptyCallback) {
- this.onEmptyCallback_ = onEmptyCallback;
- }
-
- /**
- * @return {!Task|undefined}
- * @private
- */
- popNextTask_() {
- return this.queues_.high.shift() || this.queues_.middle.shift() ||
- this.queues_.low.shift();
- }
-
- /** @private */
- consumePending_() {
- if (this.running_) {
- return;
- }
-
- const task = this.popNextTask_();
- if (!task) {
- this.running_ = false;
- if (this.onEmptyCallback_) {
- this.onEmptyCallback_();
- }
- return;
- }
-
- this.running_ = true;
- window.requestIdleCallback(() => {
- if (!this.request_.canceled) {
- task.exec().then(() => {
- this.running_ = false;
- this.consumePending_();
- });
- }
- // Nothing to do otherwise. Since the request corresponding to this
- // queue was canceled, the queue is disposed along with the request.
- });
- }
- }
-
- class SearchRequest {
- /**
- * @param {string} rawQuery
- * @param {!Element} root
- */
- constructor(rawQuery, root) {
- /** @private {string} */
- this.rawQuery_ = rawQuery;
-
- /** @private {!Element} */
- this.root_ = root;
-
- /** @type {?RegExp} */
- this.regExp = this.generateRegExp_();
-
- /**
- * Whether this request was canceled before completing.
- * @type {boolean}
- */
- this.canceled = false;
-
- /** @private {boolean} */
- this.foundMatches_ = false;
-
- /** @type {!PromiseResolver} */
- this.resolver = new PromiseResolver();
-
- /** @private {!TaskQueue} */
- this.queue_ = new TaskQueue(this);
- this.queue_.onEmpty(() => {
- this.resolver.resolve(this);
- });
-
- /** @private {!Set<!MutationObserver>} */
- this.textObservers_ = new Set();
-
- /** @private {!Array<!Node>} */
- this.highlights_ = [];
-
- /** @private {!Array<!Node>} */
- this.bubbles_ = [];
- }
-
- /**
- * @param {!Array<!Node>} highlights The highlight wrappers to add
- * @param {!Array<!Node>} bubbles The search bubbles to add.
- */
- addHighlightsAndBubbles(highlights, bubbles) {
- this.highlights_.push(...highlights);
- this.bubbles_.push(...bubbles);
- }
-
- removeAllTextObservers() {
- this.textObservers_.forEach(observer => {
- observer.disconnect();
- });
- this.textObservers_.clear();
- }
-
- removeAllHighlightsAndBubbles() {
- cr.search_highlight_utils.removeHighlights(this.highlights_);
- for (const bubble of this.bubbles_) {
- bubble.remove();
- }
- this.highlights_ = [];
- this.bubbles_ = [];
- }
-
- /** @param {!Node} textNode */
- addTextObserver(textNode) {
- const originalParentNode = /** @type {!Node} */ (textNode.parentNode);
- const observer = new MutationObserver(mutations => {
- const oldValue = mutations[0].oldValue.trim();
- const newValue = textNode.nodeValue.trim();
- if (oldValue != newValue) {
- observer.disconnect();
- this.textObservers_.delete(observer);
- cr.search_highlight_utils.findAndRemoveHighlights(originalParentNode);
- }
- });
- observer.observe(
- textNode, {characterData: true, characterDataOldValue: true});
- this.textObservers_.add(observer);
- }
-
- /**
- * Fires this search request.
- */
- start() {
- this.queue_.addTopLevelSearchTask(
- new TopLevelSearchTask(this, this.root_));
- }
-
- /**
- * @return {?RegExp}
- * @private
- */
- generateRegExp_() {
- let regExp = null;
-
- // Generate search text by escaping any characters that would be
- // problematic for regular expressions.
- const searchText = this.rawQuery_.trim().replace(SANITIZE_REGEX, '\\$&');
- if (searchText.length > 0) {
- regExp = new RegExp(`(${searchText})`, 'i');
- }
-
- return regExp;
- }
-
- /**
- * @param {string} rawQuery
- * @return {boolean} Whether this SearchRequest refers to an identical
- * query.
- */
- isSame(rawQuery) {
- return this.rawQuery_ == rawQuery;
- }
-
- /**
- * Updates the result for this search request.
- * @param {boolean} found
- */
- updateMatches(found) {
- this.foundMatches_ = this.foundMatches_ || found;
- }
-
- /** @return {boolean} Whether any matches were found. */
- didFindMatches() {
- return this.foundMatches_;
- }
- }
-
- /** @type {!RegExp} */
- const SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g;
-
- /** @interface */
- class SearchManager {
- /**
- * @param {string} text The text to search for.
- * @param {!Element} page
- * @return {!Promise<!settings.SearchRequest>} A signal indicating that
- * searching finished.
- */
- search(text, page) {}
- }
-
- /** @implements {SearchManager} */
- class SearchManagerImpl {
- constructor() {
- /** @private {!Set<!settings.SearchRequest>} */
- this.activeRequests_ = new Set();
-
- /** @private {!Set<!settings.SearchRequest>} */
- this.completedRequests_ = new Set();
-
- /** @private {?string} */
- this.lastSearchedText_ = null;
- }
-
- /** @override */
- search(text, page) {
- // Cancel any pending requests if a request with different text is
- // submitted.
- if (text != this.lastSearchedText_) {
- this.activeRequests_.forEach(function(request) {
- request.removeAllTextObservers();
- request.removeAllHighlightsAndBubbles();
- request.canceled = true;
- request.resolver.resolve(request);
- });
- this.activeRequests_.clear();
- this.completedRequests_.forEach(request => {
- request.removeAllTextObservers();
- request.removeAllHighlightsAndBubbles();
- });
- this.completedRequests_.clear();
- }
-
- this.lastSearchedText_ = text;
- const request = new SearchRequest(text, page);
- this.activeRequests_.add(request);
- request.start();
- return request.resolver.promise.then(() => {
- this.activeRequests_.delete(request);
- this.completedRequests_.add(request);
- return request;
- });
- }
- }
-
- /** @type {?SearchManager} */
- let instance = null;
-
- /** @return {!SearchManager} */
- function getSearchManager() {
- if (instance === null) {
- instance = new SearchManagerImpl();
- }
- return instance;
- }
-
- /**
- * Sets the SearchManager singleton instance, useful for testing.
- * @param {!SearchManager} searchManager
- */
- function setSearchManagerForTesting(searchManager) {
- instance = searchManager;
- }
-
- return {
- getSearchManager: getSearchManager,
- setSearchManagerForTesting: setSearchManagerForTesting,
- SearchRequest: SearchRequest,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/settings.html b/chromium/chrome/browser/resources/settings/settings.html
deleted file mode 100644
index 7842e793225..00000000000
--- a/chromium/chrome/browser/resources/settings/settings.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}" class="loading">
-<head>
- <meta charset="utf-8">
- <title>$i18n{settings}</title>
-<if expr="not optimize_webui">
- <base href="chrome://settings">
-</if>
- <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
- <style>
- html {
- background: var(--md-background-color);
- overflow: hidden;
- /* Remove 300ms delay for 'click' event, when using touch interface. */
- touch-action: manipulation;
- }
-
- html.loading::before {
- background-color: var(--md-toolbar-color);
- border-bottom: var(--md-toolbar-border);
- box-sizing: border-box;
- content: '';
- display: block;
- height: var(--md-toolbar-height);
- }
- </style>
-</head>
-<body>
- <settings-ui></settings-ui>
- <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
- <link rel="import" href="chrome://resources/html/polymer.html">
- <link rel="import" href="settings_ui/settings_ui.html">
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/settings/settings_icons_css.html b/chromium/chrome/browser/resources/settings/settings_icons_css.html
deleted file mode 100644
index cdfd12f105a..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_icons_css.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<!-- Common icon classes for Material Design settings page. -->
-<dom-module id="settings-icons">
- <template>
- <style>
-<if expr="chromeos">
- cr-icon-button.icon-add-circle {
- background-image: url(./images/settings_icon_add_circle.svg);
- }
- cr-icon-button.icon-add-wifi {
- background-image: url(./images/settings_icon_add_wifi.svg);
- }
-</if>
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_main/settings_main.html b/chromium/chrome/browser/resources/settings/settings_main/settings_main.html
deleted file mode 100644
index b005a754146..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_main/settings_main.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/managed_footnote/managed_footnote.html">
-<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/search_highlight_utils.html">
-<link rel="import" href="chrome://resources/html/promise_resolver.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../about_page/about_page.html">
-<link rel="import" href="../basic_page/basic_page.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="settings-main">
- <template>
- <style include="cr-hidden-style settings-shared">
- #overscroll {
- margin-top: 64px;
- }
-
- .showing-subpage ~ #overscroll {
- display: none;
- }
-
- #noSearchResults {
- margin-top: 80px;
- text-align: center;
- }
-
- #noSearchResults div:first-child {
- font-size: 123%; /* Should be 16px when 100% is 13px. */
- margin-bottom: 10px;
- }
-
- managed-footnote {
- border-top: none;
- /* margin-bottom is needed to compensate for the next element's 21px
- * margin at the top and 8px padding at the top. This leaves a 12px
- * padding between this element's content and the top of the next
- * element's text. */
- margin-bottom: calc(-21px - 8px);
- padding-bottom: 16px;
- padding-top: 12px;
- /* The next element spills over this element. This ensures the link
- * is clickable. */
- position: relative;
- z-index: 1;
- }
- </style>
- <div id="noSearchResults" hidden$="[[!showNoResultsFound_]]">
- <div>$i18n{searchNoResults}</div>
- <div>$i18nRaw{searchNoResultsHelp}</div>
- </div>
- <template is="dom-if"
- if="[[showManagedHeader_(inSearchMode_, showingSubpage_,
- showPages_.about)]]">
- <managed-footnote></managed-footnote>
- </template>
- <template is="dom-if" if="[[showPages_.settings]]">
- <settings-basic-page prefs="{{prefs}}"
- page-visibility="[[pageVisibility]]"
- show-android-apps="[[showAndroidApps]]"
- show-crostini="[[showCrostini]]"
- show-parental-controls="[[showParentalControls]]"
- show-plugin-vm="[[showPluginVm]]"
- have-play-store-app="[[havePlayStoreApp]]"
- on-showing-section="onShowingSection_"
- on-subpage-expand="onShowingSubpage_"
- on-showing-main-page="onShowingMainPage_"
- in-search-mode="[[inSearchMode_]]"
- advanced-toggle-expanded="{{advancedToggleExpanded}}">
- </settings-basic-page>
- </template>
- <template is="dom-if" if="[[showPages_.about]]">
- <settings-about-page role="main"
- in-search-mode="[[inSearchMode_]]"
- on-subpage-expand="onShowingSubpage_"
- on-showing-main-page="onShowingMainPage_"
- prefs="{{prefs}}"
- show-crostini="[[showCrostini]]">
- </settings-about-page>
- </template>
- <div id="overscroll" style="padding-bottom: [[overscroll_]]px"></div>
- </template>
- <script src="settings_main.js"></script>
- <script src="../search_settings.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_main/settings_main.js b/chromium/chrome/browser/resources/settings/settings_main/settings_main.js
deleted file mode 100644
index f921d174922..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_main/settings_main.js
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{about: boolean, settings: boolean}}
- */
-let MainPageVisibility;
-
-/**
- * @fileoverview
- * 'settings-main' displays the selected settings page.
- */
-Polymer({
- is: 'settings-main',
-
- behaviors: [settings.RouteObserverBehavior],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: {
- type: Object,
- notify: true,
- },
-
- advancedToggleExpanded: {
- type: Boolean,
- notify: true,
- },
-
- /** @private */
- overscroll_: {
- type: Number,
- observer: 'overscrollChanged_',
- },
-
- /**
- * Controls which main pages are displayed via dom-ifs, based on the current
- * route.
- * @private {!MainPageVisibility}
- */
- showPages_: {
- type: Object,
- value: function() {
- return {about: false, settings: false};
- },
- },
-
- /**
- * Whether a search operation is in progress or previous search results are
- * being displayed.
- * @private {boolean}
- */
- inSearchMode_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showNoResultsFound_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- showingSubpage_: Boolean,
-
- toolbarSpinnerActive: {
- type: Boolean,
- value: false,
- notify: true,
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!PageVisibility}
- */
- pageVisibility: Object,
-
- showAndroidApps: Boolean,
-
- showParentalControls: Boolean,
-
- havePlayStoreApp: Boolean,
- },
-
- /** @private */
- overscrollChanged_: function() {
- if (!this.overscroll_ && this.boundScroll_) {
- this.offsetParent.removeEventListener('scroll', this.boundScroll_);
- window.removeEventListener('resize', this.boundScroll_);
- this.boundScroll_ = null;
- } else if (this.overscroll_ && !this.boundScroll_) {
- this.boundScroll_ = () => {
- if (!this.showingSubpage_) {
- this.setOverscroll_(0);
- }
- };
- this.offsetParent.addEventListener('scroll', this.boundScroll_);
- window.addEventListener('resize', this.boundScroll_);
- }
- },
-
- /**
- * Sets the overscroll padding. Never forces a scroll, i.e., always leaves
- * any currently visible overflow as-is.
- * @param {number=} opt_minHeight The minimum overscroll height needed.
- * @private
- */
- setOverscroll_: function(opt_minHeight) {
- const scroller = this.offsetParent;
- if (!scroller) {
- return;
- }
- const overscroll = this.$.overscroll;
- const visibleBottom = scroller.scrollTop + scroller.clientHeight;
- const overscrollBottom = overscroll.offsetTop + overscroll.scrollHeight;
- // How much of the overscroll is visible (may be negative).
- const visibleOverscroll =
- overscroll.scrollHeight - (overscrollBottom - visibleBottom);
- this.overscroll_ =
- Math.max(opt_minHeight || 0, Math.ceil(visibleOverscroll));
- },
-
- /**
- * Updates the hidden state of the about and settings pages based on the
- * current route.
- * @param {!settings.Route} newRoute
- */
- currentRouteChanged: function(newRoute) {
- const inAbout = settings.routes.ABOUT.contains(settings.getCurrentRoute());
- this.showPages_ = {about: inAbout, settings: !inAbout};
-
- if (!newRoute.isSubpage()) {
- document.title = inAbout ? loadTimeData.getStringF(
- 'settingsAltPageTitle',
- loadTimeData.getString('aboutPageTitle')) :
- loadTimeData.getString('settings');
- }
- },
-
- /** @private */
- onShowingSubpage_: function() {
- this.showingSubpage_ = true;
- },
-
- /** @private */
- onShowingMainPage_: function() {
- this.showingSubpage_ = false;
- },
-
- /**
- * A handler for the 'showing-section' event fired from settings-basic-page,
- * indicating that a section should be scrolled into view as a result of a
- * navigation.
- * @param {!CustomEvent<!HTMLElement>} e
- * @private
- */
- onShowingSection_: function(e) {
- const section = e.detail;
- // Calculate the height that the overscroll padding should be set to, so
- // that the given section is displayed at the top of the viewport.
- // Find the distance from the section's top to the overscroll.
- const sectionTop = section.offsetParent.offsetTop + section.offsetTop;
- const distance = this.$.overscroll.offsetTop - sectionTop;
- const overscroll = Math.max(0, this.offsetParent.clientHeight - distance);
- this.setOverscroll_(overscroll);
- section.scrollIntoView();
- },
-
- /**
- * Returns the root page (if it exists) for a route.
- * @param {!settings.Route} route
- * @return {(?SettingsAboutPageElement|?SettingsBasicPageElement)}
- */
- getPage_: function(route) {
- if (settings.routes.ABOUT.contains(route)) {
- return /** @type {?SettingsAboutPageElement} */ (
- this.$$('settings-about-page'));
- }
- if (settings.routes.BASIC.contains(route) ||
- (settings.routes.ADVANCED &&
- settings.routes.ADVANCED.contains(route))) {
- return /** @type {?SettingsBasicPageElement} */ (
- this.$$('settings-basic-page'));
- }
- assertNotReached();
- },
-
- /**
- * @param {string} query
- * @return {!Promise} A promise indicating that searching finished.
- */
- searchContents: function(query) {
- // Trigger rendering of the basic and advanced pages and search once ready.
- this.inSearchMode_ = true;
- this.toolbarSpinnerActive = true;
-
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- const whenSearchDone =
- assert(this.getPage_(settings.routes.BASIC)).searchContents(query);
- whenSearchDone.then(result => {
- resolve();
- if (result.canceled) {
- // Nothing to do here. A previous search request was canceled
- // because a new search request was issued with a different query
- // before the previous completed.
- return;
- }
-
- this.toolbarSpinnerActive = false;
- this.inSearchMode_ = !result.wasClearSearch;
- this.showNoResultsFound_ =
- this.inSearchMode_ && !result.didFindMatches;
-
- if (this.inSearchMode_) {
- Polymer.IronA11yAnnouncer.requestAvailability();
- this.fire('iron-announce', {
- text: this.showNoResultsFound_ ?
- loadTimeData.getString('searchNoResults') :
- loadTimeData.getStringF('searchResults', query)
- });
- }
- });
- }, 0);
- });
- },
-
- /**
- * @return {boolean}
- * @private
- */
- showManagedHeader_: function() {
- return !this.inSearchMode_ && !this.showingSubpage_ &&
- !this.showPages_.about;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html
deleted file mode 100644
index 0bcf8487656..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ /dev/null
@@ -1,240 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<if expr="chromeos">
-<!-- TODO(crbug.com/986596): Remove OS icons when SplitSettings is complete. -->
-<link rel="import" href="../chromeos/os_icons.html">
-</if>
-
-<dom-module id="settings-menu">
- <template>
- <style include="settings-shared">
- :host {
- box-sizing: border-box;
- display: block;
- padding-bottom: 2px;
- padding-top: 8px;
- }
-
- :host * {
- -webkit-tap-highlight-color: transparent;
- }
-
- a[href],
- #advancedButton {
- align-items: center;
- color: var(--settings-nav-item-color);
- display: flex;
- font-weight: 500;
- margin-inline-end: 2px; /* Margin so selected outline is visible. */
- margin-inline-start: 1px;
- min-height: 20px;
- padding-bottom: 10px;
- padding-inline-start: 23px; /* 24px - 1px from margin for outline. */
- padding-top: 10px;
- }
-
- a[href].iron-selected {
- color: var(--cr-link-color);
- }
-
- a[href]:focus {
- background: transparent;
- outline: auto 5px -webkit-focus-ring-color;
- }
-
- iron-icon {
- --iron-icon-fill-color: var(--settings-nav-icon-color);
- margin-inline-end: 24px;
- pointer-events: none;
- vertical-align: top;
- }
-
- .iron-selected > iron-icon {
- fill: var(--cr-link-color);
- }
-
- #advancedButton {
- --ink-color: var(--settings-nav-item-color);
- background: none;
- border: none;
- border-radius: initial;
- box-shadow: none;
- height: unset;
- margin-top: 8px;
- padding-inline-end: 0;
- text-transform: none;
- }
-
- #advancedButton:focus {
- outline: none;
- }
-
- :host-context(.focus-outline-visible) #advancedButton:focus {
- outline: auto 5px -webkit-focus-ring-color;
- }
-
- #advancedButton > span,
- #extensionsLink > span {
- flex: 1;
- }
-
- #advancedButton > iron-icon,
- #extensionsLink > iron-icon {
- @apply --cr-icon-height-width;
- margin-inline-end: 14px; /* 16px - 2px from margin for outline. */
- }
-
- #menuSeparator {
- /* Per bettes@, this is different from the other separator lines. */
- border-bottom: 1px solid rgba(0, 0, 0, 0.08);
- margin-bottom: 8px;
- margin-top: 8px;
- }
-
- @media (prefers-color-scheme: dark) {
- #menuSeparator {
- border-bottom: var(--cr-separator-line); /* override */
- }
- }
- </style>
- <iron-selector id="topMenu" selectable="a:not(#extensionsLink)"
- attr-for-selected="href" on-iron-activate="onSelectorActivate_"
- role="navigation" on-click="onLinkClick_">
-<if expr="chromeos">
- <a href="/internet" hidden="[[!pageVisibility.internet]]">
- <iron-icon icon="os-settings:network-wifi"></iron-icon>
- $i18n{internetPageTitle}
- </a>
- <a href="/bluetooth" hidden="[[!pageVisibility.bluetooth]]">
- <iron-icon icon="cr:bluetooth"></iron-icon>
- $i18n{bluetoothPageTitle}
- </a>
- <a id="multidevice" href="/multidevice"
- hidden="[[!pageVisibility.multidevice]]">
- <iron-icon icon="os-settings:multidevice-better-together-suite">
- </iron-icon>
- $i18n{multidevicePageTitle}
- </a>
-</if>
- <a id="people" href="/people" hidden="[[!pageVisibility.people]]">
- <iron-icon icon="cr:person"></iron-icon>
- $i18n{peoplePageTitle}
- </a>
- <a id="autofill" href="/autofill"
- hidden="[[!pageVisibility.autofill]]">
- <iron-icon icon="settings:assignment"></iron-icon>
- $i18n{autofillPageTitle}
- </a>
- <a id="appearance" href="/appearance"
- hidden="[[!pageVisibility.appearance]]">
- <iron-icon icon="settings:palette"></iron-icon>
- $i18n{appearancePageTitle}
- </a>
-<if expr="chromeos">
- <a href="/device" hidden="[[!pageVisibility.device]]">
- <iron-icon icon="os-settings:laptop-chromebook"></iron-icon>
- $i18n{devicePageTitle}
- </a>
-</if>
- <a href="/search">
- <iron-icon icon="cr:search"></iron-icon>
- $i18n{searchPageTitle}
- </a>
-<if expr="chromeos">
- <a href="/androidApps" hidden="[[!showAndroidApps]]">
- <iron-icon icon="os-settings:play-prism"></iron-icon>
- $i18n{androidAppsPageTitle}
- </a>
- <a href="/crostini" hidden="[[!showCrostini]]">
- <iron-icon icon="os-settings:crostini-mascot"></iron-icon>
- $i18n{crostiniPageTitle}
- </a>
- <a href="/pluginVm" hidden="[[!showPluginVm]]">
- <iron-icon icon="os-settings:plugin-vm"></iron-icon>
- $i18n{pluginVmPageTitle}
- </a>
-</if>
-<if expr="not chromeos">
- <a id="defaultBrowser" href="/defaultBrowser"
- hidden="[[!pageVisibility.defaultBrowser]]">
- <iron-icon icon="settings:web"></iron-icon>
- $i18n{defaultBrowser}
- </a>
-</if>
- <a id="onStartup" href="/onStartup"
- hidden="[[!pageVisibility.onStartup]]">
- <iron-icon icon="settings:power-settings-new"></iron-icon>
- $i18n{onStartup}
- </a>
- <cr-button id="advancedButton"
- aria-expanded$="[[boolToString_(advancedOpened)]]"
- on-click="onAdvancedButtonToggle_"
- hidden="[[!pageVisibility.advancedSettings]]">
- <span>$i18n{advancedPageTitle}</span>
- <iron-icon icon="[[arrowState_(advancedOpened)]]">
- </iron-icon></cr-button>
- <iron-collapse id="advancedSubmenu" opened="[[advancedOpened]]"
- hidden="[[!pageVisibility.advancedSettings]]">
- <iron-selector id="subMenu" selectable="a" attr-for-selected="href"
- role="navigation" on-click="onLinkClick_">
-<if expr="chromeos">
- <a href="/dateTime" hidden="[[!pageVisibility.dateTime]]">
- <iron-icon icon="os-settings:access-time"></iron-icon>
- $i18n{dateTimePageTitle}
- </a>
-</if>
- <a href="/privacy">
- <iron-icon icon="cr:security"></iron-icon>
- $i18n{privacyPageTitle}
- </a>
- <a href="/languages">
- <iron-icon icon="settings:language"></iron-icon>
- $i18n{languagesPageTitle}
- </a>
- <a href="/downloads">
- <iron-icon icon="cr:file-download"></iron-icon>
- $i18n{downloadsPageTitle}
- </a>
- <a href="/printing" hidden="[[!pageVisibility.printing]]">
- <iron-icon icon="cr:print"></iron-icon>
- $i18n{printingPageTitle}
- </a>
- <a href="/accessibility">
- <iron-icon icon="settings:accessibility"></iron-icon>
- $i18n{a11yPageTitle}
- </a>
-<if expr="not chromeos">
- <a href="/system">
- <iron-icon icon="settings:build"></iron-icon>
- $i18n{systemPageTitle}
- </a>
-</if>
- <a id="reset" href="/reset" hidden="[[!pageVisibility.reset]]">
- <iron-icon icon="settings:restore"></iron-icon>
- $i18n{resetPageTitle}
- </a>
- </iron-selector>
- </iron-collapse>
- <div id="menuSeparator"></div>
- <a id="extensionsLink" href="chrome://extensions" target="_blank"
- hidden="[[!pageVisibility.extensions]]"
- on-click="onExtensionsLinkClick_"
- title="$i18n{extensionsLinkTooltip}">
- <span>$i18n{extensionsPageTitle}</span>
- <iron-icon class="cr-icon icon-external" actionable></iron-icon>
- </a>
- <a id="about-menu" href="/help">$i18n{aboutPageTitle}</a>
- </iron-selector>
- </template>
- <script src="settings_menu.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js
deleted file mode 100644
index c604ae4bcbf..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-menu' shows a menu with a hardcoded set of pages and subpages.
- */
-Polymer({
- is: 'settings-menu',
-
- behaviors: [settings.RouteObserverBehavior],
-
- properties: {
- advancedOpened: {
- type: Boolean,
- value: false,
- notify: true,
- },
-
- /**
- * Dictionary defining page visibility.
- * @type {!PageVisibility}
- */
- pageVisibility: Object,
- },
-
- /** @param {!settings.Route} newRoute */
- currentRouteChanged: function(newRoute) {
- // Focus the initially selected path.
- const anchors = this.root.querySelectorAll('a');
- for (let i = 0; i < anchors.length; ++i) {
- const anchorRoute =
- settings.router.getRouteForPath(anchors[i].getAttribute('href'));
- if (anchorRoute && anchorRoute.contains(newRoute)) {
- this.setSelectedUrl_(anchors[i].href);
- return;
- }
- }
-
- this.setSelectedUrl_(''); // Nothing is selected.
- },
-
- /** @private */
- onAdvancedButtonToggle_: function() {
- this.advancedOpened = !this.advancedOpened;
- },
-
- /**
- * Prevent clicks on sidebar items from navigating. These are only links for
- * accessibility purposes, taps are handled separately by <iron-selector>.
- * @param {!Event} event
- * @private
- */
- onLinkClick_: function(event) {
- if (event.target.matches('a:not(#extensionsLink)')) {
- event.preventDefault();
- }
- },
-
- /**
- * Keeps both menus in sync. |url| needs to come from |element.href| because
- * |iron-list| uses the entire url. Using |getAttribute| will not work.
- * @param {string} url
- */
- setSelectedUrl_: function(url) {
- this.$.topMenu.selected = this.$.subMenu.selected = url;
- },
-
- /**
- * @param {!Event} event
- * @private
- */
- onSelectorActivate_: function(event) {
- this.setSelectedUrl_(event.detail.selected);
-
- const path = new URL(event.detail.selected).pathname;
- const route = settings.getRouteForPath(path);
- assert(route, 'settings-menu has an entry with an invalid route.');
- settings.navigateTo(
- route, /* dynamicParams */ null, /* removeSearch */ true);
- },
-
- /**
- * @param {boolean} opened Whether the menu is expanded.
- * @return {string} Which icon to use.
- * @private
- * */
- arrowState_: function(opened) {
- return opened ? 'cr:arrow-drop-up' : 'cr:arrow-drop-down';
- },
-
- /** @private */
- onExtensionsLinkClick_: function() {
- chrome.metricsPrivate.recordUserAction(
- 'SettingsMenu_ExtensionsLinkClicked');
- },
-
- /**
- * @param {boolean} bool
- * @return {string}
- * @private
- */
- boolToString_: function(bool) {
- return bool.toString();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.html b/chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.html
deleted file mode 100644
index 84d7c6eeab1..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="../route.html">
-<script src="main_page_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.js b/chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.js
deleted file mode 100644
index 4a68b61aa47..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/main_page_behavior.js
+++ /dev/null
@@ -1,388 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * @enum {string}
- * A categorization of every possible Settings URL, necessary for implementing
- * a finite state machine.
- */
-settings.RouteState = {
- // Initial state before anything has loaded yet.
- INITIAL: 'initial',
- // A dialog that has a dedicated URL (e.g. /importData).
- DIALOG: 'dialog',
- // A section (basically a scroll position within the top level page, e.g,
- // /appearance.
- SECTION: 'section',
- // A subpage, or sub-subpage e.g, /searchEngins.
- SUBPAGE: 'subpage',
- // The top level Settings page, '/'.
- TOP_LEVEL: 'top-level',
-};
-
-cr.define('settings', function() {
- const RouteState = settings.RouteState;
-
- /**
- * @param {?settings.Route} route
- * @return {!settings.RouteState}
- */
- function classifyRoute(route) {
- if (!route) {
- return RouteState.INITIAL;
- }
- if (route === settings.routes.BASIC || route === settings.routes.ABOUT) {
- return RouteState.TOP_LEVEL;
- }
- if (route.isSubpage()) {
- return RouteState.SUBPAGE;
- }
- if (route.isNavigableDialog) {
- return RouteState.DIALOG;
- }
- return RouteState.SECTION;
- }
-
- /**
- * Responds to route changes by expanding, collapsing, or scrolling to
- * sections on the page. Expanded sections take up the full height of the
- * container. At most one section should be expanded at any given time.
- * @polymerBehavior
- */
- const MainPageBehavior = {
- properties: {
- /**
- * Whether a search operation is in progress or previous search results
- * are being displayed.
- * @private {boolean}
- */
- inSearchMode: {
- type: Boolean,
- value: false,
- observer: 'inSearchModeChanged_',
- },
- },
-
- /** @type {?HTMLElement} */
- scroller: null,
-
- /**
- * A map holding all valid state transitions.
- * @private {!Map<!settings.RouteState, !settings.RouteState>}
- */
- validTransitions_: (function() {
- const allStates = new Set([
- RouteState.DIALOG,
- RouteState.SECTION,
- RouteState.SUBPAGE,
- RouteState.TOP_LEVEL,
- ]);
-
- return new Map([
- [RouteState.INITIAL, allStates],
- [
- RouteState.DIALOG, new Set([
- RouteState.SECTION,
- RouteState.SUBPAGE,
- RouteState.TOP_LEVEL,
- ])
- ],
- [RouteState.SECTION, allStates],
- [RouteState.SUBPAGE, allStates],
- [RouteState.TOP_LEVEL, allStates],
- ]);
- })(),
-
- /** @override */
- attached: function() {
- this.scroller = this.domHost ? this.domHost.parentNode : document.body;
- },
-
- /**
- * Method to be defined by users of MainPageBehavior.
- * @param {!settings.Route} route
- * @return {boolean} Whether the given route is part of |this| page.
- */
- containsRoute: function(route) {
- return false;
- },
-
- /**
- * @param {boolean} current
- * @param {boolean} previous
- * @private
- */
- inSearchModeChanged_: function(current, previous) {
- // Ignore 1st occurrence which happens while the element is being
- // initialized.
- if (previous === undefined) {
- return;
- }
-
- if (!this.inSearchMode) {
- const route = settings.getCurrentRoute();
- if (this.containsRoute(route) &&
- classifyRoute(route) === RouteState.SECTION) {
- // Re-fire the showing-section event to trigger settings-main
- // recalculation of the overscroll, now that sections are not
- // hidden-by-search.
- this.fire('showing-section', this.getSection(route.section));
- }
- }
- },
-
- /**
- * @param {!settings.Route} route
- * @return {boolean}
- * @private
- */
- shouldExpandAdvanced_: function(route) {
- return (
- this.tagName == 'SETTINGS-BASIC-PAGE'
- // <if expr="chromeos">
- || this.tagName == 'OS-SETTINGS-PAGE'
- // </if>
- ) &&
- settings.routes.ADVANCED && settings.routes.ADVANCED.contains(route);
- },
-
- /**
- * Finds the settings section corresponding to the given route. If the
- * section is lazily loaded it force-renders it.
- * Note: If the section resides within "advanced" settings, a
- * 'hide-container' event is fired (necessary to avoid flashing). Callers
- * are responsible for firing a 'show-container' event.
- * @param {!settings.Route} route
- * @return {!Promise<!SettingsSectionElement>}
- * @private
- */
- ensureSectionForRoute_: function(route) {
- const section = this.getSection(route.section);
- if (section != null) {
- return Promise.resolve(section);
- }
-
- // The function to use to wait for <dom-if>s to render.
- const waitFn = Polymer.RenderStatus.beforeNextRender.bind(null, this);
-
- return new Promise(resolve => {
- if (this.shouldExpandAdvanced_(route)) {
- this.fire('hide-container');
- waitFn(() => {
- this.$$('#advancedPageTemplate').get().then(() => {
- resolve(this.getSection(route.section));
- });
- });
- } else {
- waitFn(() => {
- resolve(this.getSection(route.section));
- });
- }
- });
- },
-
- /**
- * @param {!settings.Route} route
- * @private
- */
- enterSubpage_: function(route) {
- this.lastScrollTop_ = this.scroller.scrollTop;
- this.scroller.scrollTop = 0;
- this.classList.add('showing-subpage');
- this.fire('subpage-expand');
- this.ensureSectionForRoute_(route).then(section => {
- section.classList.add('expanded');
- // Fire event used by a11y tests only.
- this.fire('settings-section-expanded');
-
- this.fire('show-container');
- });
- },
-
- /**
- * @param {!settings.Route} oldRoute
- * @return {!Promise<void>}
- * @private
- */
- enterMainPage_: function(oldRoute) {
- const oldSection = this.getSection(oldRoute.section);
- oldSection.classList.remove('expanded');
- this.classList.remove('showing-subpage');
- return new Promise((res, rej) => {
- requestAnimationFrame(() => {
- if (settings.lastRouteChangeWasPopstate()) {
- this.scroller.scrollTop = this.lastScrollTop_;
- }
- this.fire('showing-main-page');
- res();
- });
- });
- },
-
- /**
- * @param {!settings.Route} route
- * @private
- */
- scrollToSection_: function(route) {
- this.ensureSectionForRoute_(route).then(section => {
- if (!this.inSearchMode) {
- this.fire('showing-section', section);
- }
- this.fire('show-container');
- });
- },
-
- /**
- * Detects which state transition is appropriate for the given new/old
- * routes.
- * @param {!settings.Route} newRoute
- * @param {settings.Route} oldRoute
- * @private
- */
- getStateTransition_(newRoute, oldRoute) {
- const containsNew = this.containsRoute(newRoute);
- const containsOld = this.containsRoute(oldRoute);
-
- if (!containsNew && !containsOld) {
- // Nothing to do, since none of the old/new routes belong to this page.
- return null;
- }
-
- // Case where going from |this| page to an unrelated page. For example:
- // |this| is settings-basic-page AND
- // oldRoute is /searchEngines AND
- // newRoute is /help.
- if (containsOld && !containsNew) {
- return [classifyRoute(oldRoute), RouteState.TOP_LEVEL];
- }
-
- // Case where return from an unrelated page to |this| page. For example:
- // |this| is settings-basic-page AND
- // oldRoute is /help AND
- // newRoute is /searchEngines
- if (!containsOld && containsNew) {
- return [RouteState.TOP_LEVEL, classifyRoute(newRoute)];
- }
-
- // Case where transitioning between routes that both belong to |this|
- // page.
- return [classifyRoute(oldRoute), classifyRoute(newRoute)];
- },
-
- /**
- * @param {!settings.Route} newRoute
- * @param {settings.Route} oldRoute
- */
- currentRouteChanged(newRoute, oldRoute) {
- const transition = this.getStateTransition_(newRoute, oldRoute);
- if (transition === null) {
- return;
- }
-
- const oldState = transition[0];
- const newState = transition[1];
- assert(this.validTransitions_.get(oldState).has(newState));
-
- if (oldState == RouteState.TOP_LEVEL) {
- if (newState == RouteState.SECTION) {
- this.scrollToSection_(newRoute);
- } else if (newState == RouteState.SUBPAGE) {
- this.enterSubpage_(newRoute);
- }
- // Nothing to do here for the case of RouteState.DIALOG or TOP_LEVEL.
- // The latter happens when navigating from '/?search=foo' to '/'
- // (clearing search results).
- return;
- }
-
- if (oldState == RouteState.SECTION) {
- if (newState == RouteState.SECTION) {
- this.scrollToSection_(newRoute);
- } else if (newState == RouteState.SUBPAGE) {
- this.enterSubpage_(newRoute);
- } else if (newState == RouteState.TOP_LEVEL) {
- this.scroller.scrollTop = 0;
- }
- // Nothing to do here for the case of RouteState.DIALOG.
- return;
- }
-
- if (oldState == RouteState.SUBPAGE) {
- if (newState == RouteState.SECTION) {
- this.enterMainPage_(oldRoute);
-
- // Scroll to the corresponding section, only if the user explicitly
- // navigated to a section (via the menu).
- if (!settings.lastRouteChangeWasPopstate()) {
- this.scrollToSection_(newRoute);
- }
- } else if (newState == RouteState.SUBPAGE) {
- // Handle case where the two subpages belong to
- // different sections, but are linked to each other. For example
- // /storage and /accounts (in ChromeOS).
- if (!oldRoute.contains(newRoute) && !newRoute.contains(oldRoute)) {
- this.enterMainPage_(oldRoute).then(() => {
- this.enterSubpage_(newRoute);
- });
- return;
- }
-
- // Handle case of subpage to sub-subpage navigation.
- if (oldRoute.contains(newRoute)) {
- this.scroller.scrollTop = 0;
- return;
- }
- // When going from a sub-subpage to its parent subpage, scroll
- // position is automatically restored, because we focus the
- // sub-subpage entry point.
- } else if (newState == RouteState.TOP_LEVEL) {
- this.enterMainPage_(oldRoute);
- } else if (newState == RouteState.DIALOG) {
- // The only known case currently for such a transition is from
- // /storage to /clearBrowserData.
- this.enterMainPage_(oldRoute);
- }
- return;
- }
-
- if (oldState == RouteState.INITIAL) {
- if (newState == RouteState.SECTION) {
- this.scrollToSection_(newRoute);
- } else if (newState == RouteState.SUBPAGE) {
- this.enterSubpage_(newRoute);
- }
- // Nothing to do here for the case of RouteState.DIALOG and TOP_LEVEL.
- return;
- }
-
- if (oldState == RouteState.DIALOG) {
- if (newState == RouteState.SUBPAGE) {
- // The only known case currently for such a transition is from
- // /clearBrowserData back to /storage.
- this.enterSubpage_(newRoute);
- }
- // Nothing to do for all other cases.
- }
- },
-
- /**
- * TODO(dpapad): Rename this to |querySection| to distinguish it from
- * ensureSectionForRoute_() which force-renders the section as needed.
- * Helper function to get a section from the local DOM.
- * @param {string} section Section name of the element to get.
- * @return {?SettingsSectionElement}
- */
- getSection: function(section) {
- if (!section) {
- return null;
- }
- return /** @type {?SettingsSectionElement} */ (
- this.$$(`settings-section[section="${section}"]`));
- },
- };
-
- return {MainPageBehavior: MainPageBehavior};
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.html b/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.html
deleted file mode 100644
index 5cf2385350a..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="../route.html">
-
-<dom-module id="settings-animated-pages">
- <template>
- <iron-pages id="animatedPages" attr-for-selected="route-path"
- on-iron-select="onIronSelect_">
- <slot></slot>
- </iron-pages>
- </template>
- <script src="settings_animated_pages.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.js b/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
deleted file mode 100644
index 0ca50ae7967..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-animated-pages' is a container for a page and animated subpages.
- * It provides a set of common behaviors and animations.
- *
- * Example:
- *
- * <settings-animated-pages section="privacy">
- * <!-- Insert your section controls here -->
- * </settings-animated-pages>
- */
-
-Polymer({
- is: 'settings-animated-pages',
-
- behaviors: [settings.RouteObserverBehavior],
-
- properties: {
- /**
- * Routes with this section activate this element. For instance, if this
- * property is 'search', and currentRoute.section is also set to 'search',
- * this element will display the subpage in currentRoute.subpage.
- *
- * The section name must match the name specified in route.js.
- */
- section: String,
-
- /**
- * A Map specifying which element should be focused when exiting a subpage.
- * The key of the map holds a settings.Route path, and the value holds
- * either a query selector that identifies the desired element, an element
- * or a function to be run when a neon-animation-finish event is handled.
- * @type {?Map<string, (string|Element|Function)>}
- */
- focusConfig: Object,
- },
-
- /**
- * The last "previous" route reported by the router.
- * @private {?settings.Route}
- */
- previousRoute_: null,
-
- /** @override */
- created: function() {
- // Observe the light DOM so we know when it's ready.
- this.lightDomObserver_ =
- Polymer.dom(this).observeNodes(this.lightDomChanged_.bind(this));
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onIronSelect_: function(e) {
- // Call initialFocus() on the selected subpage, only if:
- // 1) Not a direct navigation (such that the search box stays focused), and
- // 2) Not a "back" navigation, in which case the anchor element should be
- // focused (further below in this function).
- if (this.previousRoute_ && !settings.lastRouteChangeWasPopstate()) {
- const subpage = this.querySelector('settings-subpage.iron-selected');
- if (subpage) {
- subpage.initialFocus();
- return;
- }
- }
-
- // Don't attempt to focus any anchor element, unless last navigation was a
- // 'pop' (backwards) navigation.
- if (!settings.lastRouteChangeWasPopstate()) {
- return;
- }
-
- if (!this.focusConfig || !this.previousRoute_) {
- return;
- }
-
- const subpagePaths = [];
- if (settings.routes.SITE_SETTINGS_COOKIES) {
- subpagePaths.push(settings.routes.SITE_SETTINGS_COOKIES.path);
- }
-
- if (settings.routes.SITE_SETTINGS_SITE_DATA) {
- subpagePaths.push(settings.routes.SITE_SETTINGS_SITE_DATA.path);
- }
-
- if (settings.routes.SITE_SETTINGS_ALL) {
- subpagePaths.push(settings.routes.SITE_SETTINGS_ALL.path);
- }
-
- // <if expr="chromeos">
- if (settings.routes.INTERNET_NETWORKS) {
- subpagePaths.push(settings.routes.INTERNET_NETWORKS.path);
- }
- // </if>
-
- // Only handle iron-select events from div elements and the
- // given whitelist of settings-subpage instances.
- const whitelist = ['settings-subpage#site-settings', 'div[route-path]'];
- whitelist.push.apply(
- whitelist,
- subpagePaths.map(path => `settings-subpage[route-path="${path}"]`));
- const query = whitelist.join(', ');
-
- if (!e.detail.item.matches(query)) {
- return;
- }
-
- // Ensure focus-config was correctly specified as a Polymer property.
- assert(this.focusConfig instanceof Map);
-
- let pathConfig = this.focusConfig.get(this.previousRoute_.path);
- if (pathConfig) {
- let handler;
- if (typeof pathConfig == 'function') {
- handler = pathConfig;
- } else {
- handler = () => {
- if (typeof pathConfig == 'string') {
- pathConfig = assert(this.querySelector(pathConfig));
- }
- cr.ui.focusWithoutInk(/** @type {!Element} */ (pathConfig));
- };
- }
- handler();
- }
- },
-
- /**
- * Called initially once the effective children are ready.
- * @private
- */
- lightDomChanged_: function() {
- if (this.lightDomReady_) {
- return;
- }
-
- this.lightDomReady_ = true;
- Polymer.dom(this).unobserveNodes(this.lightDomObserver_);
- this.runQueuedRouteChange_();
- },
-
- /**
- * Calls currentRouteChanged with the deferred route change info.
- * @private
- */
- runQueuedRouteChange_: function() {
- if (!this.queuedRouteChange_) {
- return;
- }
- this.async(this.currentRouteChanged.bind(
- this, this.queuedRouteChange_.newRoute,
- this.queuedRouteChange_.oldRoute));
- },
-
- /** @protected */
- currentRouteChanged: function(newRoute, oldRoute) {
- this.previousRoute_ = oldRoute;
-
- if (newRoute.section == this.section && newRoute.isSubpage()) {
- this.switchToSubpage_(newRoute, oldRoute);
- } else {
- this.$.animatedPages.selected = 'default';
- }
- },
-
- /**
- * Selects the subpage specified by |newRoute|.
- * @param {!settings.Route} newRoute
- * @param {!settings.Route} oldRoute
- * @private
- */
- switchToSubpage_: function(newRoute, oldRoute) {
- // Don't manipulate the light DOM until it's ready.
- if (!this.lightDomReady_) {
- this.queuedRouteChange_ = this.queuedRouteChange_ || {oldRoute: oldRoute};
- this.queuedRouteChange_.newRoute = newRoute;
- return;
- }
-
- this.ensureSubpageInstance_();
- this.$.animatedPages.selected = newRoute.path;
- },
-
- /**
- * Ensures that the template enclosing the subpage is stamped.
- * @private
- */
- ensureSubpageInstance_: function() {
- const routePath = settings.getCurrentRoute().path;
- const domIf = this.querySelector(`dom-if[route-path='${routePath}']`);
-
- // Nothing to do if the subpage isn't wrapped in a <dom-if> or the template
- // is already stamped.
- if (!domIf || domIf.if) {
- return;
- }
-
- // Set the subpage's id for use by neon-animated-pages.
- const content = Polymer.DomIf._contentForTemplate(
- /** @type {!HTMLTemplateElement} */ (domIf.firstElementChild));
- const subpage = content.querySelector('settings-subpage');
- subpage.setAttribute('route-path', routePath);
-
- // Carry over the
- // 1)'no-search' attribute or
- // 2) 'noSearch' Polymer property
- // template to the stamped instance (both cases are mapped to a 'no-search'
- // attribute intentionally), such that the stamped instance will also be
- // ignored by the searching algorithm.
- //
- // In the case were no-search is dynamically calculated use the following
- // pattern:
- //
- // <template is="dom-if" route-path="/myPath"
- // no-search="[[shouldSkipSearch_(foo, bar)">
- // <settings-subpage
- // no-search$="[[shouldSkipSearch_(foo, bar)">
- // ...
- // </settings-subpage>
- // </template>
- //
- // Note that the dom-if should always use the property and settings-subpage
- // should always use the attribute.
- if (domIf.hasAttribute('no-search') || domIf.noSearch) {
- subpage.setAttribute('no-search', '');
- }
-
- // Render synchronously so neon-animated-pages can select the subpage.
- domIf.if = true;
- domIf.render();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_section.html b/chromium/chrome/browser/resources/settings/settings_page/settings_section.html
deleted file mode 100644
index 83723fd748a..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/settings_section.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/util.html">
-
-<dom-module id="settings-section">
- <template>
- <style>
- :host {
- display: flex;
- flex-direction: column;
- outline: none;
- position: relative;
- }
-
- #header .title {
- color: var(--cr-primary-text-color);
- font-size: 108%;
- font-weight: 400;
- letter-spacing: .25px;
- margin-bottom: 12px;
- margin-top: var(--cr-section-vertical-margin);
- padding-bottom: 4px;
- padding-top: 8px;
- }
-
- :host(:not(.expanded)) #card {
- @apply --cr-card-elevation;
- background-color: var(--cr-card-background-color);
- border-radius: var(--cr-card-border-radius);
- flex: 1;
- }
-
- :host([hidden-by-search]) {
- display: none;
- }
- </style>
- <div id="header">
- <h2 class="title"
- aria-hidden$="[[getTitleHiddenStatus_(pageTitle)]]">[[pageTitle]]</h2>
- </div>
- <div id="card">
- <slot></slot>
- </div>
- </template>
- <script src="settings_section.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_section.js b/chromium/chrome/browser/resources/settings/settings_page/settings_section.js
deleted file mode 100644
index cff47f10b54..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/settings_section.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-section' shows a paper material themed section with a header
- * which shows its page title.
- *
- * The section can expand vertically to fill its container's padding edge.
- *
- * Example:
- *
- * <settings-section page-title="[[pageTitle]]" section="privacy">
- * <!-- Insert your section controls here -->
- * </settings-section>
- */
-
-// eslint-disable-next-line prefer-const
-let SettingsSectionElement = Polymer({
- is: 'settings-section',
-
- properties: {
- /**
- * The section name should match a name specified in route.js. The
- * MainPageBehavior will expand this section if this section name matches
- * currentRoute.section.
- */
- section: String,
-
- /**
- * Title for the section header. Initialize so we can use the
- * getTitleHiddenStatus_ method for accessibility.
- */
- pageTitle: {
- type: String,
- value: '',
- },
-
- /**
- * A CSS attribute used for temporarily hiding a SETTINGS-SECTION for the
- * purposes of searching.
- */
- hiddenBySearch: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
- },
-
- /**
- * Get the value to which to set the aria-hidden attribute of the section
- * heading.
- * @return {boolean|string} A return value of false will not add aria-hidden
- * while aria-hidden requires a string of 'true' to be hidden as per aria
- * specs. This function ensures we have the right return type.
- * @private
- */
- getTitleHiddenStatus_: function() {
- return this.pageTitle ? false : 'true';
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html b/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html
deleted file mode 100644
index b4ea404e35f..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html
+++ /dev/null
@@ -1,102 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/html/find_shortcut_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-ripple/paper-ripple.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-subpage">
- <template>
- <style include="cr-shared-style settings-shared">
- :host {
- background-color: var(--cr-card-background-color);
- box-sizing: border-box;
- display: block;
- left: 0;
- min-height: calc(100vh - var(--cr-toolbar-height));
- padding-bottom: 60px;
- position: absolute;
- right: 0;
- top: 0;
- @apply --cr-card-elevation;
- }
-
- #headerLine {
- min-height: 40px;
- padding-bottom: 24px;
- padding-top: 8px;
- }
-
- #learnMore {
- @apply --cr-paper-icon-button-margin;
- align-items: center;
- display: flex;
- height: var(--cr-icon-ripple-size);
- justify-content: center;
- position: relative; /* Needed for paper-ripple. */
- width: var(--cr-icon-ripple-size);
- }
-
- #title-icon {
- height: 36px;
- margin-inline-end: 12px;
- margin-inline-start: 2px;
- width: 36px;
- }
-
- cr-icon-button {
- /* Centers the ripple on the icon with appropriate margin on right. */
- margin-inline-end: 10px;
- margin-inline-start: -10px;
- }
-
- paper-spinner-lite {
- @apply --cr-icon-height-width;
- }
-
- h1 {
- flex: 1; /* Push other items to the end. */
- }
-
- cr-search-field {
- /* Keep normal icon spacing from subpage-title-extra controls. */
- margin-inline-start: 16px;
- }
- </style>
- <div class="settings-box first" id="headerLine">
- <cr-icon-button class="icon-arrow-back" id="closeButton"
- on-click="onTapBack_" aria-label="$i18n{back}"></cr-icon-button>
- <template is="dom-if" if="[[titleIcon]]">
- <img id="title-icon" src="[[titleIcon]]" aria-hidden="true">
- </template>
- <h1 class="cr-title-text">[[pageTitle]]</h1>
- <slot name="subpage-title-extra"></slot>
- <template is="dom-if" if="[[learnMoreUrl]]">
- <a id="learnMore" aria-label="$i18n{learnMore}"
- href="[[learnMoreUrl]]" target="_blank">
- <iron-icon icon="cr:help-outline"></iron-icon>
- <paper-ripple class="circle" center></paper-ripple>
- </a>
- </template>
- <template is="dom-if" if="[[searchLabel]]">
- <cr-search-field label="[[searchLabel]]"
- on-search-changed="onSearchChanged_">
- </cr-search-field>
- </template>
- <template is="dom-if" if="[[showSpinner]]">
- <paper-spinner-lite active title$="[[spinnerTitle]]">
- </paper-spinner-lite>
- </template>
- </div>
- <slot></slot>
- </template>
- <script src="settings_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.js b/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.js
deleted file mode 100644
index bea0032426f..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.js
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-subpage' shows a subpage beneath a subheader. The header contains
- * the subpage title, a search field and a back icon.
- */
-
-Polymer({
- is: 'settings-subpage',
-
- behaviors: [
- FindShortcutBehavior,
- Polymer.IronResizableBehavior,
- settings.RouteObserverBehavior,
- ],
-
- properties: {
- pageTitle: String,
-
- titleIcon: String,
-
- learnMoreUrl: String,
-
- /** Setting a |searchLabel| will enable search. */
- searchLabel: String,
-
- searchTerm: {
- type: String,
- notify: true,
- value: '',
- },
-
- /** If true shows an active spinner at the end of the subpage header. */
- showSpinner: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Title (i.e., tooltip) to be displayed on the spinner. If |showSpinner| is
- * false, this field has no effect.
- */
- spinnerTitle: {
- type: String,
- value: '',
- },
-
- /**
- * Indicates which element triggers this subpage. Used by the searching
- * algorithm to show search bubbles. It is |null| for subpages that are
- * skipped during searching.
- * @type {?HTMLElement}
- */
- associatedControl: {
- type: Object,
- value: null,
- },
-
- /** @private */
- active_: {
- type: Boolean,
- value: false,
- observer: 'onActiveChanged_',
- },
- },
-
- /** @private {boolean} */
- lastActiveValue_: false,
-
- // Override FindShortcutBehavior property.
- findShortcutListenOnAttach: false,
-
- /** @override */
- attached: function() {
- if (this.searchLabel) {
- // |searchLabel| should not change dynamically.
- this.listen(this, 'clear-subpage-search', 'onClearSubpageSearch_');
- }
- },
-
- /** @override */
- detached: function() {
- if (this.searchLabel) {
- // |searchLabel| should not change dynamically.
- this.unlisten(this, 'clear-subpage-search', 'onClearSubpageSearch_');
- }
- },
-
- /** Focuses the back button when page is loaded. */
- initialFocus: function() {
- Polymer.RenderStatus.afterNextRender(
- this, () => cr.ui.focusWithoutInk(this.$.closeButton));
- },
-
- /** @protected */
- currentRouteChanged: function(route) {
- this.active_ = this.getAttribute('route-path') == route.path;
- },
-
- /** @private */
- onActiveChanged_: function() {
- if (this.lastActiveValue_ == this.active_) {
- return;
- }
- this.lastActiveValue_ = this.active_;
-
- if (this.active_ && this.pageTitle) {
- document.title =
- loadTimeData.getStringF('settingsAltPageTitle', this.pageTitle);
- }
-
- if (!this.searchLabel) {
- return;
- }
-
- const searchField = this.$$('cr-search-field');
- if (searchField) {
- searchField.setValue('');
- }
-
- if (this.active_) {
- this.becomeActiveFindShortcutListener();
- } else {
- this.removeSelfAsFindShortcutListener();
- }
- },
-
- /**
- * Clear the value of the search field.
- * @param {!Event} e
- */
- onClearSubpageSearch_: function(e) {
- e.stopPropagation();
- this.$$('cr-search-field').setValue('');
- },
-
- /** @private */
- onTapBack_: function() {
- settings.navigateToPreviousRoute();
- },
-
- /** @private */
- onSearchChanged_: function(e) {
- this.searchTerm = e.detail;
- },
-
- // Override FindShortcutBehavior methods.
- handleFindShortcut: function(modalContextOpen) {
- if (modalContextOpen) {
- return false;
- }
- this.$$('cr-search-field').getSearchInput().focus();
- return true;
- },
-
- // Override FindShortcutBehavior methods.
- searchInputHasFocus: function() {
- const field = this.$$('cr-search-field');
- return field.getSearchInput() == field.shadowRoot.activeElement;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_page_css.html b/chromium/chrome/browser/resources/settings/settings_page_css.html
deleted file mode 100644
index 45685e85d55..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_page_css.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="settings_page/main_page_behavior.html">
-
-<dom-module id="settings-page-styles">
- <template>
- <style>
- :host {
- @apply --cr-centered-card-container;
- }
-
- :host(.showing-subpage),
- :host(.showing-subpage) #basicPage,
- :host(.showing-subpage) #advancedPage {
- /* The subpage container should extend to the bottom of the page. */
- height: 100%;
- }
-
- :host(.showing-subpage) settings-section:not(.expanded) {
- display: none;
- }
-
- :host > div > :not(.expanded) {
- /* The margin and padding here are doing two things: make the total
- * separation 24px; and make scrollIntoView align the section header
- * with the top item in the side nav menu. Both things are desired
- * by Alan (bettes@). */
- margin-bottom: 3px;
- }
-
- .expanded {
- min-height: 100%;
- }
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_shared_css.html b/chromium/chrome/browser/resources/settings/settings_shared_css.html
deleted file mode 100644
index f91bdedd09b..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_shared_css.html
+++ /dev/null
@@ -1,357 +0,0 @@
-<link rel="import" href="chrome://resources/cr_elements/search_highlight_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="settings_icons_css.html">
-<link rel="import" href="settings_vars_css.html">
-
-
-<!-- Common styles for Material Design settings. -->
-<dom-module id="settings-shared">
- <template>
- <style include="settings-icons cr-shared-style search-highlight-style">
- /* Prevent action-links from being selected to avoid accidental
- * selection when trying to click it. */
- a[is=action-link] {
- user-select: none;
- }
-
- /* Use <h2> as the "sub-header" mentioned in the UX design docs. */
- h2 {
- align-items: center;
- align-self: flex-start;
- color: var(--cr-secondary-text-color);
- display: flex;
- font-size: inherit;
- font-weight: 500;
- margin: 0;
- padding-bottom: 12px;
- padding-top: 32px;
- }
-
- iron-icon {
- flex-shrink: 0; /* Prevent distortion of icons in cramped UI. */
- }
-
- iron-icon.policy {
- margin-inline-end: var(--cr-controlled-by-spacing);
- }
-
- iron-list {
- --iron-list-items-container: {
- /* Text selection in an iron-list is problematic because the items are
- * reused. The result is the selection happens somewhat arbitrarily.
- * Add a |risk-selection| attribute to enabled selection in an
- * iron-list. */
- user-select: none;
- }
- }
-
- iron-list[risk-selection] {
- --iron-list-items-container: {
- /* On short lists where selection is likely desired, we'll risk having
- * text selection enabled. If the list is short enough that items are
- * not actually reused, the bugs with selection are not evident. */
- user-select: text;
- }
- }
-
- .separator + cr-icon-button {
- margin-inline-start: var(--cr-icon-ripple-margin);
- }
-
- /* Special case for buttons inside of toggle-buttons. */
- .settings-box settings-toggle-button cr-button:last-of-type {
- margin-inline-end: 16px;
- }
-
- /* Space out multiple buttons in the same row. */
- .settings-box cr-button + cr-button,
- .settings-box cr-button + controlled-button,
- .settings-box controlled-button + controlled-button,
- .settings-box controlled-button + cr-button {
- margin-inline-start: 8px;
- }
-
- span ~ a {
- margin-inline-start: 4px;
- }
-
- a[href] {
- color: var(--cr-link-color);
- text-decoration: none;
- }
-
- /* For elements that are simple out-links but don't look like anchors. */
- .inherit-color {
- color: inherit !important;
- }
-
- .primary-toggle {
- color: var(--cr-secondary-text-color);
- font-weight: 500;
- }
-
- .primary-toggle[checked] {
- color: var(--google-blue-500);
- }
-
- controlled-radio-button,
- cr-radio-button {
- min-height: var(--settings-row-min-height);
- }
-
- cr-radio-group {
- width: 100%;
- }
-
- cr-radio-group:focus {
- /* TODO(dbeam): why is this here? It looks weird and I'm not sure how to
- * actually trigger it with mouse/keyboard/screenreader. */
- background-color: var(--google-grey-300);
- outline: none;
- }
-
- @media (prefers-color-scheme: dark) {
- cr-radio-group:focus {
- background-color: var(--google-grey-800);
- }
- }
-
- /* See also: .no-min-width below. */
- .text-elide {
- @apply --cr-text-elide;
- }
-
- /* By default, flexbox children have min-width calculated to be the width
- * of the content. However, in some cases we might want to allow the
- * width to be smaller than the content (i.e. for long text to ellipsis).
- * In such cases this class should be applied.
- * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5) */
- .no-min-width {
- min-width: 0;
- }
-
- .header-aligned-button {
- margin-top: 12px; /* Align cr-button with <h2>. */
- }
-
- /* A list-frame is an outer container for list-items. It is intended to be
- * outside of a settings-box. A list-frame is likely to follow a
- * settings box. */
- .list-frame {
- @apply --settings-list-frame-padding;
- align-items: center;
- display: block;
- }
-
- /* A list-item is intended to be contained within a list-frame. The list
- * frame will set up the initial start margin. */
- .list-item {
- align-items: center;
- display: flex;
- min-height: var(--settings-row-min-height);
- padding: 0;
- }
-
- /* A thin separator line under a list item. */
- .list-item.underbar {
- border-bottom: var(--cr-separator-line);
- }
-
- .list-item.selected {
- font-weight: 500;
- }
-
- /* The middle part (horizontally) of a list item. */
- .list-item .middle {
- flex: 1;
- margin: 8px 16px;
- }
-
- /* The start (left in LTR) part (horizontally) of a list item. */
- .list-item > .start {
- flex: 1;
- }
-
- .list-item > label span[disabled] {
- opacity: var(--settings-disabled-opacity);
- }
-
- /* This button has no ink ripple. */
- .list-button[is='action-link'] {
- align-items: center;
- display: flex;
- flex: 1;
- font-weight: 500;
- min-height: inherit;
- }
-
- /* Link buttons use FocusOutlineManager to only show outlines when focus
- * was triggered by keyboard. */
- :host-context(html:not(.focus-outline-visible))
- .list-button[is='action-link'] {
- outline: none;
- }
-
- /* A row with two lines of text. Often the lower line will be .secondary.
- */
- .two-line {
- min-height: var(--settings-row-two-line-min-height);
- }
-
- /* A row with three lines of text. Often the lower lines will be
- * .secondary. */
- .three-line {
- min-height: var(--settings-row-three-line-min-height);
- }
-
- /* A settings-box is a horizontal row of text or controls within a
- * setting section (page or subpage). */
- .settings-box {
- @apply --cr-section;
- }
-
- .settings-box.two-line {
- min-height: var(--settings-row-two-line-min-height);
- }
-
- .settings-box.three-line {
- min-height: var(--settings-row-three-line-min-height);
- }
-
- .settings-box-text {
- box-sizing: border-box;
- padding-bottom: var(--cr-section-vertical-padding);
- padding-top: var(--cr-section-vertical-padding);
- }
-
- /* We use an explicit tag to remove the top border, rather than a
- * :first-of-type modifier. This is a conscious choice, please consult
- * with dbeam@ or dschuyler@ prior to changing it. */
- .settings-box.first,
- .settings-box.continuation {
- border-top: none;
- }
-
- h2.first {
- padding-top: 0;
- }
-
- .settings-box.block {
- display: block;
- }
-
- /* A start-aligned column. */
- .single-column {
- align-items: flex-start;
- flex-direction: column;
- justify-content: center;
- }
-
- /* A settings-box with no height other than the separator line. */
- .settings-box.line-only {
- min-height: 0;
- }
-
- /* A settings-box that is embedded in another settings-box (e.g. a control
- * that is associated with a toggle button). */
- .settings-box.embedded {
- padding-inline-start: var(--cr-section-indent-padding);
- }
-
- /* The lower line of text in a two-line row. */
- .secondary {
- @apply --cr-secondary-text;
- }
-
- /* The |:empty| CSS selector only works when there is no whitespace.
- * E.g. <div>[[foo]]</div> will be |:empty| when foo == ""; and
- * <div> [[foo]] </div> will not be |:empty| when foo == "". Ensure there
- * is no extra whitespace when the contents of .secondary may be "".
- */
- .secondary:empty {
- margin: 0;
- }
-
- /* The middle part (horizontally) of a row. */
- .settings-box .middle {
- align-items: center;
- flex: auto;
- padding-inline-start: 16px;
- }
-
- .settings-box .middle.two-line,
- .settings-box .start.two-line {
- display: flex;
- }
-
- /* The start (left in LTR) part (horizontally) of a row. */
- .settings-box .start {
- align-items: center;
- flex: auto;
- }
-
- /* For grouping elements with common flex options. */
- .settings-row {
- align-items: center;
- display: flex;
- flex-direction: row;
- max-width: 100%;
- min-width: 0; /* Workaround for text elision in sub-elements. */
- }
-
- .no-outline {
- background: none;
- outline: none;
- }
-
- /* Prevent icon-button's ripples from fighting with potential scrollbars.
- * Also apply to all iron-lists to align the buttons across them all.*/
- [scrollable],
- iron-list,
- .list-item {
- --cr-icon-button-margin-end: 0;
- }
-
- /* Helper for a list frame to automatically avoid the separator line. */
- .vertical-list > *:not(:first-of-type) {
- border-top: var(--cr-separator-line);
- }
-
- /* The separator a vertical line like a horizontal rule <hr> tag, but goes
- * the other way. An example is near the |sign out| button on the People
- * settings. */
- .separator {
- border-inline-start: var(--cr-separator-line);
- flex-shrink: 0;
- /* Match cr-button's default height. */
- height: 32px;
- margin: 0 16px;
- }
-
- .column-header {
- color: var(--cr-secondary-text-color);
- font-size: inherit;
- font-weight: 400;
- }
-
- /* Error message that appears in a toast to indicate the success or
- * failure of an operation. An example is when adding a printer. */
- .error-message {
- color: white;
- font: 13px;
- padding-bottom: 15px;
- padding-top: 15px;
- text-align: center;
- white-space: normal;
- }
-
- /* URLs should always be displayed using a LTR embedding - see
- * https://url.spec.whatwg.org/#url-rendering. */
- .url-directionality {
- direction: ltr;
- unicode-bidi: embed;
- }
- </style>
- </template>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html
deleted file mode 100644
index 5f25212f6be..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html
+++ /dev/null
@@ -1,168 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_drawer/cr_drawer.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_page_host_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/find_shortcut_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="../global_scroll_target_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../settings_main/settings_main.html">
-<link rel="import" href="../settings_menu/settings_menu.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../page_visibility.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<if expr="chromeos">
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_strings.html">
-</if>
-
-<dom-module id="settings-ui">
- <template>
- <style include="cr-page-host-style settings-shared">
- :host {
- @apply --layout-fit;
- display: flex;
- flex-direction: column;
- --settings-menu-width: 250px;
- /* Centered cards has a max-width of var(--cr-centered-card-max-width)
- * and a width of a certain percentage. Therefore, to make room for the
- * cards to be at their max width, the flex-basis of #main needs to be
- * whatever value the percentage of would equal to the max width. */
- --settings-main-basis: calc(var(--cr-centered-card-max-width) /
- var(--cr-centered-card-width-percentage));
- }
-
- cr-toolbar {
- @apply --layout-center;
- min-height: 56px;
- --cr-toolbar-left-spacer-width: var(--settings-menu-width);
- --cr-toolbar-center-basis: var(--settings-main-basis);
- }
-
- @media (prefers-color-scheme: light) {
- cr-toolbar {
- --iron-icon-fill-color: white;
- }
- }
-
- #cr-container-shadow-top {
- /* Needs to be higher than #container's z-index to appear above
- * scrollbars. */
- z-index: 2;
- }
-
- #container {
- align-items: flex-start;
- display: flex;
- flex: 1;
- overflow: overlay;
- position: relative;
- }
-
- #left,
- #main,
- #right {
- flex: 1 1 0;
- }
-
- #left {
- height: 100%;
- position: sticky;
- top: 0;
- }
-
- #left settings-menu {
- max-height: 100%;
- overflow: auto;
- overscroll-behavior: contain;
- width: var(--settings-menu-width);
- }
-
- #main {
- flex-basis: var(--settings-main-basis);
- }
-
- /* The breakpoint of 980px was decided on by the rounded sum of
- * var(--settings-main-basis), var(--settings-menu-width), and
- * var(--cr-section-padding). */
- @media (max-width: 980px) {
- #left,
- #right {
- display: none;
- }
-
- #main {
- min-width: auto;
- /* Add some padding to make room for borders and to prevent focus
- * indicators from being cropped. */
- padding: 0 3px;
- }
- }
- </style>
- <settings-prefs id="prefs" prefs="{{prefs}}"></settings-prefs>
- <cr-toolbar page-name="$i18n{settings}"
- clear-label="$i18n{clearSearch}"
- search-prompt="$i18n{searchPrompt}"
- on-cr-toolbar-menu-tap="onMenuButtonTap_"
- spinner-active="[[toolbarSpinnerActive_]]"
- menu-label="$i18n{menuButtonLabel}"
- on-search-changed="onSearchChanged_"
- role="banner"
- narrow="{{narrow_}}"
- narrow-threshold="980"
- show-menu="[[narrow_]]">
- </cr-toolbar>
- <cr-drawer id="drawer" on-close="onMenuClose_" heading="$i18n{settings}"
- align="$i18n{textdirection}">
- <div class="drawer-content">
- <template is="dom-if" id="drawerTemplate">
- <settings-menu page-visibility="[[pageVisibility_]]"
- show-android-apps="[[showAndroidApps_]]"
- show-crostini="[[showCrostini_]]"
- show-parental-controls="[[showParentalControls_]]"
- show-plugin-vm="[[showPluginVm_]]"
- have-play-store-app="[[havePlayStoreApp_]]"
- on-iron-activate="onIronActivate_"
- advanced-opened="{{advancedOpenedInMenu_}}">
- </settings-menu>
- </template>
- </div>
- </cr-drawer>
- <div id="container" class="no-outline">
- <div id="left">
- <settings-menu page-visibility="[[pageVisibility_]]"
- show-android-apps="[[showAndroidApps_]]"
- show-crostini="[[showCrostini_]]"
- show-parental-controls="[[showParentalControls_]]"
- show-plugin-vm="[[showPluginVm_]]"
- have-play-store-app="[[havePlayStoreApp_]]"
- on-iron-activate="onIronActivate_"
- advanced-opened="{{advancedOpenedInMenu_}}">
- </settings-menu>
- </div>
- <settings-main id="main" prefs="{{prefs}}"
- toolbar-spinner-active="{{toolbarSpinnerActive_}}"
- page-visibility="[[pageVisibility_]]"
- show-apps="[[showApps_]]"
- show-android-apps="[[showAndroidApps_]]"
- show-crostini="[[showCrostini_]]"
- show-parental-controls="[[showParentalControls_]]"
- show-plugin-vm="[[showPluginVm_]]"
- have-play-store-app="[[havePlayStoreApp_]]"
- advanced-toggle-expanded="{{advancedOpenedInMain_}}">
- </settings-main>
- <!-- An additional child of the flex #container to take up space,
- aligned with the right-hand child of the flex toolbar. -->
- <div id="right"></div>
- </div>
- </template>
- <script src="settings_ui.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js
deleted file mode 100644
index f3b8d60d861..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js
+++ /dev/null
@@ -1,363 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-ui' implements the UI for the Settings page.
- *
- * Example:
- *
- * <settings-ui prefs="{{prefs}}"></settings-ui>
- */
-cr.exportPath('settings');
-assert(
- !settings.defaultResourceLoaded,
- 'settings_ui.js run twice. You probably have an invalid import.');
-/** Global defined when the main Settings script runs. */
-settings.defaultResourceLoaded = true;
-
-Polymer({
- is: 'settings-ui',
-
- behaviors: [
- settings.RouteObserverBehavior,
- CrContainerShadowBehavior,
- FindShortcutBehavior,
- ],
-
- properties: {
- /**
- * Preferences state.
- */
- prefs: Object,
-
- /** @private */
- advancedOpenedInMain_: {
- type: Boolean,
- value: false,
- notify: true,
- observer: 'onAdvancedOpenedInMainChanged_',
- },
-
- /** @private */
- advancedOpenedInMenu_: {
- type: Boolean,
- value: false,
- notify: true,
- observer: 'onAdvancedOpenedInMenuChanged_',
- },
-
- /** @private {boolean} */
- toolbarSpinnerActive_: {
- type: Boolean,
- value: false,
- },
-
- /** @private */
- narrow_: {
- type: Boolean,
- observer: 'onNarrowChanged_',
- },
-
- /**
- * @private {!PageVisibility}
- */
- pageVisibility_: {type: Object, value: settings.pageVisibility},
-
- /** @private */
- showAndroidApps_: Boolean,
-
- /** @private */
- showCrostini_: Boolean,
-
- /** @private */
- showParentalControls_: Boolean,
-
- /** @private */
- showPluginVm_: Boolean,
-
- /** @private */
- havePlayStoreApp_: Boolean,
-
- /** @private */
- lastSearchQuery_: {
- type: String,
- value: '',
- },
- },
-
- listeners: {
- 'refresh-pref': 'onRefreshPref_',
- },
-
- /** @override */
- created: function() {
- settings.initializeRouteFromUrl();
- },
-
- /**
- * @override
- * @suppress {es5Strict} Object literals cannot contain duplicate keys in ES5
- * strict mode.
- */
- ready: function() {
- // Lazy-create the drawer the first time it is opened or swiped into view.
- listenOnce(this.$.drawer, 'cr-drawer-opening', () => {
- this.$.drawerTemplate.if = true;
- });
-
- window.addEventListener('popstate', e => {
- this.$.drawer.cancel();
- });
-
- CrPolicyStrings = {
- controlledSettingExtension:
- loadTimeData.getString('controlledSettingExtension'),
- controlledSettingExtensionWithoutName:
- loadTimeData.getString('controlledSettingExtensionWithoutName'),
- controlledSettingPolicy:
- loadTimeData.getString('controlledSettingPolicy'),
- controlledSettingRecommendedMatches:
- loadTimeData.getString('controlledSettingRecommendedMatches'),
- controlledSettingRecommendedDiffers:
- loadTimeData.getString('controlledSettingRecommendedDiffers'),
- // <if expr="chromeos">
- controlledSettingShared:
- loadTimeData.getString('controlledSettingShared'),
- controlledSettingWithOwner:
- loadTimeData.getString('controlledSettingWithOwner'),
- controlledSettingNoOwner:
- loadTimeData.getString('controlledSettingNoOwner'),
- controlledSettingParent:
- loadTimeData.getString('controlledSettingParent'),
- controlledSettingChildRestriction:
- loadTimeData.getString('controlledSettingChildRestriction'),
- // </if>
- };
-
- // <if expr="chromeos">
- CrOncStrings = {
- OncTypeCellular: loadTimeData.getString('OncTypeCellular'),
- OncTypeEthernet: loadTimeData.getString('OncTypeEthernet'),
- OncTypeMobile: loadTimeData.getString('OncTypeMobile'),
- OncTypeTether: loadTimeData.getString('OncTypeTether'),
- OncTypeVPN: loadTimeData.getString('OncTypeVPN'),
- OncTypeWiFi: loadTimeData.getString('OncTypeWiFi'),
- networkListItemConnected:
- loadTimeData.getString('networkListItemConnected'),
- networkListItemConnecting:
- loadTimeData.getString('networkListItemConnecting'),
- networkListItemConnectingTo:
- loadTimeData.getString('networkListItemConnectingTo'),
- networkListItemInitializing:
- loadTimeData.getString('networkListItemInitializing'),
- networkListItemNotAvailable:
- loadTimeData.getString('networkListItemNotAvailable'),
- networkListItemScanning:
- loadTimeData.getString('networkListItemScanning'),
- networkListItemSimCardLocked:
- loadTimeData.getString('networkListItemSimCardLocked'),
- networkListItemNotConnected:
- loadTimeData.getString('networkListItemNotConnected'),
- networkListItemNoNetwork:
- loadTimeData.getString('networkListItemNoNetwork'),
- vpnNameTemplate: loadTimeData.getString('vpnNameTemplate'),
- };
- // </if>
-
- // The SplitSettings feature hides OS settings in the browser settings page.
- // https://crbug.com/950007
- const showOSSettings = loadTimeData.getBoolean('showOSSettings');
- this.showAndroidApps_ = showOSSettings &&
- loadTimeData.valueExists('androidAppsVisible') &&
- loadTimeData.getBoolean('androidAppsVisible');
- this.showCrostini_ = showOSSettings &&
- loadTimeData.valueExists('showCrostini') &&
- loadTimeData.getBoolean('showCrostini');
- this.showParentalControls_ = showOSSettings &&
- loadTimeData.valueExists('showParentalControls') &&
- loadTimeData.getBoolean('showParentalControls');
- this.showPluginVm_ = showOSSettings &&
- loadTimeData.valueExists('showPluginVm') &&
- loadTimeData.getBoolean('showPluginVm');
- this.havePlayStoreApp_ = showOSSettings &&
- loadTimeData.valueExists('havePlayStoreApp') &&
- loadTimeData.getBoolean('havePlayStoreApp');
-
- this.addEventListener('show-container', () => {
- this.$.container.style.visibility = 'visible';
- });
-
- this.addEventListener('hide-container', () => {
- this.$.container.style.visibility = 'hidden';
- });
- },
-
- /** @override */
- attached: function() {
- document.documentElement.classList.remove('loading');
-
- setTimeout(function() {
- chrome.send(
- 'metricsHandler:recordTime',
- ['Settings.TimeUntilInteractive', window.performance.now()]);
- });
-
- // Preload bold Roboto so it doesn't load and flicker the first time used.
- document.fonts.load('bold 12px Roboto');
- settings.setGlobalScrollTarget(this.$.container);
-
- const scrollToTop = top => new Promise(resolve => {
- if (this.$.container.scrollTop === top) {
- resolve();
- return;
- }
-
- // When transitioning back to main page from a subpage on ChromeOS, using
- // 'smooth' scroll here results in the scroll changing to whatever is last
- // value of |top|. This happens even after setting the scroll position the
- // UI or programmatically.
- const behavior = cr.isChromeOS ? 'auto' : 'smooth';
- this.$.container.scrollTo({top: top, behavior: behavior});
- const onScroll = () => {
- this.debounce('scrollEnd', () => {
- this.$.container.removeEventListener('scroll', onScroll);
- resolve();
- }, 75);
- };
- this.$.container.addEventListener('scroll', onScroll);
- });
- this.addEventListener('scroll-to-top', e => {
- scrollToTop(e.detail.top).then(e.detail.callback);
- });
- this.addEventListener('scroll-to-bottom', e => {
- scrollToTop(e.detail.bottom - this.$.container.clientHeight)
- .then(e.detail.callback);
- });
- },
-
- /** @override */
- detached: function() {
- settings.resetRouteForTesting();
- },
-
- /** @param {!settings.Route} route */
- currentRouteChanged: function(route) {
- const urlSearchQuery = settings.getQueryParameters().get('search') || '';
- if (urlSearchQuery == this.lastSearchQuery_) {
- return;
- }
-
- this.lastSearchQuery_ = urlSearchQuery;
-
- const toolbar = /** @type {!CrToolbarElement} */ (this.$$('cr-toolbar'));
- const searchField =
- /** @type {CrToolbarSearchFieldElement} */ (toolbar.getSearchField());
-
- // If the search was initiated by directly entering a search URL, need to
- // sync the URL parameter to the textbox.
- if (urlSearchQuery != searchField.getValue()) {
- // Setting the search box value without triggering a 'search-changed'
- // event, to prevent an unnecessary duplicate entry in |window.history|.
- searchField.setValue(urlSearchQuery, true /* noEvent */);
- }
-
- this.$.main.searchContents(urlSearchQuery);
- },
-
- // Override FindShortcutBehavior methods.
- handleFindShortcut: function(modalContextOpen) {
- if (modalContextOpen) {
- return false;
- }
- this.$$('cr-toolbar').getSearchField().showAndFocus();
- return true;
- },
-
- // Override FindShortcutBehavior methods.
- searchInputHasFocus: function() {
- return this.$$('cr-toolbar').getSearchField().isSearchFocused();
- },
-
- /**
- * @param {!CustomEvent<string>} e
- * @private
- */
- onRefreshPref_: function(e) {
- return /** @type {SettingsPrefsElement} */ (this.$.prefs).refresh(e.detail);
- },
-
- /**
- * Handles the 'search-changed' event fired from the toolbar.
- * @param {!Event} e
- * @private
- */
- onSearchChanged_: function(e) {
- const query = e.detail;
- settings.navigateTo(
- settings.routes.BASIC,
- query.length > 0 ?
- new URLSearchParams('search=' + encodeURIComponent(query)) :
- undefined,
- /* removeSearch */ true);
- },
-
- /**
- * Called when a section is selected.
- * @private
- */
- onIronActivate_: function() {
- this.$.drawer.close();
- },
-
- /** @private */
- onMenuButtonTap_: function() {
- this.$.drawer.toggle();
- },
-
- /**
- * When this is called, The drawer animation is finished, and the dialog no
- * longer has focus. The selected section will gain focus if one was selected.
- * Otherwise, the drawer was closed due being canceled, and the main settings
- * container is given focus. That way the arrow keys can be used to scroll
- * the container, and pressing tab focuses a component in settings.
- * @private
- */
- onMenuClose_: function() {
- if (!this.$.drawer.wasCanceled()) {
- // If a navigation happened, MainPageBehavior#currentRouteChanged handles
- // focusing the corresponding section.
- return;
- }
-
- // Add tab index so that the container can be focused.
- this.$.container.setAttribute('tabindex', '-1');
- this.$.container.focus();
-
- listenOnce(this.$.container, ['blur', 'pointerdown'], () => {
- this.$.container.removeAttribute('tabindex');
- });
- },
-
- /** @private */
- onAdvancedOpenedInMainChanged_: function() {
- if (this.advancedOpenedInMain_) {
- this.advancedOpenedInMenu_ = true;
- }
- },
-
- /** @private */
- onAdvancedOpenedInMenuChanged_: function() {
- if (this.advancedOpenedInMenu_) {
- this.advancedOpenedInMain_ = true;
- }
- },
-
- /** @private */
- onNarrowChanged_: function() {
- if (this.$.drawer.open && !this.narrow_) {
- this.$.drawer.close();
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/settings_vars_css.html b/chromium/chrome/browser/resources/settings/settings_vars_css.html
deleted file mode 100644
index 481e31fd270..00000000000
--- a/chromium/chrome/browser/resources/settings/settings_vars_css.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-
-<!-- Common css variables for Material Design settings. -->
-<custom-style>
-<style>
- /* We keep our vars in sort order, though some vars must be defined prior to
- * others. The --settings-* vars are defined explicitly prior to the --paper-*
- * and --iron-* vars.
- */
- html {
- /* Some colors use non-MD colors. These custom colors are specified by
- * UX design (bettes@). */
-
- --settings-disabled-opacity: .65;
- --settings-error-color: var(--google-red-700);
-
- --settings-list-frame-padding: {
- padding-inline-end: var(--cr-section-padding);
- padding-inline-start: var(--cr-section-indent-padding);
- padding-bottom: 0;
- padding-top: 0;
- }
-
- /* No --settings-nav-icon-color needed in light mode. */
- --settings-nav-item-color: var(--google-grey-refresh-700);
-
- --settings-row-min-height: var(--cr-section-min-height);
- --settings-row-two-line-min-height: var(--cr-section-two-line-min-height);
- --settings-row-three-line-min-height:
- var(--cr-section-three-line-min-height);
-
- /* Spacing between a control (e.g. checkbox) and its label. */
- --settings-control-label-spacing: 20px;
-
- /* Spacing between policy (controlledBy) indicator and control. */
- --settings-controlled-by-spacing: var(--cr-controlled-by-spacing);
-
- --settings-input-max-width: var(--cr-default-input-max-width);
-
- --iron-icon-fill-color: var(--google-grey-refresh-700);
- --iron-icon-height: var(--cr-icon-size);
- --iron-icon-width: var(--cr-icon-size);
-
- --cr-radio-group-item-padding: 0;
- }
-
- @media (prefers-color-scheme: dark) {
- html {
- --iron-icon-fill-color: var(--google-grey-refresh-500);
- --settings-error-color: var(--google-red-refresh-300);
- --settings-nav-icon-color: var(--google-grey-refresh-500);
- --settings-nav-item-color: var(--cr-primary-text-color);
- }
- }
-</style>
-</custom-style>
diff --git a/chromium/chrome/browser/resources/settings/site_favicon.html b/chromium/chrome/browser/resources/settings/site_favicon.html
deleted file mode 100644
index 0b99dc90c3e..00000000000
--- a/chromium/chrome/browser/resources/settings/site_favicon.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/icon.html">
-
-<dom-module id="site-favicon">
- <template>
- <style>
- #favicon {
- background-repeat: no-repeat;
- background-size: contain;
- display: block;
- height: 16px;
- width: 16px;
- }
- </style>
- <div
- id="favicon"
- style="background-image: [[getBackgroundImage_(faviconUrl, url)]]">
- </div>
- </template>
- <script src="site_favicon.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_favicon.js b/chromium/chrome/browser/resources/settings/site_favicon.js
deleted file mode 100644
index b6d516d0e71..00000000000
--- a/chromium/chrome/browser/resources/settings/site_favicon.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'site-favicon' is the section to display the favicon given the
- * site URL.
- */
-
-Polymer({
- is: 'site-favicon',
-
- properties: {
- faviconUrl: String,
- url: String,
- },
-
- /** @private */
- getBackgroundImage_: function() {
- let backgroundImage = cr.icon.getFavicon('');
- if (this.faviconUrl) {
- const url = this.ensureUrlHasScheme_(this.faviconUrl);
- backgroundImage = cr.icon.getFavicon(url);
- } else if (this.url) {
- let url = this.removePatternWildcard_(this.url);
- url = this.ensureUrlHasScheme_(url);
- backgroundImage = cr.icon.getFaviconForPageURL(url || '', false);
- }
- return backgroundImage;
- },
-
- /**
- * Removes the wildcard prefix from a pattern string.
- * @param {string} pattern The pattern to remove the wildcard from.
- * @return {string} The resulting pattern.
- * @private
- */
- removePatternWildcard_: function(pattern) {
- if (!pattern || pattern.length === 0) {
- return pattern;
- }
-
- if (pattern.startsWith('http://[*.]')) {
- return pattern.replace('http://[*.]', 'http://');
- } else if (pattern.startsWith('https://[*.]')) {
- return pattern.replace('https://[*.]', 'https://');
- } else if (pattern.startsWith('[*.]')) {
- return pattern.substring(4, pattern.length);
- }
- return pattern;
- },
-
- /**
- * Ensures the URL has a scheme (assumes http if omitted).
- * @param {string} url The URL with or without a scheme.
- * @return {string} The URL with a scheme, or an empty string.
- * @private
- */
- ensureUrlHasScheme_: function(url) {
- if (!url || url.length === 0) {
- return url;
- }
- return url.includes('://') ? url : 'http://' + url;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.html b/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.html
deleted file mode 100644
index 6b38253df50..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_settings_behavior.html">
-
-<dom-module id="add-site-dialog">
- <template>
- <style include="settings-shared">
- #incognito {
- padding-bottom: 10px;
- }
- </style>
- <cr-dialog id="dialog" close-text="$i18n{close}">
- <div slot="title">$i18n{addSiteTitle}</div>
- <div slot="body">
- <cr-input id="site" label="$i18n{addSite}"
- placeholder="$i18n{addSiteExceptionPlaceholder}"
- value="{{site_}}" on-input="validate_"
- error-message="{{errorMessage_}}" spellcheck="false"
- autofocus></cr-input>
- <cr-checkbox id="incognito"
- hidden$="[[!showIncognitoSessionOnly_(hasIncognito,
- contentSetting)]]">
- $i18n{incognitoSiteOnly}
- </cr-checkbox>
- <cr-checkbox id="thirdParties"
- hidden$="[[shouldHideThirdPartyCookieCheckbox_(category)]]">
- $i18n{siteSettingsCookiesThirdPartyExceptionLabel}
- </cr-checkbox>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" id="add" on-click="onSubmit_"
- disabled>
- $i18n{add}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="add_site_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.js b/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.js
deleted file mode 100644
index a9962dde633..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.js
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'add-site-dialog' provides a dialog to add exceptions for a given Content
- * Settings category.
- */
-Polymer({
- is: 'add-site-dialog',
-
- behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * What kind of setting, e.g. Location, Camera, Cookies, and so on.
- * @type {settings.ContentSettingsTypes}
- */
- category: String,
-
- /**
- * Whether this is about an Allow, Block, SessionOnly, or other.
- * @type {settings.ContentSetting}
- */
- contentSetting: String,
-
- /** @private */
- hasIncognito: {
- type: Boolean,
- observer: 'hasIncognitoChanged_',
- },
-
- /**
- * The site to add an exception for.
- * @private
- */
- site_: String,
-
- /**
- * The error message to display when the pattern is invalid.
- * @private
- */
- errorMessage_: String,
- },
-
- /** @override */
- attached: function() {
- assert(this.category);
- assert(this.contentSetting);
- assert(typeof this.hasIncognito != 'undefined');
-
- this.$.dialog.showModal();
- },
-
- /**
- * Validates that the pattern entered is valid.
- * @private
- */
- validate_: function() {
- // If input is empty, disable the action button, but don't show the red
- // invalid message.
- if (this.$.site.value.trim() == '') {
- this.$.site.invalid = false;
- this.$.add.disabled = true;
- return;
- }
-
- this.browserProxy.isPatternValidForType(this.site_, this.category)
- .then(({isValid, reason}) => {
- this.$.site.invalid = !isValid;
- this.$.add.disabled = !isValid;
- this.errorMessage_ = reason || '';
- });
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.cancel();
- },
-
- /**
- * The tap handler for the Add [Site] button (adds the pattern and closes
- * the dialog).
- * @private
- */
- onSubmit_: function() {
- assert(!this.$.add.disabled);
- let primaryPattern = this.site_;
- let secondaryPattern = settings.SITE_EXCEPTION_WILDCARD;
-
- if (this.$.thirdParties.checked) {
- primaryPattern = settings.SITE_EXCEPTION_WILDCARD;
- secondaryPattern = this.site_;
- }
-
- this.browserProxy.setCategoryPermissionForPattern(
- primaryPattern, secondaryPattern, this.category, this.contentSetting,
- this.$.incognito.checked);
-
- this.$.dialog.close();
- },
-
- /** @private */
- showIncognitoSessionOnly_: function() {
- return this.hasIncognito && !loadTimeData.getBoolean('isGuest') &&
- this.contentSetting != settings.ContentSetting.SESSION_ONLY;
- },
-
- /** @private */
- hasIncognitoChanged_: function() {
- if (!this.hasIncognito) {
- this.$.incognito.checked = false;
- }
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldHideThirdPartyCookieCheckbox_: function() {
- return this.category !== settings.ContentSettingsTypes.COOKIES ||
- !loadTimeData.getBoolean('showImprovedCookieControlsForThirdParties');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/all_sites.html b/chromium/chrome/browser/resources/settings/site_settings/all_sites.html
deleted file mode 100644
index 53a046e4490..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/all_sites.html
+++ /dev/null
@@ -1,137 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="../global_scroll_target_behavior.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="site_entry.html">
-<link rel="import" href="site_settings_behavior.html">
-
-<dom-module id="all-sites">
- <template>
- <style include="settings-shared md-select">
- #sort {
- align-items: center;
- display: flex;
- margin: 0 var(--cr-icon-button-margin-start);
- margin-bottom: 50px;
- padding: 0 var(--cr-section-padding);
- }
-
- #sortMethod {
- margin-inline-start: 1em;
- }
-
- /* There is only one top-level heading for All Sites, so remove the
- * additional leading padding used for lists. */
- .list-frame.without-heading {
- padding-inline-start: var(--cr-section-padding);
- }
- </style>
- <div id="sort">
- <label id="sortLabel">$i18n{siteSettingsAllSitesSort}</label>
- <select id="sortMethod" class="md-select" aria-labelledby="sortLabel"
- on-change="onSortMethodChanged_">
- <option value="[[sortMethods_.MOST_VISITED]]">
- $i18n{siteSettingsAllSitesSortMethodMostVisited}
- </option>
- <option value="[[sortMethods_.STORAGE]]">
- $i18n{siteSettingsAllSitesSortMethodStorage}
- </option>
- <option value="[[sortMethods_.NAME]]">
- $i18n{siteSettingsAllSitesSortMethodName}
- </option>
- </select>
- </div>
- <div class="list-frame" hidden$="[[!siteGroupMapEmpty_(siteGroupMap)]]">
- <div class="list-item secondary">$i18n{emptyAllSitesPage}</div>
- </div>
- <div class="list-frame" hidden$="[[!noSearchResultFound_(filteredList_)]]">
- <div class="list-item secondary">$i18n{noSitesFound}</div>
- </div>
- <div class="list-frame without-heading" id="listContainer">
- <iron-list id="allSitesList"
- items="[[filteredList_]]"
- scroll-target="[[subpageScrollTarget]]">
- <template>
- <site-entry site-group="[[item]]" list-index="[[index]]"
- iron-list-tab-index="[[tabIndex]]"
- tabindex$="[[tabIndex]]"
- last-focused="{{lastFocused_}}"
- list-blurred="{{listBlurred_}}"
- sort-method="[[sortMethod_]]">
- </site-entry>
- </template>
- </iron-list>
- </div>
-
- <!-- Overflow menu. -->
- <cr-lazy-render id="menu">
- <template>
- <cr-action-menu>
- <button class="dropdown-item" role="menuitem"
- on-click="onConfirmResetSettings_">
- $i18n{siteSettingsSiteGroupReset}
- </button>
- <button class="dropdown-item" role="menuitem"
- on-click="onConfirmClearData_">
- $i18n{siteSettingsSiteGroupDelete}
- </button>
- </cr-action-menu>
- </template>
- </cr-lazy-render>
-
- <!-- Confirm reset settings dialog. -->
- <cr-lazy-render id="confirmResetSettings">
- <template>
- <cr-dialog close-text="$i18n{close}">
- <div slot="title">
- $i18n{siteSettingsSiteGroupResetDialogTitle}
- </div>
- <div slot="body">
- [[getFormatString_(
- '$i18nPolymer{siteSettingsSiteGroupResetConfirmation}',
- actionMenuModel_.item.etldPlus1)]]
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCloseDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onResetSettings_">
- $i18n{siteSettingsSiteResetAll}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- </cr-lazy-render>
-
- <!-- Confirm clear data dialog. -->
- <cr-lazy-render id="confirmClearData">
- <template>
- <cr-dialog close-text="$i18n{close}">
- <div slot="title">
- $i18n{siteSettingsSiteGroupDeleteDialogTitle}
- </div>
- <div slot="body">
- [[getFormatString_(
- '$i18nPolymer{siteSettingsSiteGroupDeleteConfirmation}',
- actionMenuModel_.item.etldPlus1)]]
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCloseDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onClearData_">
- $i18n{siteSettingsSiteClearStorage}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- </cr-lazy-render>
- </template>
- <script src="all_sites.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/all_sites.js b/chromium/chrome/browser/resources/settings/site_settings/all_sites.js
deleted file mode 100644
index 7f74c3bb909..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/all_sites.js
+++ /dev/null
@@ -1,511 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'all-sites' is the polymer element for showing the list of all sites under
- * Site Settings.
- */
-Polymer({
- is: 'all-sites',
-
- behaviors: [
- SiteSettingsBehavior,
- WebUIListenerBehavior,
- settings.RouteObserverBehavior,
- settings.GlobalScrollTargetBehavior,
- ],
-
- properties: {
- /**
- * Map containing sites to display in the widget, grouped into their
- * eTLD+1 names.
- * @type {!Map<string, !SiteGroup>}
- */
- siteGroupMap: {
- type: Object,
- value: function() {
- return new Map();
- },
- },
-
- /**
- * Filtered site group list.
- * @type {!Array<SiteGroup>}
- * @private
- */
- filteredList_: {
- type: Array,
- },
-
- /**
- * Needed by GlobalScrollTargetBehavior.
- * @override
- */
- subpageRoute: {
- type: Object,
- value: settings.routes.SITE_SETTINGS_ALL,
- readOnly: true,
- },
-
- /**
- * The search query entered into the All Sites search textbox. Used to
- * filter the All Sites list.
- * @private
- */
- filter: {
- type: String,
- value: '',
- observer: 'forceListUpdate_',
- },
-
- /**
- * All possible sort methods.
- * @type {!{name: string, mostVisited: string, storage: string}}
- * @private
- */
- sortMethods_: {
- type: Object,
- value: settings.SortMethod,
- readOnly: true,
- },
-
- /**
- * Stores the last selected item in the All Sites list.
- * @type {?{item: !SiteGroup, index: number}}
- * @private
- */
- selectedItem_: Object,
-
- /**
- * @private
- * Used to track the last-focused element across rows for the
- * focusRowBehavior.
- */
- lastFocused_: Object,
-
- /**
- * @private
- * Used to track whether the list of row items has been blurred for the
- * focusRowBehavior.
- */
- listBlurred_: Boolean,
-
- /**
- * @private {?{
- * index: number,
- * item: !SiteGroup,
- * path: string,
- * target: !HTMLElement
- * }}
- */
- actionMenuModel_: Object,
-
- /**
- * The selected sort method.
- * @type {!settings.SortMethod|undefined}
- * @private
- */
- sortMethod_: String,
- },
-
- /** @private {?settings.LocalDataBrowserProxy} */
- localDataBrowserProxy_: null,
-
- /** @override */
- created: function() {
- this.localDataBrowserProxy_ =
- settings.LocalDataBrowserProxyImpl.getInstance();
- },
-
- listeners: {
- 'open-menu': 'onOpenMenu_',
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'onStorageListFetched', this.onStorageListFetched.bind(this));
- this.addEventListener('site-entry-selected', e => {
- const event =
- /** @type {!CustomEvent<!{item: !SiteGroup, index: number}>} */ (e);
- this.selectedItem_ = event.detail;
- });
- this.addEventListener('site-entry-storage-updated', () => {
- this.debounce('site-entry-storage-updated', () => {
- if (this.sortMethods_ &&
- this.$.sortMethod.value == settings.SortMethod.STORAGE) {
- this.onSortMethodChanged_();
- }
- }, 500);
- });
- this.sortMethod_ = this.$.sortMethod.value;
- },
-
- /** @override */
- attached: function() {
- // Set scrollOffset so the iron-list scrolling accounts for the space the
- // title takes.
- Polymer.RenderStatus.afterNextRender(this, () => {
- this.$.allSitesList.scrollOffset = this.$.allSitesList.offsetTop;
- });
- },
-
- /**
- * Reload the site list when the all sites page is visited.
- *
- * settings.RouteObserverBehavior
- * @param {!settings.Route} currentRoute
- * @protected
- */
- currentRouteChanged: function(currentRoute) {
- settings.GlobalScrollTargetBehaviorImpl.currentRouteChanged.call(
- this, currentRoute);
- if (currentRoute == settings.routes.SITE_SETTINGS_ALL) {
- this.populateList_();
- }
- },
-
- /**
- * Retrieves a list of all known sites with site details.
- * @private
- */
- populateList_: function() {
- /** @type {!Array<settings.ContentSettingsTypes>} */
- const contentTypes = this.getCategoryList();
- // Make sure to include cookies, because All Sites handles data storage +
- // cookies as well as regular settings.ContentSettingsTypes.
- if (!contentTypes.includes(settings.ContentSettingsTypes.COOKIES)) {
- contentTypes.push(settings.ContentSettingsTypes.COOKIES);
- }
-
- this.browserProxy.getAllSites(contentTypes).then((response) => {
- // Create a new map to make an observable change.
- const newMap = /** @type {!Map<string, !SiteGroup>} */
- (new Map(this.siteGroupMap));
- response.forEach(siteGroup => {
- newMap.set(siteGroup.etldPlus1, siteGroup);
- });
- this.siteGroupMap = newMap;
- this.forceListUpdate_();
- });
- },
-
- /**
- * Integrate sites using storage into the existing sites map, as there
- * may be overlap between the existing sites.
- * @param {!Array<!SiteGroup>} list The list of sites using storage.
- */
- onStorageListFetched: function(list) {
- // Create a new map to make an observable change.
- const newMap = /** @type {!Map<string, !SiteGroup>} */
- (new Map(this.siteGroupMap));
- list.forEach(storageSiteGroup => {
- newMap.set(storageSiteGroup.etldPlus1, storageSiteGroup);
- });
- this.siteGroupMap = newMap;
- this.forceListUpdate_();
- this.focusOnLastSelectedEntry_();
- },
-
- /**
- * Filters the all sites list with the given search query text.
- * @param {!Map<string, !SiteGroup>} siteGroupMap The map of sites to filter.
- * @param {string} searchQuery The filter text.
- * @return {!Array<!SiteGroup>}
- * @private
- */
- filterPopulatedList_: function(siteGroupMap, searchQuery) {
- const result = [];
- for (const [etldPlus1, siteGroup] of siteGroupMap) {
- if (siteGroup.origins.find(
- originInfo => originInfo.origin.includes(searchQuery))) {
- result.push(siteGroup);
- }
- }
- return this.sortSiteGroupList_(result);
- },
-
- /**
- * Sorts the given SiteGroup list with the currently selected sort method.
- * @param {!Array<!SiteGroup>} siteGroupList The list of sites to sort.
- * @return {!Array<!SiteGroup>}
- * @private
- */
- sortSiteGroupList_: function(siteGroupList) {
- const sortMethod = this.$.sortMethod.value;
- if (!this.sortMethods_) {
- return siteGroupList;
- }
-
- if (sortMethod == settings.SortMethod.MOST_VISITED) {
- siteGroupList.sort(this.mostVisitedComparator_);
- } else if (sortMethod == settings.SortMethod.STORAGE) {
- siteGroupList.sort(this.storageComparator_);
- } else if (sortMethod == settings.SortMethod.NAME) {
- siteGroupList.sort(this.nameComparator_);
- }
- return siteGroupList;
- },
-
- /**
- * Comparator used to sort SiteGroups by the amount of engagement the user has
- * with the origins listed inside it. Note only the maximum engagement is used
- * for each SiteGroup (as opposed to the sum) in order to prevent domains with
- * higher numbers of origins from always floating to the top of the list.
- * @param {!SiteGroup} siteGroup1
- * @param {!SiteGroup} siteGroup2
- * @private
- */
- mostVisitedComparator_: function(siteGroup1, siteGroup2) {
- const getMaxEngagement = (max, originInfo) => {
- return (max > originInfo.engagement) ? max : originInfo.engagement;
- };
- const score1 = siteGroup1.origins.reduce(getMaxEngagement, 0);
- const score2 = siteGroup2.origins.reduce(getMaxEngagement, 0);
- return score2 - score1;
- },
-
- /**
- * Comparator used to sort SiteGroups by the amount of storage they use. Note
- * this sorts in descending order.
- * @param {!SiteGroup} siteGroup1
- * @param {!SiteGroup} siteGroup2
- * @private
- */
- storageComparator_: function(siteGroup1, siteGroup2) {
- const getOverallUsage = siteGroup => {
- let usage = 0;
- siteGroup.origins.forEach(originInfo => {
- usage += originInfo.usage;
- });
- return usage;
- };
-
- const siteGroup1Size = getOverallUsage(siteGroup1);
- const siteGroup2Size = getOverallUsage(siteGroup2);
- // Use the number of cookies as a tie breaker.
- return siteGroup2Size - siteGroup1Size ||
- siteGroup2.numCookies - siteGroup1.numCookies;
- },
-
- /**
- * Comparator used to sort SiteGroups by their eTLD+1 name (domain).
- * @param {!SiteGroup} siteGroup1
- * @param {!SiteGroup} siteGroup2
- * @private
- */
- nameComparator_: function(siteGroup1, siteGroup2) {
- return siteGroup1.etldPlus1.localeCompare(siteGroup2.etldPlus1);
- },
-
- /**
- * Called when the user chooses a different sort method to the default.
- * @private
- */
- onSortMethodChanged_: function() {
- this.sortMethod_ = this.$.sortMethod.value;
- this.filteredList_ =
- this.sortSiteGroupList_(this.filteredList_);
- // Force the iron-list to rerender its items, as the order has changed.
- this.$.allSitesList.fire('iron-resize');
- },
-
- /**
- * Forces the all sites list to update its list of items, taking into account
- * the search query and the sort method, then re-renders it.
- * @private
- */
- forceListUpdate_: function() {
- this.filteredList_ =
- this.filterPopulatedList_(this.siteGroupMap, this.filter);
- this.$.allSitesList.fire('iron-resize');
- },
-
- /**
- * Whether the |siteGroupMap| is empty.
- * @return {boolean}
- * @private
- */
- siteGroupMapEmpty_: function() {
- return !this.siteGroupMap.size;
- },
-
- /**
- * Whether the |filteredList_| is empty due to searching.
- * @return {boolean}
- * @private
- */
- noSearchResultFound_: function() {
- return !this.filteredList_.length && !this.siteGroupMapEmpty_();
- },
-
- /**
- * Focus on previously selected entry.
- * @private
- */
- focusOnLastSelectedEntry_: function() {
- if (this.selectedItem_ == null || this.siteGroupMap.size == 0) {
- return;
- }
- // Focus the site-entry to ensure the iron-list renders it, otherwise
- // the query selector will not be able to find it. Note the index is
- // used here instead of the item, in case the item was already removed.
- const index =
- Math.max(0, Math.min(this.selectedItem_.index, this.siteGroupMap.size));
- this.$.allSitesList.focusItem(index);
- this.selectedItem_ = null;
- },
-
- /**
- * Open the overflow menu and ensure that the item is visible in the scroll
- * pane when its menu is opened (it is possible to open off-screen items using
- * keyboard shortcuts).
- * @param {!CustomEvent<{
- * index: number, item: !SiteGroup,
- * path: string, target: !HTMLElement
- * }>} e
- * @private
- */
- onOpenMenu_: function(e) {
- const index = e.detail.index;
- const list = /** @type {IronListElement} */ (this.$['allSitesList']);
- if (index < list.firstVisibleIndex || index > list.lastVisibleIndex) {
- list.scrollToIndex(index);
- }
- const target = e.detail.target;
- this.actionMenuModel_ = e.detail;
- const menu = /** @type {CrActionMenuElement} */ (this.$.menu.get());
- menu.showAt(target);
- },
-
- /**
- * Confirms the resetting of all content settings for an origin.
- * @param {!Event} e
- * @private
- */
- onConfirmResetSettings_: function(e) {
- e.preventDefault();
- this.$.confirmResetSettings.get().showModal();
- },
-
- /**
- * Confirms the clearing of all storage data for an etld+1.
- * @param {!Event} e
- * @private
- */
- onConfirmClearData_: function(e) {
- e.preventDefault();
- this.$.confirmClearData.get().showModal();
- },
-
- /** @private */
- onCloseDialog_: function(e) {
- e.target.closest('cr-dialog').close();
- this.actionMenuModel_ = null;
- this.$.menu.get().close();
- },
-
- /**
- * Formats the |label| string with |name|, using $<num> as markers.
- * @param {string} label
- * @param {string} name
- * @return {string}
- * @private
- */
- getFormatString_: function(label, name) {
- return loadTimeData.substituteString(label, name);
- },
-
- /**
- * Resets all permissions for all origins listed in |siteGroup.origins|.
- * @param {!Event} e
- * @private
- */
- onResetSettings_: function(e) {
- const contentSettingsTypes = this.getCategoryList();
- const index = this.actionMenuModel_.index;
- this.browserProxy.recordAction(settings.AllSitesAction.RESET_PERMISSIONS);
- if (this.actionMenuModel_.item.etldPlus1 !=
- this.filteredList_[index].etldPlus1) {
- return;
- }
- for (let i = 0; i < this.filteredList_[index].origins.length; ++i) {
- const origin = this.filteredList_[index].origins[i].origin;
- this.browserProxy.setOriginPermissions(
- origin, contentSettingsTypes, settings.ContentSetting.DEFAULT);
- if (contentSettingsTypes.includes(
- settings.ContentSettingsTypes.PLUGINS)) {
- this.browserProxy.clearFlashPref(origin);
- }
- this.filteredList_[index].origins[i].hasPermissionSettings = false;
- }
- const updatedSiteGroup = {
- etldPlus1: this.filteredList_[index].etldPlus1,
- numCookies: this.filteredList_[index].numCookies,
- origins: []
- };
- for (let i = 0; i < this.filteredList_[index].origins.length; ++i) {
- const updatedOrigin =
- Object.assign({}, this.filteredList_[index].origins[i]);
- if (updatedOrigin.numCookies > 0 || updatedOrigin.usage > 0) {
- updatedOrigin.hasPermissionSettings = false;
- updatedSiteGroup.origins.push(updatedOrigin);
- }
- }
- if (updatedSiteGroup.origins.length > 0) {
- this.set('filteredList_.' + index, updatedSiteGroup);
- } else if (this.filteredList_[index].numCookies > 0) {
- // If there is no origin for this site group that has any data,
- // but the ETLD+1 has cookies in use, create a origin placeholder
- // for display purposes.
- const originPlaceHolder = {
- origin: 'http://' + this.filteredList_[index].etldPlus1 + '/',
- engagement: 0,
- usage: 0,
- numCookies: this.filteredList_[index].numCookies,
- hasPermissionSettings: false
- };
- updatedSiteGroup.origins.push(originPlaceHolder);
- this.set('filteredList_.' + index, updatedSiteGroup);
- } else {
- this.splice('filteredList_', index, 1);
- }
- this.$.allSitesList.fire('iron-resize');
- this.onCloseDialog_(e);
- },
-
- /**
- * Clear data and cookies for an etldPlus1.
- * @param {!Event} e
- * @private
- */
- onClearData_: function(e) {
- const index = this.actionMenuModel_.index;
- // Clean up the SiteGroup.
- this.browserProxy.clearEtldPlus1DataAndCookies(
- this.filteredList_[index].etldPlus1);
- const updatedSiteGroup = {
- etldPlus1: this.filteredList_[index].etldPlus1,
- numCookies: 0,
- origins: []
- };
- for (let i = 0; i < this.filteredList_[index].origins.length; ++i) {
- const updatedOrigin =
- Object.assign({}, this.filteredList_[index].origins[i]);
- if (updatedOrigin.hasPermissionSettings) {
- updatedOrigin.numCookies = 0;
- updatedOrigin.usage = 0;
- updatedSiteGroup.origins.push(updatedOrigin);
- }
- }
- if (updatedSiteGroup.origins.length > 0) {
- this.set('filteredList_.' + index, updatedSiteGroup);
- } else {
- this.splice('filteredList_', index, 1);
- }
- this.$.allSitesList.fire('iron-resize');
- this.onCloseDialog_(e);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.html b/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.html
deleted file mode 100644
index f39ca2af2e8..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="category-default-setting">
- <template>
- <style include="settings-shared"></style>
- <settings-toggle-button id="toggle"
- class="first"
- pref="{{controlParams_}}" label="[[optionLabel_]]"
- sub-label="[[optionDescription]]"
- disabled$="[[isToggleDisabled_(category)]]">
- </settings-toggle-button>
- <template is="dom-if" if="[[subOptionLabel]]">
- <settings-toggle-button id="subOptionToggle"
- pref="{{subControlParams_}}"
- label="[[subOptionLabel]]" sub-label="[[subOptionDescription]]"
- disabled$="[[!controlParams_.value]]">
- </settings-toggle-button>
- </template>
- </template>
- <script src="category_default_setting.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.js b/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.js
deleted file mode 100644
index a3e2d3277eb..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.js
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'category-default-setting' is the polymer element for showing a certain
- * category under Site Settings.
- */
-Polymer({
- is: 'category-default-setting',
-
- behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
- /* The second line, shown under the |optionLabel_| line. (optional) */
- optionDescription: String,
-
- /* The second line, shown under the |subOptionLabel| line. (optional) */
- subOptionDescription: String,
-
- /* The sub-option is a separate toggle. Setting this label will show the
- * additional toggle. Shown above |subOptionDescription|. (optional) */
- subOptionLabel: String,
-
- /* The on/off text for |optionLabel_| below. */
- toggleOffLabel: String,
- toggleOnLabel: String,
-
- /** @private {chrome.settingsPrivate.PrefObject} */
- controlParams_: {
- type: Object,
- value: function() {
- return /** @type {chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
-
- /**
- * The label to be shown next to the toggle (above |optionDescription|).
- * This will be either toggleOffLabel or toggleOnLabel.
- * @private
- */
- optionLabel_: String,
-
- /** @private {!DefaultContentSetting} */
- priorDefaultContentSetting_: {
- type: Object,
- value: function() {
- return /** @type {DefaultContentSetting} */ ({});
- },
- },
-
- /**
- * Cookies and Flash settings have a sub-control that is used to mimic a
- * tri-state value.
- * @private {chrome.settingsPrivate.PrefObject}
- */
- subControlParams_: {
- type: Object,
- value: function() {
- return /** @type {chrome.settingsPrivate.PrefObject} */ ({});
- },
- },
- },
-
- observers: [
- 'onCategoryChanged_(category)',
- 'onChangePermissionControl_(category, controlParams_.value, ' +
- 'subControlParams_.value)',
- ],
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'contentSettingCategoryChanged', this.onCategoryChanged_.bind(this));
- },
-
- /** @return {boolean} */
- get categoryEnabled() {
- return !!assert(this.controlParams_).value;
- },
-
- /**
- * A handler for changing the default permission value for a content type.
- * This is also called during page setup after we get the default state.
- * @private
- */
- onChangePermissionControl_: function() {
- if (this.category === undefined ||
- this.controlParams_.value === undefined ||
- this.subControlParams_.value === undefined) {
- // Do nothing unless all dependencies are defined.
- return;
- }
-
- // Don't override user settings with enforced settings.
- if (this.controlParams_.enforcement ==
- chrome.settingsPrivate.Enforcement.ENFORCED) {
- return;
- }
- switch (this.category) {
- case settings.ContentSettingsTypes.ADS:
- case settings.ContentSettingsTypes.BACKGROUND_SYNC:
- case settings.ContentSettingsTypes.IMAGES:
- case settings.ContentSettingsTypes.JAVASCRIPT:
- case settings.ContentSettingsTypes.MIXEDSCRIPT:
- case settings.ContentSettingsTypes.SOUND:
- case settings.ContentSettingsTypes.SENSORS:
- case settings.ContentSettingsTypes.PAYMENT_HANDLER:
- case settings.ContentSettingsTypes.POPUPS:
- case settings.ContentSettingsTypes.PROTOCOL_HANDLERS:
-
- // "Allowed" vs "Blocked".
- this.browserProxy.setDefaultValueForContentType(
- this.category,
- this.categoryEnabled ? settings.ContentSetting.ALLOW :
- settings.ContentSetting.BLOCK);
- break;
- case settings.ContentSettingsTypes.AUTOMATIC_DOWNLOADS:
- case settings.ContentSettingsTypes.CAMERA:
- case settings.ContentSettingsTypes.CLIPBOARD:
- case settings.ContentSettingsTypes.GEOLOCATION:
- case settings.ContentSettingsTypes.MIC:
- case settings.ContentSettingsTypes.NOTIFICATIONS:
- case settings.ContentSettingsTypes.UNSANDBOXED_PLUGINS:
- case settings.ContentSettingsTypes.MIDI_DEVICES:
- case settings.ContentSettingsTypes.USB_DEVICES:
- case settings.ContentSettingsTypes.SERIAL_PORTS:
- case settings.ContentSettingsTypes.BLUETOOTH_SCANNING:
- case settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE:
- // "Ask" vs "Blocked".
- this.browserProxy.setDefaultValueForContentType(
- this.category,
- this.categoryEnabled ? settings.ContentSetting.ASK :
- settings.ContentSetting.BLOCK);
- break;
- case settings.ContentSettingsTypes.COOKIES:
- // This category is tri-state: "Allow", "Block", "Keep data until
- // browser quits".
- let value = settings.ContentSetting.BLOCK;
- if (this.categoryEnabled) {
- value = this.subControlParams_.value ?
- settings.ContentSetting.SESSION_ONLY :
- settings.ContentSetting.ALLOW;
- }
- this.browserProxy.setDefaultValueForContentType(this.category, value);
- break;
- case settings.ContentSettingsTypes.PLUGINS:
- // "Run important content" vs. "Block".
- this.browserProxy.setDefaultValueForContentType(
- this.category,
- this.categoryEnabled ? settings.ContentSetting.IMPORTANT_CONTENT :
- settings.ContentSetting.BLOCK);
- break;
- default:
- assertNotReached('Invalid category: ' + this.category);
- }
- },
-
- /**
- * Update the control parameter values from the content settings.
- * @param {!DefaultContentSetting} update
- * @private
- */
- updateControlParams_: function(update) {
- // Early out if there is no actual change.
- if (this.priorDefaultContentSetting_.setting == update.setting &&
- this.priorDefaultContentSetting_.source == update.source) {
- return;
- }
- this.priorDefaultContentSetting_ = update;
-
- const basePref = {
- 'key': 'controlParams',
- 'type': chrome.settingsPrivate.PrefType.BOOLEAN,
- };
- if (update.source !== undefined &&
- update.source != ContentSettingProvider.PREFERENCE) {
- basePref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
- switch (update.source) {
- case ContentSettingProvider.POLICY:
- basePref.controlledBy =
- chrome.settingsPrivate.ControlledBy.DEVICE_POLICY;
- break;
- case ContentSettingProvider.SUPERVISED_USER:
- basePref.controlledBy = chrome.settingsPrivate.ControlledBy.PARENT;
- break;
- case ContentSettingProvider.EXTENSION:
- basePref.controlledBy = chrome.settingsPrivate.ControlledBy.EXTENSION;
- break;
- default:
- basePref.controlledBy =
- chrome.settingsPrivate.ControlledBy.USER_POLICY;
- break;
- }
- }
-
- const prefValue = this.computeIsSettingEnabled(update.setting);
- // The controlParams_ must be replaced (rather than just value changes) so
- // that observers will be notified of the change.
- this.controlParams_ = /** @type {chrome.settingsPrivate.PrefObject} */ (
- Object.assign({'value': prefValue}, basePref));
-
- const subPrefValue =
- this.category == settings.ContentSettingsTypes.COOKIES &&
- update.setting == settings.ContentSetting.SESSION_ONLY;
- // The subControlParams_ must be replaced (rather than just value changes)
- // so that observers will be notified of the change.
- this.subControlParams_ = /** @type {chrome.settingsPrivate.PrefObject} */ (
- Object.assign({'value': subPrefValue}, basePref));
- },
-
- /**
- * Handles changes to the category pref and the |category| member variable.
- * @private
- */
- onCategoryChanged_: function() {
- this.browserProxy.getDefaultValueForContentType(this.category)
- .then(defaultValue => {
- this.updateControlParams_(defaultValue);
-
- const categoryEnabled =
- this.computeIsSettingEnabled(defaultValue.setting);
- this.optionLabel_ =
- categoryEnabled ? this.toggleOnLabel : this.toggleOffLabel;
- });
- },
-
- /**
- * @return {boolean}
- * @private
- */
- isToggleDisabled_: function() {
- return this.category == settings.ContentSettingsTypes.POPUPS &&
- loadTimeData.getBoolean('isGuest');
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html b/chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
deleted file mode 100644
index 6fb8781c59a..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_list.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="category-setting-exceptions">
- <template>
- <site-list
- category="[[category]]"
- category-subtype="[[ContentSetting.BLOCK]]"
- category-header="[[blockHeader]]"
- read-only-list="[[getReadOnlyList_(readOnlyList, defaultManaged_)]]"
- search-filter="[[searchFilter]]"
- hidden$="[[!showBlockSiteList_]]">
- </site-list>
- <site-list
- category="[[category]]"
- category-subtype="[[ContentSetting.SESSION_ONLY]]"
- category-header="$i18n{siteSettingsSessionOnly}"
- read-only-list="[[getReadOnlyList_(readOnlyList, defaultManaged_)]]"
- search-filter="[[searchFilter]]">
- </site-list>
- <site-list
- category="[[category]]"
- category-subtype="[[ContentSetting.ALLOW]]"
- category-header="$i18n{siteSettingsAllow}"
- read-only-list="[[getReadOnlyList_(readOnlyList, defaultManaged_)]]"
- search-filter="[[searchFilter]]"
- hidden$="[[!showAllowSiteList_]]">
- </site-list>
- </template>
- <script src="category_setting_exceptions.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js b/chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js
deleted file mode 100644
index 44997259653..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'category-setting-exceptions' is the polymer element for showing a certain
- * category of exceptions under Site Settings.
- */
-Polymer({
- is: 'category-setting-exceptions',
-
- behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
-
- /**
- * The string ID of the category that this element is displaying data for.
- * See site_settings/constants.js for possible values.
- * @type {!settings.ContentSettingsTypes}
- */
- category: String,
-
- /**
- * Some content types (like Location) do not allow the user to manually
- * edit the exception list from within Settings.
- * @private
- */
- readOnlyList: {
- type: Boolean,
- value: false,
- },
-
- /**
- * True if the default value is managed by a policy.
- * @private
- */
- defaultManaged_: Boolean,
-
- /**
- * The heading text for the blocked exception list.
- */
- blockHeader: String,
-
- searchFilter: String,
-
- /**
- * If true, displays the Allow site list. Defaults to true.
- * @private
- */
- showAllowSiteList_: {
- type: Boolean,
- computed: 'computeShowAllowSiteList_(category)',
- },
-
- /**
- * If true, displays the Block site list. Defaults to true.
- */
- showBlockSiteList_: {
- type: Boolean,
- value: true,
- },
- },
-
- observers: [
- 'updateDefaultManaged_(category)',
- ],
-
- /** @override */
- ready: function() {
- this.ContentSetting = settings.ContentSetting;
- this.addWebUIListener(
- 'contentSettingCategoryChanged', this.updateDefaultManaged_.bind(this));
- },
-
- /**
- * Hides particular category subtypes if |this.category| does not support the
- * content setting of that type.
- * @return {boolean}
- * @private
- */
- computeShowAllowSiteList_: function() {
- return this.category !=
- settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE;
- },
-
- /**
- * Updates whether or not the default value is managed by a policy.
- * @private
- */
- updateDefaultManaged_: function() {
- if (this.category === undefined) {
- return;
- }
-
- this.browserProxy.getDefaultValueForContentType(this.category)
- .then(update => {
- this.defaultManaged_ =
- update.source === settings.SiteSettingSource.POLICY;
- });
- },
-
- /**
- * Returns true if this list is explicitly marked as readonly by a consumer
- * of this component or if the default value for these exceptions are managed
- * by a policy. User should not be able to set exceptions to managed default
- * values.
- * @return {boolean}
- * @private
- */
- getReadOnlyList_: function() {
- return this.readOnlyList || this.defaultManaged_;
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.html b/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.html
deleted file mode 100644
index af0304159c7..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="chooser_exception_list_entry.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="chooser-exception-list">
- <template>
- <style include="settings-shared">
- paper-tooltip {
- --paper-tooltip: var(--cr-tooltip);
- }
- </style>
-
- <div class="list-frame" id="empty-list-message"
- hidden$="[[hasExceptions_(chooserExceptions.*)]]">
- <div class="list-item secondary">[[emptyListMessage_]]</div>
- </div>
-
- <template is="dom-repeat" items="[[chooserExceptions]]">
- <chooser-exception-list-entry exception="[[item]]"
- on-show-tooltip="onShowTooltip_">
- </chooser-exception-list-entry>
- </template>
-
- <paper-tooltip id="tooltip" manual-mode position="top">
- [[tooltipText_]]
- </paper-tooltip>
- </template>
- <script src="chooser_exception_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.js b/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
deleted file mode 100644
index 79c7539ca33..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'chooser-exception-list' shows a list of chooser exceptions for a given
- * chooser type.
- */
-Polymer({
- is: 'chooser-exception-list',
-
- behaviors: [
- I18nBehavior,
- ListPropertyUpdateBehavior,
- SiteSettingsBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * Array of chooser exceptions to display in the widget.
- * @type {!Array<ChooserException>}
- */
- chooserExceptions: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The string ID of the chooser type that this element is displaying data
- * for.
- * See site_settings/constants.js for possible values.
- * @type {!settings.ChooserType}
- */
- chooserType: {
- observer: 'chooserTypeChanged_',
- type: String,
- value: settings.ChooserType.NONE,
- },
-
- /** @private */
- emptyListMessage_: {
- type: String,
- value: '',
- },
-
- /** @private */
- hasIncognito_: Boolean,
-
- /** @private */
- tooltipText_: String,
- },
-
- /** @override */
- created: function() {
- this.browserProxy_ =
- settings.SiteSettingsPrefsBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'contentSettingChooserPermissionChanged',
- this.objectWithinChooserTypeChanged_.bind(this));
- this.addWebUIListener(
- 'onIncognitoStatusChanged', this.onIncognitoStatusChanged_.bind(this));
- this.browserProxy.updateIncognitoStatus();
- },
-
- /**
- * Called when a chooser exception changes permission and updates the element
- * if |category| is equal to the settings category of this element.
- * @param {settings.ContentSettingsTypes} category The content settings type
- * that represents this permission category.
- * @param {settings.ChooserType} chooserType The content settings type that
- * represents the chooser data for this permission.
- * @private
- */
- objectWithinChooserTypeChanged_: function(category, chooserType) {
- if (category === this.category && chooserType === this.chooserType) {
- this.chooserTypeChanged_();
- }
- },
-
- /**
- * Called for each chooser-exception-list when incognito is enabled or
- * disabled. Only called on change (opening N incognito windows only fires one
- * message). Another message is sent when the *last* incognito window closes.
- * @private
- */
- onIncognitoStatusChanged_: function(hasIncognito) {
- this.hasIncognito_ = hasIncognito;
- this.populateList_();
- },
-
- /**
- * Configures the visibility of the widget and shows the list.
- * @private
- */
- chooserTypeChanged_: function() {
- if (this.chooserType == settings.ChooserType.NONE) {
- return;
- }
-
- // Set the message to display when the exception list is empty.
- switch (this.chooserType) {
- case settings.ChooserType.USB_DEVICES:
- this.emptyListMessage_ = this.i18n('noUsbDevicesFound');
- break;
- case settings.ChooserType.SERIAL_PORTS:
- this.emptyListMessage_ = this.i18n('noSerialPortsFound');
- break;
- default:
- this.emptyListMessage_ = '';
- }
-
- this.populateList_();
- },
-
- /**
- * Returns true if there are any chooser exceptions for this chooser type.
- * @return {boolean}
- * @private
- */
- hasExceptions_: function() {
- return this.chooserExceptions.length > 0;
- },
-
- /**
- * Need to use a common tooltip since the tooltip in the entry is cut off from
- * the iron-list.
- * @param{!CustomEvent<!{target: HTMLElement, text: string}>} e
- * @private
- */
- onShowTooltip_: function(e) {
- this.tooltipText_ = e.detail.text;
- const target = e.detail.target;
- // paper-tooltip normally determines the target from the |for| property,
- // which is a selector. Here paper-tooltip is being reused by multiple
- // potential targets.
- this.$.tooltip.target = target;
- const hide = () => {
- this.$.tooltip.hide();
- target.removeEventListener('mouseleave', hide);
- target.removeEventListener('blur', hide);
- target.removeEventListener('tap', hide);
- this.$.tooltip.removeEventListener('mouseenter', hide);
- };
- target.addEventListener('mouseleave', hide);
- target.addEventListener('blur', hide);
- target.addEventListener('tap', hide);
- this.$.tooltip.addEventListener('mouseenter', hide);
- this.$.tooltip.show();
- },
-
- /**
- * Populate the chooser exception list for display.
- * @private
- */
- populateList_: function() {
- this.browserProxy_.getChooserExceptionList(this.chooserType)
- .then(exceptionList => this.processExceptions_(exceptionList));
- },
-
- /**
- * Process the chooser exception list returned from the native layer.
- * @param {!Array<RawChooserException>} exceptionList
- * @private
- */
- processExceptions_: function(exceptionList) {
- const exceptions = exceptionList.map(exception => {
- const sites = exception.sites.map(this.expandSiteException);
- return Object.assign(exception, {sites});
- });
-
- if (!this.updateList(
- 'chooserExceptions', x => x.displayName, exceptions,
- true /* uidBasedUpdate */)) {
- // The chooser objects have not been changed, so check if their site
- // permissions have changed. The |exceptions| and |this.chooserExceptions|
- // arrays should be the same length.
- const siteUidGetter = x => x.origin + x.embeddingOrigin + x.incognito;
- exceptions.forEach((exception, index) => {
- const propertyPath = 'chooserExceptions.' + index + '.sites';
- this.updateList(propertyPath, siteUidGetter, exception.sites);
- }, this);
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.html b/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.html
deleted file mode 100644
index 3bf1c47f580..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_list_entry.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="chooser-exception-list-entry">
- <template>
- <style include="settings-shared"></style>
-
- <div class="settings-box first">
- <h2 class="start">[[exception.displayName]]</h2>
- </div>
-
- <div class="list-frame menu-content vertical-list" id="listContainer">
- <iron-list items="[[exception.sites]]" preserve-focus risk-selection>
- <template>
- <site-list-entry model="[[item]]"
- tabindex$="[[tabIndex]]" first$="[[!index]]"
- iron-list-tab-index="[[tabIndex]]"
- last-focused="{{lastFocused_}}"
- chooser-type="[[exception.chooserType]]"
- chooser-object="[[exception.object]]" read-only-list>
- </site-list-entry>
- </template>
- </iron-list>
- </div>
- </template>
- <script src="chooser_exception_list_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.js b/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.js
deleted file mode 100644
index 0e99b6067fd..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/chooser_exception_list_entry.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'chooser-exception-list-entry' shows a single chooser exception for a given
- * chooser type.
- */
-Polymer({
- is: 'chooser-exception-list-entry',
-
- behaviors: [SiteSettingsBehavior],
-
- properties: {
- /**
- * Chooser exception object to display in the widget.
- * @type {!ChooserException}
- */
- exception: Object,
-
- /** @private */
- lastFocused_: Object,
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/constants.html b/chromium/chrome/browser/resources/settings/site_settings/constants.html
deleted file mode 100644
index f6c86039d20..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/constants.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="constants.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/constants.js b/chromium/chrome/browser/resources/settings/site_settings/constants.js
deleted file mode 100644
index f073aa9b3db..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/constants.js
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-cr.exportPath('settings');
-
-/**
- * All possible contentSettingsTypes that we currently support configuring in
- * the UI. Both top-level categories and content settings that represent
- * individual permissions under Site Details should appear here.
- * This should be kept in sync with the |kContentSettingsTypeGroupNames| array
- * in chrome/browser/ui/webui/site_settings_helper.cc
- * @enum {string}
- */
-settings.ContentSettingsTypes = {
- COOKIES: 'cookies',
- IMAGES: 'images',
- JAVASCRIPT: 'javascript',
- SOUND: 'sound',
- PLUGINS: 'plugins', // AKA Flash.
- POPUPS: 'popups',
- GEOLOCATION: 'location',
- NOTIFICATIONS: 'notifications',
- MIC: 'media-stream-mic', // AKA Microphone.
- CAMERA: 'media-stream-camera',
- PROTOCOL_HANDLERS: 'register-protocol-handler',
- UNSANDBOXED_PLUGINS: 'ppapi-broker',
- AUTOMATIC_DOWNLOADS: 'multiple-automatic-downloads',
- BACKGROUND_SYNC: 'background-sync',
- MIDI_DEVICES: 'midi-sysex',
- USB_DEVICES: 'usb-devices',
- SERIAL_PORTS: 'serial-ports',
- ZOOM_LEVELS: 'zoom-levels',
- PROTECTED_CONTENT: 'protected-content',
- ADS: 'ads',
- CLIPBOARD: 'clipboard',
- SENSORS: 'sensors',
- PAYMENT_HANDLER: 'payment-handler',
- MIXEDSCRIPT: 'mixed-script',
- BLUETOOTH_SCANNING: 'bluetooth-scanning',
- NATIVE_FILE_SYSTEM_WRITE: 'native-file-system-write',
-};
-
-/**
- * Contains the possible string values for a given ContentSettingsTypes.
- * This should be kept in sync with the |ContentSetting| enum in
- * components/content_settings/core/common/content_settings.h
- * @enum {string}
- */
-settings.ContentSetting = {
- DEFAULT: 'default',
- ALLOW: 'allow',
- BLOCK: 'block',
- ASK: 'ask',
- SESSION_ONLY: 'session_only',
- IMPORTANT_CONTENT: 'detect_important_content',
-};
-
-/**
- * All possible ChooserTypes that we currently support configuring in the UI.
- * This should be kept in sync with the |kChooserTypeGroupNames| array in
- * chrome/browser/ui/webui/site_settings_helper.cc
- * @enum {string}
- */
-settings.ChooserType = {
- NONE: '',
- USB_DEVICES: 'usb-devices-data',
- SERIAL_PORTS: 'serial-ports-data',
-};
-
-/**
- * Contains the possible sources of a ContentSetting.
- * This should be kept in sync with the |SiteSettingSource| enum in
- * chrome/browser/ui/webui/site_settings_helper.h
- * @enum {string}
- */
-settings.SiteSettingSource = {
- ADS_FILTER_BLACKLIST: 'ads-filter-blacklist',
- DEFAULT: 'default',
- // This source is for the Protected Media Identifier / Protected Content
- // content setting only, which is only available on ChromeOS.
- DRM_DISABLED: 'drm-disabled',
- EMBARGO: 'embargo',
- EXTENSION: 'extension',
- INSECURE_ORIGIN: 'insecure-origin',
- KILL_SWITCH: 'kill-switch',
- POLICY: 'policy',
- PREFERENCE: 'preference',
-};
-
-/**
- * A category value to use for the All Sites list.
- * @type {string}
- */
-settings.ALL_SITES = 'all-sites';
-
-/**
- * An invalid subtype value.
- * @type {string}
- */
-settings.INVALID_CATEGORY_SUBTYPE = '';
-
-/**
- * Contains the possible record action types.
- * This should be kept in sync with the |AllSitesAction| enum in
- * chrome/browser/ui/webui/settings/site_settings_handler.cc
- * @enum {number}
- */
-settings.AllSitesAction = {
- LOAD_PAGE: 0,
- RESET_PERMISSIONS: 1,
- CLEAR_DATA: 2,
- ENTER_SITE_DETAILS: 3,
-};
-
-/**
- * Contains the possible sort methods.
- * @enum {string}
- */
-settings.SortMethod = {
- NAME: 'name',
- MOST_VISITED: 'most-visited',
- STORAGE: 'data-stored',
-};
-
-/**
- * String representation of the wildcard used for universal
- * match for SiteExceptions.
- * @type {string}
- */
-settings.SITE_EXCEPTION_WILDCARD = '*';
diff --git a/chromium/chrome/browser/resources/settings/site_settings/cookie_info.html b/chromium/chrome/browser/resources/settings/site_settings/cookie_info.html
deleted file mode 100644
index 77485cabf8a..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/cookie_info.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="cookie_info.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/cookie_info.js b/chromium/chrome/browser/resources/settings/site_settings/cookie_info.js
deleted file mode 100644
index 91289e40b9e..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/cookie_info.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{hasChildren: boolean,
- * id: string,
- * idPath: string,
- * title: string,
- * totalUsage: string,
- * type: string}}
- */
-let CookieDetails;
-
-/**
- * @typedef {{content: string,
- * label: string}}
- */
-let CookieDataForDisplay;
-
-// This structure maps the various cookie type names from C++ (hence the
-// underscores) to arrays of the different types of data each has, along with
-// the i18n name for the description of that data type.
-// This structure serves three purposes:
-// 1) to list what subset of the cookie data we want to show in the UI.
-// 2) What order to show it in.
-// 3) What user friendly label to prefix the data with.
-const cookieInfo = {
- 'cookie': [
- ['name', 'cookieName'], ['content', 'cookieContent'],
- ['domain', 'cookieDomain'], ['path', 'cookiePath'],
- ['sendfor', 'cookieSendFor'],
- ['accessibleToScript', 'cookieAccessibleToScript'],
- ['created', 'cookieCreated'], ['expires', 'cookieExpires']
- ],
- 'app_cache': [
- ['origin', 'appCacheOrigin'], ['size', 'localStorageSize'],
- ['modified', 'localStorageLastModified']
- ],
- 'database': [
- ['origin', 'databaseOrigin'], ['size', 'localStorageSize'],
- ['modified', 'localStorageLastModified']
- ],
- 'local_storage': [
- ['origin', 'localStorageOrigin'], ['size', 'localStorageSize'],
- ['modified', 'localStorageLastModified']
- ],
- 'indexed_db': [
- ['origin', 'indexedDbOrigin'], ['size', 'indexedDbSize'],
- ['modified', 'indexedDbLastModified']
- ],
- 'file_system': [
- ['origin', 'fileSystemOrigin'], ['persistent', 'fileSystemPersistentUsage'],
- ['temporary', 'fileSystemTemporaryUsage']
- ],
- 'service_worker':
- [['origin', 'serviceWorkerOrigin'], ['size', 'serviceWorkerSize']],
- 'shared_worker':
- [['worker', 'sharedWorkerWorker'], ['name', 'sharedWorkerName']],
- 'cache_storage': [
- ['origin', 'cacheStorageOrigin'], ['size', 'cacheStorageSize'],
- ['modified', 'cacheStorageLastModified']
- ],
- 'flash_lso': [['domain', 'cookieDomain']],
- 'media_license': [
- ['origin', 'mediaLicenseOrigin'], ['size', 'mediaLicenseSize'],
- ['modified', 'mediaLicenseLastModified']
- ],
-};
-
-/**
- * Get cookie data for a given HTML node.
- * @param {CookieDetails} data The contents of the cookie.
- * @return {!Array<CookieDataForDisplay>}
- */
-const getCookieData = function(data) {
- /** @type {!Array<CookieDataForDisplay>} */
- const out = [];
- const fields = cookieInfo[data.type];
- for (let i = 0; i < fields.length; i++) {
- const field = fields[i];
- // Iterate through the keys found in |cookieInfo| for the given |type|
- // and see if those keys are present in the data. If so, display them
- // (in the order determined by |cookieInfo|).
- const key = field[0];
- if (data[key].length > 0) {
- const entry = /** @type {CookieDataForDisplay} */ ({
- label: loadTimeData.getString(field[1]),
- content: data[key],
- });
- out.push(entry);
- }
- }
- return out;
-};
diff --git a/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.html b/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.html
deleted file mode 100644
index 2bc85d2723c..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="settings-edit-exception-dialog">
- <template>
- <style include="settings-shared"></style>
- <cr-dialog id="dialog">
- <div slot="title">$i18n{editSiteTitle}</div>
- <div slot="body">
- <cr-input label="$i18n{addSite}" value="{{origin_}}"
- placeholder="$i18n{addSiteExceptionPlaceholder}"
- on-input="validate_" error-message="{{errorMessage_}}"
- invalid="[[invalid_]]" autofocus spellcheck="false">
- </cr-input>
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCancelTap_"
- id="cancel">$i18n{cancel}</cr-button>
- <cr-button id="actionButton" class="action-button"
- on-click="onActionButtonTap_" disabled="[[invalid_]]">
- $i18n{save}
- </cr-button>
- </div>
- </cr-dialog>
- </template>
- <script src="edit_exception_dialog.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.js b/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.js
deleted file mode 100644
index 87bba9081fc..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.js
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview 'settings-edit-exception-dialog' is a component for editing a
- * site exception entry.
- */
-Polymer({
- is: 'settings-edit-exception-dialog',
-
- properties: {
- /**
- * @type {!SiteException}
- */
- model: {
- type: Object,
- observer: 'modelChanged_',
- },
-
- /** @private */
- origin_: String,
-
- /**
- * The localized error message to display when the pattern is invalid.
- * @private
- */
- errorMessage_: String,
-
- /**
- * Whether the current input is invalid.
- * @private
- */
- invalid_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @private {!settings.SiteSettingsPrefsBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- attached: function() {
- this.browserProxy_ =
- settings.SiteSettingsPrefsBrowserProxyImpl.getInstance();
- this.origin_ = this.model.origin;
-
- this.$.dialog.showModal();
- },
-
- /** @private */
- onCancelTap_: function() {
- this.$.dialog.close();
- },
-
- /** @private */
- onActionButtonTap_: function() {
- if (this.model.origin != this.origin_) {
- // The way to "edit" an exception is to remove it and and a new one.
- this.browserProxy_.resetCategoryPermissionForPattern(
- this.model.origin, this.model.embeddingOrigin, this.model.category,
- this.model.incognito);
-
- this.browserProxy_.setCategoryPermissionForPattern(
- this.origin_, this.origin_, this.model.category, this.model.setting,
- this.model.incognito);
- }
-
- this.$.dialog.close();
- },
-
- /** @private */
- validate_: function() {
- if (this.$$('cr-input').value.trim() == '') {
- this.invalid_ = true;
- return;
- }
-
- this.browserProxy_.isPatternValidForType(this.origin_, this.model.category)
- .then(({isValid, reason}) => {
- this.invalid_ = !isValid;
- this.errorMessage_ = reason || '';
- });
- },
-
- /** @private */
- modelChanged_: function() {
- if (!this.model) {
- this.$.dialog.cancel();
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html b/chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html
deleted file mode 100644
index 00f30d023ca..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="local_data_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.js b/chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.js
deleted file mode 100644
index 56112da2884..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.js
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the Cookies and Local Storage Data
- * section.
- */
-
-/**
- * @typedef {{
- * id: string,
- * start: number,
- * children: !Array<CookieDetails>,
- * }}
- */
-let CookieList;
-
-/**
- * @typedef {{
- * data: !Object,
- * id: string,
- * }}
- */
-let LocalDataItem;
-
-/**
- * TODO(dschuyler): add |filter| and |order|.
- * @typedef {{
- * items: !Array<!LocalDataItem>,
- * total: number,
- * }}
- */
-let LocalDataList;
-
-/**
- * Number of cookies attached to a given domain / eTLD+1.
- * @typedef {{
- * etldPlus1: string,
- * numCookies: number,
- * }}
- */
-let EtldPlus1CookieNumber;
-
-cr.define('settings', function() {
- /** @interface */
- class LocalDataBrowserProxy {
- /**
- * @param {string} filter Search filter (use "" for none).
- * @return {!Promise<!LocalDataList>}
- */
- getDisplayList(filter) {}
-
- /**
- * Removes all local data (local storage, cookies, etc.).
- * Note: on-tree-item-removed will not be sent.
- * @return {!Promise} To signal completion.
- */
- removeAll() {}
-
- /**
- * Remove items that pass the current filter. Completion signaled by
- * on-tree-item-removed.
- */
- removeShownItems() {}
-
- /**
- * Remove a specific list item. Completion signaled by on-tree-item-removed.
- * @param {string} id Which element to delete.
- */
- removeItem(id) {}
-
- /**
- * Gets the cookie details for a particular site.
- * @param {string} site The name of the site.
- * @return {!Promise<!CookieList>}
- */
- getCookieDetails(site) {}
-
- /**
- * Gets the plural string for a given number of cookies.
- * @param {number} numCookies The number of cookies.
- * @return {!Promise<string>}
- */
- getNumCookiesString(numCookies) {}
-
- /**
- * Reloads all local data.
- * TODO(dschuyler): rename function to reload().
- * @return {!Promise} To signal completion.
- */
- reloadCookies() {}
-
- /**
- * TODO(dschuyler): merge with removeItem().
- * Removes a given cookie.
- * @param {string} path The path to the parent cookie.
- */
- removeCookie(path) {}
-
- /**
- * Removes all SameSite=None cookies, as well as storage available in
- * third-party contexts.
- * Note: on-tree-item-removed will not be sent.
- * @return {!Promise} To signal completion.
- */
- removeAllThirdPartyCookies() {}
- }
-
- /**
- * @implements {settings.LocalDataBrowserProxy}
- */
- class LocalDataBrowserProxyImpl {
- /** @override */
- getDisplayList(filter) {
- return cr.sendWithPromise('localData.getDisplayList', filter);
- }
-
- /** @override */
- removeAll() {
- return cr.sendWithPromise('localData.removeAll');
- }
-
- /** @override */
- removeShownItems() {
- chrome.send('localData.removeShownItems');
- }
-
- /** @override */
- removeItem(id) {
- chrome.send('localData.removeItem', [id]);
- }
-
- /** @override */
- getCookieDetails(site) {
- return cr.sendWithPromise('localData.getCookieDetails', site);
- }
-
- /** @override */
- getNumCookiesString(numCookies) {
- return cr.sendWithPromise('localData.getNumCookiesString', numCookies);
- }
-
- /** @override */
- reloadCookies() {
- return cr.sendWithPromise('localData.reload');
- }
-
- /** @override */
- removeCookie(path) {
- chrome.send('localData.removeCookie', [path]);
- }
-
- /** @override */
- removeAllThirdPartyCookies() {
- return cr.sendWithPromise('localData.removeThirdPartyCookies');
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(LocalDataBrowserProxyImpl);
-
- return {
- LocalDataBrowserProxy: LocalDataBrowserProxy,
- LocalDataBrowserProxyImpl: LocalDataBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/media_picker.html b/chromium/chrome/browser/resources/settings/site_settings/media_picker.html
deleted file mode 100644
index 92f8c15fdf3..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/media_picker.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-
-<dom-module id="media-picker">
- <template>
- <style include="settings-shared md-select">
- :host {
- display: block;
- }
- </style>
- <div class="settings-box first" id="picker" hidden>
- <select id="mediaPicker" class="md-select" on-change="onChange_"
- aria-label$="[[label]]">
- <template is="dom-repeat" items="[[devices]]">
- <option value$="[[item.id]]">[[item.name]]</option>
- </template>
- </select>
- </div>
- </template>
- <script src="media_picker.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/media_picker.js b/chromium/chrome/browser/resources/settings/site_settings/media_picker.js
deleted file mode 100644
index 85137a83a2a..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/media_picker.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'media-picker' handles showing the dropdown allowing users to select the
- * default camera/microphone.
- */
-Polymer({
- is: 'media-picker',
-
- behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * The type of media picker, either 'camera' or 'mic'.
- */
- type: String,
-
- /** Label for a11y purposes. */
- label: String,
-
- /**
- * The devices available to pick from.
- * @type {Array<MediaPickerEntry>}
- */
- devices: Array,
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'updateDevicesMenu', this.updateDevicesMenu_.bind(this));
- this.browserProxy.getDefaultCaptureDevices(this.type);
- },
-
- /**
- * Updates the microphone/camera devices menu with the given entries.
- * @param {string} type The device type.
- * @param {!Array<MediaPickerEntry>} devices List of available devices.
- * @param {string} defaultDevice The unique id of the current default device.
- */
- updateDevicesMenu_: function(type, devices, defaultDevice) {
- if (type != this.type) {
- return;
- }
-
- this.$.picker.hidden = devices.length == 0;
- if (devices.length > 0) {
- this.devices = devices;
-
- // Wait for <select> to be populated.
- this.async(() => {
- this.$.mediaPicker.value = defaultDevice;
- });
- }
- },
-
- /**
- * A handler for when an item is selected in the media picker.
- * @private
- */
- onChange_: function() {
- this.browserProxy.setDefaultCaptureDevice(
- this.type, this.$.mediaPicker.value);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/pdf_documents.html b/chromium/chrome/browser/resources/settings/site_settings/pdf_documents.html
deleted file mode 100644
index a6a0b7cdcfe..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/pdf_documents.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-pdf-documents">
- <template>
- <style include="settings-shared">
- .secondary {
- margin-top: 0; /* Cancel separation between main and secondary text. */
- }
- </style>
- <settings-toggle-button id="toggle" class="first two-line"
- label="$i18n{siteSettingsPdfDownloadPdfs}"
- pref="{{prefs.plugins.always_open_pdf_externally}}">
- </settings-toggle-button>
- </template>
- <script src="pdf_documents.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/pdf_documents.js b/chromium/chrome/browser/resources/settings/site_settings/pdf_documents.js
deleted file mode 100644
index 66f61a6db59..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/pdf_documents.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-pdf-documents' is the polymer element for showing the
- * settings for viewing PDF documents under Site Settings.
- */
-
-Polymer({
- is: 'settings-pdf-documents',
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html b/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html
deleted file mode 100644
index 269709ab022..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html
+++ /dev/null
@@ -1,103 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="protocol-handlers">
- <template>
- <style include="settings-shared">
- :host {
- display: block;
- }
-
- .column-header {
- margin-bottom: 15px;
- margin-inline-start: 20px;
- margin-top: 15px;
- }
- </style>
- <div class="settings-box first two-line">
- <div id="categoryLabel" class="start" on-click="categoryLabelClicked_"
- actionable>
- [[computeHandlersDescription_(categoryEnabled)]]
- </div>
- <cr-toggle id="toggle" checked="{{categoryEnabled}}"
- on-change="onToggleChange_" aria-labelledby="categoryLabel">
- </cr-toggle>
- </div>
-
- <template is="dom-repeat" items="[[protocols]]" as="protocol">
- <div class="column-header">[[protocol.protocol_display_name]]</div>
-
- <div class="list-frame menu-content vertical-list">
- <template is="dom-repeat" items="[[protocol.handlers]]">
-
- <div class="list-item">
- <site-favicon url="[[item.host]]"></site-favicon>
- <div class="middle" >
- <div class="protocol-host">
- <span class="url-directionality">[[item.host]]</span>
- </div>
- <div class="secondary protocol-default"
- hidden$="[[!item.is_default]]">
- $i18n{handlerIsDefault}
- </div>
- </div>
-
- <cr-icon-button class="icon-more-vert" on-click="showMenu_"
- title="$i18n{moreActions}"></cr-icon-button>
- </div>
- </template>
- </div>
- </template>
-
- <cr-action-menu>
- <button class="dropdown-item" on-click="onDefaultClick_"
- id="defaultButton" hidden$="[[actionMenuModel_.is_default]]">
- $i18n{handlerSetDefault}
- </button>
- <button class="dropdown-item" on-click="onRemoveClick_"
- id="removeButton">
- $i18n{handlerRemove}
- </button>
- </cr-action-menu>
-
- <template is="dom-if" if="[[ignoredProtocols.length]]">
- <div class="column-header">$i18n{siteSettingsBlocked}</div>
- <div class="list-frame menu-content vertical-list">
- <template is="dom-repeat" items="[[ignoredProtocols]]">
- <div class="list-item">
- <site-favicon url="[[item.host]]"></site-favicon>
- <div class="middle" >
- <div class="protocol-host">
- <span class="url-directionality">[[item.host]]</span></div>
- <div class="secondary protocol-protocol">
- [[item.protocol_display_name]]
- </div>
- </div>
- <cr-icon-button class="icon-clear" id="removeIgnoredButton"
- on-click="onRemoveIgnored_" title="$i18n{handlerRemove}">
- </cr-icon-button>
- </div>
- </template>
- </div>
- </template>
-
-<if expr="chromeos">
- <template is="dom-if" if="[[settingsAppAvailable_]]">
- <cr-link-row on-click="onManageAndroidAppsClick_"
- label="$i18n{androidAppsManageAppLinks}" external></cr-link-row>
- </template>
-</if>
- </template>
- <script src="protocol_handlers.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js b/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js
deleted file mode 100644
index 5fe1b814e49..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'protocol-handlers' is the polymer element for showing the
- * protocol handlers category under Site Settings.
- */
-
-/**
- * All possible actions in the menu.
- * @enum {string}
- */
-const MenuActions = {
- SET_DEFAULT: 'SetDefault',
- REMOVE: 'Remove',
-};
-
-/**
- * @typedef {{host: string,
- * is_default: boolean,
- * protocol: string,
- * protocol_display_name: string,
- * spec: string}}
- */
-let HandlerEntry;
-
-/**
- * @typedef {{handlers: !Array<!HandlerEntry>,
- * protocol: string,
- * protocol_display_name: string}}
- */
-let ProtocolEntry;
-
-Polymer({
- is: 'protocol-handlers',
-
- behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * Represents the state of the main toggle shown for the category.
- */
- categoryEnabled: Boolean,
-
- /**
- * Array of protocols and their handlers.
- * @type {!Array<!ProtocolEntry>}
- */
- protocols: Array,
-
- /**
- * The targetted object for menu operations.
- * @private {?HandlerEntry}
- */
- actionMenuModel_: Object,
-
- /* Labels for the toggle on/off positions. */
- toggleOffLabel: String,
- toggleOnLabel: String,
-
- /**
- * Array of ignored (blocked) protocols.
- * @type {!Array<!HandlerEntry>}
- */
- ignoredProtocols: Array,
-
- // <if expr="chromeos">
- /** @private */
- settingsAppAvailable_: {
- type: Boolean,
- value: false,
- },
- // </if>
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'setHandlersEnabled', this.setHandlersEnabled_.bind(this));
- this.addWebUIListener(
- 'setProtocolHandlers', this.setProtocolHandlers_.bind(this));
- this.addWebUIListener(
- 'setIgnoredProtocolHandlers',
- this.setIgnoredProtocolHandlers_.bind(this));
- this.browserProxy.observeProtocolHandlers();
- },
-
- // <if expr="chromeos">
- /** @override */
- attached: function() {
- if (settings.AndroidAppsBrowserProxyImpl) {
- cr.addWebUIListener(
- 'android-apps-info-update', this.androidAppsInfoUpdate_.bind(this));
- settings.AndroidAppsBrowserProxyImpl.getInstance()
- .requestAndroidAppsInfo();
- }
- },
- // </if>
-
- // <if expr="chromeos">
- /**
- * Receives updates on whether or not ARC settings app is available.
- * @param {AndroidAppsInfo} info
- * @private
- */
- androidAppsInfoUpdate_: function(info) {
- this.settingsAppAvailable_ = info.settingsAppAvailable;
- },
- // </if>
-
- /** @private */
- categoryLabelClicked_: function() {
- this.$.toggle.click();
- },
-
- /**
- * Obtains the description for the main toggle.
- * @return {string} The description to use.
- * @private
- */
- computeHandlersDescription_: function() {
- return this.categoryEnabled ? this.toggleOnLabel : this.toggleOffLabel;
- },
-
- /**
- * Updates the main toggle to set it enabled/disabled.
- * @param {boolean} enabled The state to set.
- * @private
- */
- setHandlersEnabled_: function(enabled) {
- this.categoryEnabled = enabled;
- },
-
- /**
- * Updates the list of protocol handlers.
- * @param {!Array<!ProtocolEntry>} protocols The new protocol handler list.
- * @private
- */
- setProtocolHandlers_: function(protocols) {
- this.protocols = protocols;
- },
-
- /**
- * Updates the list of ignored protocol handlers.
- * @param {!Array<!HandlerEntry>} ignoredProtocols The new (ignored) protocol
- * handler list.
- * @private
- */
- setIgnoredProtocolHandlers_: function(ignoredProtocols) {
- this.ignoredProtocols = ignoredProtocols;
- },
-
- /**
- * Closes action menu and resets action menu model
- * @private
- */
- closeActionMenu_: function() {
- this.$$('cr-action-menu').close();
- this.actionMenuModel_ = null;
- },
-
- /**
- * A handler when the toggle is flipped.
- * @private
- */
- onToggleChange_: function(event) {
- this.browserProxy.setProtocolHandlerDefault(this.categoryEnabled);
- },
-
- /**
- * The handler for when "Set Default" is selected in the action menu.
- * @private
- */
- onDefaultClick_: function() {
- const item = this.actionMenuModel_;
- this.browserProxy.setProtocolDefault(item.protocol, item.spec);
- this.closeActionMenu_();
- },
-
- /**
- * The handler for when "Remove" is selected in the action menu.
- * @private
- */
- onRemoveClick_: function() {
- const item = this.actionMenuModel_;
- this.browserProxy.removeProtocolHandler(item.protocol, item.spec);
- this.closeActionMenu_();
- },
-
- /**
- * Handler for removing handlers that were blocked
- * @private
- */
- onRemoveIgnored_: function(event) {
- const item = event.model.item;
- this.browserProxy.removeProtocolHandler(item.protocol, item.spec);
- },
-
- /**
- * A handler to show the action menu next to the clicked menu button.
- * @param {!{model: !{item: HandlerEntry}}} event
- * @private
- */
- showMenu_: function(event) {
- this.actionMenuModel_ = event.model.item;
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
- .showAt(
- /** @type {!Element} */ (/** @type {!Event} */ (event).target));
- },
-
- // <if expr="chromeos">
- /**
- * Opens an activity to handle App links (preferred apps).
- * @private
- */
- onManageAndroidAppsClick_: function() {
- this.browserProxy.showAndroidManageAppLinks();
- },
- // </if>
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_data.html b/chromium/chrome/browser/resources/settings/site_settings/site_data.html
deleted file mode 100644
index f1a028c92b5..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_data.html
+++ /dev/null
@@ -1,101 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="../global_scroll_target_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="cookie_info.html">
-<link rel="import" href="local_data_browser_proxy.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_data_entry.html">
-
-<dom-module id="site-data">
- <template>
- <style include="settings-shared">
- paper-spinner-lite {
- @apply --cr-icon-height-width;
- opacity: 0;
- transition-delay: 1s;
- }
-
- paper-spinner-lite[active] {
- opacity: 1;
- }
-
- #removeShowingSites {
- margin-inline-start: auto;
- }
- </style>
- <div class="settings-box continuation">
- <paper-spinner-lite active="[[isLoading_]]"></paper-spinner-lite>
- <cr-button disabled$="[[isLoading_]]" id="removeShowingSites"
- on-click="onRemoveShowingSitesTap_" hidden$="[[!sites.length]]">
- [[computeRemoveLabel_(filter)]]
- </cr-button>
- <cr-button disabled$="[[isLoading_]]" id="removeThirdPartyCookies"
- on-click="onRemoveThirdPartyCookiesTap_"
- hidden$="[[!showRemoveThirdPartyCookies_(sites.length, filter)]]">
- $i18n{siteSettingsCookieRemoveAllThirdParty}
- </cr-button>
- </div>
- <iron-list id="list" items="[[sites]]" preserve-focus
- scroll-target="[[subpageScrollTarget]]" class="cr-separators">
- <template>
- <site-data-entry id$="siteItem_[[item.site]]" actionable
- model="[[item]]" first$="[[!index]]" tabindex$="[[tabIndex]]"
- iron-list-tab-index="[[tabIndex]]" last-focused="{{lastFocused_}}"
- list-blurred="{{listBlurred_}}" on-click="onSiteClick_"
- on-remove-site="onRemoveSite_">
- </site-data-entry>
- </template>
- </iron-list>
-
- <!-- Confirm Delete dialog -->
- <cr-dialog id="confirmDeleteDialog" close-text="$i18n{close}"
- on-close="onConfirmDeleteDialogClosed_">
- <div slot="title">
- $i18n{siteSettingsCookieRemoveDialogTitle}
- </div>
- <div slot="body">$i18n{siteSettingsCookieRemoveMultipleConfirmation}</div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCloseDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onConfirmDelete_">
- $i18n{siteSettingsCookiesClearAll}
- </cr-button>
- </div>
- </cr-dialog>
-
- <!-- Confirm Delete Third Party Cookies dialog -->
- <cr-dialog id="confirmDeleteThirdPartyDialog" close-text="$i18n{close}"
- on-close="onConfirmDeleteThirdPartyDialogClosed_">
- <div slot="title">
- $i18n{siteSettingsCookieRemoveThirdPartyDialogTitle}
- </div>
- <div slot="body">
- $i18n{siteSettingsCookieRemoveThirdPartyConfirmation}
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCloseThirdPartyDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onConfirmThirdPartyDelete_">
- $i18n{siteSettingsCookiesClearThirdParty}
- </cr-button>
- </div>
- </cr-dialog>
-
- </template>
- <script src="site_data.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_data.js b/chromium/chrome/browser/resources/settings/site_settings/site_data.js
deleted file mode 100644
index cd6370aa91c..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_data.js
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-data' handles showing the local storage summary list for all sites.
- */
-
-/**
- * @typedef {{
- * site: string,
- * id: string,
- * localData: string,
- * }}
- */
-let CookieDataSummaryItem;
-
-/**
- * @typedef {{
- * id: string,
- * start: number,
- * count: number,
- * }}
- */
-let CookieRemovePacket;
-
-Polymer({
- is: 'site-data',
-
- behaviors: [
- I18nBehavior,
- ListPropertyUpdateBehavior,
- settings.GlobalScrollTargetBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * The current filter applied to the cookie data list.
- */
- filter: {
- observer: 'updateSiteList_',
- notify: true,
- type: String,
- },
-
- /** @type {!Map<string, (string|Function)>} */
- focusConfig: {
- type: Object,
- observer: 'focusConfigChanged_',
- },
-
- isLoading_: Boolean,
-
- /** @type {!Array<!CookieDataSummaryItem>} */
- sites: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * settings.GlobalScrollTargetBehavior
- * @override
- */
- subpageRoute: {
- type: Object,
- value: settings.routes.SITE_SETTINGS_SITE_DATA,
- },
-
- /** @private */
- lastFocused_: Object,
-
- /** @private */
- listBlurred_: Boolean,
- },
-
- /** @private {settings.LocalDataBrowserProxy} */
- browserProxy_: null,
-
- /**
- * When navigating to site data details sub-page, |lastSelected_| holds the
- * site name as well as the index of the selected site. This is used when
- * navigating back to site data in order to focus on the correct site.
- * @private {!{item: CookieDataSummaryItem, index: number}|null}
- */
- lastSelected_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.LocalDataBrowserProxyImpl.getInstance();
- this.addWebUIListener(
- 'on-tree-item-removed', this.updateSiteList_.bind(this));
- },
-
- /**
- * Reload cookies when the site data page is visited.
- *
- * settings.RouteObserverBehavior
- * @param {!settings.Route} currentRoute
- * @protected
- */
- currentRouteChanged: function(currentRoute) {
- settings.GlobalScrollTargetBehaviorImpl.currentRouteChanged.call(
- this, currentRoute);
- if (currentRoute == settings.routes.SITE_SETTINGS_SITE_DATA) {
- this.isLoading_ = true;
- // Needed to fix iron-list rendering issue. The list will not render
- // correctly until a scroll occurs.
- // See https://crbug.com/853906.
- const ironList = /** @type {!IronListElement} */ (this.$$('iron-list'));
- ironList.scrollToIndex(0);
- this.browserProxy_.reloadCookies().then(this.updateSiteList_.bind(this));
- }
- },
-
- /**
- * @param {!Map<string, (string|Function)>} newConfig
- * @param {?Map<string, (string|Function)>} oldConfig
- * @private
- */
- focusConfigChanged_: function(newConfig, oldConfig) {
- // focusConfig is set only once on the parent, so this observer should only
- // fire once.
- assert(!oldConfig);
-
- // Populate the |focusConfig| map of the parent <settings-animated-pages>
- // element, with additional entries that correspond to subpage trigger
- // elements residing in this element's Shadow DOM.
- if (settings.routes.SITE_SETTINGS_DATA_DETAILS) {
- const onNavigatedTo = () => this.async(() => {
- if (this.lastSelected_ == null || this.sites.length == 0) {
- return;
- }
-
- const lastSelectedSite = this.lastSelected_.item.site;
- const lastSelectedIndex = this.lastSelected_.index;
- this.lastSelected_ = null;
-
- const indexFromId =
- this.sites.findIndex(site => site.site == lastSelectedSite);
-
- // If the site is no longer in |sites|, use the index as a fallback.
- // Since the sites are sorted, an alternative could be to select the
- // site that comes next in sort order.
- const indexFallback = lastSelectedIndex < this.sites.length ?
- lastSelectedIndex :
- this.sites.length - 1;
- const index = indexFromId > -1 ? indexFromId : indexFallback;
- this.focusOnSiteSelectButton_(index);
- });
- this.focusConfig.set(
- settings.routes.SITE_SETTINGS_DATA_DETAILS.path, onNavigatedTo);
- }
- },
-
- /**
- * @param {number} index
- * @private
- */
- focusOnSiteSelectButton_: function(index) {
- const ironList =
- /** @type {!IronListElement} */ (this.$$('iron-list'));
- ironList.focusItem(index);
- const siteToSelect = this.sites[index].site.replace(/[.]/g, '\\.');
- const button = this.$$(`#siteItem_${siteToSelect}`).$$('.subpage-arrow');
- cr.ui.focusWithoutInk(assert(button));
- },
-
- /**
- * Gather all the site data.
- * @private
- */
- updateSiteList_: function() {
- this.isLoading_ = true;
- this.browserProxy_.getDisplayList(this.filter).then(listInfo => {
- this.updateList('sites', item => item.site, listInfo.items);
- this.isLoading_ = false;
- this.fire('site-data-list-complete');
- });
- },
-
- /**
- * Returns the string to use for the Remove label.
- * @param {string} filter The current filter string.
- * @return {string}
- * @private
- */
- computeRemoveLabel_: function(filter) {
- if (filter.length == 0) {
- return loadTimeData.getString('siteSettingsCookieRemoveAll');
- }
- return loadTimeData.getString('siteSettingsCookieRemoveAllShown');
- },
-
- /** @private */
- onCloseDialog_: function() {
- this.$.confirmDeleteDialog.close();
- },
-
- /** @private */
- onCloseThirdPartyDialog_: function() {
- this.$.confirmDeleteThirdPartyDialog.close();
- },
-
- /** @private */
- onConfirmDeleteDialogClosed_: function() {
- cr.ui.focusWithoutInk(assert(this.$.removeShowingSites));
- },
-
- /** @private */
- onConfirmDeleteThirdPartyDialogClosed_: function() {
- cr.ui.focusWithoutInk(assert(this.$.removeAllThirdPartyCookies));
- },
-
- /**
- * Shows a dialog to confirm the deletion of multiple sites.
- * @param {!Event} e
- * @private
- */
- onRemoveShowingSitesTap_: function(e) {
- e.preventDefault();
- this.$.confirmDeleteDialog.showModal();
- },
-
- /**
- * Shows a dialog to confirm the deletion of cookies available
- * in third-party contexts and associated site data.
- * @private
- */
- onRemoveThirdPartyCookiesTap_: function(e) {
- e.preventDefault();
- this.$.confirmDeleteThirdPartyDialog.showModal();
- },
-
- /**
- * Called when deletion for all showing sites has been confirmed.
- * @private
- */
- onConfirmDelete_: function() {
- this.$.confirmDeleteDialog.close();
- if (this.filter.length == 0) {
- this.browserProxy_.removeAll().then(() => {
- this.sites = [];
- });
- } else {
- this.browserProxy_.removeShownItems();
- // We just deleted all items found by the filter, let's reset the filter.
- this.fire('clear-subpage-search');
- }
- },
-
- /**
- * Called when deletion of all third-party cookies and site data has been
- * confirmed.
- * @private
- */
- onConfirmThirdPartyDelete_: function() {
- this.$.confirmDeleteThirdPartyDialog.close();
- this.browserProxy_.removeAllThirdPartyCookies().then(() => {
- this.updateSiteList_();
- });
- },
-
- /**
- * @param {!{model: !{item: CookieDataSummaryItem, index: number}}} event
- * @private
- */
- onSiteClick_: function(event) {
- // If any delete button is selected, the focus will be in a bad state when
- // returning to this page. To avoid this, the site select button is given
- // focus. See https://crbug.com/872197.
- this.focusOnSiteSelectButton_(event.model.index);
- settings.navigateTo(
- settings.routes.SITE_SETTINGS_DATA_DETAILS,
- new URLSearchParams('site=' + event.model.item.site));
- this.lastSelected_ = event.model;
- },
-
- /**
- * @private
- * @return {boolean}
- */
- showRemoveThirdPartyCookies_: function() {
- return loadTimeData.getBoolean('enableRemovingAllThirdPartyCookies') &&
- this.sites.length > 0 && this.filter.length == 0;
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.html b/chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.html
deleted file mode 100644
index c4529c642a5..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="cookie_info.html">
-<link rel="import" href="local_data_browser_proxy.html">
-
-<dom-module id="site-data-details-subpage">
- <template>
- <style include="settings-shared md-select">
- [first] {
- border-top: none;
- }
-
- .secondary,
- .start {
- max-width: 100%;
- word-break: break-word;
- }
- </style>
- <template is="dom-repeat" items="[[entries_]]">
- <div class="settings-box" first$="[[!index]]">
- <cr-expand-button class="start" expanded="{{item.expanded_}}">
- [[getEntryDescription_(item)]]
- </cr-expand-button>
- <div class="separator"></div>
- <cr-icon-button class="icon-clear" data-id-path$="[[item.idPath]]"
- on-click="onRemove_"></cr-icon-button>
- </div>
- <iron-collapse class="list-frame vertical-list"
- opened="[[item.expanded_]]">
- <template is="dom-repeat" items="[[getCookieNodes_(item)]]">
- <div class="list-item two-line">
- <div class="start">
- [[item.label]]
- <div class="secondary">[[item.content]]</div>
- </div>
- </div>
- </template>
- </iron-collapse>
- </template>
- </template>
- <script src="site_data_details_subpage.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.js b/chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.js
deleted file mode 100644
index 37ca12214f4..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_data_details_subpage.js
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-const categoryLabels = {
- app_cache: loadTimeData.getString('cookieAppCache'),
- cache_storage: loadTimeData.getString('cookieCacheStorage'),
- database: loadTimeData.getString('cookieDatabaseStorage'),
- file_system: loadTimeData.getString('cookieFileSystem'),
- flash_lso: loadTimeData.getString('cookieFlashLso'),
- indexed_db: loadTimeData.getString('cookieDatabaseStorage'),
- local_storage: loadTimeData.getString('cookieLocalStorage'),
- service_worker: loadTimeData.getString('cookieServiceWorker'),
- shared_worker: loadTimeData.getString('cookieSharedWorker'),
- media_license: loadTimeData.getString('cookieMediaLicense'),
-};
-
-/**
- * 'site-data-details-subpage' Display cookie contents.
- */
-Polymer({
- is: 'site-data-details-subpage',
-
- behaviors: [settings.RouteObserverBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * The cookie entries for the given site.
- * @type {!Array<!CookieDetails>}
- * @private
- */
- entries_: Array,
-
- /** Set the page title on the settings-subpage parent. */
- pageTitle: {
- type: String,
- notify: true,
- },
-
- /** @private */
- site_: String,
-
- /** @private */
- siteId_: String,
- },
-
- /**
- * The browser proxy used to retrieve and change cookies.
- * @private {?settings.LocalDataBrowserProxy}
- */
- browserProxy_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.LocalDataBrowserProxyImpl.getInstance();
-
- this.addWebUIListener(
- 'on-tree-item-removed', this.getCookieDetails_.bind(this));
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- if (settings.getCurrentRoute() !=
- settings.routes.SITE_SETTINGS_DATA_DETAILS) {
- return;
- }
- const site = settings.getQueryParameters().get('site');
- if (!site) {
- return;
- }
- this.site_ = site;
- this.pageTitle = loadTimeData.getStringF('siteSettingsCookieSubpage', site);
- this.getCookieDetails_();
- },
-
- /** @private */
- getCookieDetails_: function() {
- if (!this.site_) {
- return;
- }
- this.browserProxy_.getCookieDetails(this.site_)
- .then(
- this.onCookiesLoaded_.bind(this),
- this.onCookiesLoadFailed_.bind(this));
- },
-
- /**
- * @return {!Array<!CookieDataForDisplay>}
- * @private
- */
- getCookieNodes_: function(node) {
- return getCookieData(node);
- },
-
- /**
- * @param {!CookieList} cookies
- * @private
- */
- onCookiesLoaded_: function(cookies) {
- this.siteId_ = cookies.id;
- this.entries_ = cookies.children;
- // Set up flag for expanding cookie details.
- this.entries_.forEach(function(e) {
- e.expanded_ = false;
- });
- },
-
- /**
- * The site was not found. E.g. The site data may have been deleted or the
- * site URL parameter may be mistyped.
- * @private
- */
- onCookiesLoadFailed_: function() {
- this.siteId_ = '';
- this.entries_ = [];
- },
-
- /**
- * A handler for when the user opts to remove a single cookie.
- * @param {!CookieDetails} item
- * @return {string}
- * @private
- */
- getEntryDescription_: function(item) {
- // Frequently there are multiple cookies per site. To avoid showing a list
- // of '1 cookie', '1 cookie', ... etc, it is better to show the title of the
- // cookie to differentiate them.
- if (item.type == 'cookie') {
- return item.title;
- }
- if (item.type == 'quota') {
- return item.totalUsage;
- }
- return categoryLabels[item.type];
- },
-
- /**
- * A handler for when the user opts to remove a single cookie.
- * @param {!Event} event
- * @private
- */
- onRemove_: function(event) {
- this.browserProxy_.removeCookie(
- /** @type {!CookieDetails} */ (event.currentTarget.dataset).idPath);
- },
-
- /**
- * A handler for when the user opts to remove all cookies.
- */
- removeAll: function() {
- this.browserProxy_.removeCookie(this.siteId_);
- },
-});
-
-})();
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_data_entry.html b/chromium/chrome/browser/resources/settings/site_settings/site_data_entry.html
deleted file mode 100644
index 5585d9a34c3..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_data_entry.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="cookie_info.html">
-
-<dom-module id="site-data-entry">
- <template>
- <style include="settings-shared">
- .settings-box {
- border: none;
- }
- </style>
- <div class="settings-box two-line site-item" focus-row-container actionable>
- <site-favicon url="[[model.site]]"></site-favicon>
- <div class="middle">
- <span class="url-directionality">[[model.site]]</span>
- <div class="secondary">[[model.localData]]</div>
- </div>
- <cr-icon-button class="subpage-arrow" aria-label$="[[model.site]]"
- focus-row-control focus-type="showDetails"></cr-icon-button>
- <div class="separator"></div>
- <cr-icon-button class="icon-delete-gray"
- title$="[[i18n('siteSettingsCookieRemoveSite', model.site)]]"
- on-click="onRemove_" focus-row-control focus-type="remove">
- </cr-icon-button>
- </div>
- </template>
- <script src="site_data_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_data_entry.js b/chromium/chrome/browser/resources/settings/site_settings/site_data_entry.js
deleted file mode 100644
index 0c5e30c27e7..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_data_entry.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-data-entry' handles showing the local storage summary for a site.
- */
-
-Polymer({
- is: 'site-data-entry',
-
- behaviors: [
- cr.ui.FocusRowBehavior,
- I18nBehavior,
- ],
-
- properties: {
- /** @type {!CookieDataSummaryItem} */
- model: Object,
- },
-
- /** @private {settings.LocalDataBrowserProxy} */
- browserProxy_: null,
-
- /** @override */
- ready: function() {
- this.browserProxy_ = settings.LocalDataBrowserProxyImpl.getInstance();
- },
-
- /**
- * Deletes all site data for this site.
- * @param {!Event} e
- * @private
- */
- onRemove_: function(e) {
- e.stopPropagation();
- this.browserProxy_.removeItem(this.model.site);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_details.html b/chromium/chrome/browser/resources/settings/site_settings/site_details.html
deleted file mode 100644
index fb24557a8e0..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_details.html
+++ /dev/null
@@ -1,232 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/cr_elements/action_link_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_details_permission.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="website_usage_private_api.html">
-
-<dom-module id="site-details">
- <template>
- <style include="settings-shared action-link">
- .favicon-image {
- margin: 2px;
- }
-
- #storage {
- padding-inline-end: 0;
- }
-
- #storageText {
- display: flex;
- }
-
- #resetSettingsButton {
- margin-top: 24px;
- }
- </style>
- <!-- Confirm reset settings dialog. -->
- <cr-dialog id="confirmResetSettings" close-text="$i18n{close}"
- on-close="onResetSettingsDialogClosed_">
- <div slot="body">
- [[i18n('siteSettingsSiteResetConfirmation', pageTitle)]]
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCloseDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onResetSettings_">
- $i18n{siteSettingsSiteResetAll}
- </cr-button>
- </div>
- </cr-dialog>
-
- <!-- Confirm clear storage dialog. -->
- <cr-dialog id="confirmClearStorage" close-text="$i18n{close}"
- on-close="onClearStorageDialogClosed_">
- <div slot="title">
- $i18n{siteSettingsSiteClearStorageDialogTitle}
- </div>
- <div slot="body">
- [[i18n('siteSettingsSiteClearStorageConfirmation', pageTitle)]]
- </div>
- <div slot="button-container">
- <cr-button class="cancel-button" on-click="onCloseDialog_">
- $i18n{cancel}
- </cr-button>
- <cr-button class="action-button" on-click="onClearStorage_">
- $i18n{siteSettingsSiteClearStorage}
- </cr-button>
- </div>
- </cr-dialog>
-
- <div id="usage">
- <div class="settings-box first line-only">
- <h2 class="first">$i18n{siteSettingsUsage}</h2>
- </div>
- <div class="list-frame">
- <div class="list-item" id="noStorage"
- hidden$="[[hasUsage_(storedData_, numCookies_)]]">
- <div class="start">$i18n{siteSettingsUsageNone}</div>
- </div>
- <div class="list-item" id="storage"
- hidden$="[[!hasUsage_(storedData_, numCookies_)]]">
- <div class="start" id="storageText">
- <div hidden$="[[!storedData_]]">[[storedData_]]</div>
- <div hidden$="[[!hasDataAndCookies_(storedData_, numCookies_)]]">
- &nbsp;&middot;&nbsp;
- </div>
- <div hidden$="[[!numCookies_]]">[[numCookies_]]</div>
- </div>
- <cr-button id="clearStorage" role="button" aria-disabled="false"
- on-click="onConfirmClearStorage_"
- aria-label="$i18n{siteSettingsDelete}">
- $i18n{siteSettingsDelete}
- </cr-button>
- </div>
- </div>
- </div>
-
- <div class="settings-box first">
- <h2 class="start">$i18n{siteSettingsPermissions}</h2>
- <cr-button id="resetSettingsButton" class="header-aligned-button"
- role="button" aria-disabled="false"
- on-click="onConfirmClearSettings_">
- $i18n{siteSettingsReset}
- </cr-button>
- </div>
- <div class="list-frame">
- <site-details-permission category="{{ContentSettingsTypes.GEOLOCATION}}"
- icon="cr:location-on" id="geolocation"
- label="$i18n{siteSettingsLocation}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.CAMERA}}"
- icon="cr:videocam" id="camera"
- label="$i18n{siteSettingsCamera}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.MIC}}"
- icon="cr:mic" id="mic"
- label="$i18n{siteSettingsMic}">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.SENSORS}}"
- icon="settings:sensors" id="sensors"
- label="$i18n{siteSettingsSensors}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.NOTIFICATIONS}}"
- icon="settings:notifications" id="notifications"
- label="$i18n{siteSettingsNotifications}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.JAVASCRIPT}}"
- icon="settings:code" id="javascript"
- label="$i18n{siteSettingsJavascript}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.PLUGINS}}"
- icon="cr:extension" id="plugins" label="$i18n{siteSettingsFlash}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.IMAGES}}"
- icon="settings:photo" id="images" label="$i18n{siteSettingsImages}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.POPUPS}}"
- icon="cr:open-in-new" id="popups" label="$i18n{siteSettingsPopups}">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.ADS}}"
- icon="settings:ads" id="ads"
- label="$i18n{siteSettingsAds}">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.BACKGROUND_SYNC}}"
- icon="cr:sync" id="backgroundSync"
- label="$i18n{siteSettingsBackgroundSync}">
- </site-details-permission>
- <site-details-permission category="{{ContentSettingsTypes.SOUND}}"
- icon="settings:volume-up" id="sound"
- label="$i18n{siteSettingsSound}"
- use-automatic-label="[[blockAutoplayEnabled]]">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.AUTOMATIC_DOWNLOADS}}"
- icon="cr:file-download" id="automaticDownloads"
- label="$i18n{siteSettingsAutomaticDownloads}">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.MIDI_DEVICES}}" icon="settings:midi"
- id="midiDevices" label="$i18n{siteSettingsMidiDevices}">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.USB_DEVICES}}" icon="settings:usb"
- id="usbDevices" label="$i18n{siteSettingsUsbDevices}">
- </site-details-permission>
- <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
- <site-details-permission
- category="{{ContentSettingsTypes.SERIAL_PORTS}}"
- icon="settings:serial-port" id="serialPorts"
- label="$i18n{siteSettingsSerialPorts}">
- </site-details-permission>
- </template>
- <template is="dom-if" if="[[enableNativeFileSystemWriteContentSetting_]]">
- <site-details-permission
- category="{{ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE}}"
- icon="settings:save-original" id="nativeFileSystemWrite"
- label="$i18n{siteSettingsNativeFileSystemWrite}">
- </site-details-permission>
- </template>
- <site-details-permission
- category="{{ContentSettingsTypes.UNSANDBOXED_PLUGINS}}"
- icon="cr:extension" id="unsandboxedPlugins"
- label="$i18n{siteSettingsUnsandboxedPlugins}">
- </site-details-permission>
-<if expr="chromeos">
- <site-details-permission
- category="{{ContentSettingsTypes.PROTECTED_CONTENT}}"
- icon="settings:protected-content" id="protectedContent"
- label="$i18n{siteSettingsProtectedContentIdentifiers}">
- </site-details-permission>
-</if>
- <site-details-permission
- category="{{ContentSettingsTypes.CLIPBOARD}}"
- icon="settings:clipboard" id="clipboard"
- label="$i18n{siteSettingsClipboard}">
- </site-details-permission>
- <site-details-permission
- category="{{ContentSettingsTypes.PAYMENT_HANDLER}}"
- icon="settings:payment-handler" id="paymentHandler"
- label="$i18n{siteSettingsPaymentHandler}">
- </site-details-permission>
- <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
- <site-details-permission
- category="{{ContentSettingsTypes.BLUETOOTH_SCANNING}}"
- icon="settings:bluetooth-scanning" id="bluetoothScanning"
- label="$i18n{siteSettingsBluetoothScanning}">
- </site-details-permission>
- </template>
- <template is="dom-if" if="[[enableInsecureContentContentSetting_]]">
- <site-details-permission
- category="{{ContentSettingsTypes.MIXEDSCRIPT}}"
- icon="settings:insecure-content" id="mixed-script"
- label="$i18n{siteSettingsInsecureContent}">
- </site-details-permission>
- </template>
- </div>
-
- <website-usage-private-api id="usageApi"
- website-data-usage="{{storedData_}}"
- website-cookie-usage="{{numCookies_}}">
- </website-usage-private-api>
- </template>
- <script src="site_details.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_details.js b/chromium/chrome/browser/resources/settings/site_settings/site_details.js
deleted file mode 100644
index df765e0e3af..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_details.js
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-details' show the details (permissions and usage) for a given origin
- * under Site Settings.
- */
-Polymer({
- is: 'site-details',
-
- behaviors: [
- I18nBehavior, SiteSettingsBehavior, settings.RouteObserverBehavior,
- WebUIListenerBehavior
- ],
-
- properties: {
- /**
- * Whether unified autoplay blocking is enabled.
- */
- blockAutoplayEnabled: Boolean,
-
- /**
- * Use the string representing the origin or extension name as the page
- * title of the settings-subpage parent.
- */
- pageTitle: {
- type: String,
- notify: true,
- },
-
- /**
- * The origin that this widget is showing details for.
- * @private
- */
- origin_: String,
-
- /**
- * The amount of data stored for the origin.
- * @private
- */
- storedData_: {
- type: String,
- value: '',
- },
-
- /**
- * The number of cookies stored for the origin.
- * @private
- */
- numCookies_: {
- type: String,
- value: '',
- },
-
- /** @private */
- enableExperimentalWebPlatformFeatures_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures');
- },
- },
-
- /** @private */
- enableNativeFileSystemWriteContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean(
- 'enableNativeFileSystemWriteContentSetting');
- }
- },
-
- /** @private */
- enableInsecureContentContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableInsecureContentContentSetting');
- }
- },
- },
-
- listeners: {
- 'usage-deleted': 'onUsageDeleted_',
- },
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'contentSettingSitePermissionChanged',
- this.onPermissionChanged_.bind(this));
-
- // <if expr="chromeos">
- this.addWebUIListener(
- 'prefEnableDrmChanged', this.prefEnableDrmChanged_.bind(this));
- // </if>
-
- // Refresh block autoplay status from the backend.
- this.browserProxy.fetchBlockAutoplayStatus();
- },
-
- /** @override */
- ready: function() {
- this.ContentSettingsTypes = settings.ContentSettingsTypes;
- },
-
- /**
- * settings.RouteObserverBehavior
- * @param {!settings.Route} route
- * @protected
- */
- currentRouteChanged: function(route) {
- if (route != settings.routes.SITE_SETTINGS_SITE_DETAILS) {
- return;
- }
- const site = settings.getQueryParameters().get('site');
- if (!site) {
- return;
- }
- this.origin_ = site;
- this.browserProxy.isOriginValid(this.origin_).then((valid) => {
- if (!valid) {
- settings.navigateToPreviousRoute();
- } else {
- this.$.usageApi.fetchUsageTotal(this.toUrl(this.origin_).hostname);
- this.updatePermissions_(this.getCategoryList());
- }
- });
- },
-
- /**
- * Called when a site within a category has been changed.
- * @param {!settings.ContentSettingsTypes} category The category that
- * changed.
- * @param {string} origin The origin of the site that changed.
- * @param {string} embeddingOrigin The embedding origin of the site that
- * changed.
- * @private
- */
- onPermissionChanged_: function(category, origin, embeddingOrigin) {
- if (this.origin_ === undefined || this.origin_ == '' ||
- origin === undefined || origin == '') {
- return;
- }
- if (!this.getCategoryList().includes(category)) {
- return;
- }
-
- // Site details currently doesn't support embedded origins, so ignore it
- // and just check whether the origins are the same.
- this.updatePermissions_([category]);
- },
-
- // <if expr="chromeos">
- prefEnableDrmChanged_: function() {
- this.updatePermissions_([settings.ContentSettingsTypes.PROTECTED_CONTENT]);
- },
- // </if>
-
- /**
- * Retrieves the permissions listed in |categoryList| from the backend for
- * |this.origin_|.
- * @param {!Array<!settings.ContentSettingsTypes>} categoryList The list
- * of categories to update permissions for.
- * @private
- */
- updatePermissions_: function(categoryList) {
- const permissionsMap =
- /**
- * @type {!Object<!settings.ContentSettingsTypes,
- * !SiteDetailsPermissionElement>}
- */
- (Array.prototype.reduce.call(
- this.root.querySelectorAll('site-details-permission'),
- (map, element) => {
- if (categoryList.includes(element.category)) {
- map[element.category] = element;
- }
- return map;
- },
- {}));
-
- this.browserProxy.getOriginPermissions(this.origin_, categoryList)
- .then((exceptionList) => {
- exceptionList.forEach((exception, i) => {
- // |exceptionList| should be in the same order as
- // |categoryList|.
- if (permissionsMap[categoryList[i]]) {
- permissionsMap[categoryList[i]].site = exception;
- }
- });
-
- // The displayName won't change, so just use the first
- // exception.
- assert(exceptionList.length > 0);
- this.pageTitle = exceptionList[0].displayName;
- });
- },
-
- /** @private */
- onCloseDialog_: function(e) {
- e.target.closest('cr-dialog').close();
- },
-
- /**
- * Confirms the resetting of all content settings for an origin.
- * @param {!Event} e
- * @private
- */
- onConfirmClearSettings_: function(e) {
- e.preventDefault();
- this.$.confirmResetSettings.showModal();
- },
-
- /**
- * Confirms the clearing of storage for an origin.
- * @param {!Event} e
- * @private
- */
- onConfirmClearStorage_: function(e) {
- e.preventDefault();
- this.$.confirmClearStorage.showModal();
- },
-
- /**
- * Resets all permissions for the current origin.
- * @private
- */
- onResetSettings_: function(e) {
- this.browserProxy.setOriginPermissions(
- this.origin_, this.getCategoryList(), settings.ContentSetting.DEFAULT);
- if (this.getCategoryList().includes(
- settings.ContentSettingsTypes.PLUGINS)) {
- this.browserProxy.clearFlashPref(this.origin_);
- }
-
- this.onCloseDialog_(e);
- },
-
- /**
- * Clears all data stored, except cookies, for the current origin.
- * @private
- */
- onClearStorage_: function(e) {
- if (this.hasUsage_(this.storedData_, this.numCookies_)) {
- this.$.usageApi.clearUsage(this.toUrl(this.origin_).href);
- }
-
- this.onCloseDialog_(e);
- },
-
- /**
- * Called when usage has been deleted for an origin via a non-Site Details
- * source, e.g. clear browsing data.
- * @param {!CustomEvent<!{origin: string}>} event
- * @private
- */
- onUsageDeleted_: function(event) {
- if (event.detail.origin == this.toUrl(this.origin_).href) {
- this.storedData_ = '';
- this.numCookies_ = '';
- }
- },
-
- /**
- * Checks whether this site has any usage information to show.
- * @return {boolean} Whether there is any usage information to show (e.g.
- * disk or battery).
- * @private
- */
- hasUsage_: function(storage, cookies) {
- return storage != '' || cookies != '';
- },
-
- /**
- * Checks whether this site has both storage and cookies information to show.
- * @return {boolean} Whether there are both storage and cookies information to
- * show.
- * @private
- */
- hasDataAndCookies_: function(storage, cookies) {
- return storage != '' && cookies != '';
- },
-
- /** @private */
- onResetSettingsDialogClosed_: function() {
- cr.ui.focusWithoutInk(assert(this.$$('#resetSettingsButton')));
- },
-
- /** @private */
- onClearStorageDialogClosed_: function() {
- cr.ui.focusWithoutInk(assert(this.$$('#clearStorage')));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_details_permission.html b/chromium/chrome/browser/resources/settings/site_settings/site_details_permission.html
deleted file mode 100644
index c5a1e0091d3..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_details_permission.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<!-- `site-details-permission` does not include any icon-set, so containing
- elements should import the icon-set needed for the specified |icon|. -->
-<dom-module id="site-details-permission">
- <template>
- <style include="settings-shared md-select"></style>
- <div id="details" hidden$="[[shouldHideCategory_(category)]]">
- <div id="permissionItem"
- class$="list-item [[permissionInfoStringClass_(site.source, category,
- site.setting)]]">
- <div>
- <iron-icon icon="[[icon]]">
- </iron-icon>
- </div>
- <div class="middle" id="permissionHeader">
- [[label]]
- <div class="secondary"
- hidden$="[[!hasPermissionInfoString_(site.source, category,
- site.setting)]]"
- inner-h-t-m-l="[[permissionInfoString_(
- site.source,
- category,
- site.setting,
- '$i18nPolymer{siteSettingsAdsBlockBlacklistedSingular}',
- '$i18nPolymer{siteSettingsAdsBlockNotBlacklistedSingular}',
- '$i18nPolymer{siteSettingsSourceEmbargo}',
- '$i18nPolymer{siteSettingsSourceInsecureOrigin}',
- '$i18nPolymer{siteSettingsSourceKillSwitch}',
- '$i18nPolymer{siteSettingsSourceExtensionAllow}',
- '$i18nPolymer{siteSettingsSourceExtensionBlock}',
- '$i18nPolymer{siteSettingsSourceExtensionAsk}',
- '$i18nPolymer{siteSettingsSourcePolicyAllow}',
- '$i18nPolymer{siteSettingsSourcePolicyBlock}',
- '$i18nPolymer{siteSettingsSourcePolicyAsk}',
- '$i18nPolymer{siteSettingsSourceDrmDisabled}')]]">
- </div>
- </div>
- <select id="permission" class="md-select"
- aria-labelledby="permissionHeader"
- on-change="onPermissionSelectionChange_"
- disabled$="[[!isPermissionUserControlled_(site.source, category,
- site.setting)]]">
- <option id="default" value$="[[ContentSetting.DEFAULT]]">
- [[defaultSettingString_(
- defaultSetting_,
- category,
- useAutomaticLabel)]]
- </option>
- <option id="allow" value$="[[ContentSetting.ALLOW]]"
- hidden$="[[!showAllowedSetting_(category)]]">
- $i18n{siteSettingsActionAllow}
- </option>
- <option id="block" value$="[[ContentSetting.BLOCK]]">
- [[blockSettingString_(
- category,
- '$i18n{siteSettingsActionBlock}',
- '$i18n{siteSettingsActionMute}')]]
- </option>
- <option id="ask" value$="[[ContentSetting.ASK]]"
- hidden$="[[!showAskSetting_(category, site.setting,
- site.source)]]">
- $i18n{siteSettingsActionAsk}
- </option>
- </select>
- </div>
- </div>
- </template>
- <script src="site_details_permission.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_details_permission.js b/chromium/chrome/browser/resources/settings/site_settings/site_details_permission.js
deleted file mode 100644
index 76f870645d2..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_details_permission.js
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-details-permission' handles showing the state of one permission, such
- * as Geolocation, for a given origin.
- */
-Polymer({
- is: 'site-details-permission',
-
- behaviors: [I18nBehavior, SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * If this is a sound content setting, then this controls whether it
- * should use "Automatic" instead of "Allow" as the default setting
- * allow label.
- */
- useAutomaticLabel: {type: Boolean, value: false},
-
- /**
- * The site that this widget is showing details for.
- * @type {RawSiteException}
- */
- site: Object,
-
- /**
- * The default setting for this permission category.
- * @type {settings.ContentSetting}
- * @private
- */
- defaultSetting_: String,
-
- label: String,
-
- icon: String,
- },
-
- observers: ['siteChanged_(site)'],
-
- /** @override */
- attached: function() {
- this.addWebUIListener(
- 'contentSettingCategoryChanged',
- this.onDefaultSettingChanged_.bind(this));
- },
-
- shouldHideCategory_: function(category) {
- return !this.getCategoryList().includes(category);
- },
-
- /**
- * Updates the drop-down value after |site| has changed.
- * @param {!RawSiteException} site The site to display.
- * @private
- */
- siteChanged_: function(site) {
- if (site.source == settings.SiteSettingSource.DEFAULT) {
- this.defaultSetting_ = site.setting;
- this.$.permission.value = settings.ContentSetting.DEFAULT;
- } else {
- // The default setting is unknown, so consult the C++ backend for it.
- this.updateDefaultPermission_(site);
- this.$.permission.value = site.setting;
- }
-
- if (this.isNonDefaultAsk_(site.setting, site.source)) {
- assert(
- this.$.permission.value == settings.ContentSetting.ASK,
- '\'Ask\' should only show up when it\'s currently selected.');
- }
- },
-
- /**
- * Updates the default permission setting for this permission category.
- * @param {!RawSiteException} site The site to display.
- * @private
- */
- updateDefaultPermission_: function(site) {
- this.browserProxy.getDefaultValueForContentType(this.category)
- .then((defaultValue) => {
- this.defaultSetting_ = defaultValue.setting;
- });
- },
-
- /**
- * Handles the category permission changing for this origin.
- * @param {!settings.ContentSettingsTypes} category The permission category
- * that has changed default permission.
- * @private
- */
- onDefaultSettingChanged_: function(category) {
- if (category == this.category) {
- this.updateDefaultPermission_(this.site);
- }
- },
-
- /**
- * Handles the category permission changing for this origin.
- * @private
- */
- onPermissionSelectionChange_: function() {
- this.browserProxy.setOriginPermissions(
- this.site.origin, [this.category], this.$.permission.value);
- },
-
- /**
- * Returns if we should use the custom labels for the sound type.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @return {boolean}
- * @private
- */
- useCustomSoundLabels_: function(category) {
- return category == settings.ContentSettingsTypes.SOUND &&
- loadTimeData.getBoolean('enableAutoplayWhitelistContentSetting');
- },
-
- /**
- * Updates the string used for this permission category's default setting.
- * @param {!settings.ContentSetting} defaultSetting Value of the default
- * setting for this permission category.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @param {boolean} useAutomaticLabel Whether to use the automatic label
- * if the default setting value is allow.
- * @return {string}
- * @private
- */
- defaultSettingString_: function(defaultSetting, category, useAutomaticLabel) {
- if (defaultSetting == undefined || category == undefined ||
- useAutomaticLabel == undefined) {
- return '';
- }
-
- if (defaultSetting == settings.ContentSetting.ASK ||
- defaultSetting == settings.ContentSetting.IMPORTANT_CONTENT) {
- return this.i18n('siteSettingsActionAskDefault');
- } else if (defaultSetting == settings.ContentSetting.ALLOW) {
- if (this.useCustomSoundLabels_(category) && useAutomaticLabel) {
- return this.i18n('siteSettingsActionAutomaticDefault');
- }
- return this.i18n('siteSettingsActionAllowDefault');
- } else if (defaultSetting == settings.ContentSetting.BLOCK) {
- if (this.useCustomSoundLabels_(category)) {
- return this.i18n('siteSettingsActionMuteDefault');
- }
- return this.i18n('siteSettingsActionBlockDefault');
- }
- assertNotReached(
- `No string for ${this.category}'s default of ${defaultSetting}`);
- },
-
- /**
- * Updates the string used for this permission category's block setting.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @param {string} blockString 'Block' label.
- * @param {string} muteString 'Mute' label.
- * @return {string}
- * @private
- */
- blockSettingString_: function(category, blockString, muteString) {
- if (this.useCustomSoundLabels_(category)) {
- return muteString;
- }
- return blockString;
- },
-
- /**
- * Returns true if there's a string to display that provides more information
- * about this permission's setting. Currently, this only gets called when
- * |this.site| is updated.
- * @param {!settings.SiteSettingSource} source The source of the permission.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @param {!settings.ContentSetting} setting The permission setting.
- * @return {boolean} Whether the permission will have a source string to
- * display.
- * @private
- */
- hasPermissionInfoString_: function(source, category, setting) {
- // This method assumes that an empty string will be returned for categories
- // that have no permission info string.
- return this.permissionInfoString_(
- source, category, setting,
- // Set all permission info string arguments as null. This is OK
- // because there is no need to know what the information string
- // will be, just whether there is one or not.
- null, null, null, null, null, null, null, null, null, null, null,
- null) != '';
- },
-
- /**
- * Checks if there's a additional information to display, and returns the
- * class name to apply to permissions if so.
- * @param {!settings.SiteSettingSource} source The source of the permission.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @param {!settings.ContentSetting} setting The permission setting.
- * @return {string} CSS class applied when there is an additional description
- * string.
- * @private
- */
- permissionInfoStringClass_: function(source, category, setting) {
- return this.hasPermissionInfoString_(source, category, setting) ?
- 'two-line' :
- '';
- },
-
- /**
- * Returns true if this permission can be controlled by the user.
- * @param {!settings.SiteSettingSource} source The source of the permission.
- * @return {boolean}
- * @private
- */
- isPermissionUserControlled_: function(source) {
- return !(
- source == settings.SiteSettingSource.DRM_DISABLED ||
- source == settings.SiteSettingSource.POLICY ||
- source == settings.SiteSettingSource.EXTENSION ||
- source == settings.SiteSettingSource.KILL_SWITCH ||
- source == settings.SiteSettingSource.INSECURE_ORIGIN);
- },
-
- /**
- * Returns true if the 'allow' option should be shown.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @return {boolean}
- * @private
- */
- showAllowedSetting_: function(category) {
- return !(
- category == settings.ContentSettingsTypes.SERIAL_PORTS ||
- category == settings.ContentSettingsTypes.USB_DEVICES ||
- category == settings.ContentSettingsTypes.BLUETOOTH_SCANNING ||
- category == settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE);
- },
-
- /**
- * Returns true if the 'ask' option should be shown.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @param {!settings.ContentSetting} setting The setting of the permission.
- * @param {!settings.SiteSettingSource} source The source of the permission.
- * @return {boolean}
- * @private
- */
- showAskSetting_: function(category, setting, source) {
- // For chooser-based permissions 'ask' takes the place of 'allow'.
- if (category == settings.ContentSettingsTypes.SERIAL_PORTS ||
- category == settings.ContentSettingsTypes.USB_DEVICES) {
- return true;
- }
-
- // For Bluetooth scanning permission and Native File System write permission
- // 'ask' takes the place of 'allow'.
- if (category == settings.ContentSettingsTypes.BLUETOOTH_SCANNING ||
- category == settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE) {
- return true;
- }
-
- return this.isNonDefaultAsk_(setting, source);
- },
-
- /**
- * Returns true if the permission is set to a non-default 'ask'. Currently,
- * this only gets called when |this.site| is updated.
- * @param {!settings.ContentSetting} setting The setting of the permission.
- * @param {!settings.SiteSettingSource} source The source of the permission.
- * @private
- */
- isNonDefaultAsk_: function(setting, source) {
- if (setting != settings.ContentSetting.ASK ||
- source == settings.SiteSettingSource.DEFAULT) {
- return false;
- }
-
- assert(
- source == settings.SiteSettingSource.EXTENSION ||
- source == settings.SiteSettingSource.POLICY ||
- source == settings.SiteSettingSource.PREFERENCE,
- 'Only extensions, enterprise policy or preferences can change ' +
- 'the setting to ASK.');
- return true;
- },
-
- /**
- * Updates the information string for the current permission.
- * Currently, this only gets called when |this.site| is updated.
- * @param {!settings.SiteSettingSource} source The source of the permission.
- * @param {!settings.ContentSettingsTypes} category The permission type.
- * @param {!settings.ContentSetting} setting The permission setting.
- * @param {?string} adsBlacklistString The string to show if the site is
- * blacklisted for showing bad ads.
- * @param {?string} adsBlockString The string to show if ads are blocked, but
- * the site is not blacklisted.
- * @param {?string} embargoString
- * @param {?string} insecureOriginString
- * @param {?string} killSwitchString
- * @param {?string} extensionAllowString
- * @param {?string} extensionBlockString
- * @param {?string} extensionAskString
- * @param {?string} policyAllowString
- * @param {?string} policyBlockString
- * @param {?string} policyAskString
- * @param {?string} drmDisabledString
- * @return {?string} The permission information string to display in the HTML.
- * @private
- */
- permissionInfoString_: function(
- source, category, setting, adsBlacklistString, adsBlockString,
- embargoString, insecureOriginString, killSwitchString,
- extensionAllowString, extensionBlockString, extensionAskString,
- policyAllowString, policyBlockString, policyAskString,
- drmDisabledString) {
- if (source == undefined || category == undefined || setting == undefined) {
- return null;
- }
-
- /** @type {Object<!settings.ContentSetting, ?string>} */
- const extensionStrings = {};
- extensionStrings[settings.ContentSetting.ALLOW] = extensionAllowString;
- extensionStrings[settings.ContentSetting.BLOCK] = extensionBlockString;
- extensionStrings[settings.ContentSetting.ASK] = extensionAskString;
-
- /** @type {Object<!settings.ContentSetting, ?string>} */
- const policyStrings = {};
- policyStrings[settings.ContentSetting.ALLOW] = policyAllowString;
- policyStrings[settings.ContentSetting.BLOCK] = policyBlockString;
- policyStrings[settings.ContentSetting.ASK] = policyAskString;
-
- if (source == settings.SiteSettingSource.ADS_FILTER_BLACKLIST) {
- assert(
- settings.ContentSettingsTypes.ADS == category,
- 'The ads filter blacklist only applies to Ads.');
- return adsBlacklistString;
- } else if (
- category == settings.ContentSettingsTypes.ADS &&
- setting == settings.ContentSetting.BLOCK) {
- return adsBlockString;
- } else if (source == settings.SiteSettingSource.DRM_DISABLED) {
- assert(
- settings.ContentSetting.BLOCK == setting,
- 'If DRM is disabled, Protected Content must be blocked.');
- assert(
- settings.ContentSettingsTypes.PROTECTED_CONTENT == category,
- 'The DRM disabled source only applies to Protected Content.');
- if (!drmDisabledString) {
- return null;
- }
- return loadTimeData.sanitizeInnerHtml(loadTimeData.substituteString(
- drmDisabledString,
- settings.routes.SITE_SETTINGS_PROTECTED_CONTENT.getAbsolutePath()));
- } else if (source == settings.SiteSettingSource.EMBARGO) {
- assert(
- settings.ContentSetting.BLOCK == setting,
- 'Embargo is only used to block permissions.');
- return embargoString;
- } else if (source == settings.SiteSettingSource.EXTENSION) {
- return extensionStrings[setting];
- } else if (source == settings.SiteSettingSource.INSECURE_ORIGIN) {
- assert(
- settings.ContentSetting.BLOCK == setting,
- 'Permissions can only be blocked due to insecure origins.');
- return insecureOriginString;
- } else if (source == settings.SiteSettingSource.KILL_SWITCH) {
- assert(
- settings.ContentSetting.BLOCK == setting,
- 'The permissions kill switch can only be used to block permissions.');
- return killSwitchString;
- } else if (source == settings.SiteSettingSource.POLICY) {
- return policyStrings[setting];
- } else if (
- source == settings.SiteSettingSource.DEFAULT ||
- source == settings.SiteSettingSource.PREFERENCE) {
- return '';
- }
- assertNotReached(`No string for ${category} setting source '${source}'`);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_entry.html b/chromium/chrome/browser/resources/settings/site_settings/site_entry.html
deleted file mode 100644
index 49b87662716..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_entry.html
+++ /dev/null
@@ -1,139 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="local_data_browser_proxy.html">
-<link rel="import" href="site_settings_behavior.html">
-
-<dom-module id="site-entry">
- <template>
- <style include="settings-shared">
- .row-aligned {
- display: flex;
- flex-direction: row;
- }
-
- #toggleButton {
- /** Use the minimum row height as the minimum click-target height. */
- min-height: var(--settings-row-min-height);
- }
-
- .site-representation {
- display: flex;
- }
-
- .second-line {
- margin-top: 0.1em;
- }
-
- /* Data units such as "0 KB" should always read left-to-right. */
- .data-unit {
- direction: ltr;
- unicode-bidi: isolate;
- }
-
- .list-frame {
- padding-inline-end: 0;
- }
-
- .spacing {
- padding-inline-start: 1ch;
- }
- </style>
- <div id="collapseParent" focus-row-container>
- <div class$="settings-box list-item [[getClassForIndex_(listIndex)]]">
- <div id="toggleButton" class="start row-aligned two-line"
- on-click="onSiteEntryTap_" actionable aria-expanded="false">
- <site-favicon url="[[getSiteGroupIcon_(siteGroup)]]"></site-favicon>
- <div class="middle text-elide" id="displayName">
- <div class="site-representation">
- <span class="url-directionality">[[displayName_]]</span>
- <span class="secondary"
- hidden$="[[!siteGroupScheme_(siteGroup)]]">
- &nbsp;$i18nPolymer{siteSettingsSiteRepresentationSeparator}&nbsp;
- </span>
- <span class="secondary"
- hidden$="[[!siteGroupScheme_(siteGroup)]]">
- [[siteGroupScheme_(siteGroup)]]
- </span>
- </div>
- <div class="second-line secondary">
- <span class="data-unit">[[overallUsageString_]]</span>
- <span id="cookies" hidden$="[[!siteGroup.numCookies]]">
- &middot; [[cookieString_]]
- </span>
- </div>
- </div>
- <cr-icon-button id="expandIcon" class="icon-expand-more"
- hidden$="[[!grouped_(siteGroup)]]" aria-label$="[[displayName_]]"
- aria-describedby="displayName" focus-row-control
- focus-type="expand"></cr-icon-button>
- <cr-icon-button class="subpage-arrow"
- hidden$="[[grouped_(siteGroup)]]" aria-label$="[[displayName_]]"
- aria-describedby="displayName"
- focus-row-control focus-type="show-detail"></cr-icon-button>
- </div>
- <div class="row-aligned" hidden$="[[!grouped_(siteGroup)]]">
- <div class="separator"></div>
- <cr-icon-button class="icon-more-vert" id="overflowMenuButton"
- title="$i18n{moreActions}" on-click="showOverflowMenu_"
- focus-row-control focus-type="more-actions"></cr-icon-button>
- </div>
- </div>
-
- <cr-lazy-render id="originList">
- <template>
- <iron-collapse id="collapseChild" no-animation>
- <div class="list-frame">
- <template is="dom-repeat" items="[[siteGroup.origins]]">
- <div class="settings-box list-item" on-click="onOriginTap_"
- actionable>
- <site-favicon url="[[item.origin]]"></site-favicon>
- <div class="site-representation middle text-elide">
- <span id="originSiteRepresentation"
- class="url-directionality">
- [[originRepresentation_(item)]]
- </span>
- <span class="secondary"
- hidden$="[[!originScheme_(item)]]">
- &nbsp;
- $i18nPolymer{siteSettingsSiteRepresentationSeparator}
- &nbsp;
- </span>
- <span class="secondary"
- hidden$="[[!originScheme_(item)]]">
- [[originScheme_(item)]]
- </span>
- <!--Define a spacing span so that when the direction is rtl,
- the spacing is still showing correctly. This is because the
- data-unit class is set to be ltr so the padding will be in
- wrong place if we put padding in that span.-->
- <span class="spacing" hidden$="[[!item.usage]]"></span>
- <span class="secondary data-unit" hidden$="[[!item.usage]]">
- [[originUsagesItem_(originUsages_.*, index)]]
- </span>
- <span class="secondary" hidden$="[[!item.numCookies]]">
- &nbsp;&middot;
- [[originCookiesItem_(cookiesNum_.*, index)]]
- </span>
- </div>
- <cr-icon-button class="subpage-arrow"
- aria-labelledby$="originSiteRepresentation"
- focus-row-control focus-type="detailed-sites">
- </cr-icon-button>
- </div>
- </template>
- </div>
- </iron-collapse>
- </template>
- </cr-lazy-render>
- </div>
- </template>
- <script src="site_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_entry.js b/chromium/chrome/browser/resources/settings/site_settings/site_entry.js
deleted file mode 100644
index ad60ba23464..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_entry.js
+++ /dev/null
@@ -1,454 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-entry' is an element representing a single eTLD+1 site entity.
- */
-Polymer({
- is: 'site-entry',
-
- behaviors: [SiteSettingsBehavior, cr.ui.FocusRowBehavior],
-
- properties: {
- /**
- * An object representing a group of sites with the same eTLD+1.
- * @type {!SiteGroup}
- */
- siteGroup: {
- type: Object,
- observer: 'onSiteGroupChanged_',
- },
-
- /**
- * The name to display beside the icon. If grouped_() is true, it will be
- * the eTLD+1 for all the origins, otherwise, it will return the host.
- * @private
- */
- displayName_: String,
-
- /**
- * The string to display when there is a non-zero number of cookies.
- * @private
- */
- cookieString_: String,
-
- /**
- * The position of this site-entry in its parent list.
- */
- listIndex: {
- type: Number,
- value: -1,
- },
-
- /**
- * The string to display showing the overall usage of this site-entry.
- * @private
- */
- overallUsageString_: String,
-
- /**
- * An array containing the strings to display showing the individual disk
- * usage for each origin in |siteGroup|.
- * @type {!Array<string>}
- * @private
- */
- originUsages_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * An array containing the strings to display showing the individual cookies
- * number for each origin in |siteGroup|.
- * @type {!Array<string>}
- * @private
- */
- cookiesNum_: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The selected sort method.
- * @type {!settings.SortMethod|undefined}
- */
- sortMethod: {type: String, observer: 'updateOrigins_'}
- },
-
- /** @private {?settings.LocalDataBrowserProxy} */
- localDataBrowserProxy_: null,
-
- /** @private {?Element} */
- button_: null,
-
- /** @override */
- created: function() {
- this.localDataBrowserProxy_ =
- settings.LocalDataBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- detached: function() {
- if (this.button_) {
- this.unlisten(this.button_, 'keydown', 'onButtonKeydown_');
- }
- },
-
- /** @param {!KeyboardEvent} e */
- onButtonKeydown_: function(e) {
- if (e.shiftKey && e.key === 'Tab') {
- this.focus();
- }
- },
-
- /**
- * Whether the list of origins displayed in this site-entry is a group of
- * eTLD+1 origins or not.
- * @param {SiteGroup} siteGroup The eTLD+1 group of origins.
- * @return {boolean}
- * @private
- */
- grouped_: function(siteGroup) {
- if (!siteGroup) {
- return false;
- }
- if (siteGroup.origins.length > 1 ||
- siteGroup.numCookies > siteGroup.origins[0].numCookies) {
- return true;
- }
- return false;
- },
-
- /**
- * Returns a user-friendly name for the siteGroup.
- * If grouped_() is true and eTLD+1 is available, returns the eTLD+1,
- * otherwise return the origin representation for the first origin.
- * @param {SiteGroup} siteGroup The eTLD+1 group of origins.
- * @return {string} The user-friendly name.
- * @private
- */
- siteGroupRepresentation_: function(siteGroup) {
- if (!siteGroup) {
- return '';
- }
- if (this.grouped_(siteGroup)) {
- if (siteGroup.etldPlus1 != '') {
- return siteGroup.etldPlus1;
- }
- // Fall back onto using the host of the first origin, if no eTLD+1 name
- // was computed.
- }
- return this.originRepresentation_(siteGroup.origins[0]);
- },
-
- /**
- * Returns a user-friendly name for the origin.
- * @param {OriginInfo} origin
- * @return {string} The user-friendly name.
- * @private
- */
- originRepresentation_(origin) {
- const url = this.toUrl(origin.origin);
- return url.host;
- },
-
- /**
- * @param {SiteGroup} siteGroup The eTLD+1 group of origins.
- * @private
- */
- onSiteGroupChanged_: function(siteGroup) {
- // Update the button listener.
- if (this.button_) {
- this.unlisten(this.button_, 'keydown', 'onButtonKeydown_');
- }
- this.button_ = /** @type Element */
- (this.root.querySelector('#toggleButton *:not([hidden])'));
- this.listen(assert(this.button_), 'keydown', 'onButtonKeydown_');
-
- if (!this.grouped_(siteGroup)) {
- // Ensure ungrouped |siteGroup|s do not get stuck in an opened state.
- const collapseChild = this.$.originList.getIfExists();
- if (collapseChild && collapseChild.opened) {
- this.toggleCollapsible_();
- }
- }
- if (!siteGroup) {
- return;
- }
- this.calculateUsageInfo_(siteGroup);
- this.getCookieNumString_(siteGroup.numCookies).then(string => {
- this.cookieString_ = string;
- });
- this.updateOrigins_(this.sortMethod);
- this.displayName_ = this.siteGroupRepresentation_(siteGroup);
- },
-
- /**
- * Returns any non-HTTPS scheme/protocol for the siteGroup that only contains
- * one origin. Otherwise, returns a empty string.
- * @param {SiteGroup} siteGroup The eTLD+1 group of origins.
- * @return {string} The scheme if non-HTTPS, or empty string if HTTPS.
- * @private
- */
- siteGroupScheme_: function(siteGroup) {
- if (!siteGroup || (this.grouped_(siteGroup))) {
- return '';
- }
- return this.originScheme_(siteGroup.origins[0]);
- },
-
- /**
- * Returns any non-HTTPS scheme/protocol for the origin. Otherwise, returns
- * an empty string.
- * @param {OriginInfo} origin
- * @return {string} The scheme if non-HTTPS, or empty string if HTTPS.
- * @private
- */
- originScheme_: function(origin) {
- const url = this.toUrl(origin.origin);
- const scheme = url.protocol.replace(new RegExp(':*$'), '');
- /** @type{string} */ const HTTPS_SCHEME = 'https';
- if (scheme == HTTPS_SCHEME) {
- return '';
- }
- return scheme;
- },
-
- /**
- * Get an appropriate favicon that represents this group of eTLD+1 sites as a
- * whole.
- * @param {!SiteGroup} siteGroup The eTLD+1 group of origins.
- * @return {string} URL that is used for fetching the favicon
- * @private
- */
- getSiteGroupIcon_: function(siteGroup) {
- const origins = siteGroup.origins;
- assert(origins);
- assert(origins.length >= 1);
- if (origins.length == 1) {
- return origins[0].origin;
- }
- // If we can find a origin with format "www.etld+1", use the favicon of this
- // origin. Otherwise find the origin with largest storage, and use the
- // number of cookies as a tie breaker.
- for (const originInfo of origins) {
- if (this.toUrl(originInfo.origin).host == 'www.' + siteGroup.etldPlus1) {
- return originInfo.origin;
- }
- }
- const getMaxStorage = (max, originInfo) => {
- return (
- max.usage > originInfo.usage ||
- (max.usage == originInfo.usage &&
- max.numCookies > originInfo.numCookies) ?
- max :
- originInfo);
- };
- return origins.reduce(getMaxStorage, origins[0]).origin;
- },
-
- /**
- * Calculates the amount of disk storage used by the given eTLD+1.
- * Also updates the corresponding display strings.
- * @param {SiteGroup} siteGroup The eTLD+1 group of origins.
- * @private
- */
- calculateUsageInfo_: function(siteGroup) {
- let overallUsage = 0;
- this.siteGroup.origins.forEach((originInfo, i) => {
- overallUsage += originInfo.usage;
- });
- this.browserProxy.getFormattedBytes(overallUsage).then(string => {
- this.overallUsageString_ = string;
- });
- },
-
- /**
- * Get display string for number of cookies.
- * @param {number} numCookies
- * @private
- */
- getCookieNumString_: function(numCookies) {
- if (numCookies == 0) {
- return Promise.resolve('');
- }
- return this.localDataBrowserProxy_.getNumCookiesString(numCookies);
- },
-
- /**
- * Array binding for the |originUsages_| array for use in the HTML.
- * @param {!{base: !Array<string>}} change The change record for the array.
- * @param {number} index The index of the array item.
- * @return {string}
- * @private
- */
- originUsagesItem_: function(change, index) {
- return change.base[index];
- },
-
- /**
- * Array binding for the |cookiesNum_| array for use in the HTML.
- * @param {!{base: !Array<string>}} change The change record for the array.
- * @param {number} index The index of the array item.
- * @return {string}
- * @private
- */
- originCookiesItem_: function(change, index) {
- return change.base[index];
- },
-
- /**
- * Navigates to the corresponding Site Details page for the given origin.
- * @param {string} origin The origin to navigate to the Site Details page for
- * it.
- * @private
- */
- navigateToSiteDetails_: function(origin) {
- this.fire(
- 'site-entry-selected', {item: this.siteGroup, index: this.listIndex});
- settings.navigateTo(
- settings.routes.SITE_SETTINGS_SITE_DETAILS,
- new URLSearchParams('site=' + origin));
- },
-
- /**
- * A handler for selecting a site (by clicking on the origin).
- * @param {!{model: !{index: !number}}} e
- * @private
- */
- onOriginTap_: function(e) {
- this.navigateToSiteDetails_(this.siteGroup.origins[e.model.index].origin);
- this.browserProxy.recordAction(settings.AllSitesAction.ENTER_SITE_DETAILS);
- },
-
- /**
- * A handler for clicking on a site-entry heading. This will either show a
- * list of origins or directly navigates to Site Details if there is only one.
- * @private
- */
- onSiteEntryTap_: function() {
- // Individual origins don't expand - just go straight to Site Details.
- if (!this.grouped_(this.siteGroup)) {
- this.navigateToSiteDetails_(this.siteGroup.origins[0].origin);
- this.browserProxy.recordAction(
- settings.AllSitesAction.ENTER_SITE_DETAILS);
- return;
- }
- this.toggleCollapsible_();
-
- // Make sure the expanded origins can be viewed without further scrolling
- // (in case |this| is already at the bottom of the viewport).
- this.scrollIntoViewIfNeeded();
- },
-
- /**
- * Toggles open and closed the list of origins if there is more than one.
- * @private
- */
- toggleCollapsible_: function() {
- const collapseChild =
- /** @type {IronCollapseElement} */ (this.$.originList.get());
- collapseChild.toggle();
- this.$.toggleButton.setAttribute('aria-expanded', collapseChild.opened);
- this.$.expandIcon.toggleClass('icon-expand-more');
- this.$.expandIcon.toggleClass('icon-expand-less');
- this.fire('iron-resize');
- },
-
- /**
- * Fires a custom event when the menu button is clicked. Sends the details
- * of the site entry item and where the menu should appear.
- * @param {!Event} e
- * @private
- */
- showOverflowMenu_: function(e) {
- this.fire('open-menu', {
- target: e.target,
- index: this.listIndex,
- item: this.siteGroup,
- });
- },
-
- /**
- * Returns a valid index for an origin contained in |siteGroup.origins| by
- * clamping the given |index|. This also replaces undefined |index|es with 0.
- * Use this to prevent being given out-of-bounds indexes by dom-repeat when
- * scrolling an iron-list storing these site-entries too quickly.
- * @param {!number=} index
- * @return {number}
- * @private
- */
- getIndexBoundToOriginList_: function(siteGroup, index) {
- return Math.max(0, Math.min(index, siteGroup.origins.length - 1));
- },
-
- /**
- * Returns the correct class to apply depending on this site-entry's position
- * in a list.
- * @param {number} index
- * @private
- */
- getClassForIndex_: function(index) {
- if (index == 0) {
- return 'first';
- }
- return '';
- },
-
- /**
- * Update the order and data display text for origins.
- * @param {!settings.SortMethod|undefined} sortMethod
- * @private
- */
- updateOrigins_: function(sortMethod) {
- if (!sortMethod || !this.siteGroup || !this.grouped_(this.siteGroup)) {
- return null;
- }
-
- const origins = this.siteGroup.origins.slice();
- origins.sort(this.sortFunction_(sortMethod));
- this.set('siteGroup.origins', origins);
-
- this.originUsages_ = new Array(origins.length);
- origins.forEach((originInfo, i) => {
- this.browserProxy.getFormattedBytes(originInfo.usage).then((string) => {
- this.set(`originUsages_.${i}`, string);
- });
- });
-
- this.cookiesNum_ = new Array(this.siteGroup.origins.length);
- origins.forEach((originInfo, i) => {
- this.getCookieNumString_(originInfo.numCookies).then((string) => {
- this.set(`cookiesNum_.${i}`, string);
- });
- });
- },
-
- /**
- * Sort functions for sorting origins based on selected method.
- * @param {!settings.SortMethod|undefined} sortMethod
- * @private
- */
- sortFunction_: function(sortMethod) {
- if (sortMethod == settings.SortMethod.MOST_VISITED) {
- return (origin1, origin2) => {
- return origin2.engagement - origin1.engagement;
- };
- } else if (sortMethod == settings.SortMethod.STORAGE) {
- return (origin1, origin2) => {
- return origin2.usage - origin1.usage ||
- origin2.numCookies - origin1.numCookies;
- };
- } else if (sortMethod == settings.SortMethod.NAME) {
- return (origin1, origin2) => {
- return origin1.origin.localeCompare(origin2.origin);
- };
- }
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_list.html b/chromium/chrome/browser/resources/settings/site_settings/site_list.html
deleted file mode 100644
index de095c0574e..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_list.html
+++ /dev/null
@@ -1,103 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="add_site_dialog.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="edit_exception_dialog.html">
-<link rel="import" href="site_list_entry.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-<if expr="chromeos">
- <link rel="import" href="../multidevice_page/multidevice_browser_proxy.html">
-</if>
-
-<dom-module id="site-list">
- <template>
- <style include="settings-shared">
- paper-tooltip {
- --paper-tooltip: var(--cr-tooltip);
- }
- </style>
- <div id="category">
- <div class="settings-box first">
- <h2 class="start">[[categoryHeader]]</h2>
- <cr-button id="addSite" class="header-aligned-button"
- hidden$="[[!showAddSiteButton_]]" on-click="onAddSiteTap_">
- $i18n{add}
- </cr-button>
- </div>
-
- <cr-action-menu>
- <button class="dropdown-item" id="allow"
- on-click="onAllowTap_" hidden$="[[!showAllowAction_]]">
- $i18n{siteSettingsActionAllow}
- </button>
- <button class="dropdown-item" id="block"
- on-click="onBlockTap_" hidden$="[[!showBlockAction_]]">
- $i18n{siteSettingsActionBlock}
- </button>
- <button class="dropdown-item" id="sessionOnly"
- on-click="onSessionOnlyTap_"
- hidden$="[[!showSessionOnlyActionForSite_(actionMenuSite_)]]">
- $i18n{siteSettingsActionSessionOnly}
- </button>
- <button class="dropdown-item" id="edit"
- on-click="onEditTap_">
- $i18n{edit}
- </button>
- <button class="dropdown-item" id="reset"
- on-click="onResetTap_">
- $i18n{siteSettingsActionReset}
- </button>
- </cr-action-menu>
-
- <div class="list-frame" hidden$="[[hasSites_(sites.*)]]">
- <div class="list-item secondary">$i18n{noSitesAdded}</div>
- </div>
- <div class="list-frame"
- hidden$="[[!showNoSearchResults_(searchFilter, sites.*)]]">
- <div class="list-item secondary">$i18n{searchNoResults}</div>
- </div>
- <div class="list-frame menu-content vertical-list" id="listContainer">
- <iron-list items="[[getFilteredSites_(searchFilter, sites.*)]]"
- preserve-focus risk-selection>
- <template>
- <site-list-entry model="[[item]]" read-only-list="[[readOnlyList]]"
- on-show-action-menu="onShowActionMenu_" tabindex$="[[tabIndex]]"
- first$="[[!index]]" iron-list-tab-index="[[tabIndex]]"
- last-focused="{{lastFocused_}}" list-blurred="{{listBlurred_}}"
- on-show-tooltip="onShowTooltip_">
- </site-list-entry>
- </template>
- </iron-list>
- </div>
- </div>
- <paper-tooltip id="tooltip" fit-to-visible-bounds manual-mode
- position="top">
- [[tooltipText_]]
- </paper-tooltip>
- <template is="dom-if" if="[[showEditExceptionDialog_]]" restamp>
- <settings-edit-exception-dialog model="[[actionMenuSite_]]"
- on-close="onEditExceptionDialogClosed_">
- </settings-edit-exception-dialog>
- </template>
- <template is="dom-if" if="[[showAddSiteDialog_]]" restamp>
- <add-site-dialog has-incognito="[[hasIncognito_]]" category="[[category]]"
- content-setting="[[categorySubtype]]"
- on-close="onAddSiteDialogClosed_">
- </add-site-dialog>
- </template>
- </template>
- <script src="site_list.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_list.js b/chromium/chrome/browser/resources/settings/site_settings/site_list.js
deleted file mode 100644
index bdc541bd8a9..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_list.js
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-list' shows a list of Allowed and Blocked sites for a given
- * category.
- */
-Polymer({
- is: 'site-list',
-
- behaviors: [
- SiteSettingsBehavior,
- WebUIListenerBehavior,
- ListPropertyUpdateBehavior,
- ],
-
- properties: {
- /**
- * Some content types (like Location) do not allow the user to manually
- * edit the exception list from within Settings.
- * @private
- */
- readOnlyList: {
- type: Boolean,
- value: false,
- },
-
- categoryHeader: String,
-
- /**
- * The site serving as the model for the currently open action menu.
- * @private {?SiteException}
- */
- actionMenuSite_: Object,
-
- /**
- * Whether the "edit exception" dialog should be shown.
- * @private
- */
- showEditExceptionDialog_: Boolean,
-
- /**
- * Array of sites to display in the widget.
- * @type {!Array<SiteException>}
- */
- sites: {
- type: Array,
- value: function() {
- return [];
- },
- },
-
- /**
- * The type of category this widget is displaying data for. Normally
- * either 'allow' or 'block', representing which sites are allowed or
- * blocked respectively.
- */
- categorySubtype: {
- type: String,
- value: settings.INVALID_CATEGORY_SUBTYPE,
- },
-
- /** @private */
- hasIncognito_: Boolean,
-
- /**
- * Whether to show the Add button next to the header.
- * @private
- */
- showAddSiteButton_: {
- type: Boolean,
- computed: 'computeShowAddSiteButton_(readOnlyList, category, ' +
- 'categorySubtype)',
- },
-
- /** @private */
- showAddSiteDialog_: Boolean,
-
- /**
- * Whether to show the Allow action in the action menu.
- * @private
- */
- showAllowAction_: Boolean,
-
- /**
- * Whether to show the Block action in the action menu.
- * @private
- */
- showBlockAction_: Boolean,
-
- /**
- * Whether to show the 'Clear on exit' action in the action
- * menu.
- * @private
- */
- showSessionOnlyAction_: Boolean,
-
- /**
- * All possible actions in the action menu.
- * @private
- */
- actions_: {
- readOnly: true,
- type: Object,
- values: {
- ALLOW: 'Allow',
- BLOCK: 'Block',
- RESET: 'Reset',
- SESSION_ONLY: 'SessionOnly',
- }
- },
-
- /** @private */
- lastFocused_: Object,
-
- /** @private */
- listBlurred_: Boolean,
-
- /** @private */
- tooltipText_: String,
-
- searchFilter: String,
- },
-
- // <if expr="chromeos">
- /**
- * Android messages info object containing messages feature state and
- * exception origin.
- * @private {?settings.AndroidSmsInfo}
- */
- androidSmsInfo_: null,
- // </if>
-
- /**
- * The element to return focus to, when the currently active dialog is closed.
- * @private {?HTMLElement}
- */
- activeDialogAnchor_: null,
-
- observers: ['configureWidget_(category, categorySubtype)'],
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'contentSettingSitePermissionChanged',
- this.siteWithinCategoryChanged_.bind(this));
- this.addWebUIListener(
- 'onIncognitoStatusChanged', this.onIncognitoStatusChanged_.bind(this));
- // <if expr="chromeos">
- this.addWebUIListener('settings.onAndroidSmsInfoChange', (info) => {
- this.androidSmsInfo_ = info;
- this.populateList_();
- });
- // </if>
- this.browserProxy.updateIncognitoStatus();
- },
-
- /**
- * Called when a site changes permission.
- * @param {string} category The category of the site that changed.
- * @param {string} site The site that changed.
- * @private
- */
- siteWithinCategoryChanged_: function(category, site) {
- if (category == this.category) {
- this.configureWidget_();
- }
- },
-
- /**
- * Called for each site list when incognito is enabled or disabled. Only
- * called on change (opening N incognito windows only fires one message).
- * Another message is sent when the *last* incognito window closes.
- * @private
- */
- onIncognitoStatusChanged_: function(hasIncognito) {
- this.hasIncognito_ = hasIncognito;
-
- // The SESSION_ONLY list won't have any incognito exceptions. (Minor
- // optimization, not required).
- if (this.categorySubtype == settings.ContentSetting.SESSION_ONLY) {
- return;
- }
-
- // A change notification is not sent for each site. So we repopulate the
- // whole list when the incognito profile is created or destroyed.
- this.populateList_();
- },
-
- /**
- * Configures the action menu, visibility of the widget and shows the list.
- * @private
- */
- configureWidget_: function() {
- if (this.category == undefined) {
- return;
- }
-
- // The observer for All Sites fires before the attached/ready event, so
- // initialize this here.
- if (this.browserProxy_ === undefined) {
- this.browserProxy_ =
- settings.SiteSettingsPrefsBrowserProxyImpl.getInstance();
- }
-
- this.setUpActionMenu_();
-
- // <if expr="not chromeos">
- this.populateList_();
- // </if>
-
- // <if expr="chromeos">
- this.updateAndroidSmsInfo_().then(this.populateList_.bind(this));
- // </if>
-
- // The Session permissions are only for cookies.
- if (this.categorySubtype == settings.ContentSetting.SESSION_ONLY) {
- this.$.category.hidden =
- this.category != settings.ContentSettingsTypes.COOKIES;
- }
- },
-
- /**
- * Whether there are any site exceptions added for this content setting.
- * @return {boolean}
- * @private
- */
- hasSites_: function() {
- return this.sites.length > 0;
- },
-
- /**
- * Whether the Add Site button is shown in the header for the current category
- * and category subtype.
- * @return {boolean}
- * @private
- */
- computeShowAddSiteButton_: function() {
- return !(
- this.readOnlyList ||
- (this.category ==
- settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE &&
- this.categorySubtype == settings.ContentSetting.ALLOW));
- },
-
- /**
- * @return {boolean}
- * @private
- */
- showNoSearchResults_: function() {
- return this.sites.length > 0 && this.getFilteredSites_().length == 0;
- },
-
- /**
- * A handler for the Add Site button.
- * @private
- */
- onAddSiteTap_: function() {
- assert(!this.readOnlyList);
- this.showAddSiteDialog_ = true;
- },
-
- /** @private */
- onAddSiteDialogClosed_: function() {
- this.showAddSiteDialog_ = false;
- cr.ui.focusWithoutInk(assert(this.$.addSite));
- },
-
- /**
- * Need to use common tooltip since the tooltip in the entry is cut off from
- * the iron-list.
- * @param {!CustomEvent<!{target: HTMLElement, text: string}>} e
- * @private
- */
- onShowTooltip_: function(e) {
- this.tooltipText_ = e.detail.text;
- const target = e.detail.target;
- // paper-tooltip normally determines the target from the |for| property,
- // which is a selector. Here paper-tooltip is being reused by multiple
- // potential targets.
- const tooltip = this.$.tooltip;
- tooltip.target = target;
- /** @type {{updatePosition: Function}} */ (tooltip).updatePosition();
- const hide = () => {
- this.$.tooltip.hide();
- target.removeEventListener('mouseleave', hide);
- target.removeEventListener('blur', hide);
- target.removeEventListener('tap', hide);
- this.$.tooltip.removeEventListener('mouseenter', hide);
- };
- target.addEventListener('mouseleave', hide);
- target.addEventListener('blur', hide);
- target.addEventListener('tap', hide);
- this.$.tooltip.addEventListener('mouseenter', hide);
- this.$.tooltip.show();
- },
-
- // <if expr="chromeos">
- /**
- * Load android sms info if required and sets it to the |androidSmsInfo_|
- * property. Returns a promise that resolves when load is complete.
- * @private
- */
- updateAndroidSmsInfo_: function() {
- // |androidSmsInfo_| is only relevant for NOTIFICATIONS category. Don't
- // bother fetching it for other categories.
- if (this.category === settings.ContentSettingsTypes.NOTIFICATIONS &&
- loadTimeData.valueExists('multideviceAllowedByPolicy') &&
- loadTimeData.getBoolean('multideviceAllowedByPolicy') &&
- !this.androidSmsInfo_) {
- const multideviceSetupProxy =
- settings.MultiDeviceBrowserProxyImpl.getInstance();
- return multideviceSetupProxy.getAndroidSmsInfo().then((info) => {
- this.androidSmsInfo_ = info;
- });
- }
-
- return Promise.resolve();
- },
-
- /**
- * Processes exceptions and adds showAndroidSmsNote field to
- * the required exception item.
- * @private
- */
- processExceptionsForAndroidSmsInfo_: function(sites) {
- if (!this.androidSmsInfo_ || !this.androidSmsInfo_.enabled) {
- return sites;
- }
- return sites.map((site) => {
- if (site.origin === this.androidSmsInfo_.origin) {
- return Object.assign({showAndroidSmsNote: true}, site);
- } else {
- return site;
- }
- });
- },
- // </if>
-
- /**
- * Populate the sites list for display.
- * @private
- */
- populateList_: function() {
- this.browserProxy_.getExceptionList(this.category).then(exceptionList => {
- this.processExceptions_(exceptionList);
- this.closeActionMenu_();
- });
- },
-
- /**
- * Process the exception list returned from the native layer.
- * @param {!Array<RawSiteException>} exceptionList
- * @private
- */
- processExceptions_: function(exceptionList) {
- let sites =
- exceptionList
- .filter(
- site => site.setting != settings.ContentSetting.DEFAULT &&
- site.setting == this.categorySubtype)
- .map(site => this.expandSiteException(site));
-
- // <if expr="chromeos">
- sites = this.processExceptionsForAndroidSmsInfo_(sites);
- // </if>
- this.updateList('sites', x => x.origin, sites);
- },
-
- /**
- * Set up the values to use for the action menu.
- * @private
- */
- setUpActionMenu_: function() {
- this.showAllowAction_ =
- this.categorySubtype != settings.ContentSetting.ALLOW;
- this.showBlockAction_ =
- this.categorySubtype != settings.ContentSetting.BLOCK;
- this.showSessionOnlyAction_ =
- this.categorySubtype != settings.ContentSetting.SESSION_ONLY &&
- this.category == settings.ContentSettingsTypes.COOKIES;
- },
-
- /**
- * @return {boolean} Whether to show the "Session Only" menu item for the
- * currently active site.
- * @private
- */
- showSessionOnlyActionForSite_: function() {
- // It makes no sense to show "clear on exit" for exceptions that only apply
- // to incognito. It gives the impression that they might under some
- // circumstances not be cleared on exit, which isn't true.
- if (!this.actionMenuSite_ || this.actionMenuSite_.incognito) {
- return false;
- }
-
- return this.showSessionOnlyAction_;
- },
-
- /**
- * @param {!settings.ContentSetting} contentSetting
- * @private
- */
- setContentSettingForActionMenuSite_: function(contentSetting) {
- assert(this.actionMenuSite_);
- this.browserProxy.setCategoryPermissionForPattern(
- this.actionMenuSite_.origin, this.actionMenuSite_.embeddingOrigin,
- this.category, contentSetting, this.actionMenuSite_.incognito);
- },
-
- /** @private */
- onAllowTap_: function() {
- this.setContentSettingForActionMenuSite_(settings.ContentSetting.ALLOW);
- this.closeActionMenu_();
- },
-
- /** @private */
- onBlockTap_: function() {
- this.setContentSettingForActionMenuSite_(settings.ContentSetting.BLOCK);
- this.closeActionMenu_();
- },
-
- /** @private */
- onSessionOnlyTap_: function() {
- this.setContentSettingForActionMenuSite_(
- settings.ContentSetting.SESSION_ONLY);
- this.closeActionMenu_();
- },
-
- /** @private */
- onEditTap_: function() {
- // Close action menu without resetting |this.actionMenuSite_| since it is
- // bound to the dialog.
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu')).close();
- this.showEditExceptionDialog_ = true;
- },
-
- /** @private */
- onEditExceptionDialogClosed_: function() {
- this.showEditExceptionDialog_ = false;
- this.actionMenuSite_ = null;
- if (this.activeDialogAnchor_) {
- this.activeDialogAnchor_.focus();
- this.activeDialogAnchor_ = null;
- }
- },
-
- /** @private */
- onResetTap_: function() {
- const site = this.actionMenuSite_;
- assert(site);
- this.browserProxy.resetCategoryPermissionForPattern(
- site.origin, site.embeddingOrigin, this.category, site.incognito);
- this.closeActionMenu_();
- },
-
- /**
- * @param {!Event} e
- * @private
- */
- onShowActionMenu_: function(e) {
- this.activeDialogAnchor_ = /** @type {!HTMLElement} */ (e.detail.anchor);
- this.actionMenuSite_ = e.detail.model;
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
- .showAt(this.activeDialogAnchor_);
- },
-
- /** @private */
- closeActionMenu_: function() {
- this.actionMenuSite_ = null;
- this.activeDialogAnchor_ = null;
- const actionMenu =
- /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'));
- if (actionMenu.open) {
- actionMenu.close();
- }
- },
-
- /**
- * @return {!Array<!SiteException>}
- * @private
- */
- getFilteredSites_: function() {
- if (!this.searchFilter) {
- return this.sites.slice();
- }
-
- const propNames = [
- 'displayName',
- 'origin',
- ];
- const searchFilter = this.searchFilter.toLowerCase();
- return this.sites.filter(
- site => propNames.some(
- propName => site[propName].toLowerCase().includes(searchFilter)));
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_list_entry.html b/chromium/chrome/browser/resources/settings/site_settings/site_list_entry.html
deleted file mode 100644
index da644580de1..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_list_entry.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="site-list-entry">
- <template>
- <style include="settings-shared">
- :host {
- padding-inline-end: 4px;
- }
-
- .settings-row {
- flex: 1
- }
-
- /* Tooltip is hidden since site-list will display a common tooltip. */
- cr-policy-pref-indicator::part(tooltip) {
- display: none;
- }
- </style>
- <div class="list-item" focus-row-container>
- <div class="settings-row"
- actionable$="[[allowNavigateToSiteDetail_]]" on-click="onOriginTap_">
- <site-favicon url="[[model.origin]]"></site-favicon>
- <div class="middle no-min-width">
- <div class="text-elide">
- <span class="url-directionality">[[computeDisplayName_(model)]]
- </span>
- </div>
-
- <!-- This div must not contain extra whitespace. -->
- <div class="secondary text-elide"
- id="siteDescription">[[computeSiteDescription_(model)]]</div>
- </div>
- <template is="dom-if" if="[[allowNavigateToSiteDetail_]]">
- <cr-icon-button class="subpage-arrow"
- aria-label$="[[computeDisplayName_(model)]]"
- aria-describedby="siteDescription" focus-row-control
- focus-type="site-details"></cr-icon-button>
- <div class="separator"></div>
- </template>
- </div>
- <template is="dom-if" if="[[showPolicyPrefIndicator_]]">
- <cr-policy-pref-indicator pref="[[model]]"
- icon-aria-label="[[label]]" on-mouseenter="onShowTooltip_"
- on-focus="onShowTooltip_" focus-row-control focus-type="policy">
- </cr-policy-pref-indicator>
- </template>
- <cr-icon-button id="resetSite" class="icon-delete-gray"
- hidden="[[shouldHideResetButton_(model, readOnlyList)]]"
- on-click="onResetButtonTap_"
- aria-label="$i18n{siteSettingsActionReset}" focus-row-control
- focus-type="reset"></cr-icon-button>
- <cr-icon-button id="actionMenuButton" class="icon-more-vert"
- hidden="[[shouldHideActionMenu_(model, readOnlyList)]]"
- on-click="onShowActionMenuTap_" title="$i18n{moreActions}"
- focus-row-control focus-type="menu"></cr-icon-button>
- </div>
- </template>
- <script src="site_list_entry.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_list_entry.js b/chromium/chrome/browser/resources/settings/site_settings/site_list_entry.js
deleted file mode 100644
index b296f31a34a..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_list_entry.js
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'site-list-entry' shows an Allowed and Blocked site for a given category.
- */
-Polymer({
- is: 'site-list-entry',
-
- behaviors: [
- SiteSettingsBehavior,
- cr.ui.FocusRowBehavior,
- ],
-
- properties: {
- /**
- * Some content types (like Location) do not allow the user to manually
- * edit the exception list from within Settings.
- * @private
- */
- readOnlyList: {
- type: Boolean,
- value: false,
- },
-
- /**
- * Site to display in the widget.
- * @type {!SiteException}
- */
- model: {
- type: Object,
- observer: 'onModelChanged_',
- },
-
- /**
- * If the site represented is part of a chooser exception, the chooser type
- * will be stored here to allow the permission to be manipulated.
- * @private {!settings.ChooserType}
- */
- chooserType: {
- type: String,
- value: settings.ChooserType.NONE,
- },
-
- /**
- * If the site represented is part of a chooser exception, the chooser
- * object will be stored here to allow the permission to be manipulated.
- * @private
- */
- chooserObject: {
- type: Object,
- value: null,
- },
-
- /** @private */
- showPolicyPrefIndicator_: {
- type: Boolean,
- computed: 'computeShowPolicyPrefIndicator_(model)',
- },
-
- /** @private */
- allowNavigateToSiteDetail_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @private */
- onShowTooltip_: function() {
- const indicator = assert(this.$$('cr-policy-pref-indicator'));
- // The tooltip text is used by an paper-tooltip contained inside the
- // cr-policy-pref-indicator. The text is currently held in a private
- // property. This text is needed here to send up to the common tooltip
- // component.
- const text = indicator.indicatorTooltip_;
- this.fire('show-tooltip', {target: indicator, text});
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldHideResetButton_: function() {
- if (this.model === undefined) {
- return false;
- }
-
- return this.model.enforcement ==
- chrome.settingsPrivate.Enforcement.ENFORCED ||
- !(this.readOnlyList || !!this.model.embeddingOrigin);
- },
-
- /**
- * @return {boolean}
- * @private
- */
- shouldHideActionMenu_: function() {
- if (this.model === undefined) {
- return false;
- }
-
- return this.model.enforcement ==
- chrome.settingsPrivate.Enforcement.ENFORCED ||
- this.readOnlyList || !!this.model.embeddingOrigin;
- },
-
- /**
- * A handler for selecting a site (by clicking on the origin).
- * @private
- */
- onOriginTap_: function() {
- if (!this.allowNavigateToSiteDetail_) {
- return;
- }
- settings.navigateTo(
- settings.routes.SITE_SETTINGS_SITE_DETAILS,
- new URLSearchParams('site=' + this.model.origin));
- },
-
- /**
- * Returns the appropriate display name to show for the exception.
- * This can, for example, be the website that is affected itself,
- * or the website whose third parties are also affected.
- * @return {string}
- */
- computeDisplayName_: function() {
- if (this.model.embeddingOrigin &&
- this.model.category === settings.ContentSettingsTypes.COOKIES &&
- this.model.origin.trim() == settings.SITE_EXCEPTION_WILDCARD) {
- return this.model.embeddingOrigin;
- }
- return this.model.displayName;
- },
-
- /**
- * Returns the appropriate site description to display. This can, for example,
- * be blank, an 'embedded on <site>' or 'Current incognito session' (or a
- * mix of the last two).
- * @return {string}
- */
- computeSiteDescription_: function() {
- let description = '';
-
- if (this.model.embeddingOrigin) {
- if (this.model.category === settings.ContentSettingsTypes.COOKIES &&
- this.model.origin.trim() == settings.SITE_EXCEPTION_WILDCARD) {
- description =
- loadTimeData.getString(
- 'siteSettingsCookiesThirdPartyExceptionLabel');
- } else {
- description = loadTimeData.getStringF(
- 'embeddedOnHost', this.sanitizePort(this.model.embeddingOrigin));
- }
- } else if (this.category == settings.ContentSettingsTypes.GEOLOCATION) {
- description = loadTimeData.getString('embeddedOnAnyHost');
- }
-
- // <if expr="chromeos">
- if (this.model.category === settings.ContentSettingsTypes.NOTIFICATIONS &&
- this.model.showAndroidSmsNote) {
- description = loadTimeData.getString('androidSmsNote');
- }
- // </if>
-
- if (this.model.incognito) {
- if (description.length > 0) {
- description =
- loadTimeData.getStringF('embeddedIncognitoSite', description);
- } else {
- description = loadTimeData.getString('incognitoSite');
- }
- }
- return description;
- },
-
- /**
- * @return {boolean}
- * @private
- */
- computeShowPolicyPrefIndicator_: function() {
- return this.model.enforcement ==
- chrome.settingsPrivate.Enforcement.ENFORCED &&
- !!this.model.controlledBy;
- },
-
- /** @private */
- onResetButtonTap_: function() {
- // Use the appropriate method to reset a chooser exception.
- if (this.chooserType !== settings.ChooserType.NONE &&
- this.chooserObject != null) {
- this.browserProxy.resetChooserExceptionForSite(
- this.chooserType, this.model.origin, this.model.embeddingOrigin,
- this.chooserObject);
- return;
- }
-
- this.browserProxy.resetCategoryPermissionForPattern(
- this.model.origin, this.model.embeddingOrigin, this.model.category,
- this.model.incognito);
- },
-
- /** @private */
- onShowActionMenuTap_: function() {
- // Chooser exceptions do not support the action menu, so do nothing.
- if (this.chooserType !== settings.ChooserType.NONE) {
- return;
- }
-
- this.fire(
- 'show-action-menu',
- {anchor: this.$.actionMenuButton, model: this.model});
- },
-
- /** @private */
- onModelChanged_: function() {
- if (!this.model) {
- this.allowNavigateToSiteDetail_ = false;
- return;
- }
- this.browserProxy.isOriginValid(this.model.origin).then((valid) => {
- this.allowNavigateToSiteDetail_ = valid;
- });
- }
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.html b/chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.html
deleted file mode 100644
index 5f011e4f7b7..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<script src="site_settings_behavior.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.js b/chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.js
deleted file mode 100644
index 545c49afcba..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_settings_behavior.js
+++ /dev/null
@@ -1,219 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Behavior common to Site Settings classes.
- */
-
-
-/**
- * The source information on site exceptions doesn't exactly match the
- * controlledBy values.
- * TODO(dschuyler): Can they be unified (and this dictionary removed)?
- * @type {!Object}
- */
-const kControlledByLookup = {
- 'extension': chrome.settingsPrivate.ControlledBy.EXTENSION,
- 'HostedApp': chrome.settingsPrivate.ControlledBy.EXTENSION,
- 'platform_app': chrome.settingsPrivate.ControlledBy.EXTENSION,
- 'policy': chrome.settingsPrivate.ControlledBy.USER_POLICY,
-};
-
-
-/** @polymerBehavior */
-const SiteSettingsBehaviorImpl = {
- properties: {
- /**
- * The string ID of the category this element is displaying data for.
- * See site_settings/constants.js for possible values.
- * @type {!settings.ContentSettingsTypes}
- */
- category: String,
-
- /**
- * A cached list of ContentSettingsTypes with a standard allow-block-ask
- * pattern that are currently enabled for use. This property is the same
- * across all elements with SiteSettingsBehavior ('static').
- * @type {Array<settings.ContentSettingsTypes>}
- * @private
- */
- contentTypes_: {
- type: Array,
- value: [],
- },
-
- /**
- * The browser proxy used to retrieve and change information about site
- * settings categories and the sites within.
- * @type {settings.SiteSettingsPrefsBrowserProxy}
- */
- browserProxy: Object,
- },
-
- /** @override */
- created: function() {
- this.browserProxy =
- settings.SiteSettingsPrefsBrowserProxyImpl.getInstance();
- },
-
- /** @override */
- ready: function() {
- this.ContentSetting = settings.ContentSetting;
- },
-
- /**
- * Ensures the URL has a scheme (assumes http if omitted).
- * @param {string} url The URL with or without a scheme.
- * @return {string} The URL with a scheme, or an empty string.
- */
- ensureUrlHasScheme: function(url) {
- if (url.length == 0) {
- return url;
- }
- return url.includes('://') ? url : 'http://' + url;
- },
-
- /**
- * Removes redundant ports, such as port 80 for http and 443 for https.
- * @param {string} url The URL to sanitize.
- * @return {string} The URL without redundant ports, if any.
- */
- sanitizePort: function(url) {
- const urlWithScheme = this.ensureUrlHasScheme(url);
- if (urlWithScheme.startsWith('https://') &&
- urlWithScheme.endsWith(':443')) {
- return url.slice(0, -4);
- }
- if (urlWithScheme.startsWith('http://') && urlWithScheme.endsWith(':80')) {
- return url.slice(0, -3);
- }
- return url;
- },
-
- /**
- * Returns true if the passed content setting is considered 'enabled'.
- * @param {string} setting
- * @return {boolean}
- * @protected
- */
- computeIsSettingEnabled: function(setting) {
- return setting != settings.ContentSetting.BLOCK;
- },
-
- /**
- * Converts a string origin/pattern to a URL.
- * @param {string} originOrPattern The origin/pattern to convert to URL.
- * @return {URL} The URL to return (or null if origin is not a valid URL).
- * @protected
- */
- toUrl: function(originOrPattern) {
- if (originOrPattern.length == 0) {
- return null;
- }
- // TODO(finnur): Hmm, it would probably be better to ensure scheme on the
- // JS/C++ boundary.
- // TODO(dschuyler): I agree. This filtering should be done in one go, rather
- // that during the sort. The URL generation should be wrapped in a try/catch
- // as well.
- originOrPattern = originOrPattern.replace('*://', '');
- originOrPattern = originOrPattern.replace('[*.]', '');
- return new URL(this.ensureUrlHasScheme(originOrPattern));
- },
-
- /**
- * Convert an exception (received from the C++ handler) to a full
- * SiteException.
- * @param {!RawSiteException} exception The raw site exception from C++.
- * @return {!SiteException} The expanded (full) SiteException.
- * @protected
- */
- expandSiteException: function(exception) {
- const origin = exception.origin;
- const embeddingOrigin = exception.embeddingOrigin;
-
- // TODO(patricialor): |exception.source| should be one of the values defined
- // in |settings.SiteSettingSource|.
- let enforcement = /** @type {?chrome.settingsPrivate.Enforcement} */ (null);
- if (exception.source == 'extension' || exception.source == 'HostedApp' ||
- exception.source == 'platform_app' || exception.source == 'policy') {
- enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
- }
-
- const controlledBy = /** @type {!chrome.settingsPrivate.ControlledBy} */ (
- kControlledByLookup[exception.source] ||
- chrome.settingsPrivate.ControlledBy.PRIMARY_USER);
-
- return {
- category: this.category,
- origin: origin,
- displayName: exception.displayName,
- embeddingOrigin: embeddingOrigin,
- incognito: exception.incognito,
- setting: exception.setting,
- enforcement: enforcement,
- controlledBy: controlledBy,
- };
- },
-
- /**
- * Returns list of categories for each setting.ContentSettingsTypes that are
- * currently enabled.
- * @return {!Array<!settings.ContentSettingsTypes>}
- */
- getCategoryList: function() {
- if (this.contentTypes_.length == 0) {
- for (const typeName in settings.ContentSettingsTypes) {
- const contentType = settings.ContentSettingsTypes[typeName];
- // <if expr="not chromeos">
- if (contentType == settings.ContentSettingsTypes.PROTECTED_CONTENT) {
- continue;
- }
- // </if>
- // Some categories store their data in a custom way.
- if (contentType == settings.ContentSettingsTypes.COOKIES ||
- contentType == settings.ContentSettingsTypes.PROTOCOL_HANDLERS ||
- contentType == settings.ContentSettingsTypes.ZOOM_LEVELS) {
- continue;
- }
- this.contentTypes_.push(contentType);
- }
- }
-
- const addOrRemoveSettingWithFlag = (type, flag) => {
- if (loadTimeData.getBoolean(flag)) {
- if (!this.contentTypes_.includes(type)) {
- this.contentTypes_.push(type);
- }
- } else {
- if (this.contentTypes_.includes(type)) {
- this.contentTypes_.splice(this.contentTypes_.indexOf(type), 1);
- }
- }
- };
- // These categories are gated behind flags.
- addOrRemoveSettingWithFlag(
- settings.ContentSettingsTypes.SERIAL_PORTS,
- 'enableExperimentalWebPlatformFeatures');
- addOrRemoveSettingWithFlag(
- settings.ContentSettingsTypes.BLUETOOTH_SCANNING,
- 'enableExperimentalWebPlatformFeatures');
- addOrRemoveSettingWithFlag(
- settings.ContentSettingsTypes.ADS,
- 'enableSafeBrowsingSubresourceFilter');
- addOrRemoveSettingWithFlag(
- settings.ContentSettingsTypes.PAYMENT_HANDLER,
- 'enablePaymentHandlerContentSetting');
- addOrRemoveSettingWithFlag(
- settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE,
- 'enableNativeFileSystemWriteContentSetting');
- addOrRemoveSettingWithFlag(
- settings.ContentSettingsTypes.MIXEDSCRIPT,
- 'enableInsecureContentContentSetting');
- return this.contentTypes_.slice(0);
- },
-
-};
-
-/** @polymerBehavior */
-const SiteSettingsBehavior = [SiteSettingsBehaviorImpl];
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html b/chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html
deleted file mode 100644
index 945c6ebe6c8..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="site_settings_prefs_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js b/chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
deleted file mode 100644
index 2b826c3b5a2..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
+++ /dev/null
@@ -1,533 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Site Settings" section to
- * interact with the content settings prefs.
- */
-
-/**
- * The handler will send a policy source that is similar, but not exactly the
- * same as a ControlledBy value. If the ContentSettingProvider is omitted it
- * should be treated as 'default'.
- * @enum {string}
- */
-const ContentSettingProvider = {
- POLICY: 'policy',
- SUPERVISED_USER: 'supervised_user',
- EXTENSION: 'extension',
- INSTALLED_WEBAPP_PROVIDER: 'installed_webapp_provider',
- NOTIFICATION_ANDROID: 'notification_android',
- EPHEMERAL: 'ephemeral',
- PREFERENCE: 'preference',
- DEFAULT: 'default',
- TESTS: 'tests',
- TESTS_OTHER: 'tests_other'
-};
-
-/**
- * Stores information about if a content setting is valid, and why.
- * @typedef {{isValid: boolean,
- * reason: ?string}}
- */
-let IsValid;
-
-/**
- * Stores origin information. The |hasPermissionSettings| will be set to true
- * when this origin has permissions or when there is a pattern permission
- * affecting this origin.
- * @typedef {{origin: string,
- * engagement: number,
- * usage: number,
- numCookies: number,
- hasPermissionSettings: boolean}}
- */
-let OriginInfo;
-
-/**
- * Represents a list of sites, grouped under the same eTLD+1. For example, an
- * origin "https://www.example.com" would be grouped together with
- * "https://login.example.com" and "http://example.com" under a common eTLD+1 of
- * "example.com".
- * @typedef {{etldPlus1: string,
- * numCookies: number,
- * origins: Array<OriginInfo>}}
- */
-let SiteGroup;
-
-/**
- * The site exception information passed from the C++ handler.
- * See also: SiteException.
- * @typedef {{embeddingOrigin: string,
- * incognito: boolean,
- * origin: string,
- * displayName: string,
- * setting: !settings.ContentSetting,
- * source: !settings.SiteSettingSource}}
- */
-let RawSiteException;
-
-/**
- * The site exception after it has been converted/filtered for UI use.
- * See also: RawSiteException.
- * @typedef {{category: !settings.ContentSettingsTypes,
- * embeddingOrigin: string,
- * incognito: boolean,
- * origin: string,
- * displayName: string,
- * setting: !settings.ContentSetting,
- * enforcement: ?chrome.settingsPrivate.Enforcement,
- * controlledBy: !chrome.settingsPrivate.ControlledBy,
- * showAndroidSmsNote: (boolean|undefined)}}
- */
-let SiteException;
-
-/**
- * The chooser exception information passed from the C++ handler.
- * See also: ChooserException.
- * @typedef {{chooserType: !settings.ChooserType,
- * displayName: string,
- * object: Object,
- * sites: Array<!RawSiteException>}}
- */
-let RawChooserException;
-
-/**
- * The chooser exception after it has been converted/filtered for UI use.
- * See also: RawChooserException.
- * @typedef {{chooserType: !settings.ChooserType,
- * displayName: string,
- * object: Object,
- * sites: Array<!SiteException>}}
- */
-let ChooserException;
-
-/**
- * @typedef {{setting: !settings.ContentSetting,
- * source: !ContentSettingProvider}}
- */
-let DefaultContentSetting;
-
-/**
- * @typedef {{name: string,
- * id: string}}
- */
-let MediaPickerEntry;
-
-/**
- * @typedef {{protocol: string,
- * spec: string}}
- */
-let ProtocolHandlerEntry;
-
-/**
- * @typedef {{origin: string,
- * setting: string,
- * source: string,
- * zoom: string}}
- */
-let ZoomLevelEntry;
-
-cr.define('settings', function() {
- /** @interface */
- class SiteSettingsPrefsBrowserProxy {
- /**
- * Sets the default value for a site settings category.
- * @param {string} contentType The name of the category to change.
- * @param {string} defaultValue The name of the value to set as default.
- */
- setDefaultValueForContentType(contentType, defaultValue) {}
-
- /**
- * Gets the default value for a site settings category.
- * @param {string} contentType The name of the category to query.
- * @return {!Promise<!DefaultContentSetting>}
- */
- getDefaultValueForContentType(contentType) {}
-
- /**
- * Gets a list of sites, grouped by eTLD+1, affected by any of the content
- * settings specified by |contentTypes|.
- * @param {!Array<!settings.ContentSettingsTypes>} contentTypes A list of
- * the content types to retrieve sites for.
- * @return {!Promise<!Array<!SiteGroup>>}
- */
- getAllSites(contentTypes) {}
-
- /**
- * Gets the chooser exceptions for a particular chooser type.
- * @param {settings.ChooserType} chooserType The chooser type to grab
- * exceptions from.
- * @return {!Promise<!Array<!RawChooserException>>}
- */
- getChooserExceptionList(chooserType) {}
-
- /**
- * Converts a given number of bytes into a human-readable format, with data
- * units.
- * @param {number} numBytes The number of bytes to convert.
- * @return {!Promise<string>}
- */
- getFormattedBytes(numBytes) {}
-
- /**
- * Gets the exceptions (site list) for a particular category.
- * @param {string} contentType The name of the category to query.
- * @return {!Promise<!Array<!RawSiteException>>}
- */
- getExceptionList(contentType) {}
-
- /**
- * Gets a list of category permissions for a given origin. Note that this
- * may be different to the results retrieved by getExceptionList(), since it
- * combines different sources of data to get a permission's value.
- * @param {string} origin The origin to look up permissions for.
- * @param {!Array<!settings.ContentSettingsTypes>} contentTypes A list of
- * categories to retrieve the ContentSetting for.
- * @return {!Promise<!NodeList<!RawSiteException>>}
- */
- getOriginPermissions(origin, contentTypes) {}
-
- /**
- * Resets the permissions for a list of categories for a given origin. This
- * does not support incognito settings or patterns.
- * @param {string} origin The origin to reset permissions for.
- * @param {!Array<!settings.ContentSettingsTypes>} contentTypes A list of
- * categories to set the permission for. Typically this would be a
- * single category, but sometimes it is useful to clear any permissions
- * set for all categories.
- * @param {!settings.ContentSetting} blanketSetting The setting to set all
- * permissions listed in |contentTypes| to.
- */
- setOriginPermissions(origin, contentTypes, blanketSetting) {}
-
- /**
- * Clears the flag that's set when the user has changed the Flash permission
- * for this particular origin.
- * @param {string} origin The origin to clear the Flash preference for.
- */
- clearFlashPref(origin) {}
-
- /**
- * Resets the category permission for a given origin (expressed as primary
- * and secondary patterns). Only use this if intending to remove an
- * exception - use setOriginPermissions() for origin-scoped settings.
- * @param {string} primaryPattern The origin to change (primary pattern).
- * @param {string} secondaryPattern The embedding origin to change
- * (secondary pattern).
- * @param {string} contentType The name of the category to reset.
- * @param {boolean} incognito Whether this applies only to a current
- * incognito session exception.
- */
- resetCategoryPermissionForPattern(
- primaryPattern, secondaryPattern, contentType, incognito) {}
-
- /**
- * Removes a particular chooser object permission by origin and embedding
- * origin.
- * @param {settings.ChooserType} chooserType The chooser exception type
- * @param {string} origin The origin to look up the permission for.
- * @param {string} embeddingOrigin the embedding origin to look up.
- * @param {!Object} exception The exception to revoke permission for.
- */
- resetChooserExceptionForSite(
- chooserType, origin, embeddingOrigin, exception) {}
-
- /**
- * Sets the category permission for a given origin (expressed as primary and
- * secondary patterns). Only use this if intending to set an exception - use
- * setOriginPermissions() for origin-scoped settings.
- * @param {string} primaryPattern The origin to change (primary pattern).
- * @param {string} secondaryPattern The embedding origin to change
- * (secondary pattern).
- * @param {string} contentType The name of the category to change.
- * @param {string} value The value to change the permission to.
- * @param {boolean} incognito Whether this rule applies only to the current
- * incognito session.
- */
- setCategoryPermissionForPattern(
- primaryPattern, secondaryPattern, contentType, value, incognito) {}
-
- /**
- * Checks whether an origin is valid.
- * @param {string} origin The origin to check.
- * @return {!Promise<boolean>} True if the origin is valid.
- */
- isOriginValid(origin) {}
-
- /**
- * Checks whether a setting is valid.
- * @param {string} pattern The pattern to check.
- * @param {settings.ContentSettingsTypes} category What kind of setting,
- * e.g. Location, Camera, Cookies, etc.
- * @return {!Promise<IsValid>} Contains whether or not the pattern is
- * valid for the type, and if it is invalid, the reason why.
- */
- isPatternValidForType(pattern, category) {}
-
- /**
- * Gets the list of default capture devices for a given type of media. List
- * is returned through a JS call to updateDevicesMenu.
- * @param {string} type The type to look up.
- */
- getDefaultCaptureDevices(type) {}
-
- /**
- * Sets a default devices for a given type of media.
- * @param {string} type The type of media to configure.
- * @param {string} defaultValue The id of the media device to set.
- */
- setDefaultCaptureDevice(type, defaultValue) {}
-
- /**
- * observes _all_ of the the protocol handler state, which includes a list
- * that is returned through JS calls to 'setProtocolHandlers' along with
- * other state sent with the messages 'setIgnoredProtocolHandler' and
- * 'setHandlersEnabled'.
- */
- observeProtocolHandlers() {}
-
- /**
- * Observes one aspect of the protocol handler so that updates to the
- * enabled/disabled state are sent. A 'setHandlersEnabled' will be sent
- * from C++ immediately after receiving this observe request and updates
- * may follow via additional 'setHandlersEnabled' messages.
- *
- * If |observeProtocolHandlers| is called, there's no need to call this
- * observe as well.
- */
- observeProtocolHandlersEnabledState() {}
-
- /**
- * Enables or disables the ability for sites to ask to become the default
- * protocol handlers.
- * @param {boolean} enabled Whether sites can ask to become default.
- */
- setProtocolHandlerDefault(enabled) {}
-
- /**
- * Sets a certain url as default for a given protocol handler.
- * @param {string} protocol The protocol to set a default for.
- * @param {string} url The url to use as the default.
- */
- setProtocolDefault(protocol, url) {}
-
- /**
- * Deletes a certain protocol handler by url.
- * @param {string} protocol The protocol to delete the url from.
- * @param {string} url The url to delete.
- */
- removeProtocolHandler(protocol, url) {}
-
- /**
- * Fetches the incognito status of the current profile (whether an incognito
- * profile exists). Returns the results via onIncognitoStatusChanged.
- */
- updateIncognitoStatus() {}
-
- /**
- * Fetches the currently defined zoom levels for sites. Returns the results
- * via onZoomLevelsChanged.
- */
- fetchZoomLevels() {}
-
- /**
- * Removes a zoom levels for a given host.
- * @param {string} host The host to remove zoom levels for.
- */
- removeZoomLevel(host) {}
-
- // <if expr="chromeos">
- /**
- * Links to com.android.settings.Settings$ManageDomainUrlsActivity on ARC
- * side, this is to manage app preferences.
- */
- showAndroidManageAppLinks() {}
- // </if>
-
- /**
- * Fetches the current block autoplay state. Returns the results via
- * onBlockAutoplayStatusChanged.
- */
- fetchBlockAutoplayStatus() {}
-
- /**
- * Clears all the web storage data and cookies for a given etld+1.
- * @param {string} etldPlus1 The etld+1 to clear data from.
- */
- clearEtldPlus1DataAndCookies(etldPlus1) {}
-
- /**
- * Record All Sites Page action for metrics.
- * @param {number} action number.
- */
- recordAction(action) {}
- }
-
- /**
- * @implements {settings.SiteSettingsPrefsBrowserProxy}
- */
- class SiteSettingsPrefsBrowserProxyImpl {
- /** @override */
- setDefaultValueForContentType(contentType, defaultValue) {
- chrome.send('setDefaultValueForContentType', [contentType, defaultValue]);
- }
-
- /** @override */
- getDefaultValueForContentType(contentType) {
- return cr.sendWithPromise('getDefaultValueForContentType', contentType);
- }
-
- /** @override */
- getAllSites(contentTypes) {
- return cr.sendWithPromise('getAllSites', contentTypes);
- }
-
- /** @override */
- getChooserExceptionList(chooserType) {
- return cr.sendWithPromise('getChooserExceptionList', chooserType);
- }
-
- /** @override */
- getFormattedBytes(numBytes) {
- return cr.sendWithPromise('getFormattedBytes', numBytes);
- }
-
- /** @override */
- getExceptionList(contentType) {
- return cr.sendWithPromise('getExceptionList', contentType);
- }
-
- /** @override */
- getOriginPermissions(origin, contentTypes) {
- return cr.sendWithPromise('getOriginPermissions', origin, contentTypes);
- }
-
- /** @override */
- setOriginPermissions(origin, contentTypes, blanketSetting) {
- chrome.send(
- 'setOriginPermissions', [origin, contentTypes, blanketSetting]);
- }
-
- /** @override */
- clearFlashPref(origin) {
- chrome.send('clearFlashPref', [origin]);
- }
-
- /** @override */
- resetCategoryPermissionForPattern(
- primaryPattern, secondaryPattern, contentType, incognito) {
- chrome.send(
- 'resetCategoryPermissionForPattern',
- [primaryPattern, secondaryPattern, contentType, incognito]);
- }
-
- /** @override */
- resetChooserExceptionForSite(
- chooserType, origin, embeddingOrigin, exception) {
- chrome.send(
- 'resetChooserExceptionForSite',
- [chooserType, origin, embeddingOrigin, exception]);
- }
-
- /** @override */
- setCategoryPermissionForPattern(
- primaryPattern, secondaryPattern, contentType, value, incognito) {
- chrome.send(
- 'setCategoryPermissionForPattern',
- [primaryPattern, secondaryPattern, contentType, value, incognito]);
- }
-
- /** @override */
- isOriginValid(origin) {
- return cr.sendWithPromise('isOriginValid', origin);
- }
-
- /** @override */
- isPatternValidForType(pattern, category) {
- return cr.sendWithPromise('isPatternValidForType', pattern, category);
- }
-
- /** @override */
- getDefaultCaptureDevices(type) {
- chrome.send('getDefaultCaptureDevices', [type]);
- }
-
- /** @override */
- setDefaultCaptureDevice(type, defaultValue) {
- chrome.send('setDefaultCaptureDevice', [type, defaultValue]);
- }
-
- /** @override */
- observeProtocolHandlers() {
- chrome.send('observeProtocolHandlers');
- }
-
- /** @override */
- observeProtocolHandlersEnabledState() {
- chrome.send('observeProtocolHandlersEnabledState');
- }
-
- /** @override */
- setProtocolHandlerDefault(enabled) {
- chrome.send('setHandlersEnabled', [enabled]);
- }
-
- /** @override */
- setProtocolDefault(protocol, url) {
- chrome.send('setDefault', [protocol, url]);
- }
-
- /** @override */
- removeProtocolHandler(protocol, url) {
- chrome.send('removeHandler', [protocol, url]);
- }
-
- /** @override */
- updateIncognitoStatus() {
- chrome.send('updateIncognitoStatus');
- }
-
- /** @override */
- fetchZoomLevels() {
- chrome.send('fetchZoomLevels');
- }
-
- /** @override */
- removeZoomLevel(host) {
- chrome.send('removeZoomLevel', [host]);
- }
-
- // <if expr="chromeos">
- /** @override */
- showAndroidManageAppLinks() {
- chrome.send('showAndroidManageAppLinks');
- }
- // </if>
-
- /** @override */
- fetchBlockAutoplayStatus() {
- chrome.send('fetchBlockAutoplayStatus');
- }
-
- /** @override */
- clearEtldPlus1DataAndCookies(etldPlus1) {
- chrome.send('clearEtldPlus1DataAndCookies', [etldPlus1]);
- }
-
- /** @override */
- recordAction(action) {
- chrome.send('recordAction', [action]);
- }
- }
-
- // The singleton instance_ is replaced with a test version of this wrapper
- // during testing.
- cr.addSingletonGetter(SiteSettingsPrefsBrowserProxyImpl);
-
- return {
- SiteSettingsPrefsBrowserProxy: SiteSettingsPrefsBrowserProxy,
- SiteSettingsPrefsBrowserProxyImpl: SiteSettingsPrefsBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.html b/chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.html
deleted file mode 100644
index d2867362724..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="website_usage_private_api.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.js b/chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.js
deleted file mode 100644
index 214b3ff9259..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/website_usage_private_api.js
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-'use strict';
-
-Polymer({
- is: 'website-usage-private-api',
-
- properties: {
- /**
- * The amount of data used by the given website.
- */
- websiteDataUsage: {
- type: String,
- notify: true,
- },
-
- /**
- * The number of cookies used by the given website.
- */
- websiteCookieUsage: {
- type: String,
- notify: true,
- },
- },
-
- /** @override */
- attached: function() {
- settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance = this;
- },
-
- /** @param {string} host */
- fetchUsageTotal: function(host) {
- settings.WebsiteUsagePrivateApi.fetchUsageTotal(host);
- },
-
- /**
- * @param {string} origin
- */
- clearUsage: function(origin) {
- settings.WebsiteUsagePrivateApi.clearUsage(origin);
- },
-
- /** @param {string} origin */
- notifyUsageDeleted: function(origin) {
- this.fire('usage-deleted', {origin: origin});
- },
-});
-})();
-
-cr.define('settings.WebsiteUsagePrivateApi', function() {
- /**
- * @type {Object} An instance of the polymer object defined above.
- * All data will be set here.
- */
- const websiteUsagePolymerInstance = null;
-
- /**
- * @type {string} The host for which the usage total is being fetched.
- */
- let hostName;
-
- /**
- * Encapsulates the calls between JS and C++ to fetch how much storage the
- * host is using.
- * Will update the data in |websiteUsagePolymerInstance|.
- */
- const fetchUsageTotal = function(host) {
- const instance =
- settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
- if (instance != null) {
- instance.websiteDataUsage = '';
- }
-
- hostName = host;
- chrome.send('fetchUsageTotal', [host]);
- };
-
- /**
- * Callback for when the usage total is known.
- * @param {string} host The host that the usage was fetched for.
- * @param {string} usage The string showing how much data the given host
- * is using.
- */
- const returnUsageTotal = function(host, usage, cookies) {
- const instance =
- settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
- if (instance == null) {
- return;
- }
-
- if (hostName == host) {
- instance.websiteDataUsage = usage;
- instance.websiteCookieUsage = cookies;
- }
- };
-
- /**
- * Deletes the storage being used for a given origin.
- * @param {string} origin The origin to delete storage for.
- */
- const clearUsage = function(origin) {
- chrome.send('clearUsage', [origin]);
- const instance =
- settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
- if (instance == null) {
- return;
- }
-
- instance.notifyUsageDeleted(origin);
- };
-
- return {
- websiteUsagePolymerInstance: websiteUsagePolymerInstance,
- fetchUsageTotal: fetchUsageTotal,
- returnUsageTotal: returnUsageTotal,
- clearUsage: clearUsage,
- };
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html b/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html
deleted file mode 100644
index f83154708ff..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_favicon.html">
-<link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="site_settings_prefs_browser_proxy.html">
-
-<dom-module id="zoom-levels">
- <template>
- <style include="settings-shared">
- :host {
- display: block;
- }
-
- .zoom-label {
- color: var(--cr-secondary-text-color);
- margin-inline-end: 16px;
- }
-
- #empty {
- margin-top: 15px;
- }
-
- .list-item site-favicon {
- flex-shrink: 0;
- }
-
- .list-item .middle {
- overflow-x: hidden;
- text-overflow: ellipsis;
- }
- </style>
- <div class="list-frame vertical-list" id="listContainer">
- <iron-list id="list" preserve-focus items="[[sites_]]"
- class="cr-separators" risk-selection>
- <template>
- <div class="list-item" first$="[[!index]]">
- <site-favicon url="[[item.originForFavicon]]"></site-favicon>
- <div class="middle">
- <span class="url-directionality">[[item.displayName]]</span>
- </div>
- <div class="zoom-label">[[item.zoom]]</div>
- <cr-icon-button class="icon-clear" on-click="removeZoomLevel_"
- title="$i18n{siteSettingsRemoveZoomLevel}"
- tabindex$="[[tabIndex]]"></cr-icon-button>
- </div>
- </template>
- </iron-list>
- <div id="empty" hidden$="[[!showNoSites_]]">
- $i18n{siteSettingsNoZoomedSites}
- </div>
- </div>
- </template>
- <script src="zoom_levels.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.js b/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.js
deleted file mode 100644
index e600e08fdee..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'zoom-levels' is the polymer element for showing the sites that are zoomed in
- * or out.
- */
-
-Polymer({
- is: 'zoom-levels',
-
- behaviors: [
- ListPropertyUpdateBehavior,
- SiteSettingsBehavior,
- WebUIListenerBehavior,
- ],
-
- properties: {
- /**
- * Array of sites that are zoomed in or out.
- * @type {!Array<ZoomLevelEntry>}
- */
- sites_: {
- type: Array,
- value: () => [],
- },
-
- /** @private */
- showNoSites_: {
- type: Boolean,
- value: false,
- },
- },
-
- /** @override */
- ready: function() {
- this.addWebUIListener(
- 'onZoomLevelsChanged', this.onZoomLevelsChanged_.bind(this));
- this.browserProxy.fetchZoomLevels();
- },
-
- /**
- * A handler for when zoom levels change.
- * @param {!Array<ZoomLevelEntry>} sites The up to date list of sites and
- * their zoom levels.
- */
- onZoomLevelsChanged_: function(sites) {
- this.updateList('sites_', item => item.origin, sites);
- this.showNoSites_ = this.sites_.length == 0;
- },
-
- /**
- * A handler for when a zoom level for a site is deleted.
- * @param {!{model: !{index: number}}} event
- * @private
- */
- removeZoomLevel_: function(event) {
- const site = this.sites_[event.model.index];
- this.browserProxy.removeZoomLevel(site.origin);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.html b/chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.html
deleted file mode 100644
index 3db16d2d4f1..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.html
+++ /dev/null
@@ -1,272 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../icons.html">
-<link rel="import" href="../route.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../site_settings/constants.html">
-<link rel="import" href="../site_settings/site_settings_behavior.html">
-<link rel="import" href="../site_settings/site_settings_prefs_browser_proxy.html">
-
-<dom-module id="settings-site-settings-page">
- <template>
- <style include="settings-shared">
- cr-link-row {
- --cr-icon-button-margin-start: 20px;
- }
-
- /* Add min-height to prevent shifting as sublabels load. */
- .two-line {
- --cr-section-min-height: var(--cr-section-two-line-min-height);
- }
- </style>
- <div class="settings-box first line-only">
- <h2 class="first">$i18n{siteSettingsAllSites}</h2>
- </div>
- <cr-link-row data-route="SITE_SETTINGS_ALL" id="all-sites"
- label="$i18n{siteSettingsAllSitesDescription}"
- on-click="onTapNavigate_"></cr-link-row>
- <div class="settings-box first line-only">
- <h2>$i18n{siteSettingsPermissions}</h2>
- </div>
-
- <cr-link-row class="two-line" data-route="SITE_SETTINGS_COOKIES"
- id="cookies" label="$i18n{siteSettingsCookies}"
- on-click="onTapNavigate_" start-icon="settings:cookie"
- sub-label="[[defaultSettingLabel_(
- default_.cookies,
- '$i18nPolymer{siteSettingsCookiesAllowed}',
- '$i18nPolymer{siteSettingsBlocked}',
- '$i18nPolymer{deleteDataPostSession}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_LOCATION"
- id="location" label="$i18n{siteSettingsLocation}"
- on-click="onTapNavigate_" start-icon="cr:location-on"
- sub-label="[[defaultSettingLabel_(
- default_.location,
- '$i18nPolymer{siteSettingsAskBeforeAccessing}',
- '$i18nPolymer{siteSettingsBlocked}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_CAMERA"
- id="camera" label="$i18n{siteSettingsCamera}" on-click="onTapNavigate_"
- start-icon="cr:videocam"
- sub-label="[[defaultSettingLabel_(
- default_.mediaStreamCamera,
- '$i18nPolymer{siteSettingsAskBeforeAccessing}',
- '$i18nPolymer{siteSettingsBlocked}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_MICROPHONE"
- id="microphone" label="$i18n{siteSettingsMic}" on-click="onTapNavigate_"
- start-icon="cr:mic"
- sub-label="[[defaultSettingLabel_(
- default_.mediaStreamMic,
- '$i18nPolymer{siteSettingsAskBeforeAccessing}',
- '$i18nPolymer{siteSettingsBlocked}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_SENSORS"
- id="sensors" label="$i18n{siteSettingsSensors}"
- on-click="onTapNavigate_" start-icon="settings:sensors"
- sub-label="[[defaultSettingLabel_(
- default_.sensors,
- '$i18nPolymer{siteSettingsSensorsAllow}',
- '$i18nPolymer{siteSettingsSensorsBlock}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_NOTIFICATIONS"
- id="notifications" label="$i18n{siteSettingsNotifications}"
- on-click="onTapNavigate_" start-icon="settings:notifications"
- sub-label="[[defaultSettingLabel_(
- default_.notifications,
- '$i18nPolymer{siteSettingsAskBeforeSending}',
- '$i18nPolymer{siteSettingsBlocked}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_JAVASCRIPT"
- id="javascript" label="$i18n{siteSettingsJavascript}"
- on-click="onTapNavigate_" start-icon="settings:code"
- sub-label="[[defaultSettingLabel_(
- default_.javascript,
- '$i18nPolymer{siteSettingsAllowed}',
- '$i18nPolymer{siteSettingsBlocked}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_FLASH" id="flash"
- label="$i18n{siteSettingsFlash}" on-click="onTapNavigate_"
- start-icon="cr:extension"
- sub-label="[[defaultSettingLabel_(
- default_.plugins,
- '$i18nPolymer{siteSettingsFlashAskFirst}',
- '$i18nPolymer{siteSettingsFlashBlock}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_IMAGES"
- id="images" label="$i18n{siteSettingsImages}" on-click="onTapNavigate_"
- start-icon="settings:photo"
- sub-label="[[defaultSettingLabel_(
- default_.images,
- '$i18nPolymer{siteSettingsShowAll}',
- '$i18nPolymer{siteSettingsDontShowImages}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_POPUPS"
- id="popups" label="$i18n{siteSettingsPopups}" on-click="onTapNavigate_"
- start-icon="cr:open-in-new"
- sub-label="[[defaultSettingLabel_(
- default_.popups,
- '$i18nPolymer{siteSettingsAllowed}',
- '$i18nPolymer{siteSettingsBlocked}')]]"></cr-link-row>
-
- <template is="dom-if" if="[[enableSafeBrowsingSubresourceFilter_]]">
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_ADS" id="ads"
- label="$i18n{siteSettingsAds}" on-click="onTapNavigate_"
- start-icon="settings:ads"
- sub-label="[[defaultSettingLabel_(
- default_.ads,
- '$i18nPolymer{siteSettingsAllowed}',
- '$i18nPolymer{siteSettingsAdsBlock}')]]"></cr-link-row>
- </template>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_BACKGROUND_SYNC"
- id="background-sync" label="$i18n{siteSettingsBackgroundSync}"
- on-click="onTapNavigate_" start-icon="cr:sync"
- sub-label="[[defaultSettingLabel_(
- default_.backgroundSync,
- '$i18nPolymer{siteSettingsAllowRecentlyClosedSites}',
- '$i18nPolymer{siteSettingsBackgroundSyncBlocked}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_SOUND"
- id="sound" label="$i18n{siteSettingsSound}" on-click="onTapNavigate_"
- start-icon="settings:volume-up"
- sub-label="[[defaultSettingLabel_(
- default_.sound,
- '$i18nPolymer{siteSettingsSoundAllow}',
- '$i18nPolymer{siteSettingsSoundBlock}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line"
- data-route="SITE_SETTINGS_AUTOMATIC_DOWNLOADS" id="automatic-downloads"
- label="$i18n{siteSettingsAutomaticDownloads}" on-click="onTapNavigate_"
- start-icon="cr:file-download"
- sub-label="[[defaultSettingLabel_(
- default_.multipleAutomaticDownloads,
- '$i18nPolymer{siteSettingsAutoDownloadAsk}',
- '$i18nPolymer{siteSettingsAutoDownloadBlock}')]]"></cr-link-row>
-
- <cr-link-row class="hr two-line"
- data-route="SITE_SETTINGS_UNSANDBOXED_PLUGINS" id="unsandboxed-plugins"
- label="$i18n{siteSettingsUnsandboxedPlugins}" on-click="onTapNavigate_"
- start-icon="cr:extension"
- sub-label="[[defaultSettingLabel_(
- default_.ppapiBroker,
- '$i18nPolymer{siteSettingsUnsandboxedPluginsAsk}',
- '$i18nPolymer{siteSettingsUnsandboxedPluginsBlock}')]]">
- </cr-link-row>
-
- <template is="dom-if" if="[[!isGuest_]]">
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_HANDLERS"
- id="protocol-handlers" label="$i18n{siteSettingsHandlers}"
- on-click="onTapNavigate_" start-icon="settings:protocol-handler"
- sub-label="[[defaultSettingLabel_(
- default_.registerProtocolHandler,
- '$i18nPolymer{siteSettingsHandlersAsk}',
- '$i18nPolymer{siteSettingsHandlersBlocked}')]]"></cr-link-row>
- </template>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_MIDI_DEVICES"
- id="midi-devices" label="$i18n{siteSettingsMidiDevices}"
- on-click="onTapNavigate_" start-icon="settings:midi"
- sub-label="[[defaultSettingLabel_(
- default_.midiSysex,
- '$i18nPolymer{siteSettingsMidiDevicesAsk}',
- '$i18nPolymer{siteSettingsMidiDevicesBlock}')]]"></cr-link-row>
-
- <cr-link-row class="hr" data-route="SITE_SETTINGS_ZOOM_LEVELS"
- id="zoom-levels" label="$i18n{siteSettingsZoomLevels}"
- on-click="onTapNavigate_" start-icon="settings:zoom-in"></cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_USB_DEVICES"
- id="usb-devices" label="$i18n{siteSettingsUsbDevices}"
- on-click="onTapNavigate_" start-icon="settings:usb"
- sub-label="[[defaultSettingLabel_(
- default_.usbDevices,
- '$i18nPolymer{siteSettingsUsbDevicesAsk}',
- '$i18nPolymer{siteSettingsUsbDevicesBlock}')]]"></cr-link-row>
-
- <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
- <cr-link-row
- class="hr two-line"
- data-route="SITE_SETTINGS_SERIAL_PORTS"
- icon-class="subpage-arrow"
- id="serial-ports"
- label="$i18n{siteSettingsSerialPorts}"
- on-click="onTapNavigate_"
- start-icon="settings:serial-port"
- sub-label="[[defaultSettingLabel_(
- default_.serialPorts,
- '$i18nPolymer{siteSettingsSerialPortsAsk}',
- '$i18nPolymer{siteSettingsSerialPortsBlock}')]]">
- </cr-link-row>
- </template>
-
- <template is="dom-if" if="[[enableNativeFileSystemWriteContentSetting_]]">
- <cr-link-row class="hr two-line"
- data-route="SITE_SETTINGS_NATIVE_FILE_SYSTEM_WRITE"
- icon-class="subpage-arrow" id="native-file-system-write"
- label="$i18n{siteSettingsNativeFileSystemWrite}"
- on-click="onTapNavigate_" start-icon="settings:save-original"
- sub-label="[[defaultSettingLabel_(
- default_.filesystem,
- '$i18nPolymer{siteSettingsNativeFileSystemWriteAsk}',
- '$i18nPolymer{siteSettingsNativeFileSystemWriteBlock}')]]">
- </cr-link-row>
- </template>
-
- <cr-link-row class="hr" data-route="SITE_SETTINGS_PDF_DOCUMENTS"
- id="pdf-documents" label="$i18n{siteSettingsPdfDocuments}"
- on-click="onTapNavigate_" start-icon="settings:pdf"></cr-link-row>
-
- <cr-link-row class="hr" data-route="SITE_SETTINGS_PROTECTED_CONTENT"
- id="protected-content" label="$i18n{siteSettingsProtectedContent}"
- on-click="onTapNavigate_" start-icon="settings:protected-content">
- </cr-link-row>
-
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_CLIPBOARD"
- id="clipboard" label="$i18n{siteSettingsClipboard}"
- on-click="onTapNavigate_" start-icon="settings:clipboard"
- sub-label="[[defaultSettingLabel_(
- default_.clipboard,
- '$i18nPolymer{siteSettingsAskBeforeAccessing}',
- '$i18nPolymer{siteSettingsBlocked}')]]">
- </cr-link-row>
-
- <template is="dom-if" if="[[enablePaymentHandlerContentSetting_]]">
- <cr-link-row class="hr two-line"
- data-route="SITE_SETTINGS_PAYMENT_HANDLER" id="paymentHandler"
- label="$i18n{siteSettingsPaymentHandler}" on-click="onTapNavigate_"
- start-icon="settings:payment-handler"
- sub-label="[[defaultSettingLabel_(
- default_.paymentHandler,
- '$i18nPolymer{siteSettingsPaymentHandlerAllow}',
- '$i18nPolymer{siteSettingsPaymentHandlerBlock}')]]"></cr-link-row>
- </template>
-
- <template is="dom-if" if="[[enableInsecureContentContentSetting_]]">
- <cr-link-row class="hr two-line" data-route="SITE_SETTINGS_MIXEDSCRIPT"
- label="$i18n{siteSettingsInsecureContent}"
- on-click="onTapNavigate_"
- start-icon="settings:insecure-content"
- sub-label="$i18n{siteSettingsInsecureContentBlock}"
- </cr-link-row>
- </template>
-
- <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]">
- <cr-link-row class="hr two-line"
- data-route="SITE_SETTINGS_BLUETOOTH_SCANNING"
- id="bluetooth-scanning" label="$i18n{siteSettingsBluetoothScanning}"
- on-click="onTapNavigate_" start-icon="settings:bluetooth-scanning"
- sub-label="[[defaultSettingLabel_(
- default_.bluetoothScanning,
- '$i18nPolymer{siteSettingsBluetoothScanningAsk}',
- '$i18nPolymer{siteSettingsBluetoothScanningBlock}')]]">
- </cr-link-row>
- </template>
- </template>
- <script src="site_settings_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.js b/chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
deleted file mode 100644
index 588b6d078aa..00000000000
--- a/chromium/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * 'settings-site-settings-page' is the settings page containing privacy and
- * security site settings.
- */
-
-Polymer({
- is: 'settings-site-settings-page',
-
- behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
-
- properties: {
- /**
- * An object to bind default values to (so they are not in the |this|
- * scope). The keys of this object are the values of the
- * settings.ContentSettingsTypes enum.
- * @private
- */
- default_: {
- type: Object,
- value: function() {
- return {};
- },
- },
-
- /** @private */
- isGuest_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('isGuest');
- }
- },
-
- /** @private */
- enableSafeBrowsingSubresourceFilter_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableSafeBrowsingSubresourceFilter');
- }
- },
-
- /** @private */
- enableExperimentalWebPlatformFeatures_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures');
- },
- },
-
- /** @private */
- enablePaymentHandlerContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enablePaymentHandlerContentSetting');
- }
- },
-
- /** @private */
- enableInsecureContentContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean('enableInsecureContentContentSetting');
- }
- },
-
- /** @private */
- enableNativeFileSystemWriteContentSetting_: {
- type: Boolean,
- value: function() {
- return loadTimeData.getBoolean(
- 'enableNativeFileSystemWriteContentSetting');
- }
- },
-
- /** @type {!Map<string, (string|Function)>} */
- focusConfig: {
- type: Object,
- observer: 'focusConfigChanged_',
- },
- },
-
- /**
- * @param {!Map<string, string>} newConfig
- * @param {?Map<string, string>} oldConfig
- * @private
- */
- focusConfigChanged_: function(newConfig, oldConfig) {
- // focusConfig is set only once on the parent, so this observer should only
- // fire once.
- assert(!oldConfig);
-
- // Populate the |focusConfig| map of the parent <settings-animated-pages>
- // element, with additional entries that correspond to subpage trigger
- // elements residing in this element's Shadow DOM.
- const R = settings.routes;
- const pairs = [
- [R.SITE_SETTINGS_COOKIES, 'cookies'],
- [R.SITE_SETTINGS_LOCATION, 'location'],
- [R.SITE_SETTINGS_CAMERA, 'camera'],
- [R.SITE_SETTINGS_MICROPHONE, 'microphone'],
- [R.SITE_SETTINGS_NOTIFICATIONS, 'notifications'],
- [R.SITE_SETTINGS_JAVASCRIPT, 'javascript'],
- [R.SITE_SETTINGS_SOUND, 'sound'],
- [R.SITE_SETTINGS_FLASH, 'flash'],
- [R.SITE_SETTINGS_IMAGES, 'images'],
- [R.SITE_SETTINGS_POPUPS, 'popups'],
- [R.SITE_SETTINGS_BACKGROUND_SYNC, 'background-sync'],
- [R.SITE_SETTINGS_AUTOMATIC_DOWNLOADS, 'automatic-downloads'],
- [R.SITE_SETTINGS_UNSANDBOXED_PLUGINS, 'unsandboxed-plugins'],
- [R.SITE_SETTINGS_HANDLERS, 'protocol-handlers'],
- [R.SITE_SETTINGS_MIDI_DEVICES, 'midi-devices'],
- [R.SITE_SETTINGS_ADS, 'ads'],
- [R.SITE_SETTINGS_ZOOM_LEVELS, 'zoom-levels'],
- [R.SITE_SETTINGS_USB_DEVICES, 'usb-devices'],
- [R.SITE_SETTINGS_PDF_DOCUMENTS, 'pdf-documents'],
- [R.SITE_SETTINGS_PROTECTED_CONTENT, 'protected-content'],
- [R.SITE_SETTINGS_CLIPBOARD, 'clipboard'],
- [R.SITE_SETTINGS_SENSORS, 'sensors'],
- ];
-
- if (this.enablePaymentHandlerContentSetting_) {
- pairs.push([R.SITE_SETTINGS_PAYMENT_HANDLER, 'paymentHandler']);
- }
-
- if (this.enableExperimentalWebPlatformFeatures_) {
- pairs.push([R.SITE_SETTINGS_SERIAL_PORTS, 'serial-ports']);
- }
-
- if (this.enableExperimentalWebPlatformFeatures_) {
- pairs.push([R.SITE_SETTINGS_BLUETOOTH_SCANNING, 'bluetooth-scanning']);
- }
-
- if (this.enableNativeFileSystemWriteContentSetting_) {
- pairs.push([
- R.SITE_SETTINGS_NATIVE_FILE_SYSTEM_WRITE, 'native-file-system-write'
- ]);
- }
-
- if (this.enableInsecureContentContentSetting_) {
- pairs.push([R.SITE_SETTINGS_MIXEDSCRIPT, 'mixed-script']);
- }
-
- pairs.forEach(([route, id]) => {
- this.focusConfig.set(route.path, () => this.async(() => {
- cr.ui.focusWithoutInk(assert(this.$$(`#${id}`)));
- }));
- });
- },
-
- /** @override */
- ready: function() {
- this.ContentSettingsTypes = settings.ContentSettingsTypes;
- this.ALL_SITES = settings.ALL_SITES;
-
- const keys = Object.keys(settings.ContentSettingsTypes);
- for (let i = 0; i < keys.length; ++i) {
- const key = settings.ContentSettingsTypes[keys[i]];
- // Default labels are not applicable to ZOOM.
- if (key == settings.ContentSettingsTypes.ZOOM_LEVELS) {
- continue;
- }
- // Protocol handlers are not available (and will DCHECK) in guest mode.
- if (this.isGuest_ &&
- key == settings.ContentSettingsTypes.PROTOCOL_HANDLERS) {
- continue;
- }
- // Similarly, protected content is only available in CrOS.
- // <if expr="not chromeos">
- if (key == settings.ContentSettingsTypes.PROTECTED_CONTENT) {
- continue;
- }
- // </if>
- this.updateDefaultValueLabel_(key);
- }
-
- this.addWebUIListener(
- 'contentSettingCategoryChanged',
- this.updateDefaultValueLabel_.bind(this));
- this.addWebUIListener(
- 'setHandlersEnabled', this.updateHandlersEnabled_.bind(this));
- this.browserProxy.observeProtocolHandlersEnabledState();
- },
-
- /**
- * @param {string} setting Value from settings.ContentSetting.
- * @param {string} enabled Non-block label ('feature X not allowed').
- * @param {string} disabled Block label (likely just, 'Blocked').
- * @param {?string} other Tristate value (maybe, 'session only').
- * @private
- */
- defaultSettingLabel_: function(setting, enabled, disabled, other) {
- if (setting == settings.ContentSetting.BLOCK) {
- return disabled;
- }
- if (setting == settings.ContentSetting.ALLOW) {
- return enabled;
- }
- if (other) {
- return other;
- }
- return enabled;
- },
-
- /**
- * @param {string} category The category to update.
- * @private
- */
- updateDefaultValueLabel_: function(category) {
- this.browserProxy.getDefaultValueForContentType(category).then(
- defaultValue => {
- this.set(
- 'default_.' + Polymer.CaseMap.dashToCamelCase(category),
- defaultValue.setting);
- });
- },
-
- /**
- * The protocol handlers have a separate enabled/disabled notifier.
- * @param {boolean} enabled
- * @private
- */
- updateHandlersEnabled_: function(enabled) {
- const category = settings.ContentSettingsTypes.PROTOCOL_HANDLERS;
- this.set(
- 'default_.' + Polymer.CaseMap.dashToCamelCase(category),
- enabled ? settings.ContentSetting.ALLOW :
- settings.ContentSetting.BLOCK);
- },
-
- /**
- * Navigate to the route specified in the event dataset.
- * @param {!Event} event The tap event.
- * @private
- */
- onTapNavigate_: function(event) {
- const dataSet =
- /** @type {{route: string}} */ (event.currentTarget.dataset);
- settings.navigateTo(settings.routes[dataSet.route]);
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/system_page/system_page.html b/chromium/chrome/browser/resources/settings/system_page/system_page.html
deleted file mode 100644
index a6117d772a5..00000000000
--- a/chromium/chrome/browser/resources/settings/system_page/system_page.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="../controls/extension_controlled_indicator.html">
-<link rel="import" href="../controls/settings_toggle_button.html">
-<link rel="import" href="../lifetime_browser_proxy.html">
-<link rel="import" href="../prefs/prefs.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="system_page_browser_proxy.html">
-
-<dom-module id="settings-system-page">
- <template>
- <style include="settings-shared"></style>
-<if expr="not is_macosx">
- <settings-toggle-button class="first"
- pref="{{prefs.background_mode.enabled}}"
- label="$i18n{backgroundAppsLabel}">
- </settings-toggle-button>
-</if>
- <settings-toggle-button id="hardwareAcceleration"
- pref="{{prefs.hardware_acceleration_mode.enabled}}"
- label="$i18n{hardwareAccelerationLabel}">
- <template is="dom-if" if="[[shouldShowRestart_(
- prefs.hardware_acceleration_mode.enabled.value)]]">
- <cr-button on-click="onRestartTap_" slot="more-actions">
- $i18n{restart}
- </cr-button>
- </template>
- </settings-toggle-button>
-
- <div id="proxy" class="settings-box" on-click="onProxyTap_"
- actionable$="[[isProxyDefault_]]">
- <div class="start settings-box-text" hidden$="[[!isProxyDefault_]]">
- $i18n{proxySettingsLabel}
- </div>
- <div class="start settings-box-text"
- hidden$="[[!prefs.proxy.extensionId]]">
- $i18n{proxySettingsExtensionLabel}
- </div>
- <div class="start settings-box-text"
- hidden$="[[!isProxyEnforcedByPolicy_]]">
- $i18n{proxySettingsPolicyLabel}
- </div>
- <cr-icon-button class="icon-external"
- hidden$="[[isProxyEnforcedByPolicy_]]"
- aria-label="$i18n{proxySettingsLabel}"></cr-icon-button>
- <template is="dom-if" if="[[isProxyEnforcedByPolicy_]]">
- <cr-policy-pref-indicator pref="[[prefs.proxy]]"
- icon-aria-label="$i18n{proxySettingsLabel}">
- </cr-policy-pref-indicator>
- </template>
- </div>
- <template is="dom-if" if="[[prefs.proxy.extensionId]]">
- <div class="settings-box continuation">
- <extension-controlled-indicator class="start"
- extension-id="[[prefs.proxy.extensionId]]"
- extension-name="[[prefs.proxy.controlledByName]]"
- extension-can-be-disabled="[[prefs.proxy.extensionCanBeDisabled]]"
- on-extension-disable="onExtensionDisable_">
- </extension-controlled-indicator>
- </div>
- </template>
- </template>
- <script src="system_page.js"></script>
-</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/system_page/system_page.js b/chromium/chrome/browser/resources/settings/system_page/system_page.js
deleted file mode 100644
index 7707ed9cf67..00000000000
--- a/chromium/chrome/browser/resources/settings/system_page/system_page.js
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Settings that affect how Chrome interacts with the underlying
- * operating system (i.e. network, background processes, hardware).
- */
-
-Polymer({
- is: 'settings-system-page',
-
- properties: {
- prefs: {
- type: Object,
- notify: true,
- },
-
- /** @private */
- isProxyEnforcedByPolicy_: {
- type: Boolean,
- },
-
- /** @private */
- isProxyDefault_: {
- type: Boolean,
- },
- },
-
- observers: [
- 'observeProxyPrefChanged_(prefs.proxy.*)',
- ],
-
- /**
- * @private
- */
- observeProxyPrefChanged_: function() {
- const pref = this.get('prefs.proxy');
- // TODO(dbeam): do types of policy other than USER apply on ChromeOS?
- this.isProxyEnforcedByPolicy_ =
- pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED &&
- pref.controlledBy == chrome.settingsPrivate.ControlledBy.USER_POLICY;
- this.isProxyDefault_ = !this.isProxyEnforcedByPolicy_ && !pref.extensionId;
- },
-
- /** @private */
- onExtensionDisable_: function() {
- // TODO(dbeam): this is a pretty huge bummer. It means there are things
- // (inputs) that our prefs system is not observing. And that changes from
- // other sources (i.e. disabling/enabling an extension from
- // chrome://extensions or from the omnibox directly) will not update
- // |this.prefs.proxy| directly (nor the UI). We should fix this eventually.
- this.fire('refresh-pref', 'proxy');
- },
-
- /** @private */
- onProxyTap_: function() {
- if (this.isProxyDefault_) {
- settings.SystemPageBrowserProxyImpl.getInstance().showProxySettings();
- }
- },
-
- /** @private */
- onRestartTap_: function(e) {
- // Prevent event from bubbling up to the toggle button.
- e.stopPropagation();
- // TODO(dbeam): we should prompt before restarting the browser.
- settings.LifetimeBrowserProxyImpl.getInstance().restart();
- },
-
- /**
- * @param {boolean} enabled Whether hardware acceleration is currently
- * enabled.
- * @private
- */
- shouldShowRestart_: function(enabled) {
- const proxy = settings.SystemPageBrowserProxyImpl.getInstance();
- return enabled != proxy.wasHardwareAccelerationEnabledAtStartup();
- },
-});
diff --git a/chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.html b/chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.html
deleted file mode 100644
index 47d587f83a7..00000000000
--- a/chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="system_page_browser_proxy.js"></script>
diff --git a/chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.js b/chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.js
deleted file mode 100644
index 6f948d2286d..00000000000
--- a/chromium/chrome/browser/resources/settings/system_page/system_page_browser_proxy.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Handles interprocess communication for the system page. */
-
-cr.define('settings', function() {
- /** @interface */
- class SystemPageBrowserProxy {
- /** Shows the native system proxy settings. */
- showProxySettings() {}
-
- /**
- * @return {boolean} Whether hardware acceleration was enabled when the user
- * started Chrome.
- */
- wasHardwareAccelerationEnabledAtStartup() {}
- }
-
- /**
- * @implements {settings.SystemPageBrowserProxy}
- */
- class SystemPageBrowserProxyImpl {
- /** @override */
- showProxySettings() {
- chrome.send('showProxySettings');
- }
-
- /** @override */
- wasHardwareAccelerationEnabledAtStartup() {
- return loadTimeData.getBoolean('hardwareAccelerationEnabledAtStartup');
- }
- }
-
- cr.addSingletonGetter(SystemPageBrowserProxyImpl);
-
- return {
- SystemPageBrowserProxy: SystemPageBrowserProxy,
- SystemPageBrowserProxyImpl: SystemPageBrowserProxyImpl,
- };
-});
diff --git a/chromium/chrome/common/DEPS b/chromium/chrome/common/DEPS
new file mode 100644
index 00000000000..33b6ceeeda3
--- /dev/null
+++ b/chromium/chrome/common/DEPS
@@ -0,0 +1,70 @@
+include_rules = [
+ "+chrome/chrome_elf/chrome_elf_main.h",
+ "+chrome/grit",
+ "+chrome/install_static",
+ "+chrome/services/file_util/public",
+ "+chromeos", # For chromeos_switches.h
+ "+components/autofill/content/common",
+ "+components/autofill/core/common",
+ "+components/bookmarks/common",
+ "+components/cast_certificate",
+ "+components/cdm/common",
+ "+components/cloud_devices/common",
+ "+components/content_settings/core/common",
+ "+components/crash/content/app",
+ "+components/crash/core/common",
+ "+components/crx_file",
+ "+components/data_reduction_proxy/core/common",
+ "+components/dom_distiller/core",
+ "+components/favicon_base",
+ "+components/flags_ui/flags_ui_switches.h",
+ "+components/gcm_driver",
+ "+components/metrics/call_stack_profile_builder.h",
+ "+components/metrics/call_stack_profile_metrics_provider.h",
+ "+components/metrics/call_stack_profile_params.h",
+ "+components/metrics/child_call_stack_profile_collector.h",
+ "+components/metrics/client_info.h",
+ "+components/metrics/metadata_recorder.h",
+ "+components/metrics/metrics_pref_names.h",
+ "+components/metrics/public",
+ "+components/nacl/common",
+ "+components/net_log",
+ "+components/network_session_configurator/common",
+ "+components/ntp_tiles",
+ "+components/nux",
+ "+components/offline_pages/buildflags",
+ "+components/password_manager/core/common",
+ "+components/policy/core/common",
+ "+components/prefs",
+ "+components/printing/common",
+ "+components/safe_browsing/buildflags.h",
+ "+components/safe_browsing/proto/csd.pb.h",
+ "+components/safe_browsing/web_ui/constants.h",
+ "+components/services/app_service/public",
+ "+components/strings/grit/components_strings.h",
+ "+components/translate/core/common",
+ "+components/url_formatter",
+ "+components/variations/net",
+ "+components/version_info",
+ "+device/vr/buildflags/buildflags.h",
+ "+extensions/buildflags",
+ "+extensions/common",
+ "+gin/public", # For profiling.cc
+ "+google_apis/gaia", # For gaia_switches.h
+ "+media",
+ "+ppapi/c",
+ "+ppapi/shared_impl",
+ "+ppapi/thunk",
+ "+rlz/buildflags/buildflags.h",
+ "+sandbox/linux/services/credentials.h",
+ "+services/network/public/cpp",
+ "+services/network/public/mojom",
+ "+third_party/blink/public/mojom",
+ "+third_party/boringssl/src/include",
+ "+third_party/metrics_proto", # For heap profiler test.
+ "+third_party/widevine/cdm/buildflags.h",
+ "+third_party/widevine/cdm/widevine_cdm_common.h",
+
+ # FIXME - refactor code and remove these dependencies
+ "+chrome/installer/util",
+]
diff --git a/chromium/chrome/common/OWNERS b/chromium/chrome/common/OWNERS
new file mode 100644
index 00000000000..163695aea31
--- /dev/null
+++ b/chromium/chrome/common/OWNERS
@@ -0,0 +1,64 @@
+per-file chrome_features.cc=*
+per-file chrome_features.h=*
+per-file chrome_switches.cc=*
+per-file chrome_switches.h=*
+per-file pref_names.cc=*
+per-file pref_names.h=*
+per-file url_constants.cc=*
+per-file url_constants.h=*
+
+# This is for the common case of adding or renaming files. If you're doing
+# structural changes, use usual OWNERS rules.
+per-file BUILD.gn=*
+
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
+
+per-file *_messages.cc=set noparent
+per-file *_messages.cc=file://ipc/SECURITY_OWNERS
+
+per-file *_param_traits*.*=set noparent
+per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
+
+per-file *_type_converter*.*=set noparent
+per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS
+
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
+
+# Changes to Mojo interfaces require a security review to avoid
+# introducing new sandbox escapes.
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# Content client.
+per-file chrome_content_client.cc=*
+per-file chrome_content_client_constants.h=*
+per-file chrome_content_client.h=*
+per-file chrome_content_client_unittest.cc=*
+
+# Content settings
+per-file content_settings*=markusheintz@chromium.org
+
+# Pepper files.
+per-file pepper_*=bbudge@chromium.org
+per-file pepper_*=raymes@chromium.org
+
+# Other stuff.
+per-file autocomplete_match_type.*=mpearson@chromium.org
+per-file autocomplete_match_type.*=pkasting@chromium.org
+per-file autocomplete_match_type.*=sky@chromium.org
+per-file crash_keys*=rsesek@chromium.org
+
+# WebUI. See also chrome/browser/ui/webui/OWNERS.
+per-file webui_url_constants.cc=file://ui/webui/PLATFORM_OWNERS
+per-file webui_url_constants.h=file://ui/webui/PLATFORM_OWNERS
+
+# Thread profiling
+per-file thread_profiler*=wittman@chromium.org
+
+# Heap profiler
+per-file heap_profiler*=alph@chromium.org
diff --git a/chromium/chrome/common/all_messages.h b/chromium/chrome/common/all_messages.h
new file mode 100644
index 00000000000..211030e352e
--- /dev/null
+++ b/chromium/chrome/common/all_messages.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included file, hence no include guard.
+// Inclusion of all message files present in chrome. Keep this file
+// up to date when adding a new value to the IPCMessageStart enum in
+// ipc/ipc_message_start.h to ensure the corresponding message file is
+// included here. Message classes used exclusively outside of chrome
+// should not be listed here and instead get an exemption in
+// chrome/tools/ipclist/ipclist.cc.
+
+#include "build/build_config.h"
+#include "components/nacl/common/buildflags.h"
+#include "printing/buildflags/buildflags.h"
+
+#include "chrome/common/common_message_generator.h"
+
+#if BUILDFLAG(ENABLE_PRINTING)
+// TODO(dgn) remove from here when all the code using these messages is removed
+// from /chrome. (crbug.com/311308, crbug.com/450822)
+#undef COMPONENTS_PRINTING_COMMON_PRINT_MESSAGES_H_
+#include "components/printing/common/print_messages.h" // nogncheck
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+#include "components/nacl/common/nacl_messages.h"
+#endif
diff --git a/chromium/chrome/common/apps/OWNERS b/chromium/chrome/common/apps/OWNERS
new file mode 100644
index 00000000000..0f8012a3b96
--- /dev/null
+++ b/chromium/chrome/common/apps/OWNERS
@@ -0,0 +1,2 @@
+file://apps/OWNERS
+# COMPONENT: Platform>Extensions
diff --git a/chromium/chrome/common/apps/platform_apps/OWNERS b/chromium/chrome/common/apps/platform_apps/OWNERS
new file mode 100644
index 00000000000..42444bcd16d
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/OWNERS
@@ -0,0 +1,2 @@
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
diff --git a/chromium/chrome/common/apps/platform_apps/api/arc_apps_private.idl b/chromium/chrome/common/apps/platform_apps/api/arc_apps_private.idl
new file mode 100644
index 00000000000..1a3dd23237c
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/api/arc_apps_private.idl
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.arcAppsPrivate</code> API to manage ARC apps.
+[platforms=("chromeos"), nodoc]
+namespace arcAppsPrivate {
+
+ dictionary AppInfo {
+ // The app package name.
+ DOMString packageName;
+ };
+
+ callback VoidCallback = void ();
+ callback GetLaunchableAppsCallback = void (AppInfo[] appsInfo);
+
+ interface Functions {
+ // Returns info of the installed ARC apps that are launchable, including
+ // ready and non-ready apps.
+ static void getLaunchableApps(GetLaunchableAppsCallback callback);
+
+ // Launches the ARC app with its package name. The app is launched
+ // immediately if it's ready, otherwise it will be launched when it becomes
+ // ready. The callback is called as soon as the launch is scheduled.
+ static void launchApp(DOMString packageName,
+ optional VoidCallback callback);
+ };
+
+ interface Events {
+ // Fires when a new app can be launched via $(ref:launchApp).
+ static void onInstalled(AppInfo app_info);
+ };
+};
diff --git a/chromium/chrome/common/apps/platform_apps/api/browser.idl b/chromium/chrome/common/apps/platform_apps/api/browser.idl
new file mode 100644
index 00000000000..971164bd068
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/api/browser.idl
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.browser</code> API to interact with the Chrome browser
+// associated with the current application and Chrome profile.
+namespace browser {
+ // Options for the $(ref:openTab) function.
+ dictionary OpenTabOptions {
+ // The URL to navigate to when the new tab is initially opened.
+ DOMString url;
+ };
+
+ callback Callback = void();
+
+ interface Functions {
+ // Opens a new tab in a browser window associated with the current
+ // application and Chrome profile. If no browser window for the Chrome
+ // profile is opened, a new one is opened prior to creating the new tab.
+ // |options|: Configures how the tab should be opened.
+ // |callback|: Called when the tab was successfully created, or failed to
+ // be created. If failed, $(ref:runtime.lastError) will be set.
+ static void openTab(OpenTabOptions options,
+ optional Callback callback);
+ };
+};
diff --git a/chromium/chrome/common/apps/platform_apps/api/media_galleries.idl b/chromium/chrome/common/apps/platform_apps/api/media_galleries.idl
new file mode 100644
index 00000000000..ebbb1f35db3
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/api/media_galleries.idl
@@ -0,0 +1,255 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+// Use the <code>chrome.mediaGalleries</code> API to access media files (audio,
+// images, video) from the user's local disks (with the user's consent).
+namespace mediaGalleries {
+
+ [inline_doc] enum GalleryChangeType {
+ // The contents of the gallery have changed.
+ contents_changed,
+ // The watch has been dropped because the device has been detached,
+ // the gallery permission has been removed, or any other reason.
+ watch_dropped
+ };
+
+ [inline_doc] enum GetMediaFileSystemsInteractivity {
+ // Do not act interactively.
+ no,
+ // Ask the user to manage permitted media galleries.
+ yes,
+ // Ask the user to manage permitted galleries only if the return set would
+ // otherwise be empty.
+ if_needed
+ };
+
+ [inline_doc] enum GetMetadataType {
+ // Retrieve the mime type, metadata tags, and attached images.
+ all,
+ // Retrieve only the mime type and the metadata tags.
+ mimeTypeAndTags,
+ // Retrieve only the mime type.
+ mimeTypeOnly
+ };
+
+ [nodefine, inline_doc] enum ScanProgressType {
+ // The scan started.
+ start,
+ // The scan was cancelled.
+ cancel,
+ // The scan finished but none of the result have been added,
+ // addScanResults() has to be called to ask the user for permission.
+ finish,
+ // The scan encountered an error and could not proceed.
+ error
+ };
+
+ [inline_doc] dictionary GalleryChangeDetails {
+ // Type of change event.
+ GalleryChangeType type;
+
+ // Identifies the modified gallery.
+ DOMString galleryId;
+ };
+
+ [inline_doc] dictionary MediaFileSystemsDetails {
+ // Whether to prompt the user for permission to additional media galleries
+ // before returning the permitted set. Default is silent. If the value
+ // 'yes' is passed, or if the application has not been granted access to
+ // any media galleries and the value 'if_needed' is passed, then the
+ // media gallery configuration dialog will be displayed.
+ GetMediaFileSystemsInteractivity? interactive;
+ };
+
+ [inline_doc] dictionary MediaMetadataOptions {
+ // Specifies which subset of the metadata to retrieve. Defaults to 'all'
+ // if the option is omitted.
+ GetMetadataType? metadataType;
+ };
+
+ callback MediaFileSystemsCallback =
+ void ([instanceOf=DOMFileSystem] object[] mediaFileSystems);
+
+ callback AddUserFolderCallback =
+ void ([instanceOf=DOMFileSystem] object[] mediaFileSystems,
+ DOMString selectedFileSystemName);
+
+ [nodefine] callback DropPermissionForMediaFileSystemCallback = void ();
+
+ [inline_doc] dictionary MediaFileSystemMetadata {
+ // The name of the file system.
+ DOMString name;
+
+ // A unique and persistent id for the media gallery.
+ DOMString galleryId;
+
+ // If the media gallery is on a removable device, a unique id for the
+ // device while the device is online.
+ DOMString? deviceId;
+
+ // True if the media gallery is on a removable device.
+ boolean isRemovable;
+
+ // True if the device the media gallery is on was detected as a media
+ // device. i.e. a PTP or MTP device, or a DCIM directory is present.
+ boolean isMediaDevice;
+
+ // True if the device is currently available.
+ boolean isAvailable;
+ };
+
+ [nodefine, inline_doc] dictionary ScanProgressDetails {
+ // The type of progress event, i.e. start, finish, etc.
+ ScanProgressType type;
+
+ // The number of Galleries found.
+ long? galleryCount;
+
+ // Appoximate number of media files found; some file types can be either
+ // audio or video and are included in both counts.
+ long? audioCount;
+ long? imageCount;
+ long? videoCount;
+ };
+
+ callback MediaFileSystemsMetadataCallback =
+ void (MediaFileSystemMetadata[] metadata);
+
+ dictionary StreamInfo {
+ // Describes format of container or codec of stream, i.e. "mp3", "h264".
+ DOMString type;
+
+ // An unfiltered string->string dictionary of tags for the stream.
+ object tags;
+ };
+
+ dictionary MediaMetadata {
+ // The browser sniffed mime type.
+ DOMString mimeType;
+
+ // Defined for video. In pixels.
+ long? height;
+ long? width;
+
+ // Defined for audio and video. In seconds.
+ double? duration;
+
+ // Defined for video. In degrees.
+ long? rotation;
+
+ // Defined for audio and video.
+ DOMString? album;
+ DOMString? artist;
+ DOMString? comment;
+ DOMString? copyright;
+ long? disc;
+ DOMString? genre;
+ DOMString? language;
+ DOMString? title;
+ long? track;
+
+ // All the metadata in the media file. For formats with multiple streams,
+ // stream order will be preserved. Container metadata is the first element.
+ StreamInfo[] rawTags;
+
+ // The images embedded in the media file's metadata. This is most often
+ // used for album art or video thumbnails.
+ [instanceOf=Blob] object[] attachedImages;
+ };
+
+ callback MediaMetadataCallback = void (MediaMetadata metadata);
+
+ // A dictionary that describes the add gallery watch request results.
+ dictionary AddGalleryWatchResult {
+ DOMString galleryId;
+ boolean success;
+ };
+
+ callback AddGalleryWatchCallback = void (AddGalleryWatchResult result);
+ [nodefine] callback GetAllGalleryWatchCallback =
+ void (DOMString[] galleryIds);
+
+ interface Functions {
+ // Get the media galleries configured in this user agent. If none are
+ // configured or available, the callback will receive an empty array.
+ static void getMediaFileSystems(optional MediaFileSystemsDetails details,
+ MediaFileSystemsCallback callback);
+
+ // Present a directory picker to the user and add the selected directory
+ // as a gallery. If the user cancels the picker, selectedFileSystemName
+ // will be empty.
+ // A user gesture is required for the dialog to display. Without a user
+ // gesture, the callback will run as though the user canceled.
+ static void addUserSelectedFolder(AddUserFolderCallback callback);
+
+ // Give up access to a given media gallery.
+ [nodefine, deprecated="The user can manually drop access to galleries
+ via the permissions dialog."]
+ static void dropPermissionForMediaFileSystem(
+ DOMString galleryId,
+ optional DropPermissionForMediaFileSystemCallback callback);
+
+ // Start a scan of the user's hard disks for directories containing media.
+ // The scan may take a long time so progress and completion is communicated
+ // by events. No permission is granted as a result of the scan, see
+ // addScanResults.
+ [nodefine, deprecated="The mediaGalleries API no longer supports scanning."]
+ static void startMediaScan();
+
+ // Cancel any pending media scan. Well behaved apps should provide a way
+ // for the user to cancel scans they start.
+ [nodefine, deprecated="The mediaGalleries API no longer supports scanning."]
+ static void cancelMediaScan();
+
+ // Show the user the scan results and let them add any or all of them as
+ // galleries. This should be used after the 'finish' onScanProgress()
+ // event has happened. All galleries the app has access to are returned, not
+ // just the newly added galleries.
+ [nodefine, deprecated="The mediaGalleries API no longer supports scanning."]
+ static void addScanResults(MediaFileSystemsCallback callback);
+
+ // Get metadata about a specific media file system.
+ [nocompile] static MediaFileSystemMetadata getMediaFileSystemMetadata(
+ [instanceOf=DOMFileSystem] object mediaFileSystem);
+
+ // Get metadata for all available media galleries.
+ [nodefine, deprecated="Use getMediaFileSystemMetadata instead."]
+ static void getAllMediaFileSystemMetadata(
+ MediaFileSystemsMetadataCallback callback);
+
+ // Gets the media-specific metadata for a media file. This should work
+ // for files in media galleries as well as other DOM filesystems.
+ static void getMetadata([instanceOf=Blob] object mediaFile,
+ optional MediaMetadataOptions options,
+ MediaMetadataCallback callback);
+
+ // Adds a gallery watch for the gallery with the specified gallery ID.
+ // The given callback is then fired with a success or failure result.
+ static void addGalleryWatch(DOMString galleryId,
+ AddGalleryWatchCallback callback);
+
+ // Removes a gallery watch for the gallery with the specified gallery ID.
+ static void removeGalleryWatch(DOMString galleryId);
+
+ // Notifies which galleries are being watched via the given callback.
+ [nodefine, deprecated="Applications should store their own gallery watches
+ as they are added."]
+ static void getAllGalleryWatch(GetAllGalleryWatchCallback callback);
+
+ // Removes all gallery watches.
+ [nodefine, deprecated="Use removeGalleryWatch instead."]
+ static void removeAllGalleryWatch();
+ };
+
+ interface Events {
+ // Fired when a media gallery is changed or a gallery watch is dropped.
+ static void onGalleryChanged(GalleryChangeDetails details);
+
+ // The pending media scan has changed state. See details for more
+ // information.
+ [nodefine, deprecated="The mediaGalleries API no longer supports scanning."]
+ static void onScanProgress(ScanProgressDetails details);
+ };
+};
diff --git a/chromium/chrome/common/apps/platform_apps/api/music_manager_private.idl b/chromium/chrome/common/apps/platform_apps/api/music_manager_private.idl
new file mode 100644
index 00000000000..b6d13fba921
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/api/music_manager_private.idl
@@ -0,0 +1,16 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// musicManagerPrivate.
+namespace musicManagerPrivate {
+
+ callback GetDeviceIdCallback = void (DOMString device_id);
+
+ interface Functions {
+ // Returns an identifier stable across users and reboots.
+ //
+ // |callback| : Called with the stable identifier value.
+ static void getDeviceId(GetDeviceIdCallback callback);
+ };
+};
diff --git a/chromium/chrome/common/apps/platform_apps/api/sync_file_system.idl b/chromium/chrome/common/apps/platform_apps/api/sync_file_system.idl
new file mode 100644
index 00000000000..711625661e9
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/api/sync_file_system.idl
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.syncFileSystem</code> API to save and synchronize data
+// on Google Drive. This API is NOT for accessing arbitrary user docs stored in
+// Google Drive. It provides app-specific syncable storage for offline and
+// caching usage so that the same data can be available across different
+// clients. Read <a href="app_storage.html">Manage Data</a> for more on using
+// this API.
+namespace syncFileSystem {
+ enum SyncAction {
+ added, updated, deleted
+ };
+
+ enum ServiceStatus {
+ // The sync service is being initialized (e.g. restoring data from the
+ // database, checking connectivity and authenticating to the service etc).
+ initializing,
+
+ // The sync service is up and running.
+ running,
+
+ // The sync service is not synchronizing files because the remote service
+ // needs to be authenticated by the user to proceed.
+ authentication_required,
+
+ // The sync service is not synchronizing files because the remote service
+ // is (temporarily) unavailable due to some recoverable errors, e.g.
+ // network is offline, the remote service is down or not
+ // reachable etc. More details should be given by |description| parameter
+ // in OnServiceInfoUpdated (which could contain service-specific details).
+ temporary_unavailable,
+
+ // The sync service is disabled and the content will never sync.
+ // (E.g. this could happen when the user has no account on
+ // the remote service or the sync service has had an unrecoverable
+ // error.)
+ disabled
+ };
+
+ enum FileStatus {
+ // Not conflicting and has no pending local changes.
+ synced,
+
+ // Has one or more pending local changes that haven't been synchronized.
+ pending,
+
+ // File conflicts with remote version and must be resolved manually.
+ conflicting
+ };
+
+ enum SyncDirection {
+ local_to_remote, remote_to_local
+ };
+
+ enum ConflictResolutionPolicy {
+ last_write_win, manual
+ };
+
+ dictionary FileInfo {
+ // <code>fileEntry</code> for the target file whose status has changed.
+ // Contains name and path information of synchronized file.
+ // On file deletion,
+ // <code>fileEntry</code> information will still be available
+ // but file will no longer exist.
+ [instanceOf=Entry] object fileEntry;
+
+ // Resulting file status after $(ref:onFileStatusChanged) event.
+ // The status value can be <code>'synced'</code>,
+ // <code>'pending'</code> or <code>'conflicting'</code>.
+ FileStatus status;
+
+ // Sync action taken to fire $(ref:onFileStatusChanged) event.
+ // The action value can be
+ // <code>'added'</code>, <code>'updated'</code> or <code>'deleted'</code>.
+ // Only applies if status is <code>'synced'</code>.
+ SyncAction? action;
+
+ // Sync direction for the $(ref:onFileStatusChanged) event.
+ // Sync direction value can be
+ // <code>'local_to_remote'</code> or <code>'remote_to_local'</code>.
+ // Only applies if status is <code>'synced'</code>.
+ SyncDirection? direction;
+ };
+
+ dictionary FileStatusInfo {
+ // One of the Entry's originally given to getFileStatuses.
+ [instanceOf=Entry] object fileEntry;
+
+ // The status value can be <code>'synced'</code>,
+ // <code>'pending'</code> or <code>'conflicting'</code>.
+ FileStatus status;
+
+ // Optional error that is only returned if there was a problem retrieving
+ // the FileStatus for the given file.
+ DOMString? error;
+ };
+
+ dictionary StorageInfo {
+ long usageBytes;
+ long quotaBytes;
+ };
+
+ dictionary ServiceInfo {
+ ServiceStatus state;
+ DOMString description;
+ };
+
+ // A callback type for requestFileSystem.
+ callback GetFileSystemCallback =
+ void ([instanceOf=DOMFileSystem] object fileSystem);
+
+ // A callback type for getUsageAndQuota.
+ callback QuotaAndUsageCallback = void (StorageInfo info);
+
+ // Returns true if operation was successful.
+ callback DeleteFileSystemCallback = void (boolean result);
+
+ // A callback type for getFileStatus.
+ callback GetFileStatusCallback = void (FileStatus status);
+
+ // A callback type for getFileStatuses.
+ callback GetFileStatusesCallback = void (FileStatusInfo[] status);
+
+ // A callback type for getServiceStatus.
+ callback GetServiceStatusCallback = void (ServiceStatus status);
+
+ // A callback type for getConflictResolutionPolicy.
+ callback GetConflictResolutionPolicyCallback =
+ void (ConflictResolutionPolicy policy);
+
+ // A generic result callback to indicate success or failure.
+ callback ResultCallback = void ();
+
+ interface Functions {
+ // Returns a syncable filesystem backed by Google Drive.
+ // The returned <code>DOMFileSystem</code> instance can be operated on
+ // in the same way as the Temporary and Persistant file systems (see
+ // <a href="http://dev.w3.org/2009/dap/file-system/file-dir-sys.html">
+ // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html</a>).
+ //
+ // Calling this multiple times from
+ // the same app will return the same handle to the same file system.
+ //
+ // Note this call can fail. For example, if the user is not signed in to
+ // Chrome or if there is no network operation. To handle these errors it is
+ // important chrome.runtime.lastError is checked in the callback.
+ static void requestFileSystem(GetFileSystemCallback callback);
+
+ // Sets the default conflict resolution policy
+ // for the <code>'syncable'</code> file storage for the app.
+ // By default it is set to <code>'last_write_win'</code>.
+ // When conflict resolution policy is set to <code>'last_write_win'</code>
+ // conflicts for existing files are automatically resolved next time
+ // the file is updated.
+ // |callback| can be optionally given to know if the request has
+ // succeeded or not.
+ static void setConflictResolutionPolicy(
+ ConflictResolutionPolicy policy,
+ optional ResultCallback callback);
+
+ // Gets the current conflict resolution policy.
+ static void getConflictResolutionPolicy(
+ GetConflictResolutionPolicyCallback callback);
+
+ // Returns the current usage and quota in bytes
+ // for the <code>'syncable'</code> file storage for the app.
+ static void getUsageAndQuota([instanceOf=DOMFileSystem] object fileSystem,
+ QuotaAndUsageCallback callback);
+
+ // Returns the $(ref:FileStatus) for the given <code>fileEntry</code>.
+ // The status value can be <code>'synced'</code>,
+ // <code>'pending'</code> or <code>'conflicting'</code>.
+ // Note that <code>'conflicting'</code> state only happens when
+ // the service's conflict resolution policy is set to <code>'manual'</code>.
+ static void getFileStatus([instanceOf=Entry] object fileEntry,
+ GetFileStatusCallback callback);
+
+ // Returns each $(ref:FileStatus) for the given <code>fileEntry</code> array.
+ // Typically called with the result from dirReader.readEntries().
+ static void getFileStatuses(object[] fileEntries,
+ GetFileStatusesCallback callback);
+
+ // Returns the current sync backend status.
+ static void getServiceStatus(GetServiceStatusCallback callback);
+ };
+
+ interface Events {
+ // Fired when an error or other status change has happened in the
+ // sync backend (for example, when the sync is temporarily disabled due to
+ // network or authentication error).
+ static void onServiceStatusChanged(ServiceInfo detail);
+
+ // Fired when a file has been updated by the background sync service.
+ static void onFileStatusChanged(FileInfo detail);
+ };
+
+};
diff --git a/chromium/chrome/common/apps/platform_apps/api/webstore_widget_private.idl b/chromium/chrome/common/apps/platform_apps/api/webstore_widget_private.idl
new file mode 100644
index 00000000000..ea0d07a6778
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/api/webstore_widget_private.idl
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// webstoreWidgetPrivate API.
+// This is a private API used to install apps via Chrome Web Store widget - for
+// example in Files app to install file system provider services.
+[platforms=("chromeos")]
+namespace webstoreWidgetPrivate {
+
+// Callback that does not take arguments.
+callback SimpleCallback = void();
+
+interface Functions {
+ // Requests to install a webstore item.
+ // |item_id| The id of the item to install.
+ // |silentInstallation| False to show installation prompt. True not to show.
+ // Can be set to true only for a subset of installation requests.
+ static void installWebstoreItem(DOMString itemId,
+ boolean silentInstallation,
+ SimpleCallback callback);
+};
+
+};
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc
new file mode 100644
index 00000000000..99e1f557bb7
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc
@@ -0,0 +1,45 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/apps/platform_apps/chrome_apps_api_permissions.h"
+
+#include <memory>
+
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
+
+namespace chrome_apps_api_permissions {
+namespace {
+
+template <typename T>
+std::unique_ptr<extensions::APIPermission> CreateAPIPermission(
+ const extensions::APIPermissionInfo* permission) {
+ return std::make_unique<T>(permission);
+}
+
+// WARNING: If you are modifying a permission message in this list, be sure to
+// add the corresponding permission message rule to
+// ChromePermissionMessageProvider::GetPermissionMessages as well.
+constexpr extensions::APIPermissionInfo::InitInfo permissions_to_register[] = {
+ {extensions::APIPermission::kArcAppsPrivate, "arcAppsPrivate"},
+ {extensions::APIPermission::kBrowser, "browser"},
+ {extensions::APIPermission::kFirstRunPrivate, "firstRunPrivate",
+ extensions::APIPermissionInfo::kFlagCannotBeOptional},
+ {extensions::APIPermission::kMusicManagerPrivate, "musicManagerPrivate",
+ extensions::APIPermissionInfo::kFlagCannotBeOptional},
+ {extensions::APIPermission::kMediaGalleries, "mediaGalleries",
+ extensions::APIPermissionInfo::kFlagNone,
+ &CreateAPIPermission<chrome_apps::MediaGalleriesPermission>},
+ {extensions::APIPermission::kPointerLock, "pointerLock"},
+ {extensions::APIPermission::kSyncFileSystem, "syncFileSystem"},
+ {extensions::APIPermission::kWebstoreWidgetPrivate, "webstoreWidgetPrivate",
+ extensions::APIPermissionInfo::kFlagCannotBeOptional},
+};
+
+} // namespace
+
+base::span<const extensions::APIPermissionInfo::InitInfo> GetPermissionInfos() {
+ return base::make_span(permissions_to_register);
+}
+
+} // namespace chrome_apps_api_permissions
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.h b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.h
new file mode 100644
index 00000000000..eec3cad6d55
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_permissions.h
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_API_PERMISSIONS_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_API_PERMISSIONS_H_
+
+#include "base/containers/span.h"
+#include "extensions/common/permissions/api_permission.h"
+
+namespace chrome_apps_api_permissions {
+
+// Returns the information necessary to construct Chrome app-specific
+// APIPermissions.
+base::span<const extensions::APIPermissionInfo::InitInfo> GetPermissionInfos();
+
+} // namespace chrome_apps_api_permissions
+
+#endif // CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_API_PERMISSIONS_H_
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.cc b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.cc
new file mode 100644
index 00000000000..1f7de94c949
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.cc
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/apps/platform_apps/chrome_apps_api_provider.h"
+
+#include "chrome/common/apps/platform_apps/api/api_features.h"
+#include "chrome/common/apps/platform_apps/api/generated_schemas.h"
+#include "chrome/common/apps/platform_apps/api/permission_features.h"
+#include "chrome/common/apps/platform_apps/chrome_apps_api_permissions.h"
+#include "chrome/grit/common_resources.h"
+#include "extensions/common/alias.h"
+#include "extensions/common/features/json_feature_provider_source.h"
+#include "extensions/common/permissions/permissions_info.h"
+
+namespace chrome_apps {
+
+ChromeAppsAPIProvider::ChromeAppsAPIProvider() {}
+ChromeAppsAPIProvider::~ChromeAppsAPIProvider() = default;
+
+void ChromeAppsAPIProvider::AddAPIFeatures(
+ extensions::FeatureProvider* provider) {
+ AddChromeAppsAPIFeatures(provider);
+}
+
+void ChromeAppsAPIProvider::AddManifestFeatures(
+ extensions::FeatureProvider* provider) {
+ // No Chrome-apps-specific manifest features (yet).
+}
+
+void ChromeAppsAPIProvider::AddPermissionFeatures(
+ extensions::FeatureProvider* provider) {
+ AddChromeAppsPermissionFeatures(provider);
+}
+
+void ChromeAppsAPIProvider::AddBehaviorFeatures(
+ extensions::FeatureProvider* provider) {
+ // No Chrome-apps-specific manifest features.
+}
+
+void ChromeAppsAPIProvider::AddAPIJSONSources(
+ extensions::JSONFeatureProviderSource* json_source) {
+ json_source->LoadJSON(IDR_CHROME_APP_API_FEATURES);
+}
+
+bool ChromeAppsAPIProvider::IsAPISchemaGenerated(const std::string& name) {
+ return api::ChromeAppsGeneratedSchemas::IsGenerated(name);
+}
+
+base::StringPiece ChromeAppsAPIProvider::GetAPISchema(const std::string& name) {
+ return api::ChromeAppsGeneratedSchemas::Get(name);
+}
+
+void ChromeAppsAPIProvider::RegisterPermissions(
+ extensions::PermissionsInfo* permissions_info) {
+ permissions_info->RegisterPermissions(
+ chrome_apps_api_permissions::GetPermissionInfos(),
+ base::span<const extensions::Alias>());
+}
+
+void ChromeAppsAPIProvider::RegisterManifestHandlers() {
+ // No apps-specific manifest handlers (yet).
+}
+
+} // namespace chrome_apps
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.h b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.h
new file mode 100644
index 00000000000..b5312b85422
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_api_provider.h
@@ -0,0 +1,36 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_API_PROVIDER_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_API_PROVIDER_H_
+
+#include "extensions/common/extensions_api_provider.h"
+
+namespace chrome_apps {
+
+class ChromeAppsAPIProvider : public extensions::ExtensionsAPIProvider {
+ public:
+ ChromeAppsAPIProvider();
+ ~ChromeAppsAPIProvider() override;
+
+ // ExtensionsAPIProvider:
+ void AddAPIFeatures(extensions::FeatureProvider* provider) override;
+ void AddManifestFeatures(extensions::FeatureProvider* provider) override;
+ void AddPermissionFeatures(extensions::FeatureProvider* provider) override;
+ void AddBehaviorFeatures(extensions::FeatureProvider* provider) override;
+ void AddAPIJSONSources(
+ extensions::JSONFeatureProviderSource* json_source) override;
+ bool IsAPISchemaGenerated(const std::string& name) override;
+ base::StringPiece GetAPISchema(const std::string& name) override;
+ void RegisterPermissions(
+ extensions::PermissionsInfo* permissions_info) override;
+ void RegisterManifestHandlers() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeAppsAPIProvider);
+};
+
+} // namespace chrome_apps
+
+#endif // CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_API_PROVIDER_H_
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc b/chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc
new file mode 100644
index 00000000000..e22ff4b8edd
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+} // namespace IPC
+
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.h b/chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.h
new file mode 100644
index 00000000000..1903b73d9b5
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_message_generator.h
@@ -0,0 +1,11 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included file, hence no include guard.
+
+#undef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+#include "chrome/common/apps/platform_apps/chrome_apps_messages.h"
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+#error "Failed to include header chrome/common/apps/platform_apps/chrome_apps_messages.h"
+#endif
diff --git a/chromium/chrome/common/apps/platform_apps/chrome_apps_messages.h b/chromium/chrome/common/apps/platform_apps/chrome_apps_messages.h
new file mode 100644
index 00000000000..f8138b4fc56
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/chrome_apps_messages.h
@@ -0,0 +1,15 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
+#include "ipc/ipc_message_macros.h"
+
+IPC_STRUCT_TRAITS_BEGIN(chrome_apps::MediaGalleriesPermissionData)
+ IPC_STRUCT_TRAITS_MEMBER(permission())
+IPC_STRUCT_TRAITS_END()
+
+#endif // CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
diff --git a/chromium/chrome/common/apps/platform_apps/media_galleries_permission.cc b/chromium/chrome/common/apps/platform_apps/media_galleries_permission.cc
new file mode 100644
index 00000000000..372145687f4
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/media_galleries_permission.cc
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace chrome_apps {
+
+namespace {
+
+// copyTo permission requires delete permission as a prerequisite.
+// delete permission requires read permission as a prerequisite.
+bool IsValidPermissionSet(bool has_read,
+ bool has_copy_to,
+ bool has_delete,
+ std::string* error) {
+ if (has_copy_to) {
+ if (has_read && has_delete)
+ return true;
+ if (error)
+ *error = "copyTo permission requires read and delete permissions";
+ return false;
+ }
+ if (has_delete) {
+ if (has_read)
+ return true;
+ if (error)
+ *error = "delete permission requires read permission";
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+const char MediaGalleriesPermission::kAllAutoDetectedPermission[] =
+ "allAutoDetected";
+const char MediaGalleriesPermission::kReadPermission[] = "read";
+const char MediaGalleriesPermission::kCopyToPermission[] = "copyTo";
+const char MediaGalleriesPermission::kDeletePermission[] = "delete";
+
+MediaGalleriesPermission::MediaGalleriesPermission(
+ const extensions::APIPermissionInfo* info)
+ : SetDisjunctionPermission<MediaGalleriesPermissionData,
+ MediaGalleriesPermission>(info) {}
+
+MediaGalleriesPermission::~MediaGalleriesPermission() {}
+
+bool MediaGalleriesPermission::FromValue(
+ const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) {
+ size_t unhandled_permissions_count = 0;
+ if (unhandled_permissions)
+ unhandled_permissions_count = unhandled_permissions->size();
+ bool parsed_ok = SetDisjunctionPermission<
+ MediaGalleriesPermissionData,
+ MediaGalleriesPermission>::FromValue(value, error, unhandled_permissions);
+ if (unhandled_permissions) {
+ for (size_t i = unhandled_permissions_count;
+ i < unhandled_permissions->size(); i++) {
+ (*unhandled_permissions)[i] =
+ "{\"mediaGalleries\": [" + (*unhandled_permissions)[i] + "]}";
+ }
+ }
+ if (!parsed_ok)
+ return false;
+
+ bool has_read = false;
+ bool has_copy_to = false;
+ bool has_delete = false;
+ for (auto it = data_set_.cbegin(); it != data_set_.cend(); ++it) {
+ if (it->permission() == kAllAutoDetectedPermission) {
+ continue;
+ }
+ if (it->permission() == kReadPermission) {
+ has_read = true;
+ continue;
+ }
+ if (it->permission() == kCopyToPermission) {
+ has_copy_to = true;
+ continue;
+ }
+ if (it->permission() == kDeletePermission) {
+ has_delete = true;
+ continue;
+ }
+
+ // No other permissions, so reaching this means
+ // MediaGalleriesPermissionData is probably out of sync in some way.
+ // Fail so developers notice this.
+ NOTREACHED();
+ return false;
+ }
+
+ return IsValidPermissionSet(has_read, has_copy_to, has_delete, error);
+}
+
+extensions::PermissionIDSet MediaGalleriesPermission::GetPermissions() const {
+ extensions::PermissionIDSet result;
+
+ bool has_all_auto_detected = false;
+ bool has_read = false;
+ bool has_copy_to = false;
+ bool has_delete = false;
+
+ for (const MediaGalleriesPermissionData& data : data_set_) {
+ if (data.permission() == kAllAutoDetectedPermission)
+ has_all_auto_detected = true;
+ else if (data.permission() == kReadPermission)
+ has_read = true;
+ else if (data.permission() == kCopyToPermission)
+ has_copy_to = true;
+ else if (data.permission() == kDeletePermission)
+ has_delete = true;
+ }
+
+ if (!IsValidPermissionSet(has_read, has_copy_to, has_delete, nullptr)) {
+ NOTREACHED();
+ return result;
+ }
+
+ // If |has_all_auto_detected| is false, then Chrome will prompt the user at
+ // runtime when the extension calls the getMediaGalleries API.
+ if (!has_all_auto_detected)
+ return result;
+ // No access permission case.
+ if (!has_read)
+ return result;
+
+ // Separate PermissionMessage IDs for read, copyTo, and delete. Otherwise an
+ // extension can silently gain new access capabilities.
+ result.insert(extensions::APIPermission::kMediaGalleriesAllGalleriesRead);
+
+ // For copyTo and delete, the proper combined permission message will be
+ // derived in ChromePermissionMessageProvider::GetWarningMessages(), such
+ // that the user get 1 entry for all media galleries access permissions,
+ // rather than several separate entries.
+ if (has_copy_to)
+ result.insert(extensions::APIPermission::kMediaGalleriesAllGalleriesCopyTo);
+ if (has_delete)
+ result.insert(extensions::APIPermission::kMediaGalleriesAllGalleriesDelete);
+
+ return result;
+}
+
+} // namespace chrome_apps
diff --git a/chromium/chrome/common/apps/platform_apps/media_galleries_permission.h b/chromium/chrome/common/apps/platform_apps/media_galleries_permission.h
new file mode 100644
index 00000000000..c5792fdab85
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/media_galleries_permission.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_H_
+
+#include "chrome/common/apps/platform_apps/chrome_apps_messages.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/set_disjunction_permission.h"
+
+namespace chrome_apps {
+
+// Media Galleries permissions are as follows:
+// <media-galleries-permission-pattern>
+// := <access> | <access> 'allAutoDetected' | 'allAutoDetected' |
+// <access> 'scan' | 'scan'
+// <access> := 'read' | 'read' <access> | 'read' <secondary-access>
+// <secondary-access>
+// := 'delete' | 'delete' <secondary-access> |
+// 'delete' <tertiary-access>
+// <tertiary-access>
+// := 'copyTo' | 'copyTo' <tertiary-access>
+// An example of a line for mediaGalleries permissions in a manifest file:
+// {"mediaGalleries": "read delete"},
+// We also allow a permission without any sub-permissions:
+// "mediaGalleries",
+// TODO(devlin): Move this class to chrome/common/apps/platform_apps.
+class MediaGalleriesPermission
+ : public extensions::SetDisjunctionPermission<MediaGalleriesPermissionData,
+ MediaGalleriesPermission> {
+ public:
+ struct CheckParam : public extensions::APIPermission::CheckParam {
+ explicit CheckParam(const std::string& permission)
+ : permission(permission) {}
+ const std::string permission;
+ };
+
+ explicit MediaGalleriesPermission(const extensions::APIPermissionInfo* info);
+ ~MediaGalleriesPermission() override;
+
+ // SetDisjunctionPermission overrides.
+ // MediaGalleriesPermission does additional checks to make sure the
+ // permissions do not contain unknown values.
+ bool FromValue(const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) override;
+
+ // extensions::APIPermission overrides.
+ extensions::PermissionIDSet GetPermissions() const override;
+
+ // Permission strings.
+ static const char kAllAutoDetectedPermission[];
+ static const char kScanPermission[];
+ static const char kReadPermission[];
+ static const char kCopyToPermission[];
+ static const char kDeletePermission[];
+};
+
+} // namespace chrome_apps
+
+#endif // CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_H_
diff --git a/chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.cc b/chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.cc
new file mode 100644
index 00000000000..148abbac461
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
+
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
+
+namespace chrome_apps {
+
+MediaGalleriesPermissionData::MediaGalleriesPermissionData() {}
+
+bool MediaGalleriesPermissionData::Check(
+ const extensions::APIPermission::CheckParam* param) const {
+ if (!param)
+ return false;
+
+ const MediaGalleriesPermission::CheckParam& specific_param =
+ *static_cast<const MediaGalleriesPermission::CheckParam*>(param);
+ return permission_ == specific_param.permission;
+}
+
+std::unique_ptr<base::Value> MediaGalleriesPermissionData::ToValue() const {
+ return std::make_unique<base::Value>(permission_);
+}
+
+bool MediaGalleriesPermissionData::FromValue(const base::Value* value) {
+ if (!value)
+ return false;
+
+ std::string raw_permission;
+ if (!value->GetAsString(&raw_permission))
+ return false;
+
+ std::string permission;
+ base::TrimWhitespaceASCII(raw_permission, base::TRIM_ALL, &permission);
+
+ if (permission == MediaGalleriesPermission::kAllAutoDetectedPermission ||
+ permission == MediaGalleriesPermission::kReadPermission ||
+ permission == MediaGalleriesPermission::kCopyToPermission ||
+ permission == MediaGalleriesPermission::kDeletePermission) {
+ permission_ = permission;
+ return true;
+ }
+ return false;
+}
+
+bool MediaGalleriesPermissionData::operator<(
+ const MediaGalleriesPermissionData& rhs) const {
+ return permission_ < rhs.permission_;
+}
+
+bool MediaGalleriesPermissionData::operator==(
+ const MediaGalleriesPermissionData& rhs) const {
+ return permission_ == rhs.permission_;
+}
+
+} // namespace chrome_apps
diff --git a/chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.h b/chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.h
new file mode 100644
index 00000000000..495cb48d4be
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/media_galleries_permission_data.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+
+#include <memory>
+#include <string>
+
+#include "extensions/common/permissions/api_permission.h"
+
+namespace base {
+class Value;
+}
+
+namespace chrome_apps {
+
+// A MediaGalleriesPermissionData instance represents a single part of the
+// MediaGalleriesPermission. e.g. "read" or "allAutoDetected".
+class MediaGalleriesPermissionData {
+ public:
+ MediaGalleriesPermissionData();
+
+ // Check if |param| (which must be a MediaGalleriesPermission::CheckParam)
+ // matches the encapsulated attribute.
+ bool Check(const extensions::APIPermission::CheckParam* param) const;
+
+ // Convert |this| into a base::Value.
+ std::unique_ptr<base::Value> ToValue() const;
+
+ // Populate |this| from a base::Value.
+ bool FromValue(const base::Value* value);
+
+ bool operator<(const MediaGalleriesPermissionData& rhs) const;
+ bool operator==(const MediaGalleriesPermissionData& rhs) const;
+
+ std::string permission() const { return permission_; }
+
+ // This accessor is provided for IPC_STRUCT_TRAITS_MEMBER. Please think
+ // twice before using it for anything else.
+ std::string& permission() { return permission_; }
+
+ private:
+ std::string permission_;
+};
+
+} // namespace chrome_apps
+
+#endif // CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_DATA_H_
diff --git a/chromium/chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc b/chromium/chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc
new file mode 100644
index 00000000000..d8fbc24b8be
--- /dev/null
+++ b/chromium/chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc
@@ -0,0 +1,304 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// These tests make sure MediaGalleriesPermission values are parsed correctly.
+
+#include <memory>
+
+#include "base/values.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::SocketPermissionRequest;
+using extensions::SocketPermissionData;
+
+namespace chrome_apps {
+
+namespace {
+
+void CheckFromValue(extensions::APIPermission* permission,
+ base::ListValue* value,
+ bool success_expected) {
+ std::string error;
+ std::vector<std::string> unhandled;
+ EXPECT_EQ(success_expected, permission->FromValue(value, &error, &unhandled));
+ EXPECT_EQ(success_expected, error.empty());
+ EXPECT_TRUE(unhandled.empty());
+}
+
+TEST(MediaGalleriesPermissionTest, GoodValues) {
+ const extensions::APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(
+ extensions::APIPermission::kMediaGalleries);
+
+ std::unique_ptr<extensions::APIPermission> permission(
+ permission_info->CreateAPIPermission());
+
+ // access_type + all_detected
+ std::unique_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ // all_detected
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ // access_type
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ // Repeats do not make a difference.
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ CheckFromValue(permission.get(), value.get(), true);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ CheckFromValue(permission.get(), value.get(), true);
+}
+
+TEST(MediaGalleriesPermissionTest, BadValues) {
+ const extensions::APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(
+ extensions::APIPermission::kMediaGalleries);
+
+ std::unique_ptr<extensions::APIPermission> permission(
+ permission_info->CreateAPIPermission());
+
+ // copyTo and delete without read
+ std::unique_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ CheckFromValue(permission.get(), value.get(), false);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ CheckFromValue(permission.get(), value.get(), false);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ CheckFromValue(permission.get(), value.get(), false);
+
+ // copyTo without delete
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ CheckFromValue(permission.get(), value.get(), false);
+
+ // Repeats do not make a difference.
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ CheckFromValue(permission.get(), value.get(), false);
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ CheckFromValue(permission.get(), value.get(), false);
+}
+
+TEST(MediaGalleriesPermissionTest, UnknownValues) {
+ std::string error;
+ std::vector<std::string> unhandled;
+ const extensions::APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(
+ extensions::APIPermission::kMediaGalleries);
+
+ std::unique_ptr<extensions::APIPermission> permission(
+ permission_info->CreateAPIPermission());
+
+ // A good one and an unknown one.
+ std::unique_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString("Unknown");
+ EXPECT_TRUE(permission->FromValue(value.get(), &error, &unhandled));
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(1U, unhandled.size());
+ error.clear();
+ unhandled.clear();
+
+ // Multiple unknown permissions.
+ value.reset(new base::ListValue());
+ value->AppendString("Unknown1");
+ value->AppendString("Unknown2");
+ EXPECT_TRUE(permission->FromValue(value.get(), &error, &unhandled));
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(2U, unhandled.size());
+ error.clear();
+ unhandled.clear();
+
+ // Unnknown with a NULL argument.
+ value.reset(new base::ListValue());
+ value->AppendString("Unknown1");
+ EXPECT_FALSE(permission->FromValue(value.get(), &error, NULL));
+ EXPECT_FALSE(error.empty());
+ error.clear();
+}
+
+TEST(MediaGalleriesPermissionTest, Equal) {
+ const extensions::APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(
+ extensions::APIPermission::kMediaGalleries);
+
+ std::unique_ptr<extensions::APIPermission> permission1(
+ permission_info->CreateAPIPermission());
+ std::unique_ptr<extensions::APIPermission> permission2(
+ permission_info->CreateAPIPermission());
+
+ std::unique_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ ASSERT_TRUE(permission2->FromValue(value.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ ASSERT_TRUE(permission2->FromValue(value.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ ASSERT_TRUE(permission2->FromValue(value.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ ASSERT_TRUE(permission2->FromValue(value.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+}
+
+TEST(MediaGalleriesPermissionTest, NotEqual) {
+ const extensions::APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(
+ extensions::APIPermission::kMediaGalleries);
+
+ std::unique_ptr<extensions::APIPermission> permission1(
+ permission_info->CreateAPIPermission());
+ std::unique_ptr<extensions::APIPermission> permission2(
+ permission_info->CreateAPIPermission());
+
+ std::unique_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ ASSERT_TRUE(permission2->FromValue(value.get(), NULL, NULL));
+ EXPECT_FALSE(permission1->Equal(permission2.get()));
+}
+
+TEST(MediaGalleriesPermissionTest, ToFromValue) {
+ const extensions::APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(
+ extensions::APIPermission::kMediaGalleries);
+
+ std::unique_ptr<extensions::APIPermission> permission1(
+ permission_info->CreateAPIPermission());
+ std::unique_ptr<extensions::APIPermission> permission2(
+ permission_info->CreateAPIPermission());
+
+ std::unique_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kAllAutoDetectedPermission);
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ std::unique_ptr<base::Value> vtmp(permission1->ToValue());
+ ASSERT_TRUE(vtmp);
+ ASSERT_TRUE(permission2->FromValue(vtmp.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ value->AppendString(MediaGalleriesPermission::kCopyToPermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ vtmp = permission1->ToValue();
+ ASSERT_TRUE(vtmp);
+ ASSERT_TRUE(permission2->FromValue(vtmp.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+ value.reset(new base::ListValue());
+ value->AppendString(MediaGalleriesPermission::kReadPermission);
+ value->AppendString(MediaGalleriesPermission::kDeletePermission);
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ vtmp = permission1->ToValue();
+ ASSERT_TRUE(vtmp);
+ ASSERT_TRUE(permission2->FromValue(vtmp.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+ value.reset(new base::ListValue());
+ // without sub-permission
+ ASSERT_TRUE(permission1->FromValue(NULL, NULL, NULL));
+
+ vtmp = permission1->ToValue();
+ ASSERT_TRUE(vtmp);
+ ASSERT_TRUE(permission2->FromValue(vtmp.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+}
+
+} // namespace
+
+} // namespace chrome_apps
diff --git a/chromium/chrome/common/attrition_experiments.h b/chromium/chrome/common/attrition_experiments.h
new file mode 100644
index 00000000000..56c08b3f26e
--- /dev/null
+++ b/chromium/chrome/common/attrition_experiments.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_ATTRITION_EXPERIMENTS_H_
+#define CHROME_COMMON_ATTRITION_EXPERIMENTS_H_
+
+#include "chrome/grit/chromium_strings.h"
+
+namespace attrition_experiments {
+
+// A list of all the IDs we use for the attrition experiments.
+enum Experiment {
+ kEnUs1 = IDS_TRY_TOAST_HEADING,
+ kEnUs2 = IDS_TRY_TOAST_HEADING2,
+ kEnUs3 = IDS_TRY_TOAST_HEADING3,
+ kEnUs4 = IDS_TRY_TOAST_HEADING4,
+ kSkype1 = IDS_TRY_TOAST_HEADING_SKYPE,
+};
+
+// A comma-separated list of brand codes that are associated with Skype.
+const wchar_t kSkypeBrandCode[] = L"SKPC,SKPG,SKPH,SKPI,SKPL,SKPM,SKPN";
+
+} // namespace attrition_experiments
+
+#endif // CHROME_COMMON_ATTRITION_EXPERIMENTS_H_
diff --git a/chromium/chrome/common/auto_start_linux.cc b/chromium/chrome/common/auto_start_linux.cc
new file mode 100644
index 00000000000..66b6739bfb4
--- /dev/null
+++ b/chromium/chrome/common/auto_start_linux.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/auto_start_linux.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/nix/xdg_util.h"
+#include "base/strings/string_tokenizer.h"
+
+namespace {
+
+const base::FilePath::CharType kAutostart[] = "autostart";
+
+base::FilePath GetAutostartDirectory(base::Environment* environment) {
+ base::FilePath result = base::nix::GetXDGDirectory(
+ environment,
+ base::nix::kXdgConfigHomeEnvVar,
+ base::nix::kDotConfigDir);
+ result = result.Append(kAutostart);
+ return result;
+}
+
+} // namespace
+
+bool AutoStart::AddApplication(const std::string& autostart_filename,
+ const std::string& application_name,
+ const std::string& command_line,
+ bool is_terminal_app) {
+ std::unique_ptr<base::Environment> environment(base::Environment::Create());
+ base::FilePath autostart_directory = GetAutostartDirectory(environment.get());
+ if (!base::DirectoryExists(autostart_directory) &&
+ !base::CreateDirectory(autostart_directory)) {
+ return false;
+ }
+
+ base::FilePath autostart_file =
+ autostart_directory.Append(autostart_filename);
+ std::string terminal = is_terminal_app ? "true" : "false";
+ std::string autostart_file_contents =
+ "[Desktop Entry]\n"
+ "Type=Application\n"
+ "Terminal=" + terminal + "\n"
+ "Exec=" + command_line + "\n"
+ "Name=" + application_name + "\n";
+ std::string::size_type content_length = autostart_file_contents.length();
+ if (base::WriteFile(autostart_file, autostart_file_contents.c_str(),
+ content_length) !=
+ static_cast<int>(content_length)) {
+ base::DeleteFile(autostart_file, false);
+ return false;
+ }
+ return true;
+}
+
+bool AutoStart::Remove(const std::string& autostart_filename) {
+ std::unique_ptr<base::Environment> environment(base::Environment::Create());
+ base::FilePath autostart_directory = GetAutostartDirectory(environment.get());
+ base::FilePath autostart_file =
+ autostart_directory.Append(autostart_filename);
+ return base::DeleteFile(autostart_file, false);
+}
+
+bool AutoStart::GetAutostartFileContents(
+ const std::string& autostart_filename, std::string* contents) {
+ std::unique_ptr<base::Environment> environment(base::Environment::Create());
+ base::FilePath autostart_directory = GetAutostartDirectory(environment.get());
+ base::FilePath autostart_file =
+ autostart_directory.Append(autostart_filename);
+ return base::ReadFileToString(autostart_file, contents);
+}
+
+bool AutoStart::GetAutostartFileValue(const std::string& autostart_filename,
+ const std::string& value_name,
+ std::string* value) {
+ std::string contents;
+ if (!GetAutostartFileContents(autostart_filename, &contents))
+ return false;
+ base::StringTokenizer tokenizer(contents, "\n");
+ std::string token = value_name + "=";
+ while (tokenizer.GetNext()) {
+ if (tokenizer.token().substr(0, token.length()) == token) {
+ *value = tokenizer.token().substr(token.length());
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/chromium/chrome/common/auto_start_linux.h b/chromium/chrome/common/auto_start_linux.h
new file mode 100644
index 00000000000..e04e3b62a50
--- /dev/null
+++ b/chromium/chrome/common/auto_start_linux.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_AUTO_START_LINUX_H_
+#define CHROME_COMMON_AUTO_START_LINUX_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+class AutoStart {
+ public:
+ // Registers an application to autostart on user login. |is_terminal_app|
+ // specifies whether the app will run in a terminal window.
+ static bool AddApplication(const std::string& autostart_filename,
+ const std::string& application_name,
+ const std::string& command_line,
+ bool is_terminal_app);
+ // Removes an autostart file.
+ static bool Remove(const std::string& autostart_filename);
+ // Gets the entire contents of an autostart file.
+ static bool GetAutostartFileContents(const std::string& autostart_filename,
+ std::string* contents);
+ // Gets a specific value from an autostart file.
+ static bool GetAutostartFileValue(const std::string& autostart_filename,
+ const std::string& value_name,
+ std::string* value);
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AutoStart);
+};
+
+#endif // CHROME_COMMON_AUTO_START_LINUX_H_
diff --git a/chromium/chrome/common/cast_messages.cc b/chromium/chrome/common/cast_messages.cc
new file mode 100644
index 00000000000..d1629f808cd
--- /dev/null
+++ b/chromium/chrome/common/cast_messages.cc
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/cast_messages.h"
+
+namespace IPC {
+
+void ParamTraits<media::cast::RtpTimeTicks>::Write(base::Pickle* m,
+ const param_type& p) {
+ ParamTraits<uint64_t>::Write(m, p.SerializeForIPC());
+}
+
+bool ParamTraits<media::cast::RtpTimeTicks>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ uint64_t serialized = UINT64_C(0);
+ if (ParamTraits<uint64_t>::Read(m, iter, &serialized)) {
+ *r = param_type::DeserializeForIPC(serialized);
+ return true;
+ }
+ return false;
+}
+
+void ParamTraits<media::cast::RtpTimeTicks>::Log(const param_type& p,
+ std::string* l) {
+ std::ostringstream oss;
+ oss << p;
+ l->append(oss.str());
+}
+
+void ParamTraits<media::cast::FrameId>::Write(base::Pickle* m,
+ const param_type& p) {
+ ParamTraits<uint64_t>::Write(m, p.SerializeForIPC());
+}
+
+bool ParamTraits<media::cast::FrameId>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ uint64_t serialized = UINT64_C(0);
+ if (ParamTraits<uint64_t>::Read(m, iter, &serialized)) {
+ *r = param_type::DeserializeForIPC(serialized);
+ return true;
+ }
+ return false;
+}
+
+void ParamTraits<media::cast::FrameId>::Log(const param_type& p,
+ std::string* l) {
+ std::ostringstream oss;
+ oss << p;
+ l->append(oss.str());
+}
+
+} // namespace IPC
diff --git a/chromium/chrome/common/cast_messages.h b/chromium/chrome/common/cast_messages.h
new file mode 100644
index 00000000000..9a99cec4240
--- /dev/null
+++ b/chromium/chrome/common/cast_messages.h
@@ -0,0 +1,254 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// IPC messages for the Cast transport API.
+
+#ifndef CHROME_COMMON_CAST_MESSAGES_H_
+#define CHROME_COMMON_CAST_MESSAGES_H_
+
+#include <stdint.h>
+
+#include "ipc/ipc_message_macros.h"
+#include "media/cast/common/rtp_time.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/net/cast_transport.h"
+#include "media/cast/net/rtcp/rtcp_defines.h"
+#include "net/base/ip_endpoint.h"
+
+#ifndef INTERNAL_CHROME_COMMON_CAST_MESSAGES_H_
+#define INTERNAL_CHROME_COMMON_CAST_MESSAGES_H_
+
+namespace IPC {
+
+template<>
+struct ParamTraits<media::cast::RtpTimeTicks> {
+ using param_type = media::cast::RtpTimeTicks;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template<>
+struct ParamTraits<media::cast::FrameId> {
+ using param_type = media::cast::FrameId;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+} // namespace IPC
+
+#endif // INTERNAL_CHROME_COMMON_CAST_MESSAGES_H_
+
+// Multiply-included message file, hence no include guard from here.
+
+#undef IPC_MESSAGE_EXPORT
+#define IPC_MESSAGE_EXPORT
+#define IPC_MESSAGE_START CastMsgStart
+
+IPC_ENUM_TRAITS_MAX_VALUE(
+ media::cast::EncodedFrame::Dependency,
+ media::cast::EncodedFrame::DEPENDENCY_LAST)
+IPC_ENUM_TRAITS_MAX_VALUE(media::cast::Codec,
+ media::cast::CODEC_LAST)
+IPC_ENUM_TRAITS_MAX_VALUE(media::cast::CastTransportStatus,
+ media::cast::CAST_TRANSPORT_STATUS_LAST)
+IPC_ENUM_TRAITS_MAX_VALUE(media::cast::CastLoggingEvent,
+ media::cast::kNumOfLoggingEvents)
+IPC_ENUM_TRAITS_MAX_VALUE(media::cast::EventMediaType,
+ media::cast::EVENT_MEDIA_TYPE_LAST)
+IPC_ENUM_TRAITS_MIN_MAX_VALUE(media::cast::RtpPayloadType,
+ media::cast::RtpPayloadType::FIRST,
+ media::cast::RtpPayloadType::LAST)
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::EncodedFrame)
+ IPC_STRUCT_TRAITS_MEMBER(dependency)
+ IPC_STRUCT_TRAITS_MEMBER(frame_id)
+ IPC_STRUCT_TRAITS_MEMBER(referenced_frame_id)
+ IPC_STRUCT_TRAITS_MEMBER(rtp_timestamp)
+ IPC_STRUCT_TRAITS_MEMBER(reference_time)
+ IPC_STRUCT_TRAITS_MEMBER(new_playout_delay_ms)
+ IPC_STRUCT_TRAITS_MEMBER(data)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::RtcpDlrrReportBlock)
+ IPC_STRUCT_TRAITS_MEMBER(last_rr)
+ IPC_STRUCT_TRAITS_MEMBER(delay_since_last_rr)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::CastTransportRtpConfig)
+ IPC_STRUCT_TRAITS_MEMBER(ssrc)
+ IPC_STRUCT_TRAITS_MEMBER(rtp_stream_id)
+ IPC_STRUCT_TRAITS_MEMBER(feedback_ssrc)
+ IPC_STRUCT_TRAITS_MEMBER(rtp_payload_type)
+ IPC_STRUCT_TRAITS_MEMBER(aes_key)
+ IPC_STRUCT_TRAITS_MEMBER(aes_iv_mask)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::PacketEvent)
+ IPC_STRUCT_TRAITS_MEMBER(rtp_timestamp)
+ IPC_STRUCT_TRAITS_MEMBER(frame_id)
+ IPC_STRUCT_TRAITS_MEMBER(max_packet_id)
+ IPC_STRUCT_TRAITS_MEMBER(packet_id)
+ IPC_STRUCT_TRAITS_MEMBER(size)
+ IPC_STRUCT_TRAITS_MEMBER(timestamp)
+ IPC_STRUCT_TRAITS_MEMBER(type)
+ IPC_STRUCT_TRAITS_MEMBER(media_type)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::FrameEvent)
+ IPC_STRUCT_TRAITS_MEMBER(rtp_timestamp)
+ IPC_STRUCT_TRAITS_MEMBER(frame_id)
+ IPC_STRUCT_TRAITS_MEMBER(size)
+ IPC_STRUCT_TRAITS_MEMBER(timestamp)
+ IPC_STRUCT_TRAITS_MEMBER(type)
+ IPC_STRUCT_TRAITS_MEMBER(media_type)
+ IPC_STRUCT_TRAITS_MEMBER(delay_delta)
+ IPC_STRUCT_TRAITS_MEMBER(key_frame)
+ IPC_STRUCT_TRAITS_MEMBER(target_bitrate)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::RtcpCastMessage)
+ IPC_STRUCT_TRAITS_MEMBER(remote_ssrc)
+ IPC_STRUCT_TRAITS_MEMBER(ack_frame_id)
+ IPC_STRUCT_TRAITS_MEMBER(target_delay_ms)
+ IPC_STRUCT_TRAITS_MEMBER(missing_frames_and_packets)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::RtcpPliMessage)
+ IPC_STRUCT_TRAITS_MEMBER(remote_ssrc)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::RtcpEvent)
+ IPC_STRUCT_TRAITS_MEMBER(type)
+ IPC_STRUCT_TRAITS_MEMBER(timestamp)
+ IPC_STRUCT_TRAITS_MEMBER(delay_delta)
+ IPC_STRUCT_TRAITS_MEMBER(packet_id)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::RtcpReportBlock)
+ IPC_STRUCT_TRAITS_MEMBER(remote_ssrc)
+ IPC_STRUCT_TRAITS_MEMBER(media_ssrc)
+ IPC_STRUCT_TRAITS_MEMBER(fraction_lost)
+ IPC_STRUCT_TRAITS_MEMBER(cumulative_lost)
+ IPC_STRUCT_TRAITS_MEMBER(extended_high_sequence_number)
+ IPC_STRUCT_TRAITS_MEMBER(jitter)
+ IPC_STRUCT_TRAITS_MEMBER(last_sr)
+ IPC_STRUCT_TRAITS_MEMBER(delay_since_last_sr)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(media::cast::RtcpTimeData)
+ IPC_STRUCT_TRAITS_MEMBER(ntp_seconds)
+ IPC_STRUCT_TRAITS_MEMBER(ntp_fraction)
+ IPC_STRUCT_TRAITS_MEMBER(timestamp)
+IPC_STRUCT_TRAITS_END()
+
+// Cast messages sent from the browser to the renderer.
+
+IPC_MESSAGE_CONTROL2(CastMsg_ReceivedPacket,
+ int32_t /* channel_id */,
+ media::cast::Packet /* packet */)
+
+IPC_MESSAGE_CONTROL3(CastMsg_Rtt,
+ int32_t /* channel_id */,
+ uint32_t /* rtp sender ssrc */,
+ base::TimeDelta /* rtt */)
+
+IPC_MESSAGE_CONTROL3(CastMsg_RtcpCastMessage,
+ int32_t /* channel_id */,
+ uint32_t /* rtp sender ssrc */,
+ media::cast::RtcpCastMessage /* cast_message */)
+
+// This message indicates receiving picture loss indicator from rtp receiver.
+IPC_MESSAGE_CONTROL2(CastMsg_Pli,
+ int32_t /* channel_id */,
+ uint32_t /* rtp sender ssrc */)
+
+IPC_MESSAGE_CONTROL2(CastMsg_NotifyStatusChange,
+ int32_t /* channel_id */,
+ media::cast::CastTransportStatus /* status */)
+
+IPC_MESSAGE_CONTROL3(CastMsg_RawEvents,
+ int32_t /* channel_id */,
+ std::vector<media::cast::PacketEvent> /* packet_events */,
+ std::vector<media::cast::FrameEvent> /* frame_events */)
+
+// Cast messages sent from the renderer to the browser.
+
+IPC_MESSAGE_CONTROL2(CastHostMsg_InitializeStream,
+ int32_t /*channel_id*/,
+ media::cast::CastTransportRtpConfig /*config*/)
+
+IPC_MESSAGE_CONTROL3(CastHostMsg_InsertFrame,
+ int32_t /* channel_id */,
+ uint32_t /* ssrc */,
+ media::cast::EncodedFrame /* audio/video frame */)
+
+IPC_MESSAGE_CONTROL4(CastHostMsg_SendSenderReport,
+ int32_t /* channel_id */,
+ uint32_t /* ssrc */,
+ base::TimeTicks /* current_time */,
+ media::cast::RtpTimeTicks /* cur_time_as_rtp_timestamp */)
+
+IPC_MESSAGE_CONTROL3(CastHostMsg_CancelSendingFrames,
+ int32_t /* channel_id */,
+ uint32_t /* ssrc */,
+ std::vector<media::cast::FrameId> /* frame_ids */)
+
+IPC_MESSAGE_CONTROL3(CastHostMsg_ResendFrameForKickstart,
+ int32_t /* channel_id */,
+ uint32_t /* ssrc */,
+ media::cast::FrameId /* frame_id */)
+
+IPC_MESSAGE_CONTROL3(CastHostMsg_AddValidRtpReceiver,
+ int32_t /* channel id */,
+ uint32_t /* rtp sender ssrc */,
+ uint32_t /* rtp receiver ssrc */)
+
+IPC_MESSAGE_CONTROL4(CastHostMsg_New,
+ int32_t /* channel_id */,
+ net::IPEndPoint /* local_end_point */,
+ net::IPEndPoint /* remote_end_point */,
+ base::DictionaryValue /* options */)
+
+IPC_MESSAGE_CONTROL1(CastHostMsg_Delete, int32_t /* channel_id */)
+
+// The following messages are used to build and send the RTCP packet from the
+// RTP receiver. |CastHostMsg_InitializeRtpReceiverRtcpBuilder| needs to be sent
+// before sending other optional RTCP messages.
+// |CastHostMsg_SendRtcpFromRtpReceiver| has to be sent in the end to finish
+// building the packet. The built packet will then be sent out.
+
+IPC_MESSAGE_CONTROL3(CastHostMsg_InitializeRtpReceiverRtcpBuilder,
+ int32_t /* channel id */,
+ uint32_t /* rtp_receiver_ssrc */,
+ media::cast::RtcpTimeData /* time_data */)
+
+IPC_MESSAGE_CONTROL3(CastHostMsg_AddCastFeedback,
+ int32_t /* channel id */,
+ media::cast::RtcpCastMessage /* cast message */,
+ base::TimeDelta /* target delay */)
+
+IPC_MESSAGE_CONTROL2(CastHostMsg_AddPli,
+ int32_t /* channel id */,
+ media::cast::RtcpPliMessage /* pli message */)
+
+IPC_MESSAGE_CONTROL2(
+ CastHostMsg_AddRtcpEvents,
+ int32_t /* channel id */,
+ media::cast::ReceiverRtcpEventSubscriber::RtcpEvents /* rtcp_events */)
+
+IPC_MESSAGE_CONTROL2(
+ CastHostMsg_AddRtpReceiverReport,
+ int32_t /* channel id */,
+ media::cast::RtcpReportBlock /* rtp_receiver_report_block */)
+
+IPC_MESSAGE_CONTROL1(CastHostMsg_SendRtcpFromRtpReceiver,
+ int32_t /* channel id */)
+
+#endif // CHROME_COMMON_CAST_MESSAGES_H_
diff --git a/chromium/chrome/common/channel_info.cc b/chromium/chrome/common/channel_info.cc
new file mode 100644
index 00000000000..7345984f2a7
--- /dev/null
+++ b/chromium/chrome/common/channel_info.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/channel_info.h"
+
+#include "components/version_info/version_info.h"
+#include "components/version_info/version_string.h"
+
+namespace chrome {
+
+std::string GetVersionString() {
+ return version_info::GetVersionStringWithModifier(GetChannelName());
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/channel_info.h b/chromium/chrome/common/channel_info.h
new file mode 100644
index 00000000000..c2c6c3c2496
--- /dev/null
+++ b/chromium/chrome/common/channel_info.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHANNEL_INFO_H_
+#define CHROME_COMMON_CHANNEL_INFO_H_
+
+#include <string>
+
+#include "build/build_config.h"
+
+namespace base {
+class Environment;
+}
+
+namespace version_info {
+enum class Channel;
+}
+
+namespace chrome {
+
+// Returns a version string to be displayed in "About Chromium" dialog.
+std::string GetVersionString();
+
+// Returns a human-readable modifier for the version string. For a branded
+// build, this modifier is the channel ("canary", "dev", or "beta", but ""
+// for stable). On Windows, this may be modified with additional information
+// after a hyphen. For multi-user installations, it will return "canary-m",
+// "dev-m", "beta-m", and for a stable channel multi-user installation, "m".
+// In branded builds, when the channel cannot be determined, "unknown" will
+// be returned. In unbranded builds, the modifier is usually an empty string
+// (""), although on Linux, it may vary in certain distributions.
+// GetChannelName() is intended to be used for display purposes.
+// To simply test the channel, use GetChannel().
+std::string GetChannelName();
+
+// Returns the channel for the installation. In branded builds, this will be
+// version_info::Channel::{STABLE,BETA,DEV,CANARY}. In unbranded builds, or
+// in branded builds when the channel cannot be determined, this will be
+// version_info::Channel::UNKNOWN.
+version_info::Channel GetChannel();
+
+#if defined(OS_MACOSX)
+// Maps the name of the channel to version_info::Channel, always returning
+// Channel::UNKNOWN for unbranded builds. For branded builds defaults to
+// Channel::STABLE, if channel is empty, else matches the name and returns
+// {STABLE,BETA,DEV,CANARY, UNKNOWN}.
+version_info::Channel GetChannelByName(const std::string& channel);
+#endif
+
+#if defined(OS_POSIX) && defined(GOOGLE_CHROME_BUILD)
+// Returns a channel-specific suffix to use when constructing the path of the
+// default user data directory, allowing multiple channels to run side-by-side.
+// In the stable channel, this returns the empty string.
+std::string GetChannelSuffixForDataDir();
+#endif
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// Returns the channel-specific filename of the desktop shortcut used to launch
+// the browser.
+std::string GetDesktopName(base::Environment* env);
+#endif
+
+} // namespace chrome
+
+#endif // CHROME_COMMON_CHANNEL_INFO_H_
diff --git a/chromium/chrome/common/channel_info_android.cc b/chromium/chrome/common/channel_info_android.cc
new file mode 100644
index 00000000000..d73a9460685
--- /dev/null
+++ b/chromium/chrome/common/channel_info_android.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/channel_info.h"
+
+#include "base/android/build_info.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "components/version_info/android/channel_getter.h"
+#include "components/version_info/version_info.h"
+
+namespace chrome {
+
+std::string GetChannelName() {
+ switch (GetChannel()) {
+ case version_info::Channel::UNKNOWN: return "unknown";
+ case version_info::Channel::CANARY: return "canary";
+ case version_info::Channel::DEV: return "dev";
+ case version_info::Channel::BETA: return "beta";
+ case version_info::Channel::STABLE: return std::string();
+ }
+ NOTREACHED() << "Unknown channel " << static_cast<int>(GetChannel());
+ return std::string();
+}
+
+version_info::Channel GetChannel() {
+ return version_info::android::GetChannel();
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/channel_info_chromeos.cc b/chromium/chrome/common/channel_info_chromeos.cc
new file mode 100644
index 00000000000..4f540e15166
--- /dev/null
+++ b/chromium/chrome/common/channel_info_chromeos.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/channel_info.h"
+
+#include "base/system/sys_info.h"
+#include "build/branding_buildflags.h"
+#include "components/version_info/version_info.h"
+
+namespace chrome {
+namespace {
+
+version_info::Channel g_chromeos_channel = version_info::Channel::UNKNOWN;
+
+#if defined(GOOGLE_CHROME_BUILD)
+// Sets the |g_chromeos_channel|.
+void SetChannel(const std::string& channel) {
+ if (channel == "stable-channel")
+ g_chromeos_channel = version_info::Channel::STABLE;
+ else if (channel == "beta-channel")
+ g_chromeos_channel = version_info::Channel::BETA;
+ else if (channel == "dev-channel")
+ g_chromeos_channel = version_info::Channel::DEV;
+ else if (channel == "canary-channel")
+ g_chromeos_channel = version_info::Channel::CANARY;
+ else
+ g_chromeos_channel = version_info::Channel::UNKNOWN;
+}
+#endif
+
+} // namespace
+
+std::string GetChannelName() {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ switch (g_chromeos_channel) {
+ case version_info::Channel::STABLE:
+ return std::string();
+ case version_info::Channel::BETA:
+ return "beta";
+ case version_info::Channel::DEV:
+ return "dev";
+ case version_info::Channel::CANARY:
+ return "canary";
+ default:
+ return "unknown";
+ }
+#endif
+ return std::string();
+}
+
+version_info::Channel GetChannel() {
+ static bool is_channel_set = false;
+ if (is_channel_set)
+ return g_chromeos_channel;
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ static const char kChromeOSReleaseTrack[] = "CHROMEOS_RELEASE_TRACK";
+ std::string channel;
+ if (base::SysInfo::GetLsbReleaseValue(kChromeOSReleaseTrack, &channel)) {
+ SetChannel(channel);
+ is_channel_set = true;
+ }
+#endif
+ return g_chromeos_channel;
+}
+
+#if defined(GOOGLE_CHROME_BUILD)
+std::string GetChannelSuffixForDataDir() {
+ // ChromeOS doesn't support side-by-side installations.
+ return std::string();
+}
+#endif // defined(GOOGLE_CHROME_BUILD)
+
+} // namespace chrome
diff --git a/chromium/chrome/common/channel_info_mac.mm b/chromium/chrome/common/channel_info_mac.mm
new file mode 100644
index 00000000000..3c8e51c4acb
--- /dev/null
+++ b/chromium/chrome/common/channel_info_mac.mm
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/channel_info.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/mac/bundle_locations.h"
+#include "base/strings/sys_string_conversions.h"
+#include "build/branding_buildflags.h"
+#include "components/version_info/version_info.h"
+
+namespace chrome {
+
+std::string GetChannelName() {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // Use the main Chrome application bundle and not the framework bundle.
+ // Keystone keys don't live in the framework.
+ NSBundle* bundle = base::mac::OuterBundle();
+ NSString* channel = [bundle objectForInfoDictionaryKey:@"KSChannelID"];
+
+ // Only ever return "", "unknown", "beta", "dev", or "canary" in a branded
+ // build.
+ if (![bundle objectForInfoDictionaryKey:@"KSProductID"]) {
+ // This build is not Keystone-enabled, it can't have a channel.
+ channel = @"unknown";
+ } else if (!channel) {
+ // For the stable channel, KSChannelID is not set.
+ channel = @"";
+ } else if ([channel isEqual:@"beta"] ||
+ [channel isEqual:@"dev"] ||
+ [channel isEqual:@"canary"]) {
+ // do nothing.
+ } else {
+ channel = @"unknown";
+ }
+
+ return base::SysNSStringToUTF8(channel);
+#else
+ return std::string();
+#endif
+}
+
+version_info::Channel GetChannelByName(const std::string& channel) {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ if (channel.empty())
+ return version_info::Channel::STABLE;
+ if (channel == "beta")
+ return version_info::Channel::BETA;
+ if (channel == "dev")
+ return version_info::Channel::DEV;
+ if (channel == "canary")
+ return version_info::Channel::CANARY;
+#endif
+ return version_info::Channel::UNKNOWN;
+}
+
+version_info::Channel GetChannel() {
+ return GetChannelByName(GetChannelName());
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/channel_info_posix.cc b/chromium/chrome/common/channel_info_posix.cc
new file mode 100644
index 00000000000..d5dfbe13e27
--- /dev/null
+++ b/chromium/chrome/common/channel_info_posix.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/channel_info.h"
+
+#include "base/environment.h"
+#include "base/strings/string_util.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "components/version_info/version_info.h"
+
+namespace chrome {
+
+namespace {
+
+// Helper function to return both the channel enum and modifier string.
+// Implements both together to prevent their behavior from diverging, which has
+// happened multiple times in the past.
+version_info::Channel GetChannelImpl(std::string* modifier_out,
+ std::string* data_dir_suffix_out) {
+ version_info::Channel channel = version_info::Channel::UNKNOWN;
+ std::string modifier;
+ std::string data_dir_suffix;
+
+ char* env = getenv("CHROME_VERSION_EXTRA");
+ if (env)
+ modifier = env;
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // Only ever return "", "unknown", "dev" or "beta" in a branded build.
+ if (modifier == "unstable") // linux version of "dev"
+ modifier = "dev";
+ if (modifier == "stable") {
+ channel = version_info::Channel::STABLE;
+ modifier = "";
+ } else if (modifier == "dev") {
+ channel = version_info::Channel::DEV;
+ data_dir_suffix = "-unstable";
+ } else if (modifier == "beta") {
+ channel = version_info::Channel::BETA;
+ data_dir_suffix = "-beta";
+ } else {
+ modifier = "unknown";
+ }
+#endif
+
+ if (modifier_out)
+ modifier_out->swap(modifier);
+ if (data_dir_suffix_out)
+ data_dir_suffix_out->swap(data_dir_suffix);
+
+ return channel;
+}
+
+} // namespace
+
+std::string GetChannelName() {
+ std::string modifier;
+ GetChannelImpl(&modifier, nullptr);
+ return modifier;
+}
+
+#if defined(GOOGLE_CHROME_BUILD)
+std::string GetChannelSuffixForDataDir() {
+ std::string data_dir_suffix;
+ GetChannelImpl(nullptr, &data_dir_suffix);
+ return data_dir_suffix;
+}
+#endif // defined(GOOGLE_CHROME_BUILD)
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+std::string GetDesktopName(base::Environment* env) {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ version_info::Channel product_channel(GetChannel());
+ switch (product_channel) {
+ case version_info::Channel::DEV:
+ return "google-chrome-unstable.desktop";
+ case version_info::Channel::BETA:
+ return "google-chrome-beta.desktop";
+ default:
+ return "google-chrome.desktop";
+ }
+#else // BUILDFLAG(CHROMIUM_BRANDING)
+ // Allow $CHROME_DESKTOP to override the built-in value, so that development
+ // versions can set themselves as the default without interfering with
+ // non-official, packaged versions using the built-in value.
+ std::string name;
+ if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
+ return name;
+ return "chromium-browser.desktop";
+#endif
+}
+#endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
+
+version_info::Channel GetChannel() {
+ return GetChannelImpl(nullptr, nullptr);
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/channel_info_win.cc b/chromium/chrome/common/channel_info_win.cc
new file mode 100644
index 00000000000..8a9e2d3335b
--- /dev/null
+++ b/chromium/chrome/common/channel_info_win.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/channel_info.h"
+
+#include "base/debug/profiler.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/branding_buildflags.h"
+#include "chrome/install_static/install_util.h"
+
+namespace chrome {
+
+std::string GetChannelName() {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ base::string16 channel(install_static::GetChromeChannelName());
+#if defined(DCHECK_IS_CONFIGURABLE)
+ // Adorn the channel when DCHECKs are baked into the build, as there will be
+ // a performance hit. See https://crbug.com/812058 for details.
+ channel += L"-dcheck";
+#endif // defined(DCHECK_IS_CONFIGURABLE)
+ return base::UTF16ToASCII(channel);
+#else
+ return std::string();
+#endif
+}
+
+version_info::Channel GetChannel() {
+ return install_static::GetChromeChannel();
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/child_process_logging.h b/chromium/chrome/common/child_process_logging.h
new file mode 100644
index 00000000000..cf69a11eea4
--- /dev/null
+++ b/chromium/chrome/common/child_process_logging.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHILD_PROCESS_LOGGING_H_
+#define CHROME_COMMON_CHILD_PROCESS_LOGGING_H_
+
+#include "build/build_config.h"
+
+namespace child_process_logging {
+
+#if defined(OS_WIN)
+// Sets up the base/debug/crash_logging.h mechanism.
+void Init();
+#endif // defined(OS_WIN)
+
+} // namespace child_process_logging
+
+#endif // CHROME_COMMON_CHILD_PROCESS_LOGGING_H_
diff --git a/chromium/chrome/common/child_process_logging_win.cc b/chromium/chrome/common/child_process_logging_win.cc
new file mode 100644
index 00000000000..3cb69614ff7
--- /dev/null
+++ b/chromium/chrome/common/child_process_logging_win.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/child_process_logging.h"
+
+#include <windows.h>
+
+#include <memory>
+
+#include "chrome/chrome_elf/chrome_elf_main.h"
+#include "chrome/common/crash_keys.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "components/metrics/client_info.h"
+
+namespace child_process_logging {
+
+void Init() {
+ // This would be handled by BreakpadClient::SetCrashClientIdFromGUID(), but
+ // because of the aforementioned issue, crash keys aren't ready yet at the
+ // time of Breakpad initialization, load the client id backed up in Google
+ // Update settings instead.
+ // Please note if we are using Crashpad via chrome_elf then we need to call
+ // into chrome_elf to pass in the client id.
+ std::unique_ptr<metrics::ClientInfo> client_info =
+ GoogleUpdateSettings::LoadMetricsClientInfo();
+
+ // Set the client id chrome_elf (in tests this is stubbed).
+ SetMetricsClientId(client_info ? client_info->client_id.c_str() : nullptr);
+}
+
+} // namespace child_process_logging
diff --git a/chromium/chrome/common/chrome_constants_win_unittest.cc b/chromium/chrome/common/chrome_constants_win_unittest.cc
new file mode 100644
index 00000000000..0e5bd0f996b
--- /dev/null
+++ b/chromium/chrome/common/chrome_constants_win_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/chrome_constants.h"
+
+#include <memory>
+
+#include "base/file_version_info.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome {
+
+// Verify that |kChromeVersion| is equal to the version in the VS_VERSION_INFO
+// resource of chrome.exe.
+TEST(ChromeConstants, ChromeVersion) {
+ base::FilePath current_exe_dir;
+ EXPECT_TRUE(base::PathService::Get(base::DIR_EXE, &current_exe_dir));
+ base::FilePath chrome_exe_path =
+ current_exe_dir.Append(chrome::kBrowserProcessExecutableName);
+
+ std::unique_ptr<FileVersionInfo> file_version_info(
+ FileVersionInfo::CreateFileVersionInfo(chrome_exe_path));
+ ASSERT_TRUE(file_version_info);
+ EXPECT_EQ(base::UTF16ToASCII(file_version_info->file_version()),
+ kChromeVersion);
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/chrome_content_client.cc b/chromium/chrome/common/chrome_content_client.cc
new file mode 100644
index 00000000000..c0b96efac11
--- /dev/null
+++ b/chromium/chrome/common/chrome_content_client.cc
@@ -0,0 +1,853 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/chrome_content_client.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/containers/flat_set.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/native_library.h"
+#include "base/no_destructor.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/child_process_logging.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/crash_keys.h"
+#include "chrome/common/pepper_flash.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/common_resources.h"
+#include "components/crash/core/common/crash_key.h"
+#include "components/dom_distiller/core/url_constants.h"
+#include "components/net_log/chrome_net_log.h"
+#include "components/services/heap_profiling/public/cpp/profiling_client.h"
+#include "content/public/common/cdm_info.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/buildflags/buildflags.h"
+#include "extensions/common/constants.h"
+#include "gpu/config/gpu_info.h"
+#include "gpu/config/gpu_util.h"
+#include "media/base/decrypt_config.h"
+#include "media/base/media_switches.h"
+#include "media/base/video_codecs.h"
+#include "media/media_buildflags.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "net/http/http_util.h"
+#include "pdf/buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "third_party/widevine/cdm/buildflags.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/url_constants.h"
+
+#if defined(OS_LINUX)
+#include <fcntl.h>
+#include "chrome/common/component_flash_hint_file_linux.h"
+#include "sandbox/linux/services/credentials.h"
+#endif // defined(OS_LINUX)
+
+#if defined(OS_MACOSX)
+#include "services/service_manager/sandbox/mac/nacl_loader.sb.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+#include "components/nacl/common/nacl_constants.h"
+#include "components/nacl/common/nacl_process_type.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/common/pepper_plugin_info.h"
+#include "flapper_version.h" // nogncheck In SHARED_INTERMEDIATE_DIR.
+#include "ppapi/shared_impl/ppapi_permissions.h" // nogncheck
+#endif
+
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+#include "media/cdm/cdm_paths.h" // nogncheck
+#endif
+
+#if BUILDFLAG(ENABLE_WIDEVINE) && defined(OS_LINUX)
+#include "base/no_destructor.h"
+#include "chrome/common/media/cdm_manifest.h"
+#include "third_party/widevine/cdm/widevine_cdm_common.h" // nogncheck
+// TODO(crbug.com/663554): Needed for WIDEVINE_CDM_VERSION_STRING. Support
+// component updated CDM on all desktop platforms and remove this.
+// This file is In SHARED_INTERMEDIATE_DIR.
+#include "widevine_cdm_version.h" // nogncheck
+#if !defined(OS_CHROMEOS)
+#include "chrome/common/media/component_widevine_cdm_hint_file_linux.h"
+#endif // !defined(OS_CHROMEOS)
+#endif // BUILDFLAG(ENABLE_WIDEVINE) && defined(OS_LINUX)
+
+#if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
+#include "chrome/common/media/cdm_host_file_path.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "chrome/common/media/chrome_media_drm_bridge_client.h"
+#endif
+
+namespace {
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#if BUILDFLAG(ENABLE_PDF)
+const char kPDFPluginExtension[] = "pdf";
+const char kPDFPluginDescription[] = "Portable Document Format";
+const char kPDFPluginOutOfProcessMimeType[] =
+ "application/x-google-chrome-pdf";
+const uint32_t kPDFPluginPermissions = ppapi::PERMISSION_PDF |
+ ppapi::PERMISSION_DEV;
+
+content::PepperPluginInfo::GetInterfaceFunc g_pdf_get_interface;
+content::PepperPluginInfo::PPP_InitializeModuleFunc g_pdf_initialize_module;
+content::PepperPluginInfo::PPP_ShutdownModuleFunc g_pdf_shutdown_module;
+#endif // BUILDFLAG(ENABLE_PDF)
+
+#if BUILDFLAG(ENABLE_NACL)
+content::PepperPluginInfo::GetInterfaceFunc g_nacl_get_interface;
+content::PepperPluginInfo::PPP_InitializeModuleFunc g_nacl_initialize_module;
+content::PepperPluginInfo::PPP_ShutdownModuleFunc g_nacl_shutdown_module;
+#endif
+
+// Appends the known built-in plugins to the given vector. Some built-in
+// plugins are "internal" which means they are compiled into the Chrome binary,
+// and some are extra shared libraries distributed with the browser (these are
+// not marked internal, aside from being automatically registered, they're just
+// regular plugins).
+void ComputeBuiltInPlugins(std::vector<content::PepperPluginInfo>* plugins) {
+#if BUILDFLAG(ENABLE_PDF)
+ content::PepperPluginInfo pdf_info;
+ pdf_info.is_internal = true;
+ pdf_info.is_out_of_process = true;
+ pdf_info.name = ChromeContentClient::kPDFInternalPluginName;
+ pdf_info.description = kPDFPluginDescription;
+ pdf_info.path = base::FilePath(ChromeContentClient::kPDFPluginPath);
+ content::WebPluginMimeType pdf_mime_type(
+ kPDFPluginOutOfProcessMimeType,
+ kPDFPluginExtension,
+ kPDFPluginDescription);
+ pdf_info.mime_types.push_back(pdf_mime_type);
+ pdf_info.internal_entry_points.get_interface = g_pdf_get_interface;
+ pdf_info.internal_entry_points.initialize_module = g_pdf_initialize_module;
+ pdf_info.internal_entry_points.shutdown_module = g_pdf_shutdown_module;
+ pdf_info.permissions = kPDFPluginPermissions;
+ plugins->push_back(pdf_info);
+#endif // BUILDFLAG(ENABLE_PDF)
+
+#if BUILDFLAG(ENABLE_NACL)
+ // Handle Native Client just like the PDF plugin. This means that it is
+ // enabled by default for the non-portable case. This allows apps installed
+ // from the Chrome Web Store to use NaCl even if the command line switch
+ // isn't set. For other uses of NaCl we check for the command line switch.
+ content::PepperPluginInfo nacl;
+ // The nacl plugin is now built into the Chromium binary.
+ nacl.is_internal = true;
+ nacl.path = base::FilePath(ChromeContentClient::kNaClPluginFileName);
+ nacl.name = nacl::kNaClPluginName;
+ content::WebPluginMimeType nacl_mime_type(nacl::kNaClPluginMimeType,
+ nacl::kNaClPluginExtension,
+ nacl::kNaClPluginDescription);
+ nacl.mime_types.push_back(nacl_mime_type);
+ content::WebPluginMimeType pnacl_mime_type(nacl::kPnaclPluginMimeType,
+ nacl::kPnaclPluginExtension,
+ nacl::kPnaclPluginDescription);
+ nacl.mime_types.push_back(pnacl_mime_type);
+ nacl.internal_entry_points.get_interface = g_nacl_get_interface;
+ nacl.internal_entry_points.initialize_module = g_nacl_initialize_module;
+ nacl.internal_entry_points.shutdown_module = g_nacl_shutdown_module;
+ nacl.permissions = ppapi::PERMISSION_PRIVATE | ppapi::PERMISSION_DEV;
+ plugins->push_back(nacl);
+#endif // BUILDFLAG(ENABLE_NACL)
+}
+
+// Creates a PepperPluginInfo for the specified plugin.
+// |path| is the full path to the plugin.
+// |version| is a string representation of the plugin version.
+// |is_external| is whether the plugin is supplied external to Chrome e.g. a
+// system installation of Adobe Flash.
+content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path,
+ const std::string& version,
+ bool is_external) {
+ content::PepperPluginInfo plugin;
+
+ plugin.is_out_of_process = true;
+ plugin.name = content::kFlashPluginName;
+ plugin.path = path;
+ plugin.permissions = kPepperFlashPermissions;
+ plugin.is_external = is_external;
+
+ std::vector<std::string> flash_version_numbers = base::SplitString(
+ version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (flash_version_numbers.size() < 1)
+ flash_version_numbers.push_back("11");
+ if (flash_version_numbers.size() < 2)
+ flash_version_numbers.push_back("2");
+ if (flash_version_numbers.size() < 3)
+ flash_version_numbers.push_back("999");
+ if (flash_version_numbers.size() < 4)
+ flash_version_numbers.push_back("999");
+ // E.g., "Shockwave Flash 10.2 r154":
+ plugin.description = plugin.name + " " + flash_version_numbers[0] + "." +
+ flash_version_numbers[1] + " r" + flash_version_numbers[2];
+ plugin.version = base::JoinString(flash_version_numbers, ".");
+ content::WebPluginMimeType swf_mime_type(content::kFlashPluginSwfMimeType,
+ content::kFlashPluginSwfExtension,
+ content::kFlashPluginSwfDescription);
+ plugin.mime_types.push_back(swf_mime_type);
+ content::WebPluginMimeType spl_mime_type(content::kFlashPluginSplMimeType,
+ content::kFlashPluginSplExtension,
+ content::kFlashPluginSplDescription);
+ plugin.mime_types.push_back(spl_mime_type);
+
+ return plugin;
+}
+
+bool GetCommandLinePepperFlash(content::PepperPluginInfo* plugin) {
+ const base::CommandLine::StringType flash_path =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ switches::kPpapiFlashPath);
+ if (flash_path.empty())
+ return false;
+
+ // Also get the version from the command-line. Should be something like 11.2
+ // or 11.2.123.45.
+ std::string flash_version =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kPpapiFlashVersion);
+
+ *plugin = CreatePepperFlashInfo(base::FilePath(flash_path), flash_version,
+ true);
+ return true;
+}
+
+// Check if flash player exists on disk, and if so, populate a PepperPluginInfo
+// structure. Returns false if the flash player found is not compatible with the
+// system (architecture, OS, versions, etc.).
+bool TryCreatePepperFlashInfo(const base::FilePath& flash_filename,
+ content::PepperPluginInfo* plugin) {
+ if (!base::PathExists(flash_filename))
+ return false;
+
+ base::FilePath manifest_path(
+ flash_filename.DirName().Append(FILE_PATH_LITERAL("manifest.json")));
+
+ std::string manifest_data;
+ if (!base::ReadFileToString(manifest_path, &manifest_data))
+ return false;
+
+ std::unique_ptr<base::DictionaryValue> manifest =
+ base::DictionaryValue::From(base::JSONReader::ReadDeprecated(
+ manifest_data, base::JSON_ALLOW_TRAILING_COMMAS));
+ if (!manifest)
+ return false;
+
+ base::Version version;
+ if (!CheckPepperFlashManifest(*manifest, &version)) {
+ LOG(ERROR) << "Browser not compatible with given flash manifest.";
+ return false;
+ }
+
+ *plugin = CreatePepperFlashInfo(flash_filename, version.GetString(), true);
+ return true;
+}
+
+#if defined(OS_CHROMEOS)
+bool GetComponentUpdatedPepperFlash(content::PepperPluginInfo* plugin) {
+ base::FilePath flash_filename;
+ if (!base::PathService::Get(chrome::FILE_CHROME_OS_COMPONENT_FLASH,
+ &flash_filename)) {
+ return false;
+ }
+
+ // Chrome OS mounts a disk image containing component updated flash player, at
+ // boot time, if and only if a component update is present.
+ if (!base::PathExists(flash_filename))
+ return false;
+
+ return TryCreatePepperFlashInfo(flash_filename, plugin);
+}
+#elif defined(OS_LINUX)
+// This method is used on Linux only because of architectural differences in how
+// it loads the component updated flash plugin, and not because the other
+// platforms do not support component updated flash. On other platforms, the
+// component updater sends an IPC message to all threads, at undefined points in
+// time, with the URL of the component updated flash. Because the linux zygote
+// thread has no access to the file system after it warms up, it must preload
+// the component updated flash.
+bool GetComponentUpdatedPepperFlash(content::PepperPluginInfo* plugin) {
+#if defined(FLAPPER_AVAILABLE)
+ if (component_flash_hint_file::DoesHintFileExist()) {
+ base::FilePath flash_path;
+ std::string version;
+ if (component_flash_hint_file::VerifyAndReturnFlashLocation(&flash_path,
+ &version)) {
+ // Test if the file can be mapped as executable. If the user's home
+ // directory is mounted noexec, the component flash plugin will not load.
+ // By testing for this, Chrome can fallback to the bundled flash plugin.
+ if (!component_flash_hint_file::TestExecutableMapping(flash_path)) {
+ LOG(WARNING) << "The component updated flash plugin could not be "
+ "mapped as executable. Attempting to fallback to the "
+ "bundled or system plugin.";
+ return false;
+ }
+ *plugin = CreatePepperFlashInfo(flash_path, version, false);
+ return true;
+ }
+ LOG(ERROR)
+ << "Failed to locate and load the component updated flash plugin.";
+ }
+#endif // defined(FLAPPER_AVAILABLE)
+ return false;
+}
+#endif // defined(OS_CHROMEOS)
+
+bool GetSystemPepperFlash(content::PepperPluginInfo* plugin) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ // Do not try and find System Pepper Flash if there is a specific path on
+ // the commmand-line.
+ if (command_line->HasSwitch(switches::kPpapiFlashPath))
+ return false;
+
+ base::FilePath flash_filename;
+ if (!base::PathService::Get(chrome::FILE_PEPPER_FLASH_SYSTEM_PLUGIN,
+ &flash_filename))
+ return false;
+
+ return TryCreatePepperFlashInfo(flash_filename, plugin);
+}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+#if (BUILDFLAG(BUNDLE_WIDEVINE_CDM) || \
+ BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)) && \
+ defined(OS_LINUX)
+// Create a CdmInfo for a Widevine CDM, using |version|, |cdm_library_path|, and
+// |capability|.
+std::unique_ptr<content::CdmInfo> CreateWidevineCdmInfo(
+ const base::Version& version,
+ const base::FilePath& cdm_library_path,
+ content::CdmCapability capability) {
+ return std::make_unique<content::CdmInfo>(
+ kWidevineCdmDisplayName, kWidevineCdmGuid, version, cdm_library_path,
+ kWidevineCdmFileSystemId, std::move(capability), kWidevineKeySystem,
+ false);
+}
+
+#if !defined(OS_CHROMEOS)
+// On desktop Linux, given |cdm_base_path| that points to a folder containing
+// the Widevine CDM and associated files, read the manifest included in that
+// directory and create a CdmInfo. If that is successful, return the CdmInfo. If
+// not, return nullptr.
+std::unique_ptr<content::CdmInfo> CreateCdmInfoFromWidevineDirectory(
+ const base::FilePath& cdm_base_path) {
+ // Library should be inside a platform specific directory.
+ auto cdm_library_path =
+ media::GetPlatformSpecificDirectory(cdm_base_path)
+ .Append(base::GetNativeLibraryName(kWidevineCdmLibraryName));
+ if (!base::PathExists(cdm_library_path))
+ return nullptr;
+
+ // Manifest should be at the top level.
+ auto manifest_path = cdm_base_path.Append(FILE_PATH_LITERAL("manifest.json"));
+ base::Version version;
+ content::CdmCapability capability;
+ if (!ParseCdmManifestFromPath(manifest_path, &version, &capability))
+ return nullptr;
+
+ return CreateWidevineCdmInfo(version, cdm_library_path,
+ std::move(capability));
+}
+#endif // !defined(OS_CHROMEOS)
+#endif // (BUILDFLAG(BUNDLE_WIDEVINE_CDM) ||
+ // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)) && defined(OS_LINUX)
+
+#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) && defined(OS_LINUX)
+// On Linux/ChromeOS we have to preload the CDM since it uses the zygote
+// sandbox. On Windows and Mac, the bundled CDM is handled by the component
+// updater.
+
+#if defined(OS_CHROMEOS)
+std::unique_ptr<content::CdmInfo> CreateCdmInfoForChromeOS(
+ const base::FilePath& install_dir) {
+ // On ChromeOS the Widevine CDM library is in the component directory and
+ // does not have a manifest.
+ // TODO(crbug.com/971433): Move Widevine CDM to a separate folder in the
+ // component directory so that the manifest can be included.
+ auto cdm_library_path =
+ install_dir.Append(base::GetNativeLibraryName(kWidevineCdmLibraryName));
+ if (!base::PathExists(cdm_library_path))
+ return nullptr;
+
+ // As there is no manifest, set |capability| as if it came from one. These
+ // values must match the CDM that is being bundled with Chrome.
+ content::CdmCapability capability;
+
+ // Add the supported codecs as if they came from the component manifest.
+ capability.video_codecs.push_back(media::VideoCodec::kCodecVP8);
+ capability.video_codecs.push_back(media::VideoCodec::kCodecVP9);
+ capability.video_codecs.push_back(media::VideoCodec::kCodecAV1);
+ // TODO(crbug.com/899403): Update this and tests after Widevine CDM supports
+ // VP9 profile 2.
+ capability.supports_vp9_profile2 = false;
+#if BUILDFLAG(USE_PROPRIETARY_CODECS)
+ capability.video_codecs.push_back(media::VideoCodec::kCodecH264);
+#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
+
+ // Both encryption schemes are supported on ChromeOS.
+ capability.encryption_schemes.insert(media::EncryptionMode::kCenc);
+ capability.encryption_schemes.insert(media::EncryptionMode::kCbcs);
+
+ // Both temporary and persistent sessions are supported on ChromeOS.
+ capability.session_types.insert(media::CdmSessionType::kTemporary);
+ capability.session_types.insert(media::CdmSessionType::kPersistentLicense);
+
+ return CreateWidevineCdmInfo(base::Version(WIDEVINE_CDM_VERSION_STRING),
+ cdm_library_path, std::move(capability));
+}
+#endif // defined(OS_CHROMEOS)
+
+// This code checks to see if the Widevine CDM was bundled with Chrome. If one
+// can be found and looks valid, it returns the CdmInfo for the CDM. Otherwise
+// it returns nullptr.
+content::CdmInfo* GetBundledWidevine() {
+ // We only want to do this on the first call, as if Widevine wasn't bundled
+ // with Chrome (or it was deleted/removed) it won't be loaded into the zygote.
+ static base::NoDestructor<std::unique_ptr<content::CdmInfo>> s_cdm_info(
+ []() -> std::unique_ptr<content::CdmInfo> {
+ base::FilePath install_dir;
+ CHECK(base::PathService::Get(chrome::DIR_BUNDLED_WIDEVINE_CDM,
+ &install_dir));
+
+#if defined(OS_CHROMEOS)
+ // On ChromeOS the Widevine CDM library is in the component directory
+ // (returned above) and does not have a manifest.
+ // TODO(crbug.com/971433): Move Widevine CDM to a separate folder in
+ // the component directory so that the manifest can be included.
+ return CreateCdmInfoForChromeOS(install_dir);
+#else
+ // On desktop Linux the MANIFEST is bundled with the CDM.
+ return CreateCdmInfoFromWidevineDirectory(install_dir);
+#endif // defined(OS_CHROMEOS)
+ }());
+ return s_cdm_info->get();
+}
+#endif // BUILDFLAG(BUNDLE_WIDEVINE_CDM) && defined(OS_LINUX)
+
+#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) && defined(OS_LINUX)
+// This code checks to see if a component updated Widevine CDM can be found. If
+// there is one and it looks valid, return the CdmInfo for that CDM. Otherwise
+// return nullptr.
+content::CdmInfo* GetComponentUpdatedWidevine() {
+ // We only want to do this on the first call, as the component updater may run
+ // and download a new version once Chrome has been running for a while. Since
+ // the first returned version will be the one loaded into the zygote, we want
+ // to return the same thing on subsequent calls.
+ static base::NoDestructor<std::unique_ptr<content::CdmInfo>> s_cdm_info(
+ []() -> std::unique_ptr<content::CdmInfo> {
+ auto install_dir = GetLatestComponentUpdatedWidevineCdmDirectory();
+ if (install_dir.empty())
+ return nullptr;
+
+ return CreateCdmInfoFromWidevineDirectory(install_dir);
+ }());
+ return s_cdm_info->get();
+}
+#endif // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) && defined(OS_LINUX)
+
+} // namespace
+
+ChromeContentClient::ChromeContentClient() {
+}
+
+ChromeContentClient::~ChromeContentClient() {
+}
+
+#if BUILDFLAG(ENABLE_NACL)
+void ChromeContentClient::SetNaClEntryFunctions(
+ content::PepperPluginInfo::GetInterfaceFunc get_interface,
+ content::PepperPluginInfo::PPP_InitializeModuleFunc initialize_module,
+ content::PepperPluginInfo::PPP_ShutdownModuleFunc shutdown_module) {
+ g_nacl_get_interface = get_interface;
+ g_nacl_initialize_module = initialize_module;
+ g_nacl_shutdown_module = shutdown_module;
+}
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS) && BUILDFLAG(ENABLE_PDF)
+void ChromeContentClient::SetPDFEntryFunctions(
+ content::PepperPluginInfo::GetInterfaceFunc get_interface,
+ content::PepperPluginInfo::PPP_InitializeModuleFunc initialize_module,
+ content::PepperPluginInfo::PPP_ShutdownModuleFunc shutdown_module) {
+ g_pdf_get_interface = get_interface;
+ g_pdf_initialize_module = initialize_module;
+ g_pdf_shutdown_module = shutdown_module;
+}
+#endif
+
+void ChromeContentClient::SetActiveURL(const GURL& url,
+ std::string top_origin) {
+ static crash_reporter::CrashKeyString<1024> active_url("url-chunk");
+ active_url.Set(url.possibly_invalid_spec());
+
+ // Use a large enough size for Origin::GetDebugString.
+ static crash_reporter::CrashKeyString<128> top_origin_key("top-origin");
+ top_origin_key.Set(top_origin);
+}
+
+void ChromeContentClient::SetGpuInfo(const gpu::GPUInfo& gpu_info) {
+ gpu::SetKeysForCrashLogging(gpu_info);
+}
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// static
+content::PepperPluginInfo* ChromeContentClient::FindMostRecentPlugin(
+ const std::vector<std::unique_ptr<content::PepperPluginInfo>>& plugins) {
+ if (plugins.empty())
+ return nullptr;
+
+ using PluginSortKey = std::tuple<base::Version, bool>;
+
+ std::map<PluginSortKey, content::PepperPluginInfo*> plugin_map;
+
+ for (auto& plugin : plugins) {
+ base::Version version(plugin->version);
+ DCHECK(version.IsValid());
+ plugin_map[PluginSortKey(version, plugin->is_external)] = plugin.get();
+ }
+
+ return plugin_map.rbegin()->second;
+}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+void ChromeContentClient::AddPepperPlugins(
+ std::vector<content::PepperPluginInfo>* plugins) {
+#if BUILDFLAG(ENABLE_PLUGINS)
+ ComputeBuiltInPlugins(plugins);
+
+ // If flash is disabled, do not try to add any flash plugin.
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ bool disable_bundled_flash =
+ command_line->HasSwitch(switches::kDisableBundledPpapiFlash);
+
+ std::vector<std::unique_ptr<content::PepperPluginInfo>> flash_versions;
+
+// Get component updated flash for desktop Linux and Chrome OS.
+#if defined(OS_LINUX)
+ // Depending on the sandbox configuration, the file system
+ // is not always available. If it is not available, do not try and load any
+ // flash plugin. The flash player, if any, preloaded before the sandbox
+ // initialization will continue to be used.
+ if (!sandbox::Credentials::HasFileSystemAccess())
+ return;
+
+ auto component_flash = std::make_unique<content::PepperPluginInfo>();
+ if (!disable_bundled_flash &&
+ GetComponentUpdatedPepperFlash(component_flash.get()))
+ flash_versions.push_back(std::move(component_flash));
+#endif // defined(OS_LINUX)
+
+ auto command_line_flash = std::make_unique<content::PepperPluginInfo>();
+ if (GetCommandLinePepperFlash(command_line_flash.get()))
+ flash_versions.push_back(std::move(command_line_flash));
+
+ auto system_flash = std::make_unique<content::PepperPluginInfo>();
+ if (GetSystemPepperFlash(system_flash.get()))
+ flash_versions.push_back(std::move(system_flash));
+
+ // This function will return only the most recent version of the flash plugin.
+ content::PepperPluginInfo* max_flash = FindMostRecentPlugin(flash_versions);
+ if (max_flash) {
+ plugins->push_back(*max_flash);
+ } else if (!disable_bundled_flash) {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(FLAPPER_AVAILABLE)
+ // Add a fake Flash plugin even though it doesn't actually exist - if a
+ // web page requests it, it will be component-updated on-demand. There is
+ // nothing that guarantees the component update will give us the
+ // FLAPPER_VERSION_STRING version of Flash, but using this version seems
+ // better than any other hardcoded alternative.
+ plugins->push_back(
+ CreatePepperFlashInfo(base::FilePath(ChromeContentClient::kNotPresent),
+ FLAPPER_VERSION_STRING, false));
+#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(FLAPPER_AVAILABLE)
+ }
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+}
+
+void ChromeContentClient::AddContentDecryptionModules(
+ std::vector<content::CdmInfo>* cdms,
+ std::vector<media::CdmHostFilePath>* cdm_host_file_paths) {
+ if (cdms) {
+#if BUILDFLAG(ENABLE_WIDEVINE) && defined(OS_LINUX)
+ // The Widevine CDM on Linux needs to be registered (and loaded) before the
+ // zygote is locked down. The CDM can be found from the version bundled with
+ // Chrome (if BUNDLE_WIDEVINE_CDM = true) and/or the version downloaded by
+ // the component updater (if ENABLE_WIDEVINE_CDM_COMPONENT = true). If two
+ // versions exist, take the one with the higher version number.
+ //
+ // Note that the component updater will detect the bundled version, and if
+ // there is no newer version available, select the bundled version. In this
+ // case both versions will be the same and point to the same directory, so
+ // it doesn't matter which one is loaded.
+ content::CdmInfo* bundled_widevine = nullptr;
+#if BUILDFLAG(BUNDLE_WIDEVINE_CDM)
+ bundled_widevine = GetBundledWidevine();
+#endif
+
+ content::CdmInfo* updated_widevine = nullptr;
+#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)
+ updated_widevine = GetComponentUpdatedWidevine();
+#endif
+
+ // If only a bundled version is available, or both are available and the
+ // bundled version is not less than the updated version, register the
+ // bundled version. If only the updated version is available, or both are
+ // available and the updated version is greater, then register the updated
+ // version. If neither are available, then nothing is registered.
+ if (bundled_widevine &&
+ (!updated_widevine ||
+ bundled_widevine->version >= updated_widevine->version)) {
+ VLOG(1) << "Registering bundled Widevine " << bundled_widevine->version;
+ cdms->push_back(*bundled_widevine);
+ } else if (updated_widevine) {
+ VLOG(1) << "Registering component updated Widevine "
+ << updated_widevine->version;
+ cdms->push_back(*updated_widevine);
+ } else {
+ VLOG(1) << "Widevine enabled but no library found";
+ }
+#endif // BUILDFLAG(ENABLE_WIDEVINE) && defined(OS_LINUX)
+
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+ // Register Clear Key CDM if specified in command line.
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ base::FilePath clear_key_cdm_path =
+ command_line->GetSwitchValuePath(switches::kClearKeyCdmPathForTesting);
+ if (!clear_key_cdm_path.empty() && base::PathExists(clear_key_cdm_path)) {
+ // TODO(crbug.com/764480): Remove these after we have a central place for
+ // External Clear Key (ECK) related information.
+ // Normal External Clear Key key system.
+ const char kExternalClearKeyKeySystem[] = "org.chromium.externalclearkey";
+ // A variant of ECK key system that has a different GUID.
+ const char kExternalClearKeyDifferentGuidTestKeySystem[] =
+ "org.chromium.externalclearkey.differentguid";
+
+ // Supported codecs are hard-coded in ExternalClearKeyProperties.
+ content::CdmCapability capability(
+ {}, {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs},
+ {media::CdmSessionType::kTemporary,
+ media::CdmSessionType::kPersistentLicense,
+ media::CdmSessionType::kPersistentUsageRecord},
+ {});
+
+ // Register kExternalClearKeyDifferentGuidTestKeySystem first separately.
+ // Otherwise, it'll be treated as a sub-key-system of normal
+ // kExternalClearKeyKeySystem. See MultipleCdmTypes test in
+ // ECKEncryptedMediaTest.
+ cdms->push_back(content::CdmInfo(
+ media::kClearKeyCdmDisplayName, media::kClearKeyCdmDifferentGuid,
+ base::Version("0.1.0.0"), clear_key_cdm_path,
+ media::kClearKeyCdmFileSystemId, capability,
+ kExternalClearKeyDifferentGuidTestKeySystem, false));
+
+ cdms->push_back(
+ content::CdmInfo(media::kClearKeyCdmDisplayName,
+ media::kClearKeyCdmGuid, base::Version("0.1.0.0"),
+ clear_key_cdm_path, media::kClearKeyCdmFileSystemId,
+ capability, kExternalClearKeyKeySystem, true));
+ }
+#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
+ }
+
+#if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
+ if (cdm_host_file_paths)
+ AddCdmHostFilePaths(cdm_host_file_paths);
+#endif
+}
+
+// New schemes by which content can be retrieved should almost certainly be
+// marked as "standard" schemes, even if they're internal, chrome-only schemes.
+// "Standard" here just means that its URLs behave like 'normal' URL do.
+// - Standard schemes get canonicalized like "new-scheme://hostname/[path]"
+// - Whereas "new-scheme:hostname" is a valid nonstandard URL.
+// - Thus, hostnames can't be extracted from non-standard schemes.
+// - The presence of hostnames enables the same-origin policy. Resources like
+// "new-scheme://foo/" are kept separate from "new-scheme://bar/". For
+// a nonstandard scheme, every resource loaded from that scheme could
+// have access to every other resource.
+// - The same-origin policy is very important if webpages can be
+// loaded via the scheme. Try to organize the URL space of any new scheme
+// such that hostnames provide meaningful compartmentalization of
+// privileges.
+//
+// Example standard schemes: https://, chrome-extension://, chrome://, file://
+// Example nonstandard schemes: mailto:, data:, javascript:, about:
+static const char* const kChromeStandardURLSchemes[] = {
+ extensions::kExtensionScheme,
+ chrome::kChromeNativeScheme,
+ chrome::kChromeSearchScheme,
+ dom_distiller::kDomDistillerScheme,
+#if defined(OS_CHROMEOS)
+ chrome::kCrosScheme,
+#endif
+};
+
+void ChromeContentClient::AddAdditionalSchemes(Schemes* schemes) {
+ for (auto* standard_scheme : kChromeStandardURLSchemes)
+ schemes->standard_schemes.push_back(standard_scheme);
+
+#if defined(OS_ANDROID)
+ schemes->referrer_schemes.push_back(chrome::kAndroidAppScheme);
+#endif
+
+ schemes->savable_schemes.push_back(extensions::kExtensionScheme);
+ schemes->savable_schemes.push_back(chrome::kChromeSearchScheme);
+ schemes->savable_schemes.push_back(dom_distiller::kDomDistillerScheme);
+
+ // chrome-search: resources shouldn't trigger insecure content warnings.
+ schemes->secure_schemes.push_back(chrome::kChromeSearchScheme);
+
+ // Treat as secure because communication with them is entirely in the browser,
+ // so there is no danger of manipulation or eavesdropping on communication
+ // with them by third parties.
+ schemes->secure_schemes.push_back(extensions::kExtensionScheme);
+
+ // chrome-native: is a scheme used for placeholder navigations that allow
+ // UIs to be drawn with platform native widgets instead of HTML. These pages
+ // should be treated as empty documents that can commit synchronously.
+ schemes->empty_document_schemes.push_back(chrome::kChromeNativeScheme);
+ schemes->no_access_schemes.push_back(chrome::kChromeNativeScheme);
+ schemes->secure_schemes.push_back(chrome::kChromeNativeScheme);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ schemes->service_worker_schemes.push_back(extensions::kExtensionScheme);
+
+ // As far as Blink is concerned, they should be allowed to receive CORS
+ // requests. At the Extensions layer, requests will actually be blocked unless
+ // overridden by the web_accessible_resources manifest key.
+ // TODO(kalman): See what happens with a service worker.
+ schemes->cors_enabled_schemes.push_back(extensions::kExtensionScheme);
+
+ schemes->csp_bypassing_schemes.push_back(extensions::kExtensionScheme);
+#endif
+
+#if defined(OS_CHROMEOS)
+ schemes->local_schemes.push_back(content::kExternalFileScheme);
+#endif
+
+#if defined(OS_ANDROID)
+ schemes->local_schemes.push_back(url::kContentScheme);
+#endif
+}
+
+base::string16 ChromeContentClient::GetLocalizedString(int message_id) {
+ return l10n_util::GetStringUTF16(message_id);
+}
+
+base::string16 ChromeContentClient::GetLocalizedString(
+ int message_id,
+ const base::string16& replacement) {
+ return l10n_util::GetStringFUTF16(message_id, replacement);
+}
+
+base::StringPiece ChromeContentClient::GetDataResource(
+ int resource_id,
+ ui::ScaleFactor scale_factor) {
+ return ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
+ resource_id, scale_factor);
+}
+
+base::RefCountedMemory* ChromeContentClient::GetDataResourceBytes(
+ int resource_id) {
+ return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
+ resource_id);
+}
+
+gfx::Image& ChromeContentClient::GetNativeImageNamed(int resource_id) {
+ return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
+ resource_id);
+}
+
+base::DictionaryValue ChromeContentClient::GetNetLogConstants() {
+ auto platform_dict = net_log::GetPlatformConstantsForNetLog(
+ base::CommandLine::ForCurrentProcess()->GetCommandLineString(),
+ chrome::GetChannelName());
+ if (platform_dict)
+ return std::move(*platform_dict);
+ else
+ return base::DictionaryValue();
+}
+
+std::string ChromeContentClient::GetProcessTypeNameInEnglish(int type) {
+#if BUILDFLAG(ENABLE_NACL)
+ switch (type) {
+ case PROCESS_TYPE_NACL_LOADER:
+ return "Native Client module";
+ case PROCESS_TYPE_NACL_BROKER:
+ return "Native Client broker";
+ }
+#endif
+
+ NOTREACHED() << "Unknown child process type!";
+ return "Unknown";
+}
+
+bool ChromeContentClient::AllowScriptExtensionForServiceWorker(
+ const url::Origin& script_origin) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return script_origin.scheme() == extensions::kExtensionScheme;
+#else
+ return false;
+#endif
+}
+
+blink::OriginTrialPolicy* ChromeContentClient::GetOriginTrialPolicy() {
+ // Prevent initialization race (see crbug.com/721144). There may be a
+ // race when the policy is needed for worker startup (which happens on a
+ // separate worker thread).
+ base::AutoLock auto_lock(origin_trial_policy_lock_);
+ if (!origin_trial_policy_)
+ origin_trial_policy_ = std::make_unique<ChromeOriginTrialPolicy>();
+ return origin_trial_policy_.get();
+}
+
+#if defined(OS_ANDROID)
+media::MediaDrmBridgeClient* ChromeContentClient::GetMediaDrmBridgeClient() {
+ return new ChromeMediaDrmBridgeClient();
+}
+#endif // OS_ANDROID
+
+void ChromeContentClient::BindChildProcessInterface(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle* receiving_handle) {
+ static base::NoDestructor<heap_profiling::ProfilingClient> profiling_client;
+ if (interface_name == heap_profiling::ProfilingClient::Name_) {
+ profiling_client->BindToInterface(
+ mojo::PendingReceiver<heap_profiling::mojom::ProfilingClient>(
+ std::move(*receiving_handle)));
+ }
+}
diff --git a/chromium/chrome/common/chrome_content_client.h b/chromium/chrome/common/chrome_content_client.h
new file mode 100644
index 00000000000..ff03027bd25
--- /dev/null
+++ b/chromium/chrome/common/chrome_content_client.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHROME_CONTENT_CLIENT_H_
+#define CHROME_COMMON_CHROME_CONTENT_CLIENT_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/origin_trials/chrome_origin_trial_policy.h"
+#include "components/nacl/common/buildflags.h"
+#include "content/public/common/content_client.h"
+#include "pdf/buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/common/pepper_plugin_info.h"
+#endif
+
+class ChromeContentClient : public content::ContentClient {
+ public:
+#if defined(GOOGLE_CHROME_BUILD)
+ // |kNotPresent| is a placeholder plugin location for plugins that are not
+ // currently present in this installation of Chrome, but which can be fetched
+ // on-demand and therefore should still appear in navigator.plugins.
+ static const base::FilePath::CharType kNotPresent[];
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+ static const base::FilePath::CharType kNaClPluginFileName[];
+#endif
+
+ static const char kPDFExtensionPluginName[];
+ static const char kPDFInternalPluginName[];
+ static const base::FilePath::CharType kPDFPluginPath[];
+
+ ChromeContentClient();
+ ~ChromeContentClient() override;
+
+ // The methods below are called by child processes to set the function
+ // pointers for built-in plugins. We avoid linking these plugins into
+ // chrome_common because then on Windows we would ship them twice because of
+ // the split DLL.
+#if BUILDFLAG(ENABLE_NACL)
+ static void SetNaClEntryFunctions(
+ content::PepperPluginInfo::GetInterfaceFunc get_interface,
+ content::PepperPluginInfo::PPP_InitializeModuleFunc initialize_module,
+ content::PepperPluginInfo::PPP_ShutdownModuleFunc shutdown_module);
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS) && BUILDFLAG(ENABLE_PDF)
+ static void SetPDFEntryFunctions(
+ content::PepperPluginInfo::GetInterfaceFunc get_interface,
+ content::PepperPluginInfo::PPP_InitializeModuleFunc initialize_module,
+ content::PepperPluginInfo::PPP_ShutdownModuleFunc shutdown_module);
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ // This returns the most recent plugin based on the plugin versions. In the
+ // event of a tie, a debug plugin will be considered more recent than a
+ // non-debug plugin.
+ // It does not make sense to call this on a vector that contains more than one
+ // plugin type. This function may return a nullptr if given an empty vector.
+ // The method is only visible for testing purposes.
+ static content::PepperPluginInfo* FindMostRecentPlugin(
+ const std::vector<std::unique_ptr<content::PepperPluginInfo>>& plugins);
+#endif
+
+ void SetActiveURL(const GURL& url, std::string top_origin) override;
+ void SetGpuInfo(const gpu::GPUInfo& gpu_info) override;
+ void AddPepperPlugins(
+ std::vector<content::PepperPluginInfo>* plugins) override;
+ void AddContentDecryptionModules(
+ std::vector<content::CdmInfo>* cdms,
+ std::vector<media::CdmHostFilePath>* cdm_host_file_paths) override;
+
+ void AddAdditionalSchemes(Schemes* schemes) override;
+ base::string16 GetLocalizedString(int message_id) override;
+ base::string16 GetLocalizedString(int message_id,
+ const base::string16& replacement) override;
+ base::StringPiece GetDataResource(int resource_id,
+ ui::ScaleFactor scale_factor) override;
+ base::RefCountedMemory* GetDataResourceBytes(int resource_id) override;
+ gfx::Image& GetNativeImageNamed(int resource_id) override;
+ base::DictionaryValue GetNetLogConstants() override;
+ std::string GetProcessTypeNameInEnglish(int type) override;
+
+ bool AllowScriptExtensionForServiceWorker(
+ const url::Origin& script_origin) override;
+
+ blink::OriginTrialPolicy* GetOriginTrialPolicy() override;
+
+#if defined(OS_ANDROID)
+ media::MediaDrmBridgeClient* GetMediaDrmBridgeClient() override;
+#endif // OS_ANDROID
+
+ void BindChildProcessInterface(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle* receiving_handle) override;
+
+ private:
+ // Used to lock when |origin_trial_policy_| is initialized.
+ base::Lock origin_trial_policy_lock_;
+ std::unique_ptr<ChromeOriginTrialPolicy> origin_trial_policy_;
+};
+
+#endif // CHROME_COMMON_CHROME_CONTENT_CLIENT_H_
diff --git a/chromium/chrome/common/chrome_content_client_constants.cc b/chromium/chrome/common/chrome_content_client_constants.cc
new file mode 100644
index 00000000000..a8365688d3e
--- /dev/null
+++ b/chromium/chrome/common/chrome_content_client_constants.cc
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/chrome_content_client.h"
+
+#if defined(GOOGLE_CHROME_BUILD)
+const base::FilePath::CharType ChromeContentClient::kNotPresent[] =
+ FILE_PATH_LITERAL("internal-not-yet-present");
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+const base::FilePath::CharType ChromeContentClient::kNaClPluginFileName[] =
+ FILE_PATH_LITERAL("internal-nacl-plugin");
+#endif
+
+#if defined(GOOGLE_CHROME_BUILD)
+const char ChromeContentClient::kPDFExtensionPluginName[] = "Chrome PDF Viewer";
+const char ChromeContentClient::kPDFInternalPluginName[] = "Chrome PDF Plugin";
+#else
+const char ChromeContentClient::kPDFExtensionPluginName[] =
+ "Chromium PDF Viewer";
+const char ChromeContentClient::kPDFInternalPluginName[] =
+ "Chromium PDF Plugin";
+#endif
+
+const base::FilePath::CharType ChromeContentClient::kPDFPluginPath[] =
+ FILE_PATH_LITERAL("internal-pdf-viewer");
diff --git a/chromium/chrome/common/chrome_content_client_unittest.cc b/chromium/chrome/common/chrome_content_client_unittest.cc
new file mode 100644
index 00000000000..7db44b74a63
--- /dev/null
+++ b/chromium/chrome/common/chrome_content_client_unittest.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/chrome_content_client.h"
+
+#include <string>
+
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/test/scoped_command_line.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/common/constants.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_util.h"
+
+namespace chrome_common {
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+TEST(ChromeContentClientTest, FindMostRecent) {
+ std::vector<std::unique_ptr<content::PepperPluginInfo>> version_vector;
+ // Test an empty vector.
+ EXPECT_EQ(nullptr, ChromeContentClient::FindMostRecentPlugin(version_vector));
+
+ // Now test the vector with one element.
+ content::PepperPluginInfo info;
+ info.version = "1.0.0.0";
+ version_vector.push_back(std::make_unique<content::PepperPluginInfo>(info));
+
+ content::PepperPluginInfo* most_recent =
+ ChromeContentClient::FindMostRecentPlugin(version_vector);
+ EXPECT_EQ("1.0.0.0", most_recent->version);
+
+ content::PepperPluginInfo info5;
+ info5.version = "5.0.12.1";
+ content::PepperPluginInfo info6_12;
+ info6_12.version = "6.0.0.12";
+ content::PepperPluginInfo info6_13;
+ info6_13.version = "6.0.0.13";
+
+ // Test highest version is picked.
+ version_vector.clear();
+ version_vector.push_back(std::make_unique<content::PepperPluginInfo>(info5));
+ version_vector.push_back(
+ std::make_unique<content::PepperPluginInfo>(info6_12));
+ version_vector.push_back(
+ std::make_unique<content::PepperPluginInfo>(info6_13));
+
+ most_recent = ChromeContentClient::FindMostRecentPlugin(version_vector);
+ EXPECT_EQ("6.0.0.13", most_recent->version);
+
+ // Test that order does not matter, validates tests below.
+ version_vector.clear();
+ version_vector.push_back(
+ std::make_unique<content::PepperPluginInfo>(info6_13));
+ version_vector.push_back(
+ std::make_unique<content::PepperPluginInfo>(info6_12));
+ version_vector.push_back(std::make_unique<content::PepperPluginInfo>(info5));
+
+ most_recent = ChromeContentClient::FindMostRecentPlugin(version_vector);
+ EXPECT_EQ("6.0.0.13", most_recent->version);
+
+ // Test real scenarios.
+ content::PepperPluginInfo component_flash;
+ component_flash.version = "4.3.2.1";
+ component_flash.is_external = false;
+ component_flash.name = "component_flash";
+
+ content::PepperPluginInfo system_flash;
+ system_flash.version = "4.3.2.1";
+ system_flash.is_external = true;
+ system_flash.name = "system_flash";
+
+ // The order here should be:
+ // 1. System Flash.
+ // 2. Component update.
+ version_vector.clear();
+ version_vector.push_back(
+ std::make_unique<content::PepperPluginInfo>(system_flash));
+ version_vector.push_back(
+ std::make_unique<content::PepperPluginInfo>(component_flash));
+ most_recent = ChromeContentClient::FindMostRecentPlugin(version_vector);
+ EXPECT_STREQ("system_flash", most_recent->name.c_str());
+}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+TEST(ChromeContentClientTest, AdditionalSchemes) {
+ EXPECT_TRUE(url::IsStandard(
+ extensions::kExtensionScheme,
+ url::Component(0, strlen(extensions::kExtensionScheme))));
+
+ GURL extension_url(
+ "chrome-extension://abcdefghijklmnopqrstuvwxyzabcdef/foo.html");
+ url::Origin origin = url::Origin::Create(extension_url);
+ EXPECT_EQ("chrome-extension://abcdefghijklmnopqrstuvwxyzabcdef",
+ origin.Serialize());
+
+ EXPECT_TRUE(content::IsOriginSecure(GURL("chrome-native://newtab/")));
+
+ GURL chrome_url(content::GetWebUIURL("dummyurl"));
+ EXPECT_TRUE(content::IsOriginSecure(chrome_url));
+ EXPECT_FALSE(content::OriginCanAccessServiceWorkers(chrome_url));
+ EXPECT_TRUE(
+ content::IsPotentiallyTrustworthyOrigin(url::Origin::Create(chrome_url)));
+}
+
+class OriginTrialInitializationTestThread
+ : public base::PlatformThread::Delegate {
+ public:
+ explicit OriginTrialInitializationTestThread(
+ ChromeContentClient* chrome_client)
+ : chrome_client_(chrome_client) {}
+
+ void ThreadMain() override { AccessPolicy(chrome_client_, &policy_objects_); }
+
+ // Static helper which can also be called from the main thread.
+ static void AccessPolicy(
+ ChromeContentClient* content_client,
+ std::vector<blink::OriginTrialPolicy*>* policy_objects) {
+ // Repeatedly access the lazily-created origin trial policy
+ for (int i = 0; i < 20; i++) {
+ blink::OriginTrialPolicy* policy = content_client->GetOriginTrialPolicy();
+ policy_objects->push_back(policy);
+ base::PlatformThread::YieldCurrentThread();
+ }
+ }
+
+ const std::vector<blink::OriginTrialPolicy*>* policy_objects() const {
+ return &policy_objects_;
+ }
+
+ private:
+ ChromeContentClient* chrome_client_;
+ std::vector<blink::OriginTrialPolicy*> policy_objects_;
+
+ DISALLOW_COPY_AND_ASSIGN(OriginTrialInitializationTestThread);
+};
+
+// Test that the lazy initialization of Origin Trial policy is resistant to
+// races with concurrent access. Failures (especially flaky) indicate that the
+// race prevention is no longer sufficient.
+TEST(ChromeContentClientTest, OriginTrialPolicyConcurrentInitialization) {
+ ChromeContentClient content_client;
+ std::vector<blink::OriginTrialPolicy*> policy_objects;
+ OriginTrialInitializationTestThread thread(&content_client);
+ base::PlatformThreadHandle handle;
+
+ ASSERT_TRUE(base::PlatformThread::Create(0, &thread, &handle));
+
+ // Repeatedly access the lazily-created origin trial policy
+ OriginTrialInitializationTestThread::AccessPolicy(&content_client,
+ &policy_objects);
+
+ base::PlatformThread::Join(handle);
+
+ ASSERT_EQ(20UL, policy_objects.size());
+
+ blink::OriginTrialPolicy* first_policy = policy_objects[0];
+
+ const std::vector<blink::OriginTrialPolicy*>* all_policy_objects[] = {
+ &policy_objects, thread.policy_objects(),
+ };
+
+ for (const std::vector<blink::OriginTrialPolicy*>* thread_policy_objects :
+ all_policy_objects) {
+ EXPECT_GE(20UL, thread_policy_objects->size());
+ for (blink::OriginTrialPolicy* policy : *(thread_policy_objects)) {
+ EXPECT_EQ(first_policy, policy);
+ }
+ }
+}
+
+} // namespace chrome_common
diff --git a/chromium/chrome/common/chrome_descriptors.h b/chromium/chrome/common/chrome_descriptors.h
new file mode 100644
index 00000000000..dd14978db86
--- /dev/null
+++ b/chromium/chrome/common/chrome_descriptors.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHROME_DESCRIPTORS_H_
+#define CHROME_COMMON_CHROME_DESCRIPTORS_H_
+
+#include "build/build_config.h"
+#include "content/public/common/content_descriptors.h"
+
+enum {
+#if defined(OS_ANDROID)
+ kAndroidLocalePakDescriptor = kContentIPCDescriptorMax + 1,
+ kAndroidSecondaryLocalePakDescriptor,
+ kAndroidChrome100PercentPakDescriptor,
+ kAndroidUIResourcesPakDescriptor,
+ // DFMs with native resources typically do not share file descriptors with
+ // child processes. Hence no corresponding *PakDescriptor is defined.
+ kAndroidMinidumpDescriptor,
+#endif
+};
+
+#endif // CHROME_COMMON_CHROME_DESCRIPTORS_H_
diff --git a/chromium/chrome/common/chrome_features.cc b/chromium/chrome/common/chrome_features.cc
new file mode 100644
index 00000000000..4b8205a3b36
--- /dev/null
+++ b/chromium/chrome/common/chrome_features.cc
@@ -0,0 +1,894 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/chrome_features.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_split.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_switches.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif
+
+namespace features {
+
+// All features in alphabetical order.
+
+#if defined(OS_ANDROID)
+const base::Feature kAddToHomescreenMessaging{
+ "AddToHomescreenMessaging", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+// Controls whether web apps can be installed via APKs on Chrome OS.
+const base::Feature kApkWebAppInstalls{"ApkWebAppInstalls",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_MACOSX)
+// Enable the new multi-profile-aware app shim mode.
+// TODO(https://crbug.com/982024): Delete this flag when feature is complete.
+const base::Feature kAppShimMultiProfile{"AppShimMultiProfile",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Can be used to disable RemoteCocoa (hosting NSWindows for apps in the app
+// process). For debugging purposes only.
+const base::Feature kAppShimRemoteCocoa{"AppShimRemoteCocoa",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables the "this OS is obsolete" infobar on Mac 10.9.
+// TODO(ellyjones): Remove this after the last 10.9 release.
+const base::Feature kShow10_9ObsoleteInfobar{"Show109ObsoleteInfobar",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Use the Toolkit-Views Task Manager window.
+const base::Feature kViewsTaskManager{"ViewsTaskManager",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // defined(OS_MACOSX)
+
+#if defined(OS_ANDROID)
+// Enables messaging in site permissions UI informing user when notifications
+// are disabled for the entire app.
+const base::Feature kAppNotificationStatusMessaging{
+ "AppNotificationStatusMessaging", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+// App Service related flags. See chrome/services/app_service/README.md.
+const base::Feature kAppServiceAsh{"AppServiceAsh",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kAppServiceIntentHandling{
+ "AppServiceIntentHandling", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kAppServiceShelf{"AppServiceShelf",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // !defined(OS_ANDROID)
+
+// Enables the built-in DNS resolver.
+const base::Feature kAsyncDns {
+ "AsyncDns",
+#if defined(OS_CHROMEOS) || defined(OS_MACOSX) || defined(OS_ANDROID)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
+
+#if defined(OS_ANDROID)
+const base::Feature kAutoFetchOnNetErrorPage{"AutoFetchOnNetErrorPage",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_WIN) || defined(OS_LINUX)
+// Enables the Restart background mode optimization. When all Chrome UI is
+// closed and it goes in the background, allows to restart the browser to
+// discard memory.
+const base::Feature kBackgroundModeAllowRestart{
+ "BackgroundModeAllowRestart", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // defined(OS_WIN) || defined(OS_LINUX)
+
+// Enables or disables whether permission prompts are automatically blocked
+// after the user has explicitly dismissed them too many times.
+const base::Feature kBlockPromptsIfDismissedOften{
+ "BlockPromptsIfDismissedOften", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables whether permission prompts are automatically blocked
+// after the user has ignored them too many times.
+const base::Feature kBlockPromptsIfIgnoredOften{
+ "BlockPromptsIfIgnoredOften", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Once the user declines a notification permission prompt in a WebContents,
+// automatically dismiss subsequent prompts in the same WebContents, from any
+// origin, until the next user-initiated navigation.
+const base::Feature kBlockRepeatedNotificationPermissionPrompts{
+ "BlockRepeatedNotificationPermissionPrompts",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Fixes for browser hang bugs are deployed in a field trial in order to measure
+// their impact. See crbug.com/478209.
+const base::Feature kBrowserHangFixesExperiment{
+ "BrowserHangFixesExperiment", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables redirecting users who get an interstitial when
+// accessing https://support.google.com/chrome/answer/6098869 to local
+// connection help content.
+const base::Feature kBundledConnectionHelpFeature{
+ "BundledConnectionHelp", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables the UI to configure caption settings.
+const base::Feature kCaptionSettings{"CaptionSettings",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if !defined(OS_ANDROID)
+// Enables logging UKMs for background tab activity by TabActivityWatcher.
+const base::Feature kTabMetricsLogging{"TabMetricsLogging",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_WIN)
+// Enables the blocking of third-party modules. This feature requires Windows 8
+// or higher because it depends on the ProcessExtensionPointDisablePolicy
+// mitigation, which was not available on Windows 7.
+// Note: Due to a limitation in the implementation of this feature, it is
+// required to start the browser two times to fully enable or disable it.
+const base::Feature kThirdPartyModulesBlocking{
+ "ThirdPartyModulesBlocking", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Enables the additional TLS 1.3 server-random-based downgrade protection
+// described in https://tools.ietf.org/html/rfc8446#section-4.1.3 for
+// connections which chain to a local trust anchor. The protection is
+// unconditionally enabled for known trust anchors.
+//
+// This is a MUST-level requirement of TLS 1.3, but may have compatibility
+// issues with some outdated buggy TLS-terminating proxies.
+const base::Feature kTLS13HardeningForLocalAnchors{
+ "TLS13HardeningForLocalAnchors", base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
+// Enables the dual certificate verification trial feature.
+// https://crbug.com/649026
+const base::Feature kCertDualVerificationTrialFeature{
+ "CertDualVerificationTrial", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Enables change picture video mode.
+const base::Feature kChangePictureVideoMode{"ChangePictureVideoMode",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_CHROMEOS)
+// Enables passing additional user authentication in requests to DMServer
+// (policy fetch, status report upload).
+const base::Feature kDMServerOAuthForChildUser{
+ "DMServerOAuthForChildUser", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_ANDROID)
+// Enables clearing of browsing data which is older than given time period.
+const base::Feature kClearOldBrowsingData{"ClearOldBrowsingData",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+const base::Feature kClickToOpenPDFPlaceholder{
+ "ClickToOpenPDFPlaceholder", base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_MACOSX)
+const base::Feature kImmersiveFullscreen{"ImmersiveFullscreen",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_CHROMEOS)
+// Shows a setting that allows disabling mouse acceleration.
+const base::Feature kAllowDisableMouseAcceleration{
+ "AllowDisableMouseAcceleration", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enable project Crostini, Linux VMs on Chrome OS.
+const base::Feature kCrostini{"Crostini", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable additional Crostini session status reporting for
+// managed devices only, i.e. reports of installed apps and kernel version.
+const base::Feature kCrostiniAdditionalEnterpriseReporting{
+ "CrostiniAdditionalEnterpriseReporting", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enable advanced access controls for Crostini-related features
+// (e.g. restricting VM CLI tools access, restricting Crostini root access).
+const base::Feature kCrostiniAdvancedAccessControls{
+ "CrostiniAdvancedAccessControls", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables infrastructure for applying Ansible playbook to default Crostini
+// container.
+const base::Feature kCrostiniAnsibleInfrastructure{
+ "CrostiniAnsibleInfrastructure", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables infrastructure for generating Ansible playbooks for the default
+// Crostini container from software configurations in JSON schema.
+const base::Feature kCrostiniAnsibleSoftwareManagement{
+ "CrostiniAnsibleSoftwareManagement", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables custom UI for forcibly closing unresponsive windows.
+const base::Feature kCrostiniForceClose{"CrostiniForceClose",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables the UI overhaul for Cups Printers in settings page.
+const base::Feature kCupsPrintersUiOverhaul{"CupsPrintersUiOverhaul",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enable support for "Plugin VMs" on Chrome OS.
+const base::Feature kPluginVm{"PluginVm", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Allow a Wilco DTC (diagnostics and telemetry controller) on Chrome OS.
+// More info about the project may be found here:
+// https://docs.google.com/document/d/18Ijj8YlC8Q3EWRzLspIi2dGxg4vIBVe5sJgMPt9SWYo
+const base::Feature kWilcoDtc{"WilcoDtc", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable uploading of a zip archive of system logs instead of individual files.
+const base::Feature kUploadZippedSystemLogs{"UploadZippedSystemLogs",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Enable chrome://terminal. Terminal System App will only run on
+// OS_CHROMEOS, but this flag must be defined for all platforms since
+// it is required for SystemWebApp tests.
+const base::Feature kTerminalSystemApp{"TerminalSystemApp",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable using tab sharing infobars for desktop capture.
+const base::Feature kDesktopCaptureTabSharingInfobar{
+ "DesktopCaptureTabSharingInfobar", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables new Desktop PWAs implementation that does not use
+// extensions.
+const base::Feature kDesktopPWAsWithoutExtensions{
+ "DesktopPWAsWithoutExtensions", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// When installing default installed PWAs, we wait for service workers
+// to cache resources.
+const base::Feature kDesktopPWAsCacheDuringDefaultInstall{
+ "DesktopPWAsCacheDuringDefaultInstall", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables local PWA installs to update their app manifest data if the site
+// changes its manifest.
+const base::Feature kDesktopPWAsLocalUpdating {
+ "DesktopPWAsLocalUpdating",
+#if defined(OS_CHROMEOS)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
+
+// Enables or disables use of new Desktop PWAs browser controller (that uses the
+// universal web_app::AppRegistrar) by extensions-based bookmark apps. Note that
+// the new Desktop PWAs implementation (not based on extensions) always uses the
+// new browser controller.
+const base::Feature kDesktopPWAsUnifiedUiController{
+ "DesktopPWAsUnifiedUiController", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables use of new Desktop PWAs launch manager by
+// extensions-based bookmark apps. (Note that Bookmark apps not based
+// on extensions unconditionally use the new launch manager.)
+const base::Feature kDesktopPWAsUnifiedLaunch{
+ "DesktopPWAsUnifiedLaunch", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables new Desktop PWAs Unified Sync and Storage (USS)
+// implementation that does not use extensions. Requires
+// kDesktopPWAsWithoutExtensions to be enabled.
+const base::Feature kDesktopPWAsUSS{"DesktopPWAsUSS",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables the ability to install PWAs from the omnibox.
+const base::Feature kDesktopPWAsOmniboxInstall{
+ "DesktopPWAsOmniboxInstall", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Disables downloads of unsafe file types over HTTP.
+const base::Feature kDisallowUnsafeHttpDownloads{
+ "DisallowUnsafeHttpDownloads", base::FEATURE_DISABLED_BY_DEFAULT};
+const char kDisallowUnsafeHttpDownloadsParamName[] = "MimeTypeList";
+
+// Enable DNS over HTTPS (DoH).
+const base::Feature kDnsOverHttps{"DnsOverHttps",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Set whether fallback to insecure DNS is allowed by default. This setting may
+// be overridden for individual transactions.
+const base::FeatureParam<bool> kDnsOverHttpsFallbackParam{&kDnsOverHttps,
+ "Fallback", true};
+
+// Supply one or more space-separated DoH server URI templates to use when this
+// feature is enabled. If no templates are specified, then a hardcoded mapping
+// will be used to construct a list of DoH templates associated with the IP
+// addresses of insecure resolvers in the discovered configuration.
+const base::FeatureParam<std::string> kDnsOverHttpsTemplatesParam{
+ &kDnsOverHttps, "Templates", ""};
+
+#if defined(OS_ANDROID)
+// Enable changing default downloads storage location on Android.
+const base::Feature kDownloadsLocationChange{"DownloadsLocationChange",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// If enabled, Drive will use FCM for its invalidations.
+const base::Feature kDriveFcmInvalidations{"DriveFCMInvalidations",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// If enabled, policies will use FCM (Firebase Cloud Messaging) for its
+// invalidations.
+const base::Feature kPolicyFcmInvalidations{"PolicyFCMInvalidations",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables ambient authentication in incognito mode.
+// TODO(https://crbug.com/458508): Change to disabled by default after proper
+// notice to use the policy to activate when required, M79-M80.
+const base::Feature kEnableAmbientAuthenticationInIncognito{
+ "EnableAmbientAuthenticationInIncognito", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables ambient authentication in guest sessions.
+// TODO(https://crbug.com/458508): Change to disabled by default after proper
+// notice to use the policy to activate when required, M79-M80.
+const base::Feature kEnableAmbientAuthenticationInGuestSession{
+ "EnableAmbientAuthenticationInGuestSession",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
+// Enables support for FTP URLs. When disabled ftp:// URLs will behave the same
+// as any other URL scheme that's unknown to the browser.
+// TODO(https://crbug.com/333943): FTP support is being phased out.
+const base::Feature kEnableFtp{"EnableFtpSupport",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if !defined(OS_ANDROID)
+// Upload enterprise cloud reporting without the extension.
+const base::Feature kEnterpriseReportingInBrowser{
+ "EnterpriseReportingInBrowser", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_CHROMEOS)
+// Enables event-based status reporting for child accounts in Chrome OS.
+const base::Feature kEventBasedStatusReporting{
+ "EventBasedStatusReporting", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// If enabled, this feature's |kExternalInstallDefaultButtonKey| field trial
+// parameter value controls which |ExternalInstallBubbleAlert| button is the
+// default.
+const base::Feature kExternalExtensionDefaultButtonControl{
+ "ExternalExtensionDefaultButtonControl", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables Focus Mode which brings up a PWA-like window look.
+const base::Feature kFocusMode{"FocusMode", base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if BUILDFLAG(ENABLE_VR)
+
+#if BUILDFLAG(ENABLE_OCULUS_VR)
+// Controls Oculus support.
+const base::Feature kOculusVR{"OculusVR", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // ENABLE_OCULUS_VR
+
+#if BUILDFLAG(ENABLE_OPENVR)
+// Controls OpenVR support.
+const base::Feature kOpenVR{"OpenVR", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // ENABLE_OPENVR
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+// Controls Windows Mixed Reality support.
+const base::Feature kWindowsMixedReality{"WindowsMixedReality",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // ENABLE_WINDOWS_MR
+
+#if BUILDFLAG(ENABLE_OPENXR)
+// Controls OpenXR support.
+const base::Feature kOpenXR{"OpenXR", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // ENABLE_OPENXR
+
+#endif // BUILDFLAG(ENABLE_VR)
+
+#if defined(OS_WIN)
+// Enables using GDI to print text as simply text.
+const base::Feature kGdiTextPrinting{"GdiTextPrinting",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Controls whether the GeoLanguage system is enabled. GeoLanguage uses IP-based
+// coarse geolocation to provide an estimate (for use by other Chrome features
+// such as Translate) of the local/regional language(s) corresponding to the
+// device's location. If this feature is disabled, the GeoLanguage provider is
+// not initialized at startup, and clients calling it will receive an empty list
+// of languages.
+const base::Feature kGeoLanguage{"GeoLanguage",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if !defined(OS_ANDROID)
+const base::Feature kGoogleBrandedContextMenu{
+ "GoogleBrandedContextMenu", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+// Enables or disables the Happiness Tracking System for the device.
+const base::Feature kHappinessTrackingSystem{"HappinessTrackingSystem",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+#if !defined(OS_ANDROID)
+// Enables or disables the Happiness Tracking System for Desktop Chrome.
+const base::Feature kHappinessTrackingSurveysForDesktop{
+ "HappinessTrackingSurveysForDesktop", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables the Happiness Tracking System demo mode for Desktop
+// Chrome.
+const base::Feature kHappinessTrackingSurveysForDesktopDemo{
+ "HappinessTrackingSurveysForDesktopDemo",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // !defined(OS_ANDROID)
+
+// Enables committed error pages instead of transient navigation entries for
+// HTTP auth interstitial pages (i.e. HTTP auth prompts initiated cross-origin).
+const base::Feature kHTTPAuthCommittedInterstitials{
+ "HTTPAuthCommittedInterstitials", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables navigation suggestions UI for lookalike URLs (e.g. internationalized
+// domain names that are visually similar to popular domains or to domains with
+// engagement score, such as googlé.com).
+const base::Feature kLookalikeUrlNavigationSuggestionsUI{
+ "LookalikeUrlNavigationSuggestionsUI", base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_WIN)
+// A feature that controls whether Chrome warns about incompatible applications.
+// This feature requires Windows 10 or higher to work because it depends on
+// the "Apps & Features" system settings.
+const base::Feature kIncompatibleApplicationsWarning{
+ "IncompatibleApplicationsWarning", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_CHROMEOS)
+// Enables scraping of password-expiry information during SAML login flow, which
+// can lead to an in-session flow for changing SAML password if it has expired.
+// This is safe to enable by default since it does not cause the password-expiry
+// information to be stored, or any user-visible change - in order for anything
+// to happen, the domain administrator has to intentionally send this extra
+// info in the SAML response, and enable the InSessionPasswordChange policy.
+// So, this feature is just for disabling the scraping code if it causes
+// any unforeseen issues.
+const base::Feature kInSessionPasswordChange{"InSessionPasswordChange",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_ANDROID)
+// Enables or disables the installable ambient badge infobar.
+const base::Feature kInstallableAmbientBadgeInfoBar{
+ "InstallableAmbientBadgeInfoBar", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if !defined(OS_ANDROID)
+// Enables or disables intent picker.
+const base::Feature kIntentPicker{"IntentPicker",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+const base::Feature kKernelnextVMs{"KernelnextVMs",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Uses KidsManagement UrlClassification instead of SafeSearch for supervised
+// accounts.
+const base::Feature kKidsManagementUrlClassification{
+ "KidsManagementUrlClassification", base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_MACOSX)
+// Uses NSFullSizeContentViewWindowMask where available instead of adding our
+// own views to the window frame. This is a temporary kill switch, it can be
+// removed once we feel okay about leaving it on.
+const base::Feature kMacFullSizeContentView{"MacFullSizeContentView",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+#endif
+
+#if defined(OS_MACOSX)
+// Enables the Material Design download shelf on Mac.
+const base::Feature kMacMaterialDesignDownloadShelf{
+ "MacMDDownloadShelf", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_MACOSX)
+// In case a website is trying to use the camera/microphone, but Chrome itself
+// is blocked on the system level to access these, show an icon in the Omnibox,
+// which, when clicked, displays a bubble with information on how to toggle
+// Chrome's system-level media permissions.
+const base::Feature kMacSystemMediaPermissionsInfoUi{
+ "MacSystemMediaPermissionsInfoUI", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable screen capture system permission check on Mac 10.15+.
+const base::Feature kMacSystemScreenCapturePermissionCheck{
+ "MacSystemScreenCapturePermissionCheck", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+// Sets whether dismissing the new-tab-page override bubble counts as
+// acknowledgement.
+const base::Feature kAcknowledgeNtpOverrideOnDeactivate{
+ "AcknowledgeNtpOverrideOnDeactivate", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Enables showing an entry for mixed content in site settings, which controls
+// allowing blockable mixed content. When enabled, the mixed content shield is
+// not shown on the omnibox, since its functionality is replaced by the
+// setting.
+const base::Feature kMixedContentSiteSetting{"MixedContentSiteSetting",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if !defined(OS_ANDROID)
+const base::Feature kOnConnectNative{"OnConnectNative",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Enables the use of native notification centers instead of using the Message
+// Center for displaying the toasts. The feature is hardcoded to enabled for
+// Chrome OS.
+#if BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS) && !defined(OS_CHROMEOS)
+const base::Feature kNativeNotifications{"NativeNotifications",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
+
+// When kNoReferrers is enabled, most HTTP requests will provide empty
+// referrers instead of their ordinary behavior.
+const base::Feature kNoReferrers{"NoReferrers",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if defined(OS_WIN)
+// Changes behavior of requireInteraction for notifications. Instead of staying
+// on-screen until dismissed, they are instead shown for a very long time.
+const base::Feature kNotificationDurationLongForRequireInteraction{
+ "NotificationDurationLongForRequireInteraction",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // OS_WIN
+
+#if defined(OS_POSIX)
+// Enables NTLMv2, which implicitly disables NTLMv1.
+const base::Feature kNtlmV2Enabled{"NtlmV2Enabled",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_ANDROID)
+// Enables or disabled the OOM intervention.
+const base::Feature kOomIntervention{"OomIntervention",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// Adds the base language code to the Language-Accept headers if at least one
+// corresponding language+region code is present in the user preferences.
+// For example: "en-US, fr-FR" --> "en-US, en, fr-FR, fr".
+const base::Feature kUseNewAcceptLanguageHeader{
+ "UseNewAcceptLanguageHeader", base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_CHROMEOS)
+// Enables usage of Parent Access Code to authorize certain actions on child
+// user device.
+const base::Feature kParentAccessCode{"ParentAccessCode",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kParentAccessCodeForTimeChange{
+ "ParentAccessCodeForTimeChange", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// Delegate permissions to cross-origin iframes when the feature has been
+// allowed by feature policy.
+const base::Feature kPermissionDelegation{"PermissionDelegation",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Allows prediction operations (e.g., prefetching) on all connection types.
+const base::Feature kPredictivePrefetchingAllowedOnAllConnectionTypes{
+ "PredictivePrefetchingAllowedOnAllConnectionTypes",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Allows Chrome to do preconnect when prerender fails.
+const base::Feature kPrerenderFallbackToPreconnect{
+ "PrerenderFallbackToPreconnect", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Whether to display redesign of the chrome privacy settings page
+// to the user.
+const base::Feature kPrivacySettingsRedesign{"PrivacySettingsRedesign",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// Show Flash deprecation warning to users who have manually enabled Flash.
+// https://crbug.com/918428
+const base::Feature kFlashDeprecationWarning{"FlashDeprecationWarning",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+// If enabled, Print Preview will use the CloudPrinterHandler instead of the
+// cloud print interface to communicate with the cloud print server. This
+// prevents Print Preview from making direct network requests. See
+// https://crbug.com/829414.
+const base::Feature kCloudPrinterHandler{"CloudPrinterHandler",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// If enabled, the Print Preview UI will use a different layout. See
+// https://crbug.com/945619
+const base::Feature kNewPrintPreviewLayout{"NewPrintPreviewLayout",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// Enables or disables push subscriptions keeping Chrome running in the
+// background when closed.
+const base::Feature kPushMessagingBackgroundMode{
+ "PushMessagingBackgroundMode", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables using quiet prompts for notification permission requests.
+const base::Feature kQuietNotificationPrompts{
+ "QuietNotificationPrompts", base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if defined(OS_CHROMEOS)
+// Enables permanent removal of Legacy Supervised Users on startup.
+const base::Feature kRemoveSupervisedUsersOnStartup{
+ "RemoveSupervisedUsersOnStartup", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Controls whether the user is prompted when sites request attestation.
+const base::Feature kSecurityKeyAttestationPrompt{
+ "SecurityKeyAttestationPrompt", base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_ANDROID)
+const base::Feature kShowTrustedPublisherURL{"ShowTrustedPublisherURL",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// Alternative to switches::kSitePerProcess, for turning on full site isolation.
+// Launch bug: https://crbug.com/810843. This is a //chrome-layer feature to
+// avoid turning on site-per-process by default for *all* //content embedders
+// (e.g. this approach lets ChromeCast avoid site-per-process mode).
+//
+// TODO(alexmos): Move this and the other site isolation features below to
+// browser_features, as they are only used on the browser side.
+const base::Feature kSitePerProcess {
+ "site-per-process",
+#if defined(OS_ANDROID)
+ base::FEATURE_DISABLED_BY_DEFAULT
+#else
+ base::FEATURE_ENABLED_BY_DEFAULT
+#endif
+};
+
+// Controls a mode for dynamically process-isolating sites where the user has
+// entered a password. This is intended to be used primarily when full site
+// isolation is turned off. To check whether this mode is enabled, use
+// SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled() rather than
+// checking the feature directly, since that decision is influenced by other
+// factors as well.
+const base::Feature kSiteIsolationForPasswordSites{
+ "site-isolation-for-password-sites",
+// Enabled by default on Android; see https://crbug.com/849815. Note that this
+// should not affect Android Webview, which does not include this code.
+#if defined(OS_ANDROID)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
+
+// kSitePerProcessOnlyForHighMemoryClients is checked before kSitePerProcess,
+// and (if enabled) can restrict if kSitePerProcess feature is checked at all -
+// no check will be made on devices with low memory (these devices will have no
+// Site Isolation via kSitePerProcess trials and won't activate either the
+// control or the experiment group). The threshold for what is considered a
+// "low memory" device is set (in MB) via a field trial param with the name
+// defined below ("site-per-process-low-memory-cutoff-mb") and compared against
+// base::SysInfo::AmountOfPhysicalMemoryMB().
+const base::Feature kSitePerProcessOnlyForHighMemoryClients{
+ "site-per-process-only-for-high-memory-clients",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+const char kSitePerProcessOnlyForHighMemoryClientsParamName[] =
+ "site-per-process-low-memory-cutoff-mb";
+
+#if defined(OS_CHROMEOS)
+// Enables or disables automatic setup of USB printers.
+const base::Feature kStreamlinedUsbPrinterSetup{
+ "StreamlinedUsbPrinterSetup", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables the ability to add a Samba Share to the Files app
+const base::Feature kNativeSmb{"NativeSmb", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // defined(OS_CHROMEOS)
+
+// Enables or disables the ability to use the sound content setting to mute a
+// website.
+const base::Feature kSoundContentSetting{"SoundContentSetting",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables filtering URLs by suffix to include subresources that look
+// like image resources for compression. For example,
+// http://chromium.org/image.jpg would be included.
+const base::Feature kSubresourceRedirectIncludedMediaSuffixes{
+ "SubresourceRedirectIncludedMediaSuffixes",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if !defined(OS_ANDROID)
+// Enables or disables the Javascript API to propagate sync encryption keys.
+const base::Feature kSyncEncryptionKeysWebApi{
+ "SyncEncryptionKeysWebApi", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+// Enables or disables chrome://sys-internals.
+const base::Feature kSysInternals{"SysInternals",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Enables or disables the System Web App manager.
+const base::Feature kSystemWebApps{"SystemWebApps",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables the App Management UI.
+const base::Feature kAppManagement{"AppManagement",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Disable downloads of unsafe file types over insecure transports if initiated
+// from a secure page
+const base::Feature kTreatUnsafeDownloadsAsActive{
+ "TreatUnsafeDownloadsAsActive", base::FEATURE_DISABLED_BY_DEFAULT};
+const char kTreatUnsafeDownloadsAsActiveParamName[] = "ExtensionList";
+
+// Enables or disables the intervention that unloads ad iframes with intensive
+// resource usage.
+const base::Feature kHeavyAdIntervention{"HeavyAdIntervention",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables the blocklist for the heavy ad intervention. This
+// throttles the amount of interventions that can occur on a given host in a
+// time period. This is separate from the intervention feature so it does not
+// interfere with field trial activation, as this blocklist is created for every
+// user.
+const base::Feature kHeavyAdBlocklist{"HeavyAdBlocklist",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+#if defined(OS_ANDROID)
+const base::Feature kUseDisplayWideColorGamut{"UseDisplayWideColorGamut",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+bool UseDisplayWideColorGamut() {
+ auto compute_use_display_wide_color_gamut = []() {
+ // Enabled this feature for devices listed in "enabled_models" field trial
+ // param. This is a comma separated list.
+ std::string enabled_models_list = base::GetFieldTrialParamValueByFeature(
+ kUseDisplayWideColorGamut, "enabled_models");
+ if (enabled_models_list.empty())
+ return false;
+
+ const char* current_model =
+ base::android::BuildInfo::GetInstance()->model();
+ std::vector<std::string> enabled_models =
+ base::SplitString(enabled_models_list, ",", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ for (const std::string& model : enabled_models) {
+ if (model == current_model)
+ return true;
+ }
+
+ return false;
+ };
+
+ // As it takes some work to compute this, cache the result.
+ static base::NoDestructor<bool> is_wide_color_gamut_enabled(
+ compute_use_display_wide_color_gamut());
+ return *is_wide_color_gamut_enabled;
+}
+#endif
+
+#if defined(OS_CHROMEOS)
+// Enables or disables the FTL signaling service for CRD sessions in Kiosk mode.
+const base::Feature kUseFtlSignalingForCrdHostDelegate{
+ "UseFtlSignalingForCrdHostDelegate", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_CHROMEOS)
+// Enables or disables logging for adaptive screen brightness on Chrome OS.
+const base::Feature kAdaptiveScreenBrightnessLogging{
+ "AdaptiveScreenBrightnessLogging", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables user activity event logging for power management on
+// Chrome OS.
+const base::Feature kUserActivityEventLogging{"UserActivityEventLogging",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_CHROMEOS)
+// Enables support of libcups APIs from ARC
+const base::Feature kArcCupsApi{"ArcCupsApi",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables pin quick unlock.
+// TODO(https://crbug.com/935613): Remove this & the backing code.
+const base::Feature kQuickUnlockPin{"QuickUnlockPin",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables pin on the login screen.
+const base::Feature kQuickUnlockPinSignin{"QuickUnlockPinSignin",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables fingerprint quick unlock.
+const base::Feature kQuickUnlockFingerprint{"QuickUnlockFingerprint",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables or disables flash component updates on Chrome OS.
+const base::Feature kCrosCompUpdates{"CrosCompUpdates",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables TPM firmware update capability on Chrome OS.
+const base::Feature kTPMFirmwareUpdate{"TPMFirmwareUpdate",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables "usm" service in the list of user services returned by
+// userInfo Gaia message.
+const base::Feature kCrOSEnableUSMUserService{"CrOSEnableUSMUserService",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables or disables SmartDim on Chrome OS.
+const base::Feature kSmartDim{"SmartDim", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable USBGuard at the lockscreen on Chrome OS.
+// TODO(crbug.com/874630): Remove this kill-switch
+const base::Feature kUsbguard{"USBGuard", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable USB Bouncer for managing a device whitelist for USBGuard on Chrome OS.
+const base::Feature kUsbbouncer{"USBBouncer",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enable support for multiple scheduler configurations.
+const base::Feature kSchedulerConfiguration{"SchedulerConfiguration",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#endif // defined(OS_CHROMEOS)
+
+#if !defined(OS_ANDROID)
+// Allow capturing of WebRTC event logs, and uploading of those logs to Crash.
+// Please note that a Chrome policy must also be set, for this to have effect.
+// Effectively, this is a kill-switch for the feature.
+// TODO(crbug.com/775415): Remove this kill-switch.
+const base::Feature kWebRtcRemoteEventLog{"WebRtcRemoteEventLog",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+// Compress remote-bound WebRTC event logs (if used; see kWebRtcRemoteEventLog).
+const base::Feature kWebRtcRemoteEventLogGzipped{
+ "WebRtcRemoteEventLogGzipped", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enable WebUI accessibility enhancements for review and testing.
+const base::Feature kWebUIA11yEnhancements{"WebUIA11yEnhancements",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+// Whether to enable "dark mode" enhancements in Mac Mojave or Windows 10 for
+// UIs implemented with web technologies.
+const base::Feature kWebUIDarkMode {
+ "WebUIDarkMode",
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_ANDROID)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_ANDROID)
+};
+
+#if defined(OS_WIN)
+// Enables the accelerated default browser flow for Windows 10.
+const base::Feature kWin10AcceleratedDefaultBrowserFlow{
+ "Win10AcceleratedDefaultBrowserFlow", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // defined(OS_WIN)
+
+// Enables writing basic system profile to the persistent histograms files
+// earlier.
+const base::Feature kWriteBasicSystemProfileToPersistentHistogramsFile{
+ "WriteBasicSystemProfileToPersistentHistogramsFile",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Enables improvements to the chrome://accessibility page.
+const base::Feature kAccessibilityInternalsPageImprovements{
+ "AccessibilityInternalsPageImprovements",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+} // namespace features
diff --git a/chromium/chrome/common/chrome_features.h b/chromium/chrome/common/chrome_features.h
new file mode 100644
index 00000000000..6a3e6c22860
--- /dev/null
+++ b/chromium/chrome/common/chrome_features.h
@@ -0,0 +1,552 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines all the public base::FeatureList features for the chrome
+// module.
+
+#ifndef CHROME_COMMON_CHROME_FEATURES_H_
+#define CHROME_COMMON_CHROME_FEATURES_H_
+
+#include "base/component_export.h"
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "build/build_config.h"
+#include "build/buildflag.h"
+#include "chrome/common/buildflags.h"
+#include "device/vr/buildflags/buildflags.h"
+#include "extensions/buildflags/buildflags.h"
+#include "net/net_buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "printing/buildflags/buildflags.h"
+#include "ui/base/buildflags.h"
+
+namespace features {
+
+// All features in alphabetical order. The features should be documented
+// alongside the definition of their values in the .cc file.
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAddToHomescreenMessaging;
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kApkWebAppInstalls;
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppShimMultiProfile;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppShimRemoteCocoa;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kShow10_9ObsoleteInfobar;
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kViewsTaskManager;
+#endif // defined(OS_MACOSX)
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppNotificationStatusMessaging;
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppServiceAsh;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppServiceIntentHandling;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppServiceShelf;
+#endif // !defined(OS_ANDROID)
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kAsyncDns;
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAutoFetchOnNetErrorPage;
+#endif
+
+#if defined(OS_WIN) || defined(OS_LINUX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kBackgroundModeAllowRestart;
+#endif // defined(OS_WIN) || defined(OS_LINUX)
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kBlockPromptsIfDismissedOften;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kBlockPromptsIfIgnoredOften;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kBlockRepeatedNotificationPermissionPrompts;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kBrowserHangFixesExperiment;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kBundledConnectionHelpFeature;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCaptionSettings;
+
+#if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCertDualVerificationTrialFeature;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kChangePictureVideoMode;
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDMServerOAuthForChildUser;
+#endif
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kClearOldBrowsingData;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kClickToOpenPDFPlaceholder;
+
+#if defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kImmersiveFullscreen;
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAllowDisableMouseAcceleration;
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kCrostini;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCrostiniAdditionalEnterpriseReporting;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCrostiniAdvancedAccessControls;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCrostiniAnsibleInfrastructure;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCrostiniAnsibleSoftwareManagement;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCrostiniForceClose;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCupsPrintersUiOverhaul;
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kPluginVm;
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kWilcoDtc;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kUploadZippedSystemLogs;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kTerminalSystemApp;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopCaptureTabSharingInfobar;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsWithoutExtensions;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsCacheDuringDefaultInstall;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsLocalUpdating;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsUnifiedUiController;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsUnifiedLaunch;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsUSS;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsOmniboxInstall;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDisallowUnsafeHttpDownloads;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const char kDisallowUnsafeHttpDownloadsParamName[];
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDnsOverHttps;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::FeatureParam<bool> kDnsOverHttpsFallbackParam;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::FeatureParam<std::string> kDnsOverHttpsTemplatesParam;
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDownloadsLocationChange;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDriveFcmInvalidations;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPolicyFcmInvalidations;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEnableAmbientAuthenticationInGuestSession;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEnableAmbientAuthenticationInIncognito;
+
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEnableFtp;
+#endif
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEnterpriseReportingInBrowser;
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEventBasedStatusReporting;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kExternalExtensionDefaultButtonControl;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kFocusMode;
+
+// Android expects this string from Java code, so it is always needed.
+// TODO(crbug.com/731802): Use #if BUILDFLAG(ENABLE_VR_BROWSING) instead.
+#if BUILDFLAG(ENABLE_VR) || defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kVrBrowsing;
+#endif
+#if BUILDFLAG(ENABLE_VR)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kVrBrowsingExperimentalFeatures;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kVrBrowsingExperimentalRendering;
+
+#if BUILDFLAG(ENABLE_OCULUS_VR)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kOculusVR;
+#endif // ENABLE_OCULUS_VR
+
+#if BUILDFLAG(ENABLE_OPENVR)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kOpenVR;
+#endif // ENABLE_OPENVR
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kWindowsMixedReality;
+#endif // ENABLE_WINDOWS_MR
+
+#if BUILDFLAG(ENABLE_OPENXR)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kOpenXR;
+#endif // ENABLE_OPENXR
+
+#endif // ENABLE_VR
+
+#if defined(OS_WIN)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kGdiTextPrinting;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kGeoLanguage;
+
+#if !defined(OS_ANDROID)
+// Only has an effect in branded builds.
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kGoogleBrandedContextMenu;
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHappinessTrackingSystem;
+#endif
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHappinessTrackingSurveysForDesktop;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHappinessTrackingSurveysForDesktopDemo;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHTTPAuthCommittedInterstitials;
+
+#if defined(OS_WIN)
+// Only has an effect in branded builds.
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kIncompatibleApplicationsWarning;
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kInSessionPasswordChange;
+#endif
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kInstallableAmbientBadgeInfoBar;
+#endif // defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kIntentPicker;
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kKernelnextVMs;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kKidsManagementUrlClassification;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kLookalikeUrlNavigationSuggestionsUI;
+
+#if defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kMacFullSizeContentView;
+#endif
+
+#if defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kMacMaterialDesignDownloadShelf;
+#endif
+
+#if defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kMacSystemMediaPermissionsInfoUi;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kMacSystemScreenCapturePermissionCheck;
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAcknowledgeNtpOverrideOnDeactivate;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kMixedContentSiteSetting;
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kOnConnectNative;
+#endif
+
+#if BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kNativeNotifications;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kNoReferrers;
+
+#if defined(OS_WIN)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kNotificationDurationLongForRequireInteraction;
+#endif
+
+#if defined(OS_POSIX)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kNtlmV2Enabled;
+#endif
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kOomIntervention;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kUseNewAcceptLanguageHeader;
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kParentAccessCode;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kParentAccessCodeForTimeChange;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPermissionDelegation;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPredictivePrefetchingAllowedOnAllConnectionTypes;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPrerenderFallbackToPreconnect;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPrivacySettingsRedesign;
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kFlashDeprecationWarning;
+#endif
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCloudPrinterHandler;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kNewPrintPreviewLayout;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPushMessagingBackgroundMode;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kQuietNotificationPrompts;
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kRemoveSupervisedUsersOnStartup;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSecurityKeyAttestationPrompt;
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kShowTrustedPublisherURL;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kSitePerProcess;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSiteIsolationForPasswordSites;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSitePerProcessOnlyForHighMemoryClients;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const char kSitePerProcessOnlyForHighMemoryClientsParamName[];
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kStreamlinedUsbPrinterSetup;
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kNativeSmb;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSoundContentSetting;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSubresourceRedirectIncludedMediaSuffixes;
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSyncEncryptionKeysWebApi;
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSysInternals;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSystemWebApps;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAppManagement;
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kTabMetricsLogging;
+#endif
+
+#if defined(OS_WIN)
+// Only has an effect in branded builds.
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kThirdPartyModulesBlocking;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kTLS13HardeningForLocalAnchors;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kTreatUnsafeDownloadsAsActive;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const char kTreatUnsafeDownloadsAsActiveParamName[];
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHeavyAdIntervention;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHeavyAdBlocklist;
+
+#if defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kUseDisplayWideColorGamut;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+bool UseDisplayWideColorGamut();
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kUseFtlSignalingForCrdHostDelegate;
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAdaptiveScreenBrightnessLogging;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kUserActivityEventLogging;
+
+#endif
+
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kArcCupsApi;
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kQuickUnlockPin;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kQuickUnlockPinSignin;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kQuickUnlockFingerprint;
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kCrosCompUpdates;
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kTPMFirmwareUpdate;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCrOSEnableUSMUserService;
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kSmartDim;
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kUsbguard;
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kUsbbouncer;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSchedulerConfiguration;
+#endif // defined(OS_CHROMEOS)
+
+#if !defined(OS_ANDROID)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kWebRtcRemoteEventLog;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kWebRtcRemoteEventLogGzipped;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kWebUIA11yEnhancements;
+#endif
+
+COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kWebUIDarkMode;
+
+#if defined(OS_WIN)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kWin10AcceleratedDefaultBrowserFlow;
+#endif // defined(OS_WIN)
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kWriteBasicSystemProfileToPersistentHistogramsFile;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kAccessibilityInternalsPageImprovements;
+
+bool PrefServiceEnabled();
+
+// DON'T ADD RANDOM STUFF HERE. Put it in the main section above in
+// alphabetical order, or in one of the ifdefs (also in order in each section).
+
+} // namespace features
+
+#endif // CHROME_COMMON_CHROME_FEATURES_H_
diff --git a/chromium/chrome/common/chrome_icon_resources_win.h b/chromium/chrome/common/chrome_icon_resources_win.h
new file mode 100644
index 00000000000..9400c0b27ae
--- /dev/null
+++ b/chromium/chrome/common/chrome_icon_resources_win.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHROME_ICON_RESOURCES_WIN_H_
+#define CHROME_COMMON_CHROME_ICON_RESOURCES_WIN_H_
+
+namespace icon_resources {
+
+// This file contains the indices of icon resources in chrome_exe.rc.
+
+enum {
+ // The main application icon is always index 0.
+ kApplicationIndex = 0,
+
+#if defined(GOOGLE_CHROME_BUILD)
+ // Legacy indices that are no longer used.
+ kApplication2Index = 1,
+ kApplication3Index = 2,
+ kApplication4Index = 3,
+
+ // The Chrome Canary application icon.
+ kSxSApplicationIndex = 4,
+
+ // The Chrome App Launcher icon.
+ kAppLauncherIndex = 5,
+
+ // The Chrome App Launcher Canary icon.
+ kSxSAppLauncherIndex = 6,
+
+ // The Chrome incognito icon.
+ kIncognitoIndex = 7,
+
+ // The Chrome Dev application icon.
+ kDevApplicationIndex = 8,
+
+ // The Chrome Beta application icon.
+ kBetaApplicationIndex = 9,
+#else // defined(GOOGLE_CHROME_BUILD)
+ // The Chromium App Launcher icon.
+ kAppLauncherIndex = 1,
+
+ // The Chromium incognito icon.
+ kIncognitoIndex = 2,
+#endif // defined(GOOGLE_CHROME_BUILD)
+};
+
+} // namespace icon_resources
+
+#endif // CHROME_COMMON_CHROME_ICON_RESOURCES_WIN_H_
diff --git a/chromium/chrome/common/chrome_isolated_world_ids.h b/chromium/chrome/common/chrome_isolated_world_ids.h
new file mode 100644
index 00000000000..abc94221e4e
--- /dev/null
+++ b/chromium/chrome/common/chrome_isolated_world_ids.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHROME_ISOLATED_WORLD_IDS_H_
+#define CHROME_COMMON_CHROME_ISOLATED_WORLD_IDS_H_
+
+#include "build/build_config.h"
+#include "content/public/common/isolated_world_ids.h"
+
+enum ChromeIsolatedWorldIDs {
+ // Isolated world ID for Chrome Translate.
+ ISOLATED_WORLD_ID_TRANSLATE = content::ISOLATED_WORLD_ID_CONTENT_END + 1,
+
+ // Isolated world ID for internal Chrome features.
+ ISOLATED_WORLD_ID_CHROME_INTERNAL,
+
+#if defined(OS_MACOSX)
+ // Isolated world ID for AppleScript.
+ ISOLATED_WORLD_ID_APPLESCRIPT,
+#endif // defined(OS_MACOSX)
+
+ // Numbers for isolated worlds for extensions are set in
+ // extensions/renderer/script_injection.cc, and are are greater than or equal
+ // to this number.
+ ISOLATED_WORLD_ID_EXTENSIONS
+};
+
+#endif // CHROME_COMMON_CHROME_ISOLATED_WORLD_IDS_H_
diff --git a/chromium/chrome/common/chrome_result_codes.h b/chromium/chrome/common/chrome_result_codes.h
new file mode 100644
index 00000000000..43d4a985fb7
--- /dev/null
+++ b/chromium/chrome/common/chrome_result_codes.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHROME_RESULT_CODES_H_
+#define CHROME_COMMON_CHROME_RESULT_CODES_H_
+
+#include "content/public/common/result_codes.h"
+
+namespace chrome {
+
+// IMPORTANT: This needs to stay in sync with <enum name="CrashExitCodes"> and
+// <enum name="WindowsExitCode"> in tools/metrics/histograms/enums.xml. So do
+// not remove any entries, and always append entries to the bottom just above
+// RESULT_CODE_CHROME_LAST_CODE.
+
+enum ResultCode {
+ RESULT_CODE_CHROME_START = content::RESULT_CODE_LAST_CODE,
+
+ // An invalid command line url was given.
+ RESULT_CODE_INVALID_CMDLINE_URL = RESULT_CODE_CHROME_START,
+
+ // The process is of an unknown type.
+ RESULT_CODE_BAD_PROCESS_TYPE,
+
+ // A critical chrome file is missing.
+ RESULT_CODE_MISSING_DATA,
+
+ // Failed to make Chrome default browser (not used?).
+ RESULT_CODE_SHELL_INTEGRATION_FAILED,
+
+ // Machine level install exists
+ RESULT_CODE_MACHINE_LEVEL_INSTALL_EXISTS,
+
+ // Uninstall detected another chrome instance.
+ RESULT_CODE_UNINSTALL_CHROME_ALIVE,
+
+ // The user changed their mind.
+ RESULT_CODE_UNINSTALL_USER_CANCEL,
+
+ // Delete profile as well during uninstall.
+ RESULT_CODE_UNINSTALL_DELETE_PROFILE,
+
+ // Command line parameter is not supported.
+ RESULT_CODE_UNSUPPORTED_PARAM,
+
+ // Browser import hung and was killed.
+ RESULT_CODE_IMPORTER_HUNG,
+
+ // Trying to restart the browser we crashed.
+ RESULT_CODE_RESPAWN_FAILED,
+
+ // The EXP1, EXP2, EXP3, EXP4 are generic codes used to communicate some
+ // simple outcome back to the process that launched us. This is used for
+ // experiments and the actual meaning depends on the experiment.
+ // (only EXP2 is used?)
+ RESULT_CODE_NORMAL_EXIT_EXP1,
+ RESULT_CODE_NORMAL_EXIT_EXP2,
+ RESULT_CODE_NORMAL_EXIT_EXP3,
+ RESULT_CODE_NORMAL_EXIT_EXP4,
+
+ // For experiments this return code means that the user canceled causes the
+ // did_run "dr" signal to be reset soi this chrome run does not count as
+ // active chrome usage.
+ RESULT_CODE_NORMAL_EXIT_CANCEL,
+
+ // The profile was in use on another host.
+ RESULT_CODE_PROFILE_IN_USE,
+
+ // Failed to pack an extension via the cmd line.
+ RESULT_CODE_PACK_EXTENSION_ERROR,
+
+ // Failed to silently uninstall an extension.
+ RESULT_CODE_UNINSTALL_EXTENSION_ERROR,
+
+ // The browser process exited early by passing the command line to another
+ // running browser.
+ RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED,
+
+ // A dummy value we should not use. See crbug.com/152285.
+ RESULT_CODE_NOTUSED_1,
+
+ // Failed to install an item from the webstore when the
+ // kInstallEphemeralAppFromWebstore command line flag was present.
+ // As this flag is no longer supported, this return code should never be
+ // returned.
+ RESULT_CODE_INSTALL_FROM_WEBSTORE_ERROR_2,
+
+ // A dummy value we should not use. See crbug.com/152285.
+ RESULT_CODE_NOTUSED_2,
+
+ // Returned when the user has not yet accepted the EULA.
+ RESULT_CODE_EULA_REFUSED,
+
+ // Failed to migrate user data directory for side-by-side package support
+ // (Linux-only).
+ RESULT_CODE_SXS_MIGRATION_FAILED_NOT_USED,
+
+ // The action is not allowed by a policy.
+ RESULT_CODE_ACTION_DISALLOWED_BY_POLICY,
+
+ // An browser process was sandboxed. This should never happen.
+ RESULT_CODE_INVALID_SANDBOX_STATE,
+
+ // Cloud policy enrollment is failed or given up by user.
+ RESULT_CODE_CLOUD_POLICY_ENROLLMENT_FAILED,
+
+ // Chrome was downgraded since the last launch. Perform downgrade processing
+ // and relaunch.
+ RESULT_CODE_DOWNGRADE_AND_RELAUNCH,
+
+ // Last return code (keep this last).
+ RESULT_CODE_CHROME_LAST_CODE
+};
+
+static_assert(RESULT_CODE_CHROME_LAST_CODE == 34,
+ "Please make sure the enum values are in sync with enums.xml");
+
+} // namespace chrome
+
+#endif // CHROME_COMMON_CHROME_RESULT_CODES_H_
diff --git a/chromium/chrome/common/chrome_utility_printing_messages.h b/chromium/chrome/common/chrome_utility_printing_messages.h
new file mode 100644
index 00000000000..6bd558079c9
--- /dev/null
+++ b/chromium/chrome/common/chrome_utility_printing_messages.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CHROME_UTILITY_PRINTING_MESSAGES_H_
+#define CHROME_COMMON_CHROME_UTILITY_PRINTING_MESSAGES_H_
+
+#include <string>
+
+#include "build/build_config.h"
+#include "components/printing/common/printing_param_traits_macros.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_param_traits.h"
+#include "printing/backend/print_backend.h"
+#include "printing/buildflags/buildflags.h"
+
+#define IPC_MESSAGE_START ChromeUtilityPrintingMsgStart
+
+#if defined(OS_WIN) && BUILDFLAG(ENABLE_PRINT_PREVIEW)
+// Preview and Cloud Print messages.
+IPC_STRUCT_TRAITS_BEGIN(printing::PrinterCapsAndDefaults)
+ IPC_STRUCT_TRAITS_MEMBER(printer_capabilities)
+ IPC_STRUCT_TRAITS_MEMBER(caps_mime_type)
+ IPC_STRUCT_TRAITS_MEMBER(printer_defaults)
+ IPC_STRUCT_TRAITS_MEMBER(defaults_mime_type)
+IPC_STRUCT_TRAITS_END()
+
+IPC_ENUM_TRAITS_MAX_VALUE(printing::ColorModel, printing::PROCESSCOLORMODEL_RGB)
+
+IPC_STRUCT_TRAITS_BEGIN(printing::PrinterSemanticCapsAndDefaults::Paper)
+ IPC_STRUCT_TRAITS_MEMBER(display_name)
+ IPC_STRUCT_TRAITS_MEMBER(vendor_id)
+ IPC_STRUCT_TRAITS_MEMBER(size_um)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(printing::PrinterSemanticCapsAndDefaults)
+ IPC_STRUCT_TRAITS_MEMBER(collate_capable)
+ IPC_STRUCT_TRAITS_MEMBER(collate_default)
+ IPC_STRUCT_TRAITS_MEMBER(copies_capable)
+ IPC_STRUCT_TRAITS_MEMBER(duplex_modes)
+ IPC_STRUCT_TRAITS_MEMBER(duplex_default)
+ IPC_STRUCT_TRAITS_MEMBER(color_changeable)
+ IPC_STRUCT_TRAITS_MEMBER(color_default)
+ IPC_STRUCT_TRAITS_MEMBER(color_model)
+ IPC_STRUCT_TRAITS_MEMBER(bw_model)
+ IPC_STRUCT_TRAITS_MEMBER(papers)
+ IPC_STRUCT_TRAITS_MEMBER(default_paper)
+ IPC_STRUCT_TRAITS_MEMBER(dpis)
+ IPC_STRUCT_TRAITS_MEMBER(default_dpi)
+IPC_STRUCT_TRAITS_END()
+
+//------------------------------------------------------------------------------
+// Utility process messages:
+// These are messages from the browser to the utility process.
+
+// Tells the utility process to get capabilities and defaults for the specified
+// printer. Used on Windows to isolate the service process from printer driver
+// crashes by executing this in a separate process. This does not run in a
+// sandbox.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_GetPrinterCapsAndDefaults,
+ std::string /* printer name */)
+
+// Tells the utility process to get capabilities and defaults for the specified
+// printer. Used on Windows to isolate the service process from printer driver
+// crashes by executing this in a separate process. This does not run in a
+// sandbox. Returns result as printing::PrinterSemanticCapsAndDefaults.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_GetPrinterSemanticCapsAndDefaults,
+ std::string /* printer name */)
+
+//------------------------------------------------------------------------------
+// Utility process host messages:
+// These are messages from the utility process to the browser.
+
+// Reply when the utility process has succeeded in obtaining the printer
+// capabilities and defaults.
+IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
+ std::string /* printer name */,
+ printing::PrinterCapsAndDefaults)
+
+// Reply when the utility process has succeeded in obtaining the printer
+// semantic capabilities and defaults.
+IPC_MESSAGE_CONTROL2(
+ ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Succeeded,
+ std::string /* printer name */,
+ printing::PrinterSemanticCapsAndDefaults)
+
+// Reply when the utility process has failed to obtain the printer
+// capabilities and defaults.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed,
+ std::string /* printer name */)
+
+// Reply when the utility process has failed to obtain the printer
+// semantic capabilities and defaults.
+IPC_MESSAGE_CONTROL1(
+ ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Failed,
+ std::string /* printer name */)
+#endif // defined(OS_WIN) && BUILDFLAG(ENABLE_PRINT_PREVIEW)
+
+#endif // CHROME_COMMON_CHROME_UTILITY_PRINTING_MESSAGES_H_
diff --git a/chromium/chrome/common/chrome_version.h.in b/chromium/chrome/common/chrome_version.h.in
new file mode 100644
index 00000000000..54c1b1d12f2
--- /dev/null
+++ b/chromium/chrome/common/chrome_version.h.in
@@ -0,0 +1,20 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// version.h is generated from version.h.in. Edit the source!
+
+
+// Version Information
+
+#define CHROME_VERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+#define CHROME_VERSION_STRING "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+
+// Branding Information
+
+#define COMPANY_FULLNAME_STRING "@COMPANY_FULLNAME@"
+#define COMPANY_SHORTNAME_STRING "@COMPANY_SHORTNAME@"
+#define PRODUCT_FULLNAME_STRING "@PRODUCT_FULLNAME@"
+#define PRODUCT_SHORTNAME_STRING "@PRODUCT_SHORTNAME@"
+#define COPYRIGHT_STRING "@COPYRIGHT@"
+#define OFFICIAL_BUILD_STRING "@OFFICIAL_BUILD@"
diff --git a/chromium/chrome/common/client_hints/OWNERS b/chromium/chrome/common/client_hints/OWNERS
new file mode 100644
index 00000000000..22542699c2f
--- /dev/null
+++ b/chromium/chrome/common/client_hints/OWNERS
@@ -0,0 +1,8 @@
+yoavweiss@chromium.org
+tbansal@chromium.org
+ryansturm@chromium.org
+
+# For IPC security review
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+# COMPONENT: Blink>Loader
diff --git a/chromium/chrome/common/client_hints/client_hints.cc b/chromium/chrome/common/client_hints/client_hints.cc
new file mode 100644
index 00000000000..4c27309acae
--- /dev/null
+++ b/chromium/chrome/common/client_hints/client_hints.cc
@@ -0,0 +1,66 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/client_hints/client_hints.h"
+
+#include "content/public/common/origin_util.h"
+#include "third_party/blink/public/platform/web_client_hints_type.h"
+#include "url/gurl.h"
+
+namespace client_hints {
+
+void GetAllowedClientHintsFromSource(
+ const GURL& url,
+ const ContentSettingsForOneType& client_hints_rules,
+ blink::WebEnabledClientHints* client_hints) {
+ if (client_hints_rules.empty())
+ return;
+
+ if (!content::IsOriginSecure(url))
+ return;
+
+ const GURL& origin = url.GetOrigin();
+
+ for (const auto& rule : client_hints_rules) {
+ // Look for an exact match since persisted client hints are disabled by
+ // default, and enabled only on per-host basis.
+ if (rule.primary_pattern == ContentSettingsPattern::Wildcard() ||
+ !rule.primary_pattern.Matches(origin)) {
+ continue;
+ }
+
+ // Found an exact match.
+ DCHECK(ContentSettingsPattern::Wildcard() == rule.secondary_pattern);
+ DCHECK(rule.setting_value.is_dict());
+ const base::Value* expiration_time =
+ rule.setting_value.FindKey("expiration_time");
+
+ // |expiration_time| may be null in rare cases. See
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=942398.
+ if (expiration_time == nullptr)
+ continue;
+ DCHECK(expiration_time->is_double());
+
+ if (base::Time::Now().ToDoubleT() > expiration_time->GetDouble()) {
+ // The client hint is expired.
+ return;
+ }
+
+ const base::Value* list_value = rule.setting_value.FindKey("client_hints");
+ if (list_value == nullptr)
+ continue;
+ DCHECK(list_value->is_list());
+ base::span<const base::Value> client_hints_list = list_value->GetList();
+ for (const auto& client_hint : client_hints_list) {
+ DCHECK(client_hint.is_int());
+ client_hints->SetIsEnabled(
+ static_cast<blink::mojom::WebClientHintsType>(client_hint.GetInt()),
+ true);
+ }
+ // Match found for |url| and client hints have been set.
+ return;
+ }
+}
+
+} // namespace client_hints
diff --git a/chromium/chrome/common/client_hints/client_hints.h b/chromium/chrome/common/client_hints/client_hints.h
new file mode 100644
index 00000000000..a904642a1bb
--- /dev/null
+++ b/chromium/chrome/common/client_hints/client_hints.h
@@ -0,0 +1,28 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CLIENT_HINTS_CLIENT_HINTS_H_
+#define CHROME_COMMON_CLIENT_HINTS_CLIENT_HINTS_H_
+
+#include "components/content_settings/core/common/content_settings.h"
+
+class GURL;
+
+namespace blink {
+struct WebEnabledClientHints;
+}
+
+namespace client_hints {
+
+// Retrieves the persistent client hints that should be set when fetching a
+// resource from |url|. The method updates |client_hints| with the result.
+// |client_hints_rules| contains the content settings for the client hints.
+void GetAllowedClientHintsFromSource(
+ const GURL& url,
+ const ContentSettingsForOneType& client_hints_rules,
+ blink::WebEnabledClientHints* client_hints);
+
+} // namespace client_hints
+
+#endif // CHROME_COMMON_CLIENT_HINTS_CLIENT_HINTS_H_
diff --git a/chromium/chrome/common/cloud_print/OWNERS b/chromium/chrome/common/cloud_print/OWNERS
new file mode 100644
index 00000000000..26c7dfb98b5
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/OWNERS
@@ -0,0 +1,5 @@
+# This is for the common case of adding or renaming files. If you're doing
+# structural changes, use usual OWNERS rules.
+per-file BUILD.gn=*
+
+# COMPONENT: Services>CloudPrint
diff --git a/chromium/chrome/common/cloud_print/cloud_print_class_mac.h b/chromium/chrome/common/cloud_print/cloud_print_class_mac.h
new file mode 100644
index 00000000000..c2b7e4480d5
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_class_mac.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CLASS_MAC_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CLASS_MAC_H_
+
+#import <AppKit/AppKit.h>
+
+namespace cloud_print {
+
+// Four character constant to identify Cloud print IPC call.
+extern const AEEventClass kAECloudPrintClass;
+
+} // namespace cloud_print
+
+#endif // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CLASS_MAC_H_
diff --git a/chromium/chrome/common/cloud_print/cloud_print_class_mac.mm b/chromium/chrome/common/cloud_print/cloud_print_class_mac.mm
new file mode 100644
index 00000000000..9905e89deb7
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_class_mac.mm
@@ -0,0 +1,11 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/common/cloud_print/cloud_print_class_mac.h"
+
+namespace cloud_print {
+
+const AEEventClass kAECloudPrintClass = 'GCPp';
+
+} // namespace cloud_print
diff --git a/chromium/chrome/common/cloud_print/cloud_print_constants.cc b/chromium/chrome/common/cloud_print/cloud_print_constants.cc
new file mode 100644
index 00000000000..6c9f5d3292e
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_constants.cc
@@ -0,0 +1,79 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/cloud_print/cloud_print_constants.h"
+
+namespace cloud_print {
+
+const char kCloudPrintUserAgent[] = "GoogleCloudPrintProxy";
+const char kChromeCloudPrintProxyHeader[] = "X-CloudPrint-Proxy: Chrome";
+const char kCloudPrintPushNotificationsSource[] = "cloudprint.google.com";
+
+const char kProxyIdValue[] = "proxy";
+const char kPrinterNameValue[] = "printer";
+const char kPrinterDescValue[] = "description";
+const char kPrinterCapsValue[] = "capabilities";
+const char kPrinterDisplayNameValue[] = "default_display_name";
+const char kPrinterDefaultsValue[] = "defaults";
+const char kPrinterStatusValue[] = "status";
+const char kPrinterTagValue[] = "tag";
+const char kPrinterRemoveTagValue[] = "remove_tag";
+const char kPrinterLocalSettingsValue[] = "local_settings";
+const char kMessageTextValue[] = "message";
+const char kUseCDD[] = "use_cdd";
+
+const char kContentTypeJSON[] = "application/json";
+const char kContentTypePDF[] = "application/pdf";
+const char kContentTypeXML[] = "application/xml";
+const char kContentTypeXPS[] = "application/vnd.ms-xpsdocument";
+
+const char kPrintSystemFailedMessageId[] = "printsystemfail";
+const char kGetPrinterCapsFailedMessageId[] = "getprncapsfail";
+const char kEnumPrintersFailedMessageId[] = "enumfail";
+const char kZombiePrinterMessageId[] = "zombieprinter";
+
+const char kSuccessValue[] = "success";
+const char kNameValue[] = "name";
+const char kDisplayNameValue[] = "displayName";
+const char kIdValue[] = "id";
+const char kTicketUrlValue[] = "ticketUrl";
+const char kFileUrlValue[] = "fileUrl";
+const char kPrinterListValue[] = "printers";
+const char kJobListValue[] = "jobs";
+const char kTitleValue[] = "title";
+const char kOwnerValue[] = "ownerId";
+const char kPrinterCapsHashValue[] = "capsHash";
+const char kTagsValue[] = "tags";
+const char kXMPPJidValue[] = "xmpp_jid";
+const char kOAuthCodeValue[] = "authorization_code";
+const char kCreateTimeValue[] = "createTime";
+const char kPrinterTypeValue[] = "type";
+const char kUserValue[] = "request.user";
+const char kUsersValue[] = "request.users";
+const char kLocalSettingsPendingXmppValue[] =
+ "local_settings.pending.xmpp_timeout_value";
+
+const char kNotificationUpdateSettings[] = "/update_settings";
+
+const char kChromeVersionTagName[] = "chrome_version";
+const char kSystemNameTagName[] = "system_name";
+const char kSystemVersionTagName[] = "system_version";
+
+const char kCloudPrintServiceProxyTagPrefix[] = "__cp__";
+const char kCloudPrintServiceTagsHashTagName[] = "__cp__tagshash";
+const char kCloudPrintServiceTagDryRunFlag[] = "__cp__dry_run";
+
+const char kJobFetchReasonStartup[] = "startup";
+const char kJobFetchReasonPoll[] = "poll";
+const char kJobFetchReasonNotified[] = "notified";
+const char kJobFetchReasonQueryMore[] = "querymore";
+const char kJobFetchReasonFailure[] = "failure";
+const char kJobFetchReasonRetry[] = "retry";
+
+const char kCreateLocalSettingsXmppPingFormat[] =
+ "{\"current\":{\"xmpp_timeout_value\": %d}}";
+const char kUpdateLocalSettingsXmppPingFormat[] =
+ "{\"current\":{\"xmpp_timeout_value\": %d},\"pending\":{}}";
+
+} // namespace cloud_print
diff --git a/chromium/chrome/common/cloud_print/cloud_print_constants.h b/chromium/chrome/common/cloud_print/cloud_print_constants.h
new file mode 100644
index 00000000000..b9648347f45
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_constants.h
@@ -0,0 +1,135 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CONSTANTS_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CONSTANTS_H_
+
+
+namespace cloud_print {
+
+// The string to be appended to the user-agent for cloud print requests.
+extern const char kCloudPrintUserAgent[];
+// The proxy header required by cloud print server.
+extern const char kChromeCloudPrintProxyHeader[];
+// The source of cloud print notifications.
+extern const char kCloudPrintPushNotificationsSource[];
+
+// Values used to register or update a printer with the cloud print service.
+extern const char kProxyIdValue[];
+extern const char kPrinterNameValue[];
+extern const char kPrinterDescValue[];
+extern const char kPrinterCapsValue[];
+extern const char kPrinterDisplayNameValue[];
+extern const char kPrinterDefaultsValue[];
+extern const char kPrinterStatusValue[];
+extern const char kPrinterTagValue[];
+extern const char kPrinterRemoveTagValue[];
+extern const char kPrinterLocalSettingsValue[];
+extern const char kMessageTextValue[];
+extern const char kUseCDD[];
+
+extern const char kContentTypeJSON[];
+extern const char kContentTypePDF[];
+extern const char kContentTypeXML[];
+extern const char kContentTypeXPS[];
+
+// Value of "code" parameter in cloud print "/message" requests.
+extern const char kPrintSystemFailedMessageId[];
+extern const char kGetPrinterCapsFailedMessageId[];
+extern const char kEnumPrintersFailedMessageId[];
+extern const char kZombiePrinterMessageId[];
+
+// Values in the respone JSON from the cloud print server.
+extern const char kSuccessValue[];
+extern const char kNameValue[];
+extern const char kDisplayNameValue[];
+extern const char kIdValue[];
+extern const char kTicketUrlValue[];
+extern const char kFileUrlValue[];
+extern const char kPrinterListValue[];
+extern const char kJobListValue[];
+extern const char kTitleValue[];
+extern const char kOwnerValue[];
+extern const char kPrinterCapsHashValue[];
+extern const char kTagsValue[];
+extern const char kXMPPJidValue[];
+extern const char kOAuthCodeValue[];
+extern const char kCreateTimeValue[];
+extern const char kPrinterTypeValue[];
+extern const char kUserValue[];
+extern const char kUsersValue[];
+extern const char kLocalSettingsPendingXmppValue[];
+
+// Value in XMPP notification.
+extern const char kNotificationUpdateSettings[];
+
+// Printer tag names. Don't need prefixes. They will be added on submit.
+extern const char kChromeVersionTagName[];
+extern const char kSystemNameTagName[];
+extern const char kSystemVersionTagName[];
+
+// Tags for cloud print service.
+extern const char kCloudPrintServiceProxyTagPrefix[];
+extern const char kCloudPrintServiceTagsHashTagName[];
+extern const char kCloudPrintServiceTagDryRunFlag[];
+
+// Reasons for fetching print jobs.
+// Job fetch on proxy startup.
+extern const char kJobFetchReasonStartup[];
+// Job fetch because we are polling.
+extern const char kJobFetchReasonPoll[];
+// Job fetch on being notified by the server.
+extern const char kJobFetchReasonNotified[];
+// Job fetch after a successful print to query for more jobs.
+extern const char kJobFetchReasonQueryMore[];
+// Job fetch after a job failure to query for more jobs.
+extern const char kJobFetchReasonFailure[];
+// Job fetch due to scheduled retry.
+extern const char kJobFetchReasonRetry[];
+
+// Format of the local settings containing only XMPP ping.
+extern const char kCreateLocalSettingsXmppPingFormat[];
+extern const char kUpdateLocalSettingsXmppPingFormat[];
+
+// Max retry count for job data fetch requests.
+const int kJobDataMaxRetryCount = 1;
+// Max retry count (infinity) for API fetch requests.
+const int kCloudPrintAPIMaxRetryCount = -1;
+// Max retry count (infinity) for Registration requests.
+const int kCloudPrintRegisterMaxRetryCount = -1;
+// Max retry count (infinity) for authentication requests.
+const int kCloudPrintAuthMaxRetryCount = -1;
+
+// When we don't have XMPP notifications available, we resort to polling for
+// print jobs. We choose a random interval in seconds between these 2 values.
+const int kMinJobPollIntervalSecs = 5*60; // 5 minutes in seconds
+const int kMaxJobPollIntervalSecs = 8*60; // 8 minutes in seconds
+
+// When we have XMPP notifications available, we ping server to keep connection
+// alive or check connection status.
+const int kDefaultXmppPingTimeoutSecs = 5*60;
+const int kMinXmppPingTimeoutSecs = 1*60;
+const int kXmppPingCheckIntervalSecs = 60;
+
+// Number of failed pings before we try to reinstablish XMPP connection.
+const int kMaxFailedXmppPings = 2;
+
+// The number of seconds before the OAuth2 access token is due to expire that
+// we try and refresh it.
+const int kTokenRefreshGracePeriodSecs = 5*60; // 5 minutes in seconds
+
+// The number of retries before we abandon a print job in exponential backoff
+const int kNumRetriesBeforeAbandonJob = 5;
+
+// The wait time for the second (first with wait time) retry for a job that
+// fails due to network errors
+const int kJobFirstWaitTimeSecs = 1;
+
+// The multiplier for the wait time for retrying a job that fails due to
+// network errors
+const int kJobWaitTimeExponentialMultiplier = 2;
+
+} // namespace cloud_print
+
+#endif // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CONSTANTS_H_
diff --git a/chromium/chrome/common/cloud_print/cloud_print_helpers.cc b/chromium/chrome/common/cloud_print/cloud_print_helpers.cc
new file mode 100644
index 00000000000..f6a4aa0bc4f
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_helpers.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/cloud_print/cloud_print_helpers.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "base/hash/md5.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "base/values.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/cloud_print/cloud_print_constants.h"
+#include "net/base/mime_util.h"
+#include "url/gurl.h"
+
+namespace cloud_print {
+
+namespace {
+
+// Returns printer tags generated from |printer_tags| and the default tags
+// required by cloud print server.
+PrinterTags PreparePrinterTags(const PrinterTags& printer_tags) {
+ PrinterTags printer_tags_out = printer_tags;
+ printer_tags_out[kChromeVersionTagName] = chrome::GetVersionString();
+ printer_tags_out[kSystemNameTagName] =
+ base::SysInfo::OperatingSystemName();
+ printer_tags_out[kSystemVersionTagName] =
+ base::SysInfo::OperatingSystemVersion();
+ return printer_tags_out;
+}
+
+// Returns the hash of |printer_tags|.
+std::string HashPrinterTags(const PrinterTags& printer_tags) {
+ std::string values_list;
+ PrinterTags::const_iterator it;
+ for (it = printer_tags.begin(); it != printer_tags.end(); ++it) {
+ values_list.append(it->first);
+ values_list.append(it->second);
+ }
+ return base::MD5String(values_list);
+}
+
+} // namespace
+
+std::string AppendPathToUrl(const GURL& url, const std::string& path) {
+ DCHECK_NE(path[0], '/');
+ std::string ret = url.path();
+ if (url.has_path() && (ret.back() != '/'))
+ ret += '/';
+ ret += path;
+ return ret;
+}
+
+GURL GetUrlForSearch(const GURL& cloud_print_server_url) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "search"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForSubmit(const GURL& cloud_print_server_url) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "submit"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForPrinterList(const GURL& cloud_print_server_url,
+ const std::string& proxy_id) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "list"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf("proxy=%s", proxy_id.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForPrinterRegistration(const GURL& cloud_print_server_url) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "register"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForPrinterUpdate(const GURL& cloud_print_server_url,
+ const std::string& printer_id) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "update"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf("printerid=%s", printer_id.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForPrinterDelete(const GURL& cloud_print_server_url,
+ const std::string& printer_id,
+ const std::string& reason) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "delete"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf(
+ "printerid=%s&reason=%s", printer_id.c_str(), reason.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForJobFetch(const GURL& cloud_print_server_url,
+ const std::string& printer_id,
+ const std::string& reason) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "fetch"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf(
+ "printerid=%s&deb=%s", printer_id.c_str(), reason.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForJobCjt(const GURL& cloud_print_server_url,
+ const std::string& job_id,
+ const std::string& reason) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "ticket"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf(
+ "jobid=%s&deb=%s&use_cjt=true", job_id.c_str(), reason.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForJobDelete(const GURL& cloud_print_server_url,
+ const std::string& job_id) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "deletejob"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf("jobid=%s", job_id.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForJobStatusUpdate(const GURL& cloud_print_server_url,
+ const std::string& job_id,
+ const std::string& status_string,
+ int connector_code) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "control"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf(
+ "jobid=%s&status=%s&connector_code=%d", job_id.c_str(),
+ status_string.c_str(), connector_code);
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForUserMessage(const GURL& cloud_print_server_url,
+ const std::string& message_id) {
+ std::string path(AppendPathToUrl(cloud_print_server_url, "message"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf("code=%s", message_id.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForGetAuthCode(const GURL& cloud_print_server_url,
+ const std::string& oauth_client_id,
+ const std::string& proxy_id) {
+ // We use the internal API "createrobot" instead of "getauthcode". This API
+ // will add the robot as owner to all the existing printers for this user.
+ std::string path(AppendPathToUrl(cloud_print_server_url, "createrobot"));
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ std::string query = base::StringPrintf("oauth_client_id=%s&proxy=%s",
+ oauth_client_id.c_str(),
+ proxy_id.c_str());
+ replacements.SetQueryStr(query);
+ return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+base::Value ParseResponseJSON(const std::string& response_data,
+ bool* succeeded) {
+ base::Optional<base::Value> message_value =
+ base::JSONReader::Read(response_data);
+ if (!message_value || !message_value->is_dict())
+ return base::Value();
+
+ *succeeded = message_value->FindBoolKey(kSuccessValue).value_or(false);
+ return std::move(*message_value);
+}
+
+std::string GetMultipartMimeType(const std::string& mime_boundary) {
+ return std::string("multipart/form-data; boundary=") + mime_boundary;
+}
+
+std::string GetHashOfPrinterTags(const PrinterTags& printer_tags) {
+ return HashPrinterTags(PreparePrinterTags(printer_tags));
+}
+
+std::string GetPostDataForPrinterTags(
+ const PrinterTags& printer_tags,
+ const std::string& mime_boundary,
+ const std::string& proxy_tag_prefix,
+ const std::string& tags_hash_tag_name) {
+ PrinterTags printer_tags_prepared = PreparePrinterTags(printer_tags);
+ std::string post_data;
+ for (PrinterTags::const_iterator it = printer_tags_prepared.begin();
+ it != printer_tags_prepared.end(); ++it) {
+ // TODO(gene) Escape '=' char from name. Warning for now.
+ if (it->first.find('=') != std::string::npos) {
+ LOG(WARNING) <<
+ "CP_PROXY: Printer option name contains '=' character";
+ NOTREACHED();
+ }
+ // All our tags have a special prefix to identify them as such.
+ std::string msg = base::StringPrintf("%s%s=%s",
+ proxy_tag_prefix.c_str(), it->first.c_str(), it->second.c_str());
+ net::AddMultipartValueForUpload(kPrinterTagValue, msg, mime_boundary,
+ std::string(), &post_data);
+ }
+ std::string tags_hash_msg = base::StringPrintf("%s=%s",
+ tags_hash_tag_name.c_str(),
+ HashPrinterTags(printer_tags_prepared).c_str());
+ net::AddMultipartValueForUpload(kPrinterTagValue, tags_hash_msg,
+ mime_boundary, std::string(), &post_data);
+ return post_data;
+}
+
+std::string GetCloudPrintAuthHeader(const std::string& auth_token) {
+ return base::StringPrintf("Authorization: OAuth %s", auth_token.c_str());
+}
+
+} // namespace cloud_print
diff --git a/chromium/chrome/common/cloud_print/cloud_print_helpers.h b/chromium/chrome/common/cloud_print/cloud_print_helpers.h
new file mode 100644
index 00000000000..904f934ec1b
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_helpers.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+// Helper consts and methods for both cloud print and chrome browser.
+namespace cloud_print {
+
+// A map representing printer tags.
+typedef std::map<std::string, std::string> PrinterTags;
+
+// Appends a relative path to the url making sure to append a '/' if the
+// URL's path does not end with a slash. It is assumed that |path| does not
+// begin with a '/'.
+// NOTE: Since we ALWAYS want to append here, we simply append the path string
+// instead of calling url::ResolveRelative. The input |url| may or may not
+// contain a '/' at the end.
+std::string AppendPathToUrl(const GURL& url, const std::string& path);
+
+GURL GetUrlForSearch(const GURL& cloud_print_server_url);
+GURL GetUrlForSubmit(const GURL& cloud_print_server_url);
+GURL GetUrlForPrinterList(const GURL& cloud_print_server_url,
+ const std::string& proxy_id);
+GURL GetUrlForPrinterRegistration(const GURL& cloud_print_server_url);
+GURL GetUrlForPrinterUpdate(const GURL& cloud_print_server_url,
+ const std::string& printer_id);
+GURL GetUrlForPrinterDelete(const GURL& cloud_print_server_url,
+ const std::string& printer_id,
+ const std::string& reason);
+GURL GetUrlForJobFetch(const GURL& cloud_print_server_url,
+ const std::string& printer_id,
+ const std::string& reason);
+GURL GetUrlForJobCjt(const GURL& cloud_print_server_url,
+ const std::string& job_id,
+ const std::string& reason);
+GURL GetUrlForJobDelete(const GURL& cloud_print_server_url,
+ const std::string& job_id);
+GURL GetUrlForJobStatusUpdate(const GURL& cloud_print_server_url,
+ const std::string& job_id,
+ const std::string& status_string,
+ int connector_code);
+GURL GetUrlForUserMessage(const GURL& cloud_print_server_url,
+ const std::string& message_id);
+GURL GetUrlForGetAuthCode(const GURL& cloud_print_server_url,
+ const std::string& oauth_client_id,
+ const std::string& proxy_id);
+
+// Parses the response data for any cloud print server request. The method
+// returns none Value if there was an error in parsing the JSON. The |succeeded|
+// parameters returns the value of the "success" value in the response JSON.
+// Returns the response as a dictionary value on success.
+base::Value ParseResponseJSON(const std::string& response_data,
+ bool* succeeded);
+
+// Returns the MIME type of multipart with |mime_boundary|.
+std::string GetMultipartMimeType(const std::string& mime_boundary);
+
+// Returns an MD5 hash for |printer_tags| and the default required tags.
+std::string GetHashOfPrinterTags(const PrinterTags& printer_tags);
+
+// Returns the post data for |printer_tags| and the default required tags.
+std::string GetPostDataForPrinterTags(
+ const PrinterTags& printer_tags,
+ const std::string& mime_boundary,
+ const std::string& proxy_tag_prefix,
+ const std::string& tags_hash_tag_name);
+
+// Get the cloud print auth header from |auth_token|.
+std::string GetCloudPrintAuthHeader(const std::string& auth_token);
+
+} // namespace cloud_print
+
+#endif // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_
diff --git a/chromium/chrome/common/cloud_print/cloud_print_helpers_unittest.cc b/chromium/chrome/common/cloud_print/cloud_print_helpers_unittest.cc
new file mode 100644
index 00000000000..cfd1b650f0f
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_helpers_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/cloud_print/cloud_print_helpers.h"
+
+#include "base/hash/md5.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "chrome/common/channel_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace cloud_print {
+
+namespace {
+
+void CheckURLs(const GURL& server_base_url) {
+ std::string expected_url_base = server_base_url.spec();
+ if (expected_url_base.back() != '/')
+ expected_url_base += "/";
+
+ EXPECT_EQ(base::StringPrintf("%ssearch", expected_url_base.c_str()),
+ GetUrlForSearch(server_base_url).spec());
+
+ EXPECT_EQ(base::StringPrintf("%ssubmit", expected_url_base.c_str()),
+ GetUrlForSubmit(server_base_url).spec());
+
+ EXPECT_EQ(base::StringPrintf("%slist?proxy=demoproxy",
+ expected_url_base.c_str()),
+ GetUrlForPrinterList(
+ server_base_url, std::string("demoproxy")).spec());
+
+ EXPECT_EQ(base::StringPrintf("%sregister", expected_url_base.c_str()),
+ GetUrlForPrinterRegistration(server_base_url).spec());
+
+ EXPECT_EQ(base::StringPrintf("%supdate?printerid=printeridfoo",
+ expected_url_base.c_str()),
+ GetUrlForPrinterUpdate(server_base_url, "printeridfoo").spec());
+
+ EXPECT_EQ(base::StringPrintf("%sdelete?printerid=printeridbar&reason=deleted",
+ expected_url_base.c_str()),
+ GetUrlForPrinterDelete(
+ server_base_url, "printeridbar", "deleted").spec());
+
+ EXPECT_EQ(base::StringPrintf("%sfetch?printerid=myprinter&deb=nogoodreason",
+ expected_url_base.c_str()),
+ GetUrlForJobFetch(
+ server_base_url, "myprinter", "nogoodreason").spec());
+
+ EXPECT_EQ(base::StringPrintf("%sdeletejob?jobid=myprinter",
+ expected_url_base.c_str()),
+ GetUrlForJobDelete(server_base_url, "myprinter").spec());
+
+ EXPECT_EQ(base::StringPrintf(
+ "%scontrol?jobid=myprinter&status=s1&connector_code=0",
+ expected_url_base.c_str()),
+ GetUrlForJobStatusUpdate(
+ server_base_url, "myprinter", "s1", 0).spec());
+
+ EXPECT_EQ(base::StringPrintf("%smessage?code=testmsg",
+ expected_url_base.c_str()),
+ GetUrlForUserMessage(server_base_url, "testmsg").spec());
+
+ EXPECT_EQ(base::StringPrintf(
+ "%screaterobot?oauth_client_id=democlientid&proxy=demoproxy",
+ expected_url_base.c_str()),
+ GetUrlForGetAuthCode(
+ server_base_url, "democlientid", "demoproxy").spec());
+}
+
+} // namespace
+
+TEST(CloudPrintHelpersTest, GetURLs) {
+ CheckURLs(GURL("https://www.google.com/cloudprint"));
+ CheckURLs(GURL("https://www.google.com/cloudprint/"));
+ CheckURLs(GURL("http://www.myprinterserver.com"));
+ CheckURLs(GURL("http://www.myprinterserver.com/"));
+}
+
+TEST(CloudPrintHelpersTest, GetHashOfPrinterTags) {
+ PrinterTags printer_tags;
+ printer_tags["tag1"] = std::string("value1");
+ printer_tags["tag2"] = std::string("value2");
+
+ std::string expected_list_string = base::StringPrintf(
+ "chrome_version%ssystem_name%ssystem_version%stag1value1tag2value2",
+ chrome::GetVersionString().c_str(),
+ base::SysInfo::OperatingSystemName().c_str(),
+ base::SysInfo::OperatingSystemVersion().c_str());
+ EXPECT_EQ(base::MD5String(expected_list_string),
+ GetHashOfPrinterTags(printer_tags));
+}
+
+TEST(CloudPrintHelpersTest, GetPostDataForPrinterTags) {
+ PrinterTags printer_tags;
+ printer_tags["tag1"] = std::string("value1");
+ printer_tags["tag2"] = std::string("value2");
+
+ std::string expected = base::StringPrintf(
+ "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
+ "\r\n\r\n__test__chrome_version=%s\r\n"
+ "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
+ "\r\n\r\n__test__system_name=%s\r\n"
+ "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
+ "\r\n\r\n__test__system_version=%s\r\n"
+ "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
+ "\r\n\r\n__test__tag1=value1\r\n"
+ "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
+ "\r\n\r\n__test__tag2=value2\r\n"
+ "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
+ "\r\n\r\n__test__tagshash=%s\r\n",
+ chrome::GetVersionString().c_str(),
+ base::SysInfo::OperatingSystemName().c_str(),
+ base::SysInfo::OperatingSystemVersion().c_str(),
+ GetHashOfPrinterTags(printer_tags).c_str());
+
+ EXPECT_EQ(expected, GetPostDataForPrinterTags(
+ printer_tags,
+ std::string("test_mime_boundary"),
+ std::string("__test__"),
+ std::string("__test__tagshash")));
+}
+
+TEST(CloudPrintHelpersTest, GetCloudPrintAuthHeader) {
+ std::string test_auth("testauth");
+ EXPECT_EQ("Authorization: OAuth testauth",
+ GetCloudPrintAuthHeader(test_auth));
+}
+
+} // namespace cloud_print
diff --git a/chromium/chrome/common/cloud_print/cloud_print_proxy_info.cc b/chromium/chrome/common/cloud_print/cloud_print_proxy_info.cc
new file mode 100644
index 00000000000..6e38663cb3b
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_proxy_info.cc
@@ -0,0 +1,14 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/cloud_print/cloud_print_proxy_info.h"
+
+namespace cloud_print {
+
+CloudPrintProxyInfo::CloudPrintProxyInfo() : enabled(false) {
+}
+
+CloudPrintProxyInfo::~CloudPrintProxyInfo() {}
+
+} // namespace cloud_print
diff --git a/chromium/chrome/common/cloud_print/cloud_print_proxy_info.h b/chromium/chrome/common/cloud_print/cloud_print_proxy_info.h
new file mode 100644
index 00000000000..2b97378bd72
--- /dev/null
+++ b/chromium/chrome/common/cloud_print/cloud_print_proxy_info.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_PROXY_INFO_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_PROXY_INFO_H_
+
+#include <string>
+
+namespace cloud_print {
+
+// This struct is used for ServiceHostMsg_CloudPrint_Info IPC message.
+struct CloudPrintProxyInfo {
+ CloudPrintProxyInfo();
+ ~CloudPrintProxyInfo();
+
+ bool enabled;
+ std::string email;
+ std::string proxy_id;
+};
+
+} // namespace cloud_print
+
+#endif // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_PROXY_INFO_H_
diff --git a/chromium/chrome/common/common.vsprops b/chromium/chrome/common/common.vsprops
new file mode 100644
index 00000000000..a8343918f4a
--- /dev/null
+++ b/chromium/chrome/common/common.vsprops
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="common (chrome)"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\icu\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\npapi\using_npapi.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)\common\extra_defines.vsprops"
+ >
+</VisualStudioPropertySheet>
diff --git a/chromium/chrome/common/common_message_generator.cc b/chromium/chrome/common/common_message_generator.cc
new file mode 100644
index 00000000000..f2dd63b8714
--- /dev/null
+++ b/chromium/chrome/common/common_message_generator.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/common_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h"
+#include "chrome/common/common_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+#include "chrome/common/safe_browsing/protobuf_message_write_macros.h"
+namespace IPC {
+#include "chrome/common/common_message_generator.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+#include "chrome/common/safe_browsing/protobuf_message_read_macros.h"
+namespace IPC {
+#include "chrome/common/common_message_generator.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+#include "chrome/common/safe_browsing/protobuf_message_log_macros.h"
+namespace IPC {
+#include "chrome/common/common_message_generator.h"
+} // namespace IPC
diff --git a/chromium/chrome/common/common_message_generator.h b/chromium/chrome/common/common_message_generator.h
new file mode 100644
index 00000000000..c16905baf6f
--- /dev/null
+++ b/chromium/chrome/common/common_message_generator.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included file, hence no include guard.
+
+#include "chrome/common/common_param_traits_macros.h"
+#include "chrome/common/instant_mojom_traits.h"
+#include "services/network/public/cpp/p2p_param_traits.h"
+#undef CHROME_COMMON_MAC_APP_SHIM_PARAM_TRAITS_H_
+#include "chrome/common/mac/app_shim_param_traits.h"
+#ifndef CHROME_COMMON_MAC_APP_SHIM_PARAM_TRAITS_H_
+#error "Failed to include header chrome/common/mac/app_shim_param_traits.h"
+#endif
+#undef CHROME_COMMON_PRERENDER_MESSAGES_H_
+#include "chrome/common/prerender_messages.h"
+#ifndef CHROME_COMMON_PRERENDER_MESSAGES_H_
+#error "Failed to include header chrome/common/prerender_messages.h"
+#endif
+#undef CHROME_COMMON_RENDER_MESSAGES_H_
+#include "chrome/common/render_messages.h"
+#ifndef CHROME_COMMON_RENDER_MESSAGES_H_
+#error "Failed to include header chrome/common/render_messages.h"
+#endif
+#include "components/safe_browsing/buildflags.h"
+#include "content/public/common/common_param_traits.h"
+#include "content/public/common/common_param_traits_macros.h"
+#include "extensions/buildflags/buildflags.h"
+#include "media/media_buildflags.h"
+#include "printing/buildflags/buildflags.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#undef CHROME_COMMON_CAST_MESSAGES_H_
+#include "chrome/common/cast_messages.h"
+#ifndef CHROME_COMMON_CAST_MESSAGES_H_
+#error "Failed to include header chrome/common/cast_messages.h"
+#endif
+#endif
+
+#if BUILDFLAG(ENABLE_PRINTING)
+#undef CHROME_COMMON_CHROME_UTILITY_PRINTING_MESSAGES_H_
+#include "chrome/common/chrome_utility_printing_messages.h"
+#ifndef CHROME_COMMON_CHROME_UTILITY_PRINTING_MESSAGES_H_
+#error \
+ "Failed to include header chrome/common/chrome_utility_printing_messages.h"
+#endif
+#endif
+
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+#include "chrome/services/file_util/public/mojom/safe_archive_analyzer_param_traits.h"
+#endif
diff --git a/chromium/chrome/common/common_param_traits.cc b/chromium/chrome/common/common_param_traits.cc
new file mode 100644
index 00000000000..466cfde6da8
--- /dev/null
+++ b/chromium/chrome/common/common_param_traits.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Get basic type definitions.
+#include "chrome/common/common_param_traits.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/common_param_traits_macros.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/common_param_traits_macros.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/common_param_traits_macros.h"
+} // namespace IPC
diff --git a/chromium/chrome/common/common_param_traits.h b/chromium/chrome/common/common_param_traits.h
new file mode 100644
index 00000000000..9a05b2bf99e
--- /dev/null
+++ b/chromium/chrome/common/common_param_traits.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_COMMON_PARAM_TRAITS_H_
+#define CHROME_COMMON_COMMON_PARAM_TRAITS_H_
+
+// This file provides declarations of IPC serialization macros that are used
+// in more than one IPC message file.
+
+#include "chrome/common/common_param_traits_macros.h"
+
+#endif // CHROME_COMMON_COMMON_PARAM_TRAITS_H_
diff --git a/chromium/chrome/common/common_param_traits_macros.h b/chromium/chrome/common/common_param_traits_macros.h
new file mode 100644
index 00000000000..f6b91ccd276
--- /dev/null
+++ b/chromium/chrome/common/common_param_traits_macros.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Singly or multiply-included shared traits file depending upon circumstances.
+// This allows the use of IPC serialization macros in more than one IPC message
+// file.
+#ifndef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#define CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "ipc/ipc_message_macros.h"
+
+IPC_ENUM_TRAITS_MAX_VALUE(ContentSettingsType, CONTENT_SETTINGS_NUM_TYPES - 1)
+
+#endif // CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
diff --git a/chromium/chrome/common/component_flash_hint_file_linux.cc b/chromium/chrome/common/component_flash_hint_file_linux.cc
new file mode 100644
index 00000000000..8e90d1f7a98
--- /dev/null
+++ b/chromium/chrome/common/component_flash_hint_file_linux.cc
@@ -0,0 +1,229 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/component_flash_hint_file_linux.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/mman.h>
+
+#include <memory>
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/files/scoped_file.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/path_service.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "crypto/secure_hash.h"
+#include "crypto/secure_util.h"
+#include "crypto/sha2.h"
+
+namespace component_flash_hint_file {
+
+namespace {
+
+// The current version of the hints file.
+const int kCurrentHintFileVersion = 0x10;
+// The earliest version of the hints file.
+const int kEarliestHintFileVersion = 0x10;
+// The Version field in the JSON encoded file.
+const char kVersionField[] = "Version";
+// The HashAlgorithm field in the JSON encoded file.
+const char kHashAlgoField[] = "HashAlgorithm";
+// The Hash field in the JSON encoded file.
+const char kHashField[] = "Hash";
+// The PluginPath field in the JSON encoded file.
+const char kPluginPath[] = "PluginPath";
+// The PluginVersion field in the JSON encoded file.
+const char kPluginVersion[] = "PluginVersion";
+// For use with the scoped_ptr of an mmap-ed buffer
+struct MmapDeleter {
+ explicit MmapDeleter(size_t map_size) : map_size_(map_size) { }
+ inline void operator()(uint8_t* ptr) const {
+ if (ptr != MAP_FAILED)
+ munmap(ptr, map_size_);
+ }
+
+ private:
+ size_t map_size_;
+};
+
+// Hashes the plugin file and returns the result in the out params.
+// |mapped_file| is the file to be hashed.
+// |result| is the buffer, which must be of size crypto::kSHA256Length, which
+// will contain the hash.
+// |len| is the size of the buffer, which must be crypto::kSHA256Length.
+void SHA256Hash(const base::MemoryMappedFile& mapped_file,
+ void* result,
+ size_t len) {
+ CHECK_EQ(crypto::kSHA256Length, len);
+ std::unique_ptr<crypto::SecureHash> secure_hash(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ secure_hash->Update(mapped_file.data(), mapped_file.length());
+ secure_hash->Finish(result, len);
+}
+
+// This will serialize the file to disk as JSON. The format is:
+// {
+// "Version": 0x10,
+// "HashAlgorithm": SecureHash::SHA256,
+// "Hash": <Base64 Encoded Hash>,
+// "PluginPath": /path/to/component/updated/flash.so,
+// "PluginVersion": "1.0.0.1"
+// }
+bool WriteToDisk(const int version,
+ const crypto::SecureHash::Algorithm algorithm,
+ const std::string& hash,
+ const base::FilePath& plugin_path,
+ const std::string& flash_version) {
+ base::FilePath hint_file_path;
+ if (!base::PathService::Get(chrome::FILE_COMPONENT_FLASH_HINT,
+ &hint_file_path))
+ return false;
+
+ std::string encoded_hash;
+ base::Base64Encode(hash, &encoded_hash);
+
+ // Now construct a Value object to convert to JSON.
+ base::DictionaryValue dict;
+ dict.SetInteger(kVersionField, version);
+ dict.SetInteger(kHashAlgoField, crypto::SecureHash::SHA256);
+ dict.SetString(kHashField, encoded_hash);
+ dict.SetString(kPluginPath, plugin_path.value());
+ dict.SetString(kPluginVersion, flash_version);
+ // Do the serialization of the DictionaryValue to JSON.
+ std::string json_string;
+ JSONStringValueSerializer serializer(&json_string);
+ if (!serializer.Serialize(dict))
+ return false;
+
+ return base::ImportantFileWriter::WriteFileAtomically(hint_file_path,
+ json_string);
+}
+
+} // namespace
+
+bool TestExecutableMapping(const base::FilePath& path) {
+ const base::ScopedFD fd(
+ HANDLE_EINTR(open(path.value().c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.is_valid())
+ return false;
+ const size_t map_size = sizeof(uint8_t);
+ const MmapDeleter deleter(map_size);
+ std::unique_ptr<uint8_t, MmapDeleter> buf_ptr(
+ reinterpret_cast<uint8_t*>(mmap(nullptr, map_size, PROT_READ | PROT_EXEC,
+ MAP_PRIVATE, fd.get(), 0)),
+ deleter);
+ return buf_ptr.get() != MAP_FAILED;
+}
+
+bool RecordFlashUpdate(const base::FilePath& unpacked_plugin,
+ const base::FilePath& moved_plugin,
+ const std::string& version) {
+ base::MemoryMappedFile mapped_file;
+ if (!mapped_file.Initialize(unpacked_plugin))
+ return false;
+
+ std::string hash(crypto::kSHA256Length, 0);
+ SHA256Hash(mapped_file, base::data(hash), hash.size());
+
+ return WriteToDisk(kCurrentHintFileVersion,
+ crypto::SecureHash::Algorithm::SHA256, hash, moved_plugin,
+ version);
+}
+
+bool DoesHintFileExist() {
+ base::FilePath hint_file_path;
+ if (!base::PathService::Get(chrome::FILE_COMPONENT_FLASH_HINT,
+ &hint_file_path))
+ return false;
+ return base::PathExists(hint_file_path);
+}
+
+bool VerifyAndReturnFlashLocation(base::FilePath* path,
+ std::string* flash_version) {
+ base::FilePath hint_file_path;
+ if (!base::PathService::Get(chrome::FILE_COMPONENT_FLASH_HINT,
+ &hint_file_path))
+ return false;
+
+ std::string json_string;
+ if (!base::ReadFileToString(hint_file_path, &json_string))
+ return false;
+
+ int error_code;
+ std::string error_message;
+ JSONStringValueDeserializer deserializer(json_string);
+ const std::unique_ptr<base::Value> value =
+ deserializer.Deserialize(&error_code, &error_message);
+
+ if (!value) {
+ LOG(ERROR)
+ << "Could not deserialize the component updated Flash hint file. Error "
+ << error_code << ": " << error_message;
+ return false;
+ }
+
+ base::DictionaryValue* dict = nullptr;
+ if (!value->GetAsDictionary(&dict))
+ return false;
+
+ int version;
+ if (!dict->GetInteger(kVersionField, &version))
+ return false;
+ if (version < kEarliestHintFileVersion || version > kCurrentHintFileVersion)
+ return false;
+
+ int hash_algorithm;
+ if (!dict->GetInteger(kHashAlgoField, &hash_algorithm))
+ return false;
+ if (hash_algorithm != crypto::SecureHash::SHA256)
+ return false;
+
+ std::string hash;
+ if (!dict->GetString(kHashField, &hash))
+ return false;
+
+ std::string plugin_path_str;
+ if (!dict->GetString(kPluginPath, &plugin_path_str))
+ return false;
+
+ std::string plugin_version_str;
+ if (!dict->GetString(kPluginVersion, &plugin_version_str))
+ return false;
+
+ std::string decoded_hash;
+ if (!base::Base64Decode(hash, &decoded_hash))
+ return false;
+
+ const base::FilePath plugin_path(plugin_path_str);
+ base::MemoryMappedFile plugin_file;
+ if (!plugin_file.Initialize(plugin_path))
+ return false;
+
+ std::vector<uint8_t> file_hash(crypto::kSHA256Length, 0);
+ SHA256Hash(plugin_file, &file_hash[0], file_hash.size());
+ if (!crypto::SecureMemEqual(base::data(file_hash), base::data(decoded_hash),
+ crypto::kSHA256Length)) {
+ LOG(ERROR)
+ << "The hash recorded in the component flash hint file does not "
+ "match the actual hash of the flash plugin found on disk. The "
+ "component flash plugin will not be loaded.";
+ return false;
+ }
+
+ *path = plugin_path;
+ flash_version->assign(plugin_version_str);
+ return true;
+}
+
+} // namespace component_flash_hint_file
diff --git a/chromium/chrome/common/component_flash_hint_file_linux.h b/chromium/chrome/common/component_flash_hint_file_linux.h
new file mode 100644
index 00000000000..85694ce4284
--- /dev/null
+++ b/chromium/chrome/common/component_flash_hint_file_linux.h
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_COMPONENT_FLASH_HINT_FILE_LINUX_H_
+#define CHROME_COMMON_COMPONENT_FLASH_HINT_FILE_LINUX_H_
+
+#include "build/build_config.h"
+
+#if !defined(OS_LINUX)
+#error "This file only applies to the Linux component update of Flash."
+#endif // !defined(OS_LINUX)
+
+#include <string>
+
+namespace base {
+class FilePath;
+}
+
+// The APIs in this namespace wraps the component updated flash hint file, which
+// lives inside the PepperFlash folder of the user-data-dir, so that the Linux
+// zygote process can preload the right version of flash.
+namespace component_flash_hint_file {
+
+// Records a new flash update into the hint file.
+// |unpacked_plugin| is the current location of the plugin.
+// |moved_plugin| is the location where the plugin will be loaded from.
+bool RecordFlashUpdate(const base::FilePath& unpacked_plugin,
+ const base::FilePath& moved_plugin,
+ const std::string& version);
+
+// Reports whether or not a hints file exists.
+bool DoesHintFileExist();
+
+// Return the path of the component updated flash plugin, only if the file has
+// the correct hash sum.
+// |path| will be populated with the path to the flash plugin.
+// |version| will be populated with the version of the flash plugin.
+bool VerifyAndReturnFlashLocation(base::FilePath* path, std::string* version);
+
+// Test if the specified plugin file can be mapped executable.
+// This is useful to test if the flash plugin is in a directory mounted
+// noexec, in which case Chrome will not be able to load and use the plugin.
+// |path| is the path of the flash plugin that will mapped executable.
+bool TestExecutableMapping(const base::FilePath& path);
+
+} // namespace component_flash_hint_file
+
+#endif // CHROME_COMMON_COMPONENT_FLASH_HINT_FILE_LINUX_H_
diff --git a/chromium/chrome/common/component_flash_hint_file_linux_unittest.cc b/chromium/chrome/common/component_flash_hint_file_linux_unittest.cc
new file mode 100644
index 00000000000..ca550f46a9f
--- /dev/null
+++ b/chromium/chrome/common/component_flash_hint_file_linux_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/component_flash_hint_file_linux.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/process/kill.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_path_override.h"
+#include "base/test/test_timeouts.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace chrome {
+
+class ComponentFlashHintFileTest : public base::MultiProcessTest {};
+
+TEST_F(ComponentFlashHintFileTest, ExistsTest) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+ EXPECT_FALSE(component_flash_hint_file::DoesHintFileExist());
+}
+
+TEST_F(ComponentFlashHintFileTest, InstallTest) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+ EXPECT_FALSE(component_flash_hint_file::DoesHintFileExist());
+
+ base::FilePath flash_dir;
+ ASSERT_TRUE(base::PathService::Get(
+ chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, &flash_dir));
+
+ base::File::Error error;
+ ASSERT_TRUE(base::CreateDirectoryAndGetError(flash_dir, &error));
+
+ // Write out a fixed byte array as the flash file.
+ uint8_t file[] = {0x4c, 0x65, 0x74, 0x20, 0x75, 0x73,
+ 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x67};
+ flash_dir = flash_dir.Append("libflash.so");
+ const std::string flash_version = "1.0.0.1";
+ ASSERT_EQ(static_cast<int>(sizeof(file)),
+ base::WriteFile(flash_dir, reinterpret_cast<const char*>(file),
+ sizeof(file)));
+ ASSERT_TRUE(component_flash_hint_file::RecordFlashUpdate(flash_dir, flash_dir,
+ flash_version));
+ ASSERT_TRUE(component_flash_hint_file::DoesHintFileExist());
+
+ // Confirm that the flash plugin can be verified and returned.
+ base::FilePath returned_flash_path;
+ std::string version;
+ ASSERT_TRUE(component_flash_hint_file::VerifyAndReturnFlashLocation(
+ &returned_flash_path, &version));
+ ASSERT_EQ(returned_flash_path, flash_dir);
+ ASSERT_EQ(version, flash_version);
+
+ // Now "corrupt" the flash file and make sure the checksum fails and nothing
+ // is returned.
+ file[0] = 0xAA;
+ ASSERT_TRUE(base::WriteFile(flash_dir, reinterpret_cast<const char*>(file),
+ sizeof(file)) == sizeof(file));
+ base::FilePath empty_path;
+ std::string empty_version;
+ ASSERT_FALSE(component_flash_hint_file::VerifyAndReturnFlashLocation(
+ &empty_path, &empty_version));
+ ASSERT_NE(empty_path, flash_dir);
+ ASSERT_FALSE(empty_version == flash_version);
+}
+
+TEST_F(ComponentFlashHintFileTest, CorruptionTest) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+ EXPECT_FALSE(component_flash_hint_file::DoesHintFileExist());
+
+ base::FilePath flash_dir;
+ ASSERT_TRUE(base::PathService::Get(
+ chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, &flash_dir));
+
+ base::File::Error error;
+ ASSERT_TRUE(base::CreateDirectoryAndGetError(flash_dir, &error));
+ flash_dir = flash_dir.Append("libflash.so");
+
+ const uint8_t file[] = {0x56, 0x61, 0x20, 0x67, 0x75, 0x76,
+ 0x66, 0x20, 0x62, 0x61, 0x72, 0x20};
+ ASSERT_TRUE(base::WriteFile(flash_dir, reinterpret_cast<const char*>(file),
+ sizeof(file)) == sizeof(file));
+ const std::string flash_version = "1.0.0.1";
+ ASSERT_TRUE(component_flash_hint_file::RecordFlashUpdate(flash_dir, flash_dir,
+ flash_version));
+ ASSERT_TRUE(component_flash_hint_file::DoesHintFileExist());
+
+ // Now write out a new flash version that will not be moved into place.
+ const uint8_t updated_file[] = {0x43, 0x72, 0x62, 0x63, 0x79, 0x72,
+ 0x20, 0x66, 0x7a, 0x76, 0x79, 0x76};
+ base::FilePath flash_dir_update;
+ ASSERT_TRUE(base::PathService::Get(
+ chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, &flash_dir_update));
+ flash_dir_update = flash_dir_update.Append("other_flash.so");
+ ASSERT_TRUE(base::WriteFile(flash_dir_update,
+ reinterpret_cast<const char*>(updated_file),
+ sizeof(updated_file)) == sizeof(updated_file));
+ ASSERT_TRUE(component_flash_hint_file::RecordFlashUpdate(
+ flash_dir_update, flash_dir, flash_version));
+ // |flash_dir_update| needs to be moved to |flash_dir|, but this test
+ // deliberately skips that step, so VerifyAndReturnFlashLocation should fail.
+ base::FilePath failed_flash_dir;
+ std::string failed_version;
+ ASSERT_FALSE(component_flash_hint_file::VerifyAndReturnFlashLocation(
+ &failed_flash_dir, &failed_version));
+}
+
+TEST_F(ComponentFlashHintFileTest, ExecTest1) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_path = temp_dir.GetPath().Append("plugin.so");
+ const uint8_t file[] = {0x55, 0x62, 0x79, 0x71, 0x20,
+ 0x6c, 0x62, 0x68, 0x65, 0x20};
+
+ ASSERT_TRUE(base::WriteFile(file_path, reinterpret_cast<const char*>(file),
+ sizeof(file)) == sizeof(file));
+ ASSERT_TRUE(component_flash_hint_file::TestExecutableMapping(file_path));
+}
+
+MULTIPROCESS_TEST_MAIN(NoExecMountTest) {
+ if (unshare(CLONE_NEWUSER | CLONE_NEWNS) != 0) {
+ LOG(ERROR) << "This kernel does not support unprivileged namespaces. "
+ "ExecTest2 will succeed without running.";
+ return 0;
+ }
+ // Now mount a NOEXEC fs.
+ const unsigned long tmpfs_flags = MS_NODEV | MS_NOSUID | MS_NOEXEC;
+ base::ScopedTempDir temp_dir;
+ CHECK(temp_dir.CreateUniqueTempDir());
+ CHECK_EQ(0, mount("tmpfs", temp_dir.GetPath().value().c_str(), "tmpfs",
+ tmpfs_flags, nullptr));
+ const base::FilePath file_path = temp_dir.GetPath().Append("plugin.so");
+ const uint8_t file[] = {0x56, 0x61, 0x20, 0x67, 0x75, 0x72,
+ 0x20, 0x70, 0x76, 0x67, 0x6c, 0x20};
+ bool test_exec = false;
+ bool file_written =
+ base::WriteFile(file_path, reinterpret_cast<const char*>(file),
+ sizeof(file)) == static_cast<int>(sizeof(file));
+ if (file_written)
+ test_exec = component_flash_hint_file::TestExecutableMapping(file_path);
+
+ if (umount(temp_dir.GetPath().value().c_str()) != 0)
+ LOG(ERROR) << "Could not unmount directory " << temp_dir.GetPath().value();
+
+ CHECK(file_written);
+ CHECK(!test_exec);
+ return 0;
+}
+
+TEST_F(ComponentFlashHintFileTest, ExecTest2) {
+ base::Process process = SpawnChild("NoExecMountTest");
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = 42;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/conflicts/OWNERS b/chromium/chrome/common/conflicts/OWNERS
new file mode 100644
index 00000000000..070d64169b2
--- /dev/null
+++ b/chromium/chrome/common/conflicts/OWNERS
@@ -0,0 +1,8 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+chrisha@chromium.org
+pmonette@chromium.org
+
+# COMPONENT: Internals>PlatformIntegration
+# TEAM: windows-third-party@chromium.org
diff --git a/chromium/chrome/common/conflicts/module_watcher_win.cc b/chromium/chrome/common/conflicts/module_watcher_win.cc
new file mode 100644
index 00000000000..994b1056ff3
--- /dev/null
+++ b/chromium/chrome/common/conflicts/module_watcher_win.cc
@@ -0,0 +1,266 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/conflicts/module_watcher_win.h"
+
+#include <windows.h>
+#include <tlhelp32.h>
+#include <winternl.h> // For UNICODE_STRING.
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/win/scoped_handle.h"
+
+// These structures and functions are documented in MSDN, see
+// http://msdn.microsoft.com/en-us/library/gg547638(v=vs.85).aspx
+// there are however no headers or import libraries available in the
+// Platform SDK. They are declared outside of the anonymous namespace to
+// allow them to be forward declared in the header file.
+enum {
+ // The DLL was loaded. The NotificationData parameter points to a
+ // LDR_DLL_LOADED_NOTIFICATION_DATA structure.
+ LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
+ // The DLL was unloaded. The NotificationData parameter points to a
+ // LDR_DLL_UNLOADED_NOTIFICATION_DATA structure.
+ LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
+};
+
+// Structure that is used for module load notifications.
+struct LDR_DLL_LOADED_NOTIFICATION_DATA {
+ // Reserved.
+ ULONG Flags;
+ // The full path name of the DLL module.
+ PCUNICODE_STRING FullDllName;
+ // The base file name of the DLL module.
+ PCUNICODE_STRING BaseDllName;
+ // A pointer to the base address for the DLL in memory.
+ PVOID DllBase;
+ // The size of the DLL image, in bytes.
+ ULONG SizeOfImage;
+};
+using PLDR_DLL_LOADED_NOTIFICATION_DATA = LDR_DLL_LOADED_NOTIFICATION_DATA*;
+
+// Structure that is used for module unload notifications.
+struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
+ // Reserved.
+ ULONG Flags;
+ // The full path name of the DLL module.
+ PCUNICODE_STRING FullDllName;
+ // The base file name of the DLL module.
+ PCUNICODE_STRING BaseDllName;
+ // A pointer to the base address for the DLL in memory.
+ PVOID DllBase;
+ // The size of the DLL image, in bytes.
+ ULONG SizeOfImage;
+};
+using PLDR_DLL_UNLOADED_NOTIFICATION_DATA = LDR_DLL_UNLOADED_NOTIFICATION_DATA*;
+
+// Union that is used for notifications.
+union LDR_DLL_NOTIFICATION_DATA {
+ LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
+ LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
+};
+using PLDR_DLL_NOTIFICATION_DATA = LDR_DLL_NOTIFICATION_DATA*;
+
+// Signature of the notification callback function.
+using PLDR_DLL_NOTIFICATION_FUNCTION =
+ VOID(CALLBACK*)(ULONG notification_reason,
+ const LDR_DLL_NOTIFICATION_DATA* notification_data,
+ PVOID context);
+
+// Signatures of the functions used for registering DLL notification callbacks.
+using LdrRegisterDllNotificationFunc =
+ NTSTATUS(NTAPI*)(ULONG flags,
+ PLDR_DLL_NOTIFICATION_FUNCTION notification_function,
+ PVOID context,
+ PVOID* cookie);
+using LdrUnregisterDllNotificationFunc = NTSTATUS(NTAPI*)(PVOID cookie);
+
+namespace {
+
+// Global lock for ensuring synchronization of destruction and notifications.
+//
+// Warning: Since this lock is acquired inside the DLL notification callbacks,
+// it must never be held when calling into any functions that may
+// acquire the Loader Lock, as this is a lock order violation that will
+// cause a deadlock. A noteworthy example in this file are the
+// LdrRegisterDllNotification and LdrUnregisterDllNotification
+// functions.
+base::LazyInstance<base::Lock>::Leaky g_module_watcher_lock =
+ LAZY_INSTANCE_INITIALIZER;
+// Global pointer to the singleton ModuleWatcher, if one exists. Under
+// |module_watcher_lock|.
+ModuleWatcher* g_module_watcher_instance = nullptr;
+
+// Names of the DLL notification registration functions. These are exported by
+// ntdll.
+constexpr wchar_t kNtDll[] = L"ntdll.dll";
+constexpr char kLdrRegisterDllNotification[] = "LdrRegisterDllNotification";
+constexpr char kLdrUnregisterDllNotification[] = "LdrUnregisterDllNotification";
+
+// Helper function for converting a UNICODE_STRING to a FilePath.
+base::FilePath ToFilePath(const UNICODE_STRING* str) {
+ return base::FilePath(
+ base::StringPiece16(str->Buffer, str->Length / sizeof(wchar_t)));
+}
+
+template <typename NotificationDataType>
+void OnModuleEvent(ModuleWatcher::ModuleEventType event_type,
+ const NotificationDataType& notification_data,
+ const ModuleWatcher::OnModuleEventCallback& callback) {
+ ModuleWatcher::ModuleEvent event(
+ event_type, ToFilePath(notification_data.FullDllName),
+ notification_data.DllBase, notification_data.SizeOfImage);
+ callback.Run(event);
+}
+
+} // namespace
+
+// static
+std::unique_ptr<ModuleWatcher> ModuleWatcher::Create(
+ OnModuleEventCallback callback) {
+ {
+ base::AutoLock lock(g_module_watcher_lock.Get());
+ // If a ModuleWatcher already exists then bail out.
+ if (g_module_watcher_instance)
+ return nullptr;
+ g_module_watcher_instance = new ModuleWatcher();
+ }
+
+ // Initialization mustn't occur while holding |g_module_watcher_lock|.
+ g_module_watcher_instance->Initialize(std::move(callback));
+ return base::WrapUnique(g_module_watcher_instance);
+}
+
+ModuleWatcher::~ModuleWatcher() {
+ // Done before acquiring |g_module_watcher_lock|.
+ UnregisterDllNotificationCallback();
+
+ // As soon as |g_module_watcher_instance| is null any dispatched callbacks
+ // will be silently absorbed by LoaderNotificationCallback.
+ base::AutoLock lock(g_module_watcher_lock.Get());
+ DCHECK_EQ(g_module_watcher_instance, this);
+ g_module_watcher_instance = nullptr;
+}
+
+ModuleWatcher::ModuleWatcher() {}
+
+// Initializes the ModuleWatcher instance.
+void ModuleWatcher::Initialize(OnModuleEventCallback callback) {
+ callback_ = std::move(callback);
+ RegisterDllNotificationCallback();
+
+ // The enumeration of modules is done on a background task to make sure it
+ // doesn't slow down startup.
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce(&ModuleWatcher::EnumerateAlreadyLoadedModules,
+ base::SequencedTaskRunnerHandle::Get(),
+ base::BindRepeating(&ModuleWatcher::RunCallback,
+ weak_ptr_factory_.GetWeakPtr())));
+}
+
+void ModuleWatcher::RegisterDllNotificationCallback() {
+ // It's safe to pass the return value of ::GetModuleHandle() directly to
+ // ::GetProcAddress() because ntdll is guaranteed to be loaded.
+ LdrRegisterDllNotificationFunc reg_fn =
+ reinterpret_cast<LdrRegisterDllNotificationFunc>(::GetProcAddress(
+ ::GetModuleHandle(kNtDll), kLdrRegisterDllNotification));
+ if (reg_fn)
+ reg_fn(0, &LoaderNotificationCallback, this, &dll_notification_cookie_);
+}
+
+void ModuleWatcher::UnregisterDllNotificationCallback() {
+ // It's safe to pass the return value of ::GetModuleHandle() directly to
+ // ::GetProcAddress() because ntdll is guaranteed to be loaded.
+ LdrUnregisterDllNotificationFunc unreg_fn =
+ reinterpret_cast<LdrUnregisterDllNotificationFunc>(::GetProcAddress(
+ ::GetModuleHandle(kNtDll), kLdrUnregisterDllNotification));
+ if (unreg_fn)
+ unreg_fn(dll_notification_cookie_);
+}
+
+// static
+void ModuleWatcher::EnumerateAlreadyLoadedModules(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ OnModuleEventCallback callback) {
+ // Get all modules in the current process. According to MSDN,
+ // CreateToolhelp32Snapshot should be retried as long as its returning
+ // ERROR_BAD_LENGTH. To avoid locking up here a retry limit is enforced.
+ base::win::ScopedHandle snap;
+ DWORD process_id = ::GetCurrentProcessId();
+ for (int i = 0; i < 5; ++i) {
+ snap.Set(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32,
+ process_id));
+ if (snap.IsValid())
+ break;
+ if (::GetLastError() != ERROR_BAD_LENGTH)
+ return;
+ }
+ if (!snap.IsValid())
+ return;
+
+ // Walk the module list.
+ MODULEENTRY32 module = {sizeof(module)};
+ for (BOOL result = ::Module32First(snap.Get(), &module); result != FALSE;
+ result = ::Module32Next(snap.Get(), &module)) {
+ ModuleEvent event(ModuleEventType::kModuleAlreadyLoaded,
+ base::FilePath(module.szExePath), module.modBaseAddr,
+ module.modBaseSize);
+ task_runner->PostTask(FROM_HERE, base::BindOnce(callback, event));
+ }
+}
+
+// static
+ModuleWatcher::OnModuleEventCallback ModuleWatcher::GetCallbackForContext(
+ void* context) {
+ base::AutoLock lock(g_module_watcher_lock.Get());
+ if (context != g_module_watcher_instance)
+ return OnModuleEventCallback();
+ return g_module_watcher_instance->callback_;
+}
+
+// static
+void __stdcall ModuleWatcher::LoaderNotificationCallback(
+ unsigned long notification_reason,
+ const LDR_DLL_NOTIFICATION_DATA* notification_data,
+ void* context) {
+ auto callback = GetCallbackForContext(context);
+ if (!callback)
+ return;
+
+ switch (notification_reason) {
+ case LDR_DLL_NOTIFICATION_REASON_LOADED:
+ OnModuleEvent(ModuleEventType::kModuleLoaded, notification_data->Loaded,
+ callback);
+ break;
+
+ case LDR_DLL_NOTIFICATION_REASON_UNLOADED:
+ // Intentionally ignored.
+ break;
+
+ default:
+ // This is unexpected, but not a reason to crash.
+ NOTREACHED() << "Unknown LDR_DLL_NOTIFICATION_REASON: "
+ << notification_reason;
+ }
+}
+
+void ModuleWatcher::RunCallback(const ModuleEvent& event) {
+ callback_.Run(event);
+}
diff --git a/chromium/chrome/common/conflicts/module_watcher_win.h b/chromium/chrome/common/conflicts/module_watcher_win.h
new file mode 100644
index 00000000000..b050fdf028a
--- /dev/null
+++ b/chromium/chrome/common/conflicts/module_watcher_win.h
@@ -0,0 +1,149 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CONFLICTS_MODULE_WATCHER_WIN_H_
+#define CHROME_COMMON_CONFLICTS_MODULE_WATCHER_WIN_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+
+class ModuleWatcherTest;
+
+union LDR_DLL_NOTIFICATION_DATA;
+
+// This class observes modules as they are loaded into a process's address
+// space.
+//
+// This class is safe to be created on any thread. Similarly, it is safe to be
+// destroyed on any thread, independent of the thread on which the instance was
+// created.
+class ModuleWatcher {
+ public:
+ // The types of module events that can occur.
+ enum class ModuleEventType {
+ // A module was already loaded, but its presence is being observed.
+ kModuleAlreadyLoaded,
+ // A module is in the process of being loaded.
+ kModuleLoaded,
+ };
+
+ // Houses information about a module event, and some module metadata.
+ struct ModuleEvent {
+ ModuleEvent() = default;
+ ModuleEvent(const ModuleEvent& other) = default;
+ ModuleEvent(ModuleEventType event_type,
+ const base::FilePath& module_path,
+ void* module_load_address,
+ size_t module_size)
+ : event_type(event_type),
+ module_path(module_path),
+ module_load_address(module_load_address),
+ module_size(module_size) {}
+
+ // The type of module event.
+ ModuleEventType event_type;
+ // The full path to the module on disk.
+ base::FilePath module_path;
+ // The load address of the module. Careful consideration must be made before
+ // accessing memory at this address. See the comment for
+ // OnModuleEventCallback.
+ void* module_load_address;
+ // The size of the module in memory.
+ size_t module_size;
+ };
+
+ // The type of callback that will be invoked for each module event. This
+ // callback may be run from any thread in the process, and may be invoked
+ // during initialization (while iterating over already loaded modules) or in
+ // response to LdrDllNotifications received from the loader. As such, keep the
+ // amount of work performed here to an absolute minimum.
+ //
+ // MODULE_LOADED events are always dispatched directly from the loader while
+ // under the loader's lock, so the module is guaranteed to be loaded in memory
+ // (it is safe to access module_load_address).
+ //
+ // If the event is of type MODULE_ALREADY_LOADED, then the module data comes
+ // from a snapshot and it is possible that its |module_load_address| is
+ // invalid by the time the event is sent.
+ //
+ // Note that it is possible for this callback to be invoked after the
+ // destruction of the watcher.
+ using OnModuleEventCallback =
+ base::RepeatingCallback<void(const ModuleEvent& event)>;
+
+ // Creates and starts a watcher. This enumerates all loaded modules
+ // synchronously on the current thread during construction, and provides
+ // synchronous notifications as modules are loaded. The callback is invoked in
+ // the context of the thread that is loading a module, and as such may be
+ // invoked on any thread in the process. Note that it is possible to receive
+ // two notifications for some modules as the initial loaded module enumeration
+ // races briefly with the callback mechanism. In this case both a
+ // MODULE_LOADED and a MODULE_ALREADY_LOADED event will be received for the
+ // same module. Since the callback is installed first no modules can be
+ // missed, however. This factory function may be called on any thread.
+ //
+ // Only a single instance of a watcher may exist at any moment. This will
+ // return nullptr when trying to create a second watcher.
+ static std::unique_ptr<ModuleWatcher> Create(OnModuleEventCallback callback);
+
+ // This can be called on any thread. After destruction the |callback|
+ // provided to the constructor will no longer be invoked with module events.
+ ~ModuleWatcher();
+
+ private:
+ // For unittesting.
+ friend class ModuleWatcherTest;
+
+ // Private to enforce Singleton semantics. See Create above.
+ ModuleWatcher();
+
+ // Initializes the ModuleWatcher instance.
+ void Initialize(OnModuleEventCallback callback);
+
+ // Registers a DllNotification callback with the OS. Modifies
+ // |dll_notification_cookie_|. Can be called on any thread.
+ void RegisterDllNotificationCallback();
+
+ // Removes the installed DllNotification callback. Modifies
+ // |dll_notification_cookie_|. Can be called on any thread.
+ void UnregisterDllNotificationCallback();
+
+ // Enumerates all currently loaded modules, synchronously invoking callbacks
+ // on the current thread. Can be called on any thread.
+ static void EnumerateAlreadyLoadedModules(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ OnModuleEventCallback callback);
+
+ // Helper function for retrieving the callback associated with a given
+ // LdrNotification context.
+ static OnModuleEventCallback GetCallbackForContext(void* context);
+
+ // The loader notification callback. This is actually
+ // void CALLBACK LoaderNotificationCallback(
+ // DWORD, const LDR_DLL_NOTIFICATION_DATA*, PVOID)
+ // Not using CALLBACK/DWORD/PVOID allows skipping the windows.h header from
+ // this file.
+ static void __stdcall LoaderNotificationCallback(
+ unsigned long notification_reason,
+ const LDR_DLL_NOTIFICATION_DATA* notification_data,
+ void* context);
+
+ // Used to bind the |callback_| to a WeakPtr.
+ void RunCallback(const ModuleEvent& event);
+
+ // The current callback. Can end up being invoked on any thread.
+ OnModuleEventCallback callback_;
+ // Used by the DllNotification mechanism.
+ void* dll_notification_cookie_ = nullptr;
+
+ base::WeakPtrFactory<ModuleWatcher> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleWatcher);
+};
+
+#endif // CHROME_COMMON_CONFLICTS_MODULE_WATCHER_WIN_H_
diff --git a/chromium/chrome/common/conflicts/module_watcher_win_unittest.cc b/chromium/chrome/common/conflicts/module_watcher_win_unittest.cc
new file mode 100644
index 00000000000..8de1eb13161
--- /dev/null
+++ b/chromium/chrome/common/conflicts/module_watcher_win_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/conflicts/module_watcher_win.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include <windows.h>
+
+class ModuleWatcherTest : public testing::Test {
+ protected:
+ ModuleWatcherTest()
+ : module_(nullptr),
+ module_event_count_(0),
+ module_already_loaded_event_count_(0),
+ module_loaded_event_count_(0) {}
+
+ void OnModuleEvent(const ModuleWatcher::ModuleEvent& event) {
+ ++module_event_count_;
+ switch (event.event_type) {
+ case ModuleWatcher::ModuleEventType::kModuleAlreadyLoaded:
+ ++module_already_loaded_event_count_;
+ break;
+ case ModuleWatcher::ModuleEventType::kModuleLoaded:
+ ++module_loaded_event_count_;
+ break;
+ }
+ }
+
+ void TearDown() override { UnloadModule(); }
+
+ void LoadModule() {
+ if (module_)
+ return;
+ // This module should not be a static dependency of the unit-test
+ // executable, but should be a build-system dependency or a module that is
+ // present on any Windows machine.
+ static constexpr wchar_t kModuleName[] = L"conflicts_dll.dll";
+ // The module should not already be loaded.
+ ASSERT_FALSE(::GetModuleHandle(kModuleName));
+ // It should load successfully.
+ module_ = ::LoadLibrary(kModuleName);
+ ASSERT_TRUE(module_);
+ }
+
+ void UnloadModule() {
+ if (!module_)
+ return;
+ ::FreeLibrary(module_);
+ module_ = nullptr;
+ }
+
+ void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+ std::unique_ptr<ModuleWatcher> Create() {
+ return ModuleWatcher::Create(
+ base::Bind(&ModuleWatcherTest::OnModuleEvent, base::Unretained(this)));
+ }
+
+ base::test::TaskEnvironment task_environment_;
+
+ // Holds a handle to a loaded module.
+ HMODULE module_;
+ // Total number of module events seen.
+ int module_event_count_;
+ // Total number of MODULE_ALREADY_LOADED events seen.
+ int module_already_loaded_event_count_;
+ // Total number of MODULE_LOADED events seen.
+ int module_loaded_event_count_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ModuleWatcherTest);
+};
+
+TEST_F(ModuleWatcherTest, SingleModuleWatcherOnly) {
+ std::unique_ptr<ModuleWatcher> mw1(Create());
+ EXPECT_TRUE(mw1.get());
+
+ std::unique_ptr<ModuleWatcher> mw2(Create());
+ EXPECT_FALSE(mw2.get());
+}
+
+TEST_F(ModuleWatcherTest, ModuleEvents) {
+ // Create the module watcher. This should immediately enumerate all already
+ // loaded modules on a background task.
+ std::unique_ptr<ModuleWatcher> mw(Create());
+ RunUntilIdle();
+
+ EXPECT_LT(0, module_event_count_);
+ EXPECT_LT(0, module_already_loaded_event_count_);
+ EXPECT_EQ(0, module_loaded_event_count_);
+
+ // Dynamically load a module and ensure a notification is received for it.
+ int previous_module_loaded_event_count = module_loaded_event_count_;
+ LoadModule();
+ EXPECT_LT(previous_module_loaded_event_count, module_loaded_event_count_);
+
+ UnloadModule();
+
+ // Dynamically load a module and ensure a notification is received for it.
+ previous_module_loaded_event_count = module_loaded_event_count_;
+ LoadModule();
+ EXPECT_LT(previous_module_loaded_event_count, module_loaded_event_count_);
+
+ UnloadModule();
+
+ // Destroy the module watcher.
+ mw.reset();
+
+ // Load the module and ensure no notification is received this time.
+ previous_module_loaded_event_count = module_loaded_event_count_;
+ LoadModule();
+ EXPECT_EQ(previous_module_loaded_event_count, module_loaded_event_count_);
+}
diff --git a/chromium/chrome/common/conflicts/remote_module_watcher_win.cc b/chromium/chrome/common/conflicts/remote_module_watcher_win.cc
new file mode 100644
index 00000000000..0a28e9e11cf
--- /dev/null
+++ b/chromium/chrome/common/conflicts/remote_module_watcher_win.cc
@@ -0,0 +1,84 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/conflicts/remote_module_watcher_win.h"
+
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+
+namespace {
+
+// Note: Can be called on any threads, even those not owned by the task
+// scheduler.
+void OnModuleEvent(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const ModuleWatcher::OnModuleEventCallback& on_module_event_callback,
+ const ModuleWatcher::ModuleEvent& event) {
+ task_runner->PostTask(FROM_HERE,
+ base::BindOnce(on_module_event_callback, event));
+}
+
+} // namespace
+
+// static
+constexpr base::TimeDelta RemoteModuleWatcher::kIdleDelay;
+
+RemoteModuleWatcher::~RemoteModuleWatcher() = default;
+
+// static
+RemoteModuleWatcher::UniquePtr RemoteModuleWatcher::Create(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ mojo::PendingRemote<mojom::ModuleEventSink> remote_sink) {
+ auto remote_module_watcher =
+ UniquePtr(new RemoteModuleWatcher(task_runner),
+ base::OnTaskRunnerDeleter(task_runner));
+
+ // Because |remote_module_watcher| will be sent for deletion on |task_runner|,
+ // using an unretained pointer to it is safe as the initialization is
+ // guaranteed to be run before the destructor.
+ task_runner->PostTask(
+ FROM_HERE, base::BindOnce(&RemoteModuleWatcher::InitializeOnTaskRunner,
+ base::Unretained(remote_module_watcher.get()),
+ std::move(remote_sink)));
+
+ return remote_module_watcher;
+}
+
+RemoteModuleWatcher::RemoteModuleWatcher(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : task_runner_(task_runner),
+ delay_timer_(FROM_HERE,
+ kIdleDelay,
+ this,
+ &RemoteModuleWatcher::OnTimerFired) {}
+
+void RemoteModuleWatcher::InitializeOnTaskRunner(
+ mojo::PendingRemote<mojom::ModuleEventSink> remote_sink) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ module_event_sink_.Bind(std::move(remote_sink));
+ module_watcher_ = ModuleWatcher::Create(base::BindRepeating(
+ &OnModuleEvent, task_runner_,
+ base::BindRepeating(&RemoteModuleWatcher::HandleModuleEvent,
+ weak_ptr_factory_.GetWeakPtr())));
+}
+
+void RemoteModuleWatcher::HandleModuleEvent(
+ const ModuleWatcher::ModuleEvent& event) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Accumulate events. They will be sent when the |delay_timer_| fires.
+ module_load_addresses_.push_back(
+ reinterpret_cast<uintptr_t>(event.module_load_address));
+
+ // Ensure the timer is running.
+ delay_timer_.Reset();
+}
+
+void RemoteModuleWatcher::OnTimerFired() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ module_event_sink_->OnModuleEvents(module_load_addresses_);
+ module_load_addresses_.clear();
+}
diff --git a/chromium/chrome/common/conflicts/remote_module_watcher_win.h b/chromium/chrome/common/conflicts/remote_module_watcher_win.h
new file mode 100644
index 00000000000..45909f17dc1
--- /dev/null
+++ b/chromium/chrome/common/conflicts/remote_module_watcher_win.h
@@ -0,0 +1,87 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CONFLICTS_REMOTE_MODULE_WATCHER_WIN_H_
+#define CHROME_COMMON_CONFLICTS_REMOTE_MODULE_WATCHER_WIN_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chrome/common/conflicts/module_event_sink_win.mojom.h"
+#include "chrome/common/conflicts/module_watcher_win.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+struct OnTaskRunnerDeleter;
+} // namespace base
+
+// This class is used to instantiate a ModuleWatcher instance in a child
+// process that forwards all the module events to the browser process via the
+// mojom::ModuleEventSink interface.
+class RemoteModuleWatcher {
+ public:
+ // Provided for convenience.
+ using UniquePtr =
+ std::unique_ptr<RemoteModuleWatcher, base::OnTaskRunnerDeleter>;
+
+ // The amount of time this class waits before sending all the received module
+ // events in one batch to the browser process.
+ static constexpr base::TimeDelta kIdleDelay = base::TimeDelta::FromSeconds(5);
+
+ ~RemoteModuleWatcher();
+
+ // Creates a RemoteModuleWatcher instance and initializes it on |task_runner|.
+ // The instance lives on that task runner and will be destroyed there when the
+ // UniquePtr is destroyed.
+ static UniquePtr Create(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ mojo::PendingRemote<mojom::ModuleEventSink> remote_sink);
+
+ private:
+ explicit RemoteModuleWatcher(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Initializes this instance by connecting the |module_event_sink_| instance
+ // and starting the |module_watcher_|. Called on |task_runner_|.
+ void InitializeOnTaskRunner(
+ mojo::PendingRemote<mojom::ModuleEventSink> remote_sink);
+
+ // Receives module load events from the |module_watcher_| and forwards them to
+ // the |module_event_sink_|.
+ void HandleModuleEvent(const ModuleWatcher::ModuleEvent& event);
+
+ // Sends all accumulated module events in |module_load_addresses_| to the
+ // |module_event_sink_| in one batch.
+ void OnTimerFired();
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Module events from |module_watcher_| are forwarded to the browser process
+ // through this sink.
+ mojo::Remote<mojom::ModuleEventSink> module_event_sink_;
+
+ // Observes module load events.
+ std::unique_ptr<ModuleWatcher> module_watcher_;
+
+ // Accumulates module events. They will be sent to the browser process when
+ // |delay_timer_| fires.
+ std::vector<uint64_t> module_load_addresses_;
+
+ // This timer is used to delay the sending of module events until none have
+ // been received for |kIdleDelay| amount of time.
+ base::DelayTimer delay_timer_;
+
+ base::WeakPtrFactory<RemoteModuleWatcher> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(RemoteModuleWatcher);
+};
+
+#endif // CHROME_COMMON_CONFLICTS_REMOTE_MODULE_WATCHER_WIN_H_
diff --git a/chromium/chrome/common/conflicts/remote_module_watcher_win_unittest.cc b/chromium/chrome/common/conflicts/remote_module_watcher_win_unittest.cc
new file mode 100644
index 00000000000..ca99ee16a84
--- /dev/null
+++ b/chromium/chrome/common/conflicts/remote_module_watcher_win_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/conflicts/remote_module_watcher_win.h"
+
+#include <windows.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/common/conflicts/module_event_sink_win.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class RemoteModuleWatcherTest : public testing::Test,
+ public mojom::ModuleEventSink {
+ public:
+ RemoteModuleWatcherTest() = default;
+ ~RemoteModuleWatcherTest() override = default;
+
+ mojo::PendingRemote<mojom::ModuleEventSink> Bind() {
+ return receiver_.BindNewPipeAndPassRemote();
+ }
+
+ // mojom::ModuleEventSink:
+ void OnModuleEvents(
+ const std::vector<uint64_t>& module_load_addresses) override {
+ module_event_count_ += module_load_addresses.size();
+ }
+
+ void LoadModule() {
+ if (module_handle_)
+ return;
+ // This module should not be a static dependency of the unit-test
+ // executable, but should be a build-system dependency or a module that is
+ // present on any Windows machine.
+ static constexpr wchar_t kModuleName[] = L"conflicts_dll.dll";
+ // The module should not already be loaded.
+ ASSERT_FALSE(::GetModuleHandle(kModuleName));
+ // It should load successfully.
+ module_handle_ = ::LoadLibrary(kModuleName);
+ ASSERT_TRUE(module_handle_);
+ }
+
+ void UnloadModule() {
+ if (!module_handle_)
+ return;
+ ::FreeLibrary(module_handle_);
+ module_handle_ = nullptr;
+ }
+
+ // Runs the task scheduler until no tasks are running.
+ void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+ void FastForwardByIdleDelay() {
+ task_environment_.FastForwardBy(RemoteModuleWatcher::kIdleDelay);
+ }
+
+ HMODULE module_handle() { return module_handle_; }
+
+ int module_event_count() { return module_event_count_; }
+
+ private:
+ // Must be first.
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+ // Binds a ModuleEventSinkRequest to this implementation of ModuleEventSink.
+ mojo::Receiver<mojom::ModuleEventSink> receiver_{this};
+
+ // Holds a handle to a loaded module.
+ HMODULE module_handle_ = nullptr;
+
+ // Total number of module events seen.
+ int module_event_count_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoteModuleWatcherTest);
+};
+
+} // namespace
+
+TEST_F(RemoteModuleWatcherTest, ModuleEvents) {
+ auto remote_module_watcher =
+ RemoteModuleWatcher::Create(base::ThreadTaskRunnerHandle::Get(), Bind());
+
+ // Wait until the watcher is initialized and events for already loaded modules
+ // are received.
+ RunUntilIdle();
+ // Now wait for the timer used to batch events to expire.
+ FastForwardByIdleDelay();
+
+ EXPECT_GT(module_event_count(), 0);
+
+ // Dynamically load a module and ensure a notification is received for it.
+ int previous_module_event_count = module_event_count();
+ LoadModule();
+ FastForwardByIdleDelay();
+ EXPECT_GT(module_event_count(), previous_module_event_count);
+
+ UnloadModule();
+
+ // Destroy the module watcher.
+ remote_module_watcher = nullptr;
+ RunUntilIdle();
+
+ // Load the module and ensure no notification is received this time.
+ previous_module_event_count = module_event_count();
+ LoadModule();
+ FastForwardByIdleDelay();
+
+ EXPECT_EQ(module_event_count(), previous_module_event_count);
+
+ UnloadModule();
+}
diff --git a/chromium/chrome/common/crash_keys.cc b/chromium/chrome/common/crash_keys.cc
new file mode 100644
index 00000000000..4b9d1c36537
--- /dev/null
+++ b/chromium/chrome/common/crash_keys.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/crash_keys.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/crash/core/common/crash_key.h"
+#include "components/crash/core/common/crash_keys.h"
+#include "components/flags_ui/flags_ui_switches.h"
+#include "content/public/common/content_switches.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/common/chrome_switches.h"
+#include "components/crash/content/app/crash_switches.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "ui/gl/gl_switches.h"
+#endif
+
+namespace crash_keys {
+
+// Return true if we DON'T want to upload this flag to the crash server.
+static bool IsBoringSwitch(const std::string& flag) {
+ static const char* const kIgnoreSwitches[] = {
+ switches::kEnableLogging,
+ switches::kFlagSwitchesBegin,
+ switches::kFlagSwitchesEnd,
+ switches::kLoggingLevel,
+ switches::kProcessType,
+ switches::kV,
+ switches::kVModule,
+#if defined(OS_MACOSX)
+ switches::kMetricsClientID,
+#elif defined(OS_CHROMEOS)
+ // --crash-loop-before is a "boring" switch because it is redundant;
+ // crash_reporter separately informs the crash server if it is doing
+ // crash-loop handling.
+ crash_reporter::switches::kCrashLoopBefore,
+ switches::kPpapiFlashArgs,
+ switches::kPpapiFlashPath,
+ switches::kRegisterPepperPlugins,
+ switches::kUseGL,
+ switches::kUserDataDir,
+ // Cros/CC flags are specified as raw strings to avoid dependency.
+ "child-wallpaper-large",
+ "child-wallpaper-small",
+ "default-wallpaper-large",
+ "default-wallpaper-small",
+ "guest-wallpaper-large",
+ "guest-wallpaper-small",
+ "enterprise-enable-forced-re-enrollment",
+ "enterprise-enrollment-initial-modulus",
+ "enterprise-enrollment-modulus-limit",
+ "login-profile",
+ "login-user",
+ "use-cras",
+#endif
+ };
+
+#if defined(OS_WIN)
+ // Just about everything has this, don't bother.
+ if (base::StartsWith(flag, "/prefetch:", base::CompareCase::SENSITIVE))
+ return true;
+#endif
+
+ if (!base::StartsWith(flag, "--", base::CompareCase::SENSITIVE))
+ return false;
+ size_t end = flag.find("=");
+ size_t len = (end == std::string::npos) ? flag.length() - 2 : end - 2;
+ for (size_t i = 0; i < base::size(kIgnoreSwitches); ++i) {
+ if (flag.compare(2, len, kIgnoreSwitches[i]) == 0)
+ return true;
+ }
+ return false;
+}
+
+void SetCrashKeysFromCommandLine(const base::CommandLine& command_line) {
+ return SetSwitchesFromCommandLine(command_line, &IsBoringSwitch);
+}
+
+void SetActiveExtensions(const std::set<std::string>& extensions) {
+ static crash_reporter::CrashKeyString<4> num_extensions("num-extensions");
+ num_extensions.Set(base::NumberToString(extensions.size()));
+
+ using ExtensionIDKey = crash_reporter::CrashKeyString<64>;
+ static ExtensionIDKey extension_ids[] = {
+ {"extension-1", ExtensionIDKey::Tag::kArray},
+ {"extension-2", ExtensionIDKey::Tag::kArray},
+ {"extension-3", ExtensionIDKey::Tag::kArray},
+ {"extension-4", ExtensionIDKey::Tag::kArray},
+ {"extension-5", ExtensionIDKey::Tag::kArray},
+ {"extension-6", ExtensionIDKey::Tag::kArray},
+ {"extension-7", ExtensionIDKey::Tag::kArray},
+ {"extension-8", ExtensionIDKey::Tag::kArray},
+ {"extension-9", ExtensionIDKey::Tag::kArray},
+ {"extension-10", ExtensionIDKey::Tag::kArray},
+ };
+
+ auto it = extensions.begin();
+ for (size_t i = 0; i < base::size(extension_ids); ++i) {
+ if (it == extensions.end()) {
+ extension_ids[i].Clear();
+ } else {
+ extension_ids[i].Set(*it);
+ ++it;
+ }
+ }
+}
+
+} // namespace crash_keys
diff --git a/chromium/chrome/common/crash_keys.h b/chromium/chrome/common/crash_keys.h
new file mode 100644
index 00000000000..bcf172e645a
--- /dev/null
+++ b/chromium/chrome/common/crash_keys.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_CRASH_KEYS_H_
+#define CHROME_COMMON_CRASH_KEYS_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+class CommandLine;
+}
+
+namespace crash_keys {
+
+// Sets the kNumSwitches key and the set of keys named using kSwitchFormat based
+// on the given |command_line|.
+void SetCrashKeysFromCommandLine(const base::CommandLine& command_line);
+
+// Sets the list of "active" extensions in this process. We overload "active" to
+// mean different things depending on the process type:
+// - browser: all enabled extensions
+// - renderer: the unique set of extension ids from all content scripts
+// - extension: the id of each extension running in this process (there can be
+// multiple because of process collapsing).
+void SetActiveExtensions(const std::set<std::string>& extensions);
+
+} // namespace crash_keys
+
+#endif // CHROME_COMMON_CRASH_KEYS_H_
diff --git a/chromium/chrome/common/crash_keys_unittest.cc b/chromium/chrome/common/crash_keys_unittest.cc
new file mode 100644
index 00000000000..80b1781a84d
--- /dev/null
+++ b/chromium/chrome/common/crash_keys_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/crash_keys.h"
+
+#include <set>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using crash_reporter::GetCrashKeyValue;
+
+class CrashKeysTest : public testing::Test {
+ public:
+ void SetUp() override {
+ crash_reporter::ResetCrashKeysForTesting();
+ crash_reporter::InitializeCrashKeys();
+ }
+
+ void TearDown() override {
+ crash_reporter::ResetCrashKeysForTesting();
+ }
+};
+
+TEST_F(CrashKeysTest, Extensions) {
+ // Set three extensions.
+ {
+ std::set<std::string> extensions;
+ extensions.insert("ext.1");
+ extensions.insert("ext.2");
+ extensions.insert("ext.3");
+
+ crash_keys::SetActiveExtensions(extensions);
+
+ extensions.erase(GetCrashKeyValue("extension-1"));
+ extensions.erase(GetCrashKeyValue("extension-2"));
+ extensions.erase(GetCrashKeyValue("extension-3"));
+ EXPECT_EQ(0u, extensions.size());
+
+ EXPECT_EQ("3", GetCrashKeyValue("num-extensions"));
+ EXPECT_TRUE(GetCrashKeyValue("extension-4").empty());
+ }
+
+ // Set more than the max switches.
+ {
+ std::set<std::string> extensions;
+ const int kMax = 12;
+ for (int i = 1; i <= kMax; ++i)
+ extensions.insert(base::StringPrintf("ext.%d", i));
+ crash_keys::SetActiveExtensions(extensions);
+
+ for (int i = 1; i <= kMax; ++i) {
+ extensions.erase(GetCrashKeyValue(base::StringPrintf("extension-%d", i)));
+ }
+ EXPECT_EQ(2u, extensions.size());
+
+ EXPECT_EQ("12", GetCrashKeyValue("num-extensions"));
+ EXPECT_TRUE(GetCrashKeyValue("extension-13").empty());
+ EXPECT_TRUE(GetCrashKeyValue("extension-14").empty());
+ }
+
+ // Set fewer to ensure that old ones are erased.
+ {
+ std::set<std::string> extensions;
+ for (int i = 1; i <= 5; ++i)
+ extensions.insert(base::StringPrintf("ext.%d", i));
+ crash_keys::SetActiveExtensions(extensions);
+
+ extensions.erase(GetCrashKeyValue("extension-1"));
+ extensions.erase(GetCrashKeyValue("extension-2"));
+ extensions.erase(GetCrashKeyValue("extension-3"));
+ extensions.erase(GetCrashKeyValue("extension-4"));
+ extensions.erase(GetCrashKeyValue("extension-5"));
+ EXPECT_EQ(0u, extensions.size());
+
+ EXPECT_EQ("5", GetCrashKeyValue("num-extensions"));
+ for (int i = 6; i < 20; ++i) {
+ std::string key = base::StringPrintf("extension-%d", i);
+ EXPECT_TRUE(GetCrashKeyValue(key).empty()) << key;
+ }
+ }
+}
+
+TEST_F(CrashKeysTest, IgnoreBoringFlags) {
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+ command_line.AppendSwitch("--enable-logging");
+ command_line.AppendSwitch("--v=1");
+
+ command_line.AppendSwitch("--vv=1");
+ command_line.AppendSwitch("--vvv");
+ command_line.AppendSwitch("--enable-multi-profiles");
+ command_line.AppendSwitch("--device-management-url=https://foo/bar");
+#if defined(OS_CHROMEOS)
+ command_line.AppendSwitch("--user-data-dir=/tmp");
+ command_line.AppendSwitch("--default-wallpaper-small=test.png");
+#endif
+
+ crash_keys::SetCrashKeysFromCommandLine(command_line);
+
+ EXPECT_EQ("--vv=1", GetCrashKeyValue("switch-1"));
+ EXPECT_EQ("--vvv", GetCrashKeyValue("switch-2"));
+ EXPECT_EQ("--enable-multi-profiles", GetCrashKeyValue("switch-3"));
+ EXPECT_EQ("--device-management-url=https://foo/bar",
+ GetCrashKeyValue("switch-4"));
+ EXPECT_TRUE(GetCrashKeyValue("switch-5").empty());
+}
diff --git a/chromium/chrome/common/env_vars.cc b/chromium/chrome/common/env_vars.cc
new file mode 100644
index 00000000000..f880adbab66
--- /dev/null
+++ b/chromium/chrome/common/env_vars.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/env_vars.h"
+
+namespace env_vars {
+
+// We call running in unattended mode (for automated testing) "headless".
+// This mode can be enabled using this variable or by the kNoErrorDialogs
+// switch.
+const char kHeadless[] = "CHROME_HEADLESS";
+
+// The name of the log file.
+const char kLogFileName[] = "CHROME_LOG_FILE";
+
+// Flag indicating if metro viewer is connected to browser instance.
+// As of now there is only one metro viewer instance per browser.
+const char kMetroConnected[] = "CHROME_METRO_CONNECTED";
+
+// The name of the session log directory when logged in to ChromeOS.
+const char kSessionLogDir[] = "CHROMEOS_SESSION_LOG_DIR";
+
+// CHROME_CRASHED exists if a previous instance of chrome has crashed. This
+// triggers the 'restart chrome' dialog. CHROME_RESTART contains the strings
+// that are needed to show the dialog.
+const char kShowRestart[] = "CHROME_CRASHED";
+const char kRestartInfo[] = "CHROME_RESTART";
+
+// The strings RIGHT_TO_LEFT and LEFT_TO_RIGHT indicate the locale direction.
+// For example, for Hebrew and Arabic locales, we use RIGHT_TO_LEFT so that the
+// dialog is displayed using the right orientation.
+const char kRtlLocale[] = "RIGHT_TO_LEFT";
+const char kLtrLocale[] = "LEFT_TO_RIGHT";
+
+} // namespace env_vars
diff --git a/chromium/chrome/common/env_vars.h b/chromium/chrome/common/env_vars.h
new file mode 100644
index 00000000000..847335623de
--- /dev/null
+++ b/chromium/chrome/common/env_vars.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines all the environment variables used by Chrome.
+
+#ifndef CHROME_COMMON_ENV_VARS_H__
+#define CHROME_COMMON_ENV_VARS_H__
+
+namespace env_vars {
+
+extern const char kHeadless[];
+extern const char kLogFileName[];
+extern const char kMetroConnected[];
+extern const char kSessionLogDir[];
+extern const char kShowRestart[];
+extern const char kRestartInfo[];
+extern const char kRtlLocale[];
+extern const char kLtrLocale[];
+
+} // namespace env_vars
+
+#endif // CHROME_COMMON_ENV_VARS_H__
diff --git a/chromium/chrome/common/extensions/DEPS b/chromium/chrome/common/extensions/DEPS
new file mode 100644
index 00000000000..6a5a42e5aeb
--- /dev/null
+++ b/chromium/chrome/common/extensions/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+ "+ash/keyboard/ui/resources", # For virtual keyboard
+ "+components/policy/core/common",
+ "+extensions/grit",
+ "+extensions/strings/grit",
+ "+extensions/test",
+ "+ppapi/c", # For various types.
+ "+skia",
+]
+
+specific_include_rules = {
+ # The extensions client interface is the master arbiter of which
+ # API schemas exist, so it needs to know about app APIs.
+ "chrome_extensions_client\.cc": [
+ "+apps/common/api/generated_schemas.h",
+ "+services/network/public/mojom/cors_origin_pattern.mojom.h",
+ ],
+}
diff --git a/chromium/chrome/common/extensions/OWNERS b/chromium/chrome/common/extensions/OWNERS
new file mode 100644
index 00000000000..fe353c7aa3b
--- /dev/null
+++ b/chromium/chrome/common/extensions/OWNERS
@@ -0,0 +1,13 @@
+file://extensions/OWNERS
+
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+
+# COMPONENT: Platform>Extensions
+# TEAM: chromium-extensions@chromium.org
diff --git a/chromium/chrome/common/extensions/PRESUBMIT.py b/chromium/chrome/common/extensions/PRESUBMIT.py
new file mode 100644
index 00000000000..1b0d113938c
--- /dev/null
+++ b/chromium/chrome/common/extensions/PRESUBMIT.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Presubmit script for changes affecting extensions.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+import fnmatch
+import os
+import re
+
+EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
+DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
+SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
+API_PATH = os.path.join(EXTENSIONS_PATH, 'api')
+TEMPLATES_PATH = os.path.join(DOCS_PATH, 'templates')
+PRIVATE_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'private')
+PUBLIC_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'public')
+INTROS_PATH = os.path.join(TEMPLATES_PATH, 'intros')
+ARTICLES_PATH = os.path.join(TEMPLATES_PATH, 'articles')
+
+LOCAL_PUBLIC_TEMPLATES_PATH = os.path.join('docs',
+ 'templates',
+ 'public')
+
+EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS = ('.md', '.html')
+
+def _ReadFile(filename):
+ with open(filename) as f:
+ return f.read()
+
+def _ListFilesInPublic():
+ all_files = []
+ for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH):
+ all_files.extend(
+ os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):]
+ for filename in files)
+ return all_files
+
+def _UnixName(name):
+ name = os.path.splitext(name)[0]
+ s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
+ s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
+ return s2.replace('.', '_').lower()
+
+def _FindMatchingTemplates(template_name, template_path_list):
+ matches = []
+ unix_name = _UnixName(template_name)
+ for template in template_path_list:
+ if unix_name == _UnixName(template.split(os.sep)[-1]):
+ basename, ext = os.path.splitext(template)
+ # The docserver expects clean (extensionless) template URLs, so we
+ # strip some extensions here when generating the list of matches.
+ if ext in EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS:
+ matches.append(basename)
+ else:
+ matches.append(template)
+ return matches
+
+def _SanitizeAPIName(name, api_path):
+ if not api_path.endswith(os.sep):
+ api_path += os.sep
+ filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_')
+ if 'experimental' in filename:
+ filename = 'experimental_' + filename.replace('experimental_', '')
+ return filename
+
+def _CreateIntegrationTestArgs(affected_files):
+ if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH)
+ for name in affected_files) or
+ any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH)
+ for name in affected_files)):
+ return ['-a']
+ args = []
+ for name in affected_files:
+ if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
+ fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
+ fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)):
+ args.extend(_FindMatchingTemplates(name.split(os.sep)[-1],
+ _ListFilesInPublic()))
+ if fnmatch.fnmatch(name, '%s*' % API_PATH):
+ args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH),
+ _ListFilesInPublic()))
+ return args
+
+def _CheckHeadingIDs(input_api):
+ ids_re = re.compile('<h[23].*id=.*?>')
+ headings_re = re.compile('<h[23].*?>')
+ bad_files = []
+ for name in input_api.AbsoluteLocalPaths():
+ if not os.path.exists(name):
+ continue
+ if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or
+ fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)):
+ contents = input_api.ReadFile(name)
+ if (len(re.findall(headings_re, contents)) !=
+ len(re.findall(ids_re, contents))):
+ bad_files.append(name)
+ return bad_files
+
+def _CheckLinks(input_api, output_api, results):
+ for affected_file in input_api.AffectedFiles():
+ name = affected_file.LocalPath()
+ absolute_path = affected_file.AbsoluteLocalPath()
+ if not os.path.exists(absolute_path):
+ continue
+ if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
+ fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
+ fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or
+ fnmatch.fnmatch(name, '%s*' % API_PATH)):
+ contents = _ReadFile(absolute_path)
+ args = []
+ if input_api.platform == 'win32':
+ args = [input_api.python_executable]
+ args.extend([os.path.join('docs', 'server2', 'link_converter.py'),
+ '-o',
+ '-f',
+ absolute_path])
+ output = input_api.subprocess.check_output(
+ args,
+ cwd=input_api.PresubmitLocalPath(),
+ universal_newlines=True)
+ if output != contents:
+ changes = ''
+ for i, (line1, line2) in enumerate(
+ zip(contents.split('\n'), output.split('\n'))):
+ if line1 != line2:
+ changes = ('%s\nLine %d:\n-%s\n+%s\n' %
+ (changes, i + 1, line1, line2))
+ if changes:
+ results.append(output_api.PresubmitPromptWarning(
+ 'File %s may have an old-style <a> link to an API page. Please '
+ 'run docs/server2/link_converter.py to convert the link[s], or '
+ 'convert them manually.\n\nSuggested changes are: %s' %
+ (name, changes)))
+
+def _CheckChange(input_api, output_api):
+ results = [
+ output_api.PresubmitError('File %s needs an id for each heading.' % name)
+ for name in _CheckHeadingIDs(input_api)]
+ try:
+ integration_test = []
+ # From depot_tools/presubmit_canned_checks.py:529
+ if input_api.platform == 'win32':
+ integration_test = [input_api.python_executable]
+ integration_test.append(
+ os.path.join('docs', 'server2', 'integration_test.py'))
+ integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths()))
+ input_api.subprocess.check_call(integration_test,
+ cwd=input_api.PresubmitLocalPath())
+ except input_api.subprocess.CalledProcessError:
+ results.append(output_api.PresubmitError('IntegrationTest failed!'))
+
+ # TODO(kalman): Re-enable this check, or decide to delete it forever. Now
+ # that we have multiple directories it no longer works.
+ # See http://crbug.com/297178.
+ #_CheckLinks(input_api, output_api, results)
+
+ return results
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += _CheckChange(input_api, output_api)
+ return results
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _CheckChange(input_api, output_api)
diff --git a/chromium/chrome/common/extensions/PRESUBMIT_test.py b/chromium/chrome/common/extensions/PRESUBMIT_test.py
new file mode 100755
index 00000000000..7ac79b06820
--- /dev/null
+++ b/chromium/chrome/common/extensions/PRESUBMIT_test.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import unittest
+
+import PRESUBMIT
+
+EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
+DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
+SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
+PUBLIC_PATH = os.path.join(DOCS_PATH, 'templates', 'public')
+PRIVATE_PATH = os.path.join(DOCS_PATH, 'templates', 'private')
+INTROS_PATH = os.path.join(DOCS_PATH, 'templates', 'intros')
+ARTICLES_PATH = os.path.join(DOCS_PATH, 'templates', 'articles')
+
+class PRESUBMITTest(unittest.TestCase):
+ def testCreateIntegrationTestArgs(self):
+ input_files = [
+ os.path.join(EXTENSIONS_PATH, 'test.cc'),
+ os.path.join(EXTENSIONS_PATH, 'test2.cc'),
+ os.path.join('test', 'test.py')
+ ]
+ expected_files = []
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append(os.path.join('apps', 'fileSystem.html'))
+ input_files.append(os.path.join(EXTENSIONS_PATH, 'api', 'file_system.idl'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append(os.path.join('extensions', 'alarms.html'))
+ expected_files.append(os.path.join('apps', 'alarms.html'))
+ input_files.append(os.path.join(EXTENSIONS_PATH, 'api', 'alarms.json'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append('extensions/devtools_network.html')
+ input_files.append(os.path.join(EXTENSIONS_PATH,
+ 'api',
+ 'devtools',
+ 'network.json'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append(os.path.join('extensions', 'docs.html'))
+ expected_files.append(os.path.join('apps', 'docs.html'))
+ input_files.append(os.path.join(PUBLIC_PATH, 'extensions', 'docs.html'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append(os.path.join('extensions', 'bookmarks.html'))
+ input_files.append(os.path.join(INTROS_PATH, 'bookmarks.html'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append(os.path.join('extensions', 'i18n.html'))
+ expected_files.append(os.path.join('apps', 'i18n.html'))
+ input_files.append(os.path.join(INTROS_PATH, 'i18n.html'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ expected_files.append(os.path.join('apps', 'about_apps.html'))
+ input_files.append(os.path.join(ARTICLES_PATH, 'about_apps.html'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ input_files.append(os.path.join(PRIVATE_PATH, 'type.html'))
+ self.assertEqual([ '-a' ],
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ input_files.pop()
+ input_files.append(os.path.join(SERVER2_PATH, 'test.txt'))
+ self.assertEqual(expected_files,
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+ input_files.append(os.path.join(SERVER2_PATH, 'handler.py'))
+ self.assertEqual([ '-a' ],
+ PRESUBMIT._CreateIntegrationTestArgs(input_files))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/chrome/common/extensions/chrome_extensions_api_provider.cc b/chromium/chrome/common/extensions/chrome_extensions_api_provider.cc
new file mode 100644
index 00000000000..17c0c89d83d
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_extensions_api_provider.cc
@@ -0,0 +1,67 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/chrome_extensions_api_provider.h"
+
+#include "chrome/common/extensions/api/api_features.h"
+#include "chrome/common/extensions/api/generated_schemas.h"
+#include "chrome/common/extensions/api/manifest_features.h"
+#include "chrome/common/extensions/api/permission_features.h"
+#include "chrome/common/extensions/chrome_manifest_handlers.h"
+#include "chrome/common/extensions/permissions/chrome_api_permissions.h"
+#include "chrome/grit/common_resources.h"
+#include "extensions/common/features/json_feature_provider_source.h"
+#include "extensions/common/permissions/permissions_info.h"
+
+namespace extensions {
+
+ChromeExtensionsAPIProvider::ChromeExtensionsAPIProvider() {}
+ChromeExtensionsAPIProvider::~ChromeExtensionsAPIProvider() = default;
+
+void ChromeExtensionsAPIProvider::AddAPIFeatures(FeatureProvider* provider) {
+ AddChromeAPIFeatures(provider);
+}
+
+void ChromeExtensionsAPIProvider::AddManifestFeatures(
+ FeatureProvider* provider) {
+ AddChromeManifestFeatures(provider);
+}
+
+void ChromeExtensionsAPIProvider::AddPermissionFeatures(
+ FeatureProvider* provider) {
+ AddChromePermissionFeatures(provider);
+}
+
+void ChromeExtensionsAPIProvider::AddBehaviorFeatures(
+ FeatureProvider* provider) {
+ // Note: No chrome-specific behavior features.
+}
+
+void ChromeExtensionsAPIProvider::AddAPIJSONSources(
+ JSONFeatureProviderSource* json_source) {
+ json_source->LoadJSON(IDR_CHROME_EXTENSION_API_FEATURES);
+}
+
+bool ChromeExtensionsAPIProvider::IsAPISchemaGenerated(
+ const std::string& name) {
+ return api::ChromeGeneratedSchemas::IsGenerated(name);
+}
+
+base::StringPiece ChromeExtensionsAPIProvider::GetAPISchema(
+ const std::string& name) {
+ return api::ChromeGeneratedSchemas::Get(name);
+}
+
+void ChromeExtensionsAPIProvider::RegisterPermissions(
+ PermissionsInfo* permissions_info) {
+ permissions_info->RegisterPermissions(
+ chrome_api_permissions::GetPermissionInfos(),
+ chrome_api_permissions::GetPermissionAliases());
+}
+
+void ChromeExtensionsAPIProvider::RegisterManifestHandlers() {
+ RegisterChromeManifestHandlers();
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/chrome_extensions_api_provider.h b/chromium/chrome/common/extensions/chrome_extensions_api_provider.h
new file mode 100644
index 00000000000..e3595d5536e
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_extensions_api_provider.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_CHROME_EXTENSIONS_API_PROVIDER_H_
+#define CHROME_COMMON_EXTENSIONS_CHROME_EXTENSIONS_API_PROVIDER_H_
+
+#include "base/macros.h"
+#include "extensions/common/extensions_api_provider.h"
+
+namespace extensions {
+
+class ChromeExtensionsAPIProvider : public ExtensionsAPIProvider {
+ public:
+ ChromeExtensionsAPIProvider();
+ ~ChromeExtensionsAPIProvider() override;
+
+ // ExtensionsAPIProvider:
+ void AddAPIFeatures(FeatureProvider* provider) override;
+ void AddManifestFeatures(FeatureProvider* provider) override;
+ void AddPermissionFeatures(FeatureProvider* provider) override;
+ void AddBehaviorFeatures(FeatureProvider* provider) override;
+ void AddAPIJSONSources(JSONFeatureProviderSource* json_source) override;
+ bool IsAPISchemaGenerated(const std::string& name) override;
+ base::StringPiece GetAPISchema(const std::string& name) override;
+ void RegisterPermissions(PermissionsInfo* permissions_info) override;
+ void RegisterManifestHandlers() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeExtensionsAPIProvider);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_CHROME_EXTENSIONS_API_PROVIDER_H_
diff --git a/chromium/chrome/common/extensions/chrome_extensions_client.cc b/chromium/chrome/common/extensions/chrome_extensions_client.cc
new file mode 100644
index 00000000000..a57d93fed58
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_extensions_client.cc
@@ -0,0 +1,257 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/chrome_extensions_client.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
+#include "chrome/common/extensions/chrome_extensions_api_provider.h"
+#include "chrome/common/extensions/manifest_handlers/theme_handler.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/chromium_strings.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/core_extensions_api_provider.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/url_pattern.h"
+#include "extensions/common/url_pattern_set.h"
+#include "services/network/public/mojom/cors_origin_pattern.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+// TODO(battre): Delete the HTTP URL once the blacklist is downloaded via HTTPS.
+const char kExtensionBlocklistUrlPrefix[] =
+ "http://www.gstatic.com/chrome/extensions/blacklist";
+const char kExtensionBlocklistHttpsUrlPrefix[] =
+ "https://www.gstatic.com/chrome/extensions/blacklist";
+
+const char kThumbsWhiteListedExtension[] = "khopmbdjffemhegeeobelklnbglcdgfh";
+
+} // namespace
+
+ChromeExtensionsClient::ChromeExtensionsClient() {
+ AddAPIProvider(std::make_unique<ChromeExtensionsAPIProvider>());
+ AddAPIProvider(std::make_unique<CoreExtensionsAPIProvider>());
+}
+
+ChromeExtensionsClient::~ChromeExtensionsClient() {
+}
+
+void ChromeExtensionsClient::Initialize() {
+ // Set up the scripting whitelist.
+ // Whitelist ChromeVox, an accessibility extension from Google that needs
+ // the ability to script webui pages. This is temporary and is not
+ // meant to be a general solution.
+ // TODO(dmazzoni): remove this once we have an extension API that
+ // allows any extension to request read-only access to webui pages.
+ scripting_whitelist_.push_back(extension_misc::kChromeVoxExtensionId);
+ InitializeWebStoreUrls(base::CommandLine::ForCurrentProcess());
+}
+
+void ChromeExtensionsClient::InitializeWebStoreUrls(
+ base::CommandLine* command_line) {
+ if (command_line->HasSwitch(switches::kAppsGalleryURL)) {
+ webstore_base_url_ =
+ GURL(command_line->GetSwitchValueASCII(switches::kAppsGalleryURL));
+ } else {
+ webstore_base_url_ = GURL(extension_urls::kChromeWebstoreBaseURL);
+ }
+ if (command_line->HasSwitch(switches::kAppsGalleryUpdateURL)) {
+ webstore_update_url_ = GURL(
+ command_line->GetSwitchValueASCII(switches::kAppsGalleryUpdateURL));
+ } else {
+ webstore_update_url_ = GURL(extension_urls::GetDefaultWebstoreUpdateUrl());
+ }
+}
+
+const PermissionMessageProvider&
+ChromeExtensionsClient::GetPermissionMessageProvider() const {
+ return permission_message_provider_;
+}
+
+const std::string ChromeExtensionsClient::GetProductName() {
+ return l10n_util::GetStringUTF8(IDS_PRODUCT_NAME);
+}
+
+void ChromeExtensionsClient::FilterHostPermissions(
+ const URLPatternSet& hosts,
+ URLPatternSet* new_hosts,
+ PermissionIDSet* permissions) const {
+ // When editing this function, be sure to add the same functionality to
+ // FilterHostPermissions() above.
+ for (auto i = hosts.begin(); i != hosts.end(); ++i) {
+ // Filters out every URL pattern that matches chrome:// scheme.
+ if (i->scheme() == content::kChromeUIScheme) {
+ // chrome://favicon is the only URL for chrome:// scheme that we
+ // want to support. We want to deprecate the "chrome" scheme.
+ // We should not add any additional "host" here.
+ if (GURL(chrome::kChromeUIFaviconURL).host() != i->host())
+ continue;
+ permissions->insert(APIPermission::kFavicon);
+ } else {
+ new_hosts->AddPattern(*i);
+ }
+ }
+}
+
+void ChromeExtensionsClient::SetScriptingWhitelist(
+ const ExtensionsClient::ScriptingWhitelist& whitelist) {
+ scripting_whitelist_ = whitelist;
+}
+
+const ExtensionsClient::ScriptingWhitelist&
+ChromeExtensionsClient::GetScriptingWhitelist() const {
+ return scripting_whitelist_;
+}
+
+URLPatternSet ChromeExtensionsClient::GetPermittedChromeSchemeHosts(
+ const Extension* extension,
+ const APIPermissionSet& api_permissions) const {
+ URLPatternSet hosts;
+ // Regular extensions are only allowed access to chrome://favicon.
+ hosts.AddPattern(URLPattern(URLPattern::SCHEME_CHROMEUI,
+ chrome::kChromeUIFaviconURL));
+
+ // Experimental extensions are also allowed chrome://thumb.
+ //
+ // TODO: A public API should be created for retrieving thumbnails.
+ // See http://crbug.com/222856. A temporary hack is implemented here to
+ // make chrome://thumbs available to NTP Russia extension as
+ // non-experimental.
+ if ((api_permissions.find(APIPermission::kExperimental) !=
+ api_permissions.end()) ||
+ (extension->id() == kThumbsWhiteListedExtension &&
+ extension->from_webstore())) {
+ hosts.AddPattern(URLPattern(URLPattern::SCHEME_CHROMEUI,
+ chrome::kChromeUIThumbnailURL));
+ }
+ return hosts;
+}
+
+bool ChromeExtensionsClient::IsScriptableURL(
+ const GURL& url, std::string* error) const {
+ // The gallery is special-cased as a restricted URL for scripting to prevent
+ // access to special JS bindings we expose to the gallery (and avoid things
+ // like extensions removing the "report abuse" link).
+ // TODO(erikkay): This seems like the wrong test. Shouldn't we we testing
+ // against the store app extent?
+ GURL store_url(extension_urls::GetWebstoreLaunchURL());
+ if (url.DomainIs(store_url.host())) {
+ if (error)
+ *error = manifest_errors::kCannotScriptGallery;
+ return false;
+ }
+ return true;
+}
+
+const GURL& ChromeExtensionsClient::GetWebstoreBaseURL() const {
+ return webstore_base_url_;
+}
+
+const GURL& ChromeExtensionsClient::GetWebstoreUpdateURL() const {
+ return webstore_update_url_;
+}
+
+bool ChromeExtensionsClient::IsBlacklistUpdateURL(const GURL& url) const {
+ // The extension blacklist URL is returned from the update service and
+ // therefore not determined by Chromium. If the location of the blacklist file
+ // ever changes, we need to update this function. A DCHECK in the
+ // ExtensionUpdater ensures that we notice a change. This is the full URL
+ // of a blacklist:
+ // http://www.gstatic.com/chrome/extensions/blacklist/l_0_0_0_7.txt
+ return base::StartsWith(url.spec(), kExtensionBlocklistUrlPrefix,
+ base::CompareCase::SENSITIVE) ||
+ base::StartsWith(url.spec(), kExtensionBlocklistHttpsUrlPrefix,
+ base::CompareCase::SENSITIVE);
+}
+
+std::set<base::FilePath> ChromeExtensionsClient::GetBrowserImagePaths(
+ const Extension* extension) {
+ std::set<base::FilePath> image_paths =
+ ExtensionsClient::GetBrowserImagePaths(extension);
+
+ // Theme images
+ const base::DictionaryValue* theme_images = ThemeInfo::GetImages(extension);
+ if (theme_images) {
+ for (base::DictionaryValue::Iterator it(*theme_images); !it.IsAtEnd();
+ it.Advance()) {
+ base::FilePath::StringType path;
+ if (it.value().GetAsString(&path))
+ image_paths.insert(base::FilePath(path));
+ }
+ }
+
+ const ActionInfo* action = ActionInfo::GetAnyActionInfo(extension);
+ if (action && !action->default_icon.empty())
+ action->default_icon.GetPaths(&image_paths);
+
+ return image_paths;
+}
+
+bool ChromeExtensionsClient::ExtensionAPIEnabledInExtensionServiceWorkers()
+ const {
+ return GetCurrentChannel() <=
+ extension_misc::kMinChannelForServiceWorkerBasedExtension;
+}
+
+void ChromeExtensionsClient::AddOriginAccessPermissions(
+ const Extension& extension,
+ bool is_extension_active,
+ std::vector<network::mojom::CorsOriginPatternPtr>* origin_patterns) const {
+ // Allow component extensions to access chrome://theme/.
+ //
+ // We don't want to grant these permissions to inactive component extensions,
+ // to avoid granting them in "unblessed" (non-extension) processes. If a
+ // component extension somehow starts as inactive and becomes active later,
+ // we'll re-init the origin permissions, so there's no danger in being
+ // conservative. Components shouldn't be subject to enterprise policy controls
+ // or blocking access to the webstore so they get the highest priority
+ // allowlist entry.
+ if (extensions::Manifest::IsComponentLocation(extension.location()) &&
+ is_extension_active) {
+ origin_patterns->push_back(network::mojom::CorsOriginPattern::New(
+ content::kChromeUIScheme, chrome::kChromeUIThemeHost, /*port=*/0,
+ network::mojom::CorsDomainMatchMode::kDisallowSubdomains,
+ network::mojom::CorsPortMatchMode::kAllowAnyPort,
+ network::mojom::CorsOriginAccessMatchPriority::kMaxPriority));
+ }
+
+ // TODO(jstritar): We should try to remove this special case. Also, these
+ // whitelist entries need to be updated when the kManagement permission
+ // changes.
+ if (is_extension_active && extension.permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kManagement)) {
+ origin_patterns->push_back(network::mojom::CorsOriginPattern::New(
+ content::kChromeUIScheme, chrome::kChromeUIExtensionIconHost,
+ /*port=*/0, network::mojom::CorsDomainMatchMode::kDisallowSubdomains,
+ network::mojom::CorsPortMatchMode::kAllowAnyPort,
+ network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/chrome_extensions_client.h b/chromium/chrome/common/extensions/chrome_extensions_client.h
new file mode 100644
index 00000000000..737c9708922
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_extensions_client.h
@@ -0,0 +1,70 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_CHROME_EXTENSIONS_CLIENT_H_
+#define CHROME_COMMON_EXTENSIONS_CHROME_EXTENSIONS_CLIENT_H_
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
+#include "extensions/common/extensions_client.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+// The implementation of ExtensionsClient for Chrome, which encapsulates the
+// global knowledge of features, permissions, and manifest fields.
+class ChromeExtensionsClient : public ExtensionsClient {
+ public:
+ ChromeExtensionsClient();
+ ~ChromeExtensionsClient() override;
+
+ void Initialize() override;
+
+ void InitializeWebStoreUrls(base::CommandLine* command_line) override;
+
+ const PermissionMessageProvider& GetPermissionMessageProvider()
+ const override;
+ const std::string GetProductName() override;
+ void FilterHostPermissions(const URLPatternSet& hosts,
+ URLPatternSet* new_hosts,
+ PermissionIDSet* permissions) const override;
+ void SetScriptingWhitelist(const ScriptingWhitelist& whitelist) override;
+ const ScriptingWhitelist& GetScriptingWhitelist() const override;
+ URLPatternSet GetPermittedChromeSchemeHosts(
+ const Extension* extension,
+ const APIPermissionSet& api_permissions) const override;
+ bool IsScriptableURL(const GURL& url, std::string* error) const override;
+ const GURL& GetWebstoreBaseURL() const override;
+ const GURL& GetWebstoreUpdateURL() const override;
+ bool IsBlacklistUpdateURL(const GURL& url) const override;
+ std::set<base::FilePath> GetBrowserImagePaths(
+ const Extension* extension) override;
+ bool ExtensionAPIEnabledInExtensionServiceWorkers() const override;
+ void AddOriginAccessPermissions(
+ const Extension& extension,
+ bool is_extension_active,
+ std::vector<network::mojom::CorsOriginPatternPtr>* origin_patterns)
+ const override;
+
+ private:
+ const ChromePermissionMessageProvider permission_message_provider_;
+
+ // A whitelist of extensions that can script anywhere. Do not add to this
+ // list (except in tests) without consulting the Extensions team first.
+ // Note: Component extensions have this right implicitly and do not need to be
+ // added to this list.
+ ScriptingWhitelist scripting_whitelist_;
+
+ GURL webstore_base_url_;
+ GURL webstore_update_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeExtensionsClient);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_CHROME_EXTENSIONS_CLIENT_H_
diff --git a/chromium/chrome/common/extensions/chrome_extensions_client_unittest.cc b/chromium/chrome/common/extensions/chrome_extensions_client_unittest.cc
new file mode 100644
index 00000000000..5f5c47e710b
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_extensions_client_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/chrome_extensions_client.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class ChromeExtensionsClientTest : public testing::Test {
+ public:
+ void SetUp() override {
+ extensions_client_.reset(new ChromeExtensionsClient());
+ ExtensionsClient::Set(extensions_client_.get());
+ }
+
+ private:
+ std::unique_ptr<ChromeExtensionsClient> extensions_client_;
+};
+
+// Test that a browser action extension returns a path to an icon.
+TEST_F(ChromeExtensionsClientTest, GetBrowserImagePaths) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+ install_dir = install_dir.AppendASCII("extensions")
+ .AppendASCII("api_test")
+ .AppendASCII("browser_action")
+ .AppendASCII("basics");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ ASSERT_TRUE(extension.get());
+
+ // The extension contains one icon.
+ std::set<base::FilePath> paths =
+ ExtensionsClient::Get()->GetBrowserImagePaths(extension.get());
+ ASSERT_EQ(1u, paths.size());
+ EXPECT_EQ("icon.png", paths.begin()->BaseName().AsUTF8Unsafe());
+}
+
+// Test that extensions with zero-length action icons will not load.
+TEST_F(ChromeExtensionsClientTest, CheckZeroLengthActionIconFiles) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+
+ // Try to install an extension with a zero-length browser action icon file.
+ base::FilePath ext_dir = install_dir.AppendASCII("extensions")
+ .AppendASCII("bad")
+ .AppendASCII("Extensions")
+ .AppendASCII("gggggggggggggggggggggggggggggggg");
+
+ std::string error;
+ scoped_refptr<Extension> extension2(file_util::LoadExtension(
+ ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ EXPECT_FALSE(extension2.get());
+ EXPECT_STREQ("Could not load icon 'icon.png' for browser action.",
+ error.c_str());
+
+ // Try to install an extension with a zero-length page action icon file.
+ ext_dir = install_dir.AppendASCII("extensions")
+ .AppendASCII("bad")
+ .AppendASCII("Extensions")
+ .AppendASCII("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
+
+ scoped_refptr<Extension> extension3(file_util::LoadExtension(
+ ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ EXPECT_FALSE(extension3.get());
+ EXPECT_STREQ("Could not load icon 'icon.png' for page action.",
+ error.c_str());
+}
+
+// Test that the ManifestHandlerRegistry handler map hasn't overflowed.
+// If this test fails, increase ManifestHandlerRegistry::kHandlerMax.
+TEST_F(ChromeExtensionsClientTest, CheckManifestHandlerRegistryForOverflow) {
+ ManifestHandlerRegistry* registry = ManifestHandlerRegistry::Get();
+ ASSERT_TRUE(registry);
+ ASSERT_LT(0u, registry->handlers_.size());
+ EXPECT_LE(registry->handlers_.size(), ManifestHandlerRegistry::kHandlerMax);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/chrome_manifest_handlers.cc b/chromium/chrome/common/extensions/chrome_manifest_handlers.cc
new file mode 100644
index 00000000000..89b30325e30
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_manifest_handlers.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/chrome_manifest_handlers.h"
+
+#include <memory>
+
+#include "build/build_config.h"
+#include "chrome/common/extensions/api/commands/commands_handler.h"
+#include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
+#include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h"
+#include "chrome/common/extensions/api/spellcheck/spellcheck_handler.h"
+#include "chrome/common/extensions/api/storage/storage_schema_manifest_handler.h"
+#include "chrome/common/extensions/api/system_indicator/system_indicator_handler.h"
+#include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h"
+#include "chrome/common/extensions/chrome_manifest_url_handlers.h"
+#include "chrome/common/extensions/manifest_handlers/app_icon_color_info.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/extensions/manifest_handlers/app_theme_color_info.h"
+#include "chrome/common/extensions/manifest_handlers/extension_action_handler.h"
+#include "chrome/common/extensions/manifest_handlers/linked_app_icons.h"
+#include "chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.h"
+#include "chrome/common/extensions/manifest_handlers/natively_connectable_handler.h"
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+#include "chrome/common/extensions/manifest_handlers/theme_handler.h"
+#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
+#include "extensions/common/manifest_handlers/app_isolation_info.h"
+#include "extensions/common/manifest_handlers/automation.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/manifest_url_handlers.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
+#include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
+#include "chrome/common/extensions/api/input_ime/input_components_handler.h"
+#endif
+
+namespace extensions {
+
+void RegisterChromeManifestHandlers() {
+ // TODO(devlin): Pass in |registry| rather than Get()ing it.
+ ManifestHandlerRegistry* registry = ManifestHandlerRegistry::Get();
+
+ DCHECK(!ManifestHandler::IsRegistrationFinalized());
+
+ registry->RegisterHandler(std::make_unique<AboutPageHandler>());
+ registry->RegisterHandler(std::make_unique<AppIconColorHandler>());
+ registry->RegisterHandler(std::make_unique<AppThemeColorHandler>());
+ registry->RegisterHandler(std::make_unique<AppIsolationHandler>());
+ registry->RegisterHandler(std::make_unique<AppLaunchManifestHandler>());
+ registry->RegisterHandler(std::make_unique<AutomationHandler>());
+ registry->RegisterHandler(std::make_unique<CommandsHandler>());
+ registry->RegisterHandler(std::make_unique<DevToolsPageHandler>());
+ registry->RegisterHandler(std::make_unique<ExtensionActionHandler>());
+ registry->RegisterHandler(std::make_unique<HomepageURLHandler>());
+ registry->RegisterHandler(std::make_unique<LinkedAppIconsHandler>());
+ registry->RegisterHandler(std::make_unique<MinimumChromeVersionChecker>());
+ registry->RegisterHandler(std::make_unique<NativelyConnectableHandler>());
+ registry->RegisterHandler(std::make_unique<OmniboxHandler>());
+ registry->RegisterHandler(std::make_unique<OptionsPageManifestHandler>());
+ registry->RegisterHandler(std::make_unique<SettingsOverridesHandler>());
+ registry->RegisterHandler(std::make_unique<SpellcheckHandler>());
+ registry->RegisterHandler(std::make_unique<StorageSchemaManifestHandler>());
+ registry->RegisterHandler(std::make_unique<SystemIndicatorHandler>());
+ registry->RegisterHandler(std::make_unique<ThemeHandler>());
+ registry->RegisterHandler(std::make_unique<TtsEngineManifestHandler>());
+ registry->RegisterHandler(std::make_unique<UIOverridesHandler>());
+ registry->RegisterHandler(std::make_unique<UrlHandlersParser>());
+ registry->RegisterHandler(std::make_unique<URLOverridesHandler>());
+#if defined(OS_CHROMEOS)
+ registry->RegisterHandler(std::make_unique<FileBrowserHandlerParser>());
+ registry->RegisterHandler(
+ std::make_unique<FileSystemProviderCapabilitiesHandler>());
+ registry->RegisterHandler(std::make_unique<InputComponentsHandler>());
+#endif
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/chrome_manifest_handlers.h b/chromium/chrome/common/extensions/chrome_manifest_handlers.h
new file mode 100644
index 00000000000..03d9a5fbb71
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_manifest_handlers.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_CHROME_MANIFEST_HANDLERS_H_
+#define CHROME_COMMON_EXTENSIONS_CHROME_MANIFEST_HANDLERS_H_
+
+namespace extensions {
+
+// Registers all manifest handlers used in Chrome. Should be called
+// once in each process. See also extensions/common/common_manifest_handlers.h.
+void RegisterChromeManifestHandlers();
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_CHROME_MANIFEST_HANDLERS_H_
diff --git a/chromium/chrome/common/extensions/chrome_manifest_url_handlers.cc b/chromium/chrome/common/extensions/chrome_manifest_url_handlers.cc
new file mode 100644
index 00000000000..00126be870a
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_manifest_url_handlers.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/chrome_manifest_url_handlers.h"
+
+#include <memory>
+
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/url_constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "extensions/common/permissions/api_permission.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/keyboard/ui/resources/keyboard_resource_util.h"
+#endif
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+const char kOverrideExtentUrlPatternFormat[] = "chrome://%s/*";
+
+} // namespace
+
+namespace chrome_manifest_urls {
+const GURL& GetDevToolsPage(const Extension* extension) {
+ return ManifestURL::Get(extension, keys::kDevToolsPage);
+}
+}
+
+URLOverrides::URLOverrides() {
+}
+
+URLOverrides::~URLOverrides() {
+}
+
+static base::LazyInstance<URLOverrides::URLOverrideMap>::DestructorAtExit
+ g_empty_url_overrides = LAZY_INSTANCE_INITIALIZER;
+
+// static
+const URLOverrides::URLOverrideMap& URLOverrides::GetChromeURLOverrides(
+ const Extension* extension) {
+ URLOverrides* url_overrides = static_cast<URLOverrides*>(
+ extension->GetManifestData(keys::kChromeURLOverrides));
+ return url_overrides ? url_overrides->chrome_url_overrides_
+ : g_empty_url_overrides.Get();
+}
+
+DevToolsPageHandler::DevToolsPageHandler() {
+}
+
+DevToolsPageHandler::~DevToolsPageHandler() {
+}
+
+bool DevToolsPageHandler::Parse(Extension* extension, base::string16* error) {
+ std::unique_ptr<ManifestURL> manifest_url(new ManifestURL);
+ std::string devtools_str;
+ if (!extension->manifest()->GetString(keys::kDevToolsPage, &devtools_str)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidDevToolsPage);
+ return false;
+ }
+ manifest_url->url_ = extension->GetResourceURL(devtools_str);
+ extension->SetManifestData(keys::kDevToolsPage, std::move(manifest_url));
+ PermissionsParser::AddAPIPermission(extension, APIPermission::kDevtools);
+ return true;
+}
+
+base::span<const char* const> DevToolsPageHandler::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kDevToolsPage};
+ return kKeys;
+}
+
+URLOverridesHandler::URLOverridesHandler() {
+}
+
+URLOverridesHandler::~URLOverridesHandler() {
+}
+
+bool URLOverridesHandler::Parse(Extension* extension, base::string16* error) {
+ const base::DictionaryValue* overrides = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kChromeURLOverrides,
+ &overrides)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidChromeURLOverrides);
+ return false;
+ }
+ std::unique_ptr<URLOverrides> url_overrides(new URLOverrides);
+ // Validate that the overrides are all strings
+ for (base::DictionaryValue::Iterator iter(*overrides); !iter.IsAtEnd();
+ iter.Advance()) {
+ const std::string& page = iter.key();
+ std::string val;
+ // Restrict override pages to a list of supported URLs.
+ bool is_allowed_host = page == chrome::kChromeUINewTabHost ||
+ page == chrome::kChromeUIBookmarksHost ||
+ page == chrome::kChromeUIHistoryHost;
+#if defined(OS_CHROMEOS)
+ is_allowed_host = is_allowed_host ||
+ page == chrome::kChromeUIActivationMessageHost ||
+ page == keyboard::kKeyboardHost;
+#endif
+
+ if (!is_allowed_host || !iter.value().GetAsString(&val)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidChromeURLOverrides);
+ return false;
+ }
+ // Replace the entry with a fully qualified chrome-extension:// URL.
+ url_overrides->chrome_url_overrides_[page] = extension->GetResourceURL(val);
+
+ // For component extensions, add override URL to extent patterns.
+ if (extension->is_legacy_packaged_app() &&
+ extension->location() == Manifest::COMPONENT) {
+ URLPattern pattern(URLPattern::SCHEME_CHROMEUI);
+ std::string url =
+ base::StringPrintf(kOverrideExtentUrlPatternFormat, page.c_str());
+ if (pattern.Parse(url) != URLPattern::ParseResult::kSuccess) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidURLPatternError, url);
+ return false;
+ }
+ extension->AddWebExtentPattern(pattern);
+ }
+ }
+
+ // An extension may override at most one page.
+ if (overrides->size() > 1) {
+ *error = base::ASCIIToUTF16(errors::kMultipleOverrides);
+ return false;
+ }
+
+ // If this is an NTP override extension, add the NTP override permission.
+ if (url_overrides->chrome_url_overrides_.count(chrome::kChromeUINewTabHost)) {
+ PermissionsParser::AddAPIPermission(extension,
+ APIPermission::kNewTabPageOverride);
+ }
+
+ extension->SetManifestData(keys::kChromeURLOverrides,
+ std::move(url_overrides));
+
+ return true;
+}
+
+bool URLOverridesHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ const URLOverrides::URLOverrideMap& overrides =
+ URLOverrides::GetChromeURLOverrides(extension);
+ if (overrides.empty())
+ return true;
+
+ for (const auto& entry : overrides) {
+ base::FilePath relative_path =
+ file_util::ExtensionURLToRelativeFilePath(entry.second);
+ base::FilePath resource_path =
+ extension->GetResource(relative_path).GetFilePath();
+ if (resource_path.empty() || !base::PathExists(resource_path)) {
+ *error = ErrorUtils::FormatErrorMessage(errors::kFileNotFound,
+ relative_path.AsUTF8Unsafe());
+ return false;
+ }
+ }
+ return true;
+}
+
+base::span<const char* const> URLOverridesHandler::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kChromeURLOverrides};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/chrome_manifest_url_handlers.h b/chromium/chrome/common/extensions/chrome_manifest_url_handlers.h
new file mode 100644
index 00000000000..bacd703425a
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_manifest_url_handlers.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_CHROME_MANIFEST_URL_HANDLERS_H_
+#define CHROME_COMMON_EXTENSIONS_CHROME_MANIFEST_URL_HANDLERS_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+// Chrome-specific extension manifest URL handlers.
+
+namespace extensions {
+
+namespace chrome_manifest_urls {
+const GURL& GetDevToolsPage(const Extension* extension);
+}
+
+// Stores Chrome URL overrides specified in extensions manifests.
+struct URLOverrides : public Extension::ManifestData {
+ typedef std::map<const std::string, GURL> URLOverrideMap;
+
+ URLOverrides();
+ ~URLOverrides() override;
+
+ static const URLOverrideMap& GetChromeURLOverrides(
+ const Extension* extension);
+
+ // A map of chrome:// hostnames (newtab, downloads, etc.) to Extension URLs
+ // which override the handling of those URLs. (see ExtensionOverrideUI).
+ URLOverrideMap chrome_url_overrides_;
+};
+
+// Parses the "devtools_page" manifest key.
+class DevToolsPageHandler : public ManifestHandler {
+ public:
+ DevToolsPageHandler();
+ ~DevToolsPageHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsPageHandler);
+};
+
+// Parses the "chrome_url_overrides" manifest key.
+class URLOverridesHandler : public ManifestHandler {
+ public:
+ URLOverridesHandler();
+ ~URLOverridesHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(URLOverridesHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_CHROME_MANIFEST_URL_HANDLERS_H_
diff --git a/chromium/chrome/common/extensions/chrome_manifest_url_handlers_unittest.cc b/chromium/chrome/common/extensions/chrome_manifest_url_handlers_unittest.cc
new file mode 100644
index 00000000000..458d6782520
--- /dev/null
+++ b/chromium/chrome/common/extensions/chrome_manifest_url_handlers_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_temp_dir.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+TEST(ChromeURLOverridesHandlerTest, TestFileMissing) {
+ DictionaryBuilder manifest;
+ manifest.Set("name", "ntp override");
+ manifest.Set("version", "0.1");
+ manifest.Set("manifest_version", 2);
+ manifest.Set("description", "description");
+ manifest.Set("chrome_url_overrides",
+ DictionaryBuilder().Set("newtab", "newtab.html").Build());
+ std::unique_ptr<base::DictionaryValue> manifest_value = manifest.Build();
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ scoped_refptr<Extension> extension =
+ Extension::Create(dir.GetPath(), Manifest::INTERNAL, *manifest_value,
+ Extension::NO_FLAGS, std::string(), &error);
+ ASSERT_TRUE(extension);
+ EXPECT_FALSE(
+ file_util::ValidateExtension(extension.get(), &error, &warnings));
+ EXPECT_EQ(ErrorUtils::FormatErrorMessage(manifest_errors::kFileNotFound,
+ "newtab.html"),
+ error);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/command.cc b/chromium/chrome/common/extensions/command.cc
new file mode 100644
index 00000000000..aba488f8c64
--- /dev/null
+++ b/chromium/chrome/common/extensions/command.cc
@@ -0,0 +1,542 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/command.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "ui/base/accelerators/media_keys_listener.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+namespace values = manifest_values;
+
+namespace {
+
+static const char kMissing[] = "Missing";
+
+static const char kCommandKeyNotSupported[] =
+ "Command key is not supported. Note: Ctrl means Command on Mac";
+
+#if defined(OS_CHROMEOS)
+// ChromeOS supports an additional modifier 'Search', which can result in longer
+// sequences.
+static const int kMaxTokenSize = 4;
+#else
+static const int kMaxTokenSize = 3;
+#endif // OS_CHROMEOS
+
+Command::Type GetCommandType(const std::string& command_name) {
+ if (command_name == values::kPageActionCommandEvent)
+ return Command::Type::kPageAction;
+ if (command_name == values::kBrowserActionCommandEvent)
+ return Command::Type::kBrowserAction;
+ return Command::Type::kNamed;
+}
+
+bool IsNamedCommand(const std::string& command_name) {
+ return GetCommandType(command_name) == Command::Type::kNamed;
+}
+
+bool DoesRequireModifier(const std::string& accelerator) {
+ return accelerator != values::kKeyMediaNextTrack &&
+ accelerator != values::kKeyMediaPlayPause &&
+ accelerator != values::kKeyMediaPrevTrack &&
+ accelerator != values::kKeyMediaStop;
+}
+
+// Parse an |accelerator| for a given platform (specified by |platform_key|) and
+// return the result as a ui::Accelerator if successful, or VKEY_UNKNOWN if not.
+// |index| is used when constructing an |error| messages to show which command
+// in the manifest is failing and |should_parse_media_keys| specifies whether
+// media keys are to be considered for parsing.
+// Note: If the parsing rules here are changed, make sure to update the
+// corresponding extension_command_list.js validation, which validates the user
+// input for chrome://extensions/configureCommands.
+ui::Accelerator ParseImpl(const std::string& accelerator,
+ const std::string& platform_key,
+ int index,
+ bool should_parse_media_keys,
+ base::string16* error) {
+ error->clear();
+ if (platform_key != values::kKeybindingPlatformWin &&
+ platform_key != values::kKeybindingPlatformMac &&
+ platform_key != values::kKeybindingPlatformChromeOs &&
+ platform_key != values::kKeybindingPlatformLinux &&
+ platform_key != values::kKeybindingPlatformDefault) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingUnknownPlatform, base::NumberToString(index),
+ platform_key);
+ return ui::Accelerator();
+ }
+
+ std::vector<std::string> tokens = base::SplitString(
+ accelerator, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (tokens.size() == 0 ||
+ (tokens.size() == 1 && DoesRequireModifier(accelerator)) ||
+ tokens.size() > kMaxTokenSize) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidKeyBinding,
+ base::NumberToString(index),
+ platform_key, accelerator);
+ return ui::Accelerator();
+ }
+
+ // Now, parse it into an accelerator.
+ int modifiers = ui::EF_NONE;
+ ui::KeyboardCode key = ui::VKEY_UNKNOWN;
+ for (size_t i = 0; i < tokens.size(); i++) {
+ if (tokens[i] == values::kKeyCtrl) {
+ modifiers |= ui::EF_CONTROL_DOWN;
+ } else if (tokens[i] == values::kKeyCommand) {
+ if (platform_key == values::kKeybindingPlatformMac) {
+ // Either the developer specified Command+foo in the manifest for Mac or
+ // they specified Ctrl and it got normalized to Command (to get Ctrl on
+ // Mac the developer has to specify MacCtrl). Therefore we treat this
+ // as Command.
+ modifiers |= ui::EF_COMMAND_DOWN;
+#if defined(OS_MACOSX)
+ } else if (platform_key == values::kKeybindingPlatformDefault) {
+ // If we see "Command+foo" in the Default section it can mean two
+ // things, depending on the platform:
+ // The developer specified "Ctrl+foo" for Default and it got normalized
+ // on Mac to "Command+foo". This is fine. Treat it as Command.
+ modifiers |= ui::EF_COMMAND_DOWN;
+#endif
+ } else {
+ // No other platform supports Command.
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+ } else if (tokens[i] == values::kKeySearch) {
+ // Search is a special modifier only on ChromeOS and maps to 'Command'.
+ if (platform_key == values::kKeybindingPlatformChromeOs) {
+ modifiers |= ui::EF_COMMAND_DOWN;
+ } else {
+ // No other platform supports Search.
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+ } else if (tokens[i] == values::kKeyAlt) {
+ modifiers |= ui::EF_ALT_DOWN;
+ } else if (tokens[i] == values::kKeyShift) {
+ modifiers |= ui::EF_SHIFT_DOWN;
+ } else if (tokens[i].size() == 1 || // A-Z, 0-9.
+ tokens[i] == values::kKeyComma ||
+ tokens[i] == values::kKeyPeriod ||
+ tokens[i] == values::kKeyUp ||
+ tokens[i] == values::kKeyDown ||
+ tokens[i] == values::kKeyLeft ||
+ tokens[i] == values::kKeyRight ||
+ tokens[i] == values::kKeyIns ||
+ tokens[i] == values::kKeyDel ||
+ tokens[i] == values::kKeyHome ||
+ tokens[i] == values::kKeyEnd ||
+ tokens[i] == values::kKeyPgUp ||
+ tokens[i] == values::kKeyPgDwn ||
+ tokens[i] == values::kKeySpace ||
+ tokens[i] == values::kKeyTab ||
+ tokens[i] == values::kKeyMediaNextTrack ||
+ tokens[i] == values::kKeyMediaPlayPause ||
+ tokens[i] == values::kKeyMediaPrevTrack ||
+ tokens[i] == values::kKeyMediaStop) {
+ if (key != ui::VKEY_UNKNOWN) {
+ // Multiple key assignments.
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+
+ if (tokens[i] == values::kKeyComma) {
+ key = ui::VKEY_OEM_COMMA;
+ } else if (tokens[i] == values::kKeyPeriod) {
+ key = ui::VKEY_OEM_PERIOD;
+ } else if (tokens[i] == values::kKeyUp) {
+ key = ui::VKEY_UP;
+ } else if (tokens[i] == values::kKeyDown) {
+ key = ui::VKEY_DOWN;
+ } else if (tokens[i] == values::kKeyLeft) {
+ key = ui::VKEY_LEFT;
+ } else if (tokens[i] == values::kKeyRight) {
+ key = ui::VKEY_RIGHT;
+ } else if (tokens[i] == values::kKeyIns) {
+ key = ui::VKEY_INSERT;
+ } else if (tokens[i] == values::kKeyDel) {
+ key = ui::VKEY_DELETE;
+ } else if (tokens[i] == values::kKeyHome) {
+ key = ui::VKEY_HOME;
+ } else if (tokens[i] == values::kKeyEnd) {
+ key = ui::VKEY_END;
+ } else if (tokens[i] == values::kKeyPgUp) {
+ key = ui::VKEY_PRIOR;
+ } else if (tokens[i] == values::kKeyPgDwn) {
+ key = ui::VKEY_NEXT;
+ } else if (tokens[i] == values::kKeySpace) {
+ key = ui::VKEY_SPACE;
+ } else if (tokens[i] == values::kKeyTab) {
+ key = ui::VKEY_TAB;
+ } else if (tokens[i] == values::kKeyMediaNextTrack &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_NEXT_TRACK;
+ } else if (tokens[i] == values::kKeyMediaPlayPause &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_PLAY_PAUSE;
+ } else if (tokens[i] == values::kKeyMediaPrevTrack &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_PREV_TRACK;
+ } else if (tokens[i] == values::kKeyMediaStop &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_STOP;
+ } else if (tokens[i].size() == 1 && base::IsAsciiUpper(tokens[i][0])) {
+ key = static_cast<ui::KeyboardCode>(ui::VKEY_A + (tokens[i][0] - 'A'));
+ } else if (tokens[i].size() == 1 && base::IsAsciiDigit(tokens[i][0])) {
+ key = static_cast<ui::KeyboardCode>(ui::VKEY_0 + (tokens[i][0] - '0'));
+ } else {
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidKeyBinding,
+ base::NumberToString(index),
+ platform_key, accelerator);
+ return ui::Accelerator();
+ }
+ }
+
+ bool command = (modifiers & ui::EF_COMMAND_DOWN) != 0;
+ bool ctrl = (modifiers & ui::EF_CONTROL_DOWN) != 0;
+ bool alt = (modifiers & ui::EF_ALT_DOWN) != 0;
+ bool shift = (modifiers & ui::EF_SHIFT_DOWN) != 0;
+
+ // We support Ctrl+foo, Alt+foo, Ctrl+Shift+foo, Alt+Shift+foo, but not
+ // Ctrl+Alt+foo and not Shift+foo either. For a more detailed reason why we
+ // don't support Ctrl+Alt+foo see this article:
+ // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx.
+ // On Mac Command can also be used in combination with Shift or on its own,
+ // as a modifier.
+ if (key == ui::VKEY_UNKNOWN || (ctrl && alt) || (command && alt) ||
+ (shift && !ctrl && !alt && !command)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidKeyBinding,
+ base::NumberToString(index),
+ platform_key, accelerator);
+ return ui::Accelerator();
+ }
+
+ if (ui::MediaKeysListener::IsMediaKeycode(key) &&
+ (shift || ctrl || alt || command)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingMediaKeyWithModifier,
+ base::NumberToString(index), platform_key, accelerator);
+ return ui::Accelerator();
+ }
+
+ return ui::Accelerator(key, modifiers);
+}
+
+// For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
+// platforms leave the shortcut untouched.
+std::string NormalizeShortcutSuggestion(const std::string& suggestion,
+ const std::string& platform) {
+ bool normalize = false;
+ if (platform == values::kKeybindingPlatformMac) {
+ normalize = true;
+ } else if (platform == values::kKeybindingPlatformDefault) {
+#if defined(OS_MACOSX)
+ normalize = true;
+#endif
+ }
+
+ if (!normalize)
+ return suggestion;
+
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ suggestion, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < tokens.size(); i++) {
+ if (tokens[i] == values::kKeyCtrl)
+ tokens[i] = values::kKeyCommand;
+ else if (tokens[i] == values::kKeyMacCtrl)
+ tokens[i] = values::kKeyCtrl;
+ }
+ return base::JoinString(tokens, "+");
+}
+
+} // namespace
+
+Command::Command() : global_(false), type_(Type::kNamed) {}
+
+Command::Command(const std::string& command_name,
+ const base::string16& description,
+ const std::string& accelerator,
+ bool global)
+ : command_name_(command_name),
+ description_(description),
+ global_(global),
+ type_(GetCommandType(command_name)) {
+ base::string16 error;
+ accelerator_ = ParseImpl(accelerator, CommandPlatform(), 0,
+ type_ == Type::kNamed, &error);
+}
+
+Command::Command(const Command& other) = default;
+
+Command::~Command() {}
+
+// static
+std::string Command::CommandPlatform() {
+#if defined(OS_WIN)
+ return values::kKeybindingPlatformWin;
+#elif defined(OS_MACOSX)
+ return values::kKeybindingPlatformMac;
+#elif defined(OS_CHROMEOS)
+ return values::kKeybindingPlatformChromeOs;
+#elif defined(OS_LINUX)
+ return values::kKeybindingPlatformLinux;
+#else
+ return "";
+#endif
+}
+
+// static
+ui::Accelerator Command::StringToAccelerator(const std::string& accelerator,
+ const std::string& command_name) {
+ base::string16 error;
+ ui::Accelerator parsed =
+ ParseImpl(accelerator, Command::CommandPlatform(), 0,
+ IsNamedCommand(command_name), &error);
+ return parsed;
+}
+
+// static
+std::string Command::AcceleratorToString(const ui::Accelerator& accelerator) {
+ std::string shortcut;
+
+ // Ctrl and Alt are mutually exclusive.
+ if (accelerator.IsCtrlDown())
+ shortcut += values::kKeyCtrl;
+ else if (accelerator.IsAltDown())
+ shortcut += values::kKeyAlt;
+ if (!shortcut.empty())
+ shortcut += values::kKeySeparator;
+
+ if (accelerator.IsCmdDown()) {
+#if defined(OS_CHROMEOS)
+ // Chrome OS treats the Search key like the Command key.
+ shortcut += values::kKeySearch;
+#else
+ shortcut += values::kKeyCommand;
+#endif
+ shortcut += values::kKeySeparator;
+ }
+
+ if (accelerator.IsShiftDown()) {
+ shortcut += values::kKeyShift;
+ shortcut += values::kKeySeparator;
+ }
+
+ if (accelerator.key_code() >= ui::VKEY_0 &&
+ accelerator.key_code() <= ui::VKEY_9) {
+ shortcut += '0' + (accelerator.key_code() - ui::VKEY_0);
+ } else if (accelerator.key_code() >= ui::VKEY_A &&
+ accelerator.key_code() <= ui::VKEY_Z) {
+ shortcut += 'A' + (accelerator.key_code() - ui::VKEY_A);
+ } else {
+ switch (accelerator.key_code()) {
+ case ui::VKEY_OEM_COMMA:
+ shortcut += values::kKeyComma;
+ break;
+ case ui::VKEY_OEM_PERIOD:
+ shortcut += values::kKeyPeriod;
+ break;
+ case ui::VKEY_UP:
+ shortcut += values::kKeyUp;
+ break;
+ case ui::VKEY_DOWN:
+ shortcut += values::kKeyDown;
+ break;
+ case ui::VKEY_LEFT:
+ shortcut += values::kKeyLeft;
+ break;
+ case ui::VKEY_RIGHT:
+ shortcut += values::kKeyRight;
+ break;
+ case ui::VKEY_INSERT:
+ shortcut += values::kKeyIns;
+ break;
+ case ui::VKEY_DELETE:
+ shortcut += values::kKeyDel;
+ break;
+ case ui::VKEY_HOME:
+ shortcut += values::kKeyHome;
+ break;
+ case ui::VKEY_END:
+ shortcut += values::kKeyEnd;
+ break;
+ case ui::VKEY_PRIOR:
+ shortcut += values::kKeyPgUp;
+ break;
+ case ui::VKEY_NEXT:
+ shortcut += values::kKeyPgDwn;
+ break;
+ case ui::VKEY_SPACE:
+ shortcut += values::kKeySpace;
+ break;
+ case ui::VKEY_TAB:
+ shortcut += values::kKeyTab;
+ break;
+ case ui::VKEY_MEDIA_NEXT_TRACK:
+ shortcut += values::kKeyMediaNextTrack;
+ break;
+ case ui::VKEY_MEDIA_PLAY_PAUSE:
+ shortcut += values::kKeyMediaPlayPause;
+ break;
+ case ui::VKEY_MEDIA_PREV_TRACK:
+ shortcut += values::kKeyMediaPrevTrack;
+ break;
+ case ui::VKEY_MEDIA_STOP:
+ shortcut += values::kKeyMediaStop;
+ break;
+ default:
+ return "";
+ }
+ }
+ return shortcut;
+}
+
+// static
+bool Command::IsMediaKey(const ui::Accelerator& accelerator) {
+ if (accelerator.modifiers() != 0)
+ return false;
+
+ return ui::MediaKeysListener::IsMediaKeycode(accelerator.key_code());
+}
+
+bool Command::Parse(const base::DictionaryValue* command,
+ const std::string& command_name,
+ int index,
+ base::string16* error) {
+ DCHECK(!command_name.empty());
+
+ base::string16 description;
+ if (IsNamedCommand(command_name)) {
+ if (!command->GetString(keys::kDescription, &description) ||
+ description.empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingDescription, base::NumberToString(index));
+ return false;
+ }
+ }
+
+ // We'll build up a map of platform-to-shortcut suggestions.
+ typedef std::map<const std::string, std::string> SuggestionMap;
+ SuggestionMap suggestions;
+
+ // First try to parse the |suggested_key| as a dictionary.
+ const base::DictionaryValue* suggested_key_dict;
+ if (command->GetDictionary(keys::kSuggestedKey, &suggested_key_dict)) {
+ for (base::DictionaryValue::Iterator iter(*suggested_key_dict);
+ !iter.IsAtEnd(); iter.Advance()) {
+ // For each item in the dictionary, extract the platforms specified.
+ std::string suggested_key_string;
+ if (iter.value().GetAsString(&suggested_key_string) &&
+ !suggested_key_string.empty()) {
+ // Found a platform, add it to the suggestions list.
+ suggestions[iter.key()] = suggested_key_string;
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBinding, base::NumberToString(index),
+ keys::kSuggestedKey, kMissing);
+ return false;
+ }
+ }
+ } else {
+ // No dictionary was found, fall back to using just a string, so developers
+ // don't have to specify a dictionary if they just want to use one default
+ // for all platforms.
+ std::string suggested_key_string;
+ if (command->GetString(keys::kSuggestedKey, &suggested_key_string) &&
+ !suggested_key_string.empty()) {
+ // If only a single string is provided, it must be default for all.
+ suggestions[values::kKeybindingPlatformDefault] = suggested_key_string;
+ } else {
+ suggestions[values::kKeybindingPlatformDefault] = "";
+ }
+ }
+
+ // Check if this is a global or a regular shortcut.
+ bool global = false;
+ command->GetBoolean(keys::kGlobal, &global);
+
+ // Normalize the suggestions.
+ for (auto iter = suggestions.begin(); iter != suggestions.end(); ++iter) {
+ // Before we normalize Ctrl to Command we must detect when the developer
+ // specified Command in the Default section, which will work on Mac after
+ // normalization but only fail on other platforms when they try it out on
+ // other platforms, which is not what we want.
+ if (iter->first == values::kKeybindingPlatformDefault &&
+ iter->second.find("Command+") != std::string::npos) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBinding, base::NumberToString(index),
+ keys::kSuggestedKey, kCommandKeyNotSupported);
+ return false;
+ }
+
+ suggestions[iter->first] = NormalizeShortcutSuggestion(iter->second,
+ iter->first);
+ }
+
+ std::string platform = CommandPlatform();
+ std::string key = platform;
+ if (suggestions.find(key) == suggestions.end())
+ key = values::kKeybindingPlatformDefault;
+ if (suggestions.find(key) == suggestions.end()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingMissingPlatform, base::NumberToString(index),
+ keys::kSuggestedKey, platform);
+ return false; // No platform specified and no fallback. Bail.
+ }
+
+ // For developer convenience, we parse all the suggestions (and complain about
+ // errors for platforms other than the current one) but use only what we need.
+ std::map<const std::string, std::string>::const_iterator iter =
+ suggestions.begin();
+ for ( ; iter != suggestions.end(); ++iter) {
+ ui::Accelerator accelerator;
+ if (!iter->second.empty()) {
+ // Note that we pass iter->first to pretend we are on a platform we're not
+ // on.
+ accelerator = ParseImpl(iter->second, iter->first, index,
+ IsNamedCommand(command_name), error);
+ if (accelerator.key_code() == ui::VKEY_UNKNOWN) {
+ if (error->empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBinding, base::NumberToString(index),
+ iter->first, iter->second);
+ }
+ return false;
+ }
+ }
+
+ if (iter->first == key) {
+ // This platform is our platform, so grab this key.
+ accelerator_ = accelerator;
+ command_name_ = command_name;
+ description_ = description;
+ global_ = global;
+ type_ = GetCommandType(command_name);
+ }
+ }
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/command.h b/chromium/chrome/common/extensions/command.h
new file mode 100644
index 00000000000..11a86fbfb4f
--- /dev/null
+++ b/chromium/chrome/common/extensions/command.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_COMMAND_H_
+#define CHROME_COMMON_EXTENSIONS_COMMAND_H_
+
+#include <map>
+#include <string>
+
+#include "base/strings/string16.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+class Command {
+ public:
+ enum class Type {
+ kBrowserAction,
+ kPageAction,
+ kNamed,
+ };
+
+ Command();
+ Command(const std::string& command_name,
+ const base::string16& description,
+ const std::string& accelerator,
+ bool global);
+ Command(const Command& other);
+ ~Command();
+
+ // The platform value for the Command.
+ static std::string CommandPlatform();
+
+ // Parse a string as an accelerator. If the accelerator is unparsable then
+ // a generic ui::Accelerator object will be returns (with key_code Unknown).
+ static ui::Accelerator StringToAccelerator(const std::string& accelerator,
+ const std::string& command_name);
+
+ // Returns the string representation of an accelerator without localizing the
+ // shortcut text (like accelerator::GetShortcutText() does).
+ static std::string AcceleratorToString(const ui::Accelerator& accelerator);
+
+ // Return true if the specified accelerator is one of the following multimedia
+ // keys: Next Track key, Previous Track key, Stop Media key, Play/Pause Media
+ // key, without any modifiers.
+ static bool IsMediaKey(const ui::Accelerator& accelerator);
+
+ // Parse the command.
+ bool Parse(const base::DictionaryValue* command,
+ const std::string& command_name,
+ int index,
+ base::string16* error);
+
+ // Accessors:
+ const std::string& command_name() const { return command_name_; }
+ const ui::Accelerator& accelerator() const { return accelerator_; }
+ const base::string16& description() const { return description_; }
+ bool global() const { return global_; }
+ Type type() const { return type_; }
+
+ // Setter:
+ void set_accelerator(const ui::Accelerator& accelerator) {
+ accelerator_ = accelerator;
+ }
+ void set_global(bool global) {
+ global_ = global;
+ }
+
+ private:
+ std::string command_name_;
+ ui::Accelerator accelerator_;
+ base::string16 description_;
+ bool global_;
+ Type type_;
+};
+
+// A mapping of command name (std::string) to a command object.
+typedef std::map<std::string, Command> CommandMap;
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_COMMAND_H_
diff --git a/chromium/chrome/common/extensions/command_unittest.cc b/chromium/chrome/common/extensions/command_unittest.cc
new file mode 100644
index 00000000000..adc18072fab
--- /dev/null
+++ b/chromium/chrome/common/extensions/command_unittest.cc
@@ -0,0 +1,348 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/command.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/optional.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+using CommandTest = testing::Test;
+
+struct ConstCommandsTestData {
+ bool expected_result;
+ ui::Accelerator accelerator;
+ const char* command_name;
+ const char* key;
+ const char* description;
+ base::Optional<Command::Type> type;
+};
+
+// Checks the |suggested_key| value parses into a command when specified as a
+// string or dictionary of platform specific keys. If
+// |platform_specific_only| is true, only the latter is tested. |platforms|
+// specifies all platforms to use when populating the |suggested_key|
+// dictionary.
+void CheckParse(const ConstCommandsTestData& data,
+ int i,
+ bool platform_specific_only,
+ std::vector<std::string>& platforms) {
+ SCOPED_TRACE(std::string("Command name: |") + data.command_name + "| key: |" +
+ data.key + "| description: |" + data.description +
+ "| index: " + base::NumberToString(i));
+
+ extensions::Command command;
+ std::unique_ptr<base::DictionaryValue> input(new base::DictionaryValue);
+ base::string16 error;
+
+ // First, test the parse of a string suggested_key value.
+ input->SetString("suggested_key", data.key);
+ input->SetString("description", data.description);
+
+ if (!platform_specific_only) {
+ bool result = command.Parse(input.get(), data.command_name, i, &error);
+ EXPECT_EQ(data.expected_result, result);
+ if (result) {
+ EXPECT_STREQ(data.description,
+ base::UTF16ToASCII(command.description()).c_str());
+ EXPECT_STREQ(data.command_name, command.command_name().c_str());
+ EXPECT_EQ(data.accelerator, command.accelerator());
+ }
+ }
+
+ // Now, test the parse of a platform dictionary suggested_key value.
+ if (data.key[0] != '\0') {
+ std::string current_platform = extensions::Command::CommandPlatform();
+ if (platform_specific_only &&
+ !base::Contains(platforms, current_platform)) {
+ // Given a |current_platform| without a |suggested_key|, |default| is
+ // used. However, some keys, such as Search on Chrome OS, are only valid
+ // for platform specific entries. Skip the test in this case.
+ return;
+ }
+
+ input.reset(new base::DictionaryValue);
+ auto key_dict = std::make_unique<base::DictionaryValue>();
+
+ for (size_t j = 0; j < platforms.size(); ++j)
+ key_dict->SetString(platforms[j], data.key);
+
+ input->Set("suggested_key", std::move(key_dict));
+ input->SetString("description", data.description);
+
+ bool result = command.Parse(input.get(), data.command_name, i, &error);
+ EXPECT_EQ(data.expected_result, result);
+
+ if (result) {
+ EXPECT_STREQ(data.description,
+ base::UTF16ToASCII(command.description()).c_str());
+ EXPECT_STREQ(data.command_name, command.command_name().c_str());
+ EXPECT_EQ(data.accelerator, command.accelerator());
+ ASSERT_TRUE(data.type) << "Parsed commands must specify an expected type";
+ EXPECT_EQ(*data.type, command.type());
+ }
+ }
+}
+
+TEST(CommandTest, ExtensionCommandParsing) {
+ const ui::Accelerator none = ui::Accelerator();
+ const ui::Accelerator shift_f = ui::Accelerator(ui::VKEY_F,
+ ui::EF_SHIFT_DOWN);
+#if defined(OS_MACOSX)
+ int ctrl = ui::EF_COMMAND_DOWN;
+#else
+ int ctrl = ui::EF_CONTROL_DOWN;
+#endif
+
+ const ui::Accelerator ctrl_f = ui::Accelerator(ui::VKEY_F, ctrl);
+ const ui::Accelerator alt_f = ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN);
+ const ui::Accelerator ctrl_shift_f =
+ ui::Accelerator(ui::VKEY_F, ctrl | ui::EF_SHIFT_DOWN);
+ const ui::Accelerator alt_shift_f =
+ ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
+ const ui::Accelerator ctrl_1 = ui::Accelerator(ui::VKEY_1, ctrl);
+ const ui::Accelerator ctrl_comma = ui::Accelerator(ui::VKEY_OEM_COMMA, ctrl);
+ const ui::Accelerator ctrl_dot = ui::Accelerator(ui::VKEY_OEM_PERIOD, ctrl);
+ const ui::Accelerator ctrl_left = ui::Accelerator(ui::VKEY_LEFT, ctrl);
+ const ui::Accelerator ctrl_right = ui::Accelerator(ui::VKEY_RIGHT, ctrl);
+ const ui::Accelerator ctrl_up = ui::Accelerator(ui::VKEY_UP, ctrl);
+ const ui::Accelerator ctrl_down = ui::Accelerator(ui::VKEY_DOWN, ctrl);
+ const ui::Accelerator ctrl_ins = ui::Accelerator(ui::VKEY_INSERT, ctrl);
+ const ui::Accelerator ctrl_del = ui::Accelerator(ui::VKEY_DELETE, ctrl);
+ const ui::Accelerator ctrl_home = ui::Accelerator(ui::VKEY_HOME, ctrl);
+ const ui::Accelerator ctrl_end = ui::Accelerator(ui::VKEY_END, ctrl);
+ const ui::Accelerator ctrl_pgup = ui::Accelerator(ui::VKEY_PRIOR, ctrl);
+ const ui::Accelerator ctrl_pgdwn = ui::Accelerator(ui::VKEY_NEXT, ctrl);
+ const ui::Accelerator next_track =
+ ui::Accelerator(ui::VKEY_MEDIA_NEXT_TRACK, ui::EF_NONE);
+ const ui::Accelerator prev_track =
+ ui::Accelerator(ui::VKEY_MEDIA_PREV_TRACK, ui::EF_NONE);
+ const ui::Accelerator play_pause =
+ ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, ui::EF_NONE);
+ const ui::Accelerator stop =
+ ui::Accelerator(ui::VKEY_MEDIA_STOP, ui::EF_NONE);
+
+ ConstCommandsTestData kTests[] = {
+ // Negative test (one or more missing required fields). We don't need to
+ // test |command_name| being blank as it is used as a key in the manifest,
+ // so it can't be blank (and we CHECK() when it is). A blank shortcut is
+ // permitted.
+ {false, none, "command", "", ""},
+ {false, none, "command", "Ctrl+f", ""},
+ // Ctrl+Alt is not permitted, see MSDN link in comments in Parse function.
+ {false, none, "command", "Ctrl+Alt+F", "description"},
+ // Unsupported shortcuts/too many, or missing modifier.
+ {false, none, "command", "A", "description"},
+ {false, none, "command", "F10", "description"},
+ {false, none, "command", "Ctrl+F+G", "description"},
+ {false, none, "command", "Ctrl+Alt+Shift+G", "description"},
+ // Shift on its own is not supported.
+ {false, shift_f, "command", "Shift+F", "description"},
+ {false, shift_f, "command", "F+Shift", "description"},
+ // Basic tests.
+ {true, none, "command", "", "description", Command::Type::kNamed},
+ {true, ctrl_f, "command", "Ctrl+F", "description", Command::Type::kNamed},
+ {true, alt_f, "command", "Alt+F", "description", Command::Type::kNamed},
+ {true, ctrl_shift_f, "command", "Ctrl+Shift+F", "description",
+ Command::Type::kNamed},
+ {true, alt_shift_f, "command", "Alt+Shift+F", "description",
+ Command::Type::kNamed},
+ {true, ctrl_1, "command", "Ctrl+1", "description", Command::Type::kNamed},
+ // Shortcut token order tests.
+ {true, ctrl_f, "command", "F+Ctrl", "description", Command::Type::kNamed},
+ {true, alt_f, "command", "F+Alt", "description", Command::Type::kNamed},
+ {true, ctrl_shift_f, "command", "F+Ctrl+Shift", "description",
+ Command::Type::kNamed},
+ {true, ctrl_shift_f, "command", "F+Shift+Ctrl", "description",
+ Command::Type::kNamed},
+ {true, alt_shift_f, "command", "F+Alt+Shift", "description",
+ Command::Type::kNamed},
+ {true, alt_shift_f, "command", "F+Shift+Alt", "description",
+ Command::Type::kNamed},
+ // Case insensitivity is not OK.
+ {false, ctrl_f, "command", "Ctrl+f", "description"},
+ {false, ctrl_f, "command", "cTrL+F", "description"},
+ // Skipping description is OK for browser- and pageActions.
+ {true, ctrl_f, "_execute_browser_action", "Ctrl+F", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_f, "_execute_page_action", "Ctrl+F", "",
+ Command::Type::kPageAction},
+ // Home, End, Arrow keys, etc.
+ {true, ctrl_comma, "_execute_browser_action", "Ctrl+Comma", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_dot, "_execute_browser_action", "Ctrl+Period", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_left, "_execute_browser_action", "Ctrl+Left", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_right, "_execute_browser_action", "Ctrl+Right", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_up, "_execute_browser_action", "Ctrl+Up", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_down, "_execute_browser_action", "Ctrl+Down", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_ins, "_execute_browser_action", "Ctrl+Insert", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_del, "_execute_browser_action", "Ctrl+Delete", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_home, "_execute_browser_action", "Ctrl+Home", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_end, "_execute_browser_action", "Ctrl+End", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_pgup, "_execute_browser_action", "Ctrl+PageUp", "",
+ Command::Type::kBrowserAction},
+ {true, ctrl_pgdwn, "_execute_browser_action", "Ctrl+PageDown", "",
+ Command::Type::kBrowserAction},
+ // Media keys.
+ {true, next_track, "command", "MediaNextTrack", "description",
+ Command::Type::kNamed},
+ {true, play_pause, "command", "MediaPlayPause", "description",
+ Command::Type::kNamed},
+ {true, prev_track, "command", "MediaPrevTrack", "description",
+ Command::Type::kNamed},
+ {true, stop, "command", "MediaStop", "description",
+ Command::Type::kNamed},
+ {false, none, "_execute_browser_action", "MediaNextTrack", ""},
+ {false, none, "_execute_page_action", "MediaPrevTrack", ""},
+ {false, none, "command", "Ctrl+Shift+MediaPrevTrack", "description"},
+ };
+ std::vector<std::string> all_platforms;
+ all_platforms.push_back("default");
+ all_platforms.push_back("chromeos");
+ all_platforms.push_back("linux");
+ all_platforms.push_back("mac");
+ all_platforms.push_back("windows");
+
+ for (size_t i = 0; i < base::size(kTests); ++i)
+ CheckParse(kTests[i], i, false, all_platforms);
+}
+
+TEST(CommandTest, ExtensionCommandParsingFallback) {
+ std::string description = "desc";
+ std::string command_name = "foo";
+
+ // Test that platform specific keys are honored on each platform, despite
+ // fallback being given.
+ std::unique_ptr<base::DictionaryValue> input(new base::DictionaryValue);
+ input->SetString("description", description);
+ base::DictionaryValue* key_dict = input->SetDictionary(
+ "suggested_key", std::make_unique<base::DictionaryValue>());
+ key_dict->SetString("default", "Ctrl+Shift+D");
+ key_dict->SetString("windows", "Ctrl+Shift+W");
+ key_dict->SetString("mac", "Ctrl+Shift+M");
+ key_dict->SetString("linux", "Ctrl+Shift+L");
+ key_dict->SetString("chromeos", "Ctrl+Shift+C");
+
+ extensions::Command command;
+ base::string16 error;
+ EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
+ EXPECT_STREQ(description.c_str(),
+ base::UTF16ToASCII(command.description()).c_str());
+ EXPECT_STREQ(command_name.c_str(), command.command_name().c_str());
+
+#if defined(OS_WIN)
+ ui::Accelerator accelerator(ui::VKEY_W,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#elif defined(OS_MACOSX)
+ ui::Accelerator accelerator(ui::VKEY_M,
+ ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
+#elif defined(OS_CHROMEOS)
+ ui::Accelerator accelerator(ui::VKEY_C,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#elif defined(OS_LINUX)
+ ui::Accelerator accelerator(ui::VKEY_L,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#else
+ ui::Accelerator accelerator(ui::VKEY_D,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#endif
+ EXPECT_EQ(accelerator, command.accelerator());
+
+ // Misspell a platform.
+ key_dict->SetString("windosw", "Ctrl+M");
+ EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+ EXPECT_TRUE(key_dict->Remove("windosw", NULL));
+
+ // Now remove platform specific keys (leaving just "default") and make sure
+ // every platform falls back to the default.
+ EXPECT_TRUE(key_dict->Remove("windows", NULL));
+ EXPECT_TRUE(key_dict->Remove("mac", NULL));
+ EXPECT_TRUE(key_dict->Remove("linux", NULL));
+ EXPECT_TRUE(key_dict->Remove("chromeos", NULL));
+ EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
+ EXPECT_EQ(ui::VKEY_D, command.accelerator().key_code());
+
+ // Now remove "default", leaving no option but failure. Or, in the words of
+ // the immortal Adam Savage: "Failure is always an option".
+ EXPECT_TRUE(key_dict->Remove("default", NULL));
+ EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+
+ // Make sure Command is not supported for non-Mac platforms.
+ key_dict->SetString("default", "Command+M");
+ EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+ EXPECT_TRUE(key_dict->Remove("default", NULL));
+ key_dict->SetString("windows", "Command+M");
+ EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+ EXPECT_TRUE(key_dict->Remove("windows", NULL));
+
+ // Now add only a valid platform that we are not running on to make sure devs
+ // are notified of errors on other platforms.
+#if defined(OS_WIN)
+ key_dict->SetString("mac", "Ctrl+Shift+M");
+#else
+ key_dict->SetString("windows", "Ctrl+Shift+W");
+#endif
+ EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+
+ // Make sure Mac specific keys are not processed on other platforms.
+#if !defined(OS_MACOSX)
+ key_dict->SetString("windows", "Command+Shift+M");
+ EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+#endif
+}
+
+TEST(CommandTest, ExtensionCommandParsingPlatformSpecific) {
+ ui::Accelerator search_a(ui::VKEY_A, ui::EF_COMMAND_DOWN);
+ ui::Accelerator search_shift_z(ui::VKEY_Z,
+ ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
+
+ ConstCommandsTestData kChromeOsTests[] = {
+ {true, search_shift_z, "command", "Search+Shift+Z", "description",
+ Command::Type::kNamed},
+ {true, search_a, "command", "Search+A", "description",
+ Command::Type::kNamed},
+ // Command is not valid on Chrome OS.
+ {false, search_shift_z, "command", "Command+Shift+Z", "description"},
+ };
+
+ std::vector<std::string> chromeos;
+ chromeos.push_back("chromeos");
+ for (size_t i = 0; i < base::size(kChromeOsTests); ++i)
+ CheckParse(kChromeOsTests[i], i, true, chromeos);
+
+ ConstCommandsTestData kNonChromeOsSearchTests[] = {
+ {false, search_shift_z, "command", "Search+Shift+Z", "description"},
+ };
+ std::vector<std::string> non_chromeos;
+ non_chromeos.push_back("default");
+ non_chromeos.push_back("windows");
+ non_chromeos.push_back("mac");
+ non_chromeos.push_back("linux");
+
+ for (size_t i = 0; i < base::size(kNonChromeOsSearchTests); ++i)
+ CheckParse(kNonChromeOsSearchTests[i], i, true, non_chromeos);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json
deleted file mode 100644
index 2135c3cbf6b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "My Bookmarks",
- "version": "1.1",
- "description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
- "permissions": [
- "bookmarks"
- ],
- "browser_action": {
- "default_title": "My Bookmarks",
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2,
- "content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json
deleted file mode 100644
index b901108730c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "Page Redder",
- "description": "Make the current page red",
- "version": "2.0",
- "permissions": [
- "activeTab"
- ],
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "browser_action": {
- "default_title": "Make this page red"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json
deleted file mode 100644
index e6feef42c6a..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "Print this page",
- "description": "Adds a print button to the browser.",
- "version": "1.2",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "permissions": [
- "activeTab"
- ],
- "browser_action": {
- "default_title": "Print this page",
- "default_icon": "print_16x16.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json
deleted file mode 100644
index 597e0036a1e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "A browser action which changes its icon when clicked",
- "description": "Click browser action icon to change color!",
- "version": "1.3",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "permissions": ["storage"],
- "browser_action": {
- "name": "Click to change the icon's color"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json
deleted file mode 100644
index cecb2bccd11..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "A browser action with a popup that changes the page color",
- "description": "Change the current page color",
- "version": "1.0",
- "permissions": [
- "activeTab"
- ],
- "browser_action": {
- "default_title": "Set this page's color.",
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json
deleted file mode 100644
index d2287292ddf..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name" : "BrowsingData API: Basics",
- "version" : "1.1",
- "description" : "A trivial usage example.",
- "permissions": [
- "browsingData"
- ],
- "browser_action": {
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/commands/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/commands/manifest.json
deleted file mode 100644
index a22622c85a2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/commands/manifest.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "Sample Extension Commands extension",
- "description": "Press Ctrl+Shift+F to open the browser action popup, press Ctrl+Shift+Y to send an event.",
- "version": "1.0",
- "manifest_version": 2,
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "browser_action": {
- "default_popup": "browser_action.html"
- },
- "commands": {
- "toggle-feature": {
- "suggested_key": {
- "default": "Ctrl+Shift+Y",
- "mac": "MacCtrl+Shift+Y"
- },
- "description": "Send a 'toggle-feature' event to the extension"
- },
- "_execute_browser_action": {
- "suggested_key": {
- "default": "Ctrl+Shift+F",
- "mac": "MacCtrl+Shift+F"
- }
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json
deleted file mode 100644
index adf1daefb52..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name" : "Content settings",
- "version" : "0.2",
- "description" : "Shows the content settings for the current site.",
- "permissions": [ "contentSettings", "tabs" ],
- "browser_action": {
- "default_icon": "contentSettings.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json
deleted file mode 100644
index c59cec18d17..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "Context Menus Sample",
- "description": "Shows some of the features of the Context Menus API",
- "version": "0.6",
- "permissions": ["contextMenus"],
- "background": {
- "scripts": ["sample.js"]
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json
deleted file mode 100644
index 8279823db8d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "Context Menus Sample (with Event Page)",
- "description": "Shows some of the features of the Context Menus API using an event page",
- "version": "0.7",
- "permissions": ["contextMenus"],
- "background": {
- "persistent": false,
- "scripts": ["sample.js"]
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/contextMenus/global_context_search/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/contextMenus/global_context_search/manifest.json
deleted file mode 100644
index df745d53022..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/contextMenus/global_context_search/manifest.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "Global Google Search",
- "description": "Use the context menu to search a different country's Google",
- "version": "1.0",
- "manifest_version": 2,
- "options_page": "options.html",
- "permissions": ["contextMenus", "storage"],
- "background": {
- "scripts": [ "locales.js", "background.js"],
- "persistent": false
- },
- "browser_action": {
- "default_popup": "options.html"
- },
- "icons": {
- "16": "globalGoogle16.png",
- "48": "globalGoogle48.png",
- "128": "globalGoogle128.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/cookies/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/cookies/manifest.json
deleted file mode 100644
index eb7da26d259..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/cookies/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name" : "Cookie API Test Extension",
- "version" : "0.8",
- "description" : "Testing Cookie API",
- "permissions": [ "cookies", "tabs", "http://*/*", "https://*/*" ],
- "icons": { "16": "cookie.png", "48": "cookie.png", "128": "cookie.png" },
- "browser_action": {
- "default_icon": "cookie.png"
- },
- "background": {
- "scripts": ["background.js"]
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json
deleted file mode 100644
index 19b6554da4b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "Live HTTP headers",
- "description": "Displays the live log with the http requests headers",
- "version": "0.7",
- "permissions": [
- "debugger"
- ],
- "background": {
- "scripts": ["background.js"]
- },
- "browser_action": {
- "default_icon": "icon.png",
- "default_title": "Live HTTP headers"
- },
- "manifest_version": 2
-}
-
diff --git a/chromium/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json
deleted file mode 100644
index fdc7e3d6475..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "JavaScript pause/resume",
- "description": "Pauses / resumes JavaScript execution",
- "version": "0.7",
- "permissions": [
- "debugger"
- ],
- "background": {
- "scripts": ["background.js"]
- },
- "browser_action": {
- "default_icon": "debuggerPause.png",
- "default_title": "Pause JavaScript"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/default_command_override/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/default_command_override/manifest.json
deleted file mode 100644
index 803f2b9c9fd..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/default_command_override/manifest.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "name": "Tab Flipper",
- "description": "Press Ctrl+Shift+Right or Ctrl+Shift+Left (Command+Shift+Right or Command+Shift+Left on a Mac) to flip through window tabs",
- "version": "1.0",
- "manifest_version": 2,
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "browser_action": {
- "default_icon": "images/tabFlipper16.png",
- "default_title": "Press Ctrl(Win)/Command(Mac)+Shift+ Left or Right to Flip Tabs"
- },
- "commands": {
- "flip-tabs-forward": {
- "suggested_key": {
- "default": "Ctrl+Shift+Right",
- "mac": "Command+Shift+Right"
- },
- "description": "Flip tabs forward"
- },
- "flip-tabs-backwards": {
- "suggested_key": {
- "default": "Ctrl+Shift+Left",
- "mac": "Command+Shift+Left"
- },
- "description": "Flip tabs backwards"
- }
- },
- "icons": {
- "16": "images/tabFlipper16.png",
- "32": "images/tabFlipper32.png",
- "48": "images/tabFlipper48.png",
- "128": "images/tabFlipper128.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/desktopCapture/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/desktopCapture/manifest.json
deleted file mode 100644
index 6a59859120b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/desktopCapture/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjd3VZe2un2t/ju5HGcZrbD862LLhtukwtXeySPzbQaq9zEyFzr+pVd1EeAW7ZPUA86K9/mpWiDycoSMMj9hpm+QQqBnQDrPPtBJfogkFvWsgDQaw6SXZmErTlzz1wukn/0YLQrPLJ7JPj3zGzWcoVj9AhOjQeDpq2E9P3lz85mHwG0RrxgpknP1wGNNkXl/y4WDaWHZyoX1zgcn2r3bzTdb77RWiA9pduXSn6d14GA9B9CdQA4bTDmc9HY1WaVGK0oDX2A2eJEllHqdeBJmpqPqds4cIhm0Gq6lKvxB61I2UZlbCaSIMfTTxBnt+r7NPgpxHBKJIF1xOCpuGtWuc0wIDAQAB",
- "name": "Desktop Capture Example",
- "description": "Show desktop media picker UI",
- "version": "1",
- "manifest_version": 3,
- "icons": {
- "16": "icon.png",
- "128": "icon.png"
- },
- "app": {
- "background": {
- "scripts": ["background.js"]
- }
- },
- "permissions": [
- "desktopCapture"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/deviceInfo/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/deviceInfo/basic/manifest.json
deleted file mode 100644
index 4d376d84c52..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/deviceInfo/basic/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "My Devices",
- "version": "1.1",
- "description": "A browser action with a popup dump of all devices signed into the same account as the current profile.",
- "permissions": [
- "signedInDevices"
- ],
- "browser_action": {
- "default_title": "My Devices",
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2,
- "content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json
deleted file mode 100644
index 275dced7431..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "FirePHP for Chrome",
- "version": "1.1",
- "minimum_chrome_version": "10.0",
- "description": "Extends the Developer Tools, adding support for parsing FirePHP messages from server",
- "devtools_page": "devtools.html",
- "background": { "scripts": ["background.js"] },
- "permissions": [
- "http://*/*",
- "https://*/*"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json
deleted file mode 100644
index a60485d7fbf..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "Chrome Query",
- "version": "1.1",
- "description": "Extends the Developer Tools, adding a sidebar that displays the jQuery data associated with the selected DOM element.",
- "devtools_page": "devtools.html",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/displaySource/tabCast/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/displaySource/tabCast/manifest.json
deleted file mode 100644
index b3fc8aa1e89..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/displaySource/tabCast/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "tabCast",
- "version": "0.1",
- "manifest_version": 2,
- "description": "Creates a WiFi Display Session from the captured tab media stream using chrome.displaySource API.",
- "permissions": [
- "tabCapture", "tabs", "displaySource"
- ],
- "browser_action": {
- "default_title": "Tab cast",
- "default_popup": "main.html"
- },
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/document_scan/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/document_scan/manifest.json
deleted file mode 100644
index 8f788d3aa0b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/document_scan/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "Document Scanning API Sample",
- "version": "0.1",
- "manifest_version": 2,
- "minimum_chrome_version": "37",
- "app": {
- "background": {
- "scripts": ["background.js"]
- }
- },
- "permissions": [],
- "optional_permissions": [ "documentScan" ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_filename_controller/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/download_filename_controller/manifest.json
deleted file mode 100644
index 6bdb58ecb65..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_filename_controller/manifest.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{"name": "Download Filename Controller",
- "description": "Download Filename Controller",
- "version": "0.1",
- "background": {"scripts": ["bg.js"], "persistent": false},
- "options_page": "options.html",
- "permissions": ["downloads"],
- "content_security_policy": "script-src 'self'; default-src 'self'",
- "manifest_version": 2}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json
deleted file mode 100644
index f252f0decc0..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "Download Selected Links",
- "description": "Select links on a page and download them.",
- "version": "0.1",
- "minimum_chrome_version": "16.0.884",
- "permissions": ["downloads", "<all_urls>"],
- "browser_action": {"default_popup": "popup.html"},
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json
deleted file mode 100644
index 83d05e7bf55..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json
+++ /dev/null
@@ -1,290 +0,0 @@
-{"extName": {
- "message": "Download Manager Button",
- "description": "Extension name"},
- "extDesc": {
- "message": "Browser Action Download Manager User Interface for Google Chrome",
- "description": "Extension description"},
- "badChromeVersion": {
- "message": "The downloads API is only available on the canary, dev, and beta channels.",
- "description": ""},
- "tabTitle": {
- "message": "Downloads",
- "description": "tab title"},
- "searchPlaceholder": {
- "message": "Search Downloads",
- "description": ""},
- "clearAllTitle": {
- "message": "Erase All Visible Downloads",
- "description": ""},
- "openDownloadsFolderTitle": {
- "message": "Open Downloads Folder",
- "description": ""},
- "zeroItems": {
- "message": "There are zero download items.",
- "description": ""},
- "searching": {
- "message": "Teleporting lots of goats...",
- "description": ""},
- "zeroSearchResults": {
- "message": "Zero matches",
- "description": ""},
- "managementPermissionInfo": {
- "message": "Some files were downloaded by an extension.",
- "description": ""},
- "grantManagementPermission": {
- "message": "Show links to extensions that download files.",
- "description": ""},
- "showOlderDownloads": {
- "message": "Show Older Downloads",
- "description": ""},
- "loadingOlderDownloads": {
- "message": "Loading Older Downloads...",
- "description": ""},
- "openTitle": {
- "message": "Open",
- "description": ""},
- "pauseTitle": {
- "message": "Pause",
- "description": ""},
- "resumeTitle": {
- "message": "Resume",
- "description": ""},
- "cancelTitle": {
- "message": "Cancel",
- "description": ""},
- "removeFileTitle": {
- "message": "Remove file",
- "description": ""},
- "eraseTitle": {
- "message": "Erase",
- "description": ""},
- "retryTitle": {
- "message": "Retry",
- "description": ""},
- "referrerTitle": {
- "message": "Referrer",
- "description": ""},
- "month0abbr": {"message": "Jan","description": ""},
- "month1abbr": {"message": "Feb","description": ""},
- "month2abbr": {"message": "Mar","description": ""},
- "month3abbr": {"message": "Apr","description": ""},
- "month4abbr": {"message": "May","description": ""},
- "month5abbr": {"message": "Jun","description": ""},
- "month6abbr": {"message": "Jul","description": ""},
- "month7abbr": {"message": "Aug","description": ""},
- "month8abbr": {"message": "Sep","description": ""},
- "month9abbr": {"message": "Oct","description": ""},
- "month10abbr": {"message": "Nov","description": ""},
- "month11abbr": {"message": "Dec","description": ""},
- "openWhenCompleteFinishing": {
- "message": "Opening in just a moment",
- "description": ""},
- "timeLeftFinishing": {
- "message": "finishing...",
- "description": ""},
- "openWhenCompleteDays": {
- "message": "Opening in $days$d $hours$h",
- "description": "",
- "placeholders": {
- "days": {
- "content": "$1",
- "example": "2"},
- "hours": {
- "content": "$2",
- "example": "23"}}},
- "timeLeftDays": {
- "message": "$days$d $hours$h left",
- "description": "",
- "placeholders": {
- "days": {
- "content": "$1",
- "example": "2"},
- "hours": {
- "content": "$2",
- "example": "23"}}},
- "openWhenCompleteHours": {
- "message": "Opening in $hours$h $mins$m",
- "description": "",
- "placeholders": {
- "hours": {
- "content": "$1",
- "example": "23"},
- "mins": {
- "content": "$2",
- "example": "59"}}},
- "timeLeftHours": {
- "message": "$hours$h $mins$m left",
- "description": "",
- "placeholders": {
- "hours": {
- "content": "$1",
- "example": "23"},
- "mins": {
- "content": "$2",
- "example": "59"}}},
- "openWhenCompleteMinutes": {
- "message": "Opening in $mins$m $sec$s",
- "description": "",
- "placeholders": {
- "mins": {
- "content": "$1",
- "example": "59"},
- "sec": {
- "content": "$2",
- "example": "59"}}},
- "timeLeftMinutes": {
- "message": "$mins$m $sec$s left",
- "description": "",
- "placeholders": {
- "mins": {
- "content": "$1",
- "example": "59"},
- "sec": {
- "content": "$2",
- "example": "59"}}},
- "openWhenCompleteSeconds": {
- "message": "Opening in $sec$s",
- "description": "",
- "placeholders": {
- "sec": {
- "content": "$1",
- "example": "59"}}},
- "timeLeftSeconds": {
- "message": "$sec$s left",
- "description": "",
- "placeholders": {
- "sec": {
- "content": "$1",
- "example": "59"}}},
- "error_FILE_FAILED": {
- "message": "File Failed",
- "description": ""},
- "error_FILE_ACCESS_DENIED": {
- "message": "File-System Access Denied",
- "description": ""},
- "error_FILE_NO_SPACE": {
- "message": "No Space On Disk",
- "description": ""},
- "error_FILE_NAME_TOO_LONG": {
- "message": "Filename Too Long",
- "description": ""},
- "error_FILE_TOO_LARGE": {
- "message": "File Too Large",
- "description": ""},
- "error_FILE_VIRUS_INFECTED": {
- "message": "Virus Infected",
- "description": ""},
- "error_FILE_TRANSIENT_ERROR": {
- "message": "Transient File-System Error",
- "description": ""},
- "error_FILE_BLOCKED": {
- "message": "File Blocked",
- "description": ""},
- "error_FILE_SECURITY_CHECK_FAILED": {
- "message": "Security Check Failed",
- "description": ""},
- "error_FILE_TOO_SHORT": {
- "message": "File Too Short",
- "description": ""},
- "error_NETWORK_FAILED": {
- "message": "Network Failure",
- "description": ""},
- "error_NETWORK_TIMEOUT": {
- "message": "Network Timeout",
- "description": ""},
- "error_NETWORK_DISCONNECTED": {
- "message": "Network Disconnected",
- "description": ""},
- "error_NETWORK_SERVER_DOWN": {
- "message": "Server Down",
- "description": ""},
- "error_SERVER_FAILED": {
- "message": "Server Failure",
- "description": ""},
- "error_SERVER_NO_RANGE": {
- "message": "Server No Range",
- "description": ""},
- "error_SERVER_PRECONDITION": {
- "message": "Server Precondition Failure",
- "description": ""},
- "error_SERVER_BAD_CONTENT": {
- "message": "Bad Content",
- "description": ""},
- "error_USER_CANCELED": {
- "message": "Cancelled",
- "description": ""},
- "error_USER_SHUTDOWN": {
- "message": "Cancelled",
- "description": ""},
- "error_CRASH": {
- "message": "Crash",
- "description": ""},
- "error_1": {
- "message": "File Failed",
- "description": ""},
- "error_2": {
- "message": "File-System Access Denied",
- "description": ""},
- "error_3": {
- "message": "No Space On Disk",
- "description": ""},
- "error_5": {
- "message": "Filename Too Long",
- "description": ""},
- "error_6": {
- "message": "File Too Large",
- "description": ""},
- "error_7": {
- "message": "Virus Infected",
- "description": ""},
- "error_10": {
- "message": "Transient File-System Error",
- "description": ""},
- "error_11": {
- "message": "File Blocked",
- "description": ""},
- "error_12": {
- "message": "Security Check Failed",
- "description": ""},
- "error_13": {
- "message": "File Too Short",
- "description": ""},
- "error_20": {
- "message": "Network Failure",
- "description": ""},
- "error_21": {
- "message": "Network Timeout",
- "description": ""},
- "error_22": {
- "message": "Network Disconnected",
- "description": ""},
- "error_23": {
- "message": "Server Down",
- "description": ""},
- "error_30": {
- "message": "Server Failure",
- "description": ""},
- "error_31": {
- "message": "Server No Range",
- "description": ""},
- "error_32": {
- "message": "Server Precondition Failure",
- "description": ""},
- "error_33": {
- "message": "Bad Content",
- "description": ""},
- "error_40": {
- "message": "Cancelled",
- "description": ""},
- "error_41": {
- "message": "Cancelled",
- "description": ""},
- "error_50": {
- "message": "Crash",
- "description": ""},
- "errorRemoved": {
- "message": "Removed",
- "description": ""},
- "showInFolderTitle": {
- "message": "Show in Folder",
- "description": "Alt text for show in folder icon"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json
deleted file mode 100644
index 156e69c124f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{"name": "__MSG_extName__",
- "version": "0.3",
- "manifest_version": 2,
- "description": "__MSG_extDesc__",
- "icons": {"128": "icon128.png"},
- "browser_action": {
- "default_icon": {
- "19": "icon19.png",
- "38": "icon38.png"},
- "default_title": "__MSG_extName__",
- "default_popup": "popup.html"},
- "background": {"persistent": false, "scripts": ["background.js"]},
- "default_locale": "en",
- "optional_permissions": ["management"],
- "permissions": ["downloads", "downloads.open", "downloads.shelf"]}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json
deleted file mode 100644
index eae1fbe9a55..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{"extName": {
- "message": "Download and Open Button",
- "description": "Extension name"},
- "extDesc": {
- "message": "Download and Open Context Menu Button",
- "description": "Extension description"},
- "openContextMenuTitle": {
- "message": "Download and Open",
- "description": "context menu button text"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json
deleted file mode 100644
index 98a49ab74e4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{"name": "__MSG_extName__",
- "version": "0.1",
- "manifest_version": 2,
- "description": "__MSG_extDesc__",
- "icons": {"16": "icon16.png", "128": "icon128.png"},
- "background": {"persistent": false, "scripts": ["background.js"]},
- "default_locale": "en",
- "permissions": ["contextMenus", "downloads", "downloads.open"]}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/downloads/downloads_overwrite/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/downloads/downloads_overwrite/manifest.json
deleted file mode 100644
index c1f8999dd8d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/downloads/downloads_overwrite/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "Downloads Overwrite Existing Files",
- "description": "All downloads overwrite existing files instead of adding ' (1)', ' (2)', etc.",
- "version": "1",
- "minimum_chrome_version": "26.0.1428",
- "background": {
- "scripts": ["bg.js"],
- "persistent": false
- },
- "permissions": [
- "downloads"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json
deleted file mode 100644
index 7a6e1f43d87..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "Event Page Example",
- "description": "Demonstrates usage and features of the event page",
- "version": "1.0",
- "manifest_version": 2,
- "permissions": ["alarms", "tabs", "bookmarks", "declarativeWebRequest", "*://*/*"],
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "browser_action": {
- "default_icon" : "icon.png",
- "default_title": "Start Event Page"
- },
- "commands": {
- "open-google": {
- "description": "Open a tab to google.com",
- "suggested_key": { "default": "Ctrl+Shift+L" }
- },
- "_execute_browser_action": {
- "suggested_key": { "default": "Ctrl+Shift+K" }
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json
deleted file mode 100644
index 66241fc37bf..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name" : "`extension.isAllowedFileSchemeAccess` and `extension.isAllowedIncognitoAccess` Example",
- "version" : "1.0.0",
- "description" : "Demonstrates the `extension.isAllowedFileSchemeAccess` and `extesion.isAllowedIncognitoAccess` APIs",
- "permissions" : [ "file://*" ],
- "browser_action" : {
- "default_popup": "popup.html",
- "default_icon" : "sample-19.png"
- },
- "icons" : {
- "16" : "sample-16.png",
- "48" : "sample-48.png",
- "128" : "sample-128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/archive/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/archive/manifest.json
deleted file mode 100644
index 025b5d3b62e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/archive/manifest.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "Fake Archive Handler App",
- "version": "0.1",
- "manifest_version": 2,
- "description": "Demonstrate File System Provider API usage for apps.",
- "permissions": [
- "fileSystemProvider",
- {"fileSystem": ["retainEntries"]},
- "storage"
- ],
- "file_handlers": {
- "fake": {
- "types": ["application/fake"],
- "extensions": ["fake"]
- }
- },
- "file_system_provider_capabilities": {
- "multiple_mounts": true,
- "source": "file"
- },
- "app": {
- "background": {
- "scripts": [
- "background.js"
- ]
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/basic/manifest.json
deleted file mode 100644
index fcb281cddf7..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/fileSystemProvider/basic/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "File System Provider API Extension Example",
- "version": "0.1",
- "manifest_version": 2,
- "description":
- "Demonstrate features of the API like mounting, listing directories, etc for extensions.",
- "permissions": [
- "fileSystemProvider"
- ],
- "file_system_provider_capabilities": {
- "source": "network"
- },
- "background": {
- "scripts": [
- "background.js"
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json
deleted file mode 100644
index 2829fecb424..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "Advanced Font Settings",
- "version": "0.67",
- "manifest_version": 2,
- "description": "Customize per-script font settings.",
- "options_page": "options.html",
- "icons": {
- "16": "fonts16.png",
- "128": "fonts128.png"
- },
- "permissions": ["fontSettings"]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/history/historyOverride/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/history/historyOverride/manifest.json
deleted file mode 100644
index bb44e22eed0..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/history/historyOverride/manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "manifest_version": 2,
- "name": "History Override",
- "description": "Overrides the History Page",
- "version": "1.0",
- "chrome_url_overrides" : {
- "history": "history.html"
- },
- "browser_action": {
- "default_title": "History"
- },
- "permissions": [
- "history",
- "chrome://favicon/"
- ],
- "icons": {
- "16": "history16.png",
- "32": "history32.png",
- "48": "history48.png",
- "128": "history128.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json
deleted file mode 100644
index b4aa6ffe3bf..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "Typed URL History",
- "version": "1.2",
- "description": "Reads your history, and shows the top ten pages you go to by typing the URL.",
- "permissions": [
- "history"
- ],
- "browser_action": {
- "default_popup": "typedUrls.html",
- "default_icon": "clock.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json
deleted file mode 100644
index 4b242cf319f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "CLD",
- "description": "Displays the language of a tab",
- "version": "0.3",
- "background": {
- "scripts": ["background.js"]
- },
- "browser_action": {
- "default_name": "Page Language"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/detectLanguage/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/detectLanguage/manifest.json
deleted file mode 100644
index beceb3305c1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/detectLanguage/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "Detect Language",
- "description": "Detects up to 3 languages and their percentages of the provided string",
- "version": "1.0",
-
- "browser_action": {
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
-
- "manifest_version": 2
-} \ No newline at end of file
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json
deleted file mode 100644
index b33d2f0a2c1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "chrome_extension_name": {
- "message": "AcceptLanguage"
- },
- "chrome_extension_description": {
- "message": "Returns accept languages of the browser"
- },
- "click_here": {
- "message": "Left click to list acceptLanguages."
- },
- "browser_action_title": {
- "message": "Click Me"
- },
- "chrome_accept_languages": {
- "message": "$CHROME$ accepts $languages$ languages",
- "placeholders": {
- "chrome": {
- "content": "Chrome",
- "example": "Chrome"
- },
- "languages": {
- "content": "$1",
- "example": "en-US,sr,de"
- }
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json
deleted file mode 100644
index 0e1a7ef43e0..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "chrome_extension_name": {
- "message": "AcceptLanguage"
- },
- "chrome_extension_description": {
- "message": "Devuelve los idiomas aceptados por el navegador"
- },
- "click_here": {
- "message": "Click con botón izquierdo para mostrar la lista de acceptLanguages."
- },
- "browser_action_title": {
- "message": "Haz click aquí"
- },
- "chrome_accept_languages": {
- "message": "$CHROME$ acepta los idiomas $languages$",
- "placeholders": {
- "chrome": {
- "content": "Chrome",
- "example": "Chrome"
- },
- "languages": {
- "content": "$1",
- "example": "en-US,sr,de"
- }
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json
deleted file mode 100644
index 30bd958eedd..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "chrome_extension_name": {
- "message": "Прихватљиви језици"
- },
- "chrome_extension_description": {
- "message": "Језици које прегледач прихвата"
- },
- "click_here": {
- "message": "Кликните да излиÑтате дозвољене језике."
- },
- "chrome_accept_languages": {
- "message": "$CHROME$ прихвата $languages$ језике.",
- "placeholders": {
- "chrome": {
- "content": "Chrome",
- "example": "Chrome"
- },
- "languages": {
- "content": "$1",
- "example": "en-US,sr,de"
- }
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json
deleted file mode 100644
index 52997f29891..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "__MSG_chrome_extension_name__",
- "description": "__MSG_chrome_extension_description__",
- "version": "0.2",
- "default_locale": "en_US",
- "browser_action": {
- "default_title": "__MSG_browser_action_title__",
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json
deleted file mode 100644
index 0d8e2f7757c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "application_title": {
- "message": "Eine lokalisierte gehostete Beispielanwendung"
- },
- "application_description": {
- "message": "Hier steht eine Beschreibung der Applikation, die im Web Store auftauchen wird."
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json
deleted file mode 100644
index ed87abfbf9c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "application_title": {
- "message": "Minimal Localized Hosted App",
- "description": "The title of the application, displayed in the web store."
- },
- "application_description": {
- "message": "This is the minimal set of data required to upload a localized hosted application to the web store.",
- "description": "The description of the application, displayed in the web store."
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json
deleted file mode 100644
index 9cb94fd72db..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "__MSG_application_title__",
- "description": "__MSG_application_description__",
- "version": "0.2",
- "default_locale": "en",
- "app": {
- "launch": {
- "web_url": "http://example.com/"
- }
- },
- "icons": {
- "128": "icon128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json
deleted file mode 100644
index b6fba2a4d6b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name" : "Idle - Simple Example",
- "version" : "1.0.1",
- "description" : "Demonstrates the Idle API",
- "background" : {
- "scripts": ["background.js"]
- },
- "permissions" : [ "idle" ],
- "browser_action" : {
- "default_icon" : "sample-19.png"
- },
- "icons" : {
- "16" : "sample-16.png",
- "48" : "sample-48.png",
- "128" : "sample-128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json
deleted file mode 100644
index e21aec11562..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "Test IME",
- "version": "1.0",
- "manifest_version": 2,
- "description": "A simple IME that converts all keystrokes to upper case.",
- "background": {
- "scripts": ["main.js"]
- },
- "permissions": [
- "input"
- ],
- "input_components": [
- {
- "name": "Test IME",
- "type": "ime",
- "id": "test",
- "description": "Test IME", // A user visible description
- "language": "en-US", // The primary language this IME is used for
- "layouts": ["us::eng"] // The supported keyboard layouts for this IME
- }
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json
deleted file mode 100644
index 76d6907a3b1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "Message Timer",
- "version": "1.3",
- "description": "Times how long it takes to send a message to a content script and back.",
- "content_scripts": [
- {
- "matches": ["http://*/*", "https://*/*"],
- "js": ["page.js"]
- }
- ],
- "browser_action": {
- "default_title": "Time to current page",
- "default_icon": "clock.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/app/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/app/manifest.json
deleted file mode 100644
index a339e11dade..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/app/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- // Extension ID: knldjmfmopnpolahpmmgbagdohdnhkik
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB",
- "name": "Native Messaging Example",
- "version": "1.0",
- "manifest_version": 2,
- "description": "Send a message to a native application.",
- "app": {
- "launch": {
- "local_path": "main.html"
- }
- },
- "icons": {
- "128": "icon-128.png"
- },
- "permissions": [
- "nativeMessaging"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo-win.json b/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo-win.json
deleted file mode 100644
index 84e544847dd..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo-win.json
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-{
- "name": "com.google.chrome.example.echo",
- "description": "Chrome Native Messaging API Example Host",
- "path": "native-messaging-example-host.bat",
- "type": "stdio",
- "allowed_origins": [
- "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo.json b/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo.json
deleted file mode 100644
index dfeae04f627..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/nativeMessaging/host/com.google.chrome.example.echo.json
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-{
- "name": "com.google.chrome.example.echo",
- "description": "Chrome Native Messaging API Example Host",
- "path": "HOST_PATH",
- "type": "stdio",
- "allowed_origins": [
- "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/notifications/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/notifications/manifest.json
deleted file mode 100644
index 1f77d635210..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/notifications/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "Notification Demo",
- "version": "1.1",
- "description":
- "Shows off desktop notifications, which are \"toast\" windows that pop up on the desktop.",
- "icons": {"16": "16.png", "48": "48.png", "128": "128.png"},
- "permissions": [
- "notifications"
- ],
- "options_page": "options.html",
- "background": { "scripts": ["background.js"] },
- "manifest_version": 2,
-
- // crbug.com/134315
- "web_accessible_resources": [
- "48.png"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/omnibox/newtab_search/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/omnibox/newtab_search/manifest.json
deleted file mode 100644
index 5d1c2ffbd4f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/omnibox/newtab_search/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "Omnibox New Tab Search",
- "description": "Type 'nt' plus a search term into the Omnibox to open search in new tab.",
- "version": "1.0",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "omnibox": { "keyword" : "nt" },
- "manifest_version": 2,
- "browser_action": {
- "default_icon": {
- "16": "newtab_search16.png",
- "32": "newtab_search32.png"
- }
- },
- "icons": {
- "16": "newtab_search16.png",
- "32": "newtab_search32.png",
- "48": "newtab_search48.png",
- "128": "newtab_search128.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json
deleted file mode 100644
index eafc95a7ee3..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "Omnibox Example",
- "description" : "To use, type 'omnix' plus a search term into the Omnibox.",
- "version": "1.1",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "omnibox": { "keyword" : "omnix" },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json
deleted file mode 100644
index 9efcc231540..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "Blank new tab page",
- "description": "Override the new tab page with a blank one",
- "version": "0.2",
- "incognito": "split",
- "chrome_url_overrides": {
- "newtab": "blank.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json
deleted file mode 100644
index 927faa3c1d6..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "iGoogle new tab page",
- "description": "Override the new tab page with iGoogle",
- "version": "0.2",
- "chrome_url_overrides": {
- "newtab": "redirect.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json
deleted file mode 100644
index 79134a72efa..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name" : "Page action by content",
- "version" : "1.1",
- "description" : "Shows a page action for HTML pages containing a video",
- "background" : {
- "scripts": ["background.js"],
- "persistent": false
- },
- "page_action" :
- {
- "default_icon" : "video-19.png",
- "default_title" : "There's a <video> in this page!"
- },
- "permissions": [ "declarativeContent" ],
- "icons" : {
- "48" : "video-48.png",
- "128" : "video-128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json
deleted file mode 100644
index 5c20902fd4e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "Page action by URL",
- "version": "1.0",
- "description": "Shows a page action for urls which have the letter 'g' in them.",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "page_action" :
- {
- "default_icon" : "icon-19.png",
- "default_title" : "There's a 'G' in this URL!"
- },
- "permissions" : [
- "declarativeContent"
- ],
- "icons" : {
- "48" : "icon-48.png",
- "128" : "icon-128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json
deleted file mode 100644
index 115b8d0124a..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "Animated Page Action",
- "description": "This extension adds an animated browser action to the toolbar.",
- "version": "1.2",
- "background": {
- "page": "background.html"
- },
- "page_action": {
- "default_title": "First icon"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json
deleted file mode 100644
index 2d8647edc30..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "Top Chrome Extension Questions",
- "version": "0.3",
- "description": "Sample demonstration of the optional permissions API.",
- "icons": {
- "128": "images/icon.png",
- "48": "images/icon.png",
- "16": "images/icon.png"
- },
- "browser_action": {
- "default_icon": "images/icon.png",
- "default_popup": "popup.html"
- },
- "options_page": "options.html",
- "optional_permissions": ["http://api.stackoverflow.com/"],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/power/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/api/power/_locales/en/messages.json
deleted file mode 100644
index ef50944de10..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/power/_locales/en/messages.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "extensionName": {
- "message": "Keep Awake",
- "description": "Extension name."
- },
- "extensionDescription": {
- "message": "Override system power-saving settings.",
- "description": "Extension description."
- },
- "disabledTitle": {
- "message": "Default power-saving settings",
- "description": "Browser action title when disabled."
- },
- "displayTitle": {
- "message": "Screen will be kept on",
- "description": "Browser action title when preventing screen-off."
- },
- "systemTitle": {
- "message": "System will stay awake",
- "description": "Browser action title when preventing system sleep."
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/power/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/power/manifest.json
deleted file mode 100644
index dd90842bd1c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/power/manifest.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "manifest_version": 2,
-
- "name": "__MSG_extensionName__",
- "description": "__MSG_extensionDescription__",
- "version": "1.9",
- "icons": {
- "16": "images/icon-16.png",
- "48": "images/icon-48.png",
- "128": "images/icon-128.png"
- },
-
- "permissions": [
- "power",
- "storage"
- ],
- "browser_action": {
- "default_title": "__MSG_disabledTitle__",
- "default_icon": {
- "19": "images/night-19.png",
- "38": "images/night-38.png"
- }
- },
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
-
- "default_locale": "en"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json
deleted file mode 100644
index 25114067490..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name" : "Block/allow third-party cookies API example extension",
- "version" : "0.1",
- "description" : "Sample extension which demonstrates how to access a preference.",
- "permissions": [ "privacy" ],
- "browser_action": {
- "default_icon": "advicedog.jpg",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json
deleted file mode 100644
index c72e8959829..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name" : "Block/allow referrer API example extension",
- "version" : "0.1",
- "description" : "Sample extension which demonstrates how to access a preference.",
- "permissions": [ "privacy" ],
- "browser_action": {
- "default_icon": "advicedog.jpg",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json
deleted file mode 100644
index 2c1836a6f4a..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "Process Monitor",
- "version": "1.2",
- "description": "Adds a browser action that monitors resource usage of all browser processes.",
- "permissions": [
- "processes"
- ],
- "browser_action": {
- "default_title": "Process Monitor",
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json
deleted file mode 100644
index bdbdb1e2254..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "Show Tabs in Process",
- "version": "1.0",
- "description": "Adds a browser action showing which tabs share the current tab's process.",
- "permissions": [
- "processes", "tabs", "chrome://favicon/*"
- ],
- "browser_action": {
- "default_title": "Show Tabs in this Process",
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json
deleted file mode 100644
index 1c0b9cebf0d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "Stylizr",
- "description": "Spruce up your pages with custom CSS.",
- "version": "1.0",
-
- "permissions": [
- "activeTab",
- "storage"
- ],
-
- "options_page": "options.html",
-
- "browser_action": {
- "default_icon": "icon.png",
- "default_title": "Stylize!",
- "default_popup": "popup.html"
- },
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/tabCapture/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/tabCapture/manifest.json
deleted file mode 100644
index 777e1e256e3..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/tabCapture/manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "Tab Capture Example",
- "description": "Capture a tab and play in a <video> element in a separate tab.",
- "version": "1",
- "manifest_version": 2,
- "background": {
- "scripts": ["eventPage.js"],
- "persistent": false
- },
- "browser_action": {
- "default_icon": "icon.png"
- },
- "options_ui": {
- "page": "options.html",
- "open_in_tab": false
- },
- "permissions": [
- "storage",
- "tabs",
- "tabCapture"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json
deleted file mode 100644
index 68b7728ec3f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "Tab Inspector",
- "description": "Utility for working with the extension tabs api",
- "version": "0.3",
- "permissions": ["tabs"],
- "background": {
- "persistent": false,
- "scripts": ["background.js"]
- },
- "browser_action": {
- "default_title": "show tab inspector"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json
deleted file mode 100644
index 3154c8c9368..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "Keyboard Pin",
- "version": "0.3",
- "description": "Creates a keyboard shortcut (Alt + Shift + P) to toggle the pinned state of the currently selected tab",
- "background": {
- "persistent": false,
- "scripts": ["background.js"]
- },
- "commands": {
- "toggle-pin": {
- "suggested_key": { "default": "Alt+Shift+P" },
- "description": "Toggle tab pin"
- }
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json
deleted file mode 100644
index 2fae69d1e5e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "Test Screenshot Extension",
- "version": "1.3",
- "description": "Demonstrate screenshot functionality in the chrome.tabs api.",
- "background": {
- "persistent": false,
- "scripts": ["background.js"]
- },
- "browser_action": {
- "default_icon": "camera.png",
- "default_title": "Take a screen shot!"
- },
- "permissions": [
- "activeTab"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/tabs/zoom/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/tabs/zoom/manifest.json
deleted file mode 100644
index 51db85b6d40..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/tabs/zoom/manifest.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "manifest_version": 2,
-
- "name": "Tabs Zoom API Demo",
- "description": "This extension allows the user to explore features of the new tabs zoom api.",
-
- "version": "0.1",
-
- "icons": {
- "16": "zoom16.png",
- "48": "zoom48.png"
- },
-
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
-
- "browser_action": {
- "default_icon": "zoom19.png",
- "default_title": "Zoom Extension Demo",
- "default_popup": "popup.html"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/topsites/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/topsites/basic/manifest.json
deleted file mode 100644
index c042c095a4e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/topsites/basic/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "Top Sites",
- "version": "1.2",
- "description": "Shows the top sites in a browser action",
- "permissions": ["topSites"],
- "browser_action": {
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json
deleted file mode 100644
index 84f12ac5aae..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "NTP prototyping extension",
- "version": "1.1",
- "description": "extension to prototype new NTP designs",
- "chrome_url_overrides" : {
- "newtab": "newTab.html"
- },
- "permissions": [
- "topSites",
- "chrome://favicon/"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json
deleted file mode 100644
index 35be832ec8f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "Console TTS Engine",
- "manifest_version": 2,
- "version": "2.1",
- "description": "A \"silent\" TTS engine that prints text to a small window rather than synthesizing speech.",
- "permissions": ["ttsEngine", "tabs"],
- "background": {
- "persistent": false,
- "scripts": ["console_tts_engine.js"]
- },
- "tts_engine": {
- "voices": [
- {
- "voice_name": "Console",
- "event_types": ["start", "word", "sentence", "end"]
- }
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/water_alarm_notification/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/water_alarm_notification/manifest.json
deleted file mode 100644
index a4832da210f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/water_alarm_notification/manifest.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "name": "Drink Water Event Popup",
- "description": "Demonstrates usage and features of the event page by reminding user to drink water",
- "version": "1.0",
- "manifest_version": 2,
- "permissions": ["alarms", "notifications", "storage"],
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "browser_action": {
- "default_title": "Drink Water Event",
- "default_popup": "popup.html"
- },
- "icons": {
- "16": "drink_water16.png",
- "32": "drink_water32.png",
- "48": "drink_water48.png",
- "128": "drink_water128.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json
deleted file mode 100644
index e639880d7c4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "extName": {
- "message": "WebNavigation Tech Demo",
- "description": "The extension name."
- },
- "extDescription": {
- "message": "Demonstration of the WebNavigation extension API.",
- "description": "The extension description."
- },
-
- "navigationDescription": {
- "message": ", requested $NUM$ times. Loaded in an average of $LOAD$ miliseconds.",
- "description": "The message posted in the popup for each stored navigation.",
- "placeholders": {
- "NUM": {
- "content": "$1",
- "example": "4 (The number of times this URL was accessed.)"
- },
- "LOAD": {
- "content": "$2",
- "example": "12.345 (The average load time in miliseconds.)"
- }
- }
- },
-
- "inHandler": {
- "message": "In webNavigation[`%s`] handler: %o",
- "description": "Notification displayed for each webNavigation event."
- },
-
- "inHandlerError": {
- "message": "In webNavigation[`%s`] handler: No data!",
- "description": "Notification displayed in a webNavigation event handler without data!"
- },
-
- "errorCommittedWithoutPending": {
- "message": "Wha? `onCommitted` for `%s` called, though it's not pending: %o",
- "description": "Error logged when `onCommitted` is triggered on a non-pending request."
- },
- "errorCompletedWithoutPending": {
- "message": "Wha? `onCompleted` for `%s` called, though it's not pending: %o",
- "description": "Error logged when `onCompleted` is triggered on a non-pending request."
- },
- "errorErrorOccurredWithoutPending": {
- "message": "Wha? `onErrorOccurred` for `%s` called, though it's not pending: %o",
- "description": "Error logged when `onErrorOccurred` is triggered on a non-pending request."
- },
- "errorCommittedWithoutPending": {
- "message": "Wha? `onCompleted` for `%s` called, though it's not pending: %o",
- "description": "Error logged when `onCompleted` is triggered on a non-pending request."
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json
deleted file mode 100644
index 2c0d43519dc..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "__MSG_extName__",
- "version": "0.2",
- "description": "__MSG_extDescription__",
- "default_locale": "en",
- "background": {
- "persistent": false,
- "scripts": ["navigation_collector.js", "background.js"]
- },
- "browser_action": {
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "permissions": [
- "webNavigation", "storage"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/webview/capturevisibleregion/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/webview/capturevisibleregion/manifest.json
deleted file mode 100644
index 6b27ec4102f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/webview/capturevisibleregion/manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "manifest_version": 2,
- "name": "Webview transparency",
- "description": "Sample of the webview.captureVisibleRegion api",
- "version": "1",
- "app": {
- "background": {
- "scripts": ["main.js"]
- }
- },
- "permissions": [
- "webview"
- ],
- "webview": {
- "partitions": [
- {
- "name": "partition",
- "accessible_resources": [ "test2.html" ]
- }
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_app/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_app/manifest.json
deleted file mode 100644
index 340d590139a..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_app/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "manifest_version": 2,
- "name": "WebView Extension Communications Demo: App",
- "version": "1",
- "app": {
- "background": {
- "scripts": ["main.js"]
- }
- },
- "permissions": ["webview"],
- "webview": {
- "partitions": [
- {
- "name": "partition",
- "accessible_resources": [""]
- }
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_ext/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_ext/manifest.json
deleted file mode 100644
index 0869009b022..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/webview/comm_demo_ext/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "manifest_version": 2,
-
- "name": "WebView Extension Communications Demo: Extension",
- "description": "Provides content scripts to an app hosting a WebView.",
- "version": "1.0",
-
- "background": {
- "scripts": ["background.js"]
- },
- "permissions": [
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json b/chromium/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json
deleted file mode 100644
index b5b51737667..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "Merge Windows",
- "version": "1.0.2",
- "description": "Merges all of the browser's windows into the current window",
- "icons": {
- "48": "merge_windows_48.png",
- "128": "merge_windows_128.png"
- },
- "background": {
- "persistent": false,
- "scripts": ["background.js"]
- },
- "browser_action": {
- "default_icon": "arrow_in.png",
- "default_title": "Merge Windows"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json b/chromium/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json
deleted file mode 100644
index 086138459e6..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "Simple Background App",
- "version": "0.2",
- "app": {
- "urls": [ "http://SOME_SITE_WITHOUT_PORT_NUMBERS/SOME_PATH/" ],
- "launch": {
- "web_url": "http://SOME_SITE/SOME_PATH/index.html"
- }
- },
- "permissions": ["background", "notifications"],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json b/chromium/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json
deleted file mode 100644
index 09b249e0d8c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "Calculator",
- "description": "A simple calculator.",
- "manifest_version": 2,
- "minimum_chrome_version": "23",
- "version": "1.3.3",
- "app": {"background": {"scripts": ["model.js", "view.js", "controller.js"]}},
- "icons": {
- "16": "images/icon-16x16.png",
- "128": "images/icon-128x128.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json
deleted file mode 100644
index 78e0c92406e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "App Launcher",
- "description": "Get access to your apps in a browser action",
- "version": "0.7.3",
- "permissions": ["management"],
- "browser_action": {
- "default_icon": "browser_action_icon.png",
- "default_title": "App Launcher",
- "default_popup": "popup.html"
- },
- "icons": {
- "48": "icon.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json
deleted file mode 100644
index 0d86bcb620b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "Chromium Buildbot Monitor",
- "version": "0.9.0",
- "description": "Displays the status of the Chromium buildbot in the toolbar. Click to see more detailed status in a popup.",
- "icons": { "128": "icon.png" },
- "background": {
- "scripts": ["utils.js",
- "prefs.js",
- "try_status.js",
- "active_issues.js",
- "bg.js"]
- },
- "permissions": [
- "notifications",
- "storage",
- "http://build.chromium.org/",
- "http://chromium-status.appspot.com/",
- "https://codereview.chromium.org/"
- ],
- "browser_action": {
- "default_title": "",
- "default_icon": "chromium.png",
- "default_popup": "popup.html"
- },
- "options_page": "options.html",
- "options_ui": {
- "chrome_style": true,
- "page": "options.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json
deleted file mode 100644
index 3fd46a01d4d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u062a\u062f\u0639\u0645\u0647\u0627 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u064a\u0645\u0643\u0646\u0643 \u0628\u0633\u0631\u0639\u0629 \u0627\u0644\u062a\u0639\u0631\u0641 \u0639\u0644\u0649 \u0627\u0644\u0648\u0642\u062a \u0627\u0644\u0645\u062a\u0628\u0642\u064a \u0644\u0643 \u0625\u0644\u0649 \u0623\u0646 \u064a\u062d\u064a\u0646 \u0645\u0648\u0639\u062f \u0627\u062c\u062a\u0645\u0627\u0639\u0643 \u0627\u0644\u062a\u0627\u0644\u064a \u0645\u0646 \u0623\u064a \u062a\u0642\u0648\u064a\u0645 \u0645\u0646 \u062a\u0642\u0627\u0648\u064a\u0645\u0643. \u0627\u0646\u0642\u0631 \u0639\u0644\u0649 \u0627\u0644\u0632\u0631 \u0644\u064a\u062a\u0645 \u0646\u0642\u0644\u0643 \u0625\u0644\u0649 \u0627\u0644\u062a\u0642\u0648\u064a\u0645."},"direction":{"message":"rtl"},"notitle":{"message":"(\u0644\u064a\u0633 \u0647\u0646\u0627\u0643 \u0623\u064a \u0639\u0646\u0648\u0627\u0646)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u062f\u0642\u064a\u0642\u0629","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0633\u0627\u0639\u0629","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u064a\u0648\u0645","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u062f\u0639\u0645 \u0645\u062a\u0639\u062f\u062f \u0644\u0644\u062a\u0642\u0648\u064a\u0645"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u062a\u0645 \u062d\u0641\u0638 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a."},"status_saving":{"message":"\u062c\u0627\u0631\u0650 \u0627\u0644\u062d\u0641\u0638..."},"multicalendartooltip":{"message":"\u0627\u0644\u0631\u062c\u0627\u0621 \u062a\u062d\u062f\u064a\u062f \u0627\u0644\u0645\u0631\u0628\u0639 \u0644\u062a\u0645\u0643\u064a\u0646 \u0627\u0644\u062f\u0639\u0645 \u0627\u0644\u0645\u062a\u0639\u062f\u062f \u0644\u0644\u062a\u0642\u0648\u064a\u0645."},"imagetooltip":{"message":"\u062a\u0642\u0648\u064a\u0645 Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json
deleted file mode 100644
index e7dc1a674d5..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0412\u0438\u0436\u0442\u0435 \u0431\u044a\u0440\u0437\u043e \u043a\u043e\u043b\u043a\u043e \u0432\u0440\u0435\u043c\u0435 \u0432\u0438 \u043e\u0441\u0442\u0430\u0432\u0430 \u0434\u043e \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0430\u0442\u0430 \u0432\u0438 \u0441\u0440\u0435\u0449\u0430 \u043e\u0442 \u0432\u0441\u0435\u043a\u0438 \u0441\u0432\u043e\u0439 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440. \u041a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u0432\u044a\u0440\u0445\u0443 \u0431\u0443\u0442\u043e\u043d\u0430, \u0437\u0430 \u0434\u0430 \u0433\u043e \u043e\u0442\u0432\u043e\u0440\u0438\u0442\u0435."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0411\u0435\u0437 \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u043c","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0447","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u0434","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u043e\u0434\u0434\u0440\u044a\u0436\u043a\u0430 \u043d\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0441\u0430 \u0437\u0430\u043f\u0430\u0437\u0435\u043d\u0438."},"status_saving":{"message":"\u0417\u0430\u043f\u0430\u0437\u0432\u0430 \u0441\u0435...."},"multicalendartooltip":{"message":"\u041c\u043e\u043b\u044f, \u043f\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043e\u0442\u043c\u0435\u0442\u043a\u0430 \u0432 \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u0447\u0435\u0442\u043e, \u0437\u0430 \u0434\u0430 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0442\u0435 \u043f\u043e\u0434\u0434\u0440\u044a\u0436\u043a\u0430\u0442\u0430 \u043d\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"imagetooltip":{"message":"Google \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json
deleted file mode 100644
index b0ab0c644da..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (de Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Comproveu r\u00e0pidament el temps que falta per a la propera reuni\u00f3 a qualsevol dels vostres calendaris. Feu clic al bot\u00f3 per anar-hi."},"direction":{"message":"ltr"},"notitle":{"message":"(Sense t\u00edtol)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 dies","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Compatibilitat amb m\u00faltiples calendaris"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"S'ha desat la configuraci\u00f3."},"status_saving":{"message":"S'est\u00e0 desant..."},"multicalendartooltip":{"message":"Activeu la casella per activar la compatibilitat amb m\u00faltiples calendaris"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json
deleted file mode 100644
index 6e31a42ee17..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Kontrola Kalend\u00e1\u0159e Google (od spole\u010dnosti Google)"},"title":{"message":"Kontrola Kalend\u00e1\u0159e Google"},"description":{"message":"Umo\u017e\u0148uje rychle zobrazit \u010das, kter\u00fd zb\u00fdv\u00e1 do dal\u0161\u00ed sch\u016fzky napl\u00e1novan\u00e9 v kter\u00e9mkoli z va\u0161ich kalend\u00e1\u0159\u016f. Kliknut\u00edm na tla\u010d\u00edtko p\u0159ejdete do kalend\u00e1\u0159e."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez n\u00e1zvu)"},"optionstitle":{"message":"Kontrola Kalend\u00e1\u0159e Google"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podpora n\u011bkolika kalend\u00e1\u0159\u016f"},"extensionname":{"message":"Kontrola Kalend\u00e1\u0159e Google"},"status_saved":{"message":"Nastaven\u00ed byla ulo\u017eena."},"status_saving":{"message":"Ukl\u00e1d\u00e1n\u00ed...."},"multicalendartooltip":{"message":"Za\u0161krtnut\u00edm pol\u00ed\u010dka pros\u00edm povolte podporu v\u00edce kalend\u00e1\u0159\u016f"},"imagetooltip":{"message":"Kalend\u00e1\u0159 Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json
deleted file mode 100644
index 209b6d90568..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (fra Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Tjek hurtigt hvor lang tid der er til n\u00e6ste m\u00f8de i en af dine kalendre. Klik p\u00e5 knappen for at \u00e5bne din kalender."},"direction":{"message":"ltr"},"notitle":{"message":"(Ingen titel)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 t","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Support til flere kalendere"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Indstillingerne er gemt."},"status_saving":{"message":"Gemmer..."},"multicalendartooltip":{"message":"S\u00e6t kryds i feltet for at aktivere support til flere kalendere"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json
deleted file mode 100644
index 3892f3975a2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Werfen Sie schnell einen Blick auf die verbleibende Zeit bis zum n\u00e4chsten Termin in einem Ihrer Kalender. Klicken Sie zum \u00d6ffnen Ihres Kalenders auf die Schaltfl\u00e4che."},"direction":{"message":"ltr"},"notitle":{"message":"(Kein Titel)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 Min.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 Std.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 Tage","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Unterst\u00fctzung mehrerer Kalender"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Einstellungen gespeichert"},"status_saving":{"message":"Speichern...."},"multicalendartooltip":{"message":"Aktivieren Sie das Kontrollk\u00e4stchen, um die Unterst\u00fctzung mehrerer Kalender einzuschalten."},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json
deleted file mode 100644
index 9cd461d443e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0394\u03b5\u03af\u03c4\u03b5 \u03b3\u03c1\u03ae\u03b3\u03bf\u03c1\u03b1 \u03c0\u03cc\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c3\u03c5\u03bd\u03ac\u03bd\u03c4\u03b7\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03bf\u03c0\u03bf\u03b9\u03bf\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03cc\u03b3\u03b9\u03ac \u03c3\u03b1\u03c2. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03c6\u03b5\u03c1\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03cc\u03b3\u03b9\u03cc \u03c3\u03b1\u03c2."},"direction":{"message":"ltr"},"notitle":{"message":"(\u03a7\u03c9\u03c1\u03af\u03c2 \u03c4\u03af\u03c4\u03bb\u03bf)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u03bb","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u03c9","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u03b7","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u03a5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c0\u03bf\u03bb\u03bb\u03ce\u03bd \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03bf\u03b3\u03af\u03c9\u03bd"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u039f\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b1\u03bd."},"status_saving":{"message":"\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7...."},"multicalendartooltip":{"message":"\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03c0\u03bb\u03b1\u03af\u03c3\u03b9\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c0\u03bf\u03bb\u03bb\u03ce\u03bd \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03bf\u03b3\u03af\u03c9\u03bd"},"imagetooltip":{"message":"\u0397\u03bc\u03b5\u03c1\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json
deleted file mode 100644
index 2b19e00a5f3..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Quickly see the time until your next meeting from any of your calendars. Click on the button to be taken to your calendar."},"direction":{"message":"ltr"},"notitle":{"message":"(No Title)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi Calendar Support"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Settings Saved."},"status_saving":{"message":"Saving...."},"multicalendartooltip":{"message":"Please check the box to enable multiple calendar support"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json
deleted file mode 100644
index 87ef60e9d36..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Quickly see the time until your next meeting from any of your calendars. Click the button to be taken to your calendar."},"direction":{"message":"ltr"},"notitle":{"message":"(No Title)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi-Calendar Support"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Settings Saved."},"status_saving":{"message":"Saving...."},"multicalendartooltip":{"message":"Please tick the box to enable multiple calendar support"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json
deleted file mode 100644
index 03d60100222..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (de Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Consulta r\u00e1pidamente cu\u00e1nto tiempo falta para tu pr\u00f3xima reuni\u00f3n en cualquiera de tus calendarios. Haz clic en el bot\u00f3n para acceder al calendario."},"direction":{"message":"ltr"},"notitle":{"message":"(Sin t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Compatibilidad con varios calendarios"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Se ha guardado la configuraci\u00f3n."},"status_saving":{"message":"Guardando...."},"multicalendartooltip":{"message":"Selecciona la casilla para habilitar la compatibilidad con varios calendarios"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json
deleted file mode 100644
index e7b2536a293..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (de Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"F\u00edjate r\u00e1pidamente cu\u00e1nto tiempo falta para tu pr\u00f3xima reuni\u00f3n en alguno de tus calendarios. Haz clic en el bot\u00f3n para que te lleve hasta tu calendario."},"direction":{"message":"ltr"},"notitle":{"message":"(Sin t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u00a0m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00a0h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Ayuda de Multi Calendar"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Configuraciones guardadas"},"status_saving":{"message":"Guardando...."},"multicalendartooltip":{"message":"Marca el casillero para habilitar la ayuda del calendario m\u00faltiple."},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json
deleted file mode 100644
index 5828ccd7569..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Vaadake kiiresti oma j\u00e4rgmise koosolekuni j\u00e4\u00e4nud aega \u00fcksk\u00f5ik millisest oma kalendrist. Kalendrisse minekuks kl\u00f5psake nupul."},"direction":{"message":"ltr"},"notitle":{"message":"(Pealkiri puudub)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 p\u00e4ev(a)","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi-Calendari tugi"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Seaded salvestatud."},"status_saving":{"message":"Salvestamine ..."},"multicalendartooltip":{"message":"Mitme kalendri toe lubamiseks m\u00e4rkige ruut"},"imagetooltip":{"message":"Google'i kalender"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json
deleted file mode 100644
index ddc20889a7e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (Googlen tekem\u00e4)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"N\u00e4et nopeasti ajan seuraavaan tapaamiseesi mist\u00e4 tahansa kalenteristasi. Siirryt kalenteriin napsauttamalla painiketta."},"direction":{"message":"ltr"},"notitle":{"message":"(Ei nime\u00e4)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 t","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 pv","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Useiden kalentereiden tuki"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Asetukset tallennettu."},"status_saving":{"message":"Tallennetaan..."},"multicalendartooltip":{"message":"Ota useiden kalentereiden tuki k\u00e4ytt\u00f6\u00f6n valitsemalla valintaruutu"},"imagetooltip":{"message":"Google-kalenteri"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json
deleted file mode 100644
index 16cdebaed25..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (mula sa Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Mabilisang tingnan ang oras hanggang sa iyong susunod na pagpupulong mula sa alinman sa iyong mga kalendaryo. Mag-click sa pindutan upang mapunta sa iyong kalendaryo."},"direction":{"message":"ltr"},"notitle":{"message":"(Walang Pamagat)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1o","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1a","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Suporta sa Maramihang Kalendaryo"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Na-save ang Mga Setting."},"status_saving":{"message":"Sine-save...."},"multicalendartooltip":{"message":"Pakilagyan ng check ang kahon upang paganahin ang suporta sa maramihang kalendaryo"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json
deleted file mode 100644
index f8e413af922..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google\u00a0Calendar Checker (par Google)"},"title":{"message":"Google\u00a0Calendar Checker"},"description":{"message":"V\u00e9rifiez rapidement dans vos agendas le temps qu'il vous reste avant votre prochaine r\u00e9union. Cliquez sur le bouton pour ouvrir l'agenda correspondant."},"direction":{"message":"ltr"},"notitle":{"message":"(Sans titre)"},"optionstitle":{"message":"Google\u00a0Calendar Checker"},"minutes":{"message":"$1\u00a0mn","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00a0h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u00a0j","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Prise en charge multi-agendas"},"extensionname":{"message":"Google\u00a0Calendar Checker"},"status_saved":{"message":"Param\u00e8tres enregistr\u00e9s"},"status_saving":{"message":"Enregistrement...."},"multicalendartooltip":{"message":"Cocher la case pour activer la prise en charge multi-agendas"},"imagetooltip":{"message":"Google\u00a0Agenda"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json
deleted file mode 100644
index 09f16548158..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u05de\u05d0\u05ea Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u05e8\u05d0\u05d4 \u05d1\u05de\u05d4\u05d9\u05e8\u05d5\u05ea \u05db\u05de\u05d4 \u05d6\u05de\u05df \u05d9\u05e9 \u05dc\u05da \u05e2\u05d3 \u05d4\u05e4\u05d2\u05d9\u05e9\u05d4 \u05d4\u05d1\u05d0\u05d4 \u05e9\u05dc\u05da \u05de\u05db\u05dc \u05dc\u05d5\u05d7 \u05e9\u05e0\u05d4 \u05e9\u05dc\u05da. \u05dc\u05d7\u05e5 \u05e2\u05dc \u05d4\u05dc\u05d7\u05e6\u05df \u05db\u05d3\u05d9 \u05dc\u05e2\u05d1\u05d5\u05e8 \u05dc\u05dc\u05d5\u05d7 \u05d4\u05e9\u05e0\u05d4 \u05e9\u05dc\u05da."},"direction":{"message":"rtl"},"notitle":{"message":"(\u05dc\u05dc\u05d0 \u05db\u05d5\u05ea\u05e8\u05ea)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u05d3'","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u05e9'","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u05d9'","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05d9\u05d5\u05de\u05e0\u05d9\u05dd \u05de\u05e8\u05d5\u05d1\u05d9\u05dd"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u05d4\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05e0\u05e9\u05de\u05e8\u05d5."},"status_saving":{"message":"\u05e9\u05d5\u05de\u05e8...."},"multicalendartooltip":{"message":"\u05e1\u05de\u05df \u05d0\u05ea \u05d4\u05ea\u05d9\u05d1\u05d4 \u05db\u05d3\u05d9 \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05ea \u05d4\u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05d9\u05d5\u05de\u05e0\u05d9\u05dd \u05de\u05e8\u05d5\u05d1\u05d9\u05dd \u05dc\u05e4\u05e2\u05d9\u05dc\u05d4"},"imagetooltip":{"message":"\u05d9\u05d5\u05de\u05df Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json
deleted file mode 100644
index 2b19e00a5f3..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Quickly see the time until your next meeting from any of your calendars. Click on the button to be taken to your calendar."},"direction":{"message":"ltr"},"notitle":{"message":"(No Title)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi Calendar Support"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Settings Saved."},"status_saving":{"message":"Saving...."},"multicalendartooltip":{"message":"Please check the box to enable multiple calendar support"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json
deleted file mode 100644
index f2ab5e5ce8e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (od Googlea)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Brzo pogledajte koliko imate vremena do idu\u0107eg sastanka iz svih svojih kalendara. Kliknite gumb koji \u0107e vas odvesti u va\u0161 kalendar."},"direction":{"message":"ltr"},"notitle":{"message":"(Nema naslova)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podr\u0161ka za vi\u0161e kalendara"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Postavke su spremljene."},"status_saving":{"message":"Spremanje...."},"multicalendartooltip":{"message":"Uklju\u010dite potvrdni okvir za omogu\u0107avanje podr\u0161ke za vi\u0161e kalendara"},"imagetooltip":{"message":"Google Kalendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json
deleted file mode 100644
index 6be01dc6fdb..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (a Google-t\u00f3l)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Gyorsan megn\u00e9zheti b\u00e1rmelyik napt\u00e1r\u00e1ban, hogy mennyi ideje van m\u00e9g a k\u00f6vetkez\u0151 tal\u00e1lkoz\u00f3ig. Kattintson a gombra a napt\u00e1r megtekint\u00e9s\u00e9hez."},"direction":{"message":"ltr"},"notitle":{"message":"(Nincs c\u00edm)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1p","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00f3","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1nap","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"T\u00f6bb napt\u00e1r t\u00e1mogat\u00e1sa"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Be\u00e1ll\u00edt\u00e1sok elmentve."},"status_saving":{"message":"Ment\u00e9s..."},"multicalendartooltip":{"message":"K\u00e9rj\u00fck, jel\u00f6lje be a jel\u00f6l\u0151n\u00e9gyzetet t\u00f6bb napt\u00e1r t\u00e1mogat\u00e1s\u00e1nak enged\u00e9lyez\u00e9s\u00e9hez"},"imagetooltip":{"message":"Google Napt\u00e1r"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json
deleted file mode 100644
index 37056ba89e6..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Pemeriksa Google Kalender (oleh Google)"},"title":{"message":"Pemeriksa Google Kalender"},"description":{"message":"Lihat waktu dengan cepat sampai pertemuan berikutnya dari kalender apa pun. Klik tombol untuk diarahkan ke kalender Anda."},"direction":{"message":"ltr"},"notitle":{"message":"(Tanpa Judul)"},"optionstitle":{"message":"Pemeriksa Google Kalender"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1j","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Dukungan Multi-Kalender"},"extensionname":{"message":"Pemeriksa Google Kalender"},"status_saved":{"message":"Setelan Disimpan."},"status_saving":{"message":"Menyimpan..."},"multicalendartooltip":{"message":"Harap periksa kotak untuk mengaktifkan dukungan multi-kalender"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json
deleted file mode 100644
index 90e8dafa8f1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (di Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Usa uno dei tuoi calendari per controllare rapidamente quanto manca alla prossima riunione. Fai clic sul pulsante per accedere al calendario."},"direction":{"message":"ltr"},"notitle":{"message":"(Nessun titolo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 g","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Supporto di pi\u00f9 calendari"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Impostazioni salvate."},"status_saving":{"message":"Salvataggio in corso..."},"multicalendartooltip":{"message":"Seleziona la casella per attivare il supporto di pi\u00f9 calendari"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json
deleted file mode 100644
index 30cdf0bbbb6..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker\uff08by Google\uff09"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u3069\u306e\u30ab\u30ec\u30f3\u30c0\u30fc\u304b\u3089\u3067\u3082\u6b21\u306e\u4f1a\u8b70\u307e\u3067\u306e\u6642\u9593\u3092\u3059\u3070\u3084\u304f\u30c1\u30a7\u30c3\u30af\u3002\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u30ab\u30ec\u30f3\u30c0\u30fc\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u3059\u3002"},"direction":{"message":"ltr"},"notitle":{"message":"\uff08\u30bf\u30a4\u30c8\u30eb\u306a\u3057\uff09"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u8907\u6570\u306e\u30ab\u30ec\u30f3\u30c0\u30fc\u306b\u5bfe\u5fdc"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u8a2d\u5b9a\u304c\u4fdd\u5b58\u3055\u308c\u307e\u3057\u305f\u3002"},"status_saving":{"message":"\u4fdd\u5b58\u3057\u3066\u3044\u307e\u3059..."},"multicalendartooltip":{"message":"\u8907\u6570\u306e\u30ab\u30ec\u30f3\u30c0\u30fc\u3092\u4f7f\u7528\u3067\u304d\u308b\u3088\u3046\u306b\u3059\u308b\u306b\u306f\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u30aa\u30f3\u306b\u3057\u3066\u304f\u3060\u3055\u3044"},"imagetooltip":{"message":"Google \u30ab\u30ec\u30f3\u30c0\u30fc"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json
deleted file mode 100644
index 4e381a8cdce..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8(Google \uc81c\uacf5)"},"title":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8"},"description":{"message":"\uce98\ub9b0\ub354 \uc5b4\ub514\uc5d0\uc11c\ub098 \ub2e4\uc74c \ubaa8\uc784\uae4c\uc9c0 \ub0a8\uc740 \uc2dc\uac04\uc744 \uc2e0\uc18d\ud558\uac8c \uc0b4\ud3b4\ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uce98\ub9b0\ub354\ub85c \uc774\ub3d9\ud558\ub824\uba74 \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc138\uc694."},"direction":{"message":"ltr"},"notitle":{"message":"(\uc81c\ubaa9 \uc5c6\uc74c)"},"optionstitle":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\uc5ec\ub7ec \uce98\ub9b0\ub354 \uc9c0\uc6d0"},"extensionname":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8"},"status_saved":{"message":"\uc124\uc815\uc744 \uc800\uc7a5\ud588\uc2b5\ub2c8\ub2e4."},"status_saving":{"message":"\uc800\uc7a5 \uc911..."},"multicalendartooltip":{"message":"\uc5ec\ub7ec \uce98\ub9b0\ub354 \uc9c0\uc6d0\uc744 \uc0ac\uc6a9\ud558\ub3c4\ub85d \uc124\uc815\ud558\ub824\uba74 \ud655\uc778\ub780\uc744 \uc120\ud0dd\ud558\uc138\uc694."},"imagetooltip":{"message":"Google \uce98\ub9b0\ub354"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json
deleted file mode 100644
index aac503ff0f1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Bet kuriame i\u0161 savo kalendori\u0173 greitai \u017ei\u016br\u0117kite, kiek laiko liko iki kito susitikimo. Jei norite patekti \u012f kalendori\u0173, spustel\u0117kite mygtuk\u0105."},"direction":{"message":"ltr"},"notitle":{"message":"(N\u0117ra pavadinimo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 val.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d.","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Keli\u0173 kalendori\u0173 palaikymas"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Nustatymai i\u0161saugoti."},"status_saving":{"message":"I\u0161saugoma..."},"multicalendartooltip":{"message":"Kad \u012fgalintum\u0117te keli\u0173 kalendori\u0173 palaikym\u0105, pa\u017eym\u0117kite laukel\u012f"},"imagetooltip":{"message":"\u201eGoogle\u201c kalendorius"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json
deleted file mode 100644
index be331835676..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (nodro\u0161ina Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Varat \u0101tri skat\u012bt, cik daudz laika atlicis l\u012bdz n\u0101kamajai sapulcei, izmantojot jebkuru no saviem kalend\u0101riem. Lai atv\u0113rtu kalend\u0101ru, nospiediet pogu."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez nosaukuma)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u00a0min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00a0h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u00a0d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Vair\u0101ku kalend\u0101ru atbalsts"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Iestat\u012bjumi ir saglab\u0101ti."},"status_saving":{"message":"Notiek saglab\u0101\u0161ana..."},"multicalendartooltip":{"message":"L\u016bdzu, atz\u012bm\u0113jiet lodzi\u0146u, lai iesp\u0113jotu vair\u0101ku kalend\u0101ru atbalstu."},"imagetooltip":{"message":"Google kalend\u0101rs"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json
deleted file mode 100644
index 666df6e58c2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (laget av Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Finn ut raskt hvor lenge det er til neste m\u00f8te fra alle kalendrene dine. Klikk p\u00e5 knappen for \u00e5 g\u00e5 videre til kalenderen."},"direction":{"message":"ltr"},"notitle":{"message":"(Ingen tittel)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 t","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"St\u00f8tte for flere kalendere"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Innstillinger lagret."},"status_saving":{"message":"Lagrer \u2026"},"multicalendartooltip":{"message":"Merk av i ruten for \u00e5 aktivere st\u00f8tte for flere kalendere"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json
deleted file mode 100644
index 9ffa2224652..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (van Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Snel in al uw agenda's bekijken hoe lang het nog duurt voordat uw volgende vergadering begint. Klik op de knop om naar uw agenda te gaan."},"direction":{"message":"ltr"},"notitle":{"message":"(Naamloos)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 u","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Ondersteuning voor meerdere agenda's"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Instellingen zijn opgeslagen."},"status_saving":{"message":"Opslaan..."},"multicalendartooltip":{"message":"Vink het selectievakje aan om de ondersteuning voor meerdere agenda's in te schakelen"},"imagetooltip":{"message":"Google Agenda"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json
deleted file mode 100644
index 2a3d471d2a7..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Szybko sprawd\u017a w dowolnym kalendarzu, ile masz czasu do nast\u0119pnego zebrania. Kliknij przycisk, aby przej\u015b\u0107 do kalendarza."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez tytu\u0142u)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1g","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Obs\u0142uga wielu kalendarzy"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Zapisano ustawienia."},"status_saving":{"message":"Zapisywanie..."},"multicalendartooltip":{"message":"Zaznacz pole, aby w\u0142\u0105czy\u0107 obs\u0142ug\u0119 wielu kalendarzy"},"imagetooltip":{"message":"Kalendarz Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json
deleted file mode 100644
index 3a79f1ddd6d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (do Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Veja rapidamente quanto tempo voc\u00ea tem at\u00e9 seu pr\u00f3ximo compromisso. Clique no bot\u00e3o para abrir sua agenda."},"direction":{"message":"ltr"},"notitle":{"message":"(Sem t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Suporte para v\u00e1rias agendas"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Configura\u00e7\u00f5es salvas."},"status_saving":{"message":"Salvando..."},"multicalendartooltip":{"message":"Marque a caixa para ativar o suporte para v\u00e1rias agendas"},"imagetooltip":{"message":"Google Agenda"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json
deleted file mode 100644
index 53f249fa8a4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (do Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Veja rapidamente quanto tempo falta para a sua pr\u00f3xima reuni\u00e3o a partir de qualquer um dos seus calend\u00e1rios. Clique no bot\u00e3o para aceder ao calend\u00e1rio."},"direction":{"message":"ltr"},"notitle":{"message":"(Sem t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Suporte para v\u00e1rios calend\u00e1rios"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Defini\u00e7\u00f5es guardadas."},"status_saving":{"message":"A guardar..."},"multicalendartooltip":{"message":"Marque a caixa para permitir o suporte de v\u00e1rios calend\u00e1rios."},"imagetooltip":{"message":"Calend\u00e1rio Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json
deleted file mode 100644
index 3540609401b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (de la Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Vede\u0163i rapid timpul p\u00e2n\u0103 la urm\u0103toarea \u00eent\u00e2lnire, din oricare dintre calendarele dvs. Face\u0163i clic pe buton pentru a accesa calendarul."},"direction":{"message":"ltr"},"notitle":{"message":"(F\u0103r\u0103 titlu)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 z","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Asisten\u0163\u0103 pentru mai multe calendare"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Set\u0103rile au fost salvate."},"status_saving":{"message":"Se salveaz\u0103..."},"multicalendartooltip":{"message":"Bifa\u0163i caseta pentru a activa asisten\u0163a pentru mai multe calendare"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json
deleted file mode 100644
index 343632602bd..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043e \u0432 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u043e \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0433\u043e \u043c\u0435\u0440\u043e\u043f\u0440\u0438\u044f\u0442\u0438\u044f. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0435\u0439. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044c."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u043c.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u0447.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u0434.","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0435\u0439"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b."},"status_saving":{"message":"\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...."},"multicalendartooltip":{"message":"\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0444\u043b\u0430\u0436\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0435\u0439"},"imagetooltip":{"message":"\u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044c Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json
deleted file mode 100644
index 65257bf2c7c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Kontrola Kalend\u00e1ra Google (od spolo\u010dnosti Google)"},"title":{"message":"Kontrola Kalend\u00e1ra Google"},"description":{"message":"V \u013eubovo\u013enom z va\u0161ich kalend\u00e1rov si v r\u00fdchlosti si zobrazte, ko\u013eko \u010dasu m\u00e1te do \u010fal\u0161ej sch\u00f4dzky. Kliknut\u00edm na tla\u010didlo prejdete do svojho kalend\u00e1ra."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez n\u00e1zvu)"},"optionstitle":{"message":"Kontrola Kalend\u00e1ra Google"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podpora viacer\u00fdch Kalend\u00e1rov"},"extensionname":{"message":"Kontrola Kalend\u00e1ra Google"},"status_saved":{"message":"Nastavenia boli ulo\u017een\u00e9."},"status_saving":{"message":"Prebieha ukladanie...."},"multicalendartooltip":{"message":"Ak chcete povoli\u0165 podporu viacer\u00fdch kalend\u00e1rov, za\u010diarknite toto pol\u00ed\u010dko"},"imagetooltip":{"message":"Kalend\u00e1r Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json
deleted file mode 100644
index 09a2113dd39..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Hitro preverite, koliko je \u0161e do naslednjega sestanka v katerem koli od va\u0161ih koledarjev. Kliknite gumb, da odprete koledar."},"direction":{"message":"ltr"},"notitle":{"message":"(Brez naslova)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podpora za ve\u010d koledarjev"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Nastavitve shranjene."},"status_saving":{"message":"Shranjevanje ..."},"multicalendartooltip":{"message":"Potrdite polje, da omogo\u010dite podporo za ve\u010d koledarjev."},"imagetooltip":{"message":"Google Koledar"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json
deleted file mode 100644
index d55c41b7777..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u043e\u0434 Google-\u0430)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0411\u0440\u0437\u043e \u043f\u043e\u0433\u043b\u0435\u0434\u0430\u0458\u0442\u0435 \u043a\u043e\u043b\u0438\u043a\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0430 \u0438\u043c\u0430\u0442\u0435 \u0434\u043e \u0441\u043b\u0435\u0434\u0435\u045b\u0435\u0433 \u0441\u0430\u0441\u0442\u0430\u043d\u043a\u0430 \u0443 \u0431\u0438\u043b\u043e \u043a\u043e\u043c \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0443. \u041a\u043b\u0438\u043a\u043d\u0438\u0442\u0435 \u043d\u0430 \u0434\u0443\u0433\u043c\u0435 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u043e\u0442\u0432\u043e\u0440\u0438\u043b\u0438 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0411\u0435\u0437 \u043d\u0430\u0441\u043b\u043e\u0432\u0430)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u043c\u0438\u043d","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0441","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u0434","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u043e\u0434\u0440\u0448\u043a\u0430 \u0437\u0430 \u0432\u0438\u0448\u0435 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041f\u043e\u0434\u0435\u0448\u0430\u0432\u0430\u045a\u0430 \u0441\u0443 \u0441\u0430\u0447\u0443\u0432\u0430\u043d\u0430."},"status_saving":{"message":"\u0427\u0443\u0432\u0430\u045a\u0435...."},"multicalendartooltip":{"message":"\u041f\u043e\u0442\u0432\u0440\u0434\u0438\u0442\u0435 \u0438\u0437\u0431\u043e\u0440 \u0443 \u043f\u043e\u0459\u0443 \u0437\u0430 \u043f\u043e\u0442\u0432\u0440\u0434\u0443 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u043e\u043c\u043e\u0433\u0443\u045b\u0438\u043b\u0438 \u043f\u043e\u0434\u0440\u0448\u043a\u0443 \u0437\u0430 \u0432\u0438\u0448\u0435 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"imagetooltip":{"message":"Google \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json
deleted file mode 100644
index edb0193ea9c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (fr\u00e5n Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Se snabbt i dina kalendrar hur l\u00e4nge det \u00e4r till n\u00e4sta m\u00f6te. Klicka p\u00e5 knappen s\u00e5 kommer du till kalendern."},"direction":{"message":"ltr"},"notitle":{"message":"(Namnl\u00f6s)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1tim","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Support f\u00f6r flera kalendrar"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Inst\u00e4llningarna har sparats."},"status_saving":{"message":"Sparar..."},"multicalendartooltip":{"message":"Aktivera support f\u00f6r flera kalendrar genom att markera rutan"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json
deleted file mode 100644
index c9680f1ca0d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u0e42\u0e14\u0e22 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0e14\u0e39\u0e40\u0e27\u0e25\u0e32\u0e17\u0e35\u0e48\u0e04\u0e38\u0e13\u0e21\u0e35\u0e01\u0e48\u0e2d\u0e19\u0e16\u0e36\u0e07\u0e01\u0e32\u0e23\u0e1b\u0e23\u0e30\u0e0a\u0e38\u0e21\u0e04\u0e23\u0e31\u0e49\u0e07\u0e15\u0e48\u0e2d\u0e44\u0e1b\u0e44\u0e14\u0e49\u0e08\u0e32\u0e01\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e43\u0e14\u0e46 \u0e01\u0e47\u0e44\u0e14\u0e49\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13 \u0e43\u0e2b\u0e49\u0e04\u0e25\u0e34\u0e01\u0e17\u0e35\u0e48\u0e1b\u0e38\u0e48\u0e21\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13"},"direction":{"message":"ltr"},"notitle":{"message":"(\u0e44\u0e21\u0e48\u0e21\u0e35\u0e0a\u0e37\u0e48\u0e2d)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e2b\u0e25\u0e32\u0e22\u0e23\u0e30\u0e1a\u0e1a"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32\u0e41\u0e25\u0e49\u0e27"},"status_saving":{"message":"\u0e01\u0e33\u0e25\u0e31\u0e07\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01...."},"multicalendartooltip":{"message":"\u0e42\u0e1b\u0e23\u0e14\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e0a\u0e48\u0e2d\u0e07\u0e17\u0e33\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e2b\u0e21\u0e32\u0e22 \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e2b\u0e25\u0e32\u0e22\u0e23\u0e30\u0e1a\u0e1a"},"imagetooltip":{"message":"Google \u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json
deleted file mode 100644
index a54371eb347..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (Google'dan)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Takvimlerinizden herhangi birine bakarak bir sonraki toplant\u0131n\u0131za ne kadar zaman kald\u0131\u011f\u0131n\u0131 hemen g\u00f6r\u00fcn. Takviminizi a\u00e7mak i\u00e7in d\u00fc\u011fmeyi t\u0131klay\u0131n."},"direction":{"message":"ltr"},"notitle":{"message":"(Ba\u015fl\u0131ks\u0131z)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1s","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1g","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Birden Fazla Takvim Deste\u011fi"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Ayarlar Kaydedildi."},"status_saving":{"message":"Kaydediliyor..."},"multicalendartooltip":{"message":"Birden fazla takvim deste\u011fini etkinle\u015ftirmek i\u00e7in l\u00fctfen kutuyu i\u015faretleyin"},"imagetooltip":{"message":"Google Takvim"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json
deleted file mode 100644
index 21c7514d060..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u0432\u0456\u0434 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0428\u0432\u0438\u0434\u043a\u043e \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u0434\u0430\u0439\u0442\u0435 \u0441\u0432\u043e\u0457 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0456, \u0449\u043e\u0431 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f, \u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u0447\u0430\u0441\u0443 \u0437\u0430\u043b\u0438\u0448\u0438\u043b\u043e\u0441\u044f \u0434\u043e \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0457 \u0437\u0443\u0441\u0442\u0440\u0456\u0447\u0456. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0446\u044e \u043a\u043d\u043e\u043f\u043a\u0443, \u0449\u043e\u0431 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u0434\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044f."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0411\u0435\u0437 \u043d\u0430\u0437\u0432\u0438)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u0445\u0432.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0433\u043e\u0434.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u0434\u043d.","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 \u0434\u0435\u043a\u0456\u043b\u044c\u043a\u043e\u0445 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0456\u0432"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043e."},"status_saving":{"message":"\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u043d\u043d\u044f...."},"multicalendartooltip":{"message":"\u041f\u043e\u0441\u0442\u0430\u0432\u0442\u0435 \u043f\u0440\u0430\u043f\u043e\u0440\u0435\u0446\u044c, \u0449\u043e\u0431 \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0443 \u0434\u0435\u043a\u0456\u043b\u044c\u043a\u043e\u0445 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0456\u0432"},"imagetooltip":{"message":"\u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440 Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json
deleted file mode 100644
index a54d48b6e0f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (c\u1ee7a Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Xem nhanh th\u1eddi gian tr\u01b0\u1edbc khi \u0111\u1ebfn cu\u1ed9c h\u1ecdp ti\u1ebfp theo t\u1eeb b\u1ea5t k\u1ef3 l\u1ecbch n\u00e0o c\u1ee7a b\u1ea1n. H\u00e3y nh\u1ea5p v\u00e0o n\u00fat \u0111\u1ec3 \u0111\u01b0\u1ee3c \u0111\u01b0a \u0111\u1ebfn l\u1ecbch c\u1ee7a b\u1ea1n."},"direction":{"message":"ltr"},"notitle":{"message":"(Kh\u00f4ng c\u00f3 ti\u00eau \u0111\u1ec1)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"H\u1ed7 tr\u1ee3 nhi\u1ec1u l\u1ecbch"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u0110\u00e3 l\u01b0u c\u00e0i \u0111\u1eb7t."},"status_saving":{"message":"\u0110ang l\u01b0u...."},"multicalendartooltip":{"message":"Vui l\u00f2ng ch\u1ecdn h\u1ed9p n\u00e0y \u0111\u1ec3 b\u1eadt t\u00ednh n\u0103ng h\u1ed7 tr\u1ee3 nhi\u1ec1u l\u1ecbch"},"imagetooltip":{"message":"L\u1ecbch Google"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json
deleted file mode 100644
index 7c2ecea0c70..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker\uff08\u7531 Google \u63d0\u4f9b\uff09"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u5feb\u901f\u67e5\u770b\u79bb\u60a8\u7684\u4efb\u610f\u65e5\u5386\u4e2d\u4e0b\u4e00\u6b21\u4f1a\u8bae\u8fd8\u6709\u591a\u957f\u65f6\u95f4\u3002\u60a8\u53ea\u9700\u70b9\u51fb\u8be5\u6309\u94ae\u5373\u53ef\u8fdb\u5165\u81ea\u5df1\u7684\u65e5\u5386\u3002"},"direction":{"message":"ltr"},"notitle":{"message":"\uff08\u65e0\u6807\u9898\uff09"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u591a\u65e5\u5386\u652f\u6301"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u8bbe\u7f6e\u5df2\u4fdd\u5b58\u3002"},"status_saving":{"message":"\u6b63\u5728\u4fdd\u5b58..."},"multicalendartooltip":{"message":"\u8bf7\u9009\u4e2d\u6b64\u6846\u4ee5\u542f\u7528\u591a\u65e5\u5386\u652f\u6301"},"imagetooltip":{"message":"Google \u65e5\u5386"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json
deleted file mode 100644
index 5c99aba52f1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"name":{"message":"Google Calendar Checker (\u7531 Google \u63d0\u4f9b)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u5f9e\u4efb\u4f55\u65e5\u66c6\u7686\u53ef\u8fc5\u901f\u67e5\u770b\u8ddd\u96e2\u4e0b\u6b21\u6703\u8b70\u9084\u5269\u4e0b\u591a\u5c11\u6642\u9593\u3002\u6309\u4e00\u4e0b\u6309\u9215\u5373\u53ef\u524d\u5f80\u60a8\u7684\u65e5\u66c6\u3002"},"direction":{"message":"ltr"},"notitle":{"message":"(\u7121\u6a19\u984c)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u591a\u91cd\u65e5\u66c6\u652f\u63f4"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u8a2d\u5b9a\u5df2\u5132\u5b58\u3002"},"status_saving":{"message":"\u5132\u5b58\u4e2d...."},"multicalendartooltip":{"message":"\u8acb\u52fe\u9078\u6b64\u65b9\u584a\uff0c\u4ee5\u555f\u7528\u591a\u91cd\u65e5\u66c6\u652f\u63f4\u529f\u80fd"},"imagetooltip":{"message":"Google \u65e5\u66c6"}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json
deleted file mode 100644
index 51ee7441e17..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "__MSG_name__",
- "description": "__MSG_description__",
- "manifest_version": 2,
- "default_locale":"en",
- "options_page": "views/options.html",
- "options_ui": {
- "page": "views/options.html",
- "chrome_style": true
- },
- "version": "2.0.0",
- "background": {
- "scripts": ["javascript/background.js"],
- "persistent": false
- },
- "permissions": [
- "notifications"
- ],
- "browser_action": {
- "default_icon": {
- "19": "images/icon-19.png",
- "38": "images/icon-38.png"
- },
- "default_title": "__MSG_title__"
- },
- "icons": {
- "128": "images/icon-128.png",
- "48": "images/icon-48.png",
- "16":"images/icon-16.png"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json
deleted file mode 100644
index e775432add4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "CatBlock",
- "version": "1.0",
- "description": "I can't has cheezburger!",
- "permissions": ["webRequest", "webRequestBlocking",
- "https://i.chzbgr.com/*"],
- "background": {
- "scripts": ["loldogs.js", "background.js"]
- },
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json
deleted file mode 100644
index ba372527a89..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "Catifier",
- "version": "1.0",
- "description": "Moar cats!",
- "permissions": ["declarativeWebRequest", "<all_urls>"],
- "background": {
- "scripts": ["event_page.js"],
- "persistent": false
- },
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json
deleted file mode 100644
index 771783b91fc..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "background": {
- "scripts": ["background.js"]
- },
- "description": "Add support to the omnibox to search the Chromium source code.",
- "name": "Chromium Search",
- "omnibox": { "keyword" : "src" },
- "permissions": [ "http://www.google.com/" ],
- "version": "6.1",
- "minimum_chrome_version": "9",
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/constant_context/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/constant_context/manifest.json
deleted file mode 100644
index ebccaa57ed1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/constant_context/manifest.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "name": "Constant Context",
- "description" : "Highlights elements with keywords on developer.chrome",
- "version": "1.0",
- "page_action": {
- "default_icon": {
- "16": "images/cc16.png",
- "32": "images/cc32.png"
- },
- "default_popup": "popup.html"
- },
- "icons": {
- "16": "images/cc16.png",
- "48": "images/cc48.png",
- "32": "images/cc32.png",
- "128": "images/cc128.png"
- },
- "permissions": [
- "https://developer.chrome.com/*",
- "storage",
- "declarativeContent"
- ],
- "manifest_version": 2,
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "web_accessible_resources": ["style.css"],
- "content_scripts": [
- {
- "all_frames": true,
- "js": ["content_script.js"],
- "matches": ["https://developer.chrome.com/*"],
- "run_at": "document_idle"
- }
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/download_images/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/download_images/manifest.json
deleted file mode 100644
index 58fd910b1f8..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/download_images/manifest.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "name": "Download Images",
- "description" : "Displays all webpage images and allows user to download",
- "version": "1.0",
- "manifest_version": 2,
- "page_action": {
- "default_popup": "popup.html",
- "default_icon": {
- "16": "/images/download_image16.png",
- "32": "/images/download_image32.png",
- "48": "/images/download_image48.png",
- "128": "/images/download_image128.png"
- }
- },
- "icons": {
- "16": "/images/download_image16.png",
- "32": "/images/download_image32.png",
- "48": "/images/download_image48.png",
- "128": "/images/download_image128.png"
- },
- "permissions": [
- "downloads",
- "storage",
- "activeTab",
- "declarativeContent"
- ],
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "options_page": "options.html"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json
deleted file mode 100644
index 3801b87910a..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "Email this page (by Google)",
- "description": "This extension adds an email button to the toolbar which allows you to email the page link using your default mail client or Gmail.",
- "version": "1.2.6",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "icons": { "128": "mail_128x128.png" },
- "options_page": "options.html",
- "permissions": [
- "tabs", "http://*/*", "https://*/*"
- ],
- "browser_action": {
- "default_title": "Email this page",
- "default_icon": "email_16x16.png"
- },
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/fx/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/fx/manifest.json
deleted file mode 100644
index 962c5eec6d2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/fx/manifest.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "Chrome Sounds",
- "version": "1.2",
- "description": "Enjoy a more magical and immersive experience when browsing the web using the power of sound.",
- "background": {
- "scripts": ["bg.js"]
- },
- "options_page": "options.html",
- "icons": { "128": "icon.png" },
- "permissions": [
- "tabs",
- "bookmarks",
- "http://*/*",
- "https://*/*"
- ],
- "content_scripts": [ {
- "matches": ["http://*/*", "https://*/*"],
- "js": ["content.js"],
- "all_frames": true
- }],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json
deleted file mode 100644
index 03d17586f12..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "name": "Google Document List Viewer",
- "version": "1.0.2",
- "icons": {
- "48": "img/docs_spreadsheets-48.gif",
- "128": "img/docs_spreadsheets-128.gif"
- },
- "description": "Demonstrates how to use OAuth to connect the Google Documents List Data API.",
- "background": {
- "page": "background.html"
- },
- "options_page": "options.html",
- "browser_action": {
- "default_title": "List your Google Docs",
- "default_icon": "img/docs_spreadsheets-32.gif",
- "default_popup": "popup.html"
- },
- "permissions": [
- "tabs",
- "https://docs.google.com/feeds/*",
- "https://www.google.com/accounts/OAuthGetRequestToken",
- "https://www.google.com/accounts/OAuthAuthorizeToken",
- "https://www.google.com/accounts/OAuthGetAccessToken"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json
deleted file mode 100644
index 87faf6490f2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u0644\u0639\u0631\u0636 \u0639\u062f\u062f \u0627\u0644\u0631\u0633\u0627\u0626\u0644 \u063a\u064a\u0631 \u0627\u0644\u0645\u0642\u0631\u0648\u0621\u0629 \u0641\u064a \u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0648\u0627\u0631\u062f \u0641\u064a Google Mail. \u0643\u0645\u0627 \u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0646\u0642\u0631 \u0639\u0644\u0649 \u0627\u0644\u0632\u0631 \u0644\u0641\u062a\u062d \u0628\u0631\u064a\u062f\u0643 \u0627\u0644\u0648\u0627\u0631\u062f."},"gmailcheck_node_error":{"message":"\u062e\u0637\u0623: \u062a\u0645 \u0627\u0633\u062a\u0631\u062f\u0627\u062f \u0627\u0644\u062e\u0644\u0627\u0635\u0629\u060c \u0648\u0644\u0643\u0646 \u0644\u0645 \u064a\u062a\u0645 \u0627\u0644\u0639\u062b\u0648\u0631 \u0639\u0644\u0649 &lt;fullcount&gt; \u0645\u0646 \u0627\u0644\u0639\u064f\u0642\u062f"},"gmailcheck_exception":{"message":"\u0627\u0633\u062a\u062b\u0646\u0627\u0621: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json
deleted file mode 100644
index 0903503a6e5..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u043f\u043e\u0449\u0430\u0442\u0430"},"gmailcheck_description":{"message":"\u041f\u043e\u043a\u0430\u0437\u0432\u0430 \u0431\u0440\u043e\u044f \u043d\u0435\u043f\u0440\u043e\u0447\u0435\u0442\u0435\u043d\u0438 \u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u044a\u0432 \u0432\u0445\u043e\u0434\u044f\u0449\u0430\u0442\u0430 \u0432\u0438 \u043f\u043e\u0449\u0430 \u0432 Google Mail. \u041c\u043e\u0436\u0435\u0442\u0435 \u0441\u044a\u0449\u043e \u0434\u0430 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u0432\u044a\u0440\u0445\u0443 \u0431\u0443\u0442\u043e\u043d\u0430, \u0437\u0430 \u0434\u0430 \u044f \u043e\u0442\u0432\u043e\u0440\u0438\u0442\u0435."},"gmailcheck_node_error":{"message":"\u0413\u0440\u0435\u0448\u043a\u0430: \u0415\u043c\u0438\u0441\u0438\u044f\u0442\u0430 \u0431\u0435 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0430, \u043d\u043e \u043d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d \u0432\u044a\u0437\u0435\u043b &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u0438\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json
deleted file mode 100644
index 6c2b1b2769e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Verificador de Google Mail"},"gmailcheck_description":{"message":"Mostra el nombre de missatges no llegits que hi ha a la vostra safata d'entrada de Google Mail. Tamb\u00e9 podeu fer clic al bot\u00f3 per obrir la safata d'entrada."},"gmailcheck_node_error":{"message":"Error: s'ha recuperat el feed, per\u00f2 no s'ha trobat cap node &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excepci\u00f3: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json
deleted file mode 100644
index 394e6addd6e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Kontrola e-mailu Google"},"gmailcheck_description":{"message":"Zobraz\u00ed po\u010det nep\u0159e\u010dten\u00fdch zpr\u00e1v ve slo\u017ece Doru\u010den\u00e1 po\u0161ta slu\u017eby Google Mail. Kliknut\u00edm na tla\u010d\u00edtko tuto slo\u017eku otev\u0159ete."},"gmailcheck_node_error":{"message":"Chyba: Zdroj byl na\u010dten, nebyl v\u0161ak nalezen \u017e\u00e1dn\u00fd uzel &lt;fullcount&gt;."},"gmailcheck_exception":{"message":"v\u00fdjimka: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json
deleted file mode 100644
index e1c2f82737f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google E-mail-t\u00e6ller"},"gmailcheck_description":{"message":"Viser antallet af ul\u00e6ste meddelelser i din Google Mail-indbakke. Du kan ogs\u00e5 klikke p\u00e5 knappen for at \u00e5bne din indbakke."},"gmailcheck_node_error":{"message":"Fejl: Feedet blev hentet, men der blev ikke fundet nogen &lt;fullcount&gt;-node"},"gmailcheck_exception":{"message":"undtagelse: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json
deleted file mode 100644
index 6c2188616c0..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail-Checker"},"gmailcheck_description":{"message":"Zeigt die Anzahl ungelesener Nachrichten in Ihrem Google Mail-Posteingang an. Sie k\u00f6nnen auch auf diese Schaltfl\u00e4che klicken, um Ihren Posteingang zu \u00f6ffnen."},"gmailcheck_node_error":{"message":"Fehler: Feed abgerufen, aber kein &lt;fullcount&gt; Knoten gefunden"},"gmailcheck_exception":{"message":"Ausnahme: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json
deleted file mode 100644
index 7db27143e6c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03bf\u03c5 Google Mail"},"gmailcheck_description":{"message":"\u0395\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bc\u03b7 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c3\u03bc\u03ad\u03bd\u03c9\u03bd \u03bc\u03b7\u03bd\u03c5\u03bc\u03ac\u03c4\u03c9\u03bd \u03c3\u03c4\u03b1 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03bf\u03c5 Google Mail \u03c3\u03b1\u03c2. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03bd\u03b1 \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03ac \u03c3\u03b1\u03c2."},"gmailcheck_node_error":{"message":"\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1: \u03ad\u03b3\u03b9\u03bd\u03b5 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c1\u03bf\u03ae\u03c2, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c2 &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json
deleted file mode 100644
index 063b9ca02a4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Displays the number of unread messages in your Google Mail inbox. You can also click the button to open your inbox."},"gmailcheck_node_error":{"message":"Error: feed retrieved, but no &lt;fullcount&gt; node found"},"gmailcheck_exception":{"message":"exception: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json
deleted file mode 100644
index d3fa3a3a1d8..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Displays the number of unread messages in your Google Mail inbox. You can also click the button to open your inbox."},"gmailcheck_node_error":{"message":"Error: Feed retrieved, but no &lt;fullcount&gt; node found"},"gmailcheck_exception":{"message":"exception: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json
deleted file mode 100644
index 4d8364f7a0d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Permite ver el n\u00famero de mensajes sin leer en la bandeja de entrada de Google Mail. Tambi\u00e9n puedes hacer clic en el bot\u00f3n para abrir la bandeja de entrada."},"gmailcheck_node_error":{"message":"Se ha producido un error: el feed se ha recuperado, pero no se ha encontrado ning\u00fan nodo &lt;fullcount&gt;."},"gmailcheck_exception":{"message":"excepci\u00f3n: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json
deleted file mode 100644
index 54bf27032ec..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Muestra el n\u00famero de mensajes sin leer en tu bandeja de entrada de Google Mail. Tambi\u00e9n puedes hacer clic en el bot\u00f3n para abrir tu bandeja de entrada."},"gmailcheck_node_error":{"message":"Error: se recuper\u00f3 el feed, pero no se encontr\u00f3 el nodo &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excepci\u00f3n: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json
deleted file mode 100644
index 20b1e844d81..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google'i meilikontrollija"},"gmailcheck_description":{"message":"Kuvab Google Maili postkastis olevate lugemata s\u00f5numite arvu. V\u00f5ite kl\u00f5psata ka nupul ja avada postkasti."},"gmailcheck_node_error":{"message":"Viga: voog vastuv\u00f5etud, kuid \u00fchtki &lt;fullcount&gt; s\u00f5lme ei leitud"},"gmailcheck_exception":{"message":"erand: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json
deleted file mode 100644
index f9da2c15ae9..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google-s\u00e4hk\u00f6postin tarkistus"},"gmailcheck_description":{"message":"N\u00e4ytt\u00e4\u00e4, kuinka monta lukematonta viesti\u00e4 Google Mail -postilaatikossasi on. Voit my\u00f6s avata postilaatikkosi napsauttamalla painiketta."},"gmailcheck_node_error":{"message":"Virhe: sy\u00f6te haettiin, mutta &lt;fullcount&gt;-solmua ei l\u00f6ytynyt"},"gmailcheck_exception":{"message":"poikkeus: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json
deleted file mode 100644
index 1a019a60d93..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Ipinapakita ang bilang ng mga hindi pa nababasang mensahe sa inbox ng iyong Google Mail. Maaari mo ring i-click ang pindutan upang buksan ang iyong inbox."},"gmailcheck_node_error":{"message":"Error: nakuha ang feed, ngunit walang &lt;fullcount&gt; node na natagpuan"},"gmailcheck_exception":{"message":"pagbubukod: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json
deleted file mode 100644
index 2900819e1c2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"V\u00e9rificateur de messages Google"},"gmailcheck_description":{"message":"Affiche le nombre de messages non lus dans votre bo\u00eete de r\u00e9ception Google\u00a0Mail. Vous avez \u00e9galement la possibilit\u00e9 de cliquer sur ce bouton pour ouvrir cette derni\u00e8re."},"gmailcheck_node_error":{"message":"Erreur\u00a0: flux r\u00e9cup\u00e9r\u00e9, mais aucun n\u0153ud &lt;fullcount&gt; trouv\u00e9"},"gmailcheck_exception":{"message":"exception\u00a0: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json
deleted file mode 100644
index c32e98298ab..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u05de\u05e6\u05d9\u05d2 \u05d0\u05ea \u05de\u05e1\u05e4\u05e8 \u05d4\u05d4\u05d5\u05d3\u05e2\u05d5\u05ea \u05e9\u05dc\u05d0 \u05e0\u05e7\u05e8\u05d0\u05d5 \u05d1\u05ea\u05d9\u05d1\u05ea \u05d4\u05d3\u05d5\u05d0\u05e8 \u05d4\u05e0\u05db\u05e0\u05e1 \u05e9\u05dc\u05da \u05d1-Google Mail. \u05d1\u05e0\u05d5\u05e1\u05e3, \u05ea\u05d5\u05db\u05dc \u05dc\u05dc\u05d7\u05d5\u05e5 \u05e2\u05dc \u05d4\u05dc\u05d7\u05e6\u05df \u05db\u05d3\u05d9 \u05dc\u05e4\u05ea\u05d5\u05d7 \u05d0\u05ea \u05ea\u05d9\u05d1\u05ea \u05d4\u05d3\u05d5\u05d0\u05e8 \u05d4\u05e0\u05db\u05e0\u05e1."},"gmailcheck_node_error":{"message":"\u05e9\u05d2\u05d9\u05d0\u05d4: \u05d4\u05e2\u05d3\u05db\u05d5\u05df \u05d0\u05d5\u05d7\u05d6\u05e8, \u05d0\u05da \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05e6\u05de\u05ea\u05d9\u05dd \u05e9\u05dc &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u05d7\u05e8\u05d9\u05d2: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json
deleted file mode 100644
index 0056c565f25..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google \u092e\u0947\u0932 \u091c\u093e\u0902\u091a\u0915\u0930\u094d\u0924\u093e"},"gmailcheck_description":{"message":"\u0906\u092a\u0915\u0947 Google \u092e\u0947\u0932 \u0907\u0928\u092c\u0949\u0915\u094d\u0938 \u092e\u0947\u0902 \u0928 \u092a\u095d\u0947 \u0917\u090f \u0938\u0902\u0926\u0947\u0936\u094b\u0902 \u0915\u0940 \u0938\u0902\u0916\u094d\u092f\u093e \u092a\u094d\u0930\u0926\u0930\u094d\u0936\u093f\u0924 \u0915\u0930\u0924\u093e \u0939\u0948. \u0906\u092a \u0905\u092a\u0928\u093e \u0907\u0928\u092c\u0949\u0915\u094d\u0938 \u0916\u094b\u0932\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u092c\u091f\u0928 \u0915\u094d\u0932\u093f\u0915 \u092d\u0940 \u0915\u0930 \u0938\u0915\u0924\u0947 \u0939\u0948\u0902."},"gmailcheck_node_error":{"message":"\u0924\u094d\u0930\u0941\u091f\u093f: \u095e\u0940\u0921 \u092a\u0941\u0928\u0930\u094d\u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u0940 \u0917\u0908, \u0932\u0947\u0915\u093f\u0928 \u0915\u094b\u0908 &lt;fullcount&gt; \u0928\u094b\u0921 \u0928\u0939\u0940\u0902 \u092e\u093f\u0932\u093e"},"gmailcheck_exception":{"message":"\u0905\u092a\u0935\u093e\u0926: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json
deleted file mode 100644
index 94dce1c1ae4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Provjera po\u0161te"},"gmailcheck_description":{"message":"Prikazuje broj nepro\u010ditanih poruka u ulaznom pretincu usluge Google Mail. Mo\u017eete tako\u0111er kliknuti gumb za otvaranje ulazne po\u0161te."},"gmailcheck_node_error":{"message":"Pogre\u0161ka: Feed je dohva\u0107en, ali nije prona\u0111eno \u010dvori\u0161te &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"izuzetak: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json
deleted file mode 100644
index 8b3e6c967ca..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Lev\u00e9lfigyel\u0151"},"gmailcheck_description":{"message":"Megjelen\u00edti az olvasatlan \u00fczeneteket a Google Mail be\u00e9rkez\u0151 levelei k\u00f6z\u00f6tt. A gombra kattintva is megnyithatja a be\u00e9rkez\u0151 leveleit."},"gmailcheck_node_error":{"message":"Hiba: h\u00edrcsatorna leh\u00edvva, de a csom\u00f3pont (&lt;fullcount&gt;) hi\u00e1nyzik"},"gmailcheck_exception":{"message":"kiv\u00e9tel: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json
deleted file mode 100644
index 4a8d09c247f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Menampilkan jumlah pesan yang belum dibaca dalam kotak masuk Google Mail. Anda juga dapat mengeklik tombol ini untuk membuka kotak masuk."},"gmailcheck_node_error":{"message":"Galat: umpan diperoleh, tetapi tidak ada &lt;fullcount&gt; node yang ditemukan"},"gmailcheck_exception":{"message":"pengecualian: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json
deleted file mode 100644
index 13b6e78749a..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Avvisi email"},"gmailcheck_description":{"message":"Visualizza il numero di messaggi da leggere nella posta in arrivo di Google Mail. Puoi anche fare clic sul pulsante per aprire la tua posta in arrivo."},"gmailcheck_node_error":{"message":"Errore: feed recuperato ma non sono stati trovati nodi &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"eccezione: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json
deleted file mode 100644
index 3ce2669b789..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Google Mail \u306e\u53d7\u4fe1\u30c8\u30ec\u30a4\u306b\u3042\u308b\u672a\u8aad\u306e\u30e1\u30fc\u30eb\u6570\u3092\u8868\u793a\u3057\u307e\u3059\u3002\u3053\u306e\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u53d7\u4fe1\u30c8\u30ec\u30a4\u3092\u958b\u304f\u3053\u3068\u3082\u3067\u304d\u307e\u3059\u3002"},"gmailcheck_node_error":{"message":"\u30a8\u30e9\u30fc: \u53d6\u5f97\u3057\u305f\u30d5\u30a3\u30fc\u30c9\u306b &lt;fullcount&gt; \u30ce\u30fc\u30c9\u304c\u3042\u308a\u307e\u305b\u3093"},"gmailcheck_exception":{"message":"\u4f8b\u5916: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json
deleted file mode 100644
index 934e49e4f01..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Gmaill \ubc1b\uc740\ud3b8\uc9c0\ud568\uc5d0\uc11c \uc77d\uc9c0 \uc54a\uc740 \uba54\uc77c\uc758 \uc218\ub97c \ub098\ud0c0\ub0c5\ub2c8\ub2e4. \ub610\ud55c \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc5ec \ubc1b\uc740\ud3b8\uc9c0\ud568\uc744 \uc5f4 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4."},"gmailcheck_node_error":{"message":"\uc624\ub958: \ud53c\ub4dc\ub97c \uac80\uc0c9\ud588\uc73c\ub098 \ucd1d &lt;fullcount&gt;\uac1c\uc758 \ub178\ub4dc\ub97c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4."},"gmailcheck_exception":{"message":"\uc608\uc678: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json
deleted file mode 100644
index 7da57e1c76c..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"\u201eGoogle\u201c pa\u0161to tikrintuvas"},"gmailcheck_description":{"message":"Pateikiamas \u201eGoogle\u201c pa\u0161to gaut\u0173 lai\u0161k\u0173 aplanke esan\u010di\u0173 neperskaityt\u0173 prane\u0161im\u0173 skai\u010dius. Be to, jei norite atidaryti gaut\u0173 lai\u0161k\u0173 aplank\u0105, galite spustel\u0117ti mygtuk\u0105."},"gmailcheck_node_error":{"message":"Klaida: sklaidos kanalas nuskaitytas, ta\u010diau nerastas joks &lt;fullcount&gt; mazgas"},"gmailcheck_exception":{"message":"i\u0161imtis: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json
deleted file mode 100644
index 52f2ca3e792..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Tiek r\u0101d\u012bts nelas\u012bto zi\u0146ojumu skaits Google Mail ies\u016btn\u0113. Varat ar\u012b noklik\u0161\u0137in\u0101t uz pogas, lai atv\u0113rtu ies\u016btni."},"gmailcheck_node_error":{"message":"K\u013c\u016bda: pl\u016bsma ir izg\u016bta, ta\u010du netika atrasts neviens &lt;fullcount&gt; mezgls"},"gmailcheck_exception":{"message":"iz\u0146\u0113mums: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json
deleted file mode 100644
index ad8a09df4ba..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google e-post-sjekker"},"gmailcheck_description":{"message":"Viser antallet uleste meldinger i innboksen for Google Mail. Du kan ogs\u00e5 klikke p\u00e5 knappen for \u00e5 \u00e5pne innboksen."},"gmailcheck_node_error":{"message":"Feil: innmating hentet, men finner ingen &lt;fullcount&gt;-node"},"gmailcheck_exception":{"message":"unntak: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json
deleted file mode 100644
index c40d81fa105..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Hiermee wordt het aantal ongelezen berichten in uw Postvak IN van Gmail weergegeven. U kunt ook op de knop klikken om het Postvak IN te openen."},"gmailcheck_node_error":{"message":"Fout: feed opgehaald, maar het knooppunt &lt;fullcount&gt; is niet gevonden"},"gmailcheck_exception":{"message":"uitzondering: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json
deleted file mode 100644
index d607a06d280..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Sprawdzanie poczty Google"},"gmailcheck_description":{"message":"Wy\u015bwietla liczb\u0119 nieprzeczytanych wiadomo\u015bci w Twojej skrzynce odbiorczej Google Mail. Mo\u017cesz te\u017c klikn\u0105\u0107 przycisk, aby otworzy\u0107 swoj\u0105 skrzynk\u0119 odbiorcz\u0105."},"gmailcheck_node_error":{"message":"B\u0142\u0105d: pobrano kana\u0142, ale nie odnaleziono w\u0119z\u0142a &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"wyj\u0105tek: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json
deleted file mode 100644
index 1175d0c822e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Verificador de mensagens do Google"},"gmailcheck_description":{"message":"Exibe o n\u00famero de mensagens n\u00e3o lidas na sua Caixa de entrada do Gmail. Voc\u00ea tamb\u00e9m pode clicar no bot\u00e3o para abrir a sua caixa de entrada."},"gmailcheck_node_error":{"message":"Erro: feed recuperado, mas nenhum n\u00f3 &lt;fullcount&gt; encontrado"},"gmailcheck_exception":{"message":"exce\u00e7\u00e3o: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json
deleted file mode 100644
index e6d8992af23..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Verificador do Google Mail"},"gmailcheck_description":{"message":"Apresenta o n\u00famero de mensagens n\u00e3o lidas existentes na sua caixa de entrada do Google Mail. Pode tamb\u00e9m clicar no bot\u00e3o para abrir a caixa de entrada."},"gmailcheck_node_error":{"message":"Erro: obteve-se o feed, mas n\u00e3o foi encontrado nenhum n\u00f3 &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excep\u00e7\u00e3o: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json
deleted file mode 100644
index d0d5838ce51..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Verificator de e-mail"},"gmailcheck_description":{"message":"Afi\u015feaz\u0103 num\u0103rul mesajelor necitite din folderul Mesaje primite al contului Google Mail. De asemenea, pute\u0163i s\u0103 face\u0163i clic pe buton pentru a deschide folderul Mesaje primite."},"gmailcheck_node_error":{"message":"Eroare: s-a preluat feedul, dar nu s-a g\u0103sit niciun nod &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excep\u0163ie: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json
deleted file mode 100644
index dedaef1344b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u043c \u044f\u0449\u0438\u043a\u0435 Google Mail. \u041c\u043e\u0436\u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0436\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u0430\u043f\u043a\u0443 \"\u0412\u0445\u043e\u0434\u044f\u0449\u0438\u0435\"."},"gmailcheck_node_error":{"message":"\u041e\u0448\u0438\u0431\u043a\u0430: \u0444\u0438\u0434 \u0431\u044b\u043b \u043f\u043e\u043b\u0443\u0447\u0435\u043d, \u043d\u043e \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0443\u0437\u0435\u043b &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json
deleted file mode 100644
index 13d902a78c7..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Kontrola po\u0161ty Google"},"gmailcheck_description":{"message":"Zobraz\u00ed po\u010det nepre\u010d\u00edtan\u00fdch spr\u00e1v v prie\u010dinku doru\u010denej po\u0161ty v slu\u017ebe Gmail. Kliknut\u00edm na tla\u010didlo prie\u010dinok doru\u010denej po\u0161ty otvor\u00edte."},"gmailcheck_node_error":{"message":"Chyba: informa\u010dn\u00fd kan\u00e1l bol na\u010d\u00edtan\u00fd, nena\u0161iel sa v\u0161ak \u017eiadny uzol &lt;fullcount&gt;."},"gmailcheck_exception":{"message":"v\u00fdnimka: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json
deleted file mode 100644
index 962a8c6efb4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Preverjevalnik za Google Mail"},"gmailcheck_description":{"message":"Prika\u017ee \u0161tevilo neprebranih sporo\u010dil v nabiralniku storitve Google Mail. Nabiralnik lahko odprete tudi s klikom gumba."},"gmailcheck_node_error":{"message":"Napaka: vir prejet, vendar ni bilo najdeno nobeno vozli\u0161\u010de &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"izjema: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json
deleted file mode 100644
index a2cfdb417e6..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google \u043f\u0440\u043e\u0432\u0435\u0440\u0430 \u043f\u043e\u0448\u0442\u0435"},"gmailcheck_description":{"message":"\u041f\u0440\u0438\u043a\u0430\u0437\u0443\u0458\u0435 \u0431\u0440\u043e\u0458 \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u043c\u0459\u0435\u043d\u0438\u0445 \u043f\u043e\u0440\u0443\u043a\u0430 Google Mail-\u0430. \u041c\u043e\u0436\u0435\u0442\u0435 \u0438 \u0434\u0430 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u0434\u0443\u0433\u043c\u0435 \u0438 \u043e\u0442\u0432\u043e\u0440\u0438\u0442\u0435 \u043f\u0440\u0438\u043c\u0459\u0435\u043d\u0435 \u043f\u043e\u0440\u0443\u043a\u0435."},"gmailcheck_node_error":{"message":"\u0413\u0440\u0435\u0448\u043a\u0430: \u0424\u0438\u0434 \u0458\u0435 \u043f\u0440\u0435\u0443\u0437\u0435\u0442, \u0430\u043b\u0438 \u0447\u0432\u043e\u0440 &lt;fullcount&gt; \u043d\u0438\u0458\u0435 \u043f\u0440\u043e\u043d\u0430\u0452\u0435\u043d"},"gmailcheck_exception":{"message":"\u0438\u0437\u0443\u0437\u0435\u0442\u0430\u043a: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json
deleted file mode 100644
index b4359f35bd1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Visar hur m\u00e5nga ol\u00e4sta meddelanden du har i inkorgen i Google Mail. Du kan ocks\u00e5 klicka p\u00e5 knappen om du vill \u00f6ppna inkorgen."},"gmailcheck_node_error":{"message":"Fel: feeden h\u00e4mtades, men ingen &lt;fullcount&gt;-nod hittades"},"gmailcheck_exception":{"message":"undantag: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json
deleted file mode 100644
index e5bef6cd04b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u0e41\u0e2a\u0e14\u0e07\u0e08\u0e33\u0e19\u0e27\u0e19\u0e02\u0e2d\u0e07\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e17\u0e35\u0e48\u0e22\u0e31\u0e07\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e2d\u0e48\u0e32\u0e19\u0e43\u0e19\u0e01\u0e25\u0e48\u0e2d\u0e07\u0e08\u0e14\u0e2b\u0e21\u0e32\u0e22 Google Mail \u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13 \u0e41\u0e25\u0e30\u0e04\u0e38\u0e13\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e04\u0e25\u0e34\u0e01\u0e1b\u0e38\u0e48\u0e21\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e40\u0e1b\u0e34\u0e14\u0e01\u0e25\u0e48\u0e2d\u0e07\u0e08\u0e14\u0e2b\u0e21\u0e32\u0e22\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e40\u0e0a\u0e48\u0e19\u0e01\u0e31\u0e19"},"gmailcheck_node_error":{"message":"\u0e02\u0e49\u0e2d\u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14: \u0e40\u0e23\u0e35\u0e22\u0e01\u0e04\u0e37\u0e19\u0e1f\u0e35\u0e14\u0e41\u0e25\u0e49\u0e27 \u0e41\u0e15\u0e48\u0e44\u0e21\u0e48\u0e1e\u0e1a\u0e42\u0e2b\u0e19\u0e14 &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u0e02\u0e49\u0e2d\u0e22\u0e01\u0e40\u0e27\u0e49\u0e19: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json
deleted file mode 100644
index 593057dc79f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Google Mail gelen kutunuzdaki okunmam\u0131\u015f iletilerin say\u0131s\u0131n\u0131 g\u00f6r\u00fcnt\u00fcler. Ayr\u0131ca, d\u00fc\u011fmeyi t\u0131klayarak gelen kutunuzu da a\u00e7abilirsiniz."},"gmailcheck_node_error":{"message":"Hata: yay\u0131n al\u0131nd\u0131, ancak &lt;fullcount&gt; d\u00fc\u011f\u00fcm\u00fc bulunamad\u0131"},"gmailcheck_exception":{"message":"\u00f6zel durum: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json
deleted file mode 100644
index 0d196cc2503..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u043f\u043e\u0448\u0442\u0438 Google"},"gmailcheck_description":{"message":"\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u0438\u0445 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c \u0443 \u043f\u0430\u043f\u0446\u0456 \u0437 \u0432\u0445\u0456\u0434\u043d\u0438\u043c\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f\u043c\u0438 \u0441\u043b\u0443\u0436\u0431\u0438 Google Mail. \u0429\u043e\u0431 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043f\u0430\u043f\u043a\u0443 \u0437 \u0432\u0445\u0456\u0434\u043d\u0438\u043c\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f\u043c\u0438, \u043c\u043e\u0436\u043d\u0430 \u0442\u0430\u043a\u043e\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \u0446\u044e \u043a\u043d\u043e\u043f\u043a\u0443."},"gmailcheck_node_error":{"message":"\u041f\u043e\u043c\u0438\u043b\u043a\u0430: \u043a\u0430\u043d\u0430\u043b \u0432\u0456\u0434\u043d\u043e\u0432\u043b\u0435\u043d\u043e, \u0430\u043b\u0435 \u0436\u043e\u0434\u043d\u043e\u0433\u043e \u0432\u0443\u0437\u043b\u0430 &lt;fullcount&gt; \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e"},"gmailcheck_exception":{"message":"\u0432\u0438\u043d\u044f\u0442\u043e\u043a: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json
deleted file mode 100644
index d26050fb329..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Tr\u00ecnh Ki\u1ec3m tra Th\u01b0 c\u1ee7a Google"},"gmailcheck_description":{"message":"Hi\u1ec3n th\u1ecb s\u1ed1 th\u01b0 ch\u01b0a \u0111\u1ecdc trong h\u1ed9p th\u01b0 \u0111\u1ebfn Gmail c\u1ee7a b\u1ea1n. B\u1ea1n c\u0169ng c\u00f3 th\u1ec3 nh\u1ea5p v\u00e0o n\u00fat \u0111\u1ec3 m\u1edf h\u1ed9p th\u01b0 \u0111\u1ebfn c\u1ee7a m\u00ecnh."},"gmailcheck_node_error":{"message":"L\u1ed7i: ngu\u1ed3n c\u1ea5p d\u1eef li\u1ec7u \u0111\u00e3 \u0111\u01b0\u1ee3c truy xu\u1ea5t nh\u01b0ng kh\u00f4ng t\u00ecm th\u1ea5y n\u00fat &lt;fullcount&gt; n\u00e0o"},"gmailcheck_exception":{"message":"ngo\u1ea1i l\u1ec7: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json
deleted file mode 100644
index ae6227f379e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u663e\u793a Google Mail \u6536\u4ef6\u7bb1\u4e2d\u7684\u672a\u8bfb\u90ae\u4ef6\u6570\u3002\u70b9\u51fb\u8be5\u6309\u94ae\u8fd8\u53ef\u4ee5\u6253\u5f00\u60a8\u7684\u6536\u4ef6\u7bb1\u3002"},"gmailcheck_node_error":{"message":"\u9519\u8bef\uff1a\u7cfb\u7edf\u5df2\u68c0\u7d22\u4f9b\u7a3f\uff0c\u4f46\u672a\u53d1\u73b0 &lt;fullcount&gt; \u8282\u70b9"},"gmailcheck_exception":{"message":"\u5f02\u5e38\uff1a$1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json
deleted file mode 100644
index 4c317934892..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json
+++ /dev/null
@@ -1 +0,0 @@
-{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u5728 Google Mail \u6536\u4ef6\u5323\u4e2d\u986f\u793a\u672a\u8b80\u90f5\u4ef6\u7684\u6578\u76ee\u3002\u6309\u4e00\u4e0b\u6309\u9215\u4e5f\u53ef\u4ee5\u958b\u555f\u6536\u4ef6\u5323\u3002"},"gmailcheck_node_error":{"message":"\u932f\u8aa4\uff1a\u64f7\u53d6\u5230\u8cc7\u8a0a\u63d0\u4f9b\uff0c\u4f46\u627e\u4e0d\u5230 &lt;fullcount&gt; \u7bc0\u9ede"},"gmailcheck_exception":{"message":"\u4f8b\u5916\u72c0\u6cc1\uff1a$1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json
deleted file mode 100644
index c181dcd3ca4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "background": {
- "persistent": false,
- "page": "background.html"
- },
- "browser_action": {
- "default_icon": "gmail_not_logged_in.png"
- },
- "default_locale": "en",
- "description": "__MSG_gmailcheck_description__",
- "icons": {
- "128": "icon_128.png"
- },
- "name": "__MSG_gmailcheck_name__",
- "permissions": [
- "alarms",
- "tabs",
- "webNavigation",
- "*://*.google.com/"
- ],
- "version": "4.4.0",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json
deleted file mode 100644
index ecda1b16800..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name" : "Imageinfo",
- "version" : "1.0.1",
- "description" : "Get image info for images, including EXIF data",
- "background" : { "scripts": ["background.js"] },
- "permissions" : [
- "contextMenus",
- "tabs",
- "http://*/*",
- "https://*/*"
- ],
- "minimum_chrome_version" : "6.0.0.0",
- "icons" : {
- "16" : "imageinfo-16.png",
- "48" : "imageinfo-48.png",
- "128" : "imageinfo-128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json
deleted file mode 100644
index 9c6b31e67b0..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "Chromium IRC App",
- "version": "0.1",
- "app": {
- "launch" : {
- "url": "http://localhost:8080"
- },
- "origins": ["http://localhost:8080"]
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/_locales/en/messages.json
deleted file mode 100644
index 958f7866477..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/_locales/en/messages.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extName": {
- "message": "Managed Bookmarks",
- "description": "The extension name."
- },
- "extDescription": {
- "message": "Adds bookmarks configured by your system administrator to Chrome.",
- "description": "The extension description."
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/manifest.json
deleted file mode 100644
index 6ee3f29bfb4..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "__MSG_extName__",
- "description": "__MSG_extDescription__",
- "default_locale": "en",
- "version": "1.0",
- "manifest_version": 2,
- "minimum_chrome_version": "33",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "permissions": [
- "bookmarks"
- ],
- "storage": {
- "managed_schema": "schema.json"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/schema.json b/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/schema.json
deleted file mode 100644
index 11e7aeb544b..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/managed_bookmarks/schema.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "type": "object",
- "properties": {
- "Bookmarks Bar": {
- "title": "Bookmarks in the Bookmarks Bar",
- "description": "Configures bookmarks that will appear in the Bookmarks Bar and can't be removed by the user.",
- "type": "array",
- "id": "ListOfBookmarks",
- "items": {
- "type": "object",
- "properties": {
- "title": {
- "title": "Bookmark name",
- "description": "The name that appears on the bookmark.",
- "type": "string"
- },
- "url": {
- "title": "Bookmark URL",
- "description": "The URL for the bookmark. If a URL is not set then this bookmark will be a folder.",
- "type": "string"
- },
- "children": {
- "title": "Contents of this bookmark folder",
- "description": "A list of bookmarks that will be inside this bookmark folder. If this is set then the URL for this bookmark will be ignored.",
- "$ref": "ListOfBookmarks"
- }
- }
- }
- },
- "Other Bookmarks": {
- "title": "Bookmarks in the Other Bookmarks folder",
- "description": "Configures bookmarks that will appear in the Other Bookmarks folder and can't be removed by the user.",
- "$ref": "ListOfBookmarks"
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json
deleted file mode 100644
index 2cacda9dc85..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "Mappy",
- "version": "1.0",
- "description": "Finds addresses in the web page you're on and pops up a map window.",
- "icons": { "128": "icon.png" },
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "content_scripts": [
- {
- "matches": ["http://*/*"],
- "js": ["mappy_content_script.js"]
- }
- ],
- "permissions": [
- "storage",
- "https://maps.google.com/*",
- "https://maps.googleapis.com/*"
- ],
- "page_action": {
- "default_name": "Display Map",
- "default_icon": "marker.png",
- "default_popup": "popup.html"
- },
- "manifest_version": 2,
- "content_security_policy": "default-src 'none'; style-src 'self'; script-src 'self'; connect-src https://maps.googleapis.com; img-src https://maps.googleapis.com"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json
deleted file mode 100644
index bb1f2e81008..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "Google Maps",
- "version": "3",
- "icons": { "24": "24.png", "128": "128.png" },
- "app": {
- "urls": [
- "http://maps.google.com/"
- ],
- "launch": {
- "web_url": "http://maps.google.com/"
- }
- },
- "permissions": ["geolocation"],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json
deleted file mode 100644
index 97e07c133ce..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
- "extName": {
- "message": "News Reader (by Google)"
- },
- "extDesc": {
- "message": "Displays the latest stories from Google News in a popup."
- },
- "ext_default_title": {
- "message": "Google News"
- },
-
- "1": {
- "message": "Top Stories"
- },
- "n": {
- "message": "Nation"
- },
- "w": {
- "message": "World"
- },
- "b": {
- "message": "Business"
- },
- "t": {
- "message": "Science/Technology"
- },
- "e": {
- "message": "Entertainment"
- },
- "s": {
- "message": "Sports"
- },
- "m": {
- "message": "Health"
- },
- "po": {
- "message": "Most Popular"
- },
-
- "options": {
- "message": "Options"
- },
- "more_stories": {
- "message": "More stories"
- },
-
- "direction": {
- "message": "ltr"
- },
-
- "country": {
- "message": "Country:"
- },
- "topic": {
- "message": "Topics:"
- },
- "save": {
- "message": "Save"
- },
- "saveStatus": {
- "message": "Options saved"
- },
- "storyCount": {
- "message": "Number of stories:"
- },
- "newsOption": {
- "message": "Google News Options"
- },
- "customText": {
- "message": "Custom Topics:"
- },
- "maximumTopics": {
- "message": "(Maximum $count$)",
- "placeholders": {
- "count": {
- "content": "$1"
- }
- }
- },
- "submitButton": {
- "message": "Add"
- },
- "deleteTitle": {
- "message": "Delete"
- },
- "invalidChars": {
- "message": "Invalid character(s)"
- },
- "noTopic": {
- "message": "At least one Topic must be selected"
- },
-
- "fetchError": {
- "message": "Error: Failed to fetch news stories."
- },
- "wrongTopic": {
- "message": "Error: Not a valid feed."
- },
- "noStory": {
- "message": "No story right now"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/news/manifest.json
deleted file mode 100644
index a64df9cb5aa..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news/manifest.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "__MSG_extName__",
- "version": "2.0",
- "description": "__MSG_extDesc__",
- "icons": { "128": "images/news_icon.png" },
- "default_locale":"en",
- "browser_action": {
- "default_title": "__MSG_ext_default_title__",
- "default_icon": "images/news_action.png",
- "default_popup": "views/feed.html"
- },
- "permissions": [
- "tabs",
- "http://news.google.com/*"
- ],
- "options_page": "views/options.html",
- "background": {
- "page": "views/background.html"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json
deleted file mode 100644
index 496ad4f3c57..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "News Reader",
- "version": "1.1",
- "description": "Displays the first 5 items from the 'Google News - top news' RSS feed in a popup.",
- "icons": { "128": "news_icon.png" },
- "browser_action": {
- "default_title": "Google News",
- "default_icon": "news_action.png",
- "default_popup": "feed.html"
- },
- "permissions": [
- "tabs",
- "http://news.google.com/*"
- ],
- "manifest_version": 2,
- "content_security_policy": "img-src 'self' http://* https://*; script-src 'self'; connect-src http://news.google.com; frame-src data:"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json
deleted file mode 100644
index 9f5ea5db27e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- "name": {
- "message": "News Reader",
- "description": "Extension name in manifest."
- },
- "description": {
- "message": "Displays the first 5 items from the '$Google$ News - top news' RSS feed in a popup.",
- "description": "Extension description in manifest.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "default_title": {
- "message": "$Google$ News",
- "description": "Extension browser action tooltip text in manifest.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "unknown_title": {
- "message": "Unknown title",
- "description": "Unknown news title."
- },
- "error": {
- "message": "Error: $error$",
- "description": "Generic error template. Expects error parameter to be passed in.",
- "placeholders": {
- "error": {
- "content": "$1",
- "example": "Failed to fetch RSS feed."
- }
- }
- },
- "failed_to_fetch_rss": {
- "message": "Failed to fetch RSS feed.",
- "description": "User visible error message."
- },
- "not_a_valid_feed": {
- "message": "Not a valid feed.",
- "description": "User visible error message."
- },
- "more_stories": {
- "message": "To $Google$ News \u00BB",
- "description": "Link name to more Google News.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "newsUrl": {
- "message": "http://news.google.com",
- "description": "Url to Google News."
- }
-}
-
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json
deleted file mode 100644
index 2093149dd40..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- "name": {
- "message": "Lector de noticias",
- "description": "Nombre de la extensión en el manifiesto."
- },
- "description": {
- "message": "Muestra los primeros 5 eventos de '$Google$ noticias - destacados' RSS feed en una ventana.",
- "description": "Descripción de la extensión en el manifiesto.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "default_title": {
- "message": "$Google$ noticias",
- "description": "Texto de la accion de menú de la extension en el manifiesto.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "unknown_title": {
- "message": "Título desconocido",
- "description": "Noticia con título desconocido."
- },
- "error": {
- "message": "Error: $error$",
- "description": "Plantilla de error genérico. Hace falta pasar un parámetro de error.",
- "placeholders": {
- "error": {
- "content": "$1",
- "example": "Fallo al capturar el RSS feed."
- }
- }
- },
- "failed_to_fetch_rss": {
- "message": "Fallo al capturar el RSS feed.",
- "description": "Mensaje de error visible para el usuario."
- },
- "not_a_valid_feed": {
- "message": "Feed no válido.",
- "description": "Mensaje de error visible para el usuario."
- },
- "more_stories": {
- "message": "Ir a $Google$ noticias \u00BB",
- "description": "Nombre del enlace a Google noticias.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "newsUrl": {
- "message": "http://news.google.es",
- "description": "Dirección de Google News."
- }
-}
-
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json
deleted file mode 100644
index 7d43d7a0976..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "name": {
- "message": "Читач веÑти",
- "description": "Ðазив екÑтензије у манифеÑту."
- },
- "description": {
- "message": "Приказује првих 5 веÑти Ñа '$Google$ ВеÑти - главне веÑти' у прозорчићу.",
- "description": "ÐžÐ¿Ð¸Ñ ÐµÐºÑтензије у манифеÑту.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "default_title": {
- "message": "$Google$ ВеÑти",
- "description": "Ðазив дугмета екÑтензије.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- },
- "unknown_title": {
- "message": "Ðепознат наÑлов",
- "description": "Ðепознат наÑлов веÑти."
- },
- "error": {
- "message": "Грешка - $error$",
- "description": "Општи облик грешке.",
- "placeholders": {
- "error": {
- "content": "$1",
- "example": "фид је недоÑтупан."
- }
- }
- },
- "failed_to_fetch_rss": {
- "message": "фид је недоÑтупан.",
- "description": "Порука грешке коју види кориÑник када је фид недоÑтупан."
- },
- "not_a_valid_feed": {
- "message": "неиÑправан фид.",
- "description": "Порука грешке коју види кориÑник када је фид неиÑправан."
- },
- "more_stories": {
- "message": "Ка $Google$ ВеÑтима \u00BB",
- "description": "Ðазив везе ка још веÑти.",
- "placeholders": {
- "google": {
- "content": "Google",
- "example": "Google"
- }
- }
- }
-}
-
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json
deleted file mode 100644
index 32b4cc0e6ef..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "__MSG_name__",
- "version": "1.1",
- "description": "__MSG_description__",
- "icons": { "128": "news_icon.png" },
- "browser_action": {
- "default_title": "__MSG_default_title__",
- "default_icon": "news_action.png",
- "default_popup": "feed.html"
- },
- "permissions": [
- "tabs",
- "http://news.google.com/*",
- "http://news.google.es/*"
- ],
- "default_locale": "en"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/no_cookies/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/no_cookies/manifest.json
deleted file mode 100644
index 1a0681c9b0f..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/no_cookies/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "No Cookies",
- "description": "Removes 'Cookie' and 'Set-Cookie' headers.",
- "version": "1.0",
- "manifest_version": 2,
- "permissions": [
- "webRequest",
- "webRequestBlocking",
- "https://*/*",
- "http://*/*"
- ],
- "background": {
- "scripts": ["background.js"]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json
deleted file mode 100644
index 5771b423999..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "name": "Sample - OAuth Contacts",
- "version": "1.0.6",
- "icons": { "48": "img/icon-48.png",
- "128": "img/icon-128.png" },
- "description": "Uses OAuth to connect to Google's contacts service and display a list of your contacts.",
- "background": {
- "scripts": [
- "chrome_ex_oauthsimple.js",
- "chrome_ex_oauth.js",
- "background.js"
- ]
- },
- "browser_action": {
- "default_title": "",
- "default_icon": "img/icon-19-off.png"
- },
- "permissions": [
- "tabs",
- "http://www.google.com/m8/feeds/*",
- "https://www.google.com/accounts/OAuthGetRequestToken",
- "https://www.google.com/accounts/OAuthAuthorizeToken",
- "https://www.google.com/accounts/OAuthGetAccessToken"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/optional_permissions/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/optional_permissions/manifest.json
deleted file mode 100644
index ac62ef2d528..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/optional_permissions/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "Optional Permissions New Tab",
- "version": "1.2.5.0",
- "description": "Demonstrates optional permissions in extensions",
- "permissions": ["storage"],
- "optional_permissions": [
- "topSites"
- ],
- "icons": {
- "16": "images/optional_permissions16.png",
- "32": "images/optional_permissions32.png",
- "48": "images/optional_permissions48.png",
- "128": "images/optional_permissions128.png"
- },
- "chrome_url_overrides": {
- "newtab": "newtab.html"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json
deleted file mode 100644
index f2bd7f5aec1..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extName": {
- "message": "Per-plugin content settings"
- },
- "extDescription": {
- "message": "Customize your content setting for different plugins."
- },
- "patternColumnHeader": {
- "message": "Hostname Pattern"
- },
- "settingColumnHeader": {
- "message": "Behavior"
- },
- "allowRule": {
- "message": "Allow"
- },
- "blockRule": {
- "message": "Block"
- },
- "addNewPattern": {
- "message": "Add a new hostname pattern"
- }
-} \ No newline at end of file
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json
deleted file mode 100644
index a6181e2c888..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name" : "__MSG_extName__",
- "version" : "0.6",
- "description" : "__MSG_extDescription__",
- "options_page": "options.html",
- "permissions": [
- "contentSettings"
- ],
- "icons": {
- "128": "bunny128.png",
- "48": "bunny48.png"
- },
- "minimum_chrome_version": "16.0.912",
- "default_locale": "en",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json b/chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json
deleted file mode 100644
index 74bc6f35640..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "extName": {
- "message": "Proxy Extension API Sample",
- "description": "The extension name."
- },
- "extDescription": {
- "message": "Set Chrome-specific proxies; a demonstration of Chrome's Proxy API",
- "description": "The extension description."
- },
- "headerDirectConnection": {
- "message": "Direct Connection",
- "description": "Header for 'Direct Connection' configuration `fieldset`."
- },
- "errorNoExtensionAccess": {
- "message": "Sorry. This browser's proxy settings cannot be controlled via extensions.",
- "description": "Error message displayed when `levelOfControl` is 'not_controllable'."
- },
- "errorOtherExtensionControls": {
- "message": "Sorry. This browser's proxy settings are being controlled by another extension. Please visit chrome://extensions for details.",
- "description": "Error message displayed when `levelOfControl` is 'controlled_by_other_extensions'."
- },
- "errorSettingRegularProxy": {
- "message": "Setting regular proxy settings failed. Sorry!",
- "description": "Error message, displayed when failing to set regular proxy settings."
- },
- "errorSettingIncognitoProxy": {
- "message": "Setting incognito proxy settings failed. Sorry!",
- "description": "Error message, displayed when failing to set incognito proxy settings."
- },
- "successfullySetProxy": {
- "message": "Your proxy settings have been saved successfully; this window will close automagically. Have a nice day!",
- "description": "Success message, displayed after proxy settings have been written."
- },
- "errorPopupTitle": {
- "message": "Error: $1",
- "description": "Error message used as popup title."
- },
- "errorProxyError": {
- "message": "ProxyError: $1.",
- "description": "Error message displayed in popup when an error occurs."
- },
- "errorProxyDetailedError": {
- "message": "ProxyError: $1. $2.",
- "description": "Error message displayed in popup when an error occurs."
- },
- "errorIdNotFound": {
- "message": "Element with ID `$1` doesn't exist in the document",
- "description": "Error message thrown when the given `id` doesn't exist"
- },
- "errorIdNotForm": {
- "message": "Element with ID `$1` isn't a form element.",
- "description": "Error message thrown when the given `id` isn't a form element."
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json
deleted file mode 100644
index 41e4e869678..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "__MSG_extName__",
- "version": "0.4",
- "description": "__MSG_extDescription__",
- "default_locale": "en",
- "browser_action": {
- "default_icon": "icon16.png",
- "default_popup": "popup.html"
- },
- "icons": {
- "16": "icon16.png",
- "32": "icon32.png",
- "48": "icon48.png",
- "128": "icon128.png"
- },
- "background": {
- "scripts": [
- "proxy_form_controller.js",
- "proxy_error_handler.js",
- "background.js"
- ]
- },
- "permissions": [
- "proxy"
- ],
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json
deleted file mode 100644
index fed36081f99..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "name": "Speak Selection",
- "version": "1.1",
- "description": "Speaks the current selection out loud.",
- "permissions": [
- "<all_urls>",
- "tts",
- "tabs"
- ],
-
- "background": {
- "scripts": [
- "keycodes.js",
- "tabs.js",
- "background.js"
- ]
- },
-
- "browser_action": {
- "default_icon": "SpeakSel19.png",
- "default_title": "Speak Selection"
- },
-
- "options_page": "options.html",
-
- "minimum_chrome_version": "14",
-
- "content_scripts": [
- {
- "matches": [
- "<all_urls>"
- ],
- "all_frames": true,
- "js": [
- "keycodes.js",
- "content_script.js"
- ]
- }
- ],
-
- "icons": {
- "16": "SpeakSel16.png",
- "48": "SpeakSel48.png",
- "128": "SpeakSel128.png",
- "256": "SpeakSel256.png"
- },
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json
deleted file mode 100644
index 79f5b50a6d2..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "Talking Alarm Clock",
- "version": "1.3",
- "description": "A clock with two configurable alarms that will play a sound and speak a phrase of your choice.",
- "permissions": [ "background", "tts" ],
-
- "background": { "scripts": ["common.js", "background.js"] },
-
- "browser_action": {
- "default_icon": "clock-19.png",
- "default_title": "Talking Alarm Clock",
- "default_popup": "popup.html"
- },
-
- "icons": {
- "16": "clock-16.png",
- "48": "clock-48.png",
- "128": "clock-128.png",
- "256": "clock-256.png"
- },
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json
deleted file mode 100644
index 0a856f92856..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "app": {
- "launch": {
- "local_path": "ttsdebug.html"
- }
- },
- "description": "Tool for developers of Chrome TTS engine extensions to help them test their engines are implementing the API correctly.",
- "icons": {
- "16": "16.png",
- "128": "128.png",
- "256": "256.png"
- },
- "minimum_chrome_version": "14",
- "name": "TTS Debug",
- "permissions": [ "tts" ],
- "version": "1.0",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json b/chromium/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json
deleted file mode 100644
index ade1281d469..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "app": {
- "launch": {
- "local_path": "ttsdemo.html"
- }
- },
- "description": "Demo Chrome's synthesized text-to-speech capabilities.",
- "icons": {
- "16": "16.png",
- "128": "128.png",
- "256": "256.png"
- },
- "minimum_chrome_version": "14",
- "name": "TTS Demo",
- "permissions": [ "tts" ],
- "version": "2.1",
-
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json b/chromium/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json
deleted file mode 100644
index 0fd7c52b674..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "Sandboxed Frame",
- "description": "Demonstrate use of handlebars inside a sandboxed frame",
- "version": "1.0",
- "manifest_version": 2,
- "permissions": ["notifications"],
- "background": {
- "page": "eventpage.html",
- "persistent": false
- },
- "browser_action": {
- "default_icon" : "icon.png",
- "default_title": "Start Event Page"
- },
- "sandbox": {
- "pages": ["sandbox.html"]
- },
- "web_accessible_resources": ["icon.png"]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/howto/tab_shortcuts/manifest.json b/chromium/chrome/common/extensions/docs/examples/howto/tab_shortcuts/manifest.json
deleted file mode 100644
index 198ac7a54f7..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/howto/tab_shortcuts/manifest.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "name": "Tab Shortcuts",
- "version": "1.0",
- "description": "Allows pinning and duplication of tabs via keyboard shortcuts.",
- "manifest_version": 2,
- "background": {
- "scripts": ["tab_shortcuts.js"],
- "persistent": false
- },
- "commands": {
- "toggle-pin-tab": {
- "suggested_key": {
- "default": "Ctrl+Shift+X",
- "mac": "Command+Shift+X"
- },
- "description": "Toggles whether the current tab is pinned."
- },
- "duplicate-tab": {
- "suggested_key": {
- "default": "Ctrl+Shift+Z",
- "mac": "Command+Shift+Z"
- },
- "description": "Duplicates the current tab."
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json
deleted file mode 100644
index 11f90a07c7e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "Event Tracking with Google Analytics",
- "version": "2.0.0",
- "description": "A sample extension which uses Google Analytics to track usage.",
- "browser_action": {
- "default_title": "Open the popup",
- "default_icon": "analytics-extension-icon-19.png",
- "default_popup" : "popup.html"
- },
- "icons": {
- "48": "analytics-extension-icon-48.png",
- "128": "analytics-extension-icon-128.png"
- },
-
- "manifest_version": 2,
- "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'"
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/broken_background_color/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/broken_background_color/manifest.json
deleted file mode 100644
index 8e84613c98d..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/broken_background_color/manifest.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "Broken Background Color",
- "version": "1.0",
- "description": "Fix an Extension!",
- "permissions": ["activeTab", "declarativeContent", "storage"],
- "options_page": "options.html",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "page_action": {
- "default_popup": "popup.html",
- "default_icon": {
- "16": "images/get_started16.png",
- "32": "images/get_started32.png",
- "48": "images/get_started48.png",
- "128": "images/get_started128.png"
- }
- },
- "icons": {
- "16": "images/get_started16.png",
- "32": "images/get_started32.png",
- "48": "images/get_started48.png",
- "128": "images/get_started128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/get_started/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/get_started/manifest.json
deleted file mode 100644
index b9ff56c22d5..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/get_started/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Getting Started Example",
- "version": "1.0",
- "description": "Build an Extension!",
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/get_started_complete/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/get_started_complete/manifest.json
deleted file mode 100644
index 71290e77441..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/get_started_complete/manifest.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "Getting Started Example",
- "version": "1.0",
- "description": "Build an Extension!",
- "permissions": ["activeTab", "declarativeContent", "storage"],
- "options_page": "options.html",
- "background": {
- "scripts": ["background.js"],
- "persistent": false
- },
- "page_action": {
- "default_popup": "popup.html",
- "default_icon": {
- "16": "images/get_started16.png",
- "32": "images/get_started32.png",
- "48": "images/get_started48.png",
- "128": "images/get_started128.png"
- }
- },
- "icons": {
- "16": "images/get_started16.png",
- "32": "images/get_started32.png",
- "48": "images/get_started48.png",
- "128": "images/get_started128.png"
- },
- "manifest_version": 2
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json
deleted file mode 100644
index 28a1fda3744..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "manifest_version": 2,
-
- "name": "Getting started example",
- "description": "This extension allows the user to change the background color of the current page.",
- "version": "1.0",
-
- "browser_action": {
- "default_icon": "icon.png",
- "default_popup": "popup.html"
- },
- "permissions": [
- "activeTab",
- "storage"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/hello_extensions/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/hello_extensions/manifest.json
deleted file mode 100644
index d21bdaae0ee..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/hello_extensions/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "Hello Extensions",
- "description" : "Base Level Extension",
- "version": "1.0",
- "browser_action": {
- "default_popup": "hello.html",
- "default_icon": "hello_extensions.png"
- },
- "manifest_version": 2,
- "commands": {
- "_execute_browser_action": {
- "suggested_key": {
- "default": "Ctrl+Shift+F",
- "mac": "MacCtrl+Shift+F"
- },
- "description": "Opens hello.html"
- }
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/oauth_starter/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/oauth_starter/manifest.json
deleted file mode 100644
index 1035894ad22..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/oauth_starter/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "OAuth Tutorial FriendBlock",
- "version": "1.0",
- "description": "Uses OAuth to connect to Google's People API and display contacts photos.",
- "manifest_version": 2,
- "browser_action": {
- "default_title": "FriendBlock, friends face's in a block."
- },
- "background": {
- "scripts": [
- "background.js"
- ],
- "persistent": false
- }
- }
diff --git a/chromium/chrome/common/extensions/docs/examples/tutorials/oauth_tutorial_complete/manifest.json b/chromium/chrome/common/extensions/docs/examples/tutorials/oauth_tutorial_complete/manifest.json
deleted file mode 100644
index 5afe50bee4e..00000000000
--- a/chromium/chrome/common/extensions/docs/examples/tutorials/oauth_tutorial_complete/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "OAuth Tutorial FriendBlock",
- "version": "1.0",
- "description": "Uses OAuth to connect to Google's People API and display contacts photos.",
- "manifest_version": 2,
- "browser_action": {
- "default_title": "FriendBlock, friends face's in a block."
- },
- "permissions": [
- "identity"
- ],
- "background": {
- "scripts": [
- "background.js"
- ],
- "persistent": false
- },
- "oauth2": {
- "client_id": "ClientIDFromGoogleAPIConsole",
- "scopes":["https://www.googleapis.com/auth/contacts.readonly"]
- },
- "key": "KeyFromDeveloperDashboardHere"
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/known_broken_links.json b/chromium/chrome/common/extensions/docs/server2/known_broken_links.json
deleted file mode 100644
index 4b1d59fdaa9..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/known_broken_links.json
+++ /dev/null
@@ -1,2019 +0,0 @@
-[
- [
- 302,
- "extensions/app_identity.html",
- "extensions/publish_app.html",
- "redirects to /apps/publish_app.html"
- ],
- [
- 404,
- "apps/manifest/storage.html",
- "apps/manifest/chrome:/policy",
- "target page not found"
- ],
- [
- 200,
- "apps/api_index.html",
- "#manifest",
- "target anchor not found"
- ],
- [
- 404,
- "apps/tut_oauth.html",
- "apps/examples/extensions/oauth_contacts/chrome_ex_oauth.html",
- "target page not found"
- ],
- [
- 404,
- "apps/tut_oauth.html",
- "apps/examples/extensions/oauth_contacts/chrome_ex_oauth.js",
- "target page not found"
- ],
- [
- 404,
- "apps/tut_oauth.html",
- "apps/examples/extensions/oauth_contacts/chrome_ex_oauthsimple.js",
- "target page not found"
- ],
- [
- 404,
- "apps/tut_oauth.html",
- "apps/examples/extensions/oauth_contacts/onload.js",
- "target page not found"
- ],
- [
- 200,
- "extensions/debugger.html",
- "extensions/samples.html#debugger",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/privacy.html",
- "extensions/samples.html#privacy",
- "target anchor not found"
- ],
- [
- 200,
- "apps/events.html",
- "#filtered",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/contextMenus.html",
- "extensions/samples.html#contextMenus",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/webRequest.html",
- "extensions/samples.html#webrequest",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/browsingData.html",
- "extensions/samples.html#browsingData",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/override.html",
- "extensions/samples.html#chrome_url_overrides",
- "target anchor not found"
- ],
- [
- 302,
- "apps/types.html",
- "apps/proxy.html#overview-examples",
- "redirects to /extensions/proxy.html"
- ],
- [
- 302,
- "apps/types.html",
- "apps/proxy.html#property-settings",
- "redirects to /extensions/proxy.html"
- ],
- [
- 302,
- "apps/experimental_webInspector.html",
- "apps/experimental_devtools.html",
- "redirects to /extensions/experimental_devtools.html"
- ],
- [
- 302,
- "apps/experimental_devtools_network.html",
- "apps/devtools_network.html",
- "redirects to /extensions/devtools_network.html"
- ],
- [
- 404,
- "extensions/manifest/storage.html",
- "extensions/manifest/chrome:/policy",
- "target page not found"
- ],
- [
- 302,
- "apps/tut_debugging.html",
- "apps/getstarted.html",
- "redirects to /extensions/getstarted.html"
- ],
- [
- 302,
- "apps/tut_debugging.html",
- "apps/getstarted.html#unpacked",
- "redirects to /extensions/getstarted.html"
- ],
- [
- 302,
- "apps/tut_debugging.html",
- "apps/getstarted.html#next-steps",
- "redirects to /extensions/getstarted.html"
- ],
- [
- 302,
- "apps/storage.html",
- "apps/manifest/incognito.html",
- "redirects to /extensions/manifest/incognito.html"
- ],
- [
- 302,
- "apps/messaging.html",
- "apps/tabs.html#method-sendMessage",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/messaging.html",
- "apps/tabs.html#method-connect",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/messaging.html",
- "apps/tabs.html#method-connect",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "extensions/notifications.html",
- "extensions/app_external.html",
- "redirects to /apps/app_external.html"
- ],
- [
- 302,
- "extensions/notifications.html",
- "extensions/app_lifecycle.html#create_event_page",
- "redirects to /apps/app_lifecycle.html"
- ],
- [
- 302,
- "apps/app_codelab8_webresources.html",
- "apps/webview_tag.html",
- "redirects to /apps/tags/webview.html"
- ],
- [
- 302,
- "apps/app_codelab8_webresources.html",
- "apps/webview_tag.html",
- "redirects to /apps/tags/webview.html"
- ],
- [
- 200,
- "apps/contextMenus.html",
- "apps/samples.html#contextMenus",
- "target anchor not found"
- ],
- [
- 302,
- "apps/contextMenus.html",
- "apps/tabs.html#type-Tab",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/contextMenus.html",
- "apps/tabs.html#type-Tab",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/contextMenus.html",
- "apps/tabs.html#type-Tab",
- "redirects to /extensions/tabs.html"
- ],
- [
- 404,
- "extensions/manifest/externally_connectable.html",
- "extensions/manifest/runtime.html#method-connect",
- "target page not found"
- ],
- [
- 404,
- "extensions/manifest/externally_connectable.html",
- "extensions/manifest/runtime.html#method-sendMessage",
- "target page not found"
- ],
- [
- 404,
- "extensions/manifest/externally_connectable.html",
- "extensions/manifest/runtime.html#property-MessageSender-tlsChannelId",
- "target page not found"
- ],
- [
- 404,
- "extensions/manifest/externally_connectable.html",
- "extensions/manifest/runtime.html#property-MessageSender-tlsChannelId",
- "target page not found"
- ],
- [
- 404,
- "extensions/declare_permissions.html",
- "extensions/dns.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/declare_permissions.html",
- "extensions/idltest.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/declare_permissions.html",
- "extensions/system_display.html",
- "redirects to /apps/system_display.html"
- ],
- [
- 200,
- "extensions/experimental.html",
- "extensions/samples.html#experimental",
- "target anchor not found"
- ],
- [
- 302,
- "extensions/samples.html",
- "extensions/app_runtime.html",
- "redirects to /apps/app_runtime.html"
- ],
- [
- 302,
- "extensions/samples.html",
- "extensions/app_runtime.html#event-onLaunched",
- "redirects to /apps/app_runtime.html"
- ],
- [
- 302,
- "extensions/samples.html",
- "extensions/app_window.html#method-create",
- "redirects to /apps/app_window.html"
- ],
- [
- 302,
- "extensions/samples.html",
- "extensions/app_runtime.html#event-onLaunched",
- "redirects to /apps/app_runtime.html"
- ],
- [
- 302,
- "extensions/samples.html",
- "extensions/app_runtime.html#event-onRestarted",
- "redirects to /apps/app_runtime.html"
- ],
- [
- 302,
- "extensions/samples.html",
- "extensions/app_window.html#method-create",
- "redirects to /apps/app_window.html"
- ],
- [
- 200,
- "extensions/contentSettings.html",
- "extensions/samples.html#contentSettings",
- "target anchor not found"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_network.html",
- "redirects to /extensions/devtools_network.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_panels.html",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_panels.html",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_network.html",
- "redirects to /extensions/devtools_network.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/extension.html",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_panels.html",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_panels.html#method-ExtensionSidebarPane-setPage",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_panels.html#method-ExtensionSidebarPane-setObject",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_panels.html#method-ExtensionSidebarPane-setExpression",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/tabs.html#method-executeScript",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html#property-tabId",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/tabs.html#method-executeScript",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html#method-eval",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/tabs.html#method-executeScript",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html#method-eval",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/devtools_inspectedWindow.html#method-eval",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/devtools.html",
- "apps/tabs.html#method-sendMessage",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/event_pages.html",
- "apps/declarativeWebRequest.html",
- "redirects to /extensions/declarativeWebRequest.html"
- ],
- [
- 302,
- "apps/event_pages.html",
- "apps/extension.html#method-getBackgroundPage",
- "redirects to /extensions/extension.html"
- ],
- [
- 200,
- "apps/event_pages.html",
- "apps/events.html#filtered",
- "target anchor not found"
- ],
- [
- 302,
- "apps/event_pages.html",
- "apps/tabs.html#event-onUpdated",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/event_pages.html",
- "apps/webNavigation.html#event-onCompleted",
- "redirects to /extensions/webNavigation.html"
- ],
- [
- 200,
- "extensions/declarativeWebRequest.html",
- "#type-Rule",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/topSites.html",
- "extensions/samples.html#topsites",
- "target anchor not found"
- ],
- [
- 302,
- "apps/content_scripts.html",
- "apps/extension.html",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/content_scripts.html",
- "apps/tabs.html#method-executeScript",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/content_scripts.html",
- "apps/tabs.html#method-insertCSS",
- "redirects to /extensions/tabs.html"
- ],
- [
- 200,
- "apps/content_scripts.html",
- "apps/samples.html#script",
- "target anchor not found"
- ],
- [
- 200,
- "apps/content_scripts.html",
- "apps/samples.html#message-timer",
- "target anchor not found"
- ],
- [
- 200,
- "apps/content_scripts.html",
- "apps/samples.html#page-redder",
- "target anchor not found"
- ],
- [
- 200,
- "apps/content_scripts.html",
- "apps/samples.html#email-this-page-(by-google)",
- "target anchor not found"
- ],
- [
- 302,
- "apps/experimental_devtools_inspectedWindow.html",
- "apps/devtools_inspectedWindow.html",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/background_pages.html",
- "apps/manifest/incognito.html",
- "redirects to /extensions/manifest/incognito.html"
- ],
- [
- 302,
- "apps/background_pages.html",
- "apps/overview.html#arch",
- "redirects to /extensions/overview.html"
- ],
- [
- 200,
- "apps/background_pages.html",
- "apps/declare_permissions.html#background",
- "target anchor not found"
- ],
- [
- 302,
- "apps/background_pages.html",
- "apps/extension.html#method-getViews",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/background_pages.html",
- "apps/extension.html#method-getBackgroundPage",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/background_pages.html",
- "apps/tabs.html#method-create",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/packaging.html",
- "apps/overview.html",
- "redirects to /extensions/overview.html"
- ],
- [
- 302,
- "apps/declare_permissions.html",
- "apps/cookies.html",
- "redirects to /extensions/cookies.html"
- ],
- [
- 404,
- "apps/declare_permissions.html",
- "target page not found"
- ],
- [
- 302,
- "apps/declare_permissions.html",
- "apps/desktopCapture.html",
- "redirects to /extensions/desktopCapture.html"
- ],
- [
- 404,
- "apps/declare_permissions.html",
- "apps/diagnostics.html",
- "target page not found"
- ],
- [
- 404,
- "apps/declare_permissions.html",
- "apps/dns.html",
- "target page not found"
- ],
- [
- 302,
- "apps/declare_permissions.html",
- "apps/fileBrowserHandler.html",
- "redirects to /extensions/fileBrowserHandler.html"
- ],
- [
- 404,
- "apps/declare_permissions.html",
- "apps/fileSystemProvider.html",
- "target page not found"
- ],
- [
- 302,
- "apps/declare_permissions.html",
- "apps/signedInDevices.html",
- "redirects to /extensions/signedInDevices.html"
- ],
- [
- 404,
- "apps/declare_permissions.html",
- "apps/system_network.html",
- "target page not found"
- ],
- [
- 404,
- "apps/declare_permissions.html",
- "apps/webview.html",
- "target page not found"
- ],
- [
- 302,
- "apps/app_deprecated.html",
- "apps/webview_tag.html",
- "redirects to /apps/tags/webview.html"
- ],
- [
- 200,
- "extensions/content_scripts.html",
- "extensions/samples.html#script",
- "target anchor not found"
- ],
- [
- 302,
- "apps/samples.html",
- "apps/extension.html#property-lastError",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/samples.html",
- "apps/extension.html#property-lastError-message",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/samples.html",
- "apps/extension.html#property-lastError",
- "redirects to /extensions/extension.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/activityLogPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/app.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/bookmarkManagerPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/bookmarks.html",
- "redirects to /extensions/bookmarks.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/override.html",
- "redirects to /extensions/override.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/brailleDisplayPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/browserAction.html",
- "redirects to /extensions/browserAction.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/browsingData.html",
- "redirects to /extensions/browsingData.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/cast_channel.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/cast_streaming_rtpStream.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/cast_streaming_session.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/cast_streaming_udpTransport.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/chromeosInfoPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/cloudPrintPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/commandLinePrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/commands.html",
- "redirects to /extensions/commands.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/contentSettings.html",
- "redirects to /extensions/contentSettings.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/cookies.html",
- "redirects to /extensions/cookies.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/debugger.html",
- "redirects to /extensions/debugger.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/declarativeContent.html",
- "redirects to /extensions/declarativeContent.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/declarativeWebRequest.html",
- "redirects to /extensions/declarativeWebRequest.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/webRequest.html",
- "redirects to /extensions/webRequest.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/desktopCapture.html",
- "redirects to /extensions/desktopCapture.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/developerPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/devtools_inspectedWindow.html",
- "redirects to /extensions/devtools_inspectedWindow.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/devtools_network.html",
- "redirects to /extensions/devtools_network.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/devtools_panels.html",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/diagnostics.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/dial.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/dns.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/downloads.html",
- "redirects to /extensions/downloads.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/echoPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/enterprise_platformKeysPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/experimental_accessibility.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/experimental_devtools_console.html",
- "redirects to /extensions/experimental_devtools_console.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/experimental_discovery.html",
- "redirects to /extensions/experimental_discovery.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/experimental_history.html",
- "redirects to /extensions/experimental_history.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/extension.html",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/feedbackPrivate.html",
- "redirects to /extensions/feedbackPrivate.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/fileBrowserHandler.html",
- "redirects to /extensions/fileBrowserHandler.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/fileBrowserHandlerInternal.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/fileManagerPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/fileSystemProvider.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/firstRunPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/fontSettings.html",
- "redirects to /extensions/fontSettings.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/history.html",
- "redirects to /extensions/history.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/override.html",
- "redirects to /extensions/override.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/identityPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/idltest.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/input_ime.html",
- "redirects to /extensions/input_ime.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/inputMethodPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/logPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/management.html",
- "redirects to /extensions/management.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/override.html",
- "redirects to /extensions/override.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/manifestTypes.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/mdns.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/mediaGalleriesPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/mediaPlayerPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/metricsPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/networkingPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/omnibox.html",
- "redirects to /extensions/omnibox.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/pageAction.html",
- "redirects to /extensions/pageAction.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/pageActions.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/pageCapture.html",
- "redirects to /extensions/pageCapture.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/preferencesPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/privacy.html",
- "redirects to /extensions/privacy.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/processes.html",
- "redirects to /extensions/processes.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/proxy.html",
- "redirects to /extensions/proxy.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/sessions.html",
- "redirects to /extensions/sessions.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/signedInDevices.html",
- "redirects to /extensions/signedInDevices.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/sockets_tcp.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/sockets_tcpServer.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/sockets_udp.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/streamsPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/system_network.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/systemIndicator.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/systemPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/tabCapture.html",
- "redirects to /extensions/tabCapture.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/tabs.html",
- "redirects to /extensions/tabs.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/terminalPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/test.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/topSites.html",
- "redirects to /extensions/topSites.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/ttsEngine.html",
- "redirects to /extensions/ttsEngine.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/virtualKeyboardPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/wallpaperPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/webNavigation.html",
- "redirects to /extensions/webNavigation.html"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/webRequest.html",
- "redirects to /extensions/webRequest.html"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/webRequestInternal.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/webstorePrivate.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/webview.html",
- "target page not found"
- ],
- [
- 404,
- "apps/private_apis.html",
- "apps/webviewTag.html",
- "target page not found"
- ],
- [
- 302,
- "apps/private_apis.html",
- "apps/windows.html",
- "redirects to /extensions/windows.html"
- ],
- [
- 200,
- "apps/private_apis.html",
- "#icon",
- "target anchor not found"
- ],
- [
- 200,
- "apps/private_apis.html",
- "#tooltip",
- "target anchor not found"
- ],
- [
- 200,
- "apps/private_apis.html",
- "#badge",
- "target anchor not found"
- ],
- [
- 200,
- "apps/private_apis.html",
- "#popups",
- "target anchor not found"
- ],
- [
- 200,
- "apps/private_apis.html",
- "#usage",
- "target anchor not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-ContentWindow",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-WebRequestEventInteface",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-ClearDataOptions",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-ClearDataTypeSet",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-ClearDataOptions",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-ClearDataTypeSet",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-InjectDetails",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-InjectDetails",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-InjectDetails",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-InjectDetails",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#method-setUserAgentOverride",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-DialogController",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-NewWindow",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-MediaPermissionRequest",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-GeolocationPermissionRequest",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-PointerLockPermissionRequest",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-DownloadPermissionRequest",
- "target page not found"
- ],
- [
- 404,
- "apps/tags/webview.html",
- "apps/tags/webviewTag.html#type-LoadPluginPermissionRequest",
- "target page not found"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/bookmarks.html",
- "redirects to /extensions/bookmarks.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/history.html",
- "redirects to /extensions/history.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/topSites.html",
- "redirects to /extensions/topSites.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/tabs.html",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/windows.html",
- "redirects to /extensions/windows.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/webNavigation.html",
- "redirects to /extensions/webNavigation.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/contentSettings.html",
- "redirects to /extensions/contentSettings.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/debugger.html",
- "redirects to /extensions/debugger.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/proxy.html",
- "redirects to /extensions/proxy.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/activeTab.html",
- "redirects to /extensions/activeTab.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/management.html",
- "redirects to /extensions/management.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/privacy.html",
- "redirects to /extensions/privacy.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/signedInDevices.html",
- "redirects to /extensions/signedInDevices.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/ttsEngine.html",
- "redirects to /extensions/ttsEngine.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/tabs.html#type-Tab",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/permission_warnings.html",
- "apps/management.html#method-getPermissionWarningsByManifest",
- "redirects to /extensions/management.html"
- ],
- [
- 404,
- "extensions/whats_new.html",
- "extensions/manifest/geolocation.html",
- "target page not found"
- ],
- [
- 200,
- "extensions/devtools_inspectedWindow.html",
- "extensions/samples.html#devtools",
- "target anchor not found"
- ],
- [
- 302,
- "apps/runtime.html",
- "apps/tabs.html#type-Tab",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/runtime.html",
- "apps/tabs.html#type-Tab",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/runtime.html",
- "apps/tabs.html#method-connect",
- "redirects to /extensions/tabs.html"
- ],
- [
- 302,
- "apps/runtime.html",
- "apps/tabs.html#method-sendMessage",
- "redirects to /extensions/tabs.html"
- ],
- [
- 200,
- "extensions/devtools_network.html",
- "extensions/samples.html#devtools.network",
- "target anchor not found"
- ],
- [
- 302,
- "apps/desktop_notifications.html",
- "apps/extension.html#method-getBackgroundPage",
- "redirects to /extensions/extension.html"
- ],
- [
- 302,
- "apps/desktop_notifications.html",
- "apps/extension.html#method-getViews",
- "redirects to /extensions/extension.html"
- ],
- [
- 404,
- "apps/manifest/externally_connectable.html",
- "apps/manifest/runtime.html#method-connect",
- "target page not found"
- ],
- [
- 404,
- "apps/manifest/externally_connectable.html",
- "apps/manifest/runtime.html#method-sendMessage",
- "target page not found"
- ],
- [
- 404,
- "apps/manifest/externally_connectable.html",
- "apps/manifest/runtime.html#property-MessageSender-tlsChannelId",
- "target page not found"
- ],
- [
- 404,
- "apps/manifest/externally_connectable.html",
- "apps/manifest/runtime.html#property-MessageSender-tlsChannelId",
- "target page not found"
- ],
- [
- 200,
- "extensions/omnibox.html",
- "extensions/samples.html#omnibox",
- "target anchor not found"
- ],
- [
- 302,
- "apps/experimental_devtools_panels.html",
- "apps/devtools_panels.html",
- "redirects to /extensions/devtools_panels.html"
- ],
- [
- 200,
- "extensions/api_index.html",
- "#icon",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/api_index.html",
- "#tooltip",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/api_index.html",
- "#badge",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/api_index.html",
- "#popups",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/api_index.html",
- "#manifest",
- "target anchor not found"
- ],
- [
- 302,
- "apps/faq.html",
- "apps/browserAction.html",
- "redirects to /extensions/browserAction.html"
- ],
- [
- 302,
- "apps/faq.html",
- "apps/pageAction.html",
- "redirects to /extensions/pageAction.html"
- ],
- [
- 302,
- "apps/faq.html",
- "apps/management.html",
- "redirects to /extensions/management.html"
- ],
- [
- 302,
- "extensions/runtime.html",
- "extensions/app_lifecycle.html",
- "redirects to /apps/app_lifecycle.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/activityLogPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/app.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/app_runtime.html",
- "redirects to /apps/app_runtime.html"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/app_window.html",
- "redirects to /apps/app_window.html"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/audio.html",
- "redirects to /apps/audio.html"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/bluetooth.html",
- "redirects to /apps/bluetooth.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/bookmarkManagerPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/brailleDisplayPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/cast_channel.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/cast_streaming_rtpStream.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/cast_streaming_session.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/cast_streaming_udpTransport.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/chromeosInfoPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/cloudPrintPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/commandLinePrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/developerPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/diagnostics.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/dial.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/dns.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/echoPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/enterprise_platformKeysPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/experimental_accessibility.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/fileBrowserHandlerInternal.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/fileManagerPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/fileSystem.html",
- "redirects to /apps/fileSystem.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/fileSystemProvider.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/firstRunPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/identityPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/idltest.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/inputMethodPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/logPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/manifestTypes.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/mdns.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/mediaGalleries.html",
- "redirects to /apps/mediaGalleries.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/mediaGalleriesPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/mediaPlayerPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/metricsPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/networkingPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/pageActions.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/preferencesPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/serial.html",
- "redirects to /apps/serial.html"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/socket.html",
- "redirects to /apps/socket.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/sockets_tcp.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/sockets_tcpServer.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/sockets_udp.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/streamsPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/syncFileSystem.html",
- "redirects to /apps/syncFileSystem.html"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/app_storage.html",
- "redirects to /apps/app_storage.html"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/system_display.html",
- "redirects to /apps/system_display.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/system_network.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/systemIndicator.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/systemPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/terminalPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/test.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/usb.html",
- "redirects to /apps/usb.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/virtualKeyboardPrivate.html",
- "target page not found"
- ],
- [
- 302,
- "extensions/private_apis.html",
- "extensions/wallpaper.html",
- "redirects to /apps/wallpaper.html"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/wallpaperPrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/webRequestInternal.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/webstorePrivate.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/webview.html",
- "target page not found"
- ],
- [
- 404,
- "extensions/private_apis.html",
- "extensions/webviewTag.html",
- "target page not found"
- ],
- [
- 200,
- "extensions/private_apis.html",
- "#usage",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/tabs.html",
- "#type-ImageDetails",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/tabs.html",
- "#type-ImageDetails",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/declarativeContent.html",
- "#type-Rule",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/whats_new.html",
- "#badge",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/whats_new.html",
- "#icon",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/whats_new.html",
- "#manifest",
- "target anchor not found"
- ],
- [
- 200,
- "apps/whats_new.html",
- "#manifest",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/whats_new.html",
- "#popups",
- "target anchor not found"
- ],
- [
- 200,
- "extensions/whats_new.html",
- "#tooltip",
- "target anchor not found"
- ]
-]
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json b/chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json
deleted file mode 100644
index 24f360a73d1..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json
+++ /dev/null
@@ -1,310 +0,0 @@
-[
- {
- "os": "win",
- "versions": [
- {
- "base_trunk_revision": "r?",
- "base_webkit_revision": "r?",
- "branch_revision": "NA",
- "channel": "canary",
- "date": "00/00/00",
- "prev_date": "05/13/13",
- "prev_version": "32.0.1506.0",
- "true_branch": null,
- "v8_ver": null,
- "version": "0.0.0.0",
- "wk_ver": null
- },
- {
- "base_trunk_revision": 198577,
- "base_webkit_revision": 149738,
- "branch_revision": 199640,
- "channel": "dev",
- "date": "05/13/13",
- "prev_date": "05/09/13",
- "prev_version": "31.0.1612.1",
- "true_branch": "1612",
- "v8_ver": "3.18.5.2",
- "version": "31.0.1612.2",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 198567,
- "channel": "beta",
- "date": "05/08/13",
- "prev_date": "05/01/13",
- "prev_version": "30.0.1599.0",
- "true_branch": "1599",
- "v8_ver": "3.17.6.13",
- "version": "30.0.1599.10",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": 193017,
- "channel": "stable",
- "date": "04/09/13",
- "prev_date": "03/26/13",
- "prev_version": "29.0.1547.18",
- "true_branch": "1547",
- "v8_ver": "3.16.14.11",
- "version": "29.0.1547.22",
- "wk_ver": "537.31"
- }
- ]
- },
- {
- "os": "ios",
- "versions": [
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 191487,
- "channel": "beta",
- "date": "05/10/13",
- "prev_date": "05/07/13",
- "prev_version": "30.0.1599.9",
- "true_branch": "1599",
- "v8_ver": "3.17.6.1",
- "version": "30.0.1599.10",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": "NA",
- "channel": "stable",
- "date": "04/29/13",
- "prev_date": "04/09/13",
- "prev_version": "29.0.1547.50",
- "true_branch": null,
- "v8_ver": null,
- "version": "29.0.1410.53",
- "wk_ver": null
- }
- ]
- },
- {
- "os": "cros",
- "versions": [
- {
- "base_trunk_revision": 198577,
- "base_webkit_revision": 149738,
- "branch_revision": 199640,
- "channel": "dev",
- "date": "05/14/13",
- "prev_date": "05/09/13",
- "prev_version": "31.0.1612.4",
- "true_branch": "1612",
- "v8_ver": "3.18.5.2",
- "version": "31.0.1612.11",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 198939,
- "channel": "beta",
- "date": "05/10/13",
- "prev_date": "05/03/13",
- "prev_version": "30.0.1599.76",
- "true_branch": "1599",
- "v8_ver": "3.17.6.13",
- "version": "30.0.1599.83",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": 191765,
- "channel": "stable",
- "date": "04/11/13",
- "prev_date": "04/04/13",
- "prev_version": "28.0.1547.173",
- "true_branch": "1547",
- "v8_ver": "3.16.14.11",
- "version": "29.0.1547.57",
- "wk_ver": "537.31"
- }
- ]
- },
- {
- "os": "cf",
- "versions": [
- {
- "base_trunk_revision": 198577,
- "base_webkit_revision": 149738,
- "branch_revision": 199640,
- "channel": "dev",
- "date": "05/13/13",
- "prev_date": "05/09/13",
- "prev_version": "31.0.1500.5",
- "true_branch": "1612",
- "v8_ver": "3.18.5.2",
- "version": "31.0.1500.11",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 198567,
- "channel": "beta",
- "date": "05/08/13",
- "prev_date": "05/01/13",
- "prev_version": "30.0.1453.73",
- "true_branch": "1599",
- "v8_ver": "3.17.6.13",
- "version": "30.0.1453.81",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": 193017,
- "channel": "stable",
- "date": "04/09/13",
- "prev_date": "03/26/13",
- "prev_version": "29.0.1410.43",
- "true_branch": "1547",
- "v8_ver": "3.16.14.11",
- "version": "29.0.1410.64",
- "wk_ver": "537.31"
- }
- ]
- },
- {
- "os": "mac",
- "versions": [
- {
- "base_trunk_revision": 199851,
- "base_webkit_revision": 150220,
- "branch_revision": "NA",
- "channel": "canary",
- "date": "05/14/13",
- "prev_date": "05/13/13",
- "prev_version": "32.0.1506.0",
- "true_branch": "trunk",
- "v8_ver": "3.19.0.2",
- "version": "32.0.1507.0",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 198577,
- "base_webkit_revision": 149738,
- "branch_revision": 199640,
- "channel": "dev",
- "date": "05/13/13",
- "prev_date": "05/09/13",
- "prev_version": "31.0.1500.6",
- "true_branch": "1500",
- "v8_ver": "3.18.5.2",
- "version": "31.0.1500.11",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 198567,
- "channel": "beta",
- "date": "05/08/13",
- "prev_date": "05/01/13",
- "prev_version": "30.0.1453.73",
- "true_branch": "1453",
- "v8_ver": "3.17.6.13",
- "version": "30.0.1453.81",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": 193261,
- "channel": "stable",
- "date": "04/10/13",
- "prev_date": "04/09/13",
- "prev_version": "29.0.1410.63",
- "true_branch": "1410",
- "v8_ver": "3.16.14.11",
- "version": "29.0.1410.65",
- "wk_ver": "537.31"
- }
- ]
- },
- {
- "os": "linux",
- "versions": [
- {
- "base_trunk_revision": 198577,
- "base_webkit_revision": 149738,
- "branch_revision": 199640,
- "channel": "dev",
- "date": "05/14/13",
- "prev_date": "05/09/13",
- "prev_version": "31.0.1612.1",
- "true_branch": "1612",
- "v8_ver": "3.18.5.2",
- "version": "31.0.1612.2",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 198567,
- "channel": "beta",
- "date": "05/09/13",
- "prev_date": "05/02/13",
- "prev_version": "30.0.1599.0",
- "true_branch": "1599",
- "v8_ver": "3.17.6.13",
- "version": "30.0.1599.10",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": 192696,
- "channel": "stable",
- "date": "04/09/13",
- "prev_date": "03/26/13",
- "prev_version": "29.0.1547.18",
- "true_branch": "1547",
- "v8_ver": "3.16.14.11",
- "version": "29.0.1547.22",
- "wk_ver": "537.31"
- }
- ]
- },
- {
- "os": "android",
- "versions": [
- {
- "base_trunk_revision": 190564,
- "base_webkit_revision": 146842,
- "branch_revision": 199333,
- "channel": "beta",
- "date": "05/11/13",
- "prev_date": "05/02/13",
- "prev_version": "30.0.1453.74",
- "true_branch": "1453",
- "v8_ver": "3.17.6.13",
- "version": "30.0.1453.85",
- "wk_ver": "537.36"
- },
- {
- "base_trunk_revision": 181864,
- "base_webkit_revision": 142426,
- "branch_revision": 191860,
- "channel": "stable",
- "date": "04/18/13",
- "prev_date": "03/12/13",
- "prev_version": "28.0.1364.169",
- "true_branch": "1410",
- "v8_ver": "3.16.14.11",
- "version": "29.0.1410.58",
- "wk_ver": "537.31"
- }
- ]
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/second.json b/chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/second.json
deleted file mode 100644
index f14fd588a8f..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/branch_utility/second.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"timestamp": "2014-06-03 14:20:11.466940", "version": "37.0.2024.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-05-28 17:34:14.640050", "version": "37.0.2017.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-05-23 00:42:16.974880", "version": "37.0.2008.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-05-20 18:31:15.985070", "version": "36.0.1985.18", "os": "win", "channel": "dev"}, {"timestamp": "2014-05-15 23:55:15.924940", "version": "36.0.1985.5", "os": "win", "channel": "dev"}, {"timestamp": "2014-05-13 17:00:10.004960", "version": "36.0.1985.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-05-06 22:39:01.402240", "version": "36.0.1976.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-30 21:51:18.067990", "version": "36.0.1964.4", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-29 17:35:11.397850", "version": "36.0.1964.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-24 22:42:37.461710", "version": "36.0.1951.5", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-15 23:55:39.012960", "version": "36.0.1941.0", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-10 21:26:42.836030", "version": "36.0.1933.0", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-08 17:40:09.919590", "version": "35.0.1916.27", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-04 14:24:42.078490", "version": "35.0.1916.17", "os": "win", "channel": "dev"}, {"timestamp": "2014-04-03 16:34:05.903530", "version": "35.0.1916.14", "os": "win", "channel": "dev"}, {"timestamp": "2014-03-31 20:24:18.562490", "version": "35.0.1916.6", "os": "win", "channel": "dev"}, {"timestamp": "2014-03-27 19:12:14.284860", "version": "35.0.1912.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-03-25 22:12:43.934170", "version": "35.0.1908.4", "os": "win", "channel": "dev"}, {"timestamp": "2014-03-18 19:12:45.029430", "version": "35.0.1897.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-03-11 15:52:03.062480", "version": "35.0.1883.0", "os": "win", "channel": "dev"}, {"timestamp": "2014-03-04 17:08:05.147800", "version": "35.0.1870.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-27 20:15:24.982930", "version": "35.0.1862.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-25 18:56:50.835670", "version": "34.0.1847.11", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-20 01:00:09.105560", "version": "34.0.1847.3", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-14 01:02:31.857440", "version": "34.0.1838.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-12 08:09:51.218870", "version": "34.0.1833.5", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-06 16:16:44.348590", "version": "34.0.1825.4", "os": "win", "channel": "dev"}, {"timestamp": "2014-02-05 20:10:09.511680", "version": "34.0.1820.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-28 20:49:26.805000", "version": "34.0.1809.0", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-21 21:34:21.385560", "version": "34.0.1797.2", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-16 20:06:23.869950", "version": "34.0.1788.0", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-14 15:52:46.003690", "version": "33.0.1750.29", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-13 16:52:00.486800", "version": "33.0.1750.27", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-09 16:13:53.961450", "version": "33.0.1750.22", "os": "win", "channel": "dev"}, {"timestamp": "2014-01-08 05:33:56.762720", "version": "33.0.1750.18", "os": "win", "channel": "dev"}, {"timestamp": "2013-12-19 19:49:09.574590", "version": "33.0.1750.5", "os": "win", "channel": "dev"}, {"timestamp": "2013-12-18 01:33:20.112740", "version": "33.0.1750.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-12-12 20:48:25.292070", "version": "33.0.1736.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-12-11 18:37:56.569580", "version": "33.0.1734.5", "os": "win", "channel": "dev"}, {"timestamp": "2013-12-06 21:42:30.823000", "version": "33.0.1729.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-12-03 16:28:07.934720", "version": "33.0.1726.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-11-21 18:52:48.991750", "version": "33.0.1712.4", "os": "win", "channel": "dev"}, {"timestamp": "2013-11-20 02:16:12.385330", "version": "33.0.1712.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-11-12 16:40:15.159270", "version": "33.0.1707.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-11-07 14:51:35.231580", "version": "32.0.1700.6", "os": "win", "channel": "dev"}, {"timestamp": "2013-11-05 23:25:13.273680", "version": "32.0.1700.4", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-31 19:34:38.528860", "version": "32.0.1687.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-29 20:22:29.668330", "version": "32.0.1685.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-22 19:47:38.530570", "version": "32.0.1678.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-17 18:06:15.765170", "version": "32.0.1671.4", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-15 20:08:18.091540", "version": "32.0.1671.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-09 14:25:52.405750", "version": "32.0.1664.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-04 00:12:16.636970", "version": "32.0.1659.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-10-02 00:52:42.386520", "version": "31.0.1650.8", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-26 14:40:17.476110", "version": "31.0.1650.4", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-25 03:12:18.996600", "version": "31.0.1650.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-19 22:59:22.657090", "version": "31.0.1636.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-19 01:37:49.821160", "version": "31.0.1632.7", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-12 22:32:48.501310", "version": "31.0.1626.5", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-10 22:35:15.246280", "version": "31.0.1626.1", "os": "win", "channel": "dev"}, {"timestamp": "2013-09-06 02:43:36.410970", "version": "31.0.1622.7", "os": "win", "channel": "dev"}, {"timestamp": "2013-08-28 20:56:45.395210", "version": "31.0.1612.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-08-28 01:32:03.681630", "version": "31.0.1612.1", "os": "win", "channel": "dev"}, {"timestamp": "2013-08-20 13:28:08.161490", "version": "30.0.1599.14", "os": "win", "channel": "dev"}, {"timestamp": "2013-08-15 22:36:45.696890", "version": "30.0.1599.10", "os": "win", "channel": "dev"}, {"timestamp": "2013-08-13 16:11:10.052430", "version": "30.0.1599.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-08-06 18:52:06.791740", "version": "30.0.1588.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-30 20:08:35.055580", "version": "30.0.1581.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-23 19:29:24.547040", "version": "30.0.1573.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-18 23:01:29.276420", "version": "30.0.1568.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-16 22:24:11.526890", "version": "30.0.1566.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-15 22:00:12.802300", "version": "29.0.1547.22", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-11 21:05:36.722350", "version": "29.0.1547.18", "os": "win", "channel": "dev"}, {"timestamp": "2013-07-08 22:38:42.088720", "version": "29.0.1547.15", "os": "win", "channel": "dev"}, {"timestamp": "2013-06-25 20:46:46.536100", "version": "29.0.1547.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-06-18 22:59:55.045240", "version": "29.0.1541.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-06-12 01:26:30.924470", "version": "29.0.1535.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-06-06 22:41:06.770150", "version": "29.0.1530.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-29 17:58:53.665720", "version": "29.0.1521.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-24 17:57:59.583870", "version": "29.0.1516.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-21 20:39:44.712530", "version": "28.0.1500.20", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-13 19:30:30.489720", "version": "28.0.1500.11", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-09 19:58:41.813520", "version": "28.0.1500.5", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-07 23:38:18.617460", "version": "28.0.1500.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-05-02 23:41:18.903620", "version": "28.0.1496.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-04-30 23:25:13.544170", "version": "28.0.1490.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-04-22 22:59:15.937080", "version": "28.0.1485.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-04-16 00:48:20.258500", "version": "28.0.1478.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-04-09 02:46:07.932850", "version": "28.0.1469.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-04-05 18:19:28.047880", "version": "28.0.1464.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-04-02 17:53:25.145500", "version": "27.0.1453.12", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-29 18:16:49.135440", "version": "27.0.1453.9", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-28 18:59:48.314880", "version": "27.0.1453.6", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-27 01:53:23.549490", "version": "27.0.1453.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-21 21:49:11.673003", "version": "27.0.1448.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-19 00:30:49.325001", "version": "27.0.1444.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-13 17:35:03.840143", "version": "27.0.1438.7", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-06 18:32:27.534513", "version": "27.0.1430.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-05 23:32:37.811235", "version": "27.0.1430.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-03-01 01:11:51.636345", "version": "27.0.1425.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-02-26 22:03:01.937236", "version": "27.0.1423.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-02-22 00:09:52.181335", "version": "26.0.1410.12", "os": "win", "channel": "dev"}, {"timestamp": "2013-02-20 00:15:47.421613", "version": "26.0.1410.10", "os": "win", "channel": "dev"}, {"timestamp": "2013-02-14 20:18:49.043086", "version": "26.0.1410.5", "os": "win", "channel": "dev"}, {"timestamp": "2013-02-13 01:49:23.707966", "version": "26.0.1410.3", "os": "win", "channel": "dev"}, {"timestamp": "2013-02-05 00:43:13.792515", "version": "26.0.1403.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-01-30 21:11:13.672237", "version": "26.0.1397.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-01-17 22:44:44.632486", "version": "26.0.1386.0", "os": "win", "channel": "dev"}, {"timestamp": "2013-01-16 01:48:57.646407", "version": "26.0.1384.2", "os": "win", "channel": "dev"}, {"timestamp": "2013-01-10 23:07:12.859265", "version": "25.0.1364.29", "os": "win", "channel": "dev"}, {"timestamp": "2013-01-08 00:05:42.205853", "version": "25.0.1364.26", "os": "win", "channel": "dev"}, {"timestamp": "2012-12-20 22:09:26.410205", "version": "25.0.1364.5", "os": "win", "channel": "dev"}, {"timestamp": "2012-12-19 02:42:39.891241", "version": "25.0.1364.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-12-14 02:37:09.422238", "version": "25.0.1359.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-12-10 22:13:57.477126", "version": "25.0.1354.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-12-07 00:16:59.533106", "version": "25.0.1349.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-11-28 23:28:57.174249", "version": "25.0.1337.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-11-13 00:52:33.199907", "version": "25.0.1323.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-11-06 19:47:19.447099", "version": "24.0.1312.5", "os": "win", "channel": "dev"}, {"timestamp": "2012-11-01 19:24:01.894562", "version": "24.0.1312.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-10-31 00:02:11.721761", "version": "24.0.1312.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-10-24 19:15:16.032706", "version": "24.0.1305.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-10-16 21:19:13.934724", "version": "24.0.1297.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-10-09 21:43:57.388586", "version": "24.0.1290.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-10-03 00:26:19.358696", "version": "24.0.1284.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-09-27 18:02:10.613799", "version": "23.0.1271.10", "os": "win", "channel": "dev"}, {"timestamp": "2012-09-25 21:21:48.349277", "version": "23.0.1271.6", "os": "win", "channel": "dev"}, {"timestamp": "2012-09-20 19:44:35.978350", "version": "23.0.1271.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-09-18 22:20:03.359033", "version": "23.0.1270.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-09-11 00:20:53.369165", "version": "23.0.1262.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-09-04 22:21:13.241591", "version": "23.0.1255.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-31 18:17:38.950756", "version": "23.0.1251.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-28 00:25:54.509043", "version": "23.0.1246.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-23 23:54:55.732357", "version": "23.0.1243.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-21 23:18:47.854439", "version": "22.0.1229.12", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-16 23:21:39.455435", "version": "22.0.1229.8", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-14 23:47:34.092347", "version": "22.0.1229.6", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-09 20:31:55.344300", "version": "22.0.1229.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-08-08 00:27:15.391271", "version": "22.0.1229.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-07-30 23:47:10.355043", "version": "22.0.1221.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-07-25 18:00:05.742833", "version": "22.0.1215.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-07-25 17:44:05.066159", "version": "22.0.1207.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-07-23 23:56:27.699693", "version": "22.0.1215.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-07-10 00:02:30.417237", "version": "22.0.1201.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-06-29 02:43:02.275037", "version": "21.0.1180.15", "os": "win", "channel": "dev"}, {"timestamp": "2012-06-26 02:18:28.893003", "version": "21.0.1180.11", "os": "win", "channel": "dev"}, {"timestamp": "2012-06-22 00:00:03.686656", "version": "21.0.1180.4", "os": "win", "channel": "dev"}, {"timestamp": "2012-06-19 23:29:49.503880", "version": "21.0.1180.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-06-11 23:44:12.497616", "version": "21.0.1171.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-06-05 00:47:31.436958", "version": "21.0.1163.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-30 00:52:58.227015", "version": "21.0.1155.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-22 00:44:35.275578", "version": "21.0.1145.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-17 23:34:11.088684", "version": "20.0.1132.11", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-16 00:34:12.839658", "version": "20.0.1132.8", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-11 20:57:40.039867", "version": "20.0.1132.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-09 01:08:05.596451", "version": "20.0.1130.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-04 01:43:59.271464", "version": "20.0.1123.4", "os": "win", "channel": "dev"}, {"timestamp": "2012-05-02 06:26:54.601413", "version": "20.0.1123.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-04-25 00:17:05.365861", "version": "20.0.1115.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-04-19 19:17:27.858190", "version": "20.0.1105.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-04-18 00:32:32.752429", "version": "20.0.1105.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-04-10 22:39:50.903181", "version": "20.0.1096.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-04-06 00:24:37.313690", "version": "19.0.1084.15", "os": "win", "channel": "dev"}, {"timestamp": "2012-04-03 22:45:44.162647", "version": "19.0.1084.9", "os": "win", "channel": "dev"}, {"timestamp": "2012-03-29 23:53:38.806929", "version": "19.0.1084.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-03-28 20:19:37.578118", "version": "19.0.1081.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-03-23 22:11:16.748494", "version": "19.0.1077.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-03-15 15:39:24.417446", "version": "19.0.1068.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-03-13 23:14:16.841667", "version": "19.0.1068.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-03-07 02:29:03.087025", "version": "19.0.1061.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-29 00:35:00.361479", "version": "19.0.1055.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-24 00:15:03.802307", "version": "19.0.1049.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-15 01:26:52.693277", "version": "19.0.1041.0", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-10 22:28:43.002042", "version": "19.0.1036.7", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-08 02:11:36.342233", "version": "18.0.1025.7", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-03 02:05:01.992245", "version": "18.0.1025.3", "os": "win", "channel": "dev"}, {"timestamp": "2012-02-01 00:50:43.302643", "version": "18.0.1025.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-01-25 01:52:32.852157", "version": "18.0.1017.2", "os": "win", "channel": "dev"}, {"timestamp": "2012-01-18 02:18:21.471775", "version": "18.0.1010.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-01-11 04:43:37.274858", "version": "18.0.1003.1", "os": "win", "channel": "dev"}, {"timestamp": "2012-01-05 00:02:51.015231", "version": "17.0.963.26", "os": "win", "channel": "dev"}, {"timestamp": "2011-12-16 02:56:07.709380", "version": "17.0.963.12", "os": "win", "channel": "dev"}, {"timestamp": "2011-12-13 00:20:16.966016", "version": "17.0.963.6", "os": "win", "channel": "dev"}, {"timestamp": "2011-12-09 00:45:52.579935", "version": "17.0.963.2", "os": "win", "channel": "dev"}, {"timestamp": "2011-12-06 23:45:11.121224", "version": "17.0.963.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-11-18 00:13:13.547951", "version": "17.0.942.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-11-15 00:36:36.682142", "version": "17.0.938.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-11-08 00:01:54.107311", "version": "17.0.932.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-11-03 23:38:13.871933", "version": "17.0.928.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-11-01 23:05:53.469821", "version": "16.0.912.21", "os": "win", "channel": "dev"}, {"timestamp": "2011-10-27 23:08:29.273571", "version": "16.0.912.15", "os": "win", "channel": "dev"}, {"timestamp": "2011-10-26 00:33:00.645906", "version": "16.0.912.12", "os": "win", "channel": "dev"}, {"timestamp": "2011-10-20 23:31:54.410987", "version": "16.0.912.4", "os": "win", "channel": "dev"}, {"timestamp": "2011-10-18 23:21:28.765917", "version": "16.0.912.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-10-10 23:51:01.411375", "version": "16.0.904.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-10-04 00:00:02.368667", "version": "16.0.899.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-27 00:01:19.501162", "version": "16.0.891.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-23 00:00:04.600297", "version": "16.0.889.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-21 00:29:45.279457", "version": "15.0.874.21", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-15 23:35:15.396772", "version": "15.0.874.15", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-13 23:00:02.220450", "version": "15.0.874.12", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-09 01:15:01.462271", "version": "15.0.874.5", "os": "win", "channel": "dev"}, {"timestamp": "2011-09-07 23:28:26.943667", "version": "15.0.874.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-30 00:53:44.167535", "version": "15.0.865.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-24 23:45:01.557603", "version": "15.0.861.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-17 17:50:57.766559", "version": "15.0.854.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-16 23:32:55.862838", "version": "15.0.849.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-10 23:45:12.449065", "version": "14.0.835.35", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-08 23:39:27.586298", "version": "14.0.835.29", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-05 00:20:22.656768", "version": "14.0.835.18", "os": "win", "channel": "dev"}, {"timestamp": "2011-08-01 23:30:39.166312", "version": "14.0.835.15", "os": "win", "channel": "dev"}, {"timestamp": "2011-07-29 00:07:02.915730", "version": "14.0.835.8", "os": "win", "channel": "dev"}, {"timestamp": "2011-07-27 00:29:22.495411", "version": "14.0.835.2", "os": "win", "channel": "dev"}, {"timestamp": "2011-07-19 00:18:39.510135", "version": "14.0.825.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-07-12 00:19:19.815197", "version": "14.0.814.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-28 00:25:51.373550", "version": "14.0.803.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-20 23:31:21.882224", "version": "14.0.797.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-16 23:24:40.782210", "version": "14.0.794.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-16 00:50:32.462022", "version": "13.0.782.24", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-14 01:22:42.462647", "version": "13.0.782.20", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-10 00:54:02.498892", "version": "13.0.782.14", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-08 21:28:42.336949", "version": "13.0.782.13", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-08 00:42:42.523798", "version": "13.0.782.11", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-06 05:45:01.690681", "version": "13.0.782.10", "os": "win", "channel": "dev"}, {"timestamp": "2011-06-02 00:18:23.923209", "version": "13.0.782.1", "os": "win", "channel": "dev"}, {"timestamp": "2011-05-24 00:23:25.417993", "version": "13.0.772.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-05-17 21:36:01.649909", "version": "13.0.767.1", "os": "win", "channel": "dev"}, {"timestamp": "2011-05-12 21:30:02.816615", "version": "13.0.761.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-05-07 00:26:42.381502", "version": "12.0.742.30", "os": "win", "channel": "dev"}, {"timestamp": "2011-05-06 00:50:01.184756", "version": "12.0.742.21", "os": "win", "channel": "dev"}, {"timestamp": "2011-05-03 01:32:45.837483", "version": "12.0.742.16", "os": "win", "channel": "dev"}, {"timestamp": "2011-04-29 00:47:01.978221", "version": "12.0.742.12", "os": "win", "channel": "dev"}, {"timestamp": "2011-04-26 00:48:15.791589", "version": "12.0.742.9", "os": "win", "channel": "dev"}, {"timestamp": "2011-04-22 21:52:40.866321", "version": "12.0.742.5", "os": "win", "channel": "dev"}, {"timestamp": "2011-04-21 01:07:21.274640", "version": "12.0.742.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-04-13 00:18:03.352933", "version": "12.0.733.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-04-06 01:25:31.730658", "version": "12.0.725.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-24 23:56:50.353141", "version": "12.0.712.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-21 23:12:14.929546", "version": "11.0.696.16", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-17 23:35:19.195017", "version": "11.0.696.14", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-16 00:37:36.670682", "version": "11.0.696.12", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-11 02:25:22.766989", "version": "11.0.696.3", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-09 02:42:49.254367", "version": "11.0.696.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-03 22:40:08.703552", "version": "11.0.686.3", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-01 20:35:16.946853", "version": "11.0.686.1", "os": "win", "channel": "dev"}, {"timestamp": "2011-03-01 02:36:12.261887", "version": "11.0.686.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-02-17 23:15:13.205432", "version": "11.0.672.2", "os": "win", "channel": "dev"}, {"timestamp": "2011-02-16 23:02:09.686153", "version": "10.0.648.82", "os": "win", "channel": "dev"}, {"timestamp": "2011-02-09 01:08:19.428149", "version": "10.0.648.45", "os": "win", "channel": "dev"}, {"timestamp": "2011-02-04 01:31:33.914043", "version": "10.0.648.18", "os": "win", "channel": "dev"}, {"timestamp": "2011-02-01 04:02:58.551081", "version": "10.0.648.11", "os": "win", "channel": "dev"}, {"timestamp": "2011-01-27 01:58:03.833503", "version": "10.0.648.6", "os": "win", "channel": "dev"}, {"timestamp": "2011-01-20 20:34:10.485114", "version": "10.0.642.2", "os": "win", "channel": "dev"}, {"timestamp": "2011-01-11 20:29:22.440018", "version": "10.0.634.0", "os": "win", "channel": "dev"}, {"timestamp": "2011-01-06 06:16:24.266016", "version": "10.0.628.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-20 19:43:27.003393", "version": "10.0.612.3", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-16 23:09:02.613656", "version": "10.0.612.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-14 01:51:30.028630", "version": "9.0.597.19", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-10 17:40:35.218863", "version": "9.0.597.16", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-10 02:13:57.432350", "version": "9.0.597.15", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-07 02:02:57.524983", "version": "9.0.597.10", "os": "win", "channel": "dev"}, {"timestamp": "2010-12-01 22:58:13.664530", "version": "9.0.597.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-11-19 01:08:11.505390", "version": "9.0.587.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-11-10 00:46:46.659929", "version": "9.0.576.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-11-05 01:20:03.009405", "version": "9.0.570.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-11-03 01:02:56.557176", "version": "9.0.570.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-10-30 01:11:40.679688", "version": "8.0.552.23", "os": "win", "channel": "dev"}, {"timestamp": "2010-10-27 01:07:18.524939", "version": "8.0.552.18", "os": "win", "channel": "dev"}, {"timestamp": "2010-10-21 23:21:07.362399", "version": "8.0.552.11", "os": "win", "channel": "dev"}, {"timestamp": "2010-10-19 03:29:35.380392", "version": "8.0.552.5", "os": "win", "channel": "dev"}, {"timestamp": "2010-10-13 00:03:39.843245", "version": "8.0.552.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-10-06 22:51:53.761849", "version": "7.0.544.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-09-30 20:34:16.449689", "version": "7.0.536.2", "os": "win", "channel": "dev"}, {"timestamp": "2010-09-28 22:49:52.102607", "version": "7.0.517.24", "os": "win", "channel": "dev"}, {"timestamp": "2010-09-24 23:15:18.317542", "version": "7.0.517.17", "os": "win", "channel": "dev"}, {"timestamp": "2010-09-17 01:02:24.580776", "version": "7.0.517.8", "os": "win", "channel": "dev"}, {"timestamp": "2010-09-13 21:15:08.408742", "version": "7.0.517.5", "os": "win", "channel": "dev"}, {"timestamp": "2010-09-09 01:03:59.649612", "version": "7.0.517.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-08-26 00:05:03.192544", "version": "7.0.503.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-08-17 22:28:45.281781", "version": "6.0.495.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-08-13 14:23:58.981512", "version": "6.0.490.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-08-06 22:53:12.693314", "version": "6.0.472.25", "os": "win", "channel": "dev"}, {"timestamp": "2010-08-05 00:22:02.989672", "version": "6.0.472.22", "os": "win", "channel": "dev"}, {"timestamp": "2010-07-30 23:14:34.906503", "version": "6.0.472.14", "os": "win", "channel": "dev"}, {"timestamp": "2010-07-28 23:49:30.339032", "version": "6.0.472.11", "os": "win", "channel": "dev"}, {"timestamp": "2010-07-22 00:09:53.631080", "version": "6.0.472.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-07-15 22:58:14.785376", "version": "6.0.466.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-07-09 14:18:49.147870", "version": "6.0.458.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-07-02 23:12:13.572119", "version": "6.0.453.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-06-25 01:37:06.165522", "version": "6.0.447.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-06-18 19:49:04.312351", "version": "6.0.437.3", "os": "win", "channel": "dev"}, {"timestamp": "2010-06-17 23:02:50.189680", "version": "6.0.427.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-06-17 18:31:39.466049", "version": "6.0.437.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-06-03 18:37:41.545861", "version": "6.0.422.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-05-20 21:03:10.341977", "version": "6.0.408.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-05-14 03:19:25.209091", "version": "6.0.401.1", "os": "win", "channel": "dev"}, {"timestamp": "2010-05-07 01:36:38.203980", "version": "5.0.396.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-05-04 01:31:54.330134", "version": "5.0.375.29", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-30 22:08:37.591885", "version": "5.0.375.28", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-27 20:56:13.817069", "version": "5.0.375.23", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-23 00:30:19.613747", "version": "5.0.375.17", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-17 01:18:06.570807", "version": "5.0.375.9", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-14 23:39:10.591308", "version": "5.0.375.7", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-13 01:11:37.789960", "version": "5.0.375.3", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-09 01:12:39.670401", "version": "5.0.371.0", "os": "win", "channel": "dev"}, {"timestamp": "2010-04-02 18:16:57.406165", "version": "5.0.366.2", "os": "win", "channel": "dev"}] \ No newline at end of file
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json b/chromium/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json
deleted file mode 100644
index 0c5d79d9b41..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "extension_api.h": "146163",
- "_permission_features.json": "150186",
- "file_system.idl": "150515",
- "input_ime.json": "149101",
- "context_menus.json": "140112",
- "app.json": "133722",
- "browser_action.json": "148043",
- "test.json": "145505",
- "chromeos_info_private.json": "150737",
- "font_settings.json": "147621",
- "management.json": "151113",
- "bookmark_manager.json": "140864",
- "experimental_commands.json": "149147",
- "web_navigation.json": "148204",
- "echo_private.json": "136747",
- "i18n.json": "149045",
- "storage.json": "149837",
- "experimental_media_galleries.idl": "150164",
- "web_request_internal.json": "138481",
- "app_window.idl": "147710",
- "web_request.json": "138481",
- "windows.json": "140947",
- "terminal_private.json": "136747",
- "experimental_rlz.json": "136747",
- "system_private.json": "136747",
- "content_settings.json": "136747",
- "file_browser_handler.json": "149101",
- "devtools.json": "124878",
- "experimental_dns.idl": "136747",
- "experimental_speech_input.json": "136747",
- "api.gyp": "151113",
- "experimental_identity.idl": "147523",
- "runtime.json": "148956",
- "alarms.idl": "143049",
- "declarative_web_request.json": "150421",
- "devtools/": "149291",
- "experimental_idltest.idl": "138707",
- "script_badge.json": "146930",
- "extension_api_unittest.cc": "149819",
- "events.json": "143168",
- "tts.json": "134209",
- "metrics_private.json": "136747",
- "experimental_usb.idl": "144221",
- "webstore.json": "127930",
- "managed_mode_private.json": "142720",
- "downloads.idl": "146201",
- "serial.idl": "150186",
- "page_capture.json": "147890",
- "_manifest_features.json": "150827",
- "browsing_data.json": "142133",
- "top_sites.json": "136747",
- "page_actions.json": "136747",
- "page_action.json": "137065",
- "history.json": "129086",
- "bookmarks.json": "149307",
- "permissions.json": "124878",
- "experimental_app.json": "148848",
- "media_player_private.json": "136747",
- "experimental_input_virtual_keyboard.json": "138223",
- "privacy.json": "136588",
- "tabs.json": "149631",
- "tts_engine.json": "136747",
- "experimental_bluetooth.idl": "150898",
- "idle.json": "124878",
- "input_method_private.json": "136747",
- "experimental_accessibility.json": "136747",
- "push_messaging.idl": "149536",
- "webstore_private.json": "150455",
- "extension.json": "146855",
- "extension_api.cc": "149819",
- "file_manager_private.json": "150806",
- "cloud_print_private.json": "149303",
- "omnibox.json": "124878",
- "processes.json": "137690",
- "proxy.json": "136588",
- "file_browser_handler_internal.json": "142683",
- "types.json": "146093",
- "experimental_record.json": "145342",
- "debugger.json": "147894",
- "socket.idl": "150190",
- "OWNERS": "150079",
- "cookies.json": "124878",
- "infobars.json": "136747",
- "wallpaper_private.json": "148400"
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json b/chromium/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json
deleted file mode 100644
index eee9d7f4f26..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "": [
- ".gitignore",
- "README.md",
- "analytics/",
- "appsquare/",
- "browser-tag/",
- "calculator/",
- "camera-capture/",
- "clock/",
- "context-menu/",
- "diff/",
- "dojo/",
- "filesystem-access/",
- "frameless-window/",
- "gdocs/",
- "hello-world/",
- "identity/",
- "io2012-presentation/",
- "ioio/",
- "mdns-browser/",
- "mini-code-edit/",
- "nodejs-net.coffee",
- "sandbox/",
- "sandboxed-content/",
- "serial-control-signals/",
- "serial/",
- "servo/",
- "singleton/",
- "storage/",
- "telnet/",
- "text-editor/",
- "udp/",
- "usb/",
- "weather/",
- "webgl/",
- "webintents/",
- "windows/",
- "zephyr_hxm/"
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/rietveld_patcher/expected/extensions_sidenav.json b/chromium/chrome/common/extensions/docs/server2/test_data/rietveld_patcher/expected/extensions_sidenav.json
deleted file mode 100644
index e5c4cc2e831..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/rietveld_patcher/expected/extensions_sidenav.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {
- "title": "Test Foo",
- "fileName": "test_foo.html"
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json b/chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json
deleted file mode 100644
index 904e5d809b4..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "api_calls": [
- { "name": "bobaloo.foop" },
- { "name": "jimmy.bobaloo.loopoo" },
- { "name": "jimmy.jimmy" },
- { "name": "hallo.bobaloo" }
- ]
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json b/chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json
deleted file mode 100644
index 4c0152718c1..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json
+++ /dev/null
@@ -1,24 +0,0 @@
-[
- {
- "api_calls": [
- { "name": "bobaloo.foop" },
- { "name": "jimmy.bobaloo.loopoo" },
- { "name": "jimmy.jimmy" },
- { "name": "hallo.bobaloo" }
- ]
- },
- {
- "api_calls": [
- { "name": "timmy.bobaloo.affa" },
- { "name": "jimmy.baloo.loopoo" },
- { "name": "jimmy.jimmy" }
- ]
- },
- {
- "api_calls": [
- { "name": "torta.lamb.paco" },
- { "name": "jimmy.baloo.loopoo" },
- { "name": "jimmy.jimmy" }
- ]
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json b/chromium/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json
deleted file mode 100644
index e089d8b493e..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": {
- "first": "Bob",
- "last": "Jones"
- },
- "part": "elbow"
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/add_rules_def_test.json b/chromium/chrome/common/extensions/docs/server2/test_data/test_json/add_rules_def_test.json
deleted file mode 100644
index 01fbab60cc8..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/add_rules_def_test.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "namespace": "events",
- "description": "A namespace to contain addRules.",
- "types": [
- {
- "name": "Event",
- "type": "object",
- "description": "The object to contain addRules.",
- "functions": [
- {
- "name": "addRules",
- "type": "function",
- "description": "Registers rules to handle events.",
- "parameters": [ { "name": "notable_name_to_check_for" } ]
- }
- ]
- }
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/expected_tester.json b/chromium/chrome/common/extensions/docs/server2/test_data/test_json/expected_tester.json
deleted file mode 100644
index 4c91dfd8da8..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/expected_tester.json
+++ /dev/null
@@ -1,218 +0,0 @@
-{
- "functions": [
- {
- "name": "get",
- "parameters": [
- {
- "parentName": "get",
- "functions": [],
- "choices": [
- {
- "properties": [],
- "name": "string",
- "simple_type": "string",
- "functions": [],
- "events": [],
- "id": "type-a-string",
- "description": null
- },
- {
- "properties": [],
- "last": true,
- "name": "array",
- "functions": [],
- "events": [],
- "array": {
- "properties": [],
- "name": "arrayType",
- "simple_type": "string",
- "functions": [],
- "events": [],
- "id": "type-array-arrayType",
- "description": null
- },
- "id": "type-a-array",
- "description": null
- }
- ],
- "name": "a",
- "parameters": [],
- "id": "property-get-a",
- "returns": null,
- "optional": null,
- "properties": [],
- "description": "a param"
- },
- {
- "parentName": "get",
- "last": true,
- "name": "callback",
- "simple_type": "function",
- "optional": false,
- "id": "property-get-callback",
- "description": null
- }
- ],
- "callback": {
- "parameters": [
- {
- "description": null,
- "array": {
- "properties": [],
- "name": "resultsType",
- "functions": [],
- "events": [],
- "link": {
- "text": "TypeA",
- "href": "tester.html#type-TypeA",
- "name": "TypeA"
- },
- "id": "type-results-resultsType",
- "description": null
- },
- "optional": null,
- "id": "property-callback-results",
- "parentName": "callback",
- "functions": [],
- "last": true,
- "name": "results",
- "parameters": [],
- "properties": [],
- "returns": null
- }
- ],
- "optional": false,
- "name": "callback",
- "simple_type": {
- "simple_type": "function"
- }
- },
- "returns": null,
- "id": "method-get",
- "description": "Gets stuff."
- }
- ],
- "properties": [],
- "name": "tester",
- "description": "a test api",
- "introList": [
- {
- "content": [
- {
- "text": "a test api"
- }
- ],
- "title": "Description"
- },
- {
- "content": [
- {
- "message": true,
- "trunk": true,
- "version": null
- }
- ],
- "title": "Availability"
- },
- {
- "content": [
- {
- "text": "\"thing1\", \"thing2\"",
- "perm": "tester"
- },
- {
- "text": "is an API for testing things."
- }
- ],
- "title": "Permissions"
- },
- {
- "content": [
- {
- "text": "Welcome!",
- "link": "https://tester.test.com/welcome.html"
- }
- ],
- "title": "Learn More"
- }
- ],
- "types": [
- {
- "properties": [
- {
- "parentName": "TypeA",
- "functions": [],
- "name": "b",
- "parameters": [],
- "id": "property-TypeA-b",
- "array": {
- "properties": [],
- "name": "bType",
- "functions": [],
- "events": [],
- "link": {
- "text": "TypeA",
- "href": "tester.html#type-TypeA",
- "name": "TypeA"
- },
- "id": "type-b-bType",
- "description": null
- },
- "returns": null,
- "optional": true,
- "properties": [],
- "description": "List of TypeA."
- }
- ],
- "name": "TypeA",
- "simple_type": "object",
- "functions": [],
- "events": [],
- "id": "type-TypeA",
- "description": "A cool thing."
- }
- ],
- "events": [
- {
- "callback": null,
- "name": "EventA",
- "parameters": [
- {
- "parentName": "EventA",
- "functions": [],
- "simple_type": "string",
- "name": "id",
- "parameters": [],
- "id": "property-EventA-id",
- "returns": null,
- "optional": null,
- "properties": [],
- "description": null
- },
- {
- "description": null,
- "link": {
- "text": "TypeA",
- "href": "tester.html#type-TypeA",
- "name": "TypeA"
- },
- "optional": null,
- "id": "property-EventA-bookmark",
- "parentName": "EventA",
- "functions": [],
- "last": true,
- "name": "bookmark",
- "parameters": [],
- "properties": [],
- "returns": null
- }
- ],
- "supportsRules": false,
- "filters": [],
- "conditions": [],
- "id": "event-EventA",
- "actions": [],
- "description": "A cool event."
- }
- ]
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json b/chromium/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json
deleted file mode 100644
index 11216091a63..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "ref_test": {
- "types": [
- { "name": "type1" },
- { "name": "type2" },
- { "name": "type3" }
- ],
- "events": [
- { "name": "event1" }
- ],
- "properties": [
- { "name": "prop1" }
- ],
- "functions": [
- { "name": "func1" }
- ]
- },
- "other": {
- "types": [
- { "name": "type2" }
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json b/chromium/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json
deleted file mode 100644
index 13bd52a48b0..00000000000
--- a/chromium/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "tester": {
- "types": [
- { "name": "TypeA" }
- ],
- "functions": [
- { "name": "get" }
- ],
- "events": [
- { "name": "EventA" }
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/api_availabilities.json b/chromium/chrome/common/extensions/docs/templates/json/api_availabilities.json
deleted file mode 100644
index a55ddba950f..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/api_availabilities.json
+++ /dev/null
@@ -1,51 +0,0 @@
-// Availability for these APIs either can't be accurately found or would take a
-// significant amount of specific logic to determine (devtools).
-// The versions listed here were drawn either from hardcoded HTML intro tables
-// or from the whats_new page.
-{
- "devtools.inspectedWindow": {
- "channel": "stable",
- "version": 18
- },
- "devtools.network": {
- "channel": "stable",
- "version": 18
- },
- "devtools.panels": {
- "channel": "stable",
- "version": 18
- },
- "input.ime": {
- "channel": "stable",
- "version": 21
- },
- "mediaGalleries": {
- "channel": "stable",
- "version": 23
- },
- "notifications": {
- "channel": "stable",
- "version": 28
- },
- "storage": {
- "channel": "stable",
- "version": 20
- },
- "pageCapture": {
- "channel": "stable",
- "version": 18
- },
- "webstore": {
- // Only 'webstorePrivate' is present before version 19.
- "channel": "stable",
- "version": 15
- },
- "webviewTag": {
- "channel": "stable",
- "version": 25
- },
- "webviewTag.WebRequestEventInterface": {
- "channel": "stable",
- "version": 33
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/apps_sidenav.json b/chromium/chrome/common/extensions/docs/templates/json/apps_sidenav.json
deleted file mode 100644
index cf6d4bdea32..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/apps_sidenav.json
+++ /dev/null
@@ -1,278 +0,0 @@
-[
- {
- "title": "Discover",
- "items": [
- {
- "title": "What Are Chrome Apps?",
- "href": "/apps/about_apps"
- }
- ]
- },
- {
- "title": "Develop",
- "items": [
- {
- "title": "Create Your First App",
- "href": "/apps/first_app"
- },
- {
- "title": "Tutorials",
- "toggleable": true,
- "items": [
- {
- "title": "Learn with a Codelab",
- "toggleable": true,
- "items": [
- {
- "title": "About this Codelab",
- "href": "/apps/app_codelab"
- },
- {
- "title": "1 - Set Up Development Environment",
- "href": "/apps/app_codelab1_setup"
- },
- {
- "title": "2 - Create Basic App",
- "href": "/apps/app_codelab2_basic"
- },
- {
- "title": "3 - Create MVC",
- "href": "/apps/app_codelab3_mvc"
- },
- {
- "title": "4 - Save and Fetch Data",
- "href": "/apps/app_codelab5_data"
- },
- {
- "title": "5 - Manage App Lifecycle",
- "href": "/apps/app_codelab6_lifecycle"
- },
- {
- "title": "6 - Access User's Data",
- "href": "/apps/app_codelab7_useridentification"
- },
- {
- "title": "7 - Access Web Resources",
- "href": "/apps/app_codelab8_webresources"
- },
- {
- "title": "8 - Publish App",
- "href": "/apps/app_codelab_10_publishing"
- }
- ]
- },
- {
- "title": "MVC Architecture & Frameworks",
- "toggleable": true,
- "items": [
- {
- "title": "About MVC Architecture",
- "href": "/apps/app_frameworks"
- },
- {
- "title": "Build Apps with AngularJS",
- "href": "/apps/angular_framework"
- },
- {
- "title": "Build Apps with Sencha Ext JS",
- "href": "/apps/sencha_framework"
- },
- {
- "title": "Game Engines",
- "href": "/apps/game_engines"
- }
- ]
- }
- ]
- },
- {
- "title": "Chrome App Concepts",
- "toggleable": true,
- "items": [
- {
- "title": "The Fundamentals",
- "toggleable": true,
- "items": [
- {
- "title": "App Architecture",
- "href": "/apps/app_architecture"
- },
- {
- "title": "App Lifecycle",
- "href": "/apps/app_lifecycle"
- },
- {
- "title": "Offline First",
- "href": "/apps/offline_apps"
- },
- {
- "title": "External Content",
- "href": "/apps/app_external"
- }
- ]
- },
- {
- "title": "Security",
- "toggleable": true,
- "items": [
- {
- "title": "Content Security Policy",
- "href": "/apps/contentSecurityPolicy"
- }
- ]
- },
- {
- "title": "Cloud-Enable Your App",
- "toggleable": true,
- "items": [
- {
- "title": "Cloud Messaging",
- "toggleable": true,
- "items": [
- {
- "title": "About Cloud Messaging",
- "href": "/apps/cloudMessaging"
- },
- {
- "title": "Client Reference",
- "href": "/apps/gcm"
- }
- ]
- },
- {
- "title": "Cloud Storage",
- "toggleable": true,
- "items": [
- {
- "title": "chrome.storage",
- "href": "/apps/storage"
- }
- ]
- },
- {
- "title": "User Authentication",
- "href": "/apps/app_identity"
- }
- ]
- },
- {
- "title": "Store and Retrieve Data",
- "toggleable": true,
- "items": [
- {
- "title": "Storage APIs",
- "href": "/apps/app_storage"
- }
- ]
- },
- {
- "title": "Use Low-Level System Services",
- "toggleable": true,
- "items": [
- {
- "title": "USB",
- "href": "/apps/app_usb"
- },
- {
- "title": "Serial",
- "href": "/apps/app_serial"
- },
- {
- "title": "Network Communications",
- "href": "/apps/app_network"
- },
- {
- "title": "Bluetooth",
- "href": "/apps/app_bluetooth"
- }
- ]
- },
- {
- "title": "Interact with the Host Platform",
- "toggleable": true,
- "items": [
- {
- "title": "Rich Notifications",
- "href": "/apps/desktop_notifications"
- }
- ]
- }
- ]
- }
- ]
- },
- {
- "title": "Distribute",
- "items": [
- {
- "title": "Publish Your App",
- "href": "/apps/publish_app"
- },
- {
- "title": "Monetize Your App",
- "toggleable": true,
- "items": [
- {
- "title": "Google Wallet for Digital Goods",
- "href": "/apps/google_wallet"
- }
- ]
- },
- {
- "title": "Analytics",
- "href": "/apps/analytics"
- }
- ]
- },
- {
- "title": "Reference",
- "items": [
- {
- "title": "Chrome Platform APIs",
- "href": "/apps/api_index"
- },
- {
- "title": "Webview Tag",
- "href": "/apps/tags/webview"
- },
- {
- "title": "Web APIs",
- "href": "/apps/api_other"
- },
- {
- "title": "Manifest File Format",
- "href": "/apps/manifest"
- },
- {
- "title": "Disabled Web Features",
- "href": "/apps/app_deprecated"
- }
- ]
- },
- {
- "title": "Samples",
- "items": [
- {
- "title": "Sample Apps",
- "href": "/apps/samples"
- }
- ]
- },
- {
- "title": "Help",
- "items": [
- {
- "title": "FAQ",
- "href": "/apps/faq"
- },
- {
- "title": "Google Groups",
- "href": "https://groups.google.com/a/chromium.org/forum/#!forum/chromium-apps"
- },
- {
- "title": "Stack Overflow",
- "href": "http://stackoverflow.com/questions/tagged/google-chrome-app"
- }
- ]
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/templates/json/chrome_sidenav.json b/chromium/chrome/common/extensions/docs/templates/json/chrome_sidenav.json
deleted file mode 100644
index 8c607a44a13..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/chrome_sidenav.json
+++ /dev/null
@@ -1,834 +0,0 @@
-[
- {
- "title": "Chrome",
- "href": "/extensions",
- "items": [
- {
- "title": "Extend the Browser",
- "href": "/extensions",
- "items": [
- {
- "title": "What are Extensions?",
- "href": "/extensions"
- },
- {
- "title": "Get Started Tutorial",
- "href": "/extensions/getstarted"
- },
- {
- "title": "Overview",
- "href": "/extensions/overview",
- "items": [
- {
- "title": "Manifest Format",
- "href": "/extensions/manifest"
- },
- {
- "title": "Manage Events",
- "href": "/extensions/background_pages"
- },
- {
- "title": "Design User Interface",
- "href": "/extensions/user_interface"
- },
- {
- "title": "Content Scripts",
- "href": "/extensions/content_scripts"
- },
- {
- "title": "Declare Permissions and Warn Users",
- "href": "/extensions/permission_warnings"
- },
- {
- "title": "Give Users Options",
- "href": "/extensions/options"
- }
- ]
- },
- {
- "title": "Developer Guide",
- "href": "/extensions/devguide",
- "items": [
- {
- "title": "Reach Peak Performance",
- "href": "/extensions/performance"
- },
- {
- "title": "Protect User Privacy",
- "href": "/extensions/user_privacy"
- },
- {
- "title": "Stay Secure",
- "href": "/extensions/security"
- },
- {
- "title": "Debugging",
- "href": "/extensions/tut_debugging"
- },
- {
- "title": "OAuth",
- "href": "/extensions/tut_oauth"
- },
- {
- "title": "Accessibility",
- "href": "/extensions/a11y"
- },
- {
- "title": "Content Security Policy",
- "href": "/extensions/contentSecurityPolicy"
- },
- {
- "title": "Cross-Origin XHR",
- "href": "/extensions/xhr"
- },
- {
- "title": "Internationalization",
- "href": "/extensions/i18n"
- },
- {
- "title": "Message Passing",
- "href": "/extensions/messaging"
- },
- {
- "title": "Native Messaging",
- "href": "/extensions/nativeMessaging"
- },
- {
- "title": "Match Patterns",
- "href": "/extensions/match_patterns"
- },
- {
- "title": "Extension Quality Guidelines FAQ",
- "href": "/extensions/single_purpose"
- }
- ]
- },
- {
- "title": "Samples",
- "href": "/extensions/samples"
- },
- {
- "title": "Help",
- "href": "/extensions/faq",
- "items": [
- {
- "title": "FAQ",
- "href": "/extensions/faq"
- },
- {
- "title": "What's New?",
- "href": "/extensions/whats_new"
- },
- {
- "title": "Google Groups",
- "href": "https://groups.google.com/a/chromium.org/forum/#!forum/chromium-extensions"
- },
- {
- "title": "Stack Overflow",
- "href": "http://stackoverflow.com/tags/google-chrome-extension/info"
- }
- ]
- }
- ]
- },
- {
- "title": "Web Store Publishing and Distribution",
- "href": "/webstore",
- "items": [
- {
- "title": "Distribution Overview",
- "href": "/webstore",
- "items": [
- {
- "title": "Distributing Products Built for Chrome",
- "href": "/webstore"
- },
- {
- "title": "What Is the Chrome Web Store?",
- "href": "/webstore/about_webstore"
- },
- {
- "title": "What Can You Publish?",
- "href": "/webstore/overview"
- },
- {
- "title": "Tutorial: Getting Started",
- "href": "/webstore/get_started_simple"
- },
- {
- "title": "Samples",
- "href": "/webstore/samples"
- }
- ]
- },
- {
- "title": "Publish Extensions",
- "href": "/extensions/hosting",
- "items": [
- {
- "title": "Hosting and Updating",
- "href": "/extensions/hosting"
- },
- {
- "title": "Hosting Policy Changes",
- "href": "/extensions/hosting_changes"
- },
- {
- "title": "Google Analytics",
- "href": "/extensions/tut_analytics"
- },
- {
- "title": "Publishing Themes",
- "href": "/extensions/themes"
- },
- {
- "title": "Other Deployment Options",
- "href": "/extensions/external_extensions"
- }
- ]
- },
- {
- "title": "Monetizing",
- "href": "/webstore/money",
- "items": [
- {
- "title": "Monetizing Your App",
- "href": "/webstore/money"
- },
- {
- "title": "Using Google Accounts",
- "href": "/webstore/identify_user"
- },
- {
- "title": "One-Time Payments",
- "href": "/webstore/one_time_payments"
- },
- {
- "title": "Pricing",
- "href": "/webstore/pricing"
- }
- ]
- },
- {
- "title": "Branding",
- "href": "/webstore/branding",
- "items": [
- {
- "title": "Branding Guidelines",
- "href": "/webstore/branding"
- },
- {
- "title": "Supplying Images",
- "href": "/webstore/images"
- }
- ]
- },
- {
- "title": "Help",
- "href": "/webstore/best_practices",
- "items": [
- {
- "title": "Best Practices",
- "href": "/webstore/best_practices"
- },
- {
- "title": "FAQ",
- "href": "/webstore/faq"
- },
- {
- "title": "Stack Overflow",
- "href": "http://stackoverflow.com/questions/tagged/google-chrome-app"
- },
- {
- "title": "Articles",
- "href": "/webstore/articles"
- }
- ]
- }
- ]
- },
- {
- "title": "Mobile Chrome",
- "href": "/multidevice",
- "items": [
- {
- "title": "Chrome for a Multi-Device World",
- "href": "/multidevice",
- "items": [
- {
- "title": "User Agents",
- "href": "/multidevice/user-agent"
- },
- {
- "title": "Chrome Custom Tabs",
- "href": "/multidevice/android/customtabs"
- },
- {
- "title": "Mobile Emulation",
- "href": "/devtools/docs/mobile-emulation"
- },
- {
- "title": "Remote Debugging",
- "href": "/devtools/docs/remote-debugging"
- }
- ]
- },
- {
- "title": "Chrome for Android",
- "href": "/multidevice/android/overview",
- "items": [
- {
- "title": "Overview",
- "href": "/multidevice/android/overview"
- },
- {
- "title": "Android Intents with Chrome",
- "href": "/multidevice/android/intents"
- },
- {
- "title": "Chrome Custom Tabs",
- "href": "/multidevice/android/customtabs"
- }
- ]
- },
- {
- "title": "Chrome WebView",
- "href": "/multidevice/webview/overview",
- "items": [
- {
- "title": "WebView for Android",
- "href": "/multidevice/webview/overview"
- },
- {
- "title": "Getting Started",
- "href": "/multidevice/webview/gettingstarted"
- },
- {
- "title": "Pixel-Perfect UI",
- "href": "/multidevice/webview/pixelperfect"
- },
- {
- "title": "WebView Workflow",
- "href": "/multidevice/webview/workflow"
- },
- {
- "title": "Tips & Tricks",
- "href": "/multidevice/webview/tipsandtricks"
- }
- ]
- },
- {
- "title": "Chrome for iOS",
- "href": "/multidevice/ios/links",
- "items": [
- {
- "title": "Opening Links in Chrome",
- "href": "/multidevice/ios/links"
- },
- {
- "title": "User Agent",
- "href": "/multidevice/user-agent#chrome_for_ios_user_agent"
- }
- ]
- },
- {
- "title": "FAQ",
- "href": "/multidevice/faq"
- }
- ]
- }
- ]
- },
- {
- "title": "Chrome OS",
- "items": [
- {
- "title": "Apps",
- "href": "/apps/about_apps",
- "items": [
- {
- "title": "Learn Basics",
- "href": "/apps/about_apps",
- "items": [
- {
- "title": "What Are Chrome Apps?",
- "href": "/apps/about_apps"
- },
- {
- "title": "Extension Quality Guidelines FAQ",
- "href": "/extensions/single_purpose"
- },
- {
- "title": "Create Your First App",
- "href": "/apps/first_app"
- },
- {
- "title": "App Architecture",
- "href": "/apps/app_architecture"
- },
- {
- "title": "App Lifecycle",
- "href": "/apps/app_lifecycle"
- },
- {
- "title": "Content Security Policy",
- "href": "/apps/contentSecurityPolicy"
- }
- ]
- },
- {
- "title": "Learn with Codelab",
- "href": "/apps/app_codelab_intro",
- "items": [
- {
- "title": "Intro - Build Chrome App",
- "href": "/apps/app_codelab_intro"
- },
- {
- "title": "1 - Create and run a Chrome App",
- "href": "/apps/app_codelab_basics"
- },
- {
- "title": "2 - Import an existing web app",
- "href": "/apps/app_codelab_import_todomvc"
- },
- {
- "title": "3 - Add alarms and notifications",
- "href": "/apps/app_codelab_alarms"
- },
- {
- "title": "4 - Open external links with a webview",
- "href": "/apps/app_codelab_webview"
- },
- {
- "title": "5 - Add images from the web",
- "href": "/apps/app_codelab_images"
- },
- {
- "title": "6 - Export todos to the filesystem",
- "href": "/apps/app_codelab_filesystem"
- },
- {
- "title": "7 - Publish your app",
- "href": "/apps/app_codelab_publish"
- }
- ]
- },
- {
- "title": "Run Chrome Apps on Mobile",
- "href": "/apps/chrome_apps_on_mobile"
- },
- {
- "title": "Samples",
- "href": "/apps/samples"
- },
- {
- "title": "Develop in the Cloud",
- "href": "/apps/offline_apps",
- "items": [
- {
- "title": "Offline First",
- "href": "/apps/offline_apps"
- },
- {
- "title": "Handling External Content",
- "href": "/apps/app_external"
- },
- {
- "title": "Storing Data",
- "href": "/apps/app_storage"
- },
- {
- "title": "Managing Offline Storage",
- "href": "/apps/offline_storage"
- },
- {
- "title": "Native Messaging",
- "href": "/apps/nativeMessaging"
- },
- {
- "title": "Rich Notifications",
- "href": "/apps/richNotifications"
- },
- {
- "title": "User Authentication",
- "href": "/apps/app_identity"
- }
- ]
- },
- {
- "title": "User Low-Level System Services",
- "href": "/apps/app_usb",
- "items": [
- {
- "title": "USB",
- "href": "/apps/app_usb"
- },
- {
- "title": "Serial",
- "href": "/apps/app_serial"
- },
- {
- "title": "Network Communications",
- "href": "/apps/app_network"
- },
- {
- "title": "Bluetooth",
- "href": "/apps/app_bluetooth"
- }
- ]
- },
- {
- "title": "MVC Architecture & Frameworks",
- "href": "/apps/app_frameworks",
- "items": [
- {
- "title": "About MVC Architecture",
- "href": "/apps/app_frameworks"
- },
- {
- "title": "Build Apps with AngularJS",
- "href": "/apps/angular_framework"
- },
- {
- "title": "Build Apps with SenchaJS",
- "href": "/apps/sencha_framework"
- },
- {
- "title": "Game Engines",
- "href": "/apps/game_engines"
- }
- ]
- },
- {
- "title": "Distribute Apps",
- "href": "/apps/publish_app",
- "items": [
- {
- "title": "Publish Your App",
- "href": "/apps/publish_app"
- },
- {
- "title": "Monetize Your App",
- "href": "/webstore/payments-iap"
- },
- {
- "title": "One-Time Payments",
- "href": "/webstore/one_time_payments"
- },
- {
- "title": "Analytics",
- "href": "/apps/analytics"
- }
- ]
- },
- {
- "title": "Chrome Platform APIs",
- "href": "/apps/manifest",
- "items": [
- {
- "title": "Manifest File Format",
- "href": "/apps/manifest"
- },
- {
- "title": "Webview Tag",
- "href": "/apps/tags/webview"
- },
- {
- "title": "Appview Tag",
- "href": "/apps/tags/appview"
- },
- {
- "title": "Web APIs",
- "href": "/apps/api_other"
- },
- {
- "title": "Disabled Web Features",
- "href": "/apps/app_deprecated"
- }
- ]
- },
- {
- "title": "Help",
- "href": "/apps/faq",
- "items": [
- {
- "title": "FAQ",
- "href": "/apps/faq"
- },
- {
- "title": "Stack Overflow",
- "href": "http://stackoverflow.com/questions/tagged/google-chrome-app"
- }
- ]
- }
- ]
- },
- {
- "title": "Native Client",
- "href": "/native-client",
- "items": [
- {
- "title": "Learn Basics",
- "href": "/native-client",
- "items": [
- {
- "title": "Technical Overview",
- "href": "/native-client/overview"
- },
- {
- "title": "NaCl and PNaCl",
- "href": "/native-client/nacl-and-pnacl"
- }
- ]
- },
- {
- "title": "WebAssembly Migration Guide",
- "href": "/native-client/migration"
- },
- {
- "title": "Download the SDK",
- "href": "/native-client/sdk/download",
- "items": [
- {
- "title": "Download the Native Client SDK",
- "href": "/native-client/sdk/download"
- },
- {
- "title": "Examples",
- "href": "/native-client/sdk/examples"
- },
- {
- "title": "Release Notes",
- "href": "/native-client/sdk/release-notes"
- }
- ]
- },
- {
- "title": "Tutorial",
- "href": "/native-client/devguide/tutorial/tutorial-part1",
- "items": [
- {
- "title": "Part 1: Simple PNaCl Web App",
- "href": "/native-client/devguide/tutorial/tutorial-part1"
- },
- {
- "title": "Part 2: SDK Build System and Chrome Apps",
- "href": "/native-client/devguide/tutorial/tutorial-part2"
- },
- {
- "title": "Chrome Dev Summit 2014 - Codelabs",
- "href": "/native-client/cds2014"
- }
- ]
- },
- {
- "title": "Development Cycle",
- "href": "/native-client/devguide/devcycle/building",
- "items": [
- {
- "title": "Building",
- "href": "/native-client/devguide/devcycle/building"
- },
- {
- "title": "Running",
- "href": "/native-client/devguide/devcycle/running"
- },
- {
- "title": "Debugging",
- "href": "/native-client/devguide/devcycle/debugging"
- },
- {
- "title": "Debugging with Visual Studio",
- "href": "/native-client/devguide/devcycle/vs-addin"
- },
- {
- "title": "Dynamic Linking and Loading with GlibC",
- "href": "/native-client/devguide/devcycle/dynamic-loading"
- }
- ]
- },
- {
- "title": "Coding Your Application",
- "href": "/native-client/devguide/coding/application-structure",
- "items": [
- {
- "title": "Application Structure",
- "href": "/native-client/devguide/coding/application-structure"
- },
- {
- "title": "Native Client Modules",
- "href": "/native-client/devguide/coding/native-client-modules"
- },
- {
- "title": "3D Graphics",
- "href": "/native-client/devguide/coding/3D-graphics"
- },
- {
- "title": "Audio",
- "href": "/native-client/devguide/coding/audio"
- },
- {
- "title": "File I/O",
- "href": "/native-client/devguide/coding/file-io"
- },
- {
- "title": "The nacl_io Library",
- "href": "/native-client/devguide/coding/nacl_io"
- },
- {
- "title": "Messaging System",
- "href": "/native-client/devguide/coding/message-system"
- },
- {
- "title": "Progress Events",
- "href": "/native-client/devguide/coding/progress-events"
- },
- {
- "title": "URL Loading",
- "href": "/native-client/devguide/coding/url-loading"
- },
- {
- "title": "View Change, Focus, & Input Events",
- "href": "/native-client/devguide/coding/view-focus-input-events"
- }
- ]
- },
- {
- "title": "Distribute Your Apps",
- "href": "/native-client/devguide/distributing"
- },
- {
- "title": "Pepper API Reference",
- "href": "/native-client/c-api",
- "items": [
- {
- "title": "Pepper C API (Stable)",
- "href": "/native-client/c-api"
- },
- {
- "title": "Pepper C++ API (Stable)",
- "href": "/native-client/cpp-api"
- },
- {
- "title": "Pepper C API (Beta)",
- "href": "/native-client/c-api-beta"
- },
- {
- "title": "Pepper C++ API (Beta)",
- "href": "/native-client/cpp-api-beta"
- },
- {
- "title": "Pepper C API (Dev)",
- "href": "/native-client/c-api-dev"
- },
- {
- "title": "Pepper C++ API (Dev)",
- "href": "/native-client/cpp-api-dev"
- }
- ]
- },
- {
- "title": "Additional Reference & Versions",
- "href": "/native-client/glossary",
- "items": [
- {
- "title": "Glossary",
- "href": "/native-client/glossary"
- },
- {
- "title": "Contributor Ideas",
- "href": "/native-client/reference/ideas"
- },
- {
- "title": "Native Client Manifest (nmf) Format",
- "href": "/native-client/reference/nacl-manifest-format"
- },
- {
- "title": "Contents of PNaCl Bitcode Files",
- "href": "/native-client/reference/pnacl-bitcode-manual"
- },
- {
- "title": "PNaCl Bitcode Reference Manual",
- "href": "/native-client/reference/pnacl-bitcode-abi"
- },
- {
- "title": "PNaCl Undefined Behavior",
- "href": "/native-client/reference/pnacl-undefined-behavior"
- },
- {
- "title": "PNaCl C/C++ Language Support",
- "href": "/native-client/reference/pnacl-c-cpp-language-support"
- },
- {
- "title": "Sandbox Internals",
- "href": "/native-client/reference/sandbox_internals/index",
- "items": [
- {
- "title": "ARM 32-bit Sandbox",
- "href": "/native-client/reference/sandbox_internals/arm-32-bit-sandbox"
- },
- {
- "title": "x86-64 Sandbox",
- "href": "/native-client/reference/sandbox_internals/x86-64-sandbox"
- }
- ]
- },
- {
- "title": "Design Documents",
- "href": "/native-client/reference/design-docs"
- }
- ]
- },
- {
- "title": "Help",
- "href": "/native-client/faq",
- "items": [
- {
- "title": "FAQ",
- "href": "/native-client/faq"
- },
- {
- "title": "Forums & Issues Tracker",
- "href": "/native-client/help"
- },
- {
- "title": "Publications & Presentations",
- "href": "/native-client/publications-and-presentations"
- },
- {
- "title": "Security Contest Archive",
- "href": "/native-client/community/security-contest/index"
- }
- ]
- }
- ]
- }
- ]
- },
- {
- "title": "Chrome APIs",
- "items": [
- {
- "title": "Extensions APIs",
- "href": "/extensions/api_index",
- "items": [
- {
- "title": "Extension APIs",
- "href": "/extensions/api_index"
- }
- ]
- },
- {
- "title": "Apps APIs",
- "href": "/apps/api_index",
- "items": [
- {
- "title": "Apps APIs",
- "href": "/apps/api_index"
- }
- ]
- }
- ]
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/templates/json/content_providers.json b/chromium/chrome/common/extensions/docs/templates/json/content_providers.json
deleted file mode 100644
index 2b700ce79c8..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/content_providers.json
+++ /dev/null
@@ -1,105 +0,0 @@
-// === Overview ===
-//
-// This file configures where to find and how to serve content in the docserver.
-// It's the most fundamentally important file in all of the docserver.
-//
-// === Format ===
-//
-// Each entry declares a rule with:
-// * An arbitrary identifier key e.g. "cr-extensions-examples".
-// * What URL the rule should be invoked with, given by "serveFrom", e.g.
-// "extensions/examples".
-// * An object describing where the content originates, either "chromium",
-// "github", or "gcs".
-// * "chromium" must provide a "dir" value specifying which chromium directory
-// to look in, e.g. "extensions/samples".
-// * "github" must provide "owner" and "repo" values specifying the owner of
-// the GitHub repository, and the repository name, e.g. "GoogleChrome" and
-// "chrome-app-samples" respectively.
-//
-// In the chromium example, when the user navigates to
-//
-// developer.chrome.com/extensions/examples/some/sample/path
-//
-// then
-// 1. The "cr-extensions-examples" rule is invoked (since it's served from
-// "extensions/examples").
-// 2. The docserver will look up the path "docs/examples/some/sample/path"
-// in chromium - the URL after the "serveFrom" path (some/sample/path)
-// prefixed by the chromium directory (docs/examples).
-// 3. Then render and serve it.
-//
-// === Special properties ===
-//
-// There are some other properties that can be specified:
-// * "supportsZip" indicates whether directories are allowed to be served as
-// zip files. For safety this isn't supported for arbitrary URLs, only those
-// within a rule that has "supportsZip": true.
-// * "supportsTemplates" indicates whether HTML files should be treated and
-// renderered as templates, versus just plain text. Complex documentation
-// which interacts with docserver features (like API listing) need to set
-// this to true. Otherwise, it's safer and more efficient to omit it.
-
-{
- "cr-chrome-docs-home": {
- "chromium": {
- "dir": "chrome/docs"
- },
- "defaultExtensions": [".html", ".md"],
- "serveFrom": "home",
- "supportsTemplates": true
- },
- "cr-extensions-examples": {
- "chromium": {
- "dir": "chrome/common/extensions/docs/examples"
- },
- "serveFrom": "extensions/examples",
- "supportsZip": true
- },
- "cr-public": {
- "chromium": {
- "dir": "chrome/common/extensions/docs/templates/public"
- },
- "defaultExtensions": [".html", ".md"],
- "serveFrom": "",
- "supportsTemplates": true
- },
- "cr-static": {
- "chromium": {
- "dir": "chrome/common/extensions/docs/static"
- },
- "serveFrom": "static"
- },
- "cr-native-client": {
- "chromium": {
- "dir": "native_client_sdk/doc_generated"
- },
- "defaultExtensions": [".html", ".md"],
- "serveFrom": "native-client",
- "supportsTemplates": true
- },
- "devtools-docs": {
- "defaultExtensions": [".html", ".md"],
- "gcs": {
- "bucket": "gs://chromedocs-devtools"
- },
- "serveFrom": "devtools",
- "supportsTemplates": true
- },
- "multidevice-docs": {
- "defaultExtensions": [".html", ".md"],
- "gcs": {
- "bucket": "gs://chromedocs-multidevice"
- },
- "serveFrom": "multidevice",
- "supportsTemplates": true
- },
- "webstore-docs": {
- "defaultExtensions": [".html", ".md"],
- "gcs": {
- "bucket": "gs://chromedocs-webstore"
- },
- "serveFrom": "webstore",
- "supportsTemplates": true
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/extensions_sidenav.json b/chromium/chrome/common/extensions/docs/templates/json/extensions_sidenav.json
deleted file mode 100644
index 75ad47c1053..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/extensions_sidenav.json
+++ /dev/null
@@ -1,222 +0,0 @@
-[
- {
- "title": "Getting Started",
- "href": "/extensions/getstarted"
- },
- {
- "title": "Overview",
- "href": "/extensions/overview"
- },
- {
- "title": "What's New?",
- "href": "/extensions/whats_new"
- },
- {
- "title": "Developer's Guide",
- "href": "/extensions/devguide",
- "items": [
- {
- "title": "Browser UI",
- "toggleable": true,
- "items": [
- {
- "title": "Browser Actions",
- "href": "/extensions/browserAction"
- },
- {
- "title": "Context Menus",
- "href": "/extensions/contextMenus"
- },
- {
- "title": "Desktop Notifications",
- "href": "/extensions/desktop_notifications"
- },
- {
- "title": "Omnibox",
- "href": "/extensions/omnibox"
- },
- {
- "title": "Options Pages",
- "href": "/extensions/options"
- },
- {
- "title": "Override Pages",
- "href": "/extensions/override"
- },
- {
- "title": "Page Actions",
- "href": "/extensions/pageAction"
- }
- ]
- },
- {
- "title": "Browser Interaction",
- "toggleable": true,
- "items": [
- {
- "title": "Bookmarks",
- "href": "/extensions/bookmarks"
- },
- {
- "title": "Cookies",
- "href": "/extensions/cookies"
- },
- {
- "title": "Extending DevTools",
- "href": "/extensions/devtools"
- },
- {
- "title": "Events",
- "href": "/extensions/events"
- },
- {
- "title": "History",
- "href": "/extensions/history"
- },
- {
- "title": "Management",
- "href": "/extensions/management"
- },
- {
- "title": "Tabs",
- "href": "/extensions/tabs"
- },
- {
- "title": "Windows",
- "href": "/extensions/windows"
- }
- ]
- },
- {
- "title": "Implementation",
- "toggleable": true,
- "items": [
- {
- "title": "Accessibility",
- "href": "/extensions/a11y"
- },
- {
- "title": "Event Pages",
- "href": "/extensions/event_pages"
- },
- {
- "title": "Content Security Policy",
- "href": "/extensions/contentSecurityPolicy"
- },
- {
- "title": "Content Scripts",
- "href": "/extensions/content_scripts"
- },
- {
- "title": "Cross-Origin XHR",
- "href": "/extensions/xhr"
- },
- {
- "title": "Internationalization",
- "href": "/extensions/i18n"
- },
- {
- "title": "Message Passing",
- "href": "/extensions/messaging"
- },
- {
- "title": "Optional Permissions",
- "href": "/extensions/permissions"
- },
- {
- "title": "NPAPI Plugins",
- "href": "/extensions/npapi"
- }
- ]
- },
- {
- "title": "Finishing",
- "toggleable": true,
- "items": [
- {
- "title": "Hosting",
- "href": "/extensions/hosting"
- },
- {
- "title": "Other Deployment Options",
- "href": "/extensions/external_extensions"
- }
- ]
- }
- ]
- },
- {
- "title": "Tutorials",
- "href": "/extensions/tutorials",
- "items": [
- {
- "title": "Manifest V2",
- "href": "/extensions/tut_migration_to_manifest_v2"
- },
- {
- "title": "Debugging",
- "href": "/extensions/tut_debugging"
- },
- {
- "title": "Google Analytics",
- "href": "/extensions/tut_analytics"
- },
- {
- "title": "OAuth",
- "href": "/extensions/tut_oauth"
- }
- ]
- },
- {
- "title": "Reference",
- "items": [
- {
- "title": "Formats",
- "toggleable": true,
- "items": [
- {
- "title": "Manifest Files",
- "href": "/extensions/manifest"
- },
- {
- "title": "Match Patterns",
- "href": "/extensions/match_patterns"
- }
- ]
- },
- {
- "title": "Permission Warnings",
- "href": "/extensions/permission_warnings"
- },
- {
- "title": "chrome.* APIs",
- "href": "/extensions/api_index"
- },
- {
- "title": "Other APIs",
- "href": "/extensions/api_other"
- }
- ]
- },
- {
- "title": "More",
- "items": [
- {
- "title": "FAQ",
- "href": "/extensions/faq"
- },
- {
- "title": "Chrome Web Store",
- "href": "http://code.google.com/chrome/webstore/docs/index.html"
- },
- {
- "title": "Hosted Apps",
- "href": "http://code.google.com/chrome/apps/docs/developers_guide.html"
- },
- {
- "title": "Themes",
- "href": "/extensions/themes"
- }
- ]
- }
-]
diff --git a/chromium/chrome/common/extensions/docs/templates/json/intro_tables.json b/chromium/chrome/common/extensions/docs/templates/json/intro_tables.json
deleted file mode 100644
index aae113b3440..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/intro_tables.json
+++ /dev/null
@@ -1,333 +0,0 @@
-{
- "alarms": {
- "Learn More": [
- {
- "link": "event_pages",
- "text": "Event Pages"
- }
- ]
- },
- "app_runtime": {
- "Learn More": [
- {
- "link": "app_lifecycle",
- "text": "Manage App Lifecycle"
- },
- {
- "link": "https://developers.google.com/live/shows/10291095/",
- "text": "Chrome Office Hours: Introduction to Chrome Apps"
- }
- ]
- },
- "app_window": {
- "Learn More": [
- {
- "link": "about_apps#look",
- "text": "How they look"
- },
- {
- "link": "app_lifecycle",
- "text": "Manage App Lifecycle"
- },
- {
- "link": "https://developers.google.com/live/shows/9118010/",
- "text": "Chrome Apps Office Hours: Building Awesome Multi-window Apps"
- }
- ]
- },
- "bluetooth": {
- "Learn More": [
- {
- "link": "app_bluetooth",
- "text": "Bluetooth"
- }
- ]
- },
- "bluetoothLowEnergy": {
- "Learn More": [
- {
- "link": "app_bluetooth.html",
- "text": "Bluetooth"
- }
- ]
- },
- "bluetoothSocket": {
- "Learn More": [
- {
- "link": "app_bluetooth.html",
- "text": "Bluetooth"
- }
- ]
- },
- "cookies": {
- "Permissions": [
- {
- "class": "code",
- "text": "\"cookies\""
- },
- {
- "link": "declare_permissions#host-permissions",
- "text": "host permissions"
- }
- ]
- },
- "declarativeContent": {
- "Learn More": [
- {
- "link": "events",
- "text": "Declarative Events"
- },
- {
- "link": "activeTab",
- "text": "activeTab"
- }
- ]
- },
- "declarativeNetRequest": {
- "Permissions": [
- {
- "class": "code",
- "text": "\"declarativeNetRequest\""
- },
- {
- "link": "declare_permissions#host-permissions",
- "text": "host permissions"
- }
- ]
- },
- "declarativeWebRequest": {
- "Permissions": [
- {
- "class": "code",
- "text": "\"declarativeWebRequest\""
- },
- {
- "link": "declare_permissions#host-permissions",
- "text": "host permissions"
- }
- ]
- },
- "downloads": {
- "Permissions": [
- {
- "class": "code",
- "text": "\"downloads\""
- }
- ]
- },
- "fileSystem": {
- "Permissions": [
- {
- "class": "code",
- "text": "\"fileSystem\""
- },
- {
- "class": "code",
- "text": "{\"fileSystem\": [\"write\"]}"
- },
- {
- "class": "code",
- "text": "{\"fileSystem\": [\"write\", \"retainEntries\", \"directory\"]}"
- }
- ],
- "Learn More": [
- {
- "link": "app_storage",
- "text": "Manage Data"
- },
- {
- "link": "angular_framework",
- "text": "Build Apps with AngularJS"
- },
- {
- "link": "https://developers.google.com/live/shows/7320022-1001/",
- "text": "Chrome Apps Office Hours: TextDrive and AngularJS"
- }
- ]
- },
- "gcm": {
- "Learn More": [
- {
- "link": "https://developers.google.com/cloud-messaging/chrome/client",
- "text": "Implementing GCM Client on Chrome"
- }
- ]
- },
- "identity": {
- "Learn More": [
- {
- "link": "app_identity",
- "text": "Identify User"
- }
- ]
- },
- "mediaGalleries": {
- "Permissions": [
- {
- "class": "code",
- "text": "{\"mediaGalleries\": [\"accessType1\", \"accessType2\", ...]}"
- },
- {
- "class": "code",
- "text": "{\"mediaGalleries\": [\"accessType1\", \"accessType2\", ..., \"allAutoDetected\"]}"
- },
- {
- "partial": "intro_tables/see_manifest.html"
- }
- ],
- "Learn More": [
- {
- "link": "https://developers.google.com/live/shows/10479832/",
- "text": "Chrome Office Hours: The Media Galleries API"
- }
- ]
- },
- "notifications": {
- "Learn More": [
- {
- "link": "richNotifications",
- "text": "Rich Notifications"
- },
- {
- "link": "inform_users",
- "text": "Keep Users Informed"
- },
- {
- "link": "https://developers.google.com/live/shows/83992232-1001/",
- "text": "Chrome Apps Office Hours: Rich Notifications"
- }
- ]
- },
- "permissions": {
- "Learn More": [
- {
- "link": "declare_permissions",
- "text": "Declaring permissions"
- }
- ]
- },
- "runtime": {
- "Learn More": [
- {
- "link": "app_lifecycle",
- "text": "Manage App Lifecycle",
- "extension_types": ["platform_app"]
- },
- {
- "link": "event_pages",
- "text": "Event Pages"
- }
- ]
- },
- "serial": {
- "Learn More": [
- {
- "link": "app_usb",
- "text": "Accessing Hardware Devices"
- }
- ]
- },
- "socket": {
- "Permissions": [
- {
- "class": "code",
- "text": "{\"socket\": [\"rule1\", \"rule2\"]}"
- },
- {
- "partial": "intro_tables/socket_permissions.html"
- }
- ],
- "Learn More": [
- {
- "link": "app_network",
- "text": "Network Communications"
- },
- {
- "link": "sencha_framework",
- "text": "Build Apps with Sencha ExtJS"
- },
- {
- "link": "https://developers.google.com/live/shows/7320022-5001/",
- "text": "Chrome Apps Office Hours: Networking APIs"
- },
- {
- "link": "https://developers.google.com/live/shows/7320022-2001/",
- "text": "Chrome Apps Office Hours: Controlling an AR ParrotDrone"
- }
- ]
- },
- "storage": {
- "Learn More": [
- {
- "link": "https://youtu.be/tmzxMO9lrF0",
- "text": "Chrome Apps Office Hours: Chrome Storage APIs"
- },
- {
- "link": "https://youtu.be/4cIFRn0rp5s",
- "text": "Chrome Apps Office Hours: Storage API Deep Dive"
- }
- ]
- },
- "syncFileSystem": {
- "Learn More": [
- {
- "link": "app_storage",
- "text": "Manage Data"
- },
- {
- "link": "https://developers.google.com/live/shows/83992232-2001/",
- "text": "Chrome Office Hours: Synched File System"
- }
- ]
- },
- "tabs": {
- "Permissions": [
- {
- "partial": "intro_tables/tabs_permissions.html"
- }
- ]
- },
- "tts": {
- "Learn More": [
- {
- "link": "https://developers.google.com/live/shows/7320022-7001/",
- "text": "Chrome Office Hours: Text to Speech API"
- }
- ]
- },
- "usb": {
- "Learn More": [
- {
- "link": "app_usb",
- "text": "Accessing Hardware Devices"
- }
- ]
- },
- "webRequest": {
- "Permissions": [
- {
- "class": "code",
- "text": "\"webRequest\""
- },
- {
- "link": "declare_permissions#host-permissions",
- "text": "host permissions"
- }
- ]
- },
- "webstore": {
- "Learn More": [
- {
- "link": "https://developers.google.com/chrome/web-store/docs/inline_installation",
- "text": "Using Inline Installation"
- }
- ]
- },
- "windows": {
- "Permissions": [
- {
- "partial": "intro_tables/windows_permissions.html"
- }
- ]
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/manifest.json b/chromium/chrome/common/extensions/docs/templates/json/manifest.json
deleted file mode 100644
index afbec4ec01d..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/manifest.json
+++ /dev/null
@@ -1,252 +0,0 @@
-{
- "action_handlers": {
- "documentation": "manifest/action_handlers",
- "example": ["new_note"]
- },
- "app": {
- "documentation": "manifest/app",
- "example": {},
- "level": "required"
- },
- "app.background": {
- "example": {"scripts": ["background.js"]},
- "level": "required"
- },
- "background": {
- "documentation": "background_pages"
- },
- "background.persistent": {
- "documentation": "event_pages",
- "example": false,
- "level": "recommended"
- },
- "bluetooth": {
- "documentation": "manifest/bluetooth",
- "example": {
- "uuids": [ "1105", "1006" ]
- }
- },
- "browser_action": {
- "documentation": "browserAction",
- "example": {},
- "level": "only_one"
- },
- "chrome_settings_overrides": {
- "documentation": "settings_override",
- "example": {}
- },
- "chrome_ui_overrides": {
- "documentation": "ui_override",
- "example": {
- "bookmarks_ui": {
- "remove_button": true,
- "remove_bookmark_shortcut": true
- }
- }
- },
- "chrome_url_overrides": {
- "documentation": "override",
- "example": {}
- },
- "commands": {
- "documentation": "commands",
- "example": {}
- },
- "content_scripts": {
- "documentation": "content_scripts",
- "example": [{}]
- },
- "content_security_policy": {
- "documentation": "contentSecurityPolicy",
- "example": "policyString"
- },
- "default_locale": {
- "documentation": "manifest/default_locale",
- "example": "en",
- "level": "recommended"
- },
- "description": {
- "documentation": "manifest/description",
- "example": "A plain text description",
- "level": "recommended"
- },
- "devtools_page": {
- "documentation": "devtools",
- "example": "devtools.html"
- },
- "event_rules": {
- "documentation": "manifest/event_rules",
- "example": [{}]
- },
- "export": {
- "documentation": "shared_modules",
- "example": {}
- },
- "externally_connectable": {
- "documentation": "manifest/externally_connectable",
- "example": {
- "matches": ["*://*.example.com/*"]
- }
- },
- "file_browser_handlers": {
- "documentation": "fileBrowserHandler",
- "example": []
- },
- "file_handlers": {
- "documentation": "manifest/file_handlers",
- "example": {}
- },
- "file_system_provider_capabilities": {
- "documentation": "manifest/file_system_provider",
- "example": {
- "configurable": true,
- "multiple_mounts": true,
- "source": "network"
- }
- },
- "homepage_url": {
- "documentation": "manifest/homepage_url",
- "example": "http://path/to/homepage"
- },
- "icons": {
- "documentation": "manifest/icons",
- "example": {},
- "level": "recommended"
- },
- "import": {
- "documentation": "shared_modules",
- "example": [{"id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]
- },
- "incognito": {
- "documentation": "manifest/incognito",
- "example": "spanning, split, or not_allowed"
- },
- "key": {
- "documentation": "manifest/key",
- "example": "publicKey"
- },
- "kiosk_enabled": {
- "documentation": "manifest/kiosk_enabled#kiosk_enabled",
- "example": true
- },
- "kiosk_only": {
- "documentation": "manifest/kiosk_enabled#kiosk_only",
- "example": true
- },
- "manifest_version": {
- "documentation": "manifest/manifest_version",
- "example": 2,
- "level": "required"
- },
- "minimum_chrome_version": {
- "documentation": "manifest/minimum_chrome_version",
- "example": "versionString"
- },
- "nacl_modules": {
- "documentation": "manifest/nacl_modules",
- "example": []
- },
- "name": {
- "documentation": "manifest/name#name",
- "example": "My {{platform}}",
- "level": "required"
- },
- "offline_enabled": {
- "documentation": "manifest/offline_enabled",
- "example": true
- },
- "omnibox": {
- "documentation": "omnibox",
- "example": {
- "keyword": "aString"
- }
- },
- "optional_permissions": {
- "documentation": "permissions",
- "example": ["tabs"]
- },
- "options_page": {
- "documentation": "options",
- "example": "options.html"
- },
- "options_ui": {
- "documentation": "optionsV2",
- "example": {
- "page": "options.html",
- "chrome_style": true
- }
- },
- "page_action": {
- "documentation": "pageAction",
- "example": {},
- "level": "only_one"
- },
- "permissions": {
- "documentation": "declare_permissions",
- "example": ["tabs"]
- },
- "requirements": {
- "documentation": "manifest/requirements",
- "example": {}
- },
- "sandbox": {
- "documentation": "manifest/sandbox",
- "example": []
- },
- "short_name": {
- "documentation": "manifest/name#short_name",
- "example": "Short Name"
- },
- "sockets": {
- "documentation": "manifest/sockets",
- "example": {
- "tcp": { "connect": "*" }, "udp": { "send": "*" }
- }
- },
- "storage": {
- "documentation": "manifest/storage",
- "example": {
- "managed_schema": "schema.json"
- }
- },
- "theme": {
- "documentation": "themes",
- "example": {},
- "level": "only_one"
- },
- "tts_engine": {
- "documentation": "ttsEngine",
- "example": {}
- },
- "update_url": {
- "documentation": "autoupdate",
- "example": "http://path/to/updateInfo.xml"
- },
- "url_handlers": {
- "documentation": "manifest/url_handlers",
- "example": {}
- },
- "usb_printers": {
- "documentation": "manifest/usb_printers",
- "example": {
- "filters": []
- }
- },
- "version": {
- "documentation": "manifest/version",
- "example": "versionString",
- "level": "required"
- },
- "version_name": {
- "documentation": "manifest/version",
- "example": "aString"
- },
- "web_accessible_resources": {
- "documentation": "manifest/web_accessible_resources",
- "example": []
- },
- "webview": {
- "documentation": "tags/webview#local_resources",
- "example": {}
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/permissions.json b/chromium/chrome/common/extensions/docs/templates/json/permissions.json
deleted file mode 100644
index 5d3a415c9bd..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/permissions.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
- "host-permissions": {
- "channel": "stable",
- "anchor": "host-permissions",
- "name": "[scheme]:[host]/*",
- "partial": "permissions/host_permissions.html",
- "extension_types": ["legacy_packaged_app", "extension", "platform_app"]
- },
- "activeTab": {
- "partial": "permissions/active_tab.html"
- },
- "alwaysOnTopWindows": {
- "partial": "permissions/always_on_top_windows.html"
- },
- "audioCapture": {
- "partial": "permissions/audio_capture.html"
- },
- "background": {
- "partial": "permissions/background.html"
- },
- "chrome://favicon/": {
- "anchor": "favicon",
- "extension_types": ["extension"],
- "name": "chrome://favicon/",
- "partial": "permissions/favicon.html"
- },
- "clipboardRead": {
- "partial": "permissions/clipboard_read.html"
- },
- "clipboardWrite": {
- "partial": "permissions/clipboard_write.html"
- },
- "experimental": {
- "partial": "permissions/experimental.html"
- },
- "fullscreen": {
- "partial": "permissions/fullscreen.html"
- },
- "geolocation": {
- "partial": "permissions/geolocation.html"
- },
- "nativeMessaging": {
- "partial": "permissions/nativeMessaging.html"
- },
- "notifications": {
- "partial": "permissions/notifications.html"
- },
- "pointerLock": {
- "partial": "permissions/pointer_lock.html"
- },
- "syncFileSystem": {
- "partial": "permissions/sync_file_system.html"
- },
- "tabs": {
- "partial": "permissions/tabs.html"
- },
- "unlimitedStorage": {
- "partial": "permissions/unlimited_storage.html"
- },
- "videoCapture": {
- "partial": "permissions/video_capture.html"
- },
- "webRequestBlocking": {
- "partial": "permissions/web_request_blocking.html"
- },
- "webview": {
- "partial": "permissions/webview.html"
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/strings.json b/chromium/chrome/common/extensions/docs/templates/json/strings.json
deleted file mode 100644
index 8b1d36418ef..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/strings.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- // General strings.
- "app": "app",
- "appsTitle": "Apps",
- "devtools": "devtools",
- "devtoolsTitle": "DevTools",
- "extension": "extension",
- "extensionsTitle": "Extensions",
- "events": "events",
- "methods": "methods",
- "multidevice": "multi-device",
- "multideviceTitle": "Multi-device",
- "nacl": "native client",
- "naclTitle": "Native Client",
- "properties": "properties",
- "sampleApps": "Sample Apps",
- "sampleExtensions": "Sample Extensions",
- "webstore": "webstore",
- "webstoreTitle": "Chrome Web Store",
-
- // Canonical references to DevTools articles.
- "canonicalDevToolsConsole": "https://developers.google.com/web/tools/javascript/console/console-reference",
- "canonicalDevToolsOverview": "https://developers.google.com/web/tools/setup/workspace/setup-devtools",
- "canonicalDevToolsInspectBasics": "https://developers.google.com/web/tools/iterate/inspect-styles/basics",
- "canonicalDevToolsNavigateConsole": "https://developers.google.com/web/tools/javascript/console/console-ui",
- "canonicalDevToolsTips": "https://developers.google.com/web/updates/tip/",
- "canonicalDevToolsEditStyles": "https://developers.google.com/web/tools/iterate/inspect-styles/basics",
- "canonicalDevToolsPreprocessors": "https://developers.google.com/web/tools/setup/workspace/setup-preprocessors",
- "canonicalDevToolsLocalStorage": "https://developers.google.com/web/tools/iterate/manage-data/local-storage",
- "canonicalDevToolsJavaScript": "https://developers.google.com/web/tools/javascript/",
- "canonicalDevToolsDeviceMode": "https://developers.google.com/web/tools/setup/device-testing/devtools-emulator",
- "canonicalDevToolsRemoteDebugging": "https://developers.google.com/web/tools/setup/remote-debugging/remote-debugging",
- "canonicalDevToolsWorkspaces": "https://developers.google.com/web/tools/setup/workspace/setup-workflow",
- "canonicalDevToolsNetwork": "https://developers.google.com/web/tools/profile-performance/network-performance/resource-loading",
- "canonicalDevToolsTimeline": "https://developers.google.com/web/tools/profile-performance/evaluate-performance/timeline-tool",
- "canonicalDevToolsForcedSynchronousLayoutDemo": "https://developers.google.com/web/tools/profile-performance/rendering-tools/forced-synchronous-layouts",
- "canonicalDevToolsCPUProfiling": "https://developers.google.com/web/tools/profile-performance/rendering-tools/js-execution",
- "canonicalDevToolsMemoryProfiling": "https://developers.google.com/web/tools/profile-performance/memory-problems/memory-diagnosis",
- "canonicalDevToolsMemory101": "https://developers.google.com/web/tools/profile-performance/memory-problems/memory-101",
- "canonicalDevToolsCommandLine": "https://developers.google.com/web/tools/javascript/command-line/command-line-reference",
- "canonicalDevToolsShortcuts": "https://developers.google.com/web/tools/iterate/inspect-styles/shortcuts"
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/json/whats_new.json b/chromium/chrome/common/extensions/docs/templates/json/whats_new.json
deleted file mode 100644
index 9f6b366a2a5..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/json/whats_new.json
+++ /dev/null
@@ -1,367 +0,0 @@
-{
- "backgroundpages.to-be-non-persistent": {
- "type": "additionsToExistingApis",
- "description": "Background pages can optionally be non-persistent, using a feature we call <code>event pages</code>. Event pages run only while they're being used, and will unload when idle to save resources.",
- "version": 22
- },
- "chromeSetting.set-regular-only-scope": {
- "type": "additionsToExistingApis",
- "description": "The types.ChromeSetting.set method now has a <code>regular_only</code> scope.",
- "version": 21
- },
- "browsingData.RemovalOptions.set-originTypes-property": {
- "type": "additionsToExistingApis",
- "description": "The browsingData.RemovalOptions now has an <code>originTypes</code> property.",
- "version": 21
- },
- "management.uninstall.set-showConfirmDialog": {
- "type": "additionsToExistingApis",
- "description": "The management.uninstall method now has a <code>showConfirmDialog</code> parameter.",
- "version": 21
- },
- "contextMenus.create.set-unique-IDs": {
- "type": "additionsToExistingApis",
- "description": "The contextMenus.create method now allows you to specify unique IDs for each item. This is intended to be used with the new <code>contextMenus.onClicked</code> event, to distinguish the clicked item.",
- "version": 21
- },
- "browserAction.setIcon-and-pageAction.setIcon": {
- "type": "additionsToExistingApis",
- "description": "The browserAction.setIcon and pageAction.setIcon methods now accept optional callbacks.",
- "version": 21
- },
- "privacy.websites.has-protectedContentEnabled-property": {
- "type": "additionsToExistingApis",
- "description": "The privacy.websites namespace now has a <code>protectedContentEnabled</code> property.",
- "version": 21
- },
- "tabs.move.index.accept--1": {
- "type": "additionsToExistingApis",
- "description": "The <code>index</code> parameter to the tabs.move method now accepts -1 to indicate that the tab should be placed at the end.",
- "version": 21
- },
- "tabs.highlight.windowId.be-optional": {
- "type": "additionsToExistingApis",
- "description": "The <code>windowId</code> parameter to the tabs.highlight method is now optional.",
- "version": 21
- },
- "manifest-v1-deprecated": {
- "type": "manifestChanges",
- "description": "Manifest version 1 was deprecated in Chrome 18 and will be phased out according to the Manifest version 1 support",
- "version": 20
- },
- "chrome.contextMenus.create-and-update-has-enabled-parameter": {
- "type": "additionsToExistingApis",
- "description": "The chrome.contextMenus contextMenus.create and contextMenus.update methods now have an <code>enabled</code> parameter.",
- "version": 20
- },
- " privacy.services.has-spellingServiceEnabled-setting": {
- "type": "additionsToExistingApis",
- "description": "The privacy API's privacy.services object now has a <code>spellingServiceEnabled</code> setting.",
- "version": 20
- },
- "chrome.tabs.executeScript-and-insertCSS-has-runAt-parameter": {
- "type": "additionsToExistingApis",
- "description": "The chrome.tabs tabs.executeScript and tabs.insertCSS now accept a runAt parameter.",
- "version": 20
- },
- "sendRequest.deprecated-for-runtime-and-sendMessage": {
- "type": "additionsToExistingApis",
- "description": "The <code>sendRequest()</code> method has been deprecated in favor of the <code>sendMessage()</code> method for both the runtime.sendMessage and tabs.sendMessage.",
- "version": 20
- },
- "window.window.support-fullscreen-state": {
- "type": "additionsToExistingApis",
- "description": "The window API's windows.Window object now has an <code>alwaysOnTop</code> property and supports the fullscreen state.",
- "version": 19
- },
- "chrome.tabs.query.has-currentWindow-and-lastFocusedWindow-parameter": {
- "type": "additionsToExistingApis",
- "description": "The <code>chrome.tabs</code> tabs.query method now has the <code>currentWindow</code> and <code>lastFocusedWindow</code> parameters.",
- "version": 19
- },
- "browser.action.api.has-new-getter-fucntions": {
- "type": "additionsToExistingApis",
- "description": "The browser action API has the following new getter functions: browserAction.getTitle, browserAction.getBadgeText, browserAction.getBadgeBackgroundColor, and browserAction.getPopup.",
- "version": 19
- },
- "page.action.has-new-getter-functions": {
- "type": "additionsToExistingApis",
- "description": "The page action API has the following new getter functions: pageAction.getTitle and pageAction.getPopup.",
- "version": 19
- },
- "chrome.tabs.create-update-methods-has-openerTabId-parameter": {
- "type": "additionsToExistingApis",
- "description": "The chrome.tabs tabs.create and tabs.update methods now have an <code>openerTabId</code> parameter",
- "version": 18
- },
- "new-manifest-version-field-specifies-the-version": {
- "type": "manifestChanges",
- "description": "The new manifest version field specifies the version of the manifest that your package requires. As of Chrome 18, you should use manifest version 2.",
- "version": 18
- },
- "new-CSP-field-define-extensions-policies": {
- "type": "manifestChanges",
- "description": "The new Content Security Policy (CSP) field is used to define an extension's policies towards the types of content that can be loaded and executed by the extension.",
- "version": 18
- },
- "background-pages-includes-scripts-property": {
- "type": "manifestChanges",
- "description": "Most background pages only include a list of script files. For these background pages, you can use the new background.scripts property and Chrome will generate a background page for you.",
- "version": 18
- },
- "new-web-accessible-resources-field": {
- "type": "manifestChanges",
- "description": "The new web_accessible_resources field specifies the paths of packaged resources that are expected to be usable in the context of a web page.",
- "version": 18
- },
- "permission-be-optional-for-some-apis": {
- "type": "manifestChanges",
- "description": "Permissions can be optional for the content setting API, the web navigation API, and the new web request API.",
- "version": 17
- },
- "management.ExtensionInfo.object.has-disabledReason-property": {
- "type": "additionsToExistingApis",
- "description": "The management API's management.ExtensionInfo object now has a <code>disabledReason</code> property.",
- "version": 17
- },
- "omnibox.api.works-in-split-incognito-mode": {
- "type": "additionsToExistingApis",
- "description": "The omnibox API now works in split incognito mode.",
- "version": 17
- },
- "new-requirements-field-declare-extension-requirements": {
- "type": "manifestChanges",
- "description": "The new requirements field allows you to declare extension requirements up front. For example, you can use this field to specify that your app requires 3D graphics support in order to use features such as CSS 3D Tranforms or WebGL.",
- "version": 16
- },
- "tabs.query.gets-all-tabs": {
- "type": "additionsToExistingApis",
- "description": "The new tabs.query method gets all tabs that have the specified properties or all tabs if no properties are specified.",
- "version": 16
- },
- "tabs.reload.preserve-local-cache": {
- "type": "additionsToExistingApis",
- "description": "The new tabs.reload method reloads a tab and includes the option to preserve the local cache of the reloaded tab.",
- "version": 16
- },
- "management.ExtensionInfo.has-updateURL-property": {
- "type": "additionsToExistingApis",
- "description": "The management API's management.ExtensionInfo object now has an <code>updateURL</code> property.",
- "version": 16
- },
- "external_extensions.json.limit-supported-locals": {
- "type": "additionsToExistingApis",
- "description": "You can now limit the supported locales for an external extension by adding the <code>supported_locales</code> attribute to the <code>external_extensions.json</code>.",
- "version": 16
- },
- "getAllInWindow-getSelected.deprecated": {
- "type": "additionsToExistingApis",
- "description": "The methods <code>getAllInWindow()</code> and <code>getSelected()</code> have been deprecated. To get details about all tabs in the specified window, use tabs.query with the argument {'windowId': windowId}. To get the tab that is selected in the specified window, use <code>chrome.tabs.query()</code> with the argument <code>{'active': true}</code>.",
- "version": 16
- },
- "tabs.upate-doesnot-need-tabId": {
- "type": "additionsToExistingApis",
- "description": "You are no longer required to specify the <code>tabId</code> for the tabs.update method. When not provided, the tabId defaults to the selected tab of the current window.",
- "version": 16
- },
- "external-files-be-owned-by-users-in-wheel-group": {
- "type": "additionsToExistingApis",
- "description": "External extension files on Mac OS can now be owned by users within a wheel group (or an admin group).",
- "version": 16
- },
- "experimental-permission-no-longer-required-for-panel-type": {
- "type": "additionsToExistingApis",
- "description": "Experimental permission for deprecated windows.create panel type.",
- "version": 16
- },
- "offline_enabled.field-specify-without-connection": {
- "type": "manifestChanges",
- "description": "The new offline_enabled field lets you specify that your app works well even without an internet connection.",
- "version": 15
- },
- "management.getPermissionWarningsById.retrieve-permission-warning": {
- "type": "additionsToExistingApis",
- "description": "You can retrieve permission warnings using the new management API methods management.getPermissionWarningsById and management.getPermissionWarningsByManifest.",
- "version": 15
- },
- "management.ExtensionInfo.has-offlineEnabled": {
- "type": "additionsToExistingApis",
- "description": "The management API’s management.ExtensionInfo object has a new field, offlineEnabled.",
- "version": 15
- },
- "internationalize.using-placeholders": {
- "type": "additionsToExistingApis",
- "description": "You can now internationalize content script CSS files by using __MSG_messagename__ placeholders.",
- "version": 15
- },
- "tabs.update.callback-passed-null": {
- "type": "additionsToExistingApis",
- "description": "The callback for the tabs.update method is passed null instead of the tab details if the extension does not have the tabs permission.",
- "version": 15
- },
- "content_security_policy-prevent-xss-attack": {
- "type": "manifestChanges",
- "description": "The new content_security_policy field can help prevent cross-site scripting vulnerabilities in your extension.",
- "version": 14
- },
- "nacl_modules.register-native-client-modules": {
- "type": "manifestChanges",
- "description": "The new nacl_modules field lets you register Native Client modules as content handlers for MIME types.",
- "version": 14
- },
- "context.menu.items-appear-in-documents": {
- "type": "additionsToExistingApis",
- "description": "Context menu items can now appear even in documents that have file:// or chrome:// URLs. Previously, they were restricted to documents with http:// or https:// URLs.",
- "version": 14
- },
- "drawAttention-field-specify-that-window": {
- "type": "additionsToExistingApis",
- "description": "An optional drawAttention field in windows.update's updateInfo object lets you specify that the window should entice the user to change focus to it.",
- "version": 14
- },
- "bookmarks.getSubTree-retrieve-bookmarks-hierarchy": {
- "type": "additionsToExistingApis",
- "description": "The new bookmarks.getSubTree function lets you retrieve just part of the Bookmarks hierarchy",
- "version": 14
- },
- "tabs.permission.no-longer-required-for-tabs-remove": {
- "type": "additionsToExistingApis",
- "description": "The tabs permission is no longer required for tabs.remove and tabs.onRemoved.",
- "version": 14
- },
- "exclude_matches-targets-content-script-precisely": {
- "type": "manifestChanges",
- "description": "A new <code>exclude_matches</code> item in the content_scripts field lets you target your content script more precisely. For details, see Match patterns and globs.",
- "version": 13
- },
- "new-clipboardRead-specify-capabilities-for-document.execCommand": {
- "type": "manifestChanges",
- "description": "New clipboardRead and clipboardWrite permissions specify capabilities for <code>document.execCommand()</code>.",
- "version": 13
- },
- "content-scripts-make-cross-origin-requests": {
- "type": "additionsToExistingApis",
- "description": "Content scripts can now make cross-origin XMLHttpRequests to the same sites that their parent extension can, eliminating the need to relay these requests through a background page.",
- "version": 13
- },
- "use-runat-in-greasemonkey-script": {
- "type": "additionsToExistingApis",
- "description": "You can now use <code>@run-at</code> in an imported Greasemonkey script to control when the script is injected. It works the same way as <code>run_at</code> in content scripts.",
- "version": 13
- },
- "two-new-chrome-extension-methods": {
- "type": "additionsToExistingApis",
- "description": "Two new <code>chrome.extension</code> methods—extension.isAllowedFileSchemeAccess and extension.isAllowedIncognitoAccess—let you determine whether your extension has increased access, which the user specifies using the extensions management page (chrome://extensions).",
- "version": 12
- },
- "window.create.takes-focused-value": {
- "type": "additionsToExistingApis",
- "description": "The windows.create method can now take a <code>focused</code> value. Previously, all new windows had the keyboard focus; now you can create windows without interrupting the user's typing.",
- "version": 12
- },
- "manifest-specifies-experimental-permission": {
- "type": "additionsToExistingApis",
- "description": "If the manifest specifies experimental permission, your extension can specify panel as the value of the <code>type</code> field in the windows.create method or the windows.Window type.",
- "version": 12
- },
- "cookies.onChanged.event-has-a-cause-parameter": {
- "type": "additionsToExistingApis",
- "description": "The cookies.onChanged event of <code>chrome.cookies</code> now has a <code>cause</code> parameter.",
- "version": 12
- },
- "chrome.contextMenus.create.specifies-frame-value": {
- "type": "additionsToExistingApis",
- "description": "The <code>chrome.contextMenus</code> contextMenus.create and contextMenus.update methods now let you specify a context value of frame",
- "version": 12
- },
- "host-permission-for-tabs-operation": {
- "type": "additionsToExistingApis",
- "description": "For security reasons, you can no longer call tabs.captureVisibleTab on just any tab. Instead, you now must have host permission for the URL displayed by that tab. To get the previous behavior, specify <code><all_urls></code> for the host permission.",
- "version": 11
- },
- "management.ExtensionInfo.has-homepageUrl-property": {
- "type": "additionsToExistingApis",
- "description": "The management API's management.ExtensionInfo object now has a <code>homepageUrl</code> property.",
- "version": 11
- },
- "management.has-homepageUrl-property": {
- "type": "additionsToExistingApis",
- "description": "The management API now lets you get the icons of disabled apps and extensions. Also, you can now modify the regular icon's URL to get its disabled equivalent. See management.IconInfo for details.",
- "version": 11
- },
- "cookies.set-take-callbacks": {
- "type": "additionsToExistingApis",
- "description": "The cookies API cookies.set and cookies.remove methods can now take callbacks.",
- "version": 11
- },
- "new.background-permission-extends-life-of-chrome": {
- "type": "manifestChanges",
- "description": "The new background permission extends the life of Chrome, allowing your extension or app to run even when Chrome has no windows open.",
- "version": 10
- },
- "windows.create.has-tabId-field": {
- "type": "additionsToExistingApis",
- "description": "The windows.create method now has a <code>tabId</code> field. You can use it to move a tab or panel into a new window.",
- "version": 10
- },
- "homepage-url-field-specify-extension-homepage": {
- "type": "manifestChanges",
- "description": "The homepage_url field lets you specify the extension or app's homepage.",
- "version": 9
- },
- "tabs.Tab.has-pinned-property": {
- "type": "additionsToExistingApis",
- "description": "The tabs.Tab object now has a <code>pinned</code> property that's reflected in various <code>chrome.tabs</code> methods. For example, you can tabs.create a pinned tab.",
- "version": 9
- },
- "windows.create.takes-urls": {
- "type": "additionsToExistingApis",
- "description": "The windows.create method can now take a list of URLs, letting you create multiple tabs in the new window.",
- "version": 9
- },
- "management.get.specified-apps": {
- "type": "additionsToExistingApis",
- "description": "The new management.get method lets you get information about the specified extension or app.",
- "version": 9
- },
- "introduce-split-incognito-mode": {
- "type": "manifestChanges",
- "description": "Introduced split incognito mode as the default for installable web apps (also available to extensions).",
- "version": 7
- },
- "tabs.create-and-update-no-longer-require-tabs-permission": {
- "type": "manifestChanges",
- "description": "The tabs API <code>create()</code> and <code>update()</code> methods no longer require the tabs permission, removing one common cause of scary dialogs.",
- "version": 7
- },
- "geolocation-permission-access-users-location": {
- "type": "manifestChanges",
- "description": "The geolocation permission gives an extension access to the user's physical location.",
- "version": 6
- },
- "match-patterns-select-all-schemes": {
- "type": "manifestChanges",
- "description": "Match patterns can now select all schemes or all URLs.",
- "version": 6
- },
- "access-file-urls-triggers-access-warning": {
- "type": "manifestChanges",
- "description": "Access to file:/// URLs no longer triggers the access to your machine security warning, but now requires user opt-in from the extensions management page.",
- "version": 6
- },
- "extension.getViews.return-popup-views": {
- "type": "additionsToExistingApis",
- "description": "The extension.getViews method can now return popup views.",
- "version": 6
- },
- "windows.WINDOW_ID_NONE.identifies": {
- "type": "additionsToExistingApis",
- "description": "A new windows.WINDOW_ID_NONE constant identifies when focus shifts away from the browser.",
- "version": 6
- },
- "tabs.getCurrent-returns-tab": {
- "type": "additionsToExistingApis",
- "description": "The new tabs.getCurrent method returns the tab associated with the currently executing script.",
- "version": 6
- }
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/public/apps/redirects.json b/chromium/chrome/common/extensions/docs/templates/public/apps/redirects.json
deleted file mode 100644
index 6347547fcba..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/public/apps/redirects.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "": "about_apps",
- "app_csp": "contentSecurityPolicy",
- "cloudMessaging": "https://developers.google.com/cloud-messaging/chrome/client",
- "cloudMessagingV1": "https://developers.google.com/cloud-messaging/chrome/client",
- "crx": "/extensions/linux_hosting",
- "pushMessaging": "https://developers.google.com/cloud-messaging/chrome/client",
- "gcm_tos": "https://developers.google.com/terms/",
- "gcm_server": "https://developers.google.com/cloud-messaging/server",
- "getstarted_arc": "https://developer.android.com/chrome-os/intro.html",
- "google_wallet": "../webstore/payments-iap",
- "experimental_systemInfo_storage": "system_storage",
- "in_app_payments": "google_wallet",
- "index": "about_apps",
- "inform_users": "https://developers.google.com/cloud-messaging/chrome/client",
- "packaging": "/extensions/packaging",
- "systemInfo_cpu": "system_cpu",
- "systemInfo_display": "system_display",
- "systemInfo_memory": "system_memory",
- "webview": "tags/webview",
- "webview_tag": "tags/webview",
- "app_codelab": "app_codelab_intro",
- "app_codelab1_setup": "app_codelab_intro",
- "app_codelab2_basic": "app_codelab_intro",
- "app_codelab3_mvc": "app_codelab_intro",
- "app_codelab5_data": "app_codelab_intro",
- "app_codelab6_lifecycle": "app_codelab_intro",
- "app_codelab7_useridentification": "app_codelab_intro",
- "app_codelab8_webresources": "app_codelab_intro",
- "app_codelab_10_publishing": "app_codelab_intro",
- "arc_in_app_payments": "https://developer.android.com/chrome-os/intro.html",
- "arc_playservices": "https://developer.android.com/chrome-os/intro.html"
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/public/extensions/redirects.json b/chromium/chrome/common/extensions/docs/templates/public/extensions/redirects.json
deleted file mode 100644
index f855b38d02a..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/public/extensions/redirects.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "experimental.debugger": "debugger",
- "experimental_debugger": "debugger",
- "experimental.infobars": "infobars",
- "experimental_infobars": "infobars",
- "experimental.systemInfo_storage": "system_storage",
- "experimental_systemInfo_storage": "system_storage",
- "systemInfo_cpu": "system_cpu",
- "systemInfo_memory": "system_memory",
- "index": ".",
- "optionsV2": "/extensions/options",
- "cloudMessaging": "https://developers.google.com/cloud-messaging/chrome/client",
- "cloudMessagingV1": "https://developers.google.com/cloud-messaging/chrome/client",
- "pushMessaging": "https://developers.google.com/cloud-messaging/chrome/client",
- "gcm_tos": "https://developers.google.com/terms/",
- "gcm_server": "https://developers.google.com/cloud-messaging/server",
- "autoupdate": "hosting",
- "packaging": "hosting",
- "crx": "hosting"
-}
diff --git a/chromium/chrome/common/extensions/docs/templates/public/redirects.json b/chromium/chrome/common/extensions/docs/templates/public/redirects.json
deleted file mode 100644
index 6c724a291fe..00000000000
--- a/chromium/chrome/common/extensions/docs/templates/public/redirects.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "": "/home",
- "apps": "/apps/about_apps"
-}
diff --git a/chromium/chrome/common/extensions/extension_constants.cc b/chromium/chrome/common/extensions/extension_constants.cc
new file mode 100644
index 00000000000..53d88ca26df
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_constants.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "chrome/common/extensions/extension_constants.h"
+
+namespace extension_urls {
+
+const char kWebstoreSourceField[] = "utm_source";
+
+const char kLaunchSourceAppList[] = "chrome-app-launcher";
+const char kLaunchSourceAppListSearch[] = "chrome-app-launcher-search";
+const char kLaunchSourceAppListInfoDialog[] = "chrome-app-launcher-info-dialog";
+
+} // namespace extension_urls
+
+namespace extension_misc {
+
+const char kCalculatorAppId[] = "joodangkbfjnajiiifokapkpmhfnpleo";
+const char kCalendarAppId[] = "ejjicmeblgpmajnghnpcppodonldlgfn";
+const char kChromeRemoteDesktopAppId[] = "gbchcmhmhahfdphkhkmpfmihenigjmpp";
+const char kCloudPrintAppId[] = "mfehgcgbbipciphmccgaenjidiccnmng";
+const char kDataSaverExtensionId[] = "pfmgfdlgomnbgkofeojodiodmgpgmkac";
+const char kDocsOfflineExtensionId[] = "ghbmnnjooekpmoecnnnilnnbdlolhkhi";
+const char kDriveHostedAppId[] = "apdfllckaahabafndbhieahigkjlhalf";
+const char kEnterpriseWebStoreAppId[] = "afchcafgojfnemjkcbhfekplkmjaldaa";
+const char kGmailAppId[] = "pjkljhegncpnkpknbcohdijeoejaedia";
+const char kGoogleDocAppId[] = "aohghmighlieiainnegkcijnfilokake";
+const char kGoogleMapsAppId[] = "lneaknkopdijkpnocmklfnjbeapigfbh";
+const char kGooglePhotosAppId[] = "hcglmfcclpfgljeaiahehebeoaiicbko";
+const char kGooglePlayBooksAppId[] = "mmimngoggfoobjdlefbcabngfnmieonb";
+const char kGooglePlayMoviesAppId[] = "gdijeikdkaembjbdobgfkoidjkpbmlkd";
+const char kGooglePlayMusicAppId[] = "icppfcnhkcmnfdhfhphakoifcfokfdhg";
+const char kGooglePlusAppId[] = "dlppkpafhbajpcmmoheippocdidnckmm";
+const char kGoogleSheetsAppId[] = "felcaaldnbdncclmgdcncolpebgiejap";
+const char kGoogleSlidesAppId[] = "aapocclcgogkmnckokdopfmhonfmgoek";
+const char kHTermAppId[] = "pnhechapfaindjhompbnflcldabbghjo";
+const char kHTermDevAppId[] = "okddffdblfhhnmhodogpojmfkjmhinfp";
+const char kIdentityApiUiAppId[] = "ahjaciijnoiaklcomgnblndopackapon";
+const char kCroshBuiltinAppId[] = "nkoccljplnhpfnfiajclkommnmllphnl";
+const char kTextEditorAppId[] = "mmfbcljfglbokpmkimbfghdkjmjhdgbg";
+const char kInAppPaymentsSupportAppId[] = "nmmhkkegccagdldgiimedpiccmgmieda";
+const char kMediaRouterStableExtensionId[] = "pkedcjkdefgpdelpbcmbmeomcjbeemfm";
+const char kCloudReportingExtensionId[] = "oempjldejiginopiohodkdoklcjklbaa";
+
+const char* const kBuiltInFirstPartyExtensionIds[] = {
+ kCalculatorAppId,
+ kCalendarAppId,
+ kChromeRemoteDesktopAppId,
+ kCloudPrintAppId,
+ kDataSaverExtensionId,
+ kDocsOfflineExtensionId,
+ kDriveHostedAppId,
+ kEnterpriseWebStoreAppId,
+ kGmailAppId,
+ kGoogleDocAppId,
+ kGoogleMapsAppId,
+ kGooglePhotosAppId,
+ kGooglePlayBooksAppId,
+ kGooglePlayMoviesAppId,
+ kGooglePlayMusicAppId,
+ kGooglePlusAppId,
+ kGoogleSheetsAppId,
+ kGoogleSlidesAppId,
+ kHTermAppId,
+ kHTermDevAppId,
+ kIdentityApiUiAppId,
+ kCroshBuiltinAppId,
+ kTextEditorAppId,
+ kInAppPaymentsSupportAppId,
+ kMediaRouterStableExtensionId,
+ kCloudReportingExtensionId,
+#if defined(OS_CHROMEOS)
+ kAssessmentAssistantExtensionId,
+ kAutoclickExtensionId,
+ kSelectToSpeakExtensionId,
+ kSwitchAccessExtensionId,
+ kFirstRunDialogId,
+ kEspeakSpeechSynthesisExtensionId,
+ kGoogleSpeechSynthesisExtensionId,
+ kWallpaperManagerId,
+ kZipArchiverExtensionId,
+ kChromeCameraAppId,
+ kChromeCameraAppDevId,
+#endif // defined(OS_CHROMEOS)
+ nullptr, // Null-terminated array.
+};
+
+#if defined(OS_CHROMEOS)
+const char kAssessmentAssistantExtensionId[] =
+ "gndmhdcefbhlchkhipcnnbkcmicncehk";
+const char kAutoclickExtensionId[] = "egfdjlfmgnehecnclamagfafdccgfndp";
+const char kAutoclickExtensionPath[] = "chromeos/autoclick";
+const char kChromeVoxExtensionPath[] = "chromeos/chromevox";
+const char kSelectToSpeakExtensionId[] = "klbcgckkldhdhonijdbnhhaiedfkllef";
+const char kSelectToSpeakExtensionPath[] = "chromeos/select_to_speak";
+const char kSwitchAccessExtensionId[] = "pmehocpgjmkenlokgjfkaichfjdhpeol";
+const char kSwitchAccessExtensionPath[] = "chromeos/switch_access";
+const char kGuestManifestFilename[] = "manifest_guest.json";
+const char kConnectivityDiagnosticsPath[] =
+ "/usr/share/chromeos-assets/connectivity_diagnostics";
+const char kConnectivityDiagnosticsLauncherPath[] =
+ "/usr/share/chromeos-assets/connectivity_diagnostics_launcher";
+const char kFirstRunDialogId[] = "jdgcneonijmofocbhmijhacgchbihela";
+const char kEspeakSpeechSynthesisExtensionPath[] =
+ "/usr/share/chromeos-assets/speech_synthesis/espeak-ng";
+const char kEspeakSpeechSynthesisExtensionId[] =
+ "dakbfdmgjiabojdgbiljlhgjbokobjpg";
+const char kGoogleSpeechSynthesisExtensionPath[] =
+ "/usr/share/chromeos-assets/speech_synthesis/patts";
+const char kGoogleSpeechSynthesisExtensionId[] =
+ "gjjabgpgjpampikjhjpfhneeoapjbjaf";
+const char kWallpaperManagerId[] = "obklkkbkpaoaejdabbfldmcfplpdgolj";
+const char kZipArchiverExtensionId[] = "dmboannefpncccogfdikhmhpmdnddgoe";
+const char kZipArchiverExtensionPath[] = "chromeos/zip_archiver";
+const char kChromeCameraAppId[] = "hfhhnacclhffhdffklopdkcgdhifgngh";
+const char kChromeCameraAppDevId[] = "flgnmkgjffmkephdokeeliiopbjaafpm";
+const char kChromeCameraAppPath[] = "chromeos/camera";
+
+#endif // defined(CHROME_OS)
+
+const char kAppStateNotInstalled[] = "not_installed";
+const char kAppStateInstalled[] = "installed";
+const char kAppStateDisabled[] = "disabled";
+const char kAppStateRunning[] = "running";
+const char kAppStateCannotRun[] = "cannot_run";
+const char kAppStateReadyToRun[] = "ready_to_run";
+
+const char kMediaFileSystemPathPart[] = "_";
+} // namespace extension_misc
diff --git a/chromium/chrome/common/extensions/extension_constants.h b/chromium/chrome/common/extensions/extension_constants.h
new file mode 100644
index 00000000000..75717b341f9
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_constants.h
@@ -0,0 +1,272 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "url/gurl.h"
+
+namespace extension_urls {
+
+// Field to use with webstore URL for tracking launch source.
+extern const char kWebstoreSourceField[];
+
+// Values to use with webstore URL launch source field.
+extern const char kLaunchSourceAppList[];
+extern const char kLaunchSourceAppListSearch[];
+extern const char kLaunchSourceAppListInfoDialog[];
+
+} // namespace extension_urls
+
+namespace extension_misc {
+
+// The extension id of the Calculator application.
+extern const char kCalculatorAppId[];
+
+// The extension id of the Calendar application.
+extern const char kCalendarAppId[];
+
+// The extension id of the Chrome Remote Desktop application.
+extern const char kChromeRemoteDesktopAppId[];
+
+// The extension id of the Cloud Print component application.
+extern const char kCloudPrintAppId[];
+
+// The extension id of the Data Saver extension.
+extern const char kDataSaverExtensionId[];
+
+// The extension id of the Google Docs Offline extension.
+extern const char kDocsOfflineExtensionId[];
+
+// The extension id of the Drive hosted app.
+extern const char kDriveHostedAppId[];
+
+// The extension id of the Enterprise Web Store component application.
+extern const char kEnterpriseWebStoreAppId[];
+
+// The extension id of GMail application.
+extern const char kGmailAppId[];
+
+// The extension id of the Google Doc application.
+extern const char kGoogleDocAppId[];
+
+// The extension id of the Google Maps application.
+extern const char kGoogleMapsAppId[];
+
+// The extension id of the Google Photos application.
+extern const char kGooglePhotosAppId[];
+
+// The extension id of the Google Play Books application.
+extern const char kGooglePlayBooksAppId[];
+
+// The extension id of the Google Play Movies application.
+extern const char kGooglePlayMoviesAppId[];
+
+// The extension id of the Google Play Music application.
+extern const char kGooglePlayMusicAppId[];
+
+// The extension id of the Google+ application.
+extern const char kGooglePlusAppId[];
+
+// The extension id of the Google Sheets application.
+extern const char kGoogleSheetsAppId[];
+
+// The extension id of the Google Slides application.
+extern const char kGoogleSlidesAppId[];
+
+// The extension id of the HTerm app for ChromeOS.
+extern const char kHTermAppId[];
+
+// The extension id of the HTerm dev app for ChromeOS.
+extern const char kHTermDevAppId[];
+
+// The extension id of the Identity API UI application.
+extern const char kIdentityApiUiAppId[];
+
+// The extension id of the Crosh component app for ChromeOS.
+extern const char kCroshBuiltinAppId[];
+
+// The extension id of the Text Editor application.
+extern const char kTextEditorAppId[];
+
+// The extension id of the in-app payments support application.
+extern const char kInAppPaymentsSupportAppId[];
+
+// The extension id of the stable media router extension.
+extern const char kMediaRouterStableExtensionId[];
+
+// The extension id of the Chrome Reporting extension.
+extern const char kCloudReportingExtensionId[];
+
+// A list of all the first party extension IDs, last entry is null.
+extern const char* const kBuiltInFirstPartyExtensionIds[];
+
+// The buckets used for app launches.
+enum AppLaunchBucket {
+ // Launch from NTP apps section while maximized.
+ APP_LAUNCH_NTP_APPS_MAXIMIZED,
+
+ // Launch from NTP apps section while collapsed.
+ APP_LAUNCH_NTP_APPS_COLLAPSED,
+
+ // Launch from NTP apps section while in menu mode.
+ APP_LAUNCH_NTP_APPS_MENU,
+
+ // Launch from NTP most visited section in any mode.
+ APP_LAUNCH_NTP_MOST_VISITED,
+
+ // Launch from NTP recently closed section in any mode.
+ APP_LAUNCH_NTP_RECENTLY_CLOSED,
+
+ // App link clicked from bookmark bar.
+ APP_LAUNCH_BOOKMARK_BAR,
+
+ // Nvigated to an app from within a web page (like by clicking a link).
+ APP_LAUNCH_CONTENT_NAVIGATION,
+
+ // Launch from session restore.
+ APP_LAUNCH_SESSION_RESTORE,
+
+ // Autolaunched at startup, like for pinned tabs.
+ APP_LAUNCH_AUTOLAUNCH,
+
+ // Launched from omnibox app links.
+ APP_LAUNCH_OMNIBOX_APP,
+
+ // App URL typed directly into the omnibox (w/ instant turned off).
+ APP_LAUNCH_OMNIBOX_LOCATION,
+
+ // Navigate to an app URL via instant.
+ APP_LAUNCH_OMNIBOX_INSTANT,
+
+ // Launch via chrome.management.launchApp.
+ APP_LAUNCH_EXTENSION_API,
+
+ // Launch an app via a shortcut. This includes using the --app or --app-id
+ // command line arguments, or via an app shim process on Mac.
+ APP_LAUNCH_CMD_LINE_APP,
+
+ // App launch by passing the URL on the cmd line (not using app switches).
+ APP_LAUNCH_CMD_LINE_URL,
+
+ // User clicked web store launcher on NTP.
+ APP_LAUNCH_NTP_WEBSTORE,
+
+ // App launched after the user re-enabled it on the NTP.
+ APP_LAUNCH_NTP_APP_RE_ENABLE,
+
+ // URL launched using the --app cmd line option, but the URL does not
+ // correspond to an installed app. These launches are left over from a
+ // feature that let you make desktop shortcuts from the file menu.
+ APP_LAUNCH_CMD_LINE_APP_LEGACY,
+
+ // User clicked web store link on the NTP footer.
+ APP_LAUNCH_NTP_WEBSTORE_FOOTER,
+
+ // User clicked [+] icon in apps page.
+ APP_LAUNCH_NTP_WEBSTORE_PLUS_ICON,
+
+ // User clicked icon in app launcher main view.
+ APP_LAUNCH_APP_LIST_MAIN,
+
+ // User clicked app launcher search result.
+ APP_LAUNCH_APP_LIST_SEARCH,
+
+ // User clicked the chrome app icon from the app launcher's main view.
+ APP_LAUNCH_APP_LIST_MAIN_CHROME,
+
+ // User clicked the webstore icon from the app launcher's main view.
+ APP_LAUNCH_APP_LIST_MAIN_WEBSTORE,
+
+ // User clicked the chrome app icon from the app launcher's search view.
+ APP_LAUNCH_APP_LIST_SEARCH_CHROME,
+
+ // User clicked the webstore icon from the app launcher's search view.
+ APP_LAUNCH_APP_LIST_SEARCH_WEBSTORE,
+ APP_LAUNCH_BUCKET_BOUNDARY,
+ APP_LAUNCH_BUCKET_INVALID
+};
+
+#if defined(OS_CHROMEOS)
+// The extension id of the Assessment Assistant extension.
+extern const char kAssessmentAssistantExtensionId[];
+// The extension id of the Automatic Clicks extension.
+extern const char kAutoclickExtensionId[];
+// Path to preinstalled Automatic Clicks extension (relative to
+// |chrome::DIR_RESOURCES|).
+extern const char kAutoclickExtensionPath[];
+// Path to preinstalled ChromeVox screen reader extension (relative to
+// |chrome::DIR_RESOURCES|).
+extern const char kChromeVoxExtensionPath[];
+// The extension id of the Select-to-speak extension.
+extern const char kSelectToSpeakExtensionId[];
+// Path to preinstalled Select-to-speak extension (relative to
+// |chrome::DIR_RESOURCES|).
+extern const char kSelectToSpeakExtensionPath[];
+// The extension id of the Switch access extension.
+extern const char kSwitchAccessExtensionId[];
+// Path to preinstalled Switch access extension (relative to
+// |chrome::DIR_RESOURCES|).
+extern const char kSwitchAccessExtensionPath[];
+// Name of the manifest file in an extension when a special manifest is used
+// for guest mode.
+extern const char kGuestManifestFilename[];
+// Path to preinstalled Connectivity Diagnostics extension.
+extern const char kConnectivityDiagnosticsPath[];
+extern const char kConnectivityDiagnosticsLauncherPath[];
+// The extension id of the first run dialog application.
+extern const char kFirstRunDialogId[];
+// Path to preinstalled Google speech synthesis extension.
+extern const char kGoogleSpeechSynthesisExtensionPath[];
+// The extension id of the Google speech synthesis extension.
+extern const char kGoogleSpeechSynthesisExtensionId[];
+// Path to preinstalled eSpeak-NG speech synthesis extension.
+extern const char kEspeakSpeechSynthesisExtensionPath[];
+// The extension id of the eSpeak-NG speech synthesis extension.
+extern const char kEspeakSpeechSynthesisExtensionId[];
+// The extension id of the wallpaper manager application.
+extern const char kWallpaperManagerId[];
+// The extension id of the zip archiver extension.
+extern const char kZipArchiverExtensionId[];
+// Path to preinstalled zip archiver extension.
+extern const char kZipArchiverExtensionPath[];
+// The app ID of Chrome camera app.
+extern const char kChromeCameraAppId[];
+// The dev app ID of Chrome camera app.
+extern const char kChromeCameraAppDevId[];
+// Path to preinstalled Chrome camera app.
+extern const char kChromeCameraAppPath[];
+#endif
+
+// What causes an extension to be installed? Used in histograms, so don't
+// change existing values.
+enum CrxInstallCause {
+ INSTALL_CAUSE_UNSET = 0,
+ INSTALL_CAUSE_USER_DOWNLOAD,
+ INSTALL_CAUSE_UPDATE,
+ INSTALL_CAUSE_EXTERNAL_FILE,
+ INSTALL_CAUSE_AUTOMATION,
+ NUM_INSTALL_CAUSES
+};
+
+// The states that an app can be in, as reported by chrome.app.installState
+// and chrome.app.runningState.
+extern const char kAppStateNotInstalled[];
+extern const char kAppStateInstalled[];
+extern const char kAppStateDisabled[];
+extern const char kAppStateRunning[];
+extern const char kAppStateCannotRun[];
+extern const char kAppStateReadyToRun[];
+
+// The path part of the file system url used for media file systems.
+extern const char kMediaFileSystemPathPart[];
+} // namespace extension_misc
+
+#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
diff --git a/chromium/chrome/common/extensions/extension_metrics.cc b/chromium/chrome/common/extensions/extension_metrics.cc
new file mode 100644
index 00000000000..8e6f776ca9c
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_metrics.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/extension_metrics.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+void RecordAppLaunchType(extension_misc::AppLaunchBucket bucket,
+ extensions::Manifest::Type app_type) {
+ DCHECK_LT(bucket, extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
+ if (app_type == extensions::Manifest::TYPE_PLATFORM_APP) {
+ UMA_HISTOGRAM_ENUMERATION("Apps.AppLaunch", bucket,
+ extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("Extensions.AppLaunch", bucket,
+ extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
+ }
+}
+
+void RecordAppListSearchLaunch(const extensions::Extension* extension) {
+ extension_misc::AppLaunchBucket bucket =
+ extension_misc::APP_LAUNCH_APP_LIST_SEARCH;
+ if (extension->id() == extensions::kWebStoreAppId)
+ bucket = extension_misc::APP_LAUNCH_APP_LIST_SEARCH_WEBSTORE;
+ else if (extension->id() == extension_misc::kChromeAppId)
+ bucket = extension_misc::APP_LAUNCH_APP_LIST_SEARCH_CHROME;
+ RecordAppLaunchType(bucket, extension->GetType());
+}
+
+void RecordAppListMainLaunch(const extensions::Extension* extension) {
+ extension_misc::AppLaunchBucket bucket =
+ extension_misc::APP_LAUNCH_APP_LIST_MAIN;
+ if (extension->id() == extensions::kWebStoreAppId)
+ bucket = extension_misc::APP_LAUNCH_APP_LIST_MAIN_WEBSTORE;
+ else if (extension->id() == extension_misc::kChromeAppId)
+ bucket = extension_misc::APP_LAUNCH_APP_LIST_MAIN_CHROME;
+ RecordAppLaunchType(bucket, extension->GetType());
+}
+
+void RecordWebStoreLaunch() {
+ RecordAppLaunchType(extension_misc::APP_LAUNCH_NTP_WEBSTORE,
+ extensions::Manifest::TYPE_HOSTED_APP);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/extension_metrics.h b/chromium/chrome/common/extensions/extension_metrics.h
new file mode 100644
index 00000000000..ec31705df61
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_metrics.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_METRICS_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_METRICS_H_
+
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+
+class Extension;
+
+// Records the given type of app launch for UMA.
+void RecordAppLaunchType(extension_misc::AppLaunchBucket bucket,
+ extensions::Manifest::Type app_type);
+
+// Records an app launch from the search view of the app list.
+void RecordAppListSearchLaunch(const extensions::Extension* extension);
+
+// Records an app launch from the main view of the app list.
+void RecordAppListMainLaunch(const extensions::Extension* extension);
+
+// Records a web store launch in the appropriate histograms.
+void RecordWebStoreLaunch();
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_METRICS_H_
diff --git a/chromium/chrome/common/extensions/extension_test_util.cc b/chromium/chrome/common/extensions/extension_test_util.cc
new file mode 100644
index 00000000000..ee6ef94256b
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_test_util.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/extension_test_util.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/version_info/channel.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using extensions::Extension;
+using extensions::Manifest;
+
+namespace extension_test_util {
+
+scoped_refptr<Extension> LoadManifestUnchecked(const std::string& dir,
+ const std::string& test_file,
+ Manifest::Location location,
+ int extra_flags,
+ const std::string& id,
+ std::string* error) {
+ base::FilePath path;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &path);
+ path = path.AppendASCII("extensions")
+ .AppendASCII(dir)
+ .AppendASCII(test_file);
+
+ JSONFileValueDeserializer deserializer(path);
+ std::unique_ptr<base::Value> result = deserializer.Deserialize(NULL, error);
+ if (!result)
+ return NULL;
+ const base::DictionaryValue* dict;
+ CHECK(result->GetAsDictionary(&dict));
+
+ scoped_refptr<Extension> extension = Extension::Create(
+ path.DirName(), location, *dict, extra_flags, id, error);
+ return extension;
+}
+
+scoped_refptr<Extension> LoadManifestUnchecked(const std::string& dir,
+ const std::string& test_file,
+ Manifest::Location location,
+ int extra_flags,
+ std::string* error) {
+ return LoadManifestUnchecked(
+ dir, test_file, location, extra_flags, std::string(), error);
+}
+
+scoped_refptr<Extension> LoadManifest(const std::string& dir,
+ const std::string& test_file,
+ Manifest::Location location,
+ int extra_flags) {
+ std::string error;
+ scoped_refptr<Extension> extension =
+ LoadManifestUnchecked(dir, test_file, location, extra_flags, &error);
+
+ EXPECT_TRUE(extension.get()) << test_file << ":" << error;
+ return extension;
+}
+
+scoped_refptr<Extension> LoadManifest(const std::string& dir,
+ const std::string& test_file,
+ int extra_flags) {
+ return LoadManifest(dir, test_file, Manifest::INVALID_LOCATION, extra_flags);
+}
+
+scoped_refptr<Extension> LoadManifestStrict(const std::string& dir,
+ const std::string& test_file) {
+ return LoadManifest(dir, test_file, Extension::NO_FLAGS);
+}
+
+scoped_refptr<Extension> LoadManifest(const std::string& dir,
+ const std::string& test_file) {
+ return LoadManifest(dir, test_file, Extension::NO_FLAGS);
+}
+
+void SetGalleryUpdateURL(const GURL& new_url) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ command_line->AppendSwitchASCII(switches::kAppsGalleryUpdateURL,
+ new_url.spec());
+ extensions::ExtensionsClient::Get()->InitializeWebStoreUrls(command_line);
+}
+
+std::unique_ptr<extensions::ScopedCurrentChannel>
+GetOverrideChannelForActionType(extensions::ActionInfo::Type action_type) {
+ std::unique_ptr<extensions::ScopedCurrentChannel> channel;
+ // The "action" key is currently restricted to trunk. Use a fake channel iff
+ // we're testing that key, so that we still get multi-channel coverage for
+ // browser and page actions.
+ switch (action_type) {
+ case extensions::ActionInfo::TYPE_ACTION:
+ channel = std::make_unique<extensions::ScopedCurrentChannel>(
+ version_info::Channel::UNKNOWN);
+ break;
+ case extensions::ActionInfo::TYPE_PAGE:
+ case extensions::ActionInfo::TYPE_BROWSER:
+ break;
+ }
+ return channel;
+}
+
+} // namespace extension_test_util
diff --git a/chromium/chrome/common/extensions/extension_test_util.h b/chromium/chrome/common/extensions/extension_test_util.h
new file mode 100644
index 00000000000..0c20ba4223c
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_test_util.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
+#include "extensions/common/manifest.h"
+
+class GURL;
+
+namespace extensions {
+class Extension;
+class ScopedCurrentChannel;
+}
+
+namespace extension_test_util {
+
+// Helpers for loading manifests, |dir| is relative to chrome::DIR_TEST_DATA
+// followed by "extensions".
+scoped_refptr<extensions::Extension> LoadManifestUnchecked(
+ const std::string& dir,
+ const std::string& test_file,
+ extensions::Manifest::Location location,
+ int extra_flags,
+ const std::string& id,
+ std::string* error);
+
+scoped_refptr<extensions::Extension> LoadManifestUnchecked(
+ const std::string& dir,
+ const std::string& test_file,
+ extensions::Manifest::Location location,
+ int extra_flags,
+ std::string* error);
+
+scoped_refptr<extensions::Extension> LoadManifest(
+ const std::string& dir,
+ const std::string& test_file,
+ extensions::Manifest::Location location,
+ int extra_flags);
+
+scoped_refptr<extensions::Extension> LoadManifest(const std::string& dir,
+ const std::string& test_file,
+ int extra_flags);
+
+scoped_refptr<extensions::Extension> LoadManifestStrict(
+ const std::string& dir,
+ const std::string& test_file);
+
+scoped_refptr<extensions::Extension> LoadManifest(const std::string& dir,
+ const std::string& test_file);
+
+void SetGalleryUpdateURL(const GURL& new_url);
+
+// Returns a ScopedCurrentChannel object to use in tests if one is necessary for
+// the given |action_type| specified in the manifest. This will only return
+// non-null if the "action" manifest key is used.
+// TODO(https://crbug.com/893373): Remove this one the "action" key is launched
+// to stable.
+std::unique_ptr<extensions::ScopedCurrentChannel>
+GetOverrideChannelForActionType(extensions::ActionInfo::Type action_type);
+
+} // namespace extension_test_util
+
+#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
diff --git a/chromium/chrome/common/extensions/extension_unittest.cc b/chromium/chrome/common/extensions/extension_unittest.cc
new file mode 100644
index 00000000000..09fe2826445
--- /dev/null
+++ b/chromium/chrome/common/extensions/extension_unittest.cc
@@ -0,0 +1,447 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/command.h"
+#include "chrome/common/extensions/extension_test_util.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handlers/content_scripts_handler.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/value_builder.h"
+#include "extensions/test/test_extension_dir.h"
+#include "net/base/mime_sniffer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "skia/ext/image_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "url/gurl.h"
+
+using extension_test_util::LoadManifest;
+using extension_test_util::LoadManifestStrict;
+using base::FilePath;
+
+namespace extensions {
+
+// We persist location values in the preferences, so this is a sanity test that
+// someone doesn't accidentally change them.
+TEST(ExtensionTest, LocationValuesTest) {
+ ASSERT_EQ(0, Manifest::INVALID_LOCATION);
+ ASSERT_EQ(1, Manifest::INTERNAL);
+ ASSERT_EQ(2, Manifest::EXTERNAL_PREF);
+ ASSERT_EQ(3, Manifest::EXTERNAL_REGISTRY);
+ ASSERT_EQ(4, Manifest::UNPACKED);
+ ASSERT_EQ(5, Manifest::COMPONENT);
+ ASSERT_EQ(6, Manifest::EXTERNAL_PREF_DOWNLOAD);
+ ASSERT_EQ(7, Manifest::EXTERNAL_POLICY_DOWNLOAD);
+ ASSERT_EQ(8, Manifest::COMMAND_LINE);
+ ASSERT_EQ(9, Manifest::EXTERNAL_POLICY);
+}
+
+TEST(ExtensionTest, LocationPriorityTest) {
+ for (int i = 0; i < Manifest::NUM_LOCATIONS; i++) {
+ Manifest::Location loc = static_cast<Manifest::Location>(i);
+
+ // INVALID is not a valid location.
+ if (loc == Manifest::INVALID_LOCATION)
+ continue;
+
+ // Comparing a location that has no rank will hit a CHECK. Do a
+ // compare with every valid location, to be sure each one is covered.
+
+ // Check that no install source can override a componenet extension.
+ ASSERT_EQ(Manifest::COMPONENT,
+ Manifest::GetHigherPriorityLocation(Manifest::COMPONENT, loc));
+ ASSERT_EQ(Manifest::COMPONENT,
+ Manifest::GetHigherPriorityLocation(loc, Manifest::COMPONENT));
+
+ // Check that any source can override a user install. This might change
+ // in the future, in which case this test should be updated.
+ ASSERT_EQ(loc,
+ Manifest::GetHigherPriorityLocation(Manifest::INTERNAL, loc));
+ ASSERT_EQ(loc,
+ Manifest::GetHigherPriorityLocation(loc, Manifest::INTERNAL));
+ }
+
+ // Check a few interesting cases that we know can happen:
+ ASSERT_EQ(Manifest::EXTERNAL_POLICY_DOWNLOAD,
+ Manifest::GetHigherPriorityLocation(
+ Manifest::EXTERNAL_POLICY_DOWNLOAD,
+ Manifest::EXTERNAL_PREF));
+
+ ASSERT_EQ(Manifest::EXTERNAL_PREF,
+ Manifest::GetHigherPriorityLocation(
+ Manifest::INTERNAL,
+ Manifest::EXTERNAL_PREF));
+}
+
+TEST(ExtensionTest, EnsureNewLinesInExtensionNameAreCollapsed) {
+ DictionaryBuilder manifest;
+ std::string unsanitized_name = "Test\n\n\n\n\n\n\n\n\n\n\n\nNew lines\u0085";
+ manifest.Set("name", unsanitized_name)
+ .Set("manifest_version", 2)
+ .Set("description", "some description");
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(manifest.Build())
+ .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+ .Build();
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ("TestNew lines", extension->name());
+ // Ensure that non-localized name is not sanitized.
+ EXPECT_EQ(unsanitized_name, extension->non_localized_name());
+}
+
+TEST(ExtensionTest, EnsureWhitespacesInExtensionNameAreCollapsed) {
+ DictionaryBuilder manifest;
+ std::string unsanitized_name = "Test Whitespace";
+ manifest.Set("name", unsanitized_name)
+ .Set("manifest_version", 2)
+ .Set("description", "some description");
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(manifest.Build())
+ .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+ .Build();
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ("Test Whitespace", extension->name());
+ // Ensure that non-localized name is not sanitized.
+ EXPECT_EQ(unsanitized_name, extension->non_localized_name());
+}
+
+// TODO(crbug.com/794252): Disallow empty extension names from being locally
+// loaded.
+TEST(ExtensionTest, EmptyName) {
+ DictionaryBuilder manifest1;
+ manifest1.Set("name", "")
+ .Set("manifest_version", 2)
+ .Set("description", "some description");
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(manifest1.Build())
+ .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+ .Build();
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ("", extension->name());
+
+ DictionaryBuilder manifest2;
+ manifest2.Set("name", " ")
+ .Set("manifest_version", 2)
+ .Set("description", "some description");
+ extension =
+ ExtensionBuilder()
+ .SetManifest(manifest2.Build())
+ .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+ .Build();
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ("", extension->name());
+}
+
+TEST(ExtensionTest, RTLNameInLTRLocale) {
+ // Test the case when a directional override is the first character.
+ auto run_rtl_test = [](const wchar_t* name, const wchar_t* expected) {
+ SCOPED_TRACE(
+ base::StringPrintf("Name: %ls, Expected: %ls", name, expected));
+ DictionaryBuilder manifest;
+ manifest.Set("name", base::WideToUTF8(name))
+ .Set("manifest_version", 2)
+ .Set("description", "some description")
+ .Set("version",
+ "0.1"); // <NOTE> Moved this here to avoid the MergeManifest call.
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder().SetManifest(manifest.Build()).Build();
+ ASSERT_TRUE(extension);
+ const int kResourceId = IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE;
+ const base::string16 expected_utf16 = base::WideToUTF16(expected);
+ EXPECT_EQ(l10n_util::GetStringFUTF16(kResourceId, expected_utf16),
+ l10n_util::GetStringFUTF16(kResourceId,
+ base::UTF8ToUTF16(extension->name())));
+ EXPECT_EQ(base::WideToUTF8(expected), extension->name());
+ };
+
+ run_rtl_test(L"\x202emoc.elgoog", L"\x202emoc.elgoog\x202c");
+ run_rtl_test(L"\x202egoogle\x202e.com/\x202eguest",
+ L"\x202egoogle\x202e.com/\x202eguest\x202c\x202c\x202c");
+ run_rtl_test(L"google\x202e.com", L"google\x202e.com\x202c");
+
+ run_rtl_test(L"كبير Google التطبيق",
+#if !defined(OS_WIN)
+ L"\x200e\x202bكبير Google التطبيق\x202c\x200e");
+#else
+ // On Windows for an LTR locale, no changes to the string are
+ // made.
+ L"كبير Google التطبيق");
+#endif // !OS_WIN
+}
+
+TEST(ExtensionTest, GetResourceURLAndPath) {
+ scoped_refptr<Extension> extension = LoadManifestStrict("empty_manifest",
+ "empty.json");
+ EXPECT_TRUE(extension.get());
+
+ EXPECT_EQ(extension->url().spec() + "bar/baz.js",
+ Extension::GetResourceURL(extension->url(), "bar/baz.js").spec());
+ EXPECT_EQ(extension->url().spec() + "baz.js",
+ Extension::GetResourceURL(extension->url(),
+ "bar/../baz.js").spec());
+ EXPECT_EQ(extension->url().spec() + "baz.js",
+ Extension::GetResourceURL(extension->url(), "../baz.js").spec());
+
+ // Test that absolute-looking paths ("/"-prefixed) are pasted correctly.
+ EXPECT_EQ(extension->url().spec() + "test.html",
+ extension->GetResourceURL("/test.html").spec());
+}
+
+TEST(ExtensionTest, GetResource) {
+ const FilePath valid_path_test_cases[] = {
+ FilePath(FILE_PATH_LITERAL("manifest.json")),
+ FilePath(FILE_PATH_LITERAL("a/b/c/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("com/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("lpt/manifest.json")),
+ };
+ const FilePath invalid_path_test_cases[] = {
+ // Directory name
+ FilePath(FILE_PATH_LITERAL("src/")),
+ // Contains a drive letter specification.
+ FilePath(FILE_PATH_LITERAL("C:\\manifest.json")),
+ // Use backslash '\\' as separator.
+ FilePath(FILE_PATH_LITERAL("a\\b\\c\\manifest.json")),
+ // Reserved Characters with extension
+ FilePath(FILE_PATH_LITERAL("mani>fest.json")),
+ FilePath(FILE_PATH_LITERAL("mani<fest.json")),
+ FilePath(FILE_PATH_LITERAL("mani*fest.json")),
+ FilePath(FILE_PATH_LITERAL("mani:fest.json")),
+ FilePath(FILE_PATH_LITERAL("mani?fest.json")),
+ FilePath(FILE_PATH_LITERAL("mani|fest.json")),
+ // Reserved Characters without extension
+ FilePath(FILE_PATH_LITERAL("mani>fest")),
+ FilePath(FILE_PATH_LITERAL("mani<fest")),
+ FilePath(FILE_PATH_LITERAL("mani*fest")),
+ FilePath(FILE_PATH_LITERAL("mani:fest")),
+ FilePath(FILE_PATH_LITERAL("mani?fest")),
+ FilePath(FILE_PATH_LITERAL("mani|fest")),
+ // Reserved Names with extension.
+ FilePath(FILE_PATH_LITERAL("com1.json")),
+ FilePath(FILE_PATH_LITERAL("com9.json")),
+ FilePath(FILE_PATH_LITERAL("LPT1.json")),
+ FilePath(FILE_PATH_LITERAL("LPT9.json")),
+ FilePath(FILE_PATH_LITERAL("CON.json")),
+ FilePath(FILE_PATH_LITERAL("PRN.json")),
+ FilePath(FILE_PATH_LITERAL("AUX.json")),
+ FilePath(FILE_PATH_LITERAL("NUL.json")),
+ // Reserved Names without extension.
+ FilePath(FILE_PATH_LITERAL("com1")),
+ FilePath(FILE_PATH_LITERAL("com9")),
+ FilePath(FILE_PATH_LITERAL("LPT1")),
+ FilePath(FILE_PATH_LITERAL("LPT9")),
+ FilePath(FILE_PATH_LITERAL("CON")),
+ FilePath(FILE_PATH_LITERAL("PRN")),
+ FilePath(FILE_PATH_LITERAL("AUX")),
+ FilePath(FILE_PATH_LITERAL("NUL")),
+ // Reserved Names as directory.
+ FilePath(FILE_PATH_LITERAL("com1/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("com9/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("LPT1/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("LPT9/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("CON/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("PRN/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("AUX/manifest.json")),
+ FilePath(FILE_PATH_LITERAL("NUL/manifest.json")),
+ };
+
+ scoped_refptr<Extension> extension = LoadManifestStrict("empty_manifest",
+ "empty.json");
+ EXPECT_TRUE(extension.get());
+ for (size_t i = 0; i < base::size(valid_path_test_cases); ++i)
+ EXPECT_TRUE(!extension->GetResource(valid_path_test_cases[i]).empty());
+ for (size_t i = 0; i < base::size(invalid_path_test_cases); ++i)
+ EXPECT_TRUE(extension->GetResource(invalid_path_test_cases[i]).empty());
+}
+
+TEST(ExtensionTest, GetAbsolutePathNoError) {
+ scoped_refptr<Extension> extension = LoadManifestStrict("absolute_path",
+ "absolute.json");
+ EXPECT_TRUE(extension.get());
+ std::string err;
+ std::vector<InstallWarning> warnings;
+ EXPECT_TRUE(file_util::ValidateExtension(extension.get(), &err, &warnings));
+ EXPECT_EQ(0U, warnings.size());
+
+ EXPECT_EQ(extension->path().AppendASCII("test.html").value(),
+ extension->GetResource("test.html").GetFilePath().value());
+ EXPECT_EQ(extension->path().AppendASCII("test.js").value(),
+ extension->GetResource("test.js").GetFilePath().value());
+}
+
+
+TEST(ExtensionTest, IdIsValid) {
+ EXPECT_TRUE(crx_file::id_util::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
+ EXPECT_TRUE(crx_file::id_util::IdIsValid("pppppppppppppppppppppppppppppppp"));
+ EXPECT_TRUE(crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmnop"));
+ EXPECT_TRUE(crx_file::id_util::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP"));
+ EXPECT_FALSE(crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmno"));
+ EXPECT_FALSE(
+ crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmnopa"));
+ EXPECT_FALSE(
+ crx_file::id_util::IdIsValid("0123456789abcdef0123456789abcdef"));
+ EXPECT_FALSE(
+ crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmnoq"));
+ EXPECT_FALSE(
+ crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmno0"));
+}
+
+// This test ensures that the mimetype sniffing code stays in sync with the
+// actual crx files that we test other parts of the system with.
+TEST(ExtensionTest, MimeTypeSniffing) {
+ auto get_mime_type_from_crx = [](const base::FilePath& file_path) {
+ SCOPED_TRACE(file_path.AsUTF8Unsafe());
+
+ std::string data;
+ EXPECT_TRUE(base::ReadFileToString(file_path, &data));
+
+ std::string result;
+ EXPECT_TRUE(net::SniffMimeType(
+ data.c_str(), data.size(), GURL("http://www.example.com/foo.crx"),
+ std::string(), net::ForceSniffFileUrlsForHtml::kDisabled, &result));
+
+ return result;
+ };
+
+ base::FilePath dir_path;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &dir_path));
+ dir_path = dir_path.AppendASCII("extensions");
+
+ // First, test an extension packed a long time ago (but in this galaxy).
+ // Specifically, this package is using the crx2 format, whereas modern chrome
+ // uses crx3.
+ EXPECT_EQ(
+ Extension::kMimeType,
+ get_mime_type_from_crx(dir_path.AppendASCII("legacy_crx_package.crx")));
+
+ // Then, an extension whose crx has a bad magic number (it should be Cr24).
+ EXPECT_EQ("application/octet-stream",
+ get_mime_type_from_crx(dir_path.AppendASCII("bad_magic.crx")));
+
+ // Finally, an extension that we pack right. This. Instant.
+ // This verifies that the modern extensions Chrome packs are always
+ // recognized as the extension mime type.
+ // Regression test for https://crbug.com/831284.
+ TestExtensionDir test_dir;
+ test_dir.WriteManifest(R"(
+ {
+ "name": "New extension",
+ "version": "0.2",
+ "manifest_version": 2
+ })");
+ EXPECT_EQ(Extension::kMimeType, get_mime_type_from_crx(test_dir.Pack()));
+}
+
+TEST(ExtensionTest, WantsFileAccess) {
+ scoped_refptr<Extension> extension;
+ GURL file_url("file:///etc/passwd");
+
+ // Ignore the policy delegate for this test.
+ PermissionsData::SetPolicyDelegate(NULL);
+
+ // <all_urls> permission
+ extension = LoadManifest("permissions", "permissions_all_urls.json");
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_FALSE(
+ extension->permissions_data()->CanAccessPage(file_url, -1, nullptr));
+ extension = LoadManifest(
+ "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS);
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_TRUE(
+ extension->permissions_data()->CanAccessPage(file_url, -1, nullptr));
+
+ // file:///* permission
+ extension = LoadManifest("permissions", "permissions_file_scheme.json");
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_FALSE(
+ extension->permissions_data()->CanAccessPage(file_url, -1, nullptr));
+ extension = LoadManifest("permissions",
+ "permissions_file_scheme.json",
+ Extension::ALLOW_FILE_ACCESS);
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_TRUE(
+ extension->permissions_data()->CanAccessPage(file_url, -1, nullptr));
+
+ // http://* permission
+ extension = LoadManifest("permissions", "permissions_http_scheme.json");
+ EXPECT_FALSE(extension->wants_file_access());
+ EXPECT_FALSE(
+ extension->permissions_data()->CanAccessPage(file_url, -1, nullptr));
+ extension = LoadManifest("permissions",
+ "permissions_http_scheme.json",
+ Extension::ALLOW_FILE_ACCESS);
+ EXPECT_FALSE(extension->wants_file_access());
+ EXPECT_FALSE(
+ extension->permissions_data()->CanAccessPage(file_url, -1, nullptr));
+
+ // <all_urls> content script match
+ extension = LoadManifest("permissions", "content_script_all_urls.json");
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage(
+ file_url, -1, nullptr));
+ extension = LoadManifest("permissions", "content_script_all_urls.json",
+ Extension::ALLOW_FILE_ACCESS);
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_TRUE(extension->permissions_data()->CanRunContentScriptOnPage(
+ file_url, -1, nullptr));
+
+ // file:///* content script match
+ extension = LoadManifest("permissions", "content_script_file_scheme.json");
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage(
+ file_url, -1, nullptr));
+ extension = LoadManifest("permissions", "content_script_file_scheme.json",
+ Extension::ALLOW_FILE_ACCESS);
+ EXPECT_TRUE(extension->wants_file_access());
+ EXPECT_TRUE(extension->permissions_data()->CanRunContentScriptOnPage(
+ file_url, -1, nullptr));
+
+ // http://* content script match
+ extension = LoadManifest("permissions", "content_script_http_scheme.json");
+ EXPECT_FALSE(extension->wants_file_access());
+ EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage(
+ file_url, -1, nullptr));
+ extension = LoadManifest("permissions", "content_script_http_scheme.json",
+ Extension::ALLOW_FILE_ACCESS);
+ EXPECT_FALSE(extension->wants_file_access());
+ EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage(
+ file_url, -1, nullptr));
+}
+
+TEST(ExtensionTest, ExtraFlags) {
+ scoped_refptr<Extension> extension;
+ extension = LoadManifest("app", "manifest.json", Extension::FROM_WEBSTORE);
+ EXPECT_TRUE(extension->from_webstore());
+
+ extension = LoadManifest("app", "manifest.json", Extension::FROM_BOOKMARK);
+ EXPECT_TRUE(extension->from_bookmark());
+
+ extension = LoadManifest("app", "manifest.json", Extension::NO_FLAGS);
+ EXPECT_FALSE(extension->from_bookmark());
+ EXPECT_FALSE(extension->from_webstore());
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/feature_switch_unittest.cc b/chromium/chrome/common/extensions/feature_switch_unittest.cc
new file mode 100644
index 00000000000..98fc911d2bd
--- /dev/null
+++ b/chromium/chrome/common/extensions/feature_switch_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/feature_switch.h"
+
+#include "base/command_line.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::FeatureSwitch;
+
+namespace {
+
+const char kSwitchName[] = "test-switch";
+
+template<FeatureSwitch::DefaultValue T>
+class FeatureSwitchTest : public testing::Test {
+ public:
+ FeatureSwitchTest()
+ : command_line_(base::CommandLine::NO_PROGRAM),
+ feature_(&command_line_, kSwitchName, T) {}
+ protected:
+ base::CommandLine command_line_;
+ FeatureSwitch feature_;
+};
+
+typedef FeatureSwitchTest<FeatureSwitch::DEFAULT_DISABLED>
+ FeatureSwitchDisabledTest;
+typedef FeatureSwitchTest<FeatureSwitch::DEFAULT_ENABLED>
+ FeatureSwitchEnabledTest;
+
+} // namespace
+
+TEST_F(FeatureSwitchDisabledTest, NoSwitchValue) {
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, FalseSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "0");
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, GibberishSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "monkey");
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, Override) {
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, TrueSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "1");
+ EXPECT_TRUE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, TrimSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, " \t 1\n ");
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, NoSwitchValue) {
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, TrueSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "1");
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, GibberishSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "monkey");
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, Override) {
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, FalseSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "0");
+ EXPECT_FALSE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, TrimSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "\t\t 0 \n");
+ EXPECT_FALSE(feature_.IsEnabled());
+}
diff --git a/chromium/chrome/common/extensions/image_writer/OWNERS b/chromium/chrome/common/extensions/image_writer/OWNERS
new file mode 100644
index 00000000000..22549adc7c9
--- /dev/null
+++ b/chromium/chrome/common/extensions/image_writer/OWNERS
@@ -0,0 +1 @@
+haven@chromium.org
diff --git a/chromium/chrome/common/extensions/image_writer/image_writer_util_mac.cc b/chromium/chrome/common/extensions/image_writer/image_writer_util_mac.cc
new file mode 100644
index 00000000000..62432ee27aa
--- /dev/null
+++ b/chromium/chrome/common/extensions/image_writer/image_writer_util_mac.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/image_writer/image_writer_util_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOBSD.h>
+#include <IOKit/storage/IOMedia.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_ioobject.h"
+#include "base/strings/sys_string_conversions.h"
+
+namespace extensions {
+
+namespace {
+
+bool IsUsbDevice(io_object_t disk_obj) {
+ io_object_t current_obj = disk_obj;
+ io_object_t parent_obj = 0;
+ // Keep scoped object outside the loop so the object lives to the next
+ // GetParentEntry.
+ base::mac::ScopedIOObject<io_object_t> parent_obj_ref(parent_obj);
+
+ while ((IORegistryEntryGetParentEntry(
+ current_obj, kIOServicePlane, &parent_obj)) == KERN_SUCCESS) {
+ current_obj = parent_obj;
+ parent_obj_ref.reset(parent_obj);
+
+ base::ScopedCFTypeRef<CFStringRef> class_name(
+ IOObjectCopyClass(current_obj));
+ if (!class_name) {
+ LOG(ERROR) << "Could not get object class of IO Registry Entry.";
+ continue;
+ }
+
+ if (CFStringCompare(class_name.get(), CFSTR("IOUSBMassStorageClass"), 0) ==
+ kCFCompareEqualTo) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace
+
+bool IsSuitableRemovableStorageDevice(io_object_t disk_obj,
+ std::string* out_bsd_name,
+ uint64_t* out_size_in_bytes,
+ bool* out_removable) {
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> dict;
+ kern_return_t result = IORegistryEntryCreateCFProperties(
+ disk_obj, dict.InitializeInto(), kCFAllocatorDefault, 0);
+ if (result != KERN_SUCCESS) {
+ MACH_LOG(ERROR, result) << "Unable to get properties of disk object.";
+ return false;
+ }
+
+ // Do not allow Core Storage volumes, even though they are marked as "whole
+ // media", as they are entirely contained on a different volume.
+ CFBooleanRef cf_corestorage = base::mac::GetValueFromDictionary<CFBooleanRef>(
+ dict, CFSTR("CoreStorage"));
+ if (cf_corestorage && CFBooleanGetValue(cf_corestorage))
+ return false;
+
+ // Do not allow APFS containers, even though they are marked as "whole
+ // media", as they are entirely contained on a different volume.
+ CFStringRef cf_content =
+ base::mac::GetValueFromDictionary<CFStringRef>(dict, CFSTR("Content"));
+ if (cf_content &&
+ CFStringCompare(cf_content, CFSTR("EF57347C-0000-11AA-AA11-00306543ECAC"),
+ 0) == kCFCompareEqualTo) {
+ return false;
+ }
+
+ CFBooleanRef cf_removable = base::mac::GetValueFromDictionary<CFBooleanRef>(
+ dict, CFSTR(kIOMediaRemovableKey));
+ bool removable = CFBooleanGetValue(cf_removable);
+ bool is_usb = IsUsbDevice(disk_obj);
+
+ if (!removable && !is_usb)
+ return false;
+
+ if (out_size_in_bytes) {
+ CFNumberRef cf_media_size = base::mac::GetValueFromDictionary<CFNumberRef>(
+ dict, CFSTR(kIOMediaSizeKey));
+ if (cf_media_size)
+ CFNumberGetValue(cf_media_size, kCFNumberLongLongType, out_size_in_bytes);
+ else
+ *out_size_in_bytes = 0;
+ }
+
+ if (out_bsd_name) {
+ CFStringRef cf_bsd_name = base::mac::GetValueFromDictionary<CFStringRef>(
+ dict, CFSTR(kIOBSDNameKey));
+ if (out_bsd_name)
+ *out_bsd_name = base::SysCFStringRefToUTF8(cf_bsd_name);
+ else
+ *out_bsd_name = std::string();
+ }
+
+ if (out_removable)
+ *out_removable = removable;
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/image_writer/image_writer_util_mac.h b/chromium/chrome/common/extensions/image_writer/image_writer_util_mac.h
new file mode 100644
index 00000000000..ca11e589422
--- /dev/null
+++ b/chromium/chrome/common/extensions/image_writer/image_writer_util_mac.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_IMAGE_WRITER_IMAGE_WRITER_UTIL_MAC_H_
+#define CHROME_COMMON_EXTENSIONS_IMAGE_WRITER_IMAGE_WRITER_UTIL_MAC_H_
+
+#include <IOKit/IOKitLib.h>
+
+#include <string>
+
+namespace extensions {
+
+// Determines whether the specified disk is suitable for writing an image onto.
+// If this function returns true, it also returns other info values; pass
+// null if those values are not wanted.
+bool IsSuitableRemovableStorageDevice(io_object_t disk_obj,
+ std::string* out_bsd_name,
+ uint64_t* out_size_in_bytes,
+ bool* out_removable);
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_IMAGE_WRITER_IMAGE_WRITER_UTIL_MAC_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.cc b/chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.cc
new file mode 100644
index 00000000000..12b55c1193f
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.cc
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/app_icon_color_info.h"
+
+#include <memory>
+
+#include "base/no_destructor.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/image_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+const AppIconColorInfo& GetAppIconColorInfo(const Extension* extension) {
+ static const base::NoDestructor<AppIconColorInfo> fallback;
+
+ AppIconColorInfo* info = static_cast<AppIconColorInfo*>(
+ extension->GetManifestData(keys::kAppIconColor));
+ return info ? *info : *fallback;
+}
+
+} // namespace
+
+AppIconColorInfo::AppIconColorInfo() : icon_color_(SK_ColorTRANSPARENT) {
+}
+
+AppIconColorInfo::~AppIconColorInfo() {
+}
+
+// static
+SkColor AppIconColorInfo::GetIconColor(const Extension* extension) {
+ return GetAppIconColorInfo(extension).icon_color_;
+}
+
+// static
+const std::string& AppIconColorInfo::GetIconColorString(
+ const Extension* extension) {
+ return GetAppIconColorInfo(extension).icon_color_string_;
+}
+
+AppIconColorHandler::AppIconColorHandler() {
+}
+
+AppIconColorHandler::~AppIconColorHandler() {
+}
+
+bool AppIconColorHandler::Parse(Extension* extension, base::string16* error) {
+ std::unique_ptr<AppIconColorInfo> app_icon_color_info(new AppIconColorInfo);
+
+ const base::Value* temp = NULL;
+ if (extension->manifest()->Get(keys::kAppIconColor, &temp)) {
+ if (!temp->GetAsString(&app_icon_color_info->icon_color_string_)) {
+ *error =
+ base::UTF8ToUTF16(extensions::manifest_errors::kInvalidAppIconColor);
+ return false;
+ }
+
+ if (!image_util::ParseHexColorString(
+ app_icon_color_info->icon_color_string_,
+ &app_icon_color_info->icon_color_)) {
+ *error =
+ base::UTF8ToUTF16(extensions::manifest_errors::kInvalidAppIconColor);
+ return false;
+ }
+ }
+
+ extension->SetManifestData(keys::kAppIconColor,
+ std::move(app_icon_color_info));
+ return true;
+}
+
+base::span<const char* const> AppIconColorHandler::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kAppIconColor};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.h b/chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.h
new file mode 100644
index 00000000000..41348e98bda
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_icon_color_info.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_ICON_COLOR_INFO_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_ICON_COLOR_INFO_H_
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace extensions {
+
+// A structure to hold the parsed app icon color data.
+struct AppIconColorInfo : public Extension::ManifestData {
+ AppIconColorInfo();
+ ~AppIconColorInfo() override;
+
+ static SkColor GetIconColor(const Extension* extension);
+ static const std::string& GetIconColorString(const Extension* extension);
+
+ // The color to use if icons need to be generated for the app.
+ SkColor icon_color_;
+
+ // The string representation of the icon color.
+ std::string icon_color_string_;
+};
+
+// Parses the "app.icon_color" manifest key.
+class AppIconColorHandler : public ManifestHandler {
+ public:
+ AppIconColorHandler();
+ ~AppIconColorHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(AppIconColorHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_ICON_COLOR_INFO_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_launch_info.cc b/chromium/chrome/common/extensions/manifest_handlers/app_launch_info.cc
new file mode 100644
index 00000000000..7e12fb7cf64
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_launch_info.cc
@@ -0,0 +1,330 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/url_constants.h"
+#include "components/cloud_devices/common/cloud_devices_urls.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace values = manifest_values;
+namespace errors = manifest_errors;
+
+namespace {
+
+bool ReadLaunchDimension(const extensions::Manifest* manifest,
+ const char* key,
+ int* target,
+ bool is_valid_container,
+ base::string16* error) {
+ const base::Value* temp = NULL;
+ if (manifest->Get(key, &temp)) {
+ if (!is_valid_container) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValueContainer,
+ key);
+ return false;
+ }
+ if (!temp->GetAsInteger(target) || *target < 0) {
+ *target = 0;
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValue,
+ key);
+ return false;
+ }
+ }
+ return true;
+}
+
+static base::LazyInstance<AppLaunchInfo>::DestructorAtExit
+ g_empty_app_launch_info = LAZY_INSTANCE_INITIALIZER;
+
+const AppLaunchInfo& GetAppLaunchInfo(const Extension* extension) {
+ AppLaunchInfo* info = static_cast<AppLaunchInfo*>(
+ extension->GetManifestData(keys::kLaunch));
+ return info ? *info : g_empty_app_launch_info.Get();
+}
+
+} // namespace
+
+AppLaunchInfo::AppLaunchInfo()
+ : launch_container_(LaunchContainer::kLaunchContainerTab),
+ launch_width_(0),
+ launch_height_(0) {}
+
+AppLaunchInfo::~AppLaunchInfo() {
+}
+
+// static
+const std::string& AppLaunchInfo::GetLaunchLocalPath(
+ const Extension* extension) {
+ return GetAppLaunchInfo(extension).launch_local_path_;
+}
+
+// static
+const GURL& AppLaunchInfo::GetLaunchWebURL(
+ const Extension* extension) {
+ return GetAppLaunchInfo(extension).launch_web_url_;
+}
+
+// static
+extensions::LaunchContainer AppLaunchInfo::GetLaunchContainer(
+ const Extension* extension) {
+ return GetAppLaunchInfo(extension).launch_container_;
+}
+
+// static
+int AppLaunchInfo::GetLaunchWidth(const Extension* extension) {
+ return GetAppLaunchInfo(extension).launch_width_;
+}
+
+// static
+int AppLaunchInfo::GetLaunchHeight(const Extension* extension) {
+ return GetAppLaunchInfo(extension).launch_height_;
+}
+
+// static
+GURL AppLaunchInfo::GetFullLaunchURL(const Extension* extension) {
+ const AppLaunchInfo& info = GetAppLaunchInfo(extension);
+ if (info.launch_local_path_.empty())
+ return info.launch_web_url_;
+ else
+ return extension->url().Resolve(info.launch_local_path_);
+}
+
+bool AppLaunchInfo::Parse(Extension* extension, base::string16* error) {
+ if (!LoadLaunchURL(extension, error) ||
+ !LoadLaunchContainer(extension, error))
+ return false;
+ return true;
+}
+
+bool AppLaunchInfo::LoadLaunchURL(Extension* extension, base::string16* error) {
+ const base::Value* temp = NULL;
+
+ // Launch URL can be either local (to chrome-extension:// root) or an absolute
+ // web URL.
+ if (extension->manifest()->Get(keys::kLaunchLocalPath, &temp)) {
+ if (extension->manifest()->Get(keys::kLaunchWebURL, NULL)) {
+ *error = base::ASCIIToUTF16(errors::kLaunchPathAndURLAreExclusive);
+ return false;
+ }
+
+ if (extension->manifest()->Get(keys::kWebURLs, NULL)) {
+ *error = base::ASCIIToUTF16(errors::kLaunchPathAndExtentAreExclusive);
+ return false;
+ }
+
+ std::string launch_path;
+ if (!temp->GetAsString(&launch_path)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchLocalPath);
+ return false;
+ }
+
+ // Ensure the launch path is a valid relative URL.
+ GURL resolved = extension->url().Resolve(launch_path);
+ if (!resolved.is_valid() || resolved.GetOrigin() != extension->url()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchLocalPath);
+ return false;
+ }
+
+ launch_local_path_ = launch_path;
+ } else if (extension->manifest()->Get(keys::kLaunchWebURL, &temp)) {
+ std::string launch_url;
+ if (!temp->GetAsString(&launch_url)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL);
+ return false;
+ }
+
+ // Ensure the launch web URL is a valid absolute URL and web extent scheme.
+ GURL url(launch_url);
+ URLPattern pattern(Extension::kValidWebExtentSchemes);
+ if (extension->from_bookmark()) {
+ // System Web Apps are bookmark apps that point to chrome:// URLs.
+ int valid_schemes = Extension::kValidBookmarkAppSchemes;
+ if (extension->location() == Manifest::EXTERNAL_COMPONENT)
+ valid_schemes |= URLPattern::SCHEME_CHROMEUI;
+ pattern.SetValidSchemes(valid_schemes);
+ }
+ if ((!url.is_valid() || !pattern.SetScheme(url.scheme()))) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL);
+ return false;
+ }
+
+ launch_web_url_ = url;
+ } else if (extension->is_legacy_packaged_app()) {
+ *error = base::ASCIIToUTF16(errors::kLaunchURLRequired);
+ return false;
+ }
+
+ // For the Chrome component app, override launch url to new tab.
+ if (extension->id() == extension_misc::kChromeAppId) {
+ launch_web_url_ = GURL(chrome::kChromeUINewTabURL);
+ return true;
+ }
+
+ // If there is no extent, we default the extent based on the launch URL.
+ // Skip this step if the extension is from a bookmark app, as they are
+ // permissionless.
+ if (extension->web_extent().is_empty() && !launch_web_url_.is_empty() &&
+ !extension->from_bookmark()) {
+ URLPattern pattern(Extension::kValidWebExtentSchemes);
+ if (!pattern.SetScheme("*")) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL);
+ return false;
+ }
+ pattern.SetHost(launch_web_url_.host());
+ pattern.SetPath("/*");
+ extension->AddWebExtentPattern(pattern);
+ }
+
+ // In order for the --apps-gallery-url switch to work with the gallery
+ // process isolation, we must insert any provided value into the component
+ // app's launch url and web extent.
+ if (extension->id() == extensions::kWebStoreAppId) {
+ std::string gallery_url_str =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kAppsGalleryURL);
+
+ // Empty string means option was not used.
+ if (!gallery_url_str.empty()) {
+ GURL gallery_url(gallery_url_str);
+ OverrideLaunchURL(extension, gallery_url);
+ }
+ } else if (extension->id() == extension_misc::kCloudPrintAppId) {
+ // In order for the --type=service switch to work, we must update the launch
+ // URL and web extent.
+ GURL url =
+ cloud_devices::GetCloudPrintRelativeURL("enable_chrome_connector");
+ if (!url.is_empty()) {
+ OverrideLaunchURL(extension, url);
+ }
+ }
+
+ return true;
+}
+
+bool AppLaunchInfo::LoadLaunchContainer(Extension* extension,
+ base::string16* error) {
+ const base::Value* tmp_launcher_container = NULL;
+ if (!extension->manifest()->Get(keys::kLaunchContainer,
+ &tmp_launcher_container))
+ return true;
+
+ std::string launch_container_string;
+ if (!tmp_launcher_container->GetAsString(&launch_container_string)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidLaunchContainer);
+ return false;
+ }
+
+ if (launch_container_string == values::kLaunchContainerPanelDeprecated) {
+ launch_container_ = LaunchContainer::kLaunchContainerPanelDeprecated;
+ } else if (launch_container_string == values::kLaunchContainerTab) {
+ launch_container_ = LaunchContainer::kLaunchContainerTab;
+ } else {
+ *error = base::ASCIIToUTF16(errors::kInvalidLaunchContainer);
+ return false;
+ }
+
+ // TODO(manucornet): Remove this special behavior now that panels are
+ // deprecated.
+ bool can_specify_initial_size =
+ launch_container_ == LaunchContainer::kLaunchContainerPanelDeprecated;
+
+ // Validate the container width if present.
+ if (!ReadLaunchDimension(extension->manifest(),
+ keys::kLaunchWidth,
+ &launch_width_,
+ can_specify_initial_size,
+ error)) {
+ return false;
+ }
+
+ // Validate container height if present.
+ if (!ReadLaunchDimension(extension->manifest(),
+ keys::kLaunchHeight,
+ &launch_height_,
+ can_specify_initial_size,
+ error)) {
+ return false;
+ }
+
+ return true;
+}
+
+void AppLaunchInfo::OverrideLaunchURL(Extension* extension,
+ GURL override_url) {
+ if (!override_url.is_valid()) {
+ DLOG(WARNING) << "Invalid override url given for " << extension->name();
+ return;
+ }
+ if (override_url.has_port()) {
+ DLOG(WARNING) << "Override URL passed for " << extension->name()
+ << " should not contain a port. Removing it.";
+
+ GURL::Replacements remove_port;
+ remove_port.ClearPort();
+ override_url = override_url.ReplaceComponents(remove_port);
+ }
+
+ launch_web_url_ = override_url;
+
+ URLPattern pattern(Extension::kValidWebExtentSchemes);
+ URLPattern::ParseResult result = pattern.Parse(override_url.spec());
+ DCHECK_EQ(result, URLPattern::ParseResult::kSuccess);
+ pattern.SetPath(pattern.path() + '*');
+ extension->AddWebExtentPattern(pattern);
+}
+
+AppLaunchManifestHandler::AppLaunchManifestHandler() {
+}
+
+AppLaunchManifestHandler::~AppLaunchManifestHandler() {
+}
+
+bool AppLaunchManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ std::unique_ptr<AppLaunchInfo> info(new AppLaunchInfo);
+ if (!info->Parse(extension, error))
+ return false;
+ extension->SetManifestData(keys::kLaunch, std::move(info));
+ return true;
+}
+
+bool AppLaunchManifestHandler::AlwaysParseForType(Manifest::Type type) const {
+ return type == Manifest::TYPE_LEGACY_PACKAGED_APP;
+}
+
+base::span<const char* const> AppLaunchManifestHandler::Keys() const {
+ static constexpr const char* kKeys[] = {
+ keys::kLaunchLocalPath, keys::kLaunchWebURL, keys::kLaunchContainer,
+ keys::kLaunchHeight, keys::kLaunchWidth};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_launch_info.h b/chromium/chrome/common/extensions/manifest_handlers/app_launch_info.h
new file mode 100644
index 00000000000..6bfd10e7b94
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_launch_info.h
@@ -0,0 +1,85 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_LAUNCH_INFO_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_LAUNCH_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+// Container that holds the parsed app launch data.
+class AppLaunchInfo : public Extension::ManifestData {
+ public:
+ AppLaunchInfo();
+ ~AppLaunchInfo() override;
+
+ // Get the local path inside the extension to use with the launcher.
+ static const std::string& GetLaunchLocalPath(const Extension* extension);
+
+ // Get the absolute web url to use with the launcher.
+ static const GURL& GetLaunchWebURL(const Extension* extension);
+
+ // The window type that an app's manifest specifies to launch into.
+ // This is not always the window type an app will open into, because
+ // users can override the way each app launches. See
+ // ExtensionPrefs::GetLaunchContainer(), which looks at a per-app pref
+ // to decide what container an app will launch in.
+ static LaunchContainer GetLaunchContainer(
+ const Extension* extension);
+
+ // The default size of the container when launching. Only respected for
+ // containers like panels and windows.
+ static int GetLaunchWidth(const Extension* extension);
+ static int GetLaunchHeight(const Extension* extension);
+
+ // Get the fully resolved absolute launch URL.
+ static GURL GetFullLaunchURL(const Extension* extension);
+
+ bool Parse(Extension* extension, base::string16* error);
+
+ private:
+ bool LoadLaunchURL(Extension* extension, base::string16* error);
+ bool LoadLaunchContainer(Extension* extension, base::string16* error);
+ void OverrideLaunchURL(Extension* extension, GURL override_url);
+
+ std::string launch_local_path_;
+
+ GURL launch_web_url_;
+
+ LaunchContainer launch_container_;
+
+ int launch_width_;
+ int launch_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppLaunchInfo);
+};
+
+// Parses all app launch related keys in the manifest.
+class AppLaunchManifestHandler : public ManifestHandler {
+ public:
+ AppLaunchManifestHandler();
+ ~AppLaunchManifestHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(AppLaunchManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_LAUNCH_INFO_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.cc b/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.cc
new file mode 100644
index 00000000000..aaf0eca56e9
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.cc
@@ -0,0 +1,68 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/app_theme_color_info.h"
+
+#include <memory>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/image_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+AppThemeColorInfo::AppThemeColorInfo() {}
+
+AppThemeColorInfo::~AppThemeColorInfo() {}
+
+// static
+base::Optional<SkColor> AppThemeColorInfo::GetThemeColor(
+ const Extension* extension) {
+ AppThemeColorInfo* info = static_cast<AppThemeColorInfo*>(
+ extension->GetManifestData(keys::kAppThemeColor));
+ return info ? info->theme_color : base::Optional<SkColor>();
+}
+
+AppThemeColorHandler::AppThemeColorHandler() {}
+
+AppThemeColorHandler::~AppThemeColorHandler() {}
+
+bool AppThemeColorHandler::Parse(Extension* extension, base::string16* error) {
+ std::string theme_color_string;
+ SkColor theme_color = SK_ColorTRANSPARENT;
+ if (!extension->manifest()->GetString(keys::kAppThemeColor,
+ &theme_color_string) ||
+ !image_util::ParseRgbColorString(theme_color_string, &theme_color)) {
+ *error = base::UTF8ToUTF16(errors::kInvalidAppThemeColor);
+ return false;
+ }
+
+ // Currently, only allow the theme_color key for bookmark apps. We'll add
+ // an install warning in Validate().
+ if (!extension->from_bookmark()) {
+ extension->AddInstallWarning(
+ InstallWarning(errors::kInvalidThemeColorAppType));
+ return true;
+ }
+
+ auto app_theme_color_info = std::make_unique<AppThemeColorInfo>();
+ app_theme_color_info->theme_color = static_cast<SkColor>(theme_color);
+ extension->SetManifestData(keys::kAppThemeColor,
+ std::move(app_theme_color_info));
+
+ return true;
+}
+
+base::span<const char* const> AppThemeColorHandler::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kAppThemeColor};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.h b/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.h
new file mode 100644
index 00000000000..b8984825068
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_info.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_THEME_COLOR_INFO_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_THEME_COLOR_INFO_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace extensions {
+
+// A structure to hold the parsed app theme color data.
+struct AppThemeColorInfo : public Extension::ManifestData {
+ AppThemeColorInfo();
+ ~AppThemeColorInfo() override;
+
+ static base::Optional<SkColor> GetThemeColor(const Extension* extension);
+
+ // The color to use for the browser frame.
+ base::Optional<SkColor> theme_color;
+};
+
+// Parses the "app.theme_color" manifest key.
+class AppThemeColorHandler : public ManifestHandler {
+ public:
+ AppThemeColorHandler();
+ ~AppThemeColorHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(AppThemeColorHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_APP_THEME_COLOR_INFO_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_manifest_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_manifest_unittest.cc
new file mode 100644
index 00000000000..92e47056e14
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/app_theme_color_manifest_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class ThemeColorMatchesManifestTest : public ChromeManifestTest {};
+
+TEST_F(ThemeColorMatchesManifestTest, ThemeColor) {
+ Testcase testcases[] = {
+ Testcase("theme_color.json", std::string(),
+ extensions::Manifest::INTERNAL, Extension::FROM_BOOKMARK),
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_SUCCESS);
+
+ Testcase failure_testcases[] = {
+ Testcase("theme_color_wrong_type.json",
+ extensions::manifest_errors::kInvalidAppThemeColor),
+ };
+ RunTestcases(failure_testcases, base::size(failure_testcases),
+ EXPECT_TYPE_ERROR);
+
+ Testcase warning_testcases[] = {
+ Testcase("theme_color.json",
+ extensions::manifest_errors::kInvalidThemeColorAppType),
+ };
+ RunTestcases(warning_testcases, base::size(warning_testcases),
+ EXPECT_TYPE_WARNING);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/automation_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/automation_unittest.cc
new file mode 100644
index 00000000000..9b45a321ff5
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/automation_unittest.cc
@@ -0,0 +1,292 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/automation.h"
+#include "extensions/common/permissions/permission_message_test_util.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+class AutomationManifestTest : public ChromeManifestTest {
+ public:
+ AutomationManifestTest() : channel_(version_info::Channel::UNKNOWN) {}
+
+ protected:
+ AutomationInfo* GetAutomationInfo(scoped_refptr<Extension> extension) {
+ return static_cast<AutomationInfo*>(
+ extension->GetManifestData(manifest_keys::kAutomation));
+ }
+
+ private:
+ ScopedCurrentChannel channel_;
+};
+
+TEST_F(AutomationManifestTest, AsBooleanFalse) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_boolean_false.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_FALSE(info);
+}
+
+TEST_F(AutomationManifestTest, AsBooleanTrue) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_boolean_true.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ "Read and change your data on www.google.com"));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, InteractTrue) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_interact_true.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ "Read and change your data on www.google.com"));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_TRUE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, Matches) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "automation_matches.json",
+ ErrorUtils::FormatErrorMessage(
+ automation_errors::kErrorInvalidMatch, "www.badpattern.com",
+ URLPattern::GetParseResultString(
+ URLPattern::ParseResult::kMissingSchemeSeparator)));
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ "Read your data on www.google.com and www.twitter.com"));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_FALSE(info->matches.is_empty());
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com")));
+
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://www.bing.com/")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://www.bing.com")));
+}
+
+TEST_F(AutomationManifestTest, MatchesAndPermissions) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_matches_and_permissions.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(
+ VerifyTwoPermissionMessages(extension->permissions_data(),
+ "Read and change your data on www.google.com",
+ "Read your data on www.twitter.com", false));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_FALSE(info->matches.is_empty());
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com")));
+}
+
+TEST_F(AutomationManifestTest, EmptyMatches) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectWarning("automation_empty_matches.json",
+ automation_errors::kErrorNoMatchesProvided);
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, NoValidMatches) {
+ std::string error;
+ scoped_refptr<Extension> extension =
+ LoadExtension(ManifestData("automation_no_valid_matches.json"), &error);
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ("", error);
+ EXPECT_EQ(2u, extension->install_warnings().size());
+ EXPECT_EQ(ErrorUtils::FormatErrorMessage(
+ automation_errors::kErrorInvalidMatch, "www.badpattern.com",
+ URLPattern::GetParseResultString(
+ URLPattern::ParseResult::kMissingSchemeSeparator)),
+ extension->install_warnings()[0].message);
+ EXPECT_EQ(automation_errors::kErrorNoMatchesProvided,
+ extension->install_warnings()[1].message);
+
+ EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, DesktopFalse) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_desktop_false.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ "Read and change your data on www.google.com"));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, DesktopTrue) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_desktop_true.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_TRUE(info->desktop);
+ EXPECT_TRUE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, Desktop_InteractTrue) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_desktop_interact_true.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_TRUE(info->desktop);
+ EXPECT_TRUE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, Desktop_InteractFalse) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectWarning("automation_desktop_interact_false.json",
+ automation_errors::kErrorDesktopTrueInteractFalse);
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_TRUE(info->desktop);
+ EXPECT_TRUE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, Desktop_MatchesSpecified) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "automation_desktop_matches_specified.json",
+ automation_errors::kErrorDesktopTrueMatchesSpecified);
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_TRUE(info->desktop);
+ EXPECT_TRUE(info->interact);
+ EXPECT_TRUE(info->matches.is_empty());
+}
+
+TEST_F(AutomationManifestTest, AllHostsInteractFalse) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_all_hosts_interact_false.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ l10n_util::GetStringUTF16(
+ IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS_READ_ONLY)));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_FALSE(info->interact);
+ EXPECT_FALSE(info->matches.is_empty());
+ EXPECT_TRUE(info->matches.MatchesAllURLs());
+}
+
+TEST_F(AutomationManifestTest, AllHostsInteractTrue) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("automation_all_hosts_interact_true.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyOnePermissionMessage(
+ extension->permissions_data(),
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
+
+ const AutomationInfo* info = AutomationInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_FALSE(info->desktop);
+ EXPECT_TRUE(info->interact);
+ EXPECT_FALSE(info->matches.is_empty());
+ EXPECT_TRUE(info->matches.MatchesAllURLs());
+}
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
new file mode 100644
index 00000000000..0cf96ce9e76
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "chrome/common/webui_url_constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/content_scripts_handler.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+class ContentScriptsManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(ContentScriptsManifestTest, MatchPattern) {
+ Testcase testcases[] = {
+ // chrome:// urls are not allowed.
+ Testcase("content_script_chrome_url_invalid.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidMatch, base::NumberToString(0),
+ base::NumberToString(0),
+ URLPattern::GetParseResultString(
+ URLPattern::ParseResult::kInvalidScheme))),
+
+ // Match paterns must be strings.
+ Testcase("content_script_match_pattern_not_string.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidMatch, base::NumberToString(0),
+ base::NumberToString(0), errors::kExpectString))};
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+
+ LoadAndExpectSuccess("ports_in_content_scripts.json");
+}
+
+TEST_F(ContentScriptsManifestTest, OnChromeUrlsWithFlag) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kExtensionsOnChromeURLs);
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("content_script_chrome_url_invalid.json");
+ const GURL newtab_url(chrome::kChromeUINewTabURL);
+ EXPECT_TRUE(
+ ContentScriptsInfo::ExtensionHasScriptAtURL(extension.get(), newtab_url));
+}
+
+TEST_F(ContentScriptsManifestTest, ScriptableHosts) {
+ // TODO(yoz): Test GetScriptableHosts.
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("content_script_yahoo.json");
+ URLPatternSet scriptable_hosts =
+ ContentScriptsInfo::GetScriptableHosts(extension.get());
+
+ URLPatternSet expected;
+ expected.AddPattern(
+ URLPattern(URLPattern::SCHEME_HTTP, "http://yahoo.com/*"));
+
+ EXPECT_EQ(expected, scriptable_hosts);
+}
+
+TEST_F(ContentScriptsManifestTest, ContentScriptIds) {
+ scoped_refptr<Extension> extension1 =
+ LoadAndExpectSuccess("content_script_yahoo.json");
+ scoped_refptr<Extension> extension2 =
+ LoadAndExpectSuccess("content_script_yahoo.json");
+ const UserScriptList& user_scripts1 =
+ ContentScriptsInfo::GetContentScripts(extension1.get());
+ ASSERT_EQ(1u, user_scripts1.size());
+ int id = user_scripts1[0]->id();
+ const UserScriptList& user_scripts2 =
+ ContentScriptsInfo::GetContentScripts(extension2.get());
+ ASSERT_EQ(1u, user_scripts2.size());
+ // The id of the content script should be one higher than the previous.
+ EXPECT_EQ(id + 1, user_scripts2[0]->id());
+}
+
+TEST_F(ContentScriptsManifestTest, FailLoadingNonUTF8Scripts) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+ install_dir = install_dir.AppendASCII("extensions")
+ .AppendASCII("bad")
+ .AppendASCII("bad_encoding");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ ASSERT_TRUE(extension.get() == NULL);
+ ASSERT_STREQ(
+ "Could not load file 'bad_encoding.js' for content script. "
+ "It isn't UTF-8 encoded.",
+ error.c_str());
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc
new file mode 100644
index 00000000000..f3dbc6d1cc0
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class ExcludeMatchesManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(ExcludeMatchesManifestTest, ExcludeMatchPatterns) {
+ Testcase testcases[] = {
+ Testcase("exclude_matches.json"),
+ Testcase("exclude_matches_empty.json")
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_SUCCESS);
+
+ Testcase testcases2[] = {
+ Testcase("exclude_matches_not_list.json",
+ "Invalid value for 'content_scripts[0].exclude_matches'."),
+ Testcase("exclude_matches_invalid_host.json",
+ "Invalid value for 'content_scripts[0].exclude_matches[0]': "
+ "Invalid host wildcard.")
+ };
+ RunTestcases(testcases2, base::size(testcases2), EXPECT_TYPE_ERROR);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.cc b/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
new file mode 100644
index 00000000000..07307416cda
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/extension_action_handler.h"
+
+#include <memory>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/image_util.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+ExtensionActionHandler::ExtensionActionHandler() {
+}
+
+ExtensionActionHandler::~ExtensionActionHandler() {
+}
+
+bool ExtensionActionHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const char* key = nullptr;
+ const char* error_key = nullptr;
+ ActionInfo::Type type = ActionInfo::TYPE_ACTION;
+ if (extension->manifest()->HasKey(manifest_keys::kAction)) {
+ key = manifest_keys::kAction;
+ error_key = manifest_errors::kInvalidAction;
+ // type ACTION is correct.
+ }
+
+ if (extension->manifest()->HasKey(manifest_keys::kPageAction)) {
+ if (key != nullptr) {
+ // An extension can only have one action.
+ *error = base::ASCIIToUTF16(manifest_errors::kOneUISurfaceOnly);
+ return false;
+ }
+ key = manifest_keys::kPageAction;
+ error_key = manifest_errors::kInvalidPageAction;
+ type = ActionInfo::TYPE_PAGE;
+ }
+
+ if (extension->manifest()->HasKey(manifest_keys::kBrowserAction)) {
+ if (key != nullptr) {
+ // An extension can only have one action.
+ *error = base::ASCIIToUTF16(manifest_errors::kOneUISurfaceOnly);
+ return false;
+ }
+ key = manifest_keys::kBrowserAction;
+ error_key = manifest_errors::kInvalidBrowserAction;
+ type = ActionInfo::TYPE_BROWSER;
+ }
+
+ if (key) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!extension->manifest()->GetDictionary(key, &dict)) {
+ *error = base::ASCIIToUTF16(error_key);
+ return false;
+ }
+
+ std::unique_ptr<ActionInfo> action_info =
+ ActionInfo::Load(extension, type, dict, error);
+ if (!action_info)
+ return false; // Failed to parse extension action definition.
+
+ switch (type) {
+ case ActionInfo::TYPE_ACTION:
+ ActionInfo::SetExtensionActionInfo(extension, std::move(action_info));
+ break;
+ case ActionInfo::TYPE_PAGE:
+ ActionInfo::SetPageActionInfo(extension, std::move(action_info));
+ break;
+ case ActionInfo::TYPE_BROWSER:
+ ActionInfo::SetBrowserActionInfo(extension, std::move(action_info));
+ break;
+ }
+ } else { // No key, used for synthesizing an action for extensions with none.
+ if (Manifest::IsComponentLocation(extension->location()))
+ return true; // Don't synthesize actions for component extensions.
+ if (extension->was_installed_by_default())
+ return true; // Don't synthesize actions for default extensions.
+
+ // Set an empty page action. We use a page action (instead of a browser
+ // action) because the action should not be seen as enabled on every page.
+ auto action_info = std::make_unique<ActionInfo>(ActionInfo::TYPE_PAGE);
+ action_info->synthesized = true;
+ ActionInfo::SetPageActionInfo(extension, std::move(action_info));
+ }
+
+ return true;
+}
+
+bool ExtensionActionHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ int error_message = 0;
+ const ActionInfo* action = ActionInfo::GetPageActionInfo(extension);
+ if (action) {
+ error_message = IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED;
+ } else {
+ action = ActionInfo::GetBrowserActionInfo(extension);
+ error_message = IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED;
+ }
+
+ // Analyze the icons for visibility using the default toolbar color, since
+ // the majority of Chrome users don't modify their theme.
+ if (action && !action->default_icon.empty() &&
+ !file_util::ValidateExtensionIconSet(
+ action->default_icon, extension, error_message,
+ image_util::kDefaultToolbarColor, error)) {
+ return false;
+ }
+ return true;
+}
+
+bool ExtensionActionHandler::AlwaysParseForType(Manifest::Type type) const {
+ return type == Manifest::TYPE_EXTENSION || type == Manifest::TYPE_USER_SCRIPT;
+}
+
+base::span<const char* const> ExtensionActionHandler::Keys() const {
+ static constexpr const char* kKeys[] = {
+ manifest_keys::kPageAction,
+ manifest_keys::kBrowserAction,
+ };
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.h b/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.h
new file mode 100644
index 00000000000..295c4247f90
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_EXTENSION_ACTION_HANDLER_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_EXTENSION_ACTION_HANDLER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// Parses the "page_action" and "browser_action" manifest keys.
+class ExtensionActionHandler : public ManifestHandler {
+ public:
+ ExtensionActionHandler();
+ ~ExtensionActionHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ bool AlwaysParseForType(Manifest::Type type) const override;
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionActionHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_EXTENSION_ACTION_HANDLER_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc
new file mode 100644
index 00000000000..ac0b3f7187a
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/extension_action_handler.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
+#include "components/version_info/channel.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+// TODO(devlin): We don't need this separate enum now that SystemIndicator is
+// no longer part of ActionInfo.
+enum class TestActionType {
+ kBrowserAction,
+ kPageAction,
+ kAction,
+};
+
+base::FilePath GetTestDataDir() {
+ base::FilePath path;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &path);
+ return path.AppendASCII("extensions").AppendASCII("manifest_handlers");
+}
+
+} // namespace
+
+// Tests that an unpacked extension with an invisible browser action
+// default icon fails as expected.
+TEST(ExtensionActionHandlerTest, LoadInvisibleBrowserActionIconUnpacked) {
+ base::FilePath extension_dir =
+ GetTestDataDir().AppendASCII("browser_action_invisible_icon");
+ // Set the flag that enables the error.
+ file_util::SetReportErrorForInvisibleIconForTesting(true);
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ extension_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ file_util::SetReportErrorForInvisibleIconForTesting(false);
+ EXPECT_FALSE(extension);
+ EXPECT_EQ("The icon is not sufficiently visible 'invisible_icon.png'.",
+ error);
+}
+
+// Tests that an unpacked extension with an invisible page action
+// default icon fails as expected.
+TEST(ExtensionActionHandlerTest, LoadInvisiblePageActionIconUnpacked) {
+ base::FilePath extension_dir =
+ GetTestDataDir().AppendASCII("page_action_invisible_icon");
+ // Set the flag that enables the error.
+ file_util::SetReportErrorForInvisibleIconForTesting(true);
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ extension_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ file_util::SetReportErrorForInvisibleIconForTesting(false);
+ EXPECT_FALSE(extension);
+ EXPECT_EQ("The icon is not sufficiently visible 'invisible_icon.png'.",
+ error);
+}
+
+// A parameterized test suite to test each different extension action key
+// ("page_action", "browser_action", "action").
+class ExtensionActionManifestTest
+ : public ManifestTest,
+ public testing::WithParamInterface<TestActionType> {
+ public:
+ ExtensionActionManifestTest() {}
+ ~ExtensionActionManifestTest() override {}
+
+ // Constructs and returns a ManifestData object with the provided
+ // |action_spec|.
+ ManifestData GetManifestData(const char* action_spec) {
+ constexpr char kManifestStub[] =
+ R"({
+ "name": "Test",
+ "manifest_version": 2,
+ "version": "0.1",
+ "%s": %s
+ })";
+
+ const char* action_key = nullptr;
+ switch (GetParam()) {
+ case TestActionType::kBrowserAction:
+ action_key = manifest_keys::kBrowserAction;
+ break;
+ case TestActionType::kPageAction:
+ action_key = manifest_keys::kPageAction;
+ break;
+ case TestActionType::kAction:
+ action_key = manifest_keys::kAction;
+ break;
+ }
+
+ base::Value manifest_value = base::test::ParseJson(
+ base::StringPrintf(kManifestStub, action_key, action_spec));
+ EXPECT_TRUE(manifest_value.is_dict());
+ EXPECT_FALSE(manifest_value.is_none());
+ return ManifestData(std::move(manifest_value), "test");
+ }
+
+ // Returns the ActionInfo for the given |extension| corresponding with the
+ // action key of the test param.
+ const ActionInfo* GetActionInfo(const Extension& extension) {
+ const ActionInfo* action_info = nullptr;
+ switch (GetParam()) {
+ case TestActionType::kBrowserAction:
+ action_info = ActionInfo::GetBrowserActionInfo(&extension);
+ break;
+ case TestActionType::kPageAction:
+ action_info = ActionInfo::GetPageActionInfo(&extension);
+ break;
+ case TestActionType::kAction:
+ action_info = ActionInfo::GetExtensionActionInfo(&extension);
+ break;
+ }
+
+ return action_info;
+ }
+
+ private:
+ // The "action" key is restricted to trunk.
+ ScopedCurrentChannel scoped_channel_{version_info::Channel::UNKNOWN};
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionActionManifestTest);
+};
+
+// Tests that parsing an action succeeds and properly populates the given
+// fields.
+TEST_P(ExtensionActionManifestTest, Basic) {
+ constexpr char kValidAllFields[] =
+ R"({
+ "default_popup": "popup.html",
+ "default_title": "Title",
+ "default_icon": "icon.png"
+ })";
+ scoped_refptr<const Extension> extension =
+ LoadAndExpectSuccess(GetManifestData(kValidAllFields));
+ ASSERT_TRUE(extension);
+ const ActionInfo* action_info = GetActionInfo(*extension);
+ ASSERT_TRUE(action_info);
+
+ EXPECT_EQ(extension->GetResourceURL("popup.html"),
+ action_info->default_popup_url);
+ EXPECT_EQ("Title", action_info->default_title);
+ // Make a copy of the map since [] is more readable than find() for comparing
+ // values.
+ ExtensionIconSet::IconMap icons = action_info->default_icon.map();
+ EXPECT_EQ(1u, icons.size());
+ EXPECT_EQ(icons[extension_misc::EXTENSION_ICON_GIGANTOR], "icon.png");
+}
+
+// Tests that specifying an empty action (e.g., "action": {}) works correctly,
+// with empty defaults.
+TEST_P(ExtensionActionManifestTest, TestEmptyAction) {
+ constexpr char kValidNoFields[] = "{}";
+ scoped_refptr<const Extension> extension =
+ LoadAndExpectSuccess(GetManifestData(kValidNoFields));
+ ASSERT_TRUE(extension);
+ const ActionInfo* action_info = GetActionInfo(*extension);
+ ASSERT_TRUE(action_info);
+
+ EXPECT_EQ(GURL(), action_info->default_popup_url);
+ EXPECT_EQ(std::string(), action_info->default_title);
+ const ExtensionIconSet::IconMap& icons = action_info->default_icon.map();
+ EXPECT_TRUE(icons.empty());
+}
+
+// Tests specifying an icon dictionary (with different pixel sizes) in the
+// action.
+TEST_P(ExtensionActionManifestTest, ValidIconDictionary) {
+ constexpr char kValidIconDictionary[] =
+ R"({
+ "default_icon": {
+ "24": "icon24.png",
+ "48": "icon48.png",
+ "79": "icon79.png"
+ }
+ })";
+
+ scoped_refptr<const Extension> extension =
+ LoadAndExpectSuccess(GetManifestData(kValidIconDictionary));
+ ASSERT_TRUE(extension);
+ const ActionInfo* action_info = GetActionInfo(*extension);
+ ASSERT_TRUE(action_info);
+
+ EXPECT_EQ(GURL(), action_info->default_popup_url);
+ EXPECT_EQ(std::string(), action_info->default_title);
+ // Make a copy of the map since [] is more readable than find() for comparing
+ // values.
+ ExtensionIconSet::IconMap icons = action_info->default_icon.map();
+ EXPECT_EQ(3u, icons.size());
+ EXPECT_EQ("icon24.png", icons[24]);
+ EXPECT_EQ("icon48.png", icons[48]);
+ EXPECT_EQ("icon79.png", icons[79]);
+}
+
+// Tests some invalid cases.
+TEST_P(ExtensionActionManifestTest, Invalid) {
+ constexpr char kInvalidTopLevel1[] = "[]";
+ constexpr char kInvalidTopLevel2[] = "\"foo\"";
+ constexpr char kInvalidTopLevel3[] = "17";
+
+ const char* expected_error = nullptr;
+ switch (GetParam()) {
+ case TestActionType::kBrowserAction:
+ expected_error = manifest_errors::kInvalidBrowserAction;
+ break;
+ case TestActionType::kPageAction:
+ expected_error = manifest_errors::kInvalidPageAction;
+ break;
+ case TestActionType::kAction:
+ expected_error = manifest_errors::kInvalidAction;
+ break;
+ }
+
+ for (const char* spec :
+ {kInvalidTopLevel1, kInvalidTopLevel2, kInvalidTopLevel3}) {
+ LoadAndExpectError(GetManifestData(spec), expected_error);
+ }
+
+ constexpr char kInvalidPopup[] = R"({ "default_popup": {} })";
+ LoadAndExpectError(GetManifestData(kInvalidPopup),
+ manifest_errors::kInvalidActionDefaultPopup);
+ constexpr char kInvalidTitle[] = R"({ "default_title": {} })";
+ LoadAndExpectError(GetManifestData(kInvalidTitle),
+ manifest_errors::kInvalidActionDefaultTitle);
+ constexpr char kInvalidIcon[] = R"({ "default_icon": [] })";
+ LoadAndExpectError(GetManifestData(kInvalidIcon),
+ manifest_errors::kInvalidActionDefaultIcon);
+}
+
+// Test the handling of the default_state key.
+TEST_P(ExtensionActionManifestTest, DefaultState) {
+ constexpr char kDefaultStateDisabled[] = R"({"default_state": "disabled"})";
+ constexpr char kDefaultStateEnabled[] = R"({"default_state": "enabled"})";
+ constexpr char kDefaultStateInvalid[] = R"({"default_state": "foo"})";
+
+ // default_state is only valid for "action" types.
+ const bool default_state_allowed = GetParam() == TestActionType::kAction;
+ const char* key_disallowed_error =
+ manifest_errors::kDefaultStateShouldNotBeSet;
+
+ struct {
+ // The manifest definition of the action key.
+ const char* spec;
+ // The expected error, if parsing was unsuccessful.
+ const char* expected_error;
+ // The expected state, if parsing was successful.
+ base::Optional<ActionInfo::DefaultState> expected_state;
+ } test_cases[] = {
+ {kDefaultStateDisabled,
+ default_state_allowed ? nullptr : key_disallowed_error,
+ default_state_allowed ? base::make_optional(ActionInfo::STATE_DISABLED)
+ : base::nullopt},
+ {kDefaultStateEnabled,
+ default_state_allowed ? nullptr : key_disallowed_error,
+ default_state_allowed ? base::make_optional(ActionInfo::STATE_ENABLED)
+ : base::nullopt},
+ {kDefaultStateInvalid,
+ default_state_allowed ? manifest_errors::kInvalidActionDefaultState
+ : key_disallowed_error,
+ base::nullopt},
+ };
+
+ for (const auto& test_case : test_cases) {
+ SCOPED_TRACE(test_case.spec);
+
+ if (test_case.expected_error == nullptr) {
+ ASSERT_TRUE(test_case.expected_state);
+ scoped_refptr<const Extension> extension =
+ LoadAndExpectSuccess(GetManifestData(test_case.spec));
+ ASSERT_TRUE(extension);
+ const ActionInfo* action_info = GetActionInfo(*extension);
+ ASSERT_TRUE(action_info);
+ EXPECT_EQ(*test_case.expected_state, action_info->default_state);
+ } else {
+ ASSERT_FALSE(test_case.expected_state);
+ LoadAndExpectError(GetManifestData(test_case.spec),
+ test_case.expected_error);
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ ExtensionActionManifestTest,
+ testing::Values(TestActionType::kBrowserAction,
+ TestActionType::kPageAction,
+ TestActionType::kAction));
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.cc b/chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.cc
new file mode 100644
index 00000000000..79a6f4e5148
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.cc
@@ -0,0 +1,110 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/linked_app_icons.h"
+
+#include <memory>
+
+#include "base/lazy_instance.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+static base::LazyInstance<LinkedAppIcons>::DestructorAtExit
+ g_empty_linked_app_icons = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+LinkedAppIcons::IconInfo::IconInfo() {
+}
+
+LinkedAppIcons::IconInfo::~IconInfo() {
+}
+
+LinkedAppIcons::LinkedAppIcons() {
+}
+
+LinkedAppIcons::LinkedAppIcons(const LinkedAppIcons& other) = default;
+
+LinkedAppIcons::~LinkedAppIcons() {
+}
+
+// static
+const LinkedAppIcons& LinkedAppIcons::GetLinkedAppIcons(
+ const Extension* extension) {
+ LinkedAppIcons* info = static_cast<LinkedAppIcons*>(
+ extension->GetManifestData(keys::kLinkedAppIcons));
+ return info ? *info : g_empty_linked_app_icons.Get();
+}
+
+LinkedAppIconsHandler::LinkedAppIconsHandler() {
+}
+
+LinkedAppIconsHandler::~LinkedAppIconsHandler() {
+}
+
+bool LinkedAppIconsHandler::Parse(Extension* extension, base::string16* error) {
+ std::unique_ptr<LinkedAppIcons> linked_app_icons(new LinkedAppIcons);
+
+ const base::Value* icons_value = nullptr;
+ const base::ListValue* icons_list = nullptr;
+ if (extension->manifest()->Get(keys::kLinkedAppIcons, &icons_value)) {
+ if (!icons_value->GetAsList(&icons_list)) {
+ *error = base::UTF8ToUTF16(
+ extensions::manifest_errors::kInvalidLinkedAppIcons);
+ return false;
+ }
+
+ for (const auto& icon_value : *icons_list) {
+ const base::DictionaryValue* icon_dict = nullptr;
+ if (!icon_value.GetAsDictionary(&icon_dict)) {
+ *error = base::UTF8ToUTF16(
+ extensions::manifest_errors::kInvalidLinkedAppIcon);
+ return false;
+ }
+
+ std::string url_string;
+ if (!icon_dict->GetString(keys::kLinkedAppIconURL, &url_string)) {
+ *error = base::UTF8ToUTF16(
+ extensions::manifest_errors::kInvalidLinkedAppIconURL);
+ return false;
+ }
+
+ LinkedAppIcons::IconInfo info;
+ info.url = GURL(url_string);
+ if (!info.url.is_valid()) {
+ *error = base::UTF8ToUTF16(
+ extensions::manifest_errors::kInvalidLinkedAppIconURL);
+ return false;
+ }
+
+ if (!icon_dict->GetInteger(keys::kLinkedAppIconSize, &info.size)) {
+ *error = base::UTF8ToUTF16(
+ extensions::manifest_errors::kInvalidLinkedAppIconSize);
+ return false;
+ }
+
+ linked_app_icons->icons.push_back(info);
+ }
+ }
+
+ extension->SetManifestData(keys::kLinkedAppIcons,
+ std::move(linked_app_icons));
+ return true;
+}
+
+base::span<const char* const> LinkedAppIconsHandler::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kLinkedAppIcons};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.h b/chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.h
new file mode 100644
index 00000000000..32845384e44
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/linked_app_icons.h
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_LINKED_APP_ICONS_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_LINKED_APP_ICONS_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// A structure to hold the parsed linked app icon data.
+struct LinkedAppIcons : public Extension::ManifestData {
+ struct IconInfo {
+ IconInfo();
+ ~IconInfo();
+
+ GURL url;
+ int size;
+ };
+
+ LinkedAppIcons();
+ LinkedAppIcons(const LinkedAppIcons& other);
+ ~LinkedAppIcons() override;
+
+ static const LinkedAppIcons& GetLinkedAppIcons(const Extension* extension);
+
+ std::vector<IconInfo> icons;
+};
+
+// Parses the "app.linked_icons" manifest key.
+class LinkedAppIconsHandler : public ManifestHandler {
+ public:
+ LinkedAppIconsHandler();
+ ~LinkedAppIconsHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(LinkedAppIconsHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_LINKED_APP_ICONS_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.cc b/chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.cc
new file mode 100644
index 00000000000..51c27ebe3ad
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.cc
@@ -0,0 +1,63 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
+#include "chrome/grit/chromium_strings.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+MinimumChromeVersionChecker::MinimumChromeVersionChecker() {
+}
+
+MinimumChromeVersionChecker::~MinimumChromeVersionChecker() {
+}
+
+bool MinimumChromeVersionChecker::Parse(Extension* extension,
+ base::string16* error) {
+ std::string minimum_version_string;
+ if (!extension->manifest()->GetString(keys::kMinimumChromeVersion,
+ &minimum_version_string)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidMinimumChromeVersion);
+ return false;
+ }
+
+ base::Version minimum_version(minimum_version_string);
+ if (!minimum_version.IsValid()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidMinimumChromeVersion);
+ return false;
+ }
+
+ const base::Version& current_version = version_info::GetVersion();
+ if (!current_version.IsValid()) {
+ NOTREACHED();
+ return false;
+ }
+
+ if (current_version.CompareTo(minimum_version) < 0) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kChromeVersionTooLow,
+ l10n_util::GetStringUTF8(IDS_PRODUCT_NAME),
+ minimum_version_string);
+ return false;
+ }
+ return true;
+}
+
+base::span<const char* const> MinimumChromeVersionChecker::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kMinimumChromeVersion};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.h b/chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.h
new file mode 100644
index 00000000000..4ca64305326
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/minimum_chrome_version_checker.h
@@ -0,0 +1,31 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_MINIMUM_CHROME_VERSION_CHECKER_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_MINIMUM_CHROME_VERSION_CHECKER_H_
+
+#include "base/macros.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// Checks that the "minimum_chrome_version" requirement is met.
+class MinimumChromeVersionChecker : public ManifestHandler {
+ public:
+ MinimumChromeVersionChecker();
+ ~MinimumChromeVersionChecker() override;
+
+ // Validate minimum Chrome version. We don't need to store this, since the
+ // extension is not valid if it is incorrect.
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(MinimumChromeVersionChecker);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_MINIMUM_CHROME_VERSION_CHECKER_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc b/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc
new file mode 100644
index 00000000000..397c3f73ce6
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc
@@ -0,0 +1,72 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/natively_connectable_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace {
+
+const NativelyConnectableHosts* GetHosts(const Extension& extension) {
+ return static_cast<const NativelyConnectableHosts*>(
+ extension.GetManifestData(manifest_keys::kNativelyConnectable));
+}
+
+} // namespace
+
+NativelyConnectableHosts::NativelyConnectableHosts() = default;
+NativelyConnectableHosts::~NativelyConnectableHosts() = default;
+
+// static
+const std::set<std::string>*
+NativelyConnectableHosts::GetConnectableNativeMessageHosts(
+ const Extension& extension) {
+ const auto* hosts = GetHosts(extension);
+ if (!hosts) {
+ return nullptr;
+ }
+ return &hosts->hosts;
+}
+
+NativelyConnectableHandler::NativelyConnectableHandler() = default;
+NativelyConnectableHandler::~NativelyConnectableHandler() = default;
+
+bool NativelyConnectableHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* natively_connectable_hosts = nullptr;
+ if (!extension->manifest()->GetList(manifest_keys::kNativelyConnectable,
+ &natively_connectable_hosts)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidNativelyConnectable);
+ return false;
+ }
+
+ auto hosts = std::make_unique<NativelyConnectableHosts>();
+ for (const auto& host : natively_connectable_hosts->GetList()) {
+ if (!host.is_string() || host.GetString().empty()) {
+ *error =
+ base::ASCIIToUTF16(manifest_errors::kInvalidNativelyConnectableValue);
+ return false;
+ }
+ hosts->hosts.insert(host.GetString());
+ }
+
+ extension->SetManifestData(manifest_keys::kNativelyConnectable,
+ std::move(hosts));
+ return true;
+}
+
+base::span<const char* const> NativelyConnectableHandler::Keys() const {
+ static constexpr const char* kKeys[] = {manifest_keys::kNativelyConnectable};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.h b/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.h
new file mode 100644
index 00000000000..8a32c3a4091
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler.h
@@ -0,0 +1,48 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_NATIVELY_CONNECTABLE_HANDLER_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_NATIVELY_CONNECTABLE_HANDLER_H_
+
+#include <set>
+#include <string>
+
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// A structure to hold the parsed list of native messaging hosts that can
+// connect to this extension.
+struct NativelyConnectableHosts : public Extension::ManifestData {
+ NativelyConnectableHosts();
+ ~NativelyConnectableHosts() override;
+
+ static const std::set<std::string>* GetConnectableNativeMessageHosts(
+ const Extension& extension);
+
+ // A set of native messaging hosts allowed to initiate connection to this
+ // extension.
+ std::set<std::string> hosts;
+};
+
+// Parses the "natively_connectable" manifest key.
+class NativelyConnectableHandler : public ManifestHandler {
+ public:
+ NativelyConnectableHandler();
+ ~NativelyConnectableHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(NativelyConnectableHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_NATIVELY_CONNECTABLE_HANDLER_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc
new file mode 100644
index 00000000000..67e59904ce5
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/natively_connectable_handler.h"
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permission_message_test_util.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+using NativelyConnectableManifestTest = ChromeManifestTest;
+
+TEST_F(NativelyConnectableManifestTest, Basic) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("natively_connectable.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
+
+ auto* hosts =
+ NativelyConnectableHosts::GetConnectableNativeMessageHosts(*extension);
+ ASSERT_TRUE(hosts);
+ EXPECT_EQ(*hosts, (std::set<std::string>{
+ "com.google.chrome.test.echo",
+ "com.google.chrome.test.host_binary_missing",
+ }));
+}
+
+TEST_F(NativelyConnectableManifestTest, Unset) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("natively_connectable_unset.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
+
+ EXPECT_FALSE(
+ NativelyConnectableHosts::GetConnectableNativeMessageHosts(*extension));
+}
+
+TEST_F(NativelyConnectableManifestTest, IncorrectType) {
+ LoadAndExpectError("natively_connectable_incorrect_type.json",
+ manifest_errors::kInvalidNativelyConnectable);
+}
+
+TEST_F(NativelyConnectableManifestTest, IncorrectValuesType) {
+ LoadAndExpectError("natively_connectable_incorrect_values_type.json",
+ manifest_errors::kInvalidNativelyConnectableValue);
+}
+
+TEST_F(NativelyConnectableManifestTest, EmptyHost) {
+ LoadAndExpectError("natively_connectable_empty_host.json",
+ manifest_errors::kInvalidNativelyConnectableValue);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc b/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
new file mode 100644
index 00000000000..6c83bbae4d8
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
@@ -0,0 +1,183 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/url_formatter/url_formatter.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/permissions/manifest_permission.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/permissions/settings_override_permission.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+#include "url/gurl.h"
+
+using extensions::api::manifest_types::ChromeSettingsOverrides;
+
+namespace extensions {
+namespace {
+
+std::unique_ptr<GURL> CreateManifestURL(const std::string& url) {
+ std::unique_ptr<GURL> manifest_url(new GURL(url));
+ if (!manifest_url->is_valid() ||
+ !manifest_url->SchemeIsHTTPOrHTTPS())
+ return std::unique_ptr<GURL>();
+ return manifest_url;
+}
+
+std::unique_ptr<GURL> ParseHomepage(const ChromeSettingsOverrides& overrides,
+ base::string16* error) {
+ if (!overrides.homepage)
+ return std::unique_ptr<GURL>();
+ std::unique_ptr<GURL> manifest_url = CreateManifestURL(*overrides.homepage);
+ if (!manifest_url) {
+ *error = extensions::ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidHomepageOverrideURL, *overrides.homepage);
+ }
+ return manifest_url;
+}
+
+std::vector<GURL> ParseStartupPage(const ChromeSettingsOverrides& overrides,
+ base::string16* error) {
+ std::vector<GURL> urls;
+ if (!overrides.startup_pages)
+ return urls;
+
+ for (std::vector<std::string>::const_iterator i =
+ overrides.startup_pages->begin(); i != overrides.startup_pages->end();
+ ++i) {
+ std::unique_ptr<GURL> manifest_url = CreateManifestURL(*i);
+ if (!manifest_url) {
+ *error = extensions::ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidStartupOverrideURL, *i);
+ } else {
+ urls.push_back(GURL());
+ urls.back().Swap(manifest_url.get());
+ }
+ }
+ return urls;
+}
+
+std::unique_ptr<ChromeSettingsOverrides::Search_provider> ParseSearchEngine(
+ ChromeSettingsOverrides* overrides,
+ base::string16* error) {
+ if (!overrides->search_provider)
+ return std::unique_ptr<ChromeSettingsOverrides::Search_provider>();
+ if (!CreateManifestURL(overrides->search_provider->search_url)) {
+ *error = extensions::ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidSearchEngineURL,
+ overrides->search_provider->search_url);
+ return std::unique_ptr<ChromeSettingsOverrides::Search_provider>();
+ }
+ if (overrides->search_provider->prepopulated_id)
+ return std::move(overrides->search_provider);
+ if (!overrides->search_provider->name ||
+ !overrides->search_provider->keyword ||
+ !overrides->search_provider->encoding ||
+ !overrides->search_provider->favicon_url) {
+ *error =
+ base::ASCIIToUTF16(manifest_errors::kInvalidSearchEngineMissingKeys);
+ return std::unique_ptr<ChromeSettingsOverrides::Search_provider>();
+ }
+ if (!CreateManifestURL(*overrides->search_provider->favicon_url)) {
+ *error = extensions::ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidSearchEngineURL,
+ *overrides->search_provider->favicon_url);
+ return std::unique_ptr<ChromeSettingsOverrides::Search_provider>();
+ }
+ return std::move(overrides->search_provider);
+}
+
+std::string FormatUrlForDisplay(const GURL& url) {
+ base::StringPiece host = url.host_piece();
+ // A www. prefix is not informative and thus not worth the limited real estate
+ // in the permissions UI.
+ // TODO(catmullings): Ideally, we wouldn't be using custom code to format URLs
+ // here, since we have a number of methods that do that more universally.
+ return base::UTF16ToUTF8(url_formatter::StripWWW(base::UTF8ToUTF16(host)));
+}
+
+} // namespace
+
+SettingsOverrides::SettingsOverrides() {}
+
+SettingsOverrides::~SettingsOverrides() {}
+
+// static
+const SettingsOverrides* SettingsOverrides::Get(
+ const Extension* extension) {
+ return static_cast<SettingsOverrides*>(
+ extension->GetManifestData(manifest_keys::kSettingsOverride));
+}
+
+SettingsOverridesHandler::SettingsOverridesHandler() {}
+
+SettingsOverridesHandler::~SettingsOverridesHandler() {}
+
+bool SettingsOverridesHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* dict = NULL;
+ CHECK(extension->manifest()->Get(manifest_keys::kSettingsOverride, &dict));
+ std::unique_ptr<ChromeSettingsOverrides> settings(
+ ChromeSettingsOverrides::FromValue(*dict, error));
+ if (!settings)
+ return false;
+
+ std::unique_ptr<SettingsOverrides> info(new SettingsOverrides);
+ info->homepage = ParseHomepage(*settings, error);
+ info->search_engine = ParseSearchEngine(settings.get(), error);
+ info->startup_pages = ParseStartupPage(*settings, error);
+ if (!info->homepage && !info->search_engine && info->startup_pages.empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidEmptyDictionary,
+ manifest_keys::kSettingsOverride);
+ return false;
+ }
+
+ if (info->search_engine) {
+ PermissionsParser::AddAPIPermission(
+ extension, new SettingsOverrideAPIPermission(
+ PermissionsInfo::GetInstance()->GetByID(
+ APIPermission::kSearchProvider),
+ FormatUrlForDisplay(*CreateManifestURL(
+ info->search_engine->search_url))));
+ }
+ if (!info->startup_pages.empty()) {
+ PermissionsParser::AddAPIPermission(
+ extension,
+ new SettingsOverrideAPIPermission(
+ PermissionsInfo::GetInstance()->GetByID(
+ APIPermission::kStartupPages),
+ // We only support one startup page even though the type of the
+ // manifest property is a list, only the first one is used.
+ FormatUrlForDisplay(info->startup_pages[0])));
+ }
+ if (info->homepage) {
+ PermissionsParser::AddAPIPermission(
+ extension,
+ new SettingsOverrideAPIPermission(
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kHomepage),
+ FormatUrlForDisplay(*(info->homepage))));
+ }
+ extension->SetManifestData(manifest_keys::kSettingsOverride, std::move(info));
+ return true;
+}
+
+base::span<const char* const> SettingsOverridesHandler::Keys() const {
+ static constexpr const char* kKeys[] = {manifest_keys::kSettingsOverride};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h b/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h
new file mode 100644
index 00000000000..265fb1ecf9f
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_SETTINGS_OVERRIDES_HANDLER_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_SETTINGS_OVERRIDES_HANDLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/common/extensions/api/manifest_types.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+enum SettingsApiOverrideType {
+ BUBBLE_TYPE_HOME_PAGE = 0,
+ BUBBLE_TYPE_SEARCH_ENGINE,
+ BUBBLE_TYPE_STARTUP_PAGES,
+};
+
+// SettingsOverride is associated with "chrome_settings_overrides" manifest key.
+// An extension can add a search engine as default or non-default, overwrite the
+// homepage and append a startup page to the list.
+struct SettingsOverrides : public Extension::ManifestData {
+ SettingsOverrides();
+ ~SettingsOverrides() override;
+
+ static const SettingsOverrides* Get(const Extension* extension);
+
+ std::unique_ptr<api::manifest_types::ChromeSettingsOverrides::Search_provider>
+ search_engine;
+ std::unique_ptr<GURL> homepage;
+ std::vector<GURL> startup_pages;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SettingsOverrides);
+};
+
+class SettingsOverridesHandler : public ManifestHandler {
+ public:
+ SettingsOverridesHandler();
+ ~SettingsOverridesHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingsOverridesHandler);
+};
+
+} // namespace extensions
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_SETTINGS_OVERRIDES_HANDLER_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc
new file mode 100644
index 00000000000..1575768051a
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+
+#include <memory>
+
+#include "base/json/json_string_value_serializer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kManifest[] =
+ "{"
+ " \"version\" : \"1.0.0.0\","
+ " \"manifest_version\" : 2,"
+ " \"name\" : \"Test\","
+ " \"chrome_settings_overrides\" : {"
+ " \"homepage\" : \"http://www.homepage.com\","
+ " \"search_provider\" : {"
+ " \"name\" : \"first\","
+ " \"keyword\" : \"firstkey\","
+ " \"search_url\" : \"http://www.foo.com/s?q={searchTerms}\","
+ " \"favicon_url\" : \"http://www.foo.com/favicon.ico\","
+ " \"suggest_url\" : \"http://www.foo.com/s?q={searchTerms}\","
+ " \"encoding\" : \"UTF-8\","
+ " \"is_default\" : true"
+ " },"
+ " \"startup_pages\" : [\"http://www.startup.com\"]"
+ " }"
+ "}";
+
+const char kPrepopulatedManifest[] =
+ "{"
+ " \"version\" : \"1.0.0.0\","
+ " \"manifest_version\" : 2,"
+ " \"name\" : \"Test\","
+ " \"chrome_settings_overrides\" : {"
+ " \"search_provider\" : {"
+ " \"search_url\" : \"http://www.foo.com/s?q={searchTerms}\","
+ " \"prepopulated_id\" : 3,"
+ " \"is_default\" : true"
+ " }"
+ " }"
+ "}";
+
+const char kBrokenManifest[] =
+ "{"
+ " \"version\" : \"1.0.0.0\","
+ " \"manifest_version\" : 2,"
+ " \"name\" : \"Test\","
+ " \"chrome_settings_overrides\" : {"
+ " \"homepage\" : \"{invalid}\","
+ " \"search_provider\" : {"
+ " \"name\" : \"first\","
+ " \"keyword\" : \"firstkey\","
+ " \"search_url\" : \"{invalid}/s?q={searchTerms}\","
+ " \"favicon_url\" : \"{invalid}/favicon.ico\","
+ " \"encoding\" : \"UTF-8\","
+ " \"is_default\" : true"
+ " },"
+ " \"startup_pages\" : [\"{invalid}\"]"
+ " }"
+ "}";
+
+using extensions::api::manifest_types::ChromeSettingsOverrides;
+using extensions::Extension;
+using extensions::Manifest;
+using extensions::SettingsOverrides;
+namespace manifest_keys = extensions::manifest_keys;
+
+TEST(OverrideSettingsTest, ParseManifest) {
+#if defined(OS_MACOSX)
+ // On Mac, this API is limited to trunk.
+ extensions::ScopedCurrentChannel scoped_channel(
+ version_info::Channel::UNKNOWN);
+#endif // OS_MACOSX
+
+ std::string manifest(kManifest);
+ JSONStringValueDeserializer json(manifest);
+ std::string error;
+ std::unique_ptr<base::Value> root(json.Deserialize(NULL, &error));
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(root->is_dict());
+ scoped_refptr<Extension> extension = Extension::Create(
+ base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
+ Manifest::INVALID_LOCATION,
+ *static_cast<base::DictionaryValue*>(root.get()),
+ Extension::NO_FLAGS,
+ &error);
+ ASSERT_TRUE(extension.get());
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ ASSERT_TRUE(extension->manifest()->HasPath(manifest_keys::kSettingsOverride));
+
+ SettingsOverrides* settings_override = static_cast<SettingsOverrides*>(
+ extension->GetManifestData(manifest_keys::kSettingsOverride));
+ ASSERT_TRUE(settings_override);
+ ASSERT_TRUE(settings_override->search_engine);
+ EXPECT_TRUE(settings_override->search_engine->is_default);
+ const ChromeSettingsOverrides::Search_provider* search_engine =
+ settings_override->search_engine.get();
+ EXPECT_EQ("first", *search_engine->name);
+ EXPECT_EQ("firstkey", *search_engine->keyword);
+ EXPECT_EQ("http://www.foo.com/s?q={searchTerms}", search_engine->search_url);
+ EXPECT_EQ("http://www.foo.com/favicon.ico", *search_engine->favicon_url);
+ EXPECT_EQ("http://www.foo.com/s?q={searchTerms}",
+ *search_engine->suggest_url);
+ EXPECT_EQ("UTF-8", *search_engine->encoding);
+
+ EXPECT_EQ(std::vector<GURL>(1, GURL("http://www.startup.com")),
+ settings_override->startup_pages);
+
+ ASSERT_TRUE(settings_override->homepage);
+ EXPECT_EQ(GURL("http://www.homepage.com"), *settings_override->homepage);
+#else
+ EXPECT_FALSE(
+ extension->manifest()->HasPath(manifest_keys::kSettingsOverride));
+#endif
+}
+
+TEST(OverrideSettingsTest, ParsePrepopulatedId) {
+#if defined(OS_MACOSX)
+ // On Mac, this API is limited to trunk.
+ extensions::ScopedCurrentChannel scoped_channel(
+ version_info::Channel::UNKNOWN);
+#endif // OS_MACOSX
+
+ std::string manifest(kPrepopulatedManifest);
+ JSONStringValueDeserializer json(manifest);
+ std::string error;
+ std::unique_ptr<base::Value> root(json.Deserialize(NULL, &error));
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(root->is_dict());
+ scoped_refptr<Extension> extension =
+ Extension::Create(base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
+ Manifest::INVALID_LOCATION,
+ *static_cast<base::DictionaryValue*>(root.get()),
+ Extension::NO_FLAGS,
+ &error);
+ ASSERT_TRUE(extension.get());
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ ASSERT_TRUE(extension->manifest()->HasPath(manifest_keys::kSettingsOverride));
+
+ SettingsOverrides* settings_override = static_cast<SettingsOverrides*>(
+ extension->GetManifestData(manifest_keys::kSettingsOverride));
+ ASSERT_TRUE(settings_override);
+ ASSERT_TRUE(settings_override->search_engine);
+ EXPECT_TRUE(settings_override->search_engine->is_default);
+ const ChromeSettingsOverrides::Search_provider* search_engine =
+ settings_override->search_engine.get();
+ ASSERT_TRUE(search_engine->prepopulated_id);
+ EXPECT_EQ("http://www.foo.com/s?q={searchTerms}", search_engine->search_url);
+ EXPECT_EQ(3, *search_engine->prepopulated_id);
+#else
+ EXPECT_FALSE(
+ extension->manifest()->HasPath(manifest_keys::kSettingsOverride));
+#endif
+}
+
+TEST(OverrideSettingsTest, ParseBrokenManifest) {
+#if defined(OS_MACOSX)
+ // On Mac, this API is limited to trunk.
+ extensions::ScopedCurrentChannel scoped_channel(
+ version_info::Channel::UNKNOWN);
+#endif // OS_MACOSX
+
+ std::string manifest(kBrokenManifest);
+ JSONStringValueDeserializer json(manifest);
+ std::string error;
+ std::unique_ptr<base::Value> root(json.Deserialize(NULL, &error));
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(root->is_dict());
+ scoped_refptr<Extension> extension = Extension::Create(
+ base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
+ Manifest::INVALID_LOCATION,
+ *static_cast<base::DictionaryValue*>(root.get()),
+ Extension::NO_FLAGS,
+ &error);
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ EXPECT_FALSE(extension.get());
+ EXPECT_EQ(
+ extensions::ErrorUtils::FormatErrorMessage(
+ extensions::manifest_errors::kInvalidEmptyDictionary,
+ extensions::manifest_keys::kSettingsOverride),
+ error);
+#else
+ EXPECT_TRUE(extension.get());
+ EXPECT_FALSE(
+ extension->manifest()->HasPath(manifest_keys::kSettingsOverride));
+#endif
+}
+
+} // namespace
diff --git a/chromium/chrome/common/extensions/manifest_handlers/theme_handler.cc b/chromium/chrome/common/extensions/manifest_handlers/theme_handler.cc
new file mode 100644
index 00000000000..327b035e9d3
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/theme_handler.cc
@@ -0,0 +1,224 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/theme_handler.h"
+
+#include <memory>
+
+#include "base/files/file_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/grit/generated_resources.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+bool LoadImages(const base::DictionaryValue* theme_value,
+ base::string16* error,
+ ThemeInfo* theme_info) {
+ const base::DictionaryValue* images_value = NULL;
+ if (theme_value->GetDictionary(keys::kThemeImages, &images_value)) {
+ // Validate that the images are all strings.
+ for (base::DictionaryValue::Iterator iter(*images_value); !iter.IsAtEnd();
+ iter.Advance()) {
+ // The value may be a dictionary of scales and files paths.
+ // Or the value may be a file path, in which case a scale
+ // of 100% is assumed.
+ if (iter.value().is_dict()) {
+ const base::DictionaryValue* inner_value = NULL;
+ if (iter.value().GetAsDictionary(&inner_value)) {
+ for (base::DictionaryValue::Iterator inner_iter(*inner_value);
+ !inner_iter.IsAtEnd(); inner_iter.Advance()) {
+ if (!inner_iter.value().is_string()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidThemeImages);
+ return false;
+ }
+ }
+ } else {
+ *error = base::ASCIIToUTF16(errors::kInvalidThemeImages);
+ return false;
+ }
+ } else if (!iter.value().is_string()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidThemeImages);
+ return false;
+ }
+ }
+ theme_info->theme_images_.reset(images_value->DeepCopy());
+ }
+ return true;
+}
+
+bool LoadColors(const base::DictionaryValue* theme_value,
+ base::string16* error,
+ ThemeInfo* theme_info) {
+ const base::DictionaryValue* colors_value = NULL;
+ if (theme_value->GetDictionary(keys::kThemeColors, &colors_value)) {
+ // Validate that the colors are RGB or RGBA lists.
+ for (base::DictionaryValue::Iterator iter(*colors_value); !iter.IsAtEnd();
+ iter.Advance()) {
+ const base::ListValue* color_list = NULL;
+ double alpha = 0.0;
+ int color = 0;
+ // The color must be a list...
+ if (!iter.value().GetAsList(&color_list) ||
+ // ... and either 3 items (RGB) or 4 (RGBA).
+ ((color_list->GetSize() != 3) &&
+ ((color_list->GetSize() != 4) ||
+ // For RGBA, the fourth item must be a real or int alpha value.
+ // Note that GetDouble() can get an integer value.
+ !color_list->GetDouble(3, &alpha))) ||
+ // For both RGB and RGBA, the first three items must be ints (R,G,B).
+ !color_list->GetInteger(0, &color) ||
+ !color_list->GetInteger(1, &color) ||
+ !color_list->GetInteger(2, &color)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidThemeColors);
+ return false;
+ }
+ }
+ theme_info->theme_colors_.reset(colors_value->DeepCopy());
+ }
+ return true;
+}
+
+bool LoadTints(const base::DictionaryValue* theme_value,
+ base::string16* error,
+ ThemeInfo* theme_info) {
+ const base::DictionaryValue* tints_value = NULL;
+ if (!theme_value->GetDictionary(keys::kThemeTints, &tints_value))
+ return true;
+
+ // Validate that the tints are all reals.
+ for (base::DictionaryValue::Iterator iter(*tints_value); !iter.IsAtEnd();
+ iter.Advance()) {
+ const base::ListValue* tint_list = NULL;
+ double v = 0.0;
+ if (!iter.value().GetAsList(&tint_list) ||
+ tint_list->GetSize() != 3 ||
+ !tint_list->GetDouble(0, &v) ||
+ !tint_list->GetDouble(1, &v) ||
+ !tint_list->GetDouble(2, &v)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidThemeTints);
+ return false;
+ }
+ }
+ theme_info->theme_tints_.reset(tints_value->DeepCopy());
+ return true;
+}
+
+bool LoadDisplayProperties(const base::DictionaryValue* theme_value,
+ base::string16* error,
+ ThemeInfo* theme_info) {
+ const base::DictionaryValue* display_properties_value = NULL;
+ if (theme_value->GetDictionary(keys::kThemeDisplayProperties,
+ &display_properties_value)) {
+ theme_info->theme_display_properties_.reset(
+ display_properties_value->DeepCopy());
+ }
+ return true;
+}
+
+const ThemeInfo* GetInfo(const Extension* extension) {
+ return static_cast<ThemeInfo*>(extension->GetManifestData(keys::kTheme));
+}
+
+} // namespace
+
+ThemeInfo::ThemeInfo() {
+}
+
+ThemeInfo::~ThemeInfo() {
+}
+
+// static
+const base::DictionaryValue* ThemeInfo::GetImages(const Extension* extension) {
+ const ThemeInfo* theme_info = GetInfo(extension);
+ return theme_info ? theme_info->theme_images_.get() : NULL;
+}
+
+// static
+const base::DictionaryValue* ThemeInfo::GetColors(const Extension* extension) {
+ const ThemeInfo* theme_info = GetInfo(extension);
+ return theme_info ? theme_info->theme_colors_.get() : NULL;
+}
+
+// static
+const base::DictionaryValue* ThemeInfo::GetTints(const Extension* extension) {
+ const ThemeInfo* theme_info = GetInfo(extension);
+ return theme_info ? theme_info->theme_tints_.get() : NULL;
+}
+
+// static
+const base::DictionaryValue* ThemeInfo::GetDisplayProperties(
+ const Extension* extension) {
+ const ThemeInfo* theme_info = GetInfo(extension);
+ return theme_info ? theme_info->theme_display_properties_.get() : NULL;
+}
+
+ThemeHandler::ThemeHandler() {
+}
+
+ThemeHandler::~ThemeHandler() {
+}
+
+bool ThemeHandler::Parse(Extension* extension, base::string16* error) {
+ const base::DictionaryValue* theme_value = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kTheme, &theme_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidTheme);
+ return false;
+ }
+
+ std::unique_ptr<ThemeInfo> theme_info(new ThemeInfo);
+ if (!LoadImages(theme_value, error, theme_info.get()))
+ return false;
+ if (!LoadColors(theme_value, error, theme_info.get()))
+ return false;
+ if (!LoadTints(theme_value, error, theme_info.get()))
+ return false;
+ if (!LoadDisplayProperties(theme_value, error, theme_info.get()))
+ return false;
+
+ extension->SetManifestData(keys::kTheme, std::move(theme_info));
+ return true;
+}
+
+bool ThemeHandler::Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // Validate that theme images exist.
+ if (extension->is_theme()) {
+ const base::DictionaryValue* images_value =
+ extensions::ThemeInfo::GetImages(extension);
+ if (images_value) {
+ for (base::DictionaryValue::Iterator iter(*images_value); !iter.IsAtEnd();
+ iter.Advance()) {
+ std::string val;
+ if (iter.value().GetAsString(&val)) {
+ base::FilePath image_path = extension->path().Append(
+ base::FilePath::FromUTF8Unsafe(val));
+ if (!base::PathExists(image_path)) {
+ *error =
+ l10n_util::GetStringFUTF8(IDS_EXTENSION_INVALID_IMAGE_PATH,
+ image_path.LossyDisplayName());
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+base::span<const char* const> ThemeHandler::Keys() const {
+ static constexpr const char* kKeys[] = {keys::kTheme};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/theme_handler.h b/chromium/chrome/common/extensions/manifest_handlers/theme_handler.h
new file mode 100644
index 00000000000..7f1b3f35c68
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/theme_handler.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_THEME_HANDLER_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_THEME_HANDLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// A structure to hold the parsed theme data.
+struct ThemeInfo : public Extension::ManifestData {
+ // Define out of line constructor/destructor to please Clang.
+ ThemeInfo();
+ ~ThemeInfo() override;
+
+ static const base::DictionaryValue* GetImages(const Extension* extension);
+ static const base::DictionaryValue* GetColors(const Extension* extension);
+ static const base::DictionaryValue* GetTints(const Extension* extension);
+ static const base::DictionaryValue* GetDisplayProperties(
+ const Extension* extension);
+
+ // A map of resource id's to relative file paths.
+ std::unique_ptr<base::DictionaryValue> theme_images_;
+
+ // A map of color names to colors.
+ std::unique_ptr<base::DictionaryValue> theme_colors_;
+
+ // A map of color names to colors.
+ std::unique_ptr<base::DictionaryValue> theme_tints_;
+
+ // A map of display properties.
+ std::unique_ptr<base::DictionaryValue> theme_display_properties_;
+};
+
+// Parses the "theme" manifest key.
+class ThemeHandler : public ManifestHandler {
+ public:
+ ThemeHandler();
+ ~ThemeHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ThemeHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_THEME_HANDLER_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.cc b/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.cc
new file mode 100644
index 00000000000..51c6303f21e
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.cc
@@ -0,0 +1,181 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
+
+#include <memory>
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/grit/generated_resources.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/manifest_permission.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "ipc/ipc_message.h"
+
+using extensions::api::manifest_types::ChromeUIOverrides;
+
+namespace extensions {
+
+// The manifest permission implementation supports a permission for overriding
+// the bookmark UI.
+class UIOverridesHandler::ManifestPermissionImpl : public ManifestPermission {
+ public:
+ explicit ManifestPermissionImpl(bool override_bookmarks_ui_permission)
+ : override_bookmarks_ui_permission_(override_bookmarks_ui_permission) {}
+
+ // extensions::ManifestPermission overrides.
+ std::string name() const override { return manifest_keys::kUIOverride; }
+
+ std::string id() const override { return name(); }
+
+ PermissionIDSet GetPermissions() const override {
+ PermissionIDSet permissions;
+ if (override_bookmarks_ui_permission_) {
+ // TODO(sashab): Add rule to ChromePermissionMessageProvider:
+ // kOverrideBookmarksUI ->
+ // IDS_EXTENSION_PROMPT_WARNING_OVERRIDE_BOOKMARKS_UI
+ permissions.insert(APIPermission::kOverrideBookmarksUI);
+ }
+ return permissions;
+ }
+
+ bool FromValue(const base::Value* value) override {
+ return value && value->GetAsBoolean(&override_bookmarks_ui_permission_);
+ }
+
+ std::unique_ptr<base::Value> ToValue() const override {
+ return std::unique_ptr<base::Value>(
+ new base::Value(override_bookmarks_ui_permission_));
+ }
+
+ std::unique_ptr<ManifestPermission> Diff(
+ const ManifestPermission* rhs) const override {
+ const ManifestPermissionImpl* other =
+ static_cast<const ManifestPermissionImpl*>(rhs);
+
+ return std::make_unique<ManifestPermissionImpl>(
+ override_bookmarks_ui_permission_ &&
+ !other->override_bookmarks_ui_permission_);
+ }
+
+ std::unique_ptr<ManifestPermission> Union(
+ const ManifestPermission* rhs) const override {
+ const ManifestPermissionImpl* other =
+ static_cast<const ManifestPermissionImpl*>(rhs);
+
+ return std::make_unique<ManifestPermissionImpl>(
+ override_bookmarks_ui_permission_ ||
+ other->override_bookmarks_ui_permission_);
+ }
+
+ std::unique_ptr<ManifestPermission> Intersect(
+ const ManifestPermission* rhs) const override {
+ const ManifestPermissionImpl* other =
+ static_cast<const ManifestPermissionImpl*>(rhs);
+
+ return std::make_unique<ManifestPermissionImpl>(
+ override_bookmarks_ui_permission_ &&
+ other->override_bookmarks_ui_permission_);
+ }
+
+ private:
+ bool override_bookmarks_ui_permission_;
+};
+
+UIOverrides::UIOverrides() {}
+
+UIOverrides::~UIOverrides() {}
+
+const UIOverrides* UIOverrides::Get(const Extension* extension) {
+ return static_cast<UIOverrides*>(
+ extension->GetManifestData(manifest_keys::kUIOverride));
+}
+
+bool UIOverrides::RemovesBookmarkButton(const Extension* extension) {
+ const UIOverrides* ui_overrides = Get(extension);
+ return ui_overrides && ui_overrides->bookmarks_ui &&
+ ui_overrides->bookmarks_ui->remove_button &&
+ *ui_overrides->bookmarks_ui->remove_button;
+}
+
+bool UIOverrides::RemovesBookmarkShortcut(const Extension* extension) {
+ const UIOverrides* ui_overrides = Get(extension);
+ return ui_overrides && ui_overrides->bookmarks_ui &&
+ ui_overrides->bookmarks_ui->remove_bookmark_shortcut &&
+ *ui_overrides->bookmarks_ui->remove_bookmark_shortcut;
+}
+
+bool UIOverrides::RemovesBookmarkAllTabsShortcut(const Extension* extension) {
+ const UIOverrides* ui_overrides = Get(extension);
+ return ui_overrides && ui_overrides->bookmarks_ui &&
+ ui_overrides->bookmarks_ui->remove_bookmark_open_pages_shortcut &&
+ *ui_overrides->bookmarks_ui->remove_bookmark_open_pages_shortcut;
+}
+
+UIOverridesHandler::UIOverridesHandler() {}
+
+UIOverridesHandler::~UIOverridesHandler() {}
+
+bool UIOverridesHandler::Parse(Extension* extension, base::string16* error) {
+ const base::Value* dict = NULL;
+ CHECK(extension->manifest()->Get(manifest_keys::kUIOverride, &dict));
+ std::unique_ptr<ChromeUIOverrides> overrides(
+ ChromeUIOverrides::FromValue(*dict, error));
+ if (!overrides)
+ return false;
+
+ std::unique_ptr<UIOverrides> info(new UIOverrides);
+ info->bookmarks_ui.swap(overrides->bookmarks_ui);
+ if (!info->bookmarks_ui) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidEmptyDictionary,
+ manifest_keys::kUIOverride);
+ return false;
+ }
+ info->manifest_permission.reset(new ManifestPermissionImpl(
+ info->bookmarks_ui.get() != NULL));
+ extension->SetManifestData(manifest_keys::kUIOverride, std::move(info));
+ return true;
+}
+
+bool UIOverridesHandler::Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ const UIOverrides* ui_overrides = UIOverrides::Get(extension);
+
+ if (ui_overrides && ui_overrides->bookmarks_ui) {
+ if (!FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled()) {
+ warnings->push_back(InstallWarning(
+ ErrorUtils::FormatErrorMessage(
+ manifest_errors::kUnrecognizedManifestProperty,
+ manifest_keys::kBookmarkUI,
+ manifest_keys::kUIOverride)));
+ }
+ }
+
+ return true;
+}
+
+ManifestPermission* UIOverridesHandler::CreatePermission() {
+ return new ManifestPermissionImpl(false);
+}
+
+ManifestPermission* UIOverridesHandler::CreateInitialRequiredPermission(
+ const Extension* extension) {
+ const UIOverrides* data = UIOverrides::Get(extension);
+ if (data)
+ return data->manifest_permission->Clone().release();
+ return NULL;
+}
+base::span<const char* const> UIOverridesHandler::Keys() const {
+ static constexpr const char* kKeys[] = {manifest_keys::kUIOverride};
+ return kKeys;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.h b/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.h
new file mode 100644
index 00000000000..c9cbe44cbb3
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler.h
@@ -0,0 +1,64 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_UI_OVERRIDES_HANDLER_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_UI_OVERRIDES_HANDLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/common/extensions/api/manifest_types.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+class ManifestPermission;
+
+// UIOverrides is associated with "chrome_ui_overrides" manifest key, and
+// represents manifest settings to override aspects of the Chrome user
+// interface.
+struct UIOverrides : public Extension::ManifestData {
+ UIOverrides();
+ ~UIOverrides() override;
+
+ static const UIOverrides* Get(const Extension* extension);
+
+ static bool RemovesBookmarkButton(const Extension* extension);
+ static bool RemovesBookmarkShortcut(const Extension* extension);
+ static bool RemovesBookmarkAllTabsShortcut(const Extension* extension);
+
+ std::unique_ptr<api::manifest_types::ChromeUIOverrides::Bookmarks_ui>
+ bookmarks_ui;
+
+ std::unique_ptr<ManifestPermission> manifest_permission;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UIOverrides);
+};
+
+class UIOverridesHandler : public ManifestHandler {
+ public:
+ UIOverridesHandler();
+ ~UIOverridesHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ ManifestPermission* CreatePermission() override;
+ ManifestPermission* CreateInitialRequiredPermission(
+ const Extension* extension) override;
+
+ private:
+ class ManifestPermissionImpl;
+
+ base::span<const char* const> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(UIOverridesHandler);
+};
+
+} // namespace extensions
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_UI_OVERRIDES_HANDLER_H_
diff --git a/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler_unittest.cc b/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler_unittest.cc
new file mode 100644
index 00000000000..4ed61a42315
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_handlers/ui_overrides_handler_unittest.cc
@@ -0,0 +1,107 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kManifest[] =
+ "{"
+ " \"version\" : \"1.0.0.0\","
+ " \"manifest_version\" : 2,"
+ " \"name\" : \"Test\","
+ " \"chrome_ui_overrides\" : {"
+ " \"bookmarks_ui\" : {"
+ " \"remove_button\" : true,"
+ " \"remove_bookmark_shortcut\" : true"
+ " }"
+ " }"
+ "}";
+
+const char kBrokenManifest[] =
+ "{"
+ " \"version\" : \"1.0.0.0\","
+ " \"manifest_version\" : 2,"
+ " \"name\" : \"Test\","
+ " \"chrome_ui_overrides\" : {"
+ " }"
+ "}";
+
+using extensions::api::manifest_types::ChromeUIOverrides;
+using extensions::Extension;
+using extensions::Manifest;
+using extensions::UIOverrides;
+namespace manifest_keys = extensions::manifest_keys;
+
+class UIOverrideTest : public testing::Test {
+};
+
+
+TEST_F(UIOverrideTest, ParseManifest) {
+ extensions::ScopedCurrentChannel channel(version_info::Channel::DEV);
+ // This functionality requires a feature flag.
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ "--enable-override-bookmarks-ui", "1");
+ std::string manifest(kManifest);
+ JSONStringValueDeserializer json(manifest);
+ std::string error;
+ std::unique_ptr<base::Value> root(json.Deserialize(NULL, &error));
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(root->is_dict());
+ scoped_refptr<Extension> extension = Extension::Create(
+ base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
+ Manifest::INVALID_LOCATION,
+ *static_cast<base::DictionaryValue*>(root.get()),
+ Extension::NO_FLAGS,
+ &error);
+ ASSERT_TRUE(extension.get()) << error;
+ ASSERT_TRUE(extension->manifest()->HasPath(manifest_keys::kUIOverride));
+
+ UIOverrides* ui_override = static_cast<UIOverrides*>(
+ extension->GetManifestData(manifest_keys::kUIOverride));
+ ASSERT_TRUE(ui_override);
+ ASSERT_TRUE(ui_override->bookmarks_ui);
+ EXPECT_TRUE(ui_override->bookmarks_ui->remove_button);
+ EXPECT_TRUE(ui_override->bookmarks_ui->remove_bookmark_shortcut);
+}
+
+TEST_F(UIOverrideTest, ParseBrokenManifest) {
+ extensions::ScopedCurrentChannel channel(version_info::Channel::DEV);
+ // This functionality requires a feature flag.
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ "--enable-override-bookmarks-ui", "1");
+ std::string manifest(kBrokenManifest);
+ JSONStringValueDeserializer json(manifest);
+ std::string error;
+ std::unique_ptr<base::Value> root(json.Deserialize(NULL, &error));
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(root->is_dict());
+ scoped_refptr<Extension> extension = Extension::Create(
+ base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
+ Manifest::INVALID_LOCATION,
+ *static_cast<base::DictionaryValue*>(root.get()),
+ Extension::NO_FLAGS,
+ &error);
+ EXPECT_FALSE(extension.get());
+ EXPECT_EQ(
+ extensions::ErrorUtils::FormatErrorMessage(
+ extensions::manifest_errors::kInvalidEmptyDictionary,
+ extensions::manifest_keys::kUIOverride),
+ error);
+}
+
+} // namespace
diff --git a/chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.cc b/chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.cc
new file mode 100644
index 00000000000..656b54a7e8e
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/version_info/version_info.h"
+
+ChromeManifestTest::ChromeManifestTest()
+ // CHANNEL_UNKNOWN == trunk.
+ : current_channel_(version_info::Channel::UNKNOWN) {}
+
+ChromeManifestTest::~ChromeManifestTest() {
+}
+
+base::FilePath ChromeManifestTest::GetTestDataDir() {
+ base::FilePath path;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &path);
+ return path.AppendASCII("extensions").AppendASCII("manifest_tests");
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.h b/chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.h
new file mode 100644
index 00000000000..bfcd99b40fd
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/chrome_manifest_test.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_TESTS_CHROME_MANIFEST_TEST_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_TESTS_CHROME_MANIFEST_TEST_H_
+
+#include "base/macros.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_test.h"
+
+// Base class for unit tests that load manifest data from Chrome TEST_DATA_DIR.
+// TODO(jamescook): Move this class and all subclasses into the extensions
+// namespace.
+class ChromeManifestTest : public extensions::ManifestTest {
+ public:
+ ChromeManifestTest();
+ ~ChromeManifestTest() override;
+
+ // ManifestTest overrides:
+ base::FilePath GetTestDataDir() override;
+
+ private:
+ // Force the manifest tests to run as though they are on trunk, since several
+ // tests rely on manifest features being available that aren't on
+ // stable/beta.
+ //
+ // These objects nest, so if a test wants to explicitly test the behaviour
+ // on stable or beta, declare it inside that test.
+ extensions::ScopedCurrentChannel current_channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeManifestTest);
+};
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_TESTS_CHROME_MANIFEST_TEST_H_
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_about_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_about_unittest.cc
new file mode 100644
index 00000000000..88b89ddf3d0
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_about_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extensions::manifest_errors;
+
+class AboutPageManifestTest : public ChromeManifestTest {};
+
+TEST_F(AboutPageManifestTest, AboutPageInSharedModules) {
+ scoped_refptr<extensions::Extension> extension;
+ extension = LoadAndExpectSuccess("shared_module_about.json");
+ EXPECT_EQ(GURL("chrome-extension://" + extension->id() + "/about.html"),
+ extensions::ManifestURL::GetAboutPage(extension.get()));
+
+ Testcase testcases[] = {
+ // Forbid data types other than strings.
+ Testcase("shared_module_about_invalid_type.json",
+ errors::kInvalidAboutPage),
+
+ // Forbid absolute URLs.
+ Testcase("shared_module_about_absolute.json",
+ errors::kInvalidAboutPageExpectRelativePath)};
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc
new file mode 100644
index 00000000000..5009ec2535f
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc
@@ -0,0 +1,261 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+class ExtensionManifestBackgroundTest : public ChromeManifestTest {
+};
+
+// TODO(devlin): Can this file move to //extensions?
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundPermission) {
+ LoadAndExpectError("background_permission.json",
+ errors::kBackgroundPermissionNeeded);
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundScripts) {
+ std::string error;
+ base::Value manifest = LoadManifest("background_scripts.json", &error);
+ ASSERT_TRUE(manifest.is_dict());
+
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess(ManifestData(&manifest, "")));
+ ASSERT_TRUE(extension.get());
+ const std::vector<std::string>& background_scripts =
+ BackgroundInfo::GetBackgroundScripts(extension.get());
+ ASSERT_EQ(2u, background_scripts.size());
+ EXPECT_EQ("foo.js", background_scripts[0u]);
+ EXPECT_EQ("bar/baz.js", background_scripts[1u]);
+
+ EXPECT_TRUE(BackgroundInfo::HasBackgroundPage(extension.get()));
+ EXPECT_EQ(
+ std::string("/") + kGeneratedBackgroundPageFilename,
+ BackgroundInfo::GetBackgroundURL(extension.get()).path());
+
+ manifest.SetPath({"background", "page"}, base::Value("monkey.html"));
+ LoadAndExpectError(ManifestData(&manifest, ""),
+ errors::kInvalidBackgroundCombination);
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundPage) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("background_page.json"));
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ("/foo.html",
+ BackgroundInfo::GetBackgroundURL(extension.get()).path());
+ EXPECT_TRUE(BackgroundInfo::AllowJSAccess(extension.get()));
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundAllowNoJsAccess) {
+ scoped_refptr<Extension> extension;
+ extension = LoadAndExpectSuccess("background_allow_no_js_access.json");
+ ASSERT_TRUE(extension.get());
+ EXPECT_FALSE(BackgroundInfo::AllowJSAccess(extension.get()));
+
+ extension = LoadAndExpectSuccess("background_allow_no_js_access2.json");
+ ASSERT_TRUE(extension.get());
+ EXPECT_FALSE(BackgroundInfo::AllowJSAccess(extension.get()));
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundPageWebRequest) {
+ ScopedCurrentChannel current_channel(version_info::Channel::DEV);
+
+ std::string error;
+ base::Value manifest = LoadManifest("background_page.json", &error);
+ ASSERT_FALSE(manifest.is_none());
+ manifest.SetPath({"background", "persistent"}, base::Value(false));
+ manifest.SetKey(keys::kManifestVersion, base::Value(2));
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess(ManifestData(&manifest, "")));
+ ASSERT_TRUE(extension.get());
+ EXPECT_TRUE(BackgroundInfo::HasLazyBackgroundPage(extension.get()));
+
+ base::Value permissions(base::Value::Type::LIST);
+ permissions.Append(base::Value("webRequest"));
+ manifest.SetKey(keys::kPermissions, std::move(permissions));
+ LoadAndExpectError(ManifestData(&manifest, ""),
+ errors::kWebRequestConflictsWithLazyBackground);
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundPageTransientBackground) {
+ ScopedCurrentChannel current_channel(version_info::Channel::DEV);
+
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess(ManifestData(base::test::ParseJson(R"(
+ {
+ "name": "test",
+ "manifest_version": 2,
+ "version": "1",
+ "background": {
+ "page": "foo.html"
+ }
+ })"),
+ "")));
+ ASSERT_TRUE(extension.get());
+ EXPECT_TRUE(BackgroundInfo::HasPersistentBackgroundPage(extension.get()));
+
+ LoadAndExpectError(
+ ManifestData(base::test::ParseJson(R"(
+ {
+ "name": "test",
+ "manifest_version": 2,
+ "version": "1",
+ "permissions": [
+ "transientBackground"
+ ],
+ "background": {
+ "page": "foo.html"
+ }
+ })"),
+ ""),
+ errors::kTransientBackgroundConflictsWithPersistentBackground);
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundPagePersistentPlatformApp) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("background_page_persistent_app.json");
+ ASSERT_TRUE(extension->is_platform_app());
+ ASSERT_TRUE(BackgroundInfo::HasBackgroundPage(extension.get()));
+ EXPECT_FALSE(BackgroundInfo::HasPersistentBackgroundPage(extension.get()));
+
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ ManifestHandler::ValidateExtension(extension.get(), &error, &warnings);
+ // Persistent background pages are not supported for packaged apps.
+ // The persistent flag is ignored and a warining is printed.
+ EXPECT_EQ(1U, warnings.size());
+ EXPECT_EQ(errors::kInvalidBackgroundPersistentInPlatformApp,
+ warnings[0].message);
+}
+
+TEST_F(ExtensionManifestBackgroundTest, BackgroundPagePersistentInvalidKey) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("background_page_invalid_persistent_key_app.json");
+ ASSERT_TRUE(extension->is_platform_app());
+ ASSERT_TRUE(BackgroundInfo::HasBackgroundPage(extension.get()));
+ EXPECT_FALSE(BackgroundInfo::HasPersistentBackgroundPage(extension.get()));
+
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ ManifestHandler::ValidateExtension(extension.get(), &error, &warnings);
+ // The key 'background.persistent' is not supported for packaged apps.
+ EXPECT_EQ(1U, warnings.size());
+ EXPECT_EQ(errors::kBackgroundPersistentInvalidForPlatformApps,
+ warnings[0].message);
+}
+
+// Tests channel restriction on "background.service_worker" key.
+TEST_F(ExtensionManifestBackgroundTest, ServiceWorkerBasedBackgroundKey) {
+ // TODO(lazyboy): Add exhaustive tests here, e.g.
+ // - specifying a non-existent file.
+ // - specifying multiple files.
+ // - specifying invalid type (non-string) values.
+ {
+ ScopedCurrentChannel dev(version_info::Channel::DEV);
+ scoped_refptr<Extension> extension =
+ LoadAndExpectWarning("service_worker_based_background.json",
+ "'background.service_worker' requires canary "
+ "channel or newer, but this is the dev channel.");
+ }
+ {
+ ScopedCurrentChannel canary(version_info::Channel::CANARY);
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("service_worker_based_background.json");
+ }
+ {
+ ScopedCurrentChannel trunk(version_info::Channel::UNKNOWN);
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("service_worker_based_background.json");
+ }
+}
+
+TEST_F(ExtensionManifestBackgroundTest, ManifestV3Restrictions) {
+ auto get_expected_error = [](base::StringPiece key) {
+ return ErrorUtils::FormatErrorMessage(
+ errors::kBackgroundSpecificationInvalidForManifestV3, key);
+ };
+
+ {
+ constexpr char kManifestBackgroundPage[] =
+ R"({
+ "name": "MV3 Test",
+ "manifest_version": 3,
+ "version": "0.1",
+ "background": {
+ "page": "background.html"
+ }
+ })";
+ base::Value manifest_value = base::test::ParseJson(kManifestBackgroundPage);
+ LoadAndExpectError(
+ ManifestData(std::move(manifest_value), "background page"),
+ get_expected_error(keys::kBackgroundPage));
+ }
+ {
+ constexpr char kManifestBackgroundScripts[] =
+ R"({
+ "name": "MV3 Test",
+ "manifest_version": 3,
+ "version": "0.1",
+ "background": {
+ "scripts": ["background.js"]
+ }
+ })";
+ base::Value manifest_value =
+ base::test::ParseJson(kManifestBackgroundScripts);
+ LoadAndExpectError(
+ ManifestData(std::move(manifest_value), "background scripts"),
+ get_expected_error(keys::kBackgroundScripts));
+ }
+ {
+ constexpr char kManifestBackgroundPersistent[] =
+ R"({
+ "name": "MV3 Test",
+ "manifest_version": 3,
+ "version": "0.1",
+ "background": {
+ "service_worker": "worker.js",
+ "persistent": true
+ }
+ })";
+ base::Value manifest_value =
+ base::test::ParseJson(kManifestBackgroundPersistent);
+ LoadAndExpectError(
+ ManifestData(std::move(manifest_value), "persistent background"),
+ get_expected_error(keys::kBackgroundPersistent));
+ }
+ {
+ // An extension with no background key present should still be allowed.
+ constexpr char kManifestBackgroundPersistent[] =
+ R"({
+ "name": "MV3 Test",
+ "manifest_version": 3,
+ "version": "0.1"
+ })";
+ base::Value manifest_value =
+ base::test::ParseJson(kManifestBackgroundPersistent);
+ LoadAndExpectSuccess(
+ ManifestData(std::move(manifest_value), "no background"));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc
new file mode 100644
index 00000000000..60568eb3a38
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "chrome/common/url_constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+typedef ChromeManifestTest ChromePermissionManifestTest;
+
+TEST_F(ChromePermissionManifestTest, ChromeURLPermissionInvalid) {
+ LoadAndExpectWarning("permission_chrome_url_invalid.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidPermissionScheme,
+ chrome::kChromeUINewTabURL));
+}
+
+TEST_F(ChromePermissionManifestTest, ChromeURLPermissionAllowedWithFlag) {
+ // Ignore the policy delegate for this test.
+ PermissionsData::SetPolicyDelegate(NULL);
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kExtensionsOnChromeURLs);
+ std::string error;
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("permission_chrome_url_invalid.json");
+ EXPECT_EQ("", error);
+ const GURL newtab_url(chrome::kChromeUINewTabURL);
+ EXPECT_TRUE(
+ extension->permissions_data()->CanAccessPage(newtab_url, 0, &error))
+ << error;
+}
+
+TEST_F(ChromePermissionManifestTest,
+ ChromeResourcesPermissionValidOnlyForComponents) {
+ LoadAndExpectWarning("permission_chrome_resources_url.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidPermissionScheme,
+ "chrome://resources/"));
+ std::string error;
+ LoadExtension(ManifestData("permission_chrome_resources_url.json"),
+ &error,
+ extensions::Manifest::COMPONENT,
+ Extension::NO_FLAGS);
+ EXPECT_EQ("", error);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
new file mode 100644
index 00000000000..680a8ca11be
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extensions::manifest_errors;
+namespace keys = extensions::manifest_keys;
+using extensions::ErrorUtils;
+
+class ContentSecurityPolicyManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(ContentSecurityPolicyManifestTest, InsecureContentSecurityPolicy) {
+ Testcase testcases[] = {
+ Testcase("insecure_contentsecuritypolicy_1.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidCSPInsecureValueIgnored,
+ keys::kContentSecurityPolicy, "http://example.com",
+ "script-src")),
+ Testcase(
+ "insecure_contentsecuritypolicy_2.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidCSPInsecureValueIgnored,
+ keys::kContentSecurityPolicy, "'unsafe-inline'", "script-src")),
+ Testcase("insecure_contentsecuritypolicy_3.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidCSPMissingSecureSrc,
+ keys::kContentSecurityPolicy, "object-src"))};
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_WARNING);
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_default_extent_path_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_default_extent_path_unittest.cc
new file mode 100644
index 00000000000..9d2a9bb58cf
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_default_extent_path_unittest.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST_F(ChromeManifestTest, DefaultPathForExtent) {
+ scoped_refptr<extensions::Extension> extension(
+ LoadAndExpectSuccess("default_path_for_extent.json"));
+
+ ASSERT_EQ(1u, extension->web_extent().patterns().size());
+ EXPECT_EQ("/*", extension->web_extent().patterns().begin()->path());
+ EXPECT_TRUE(extension->web_extent().MatchesURL(
+ GURL("http://www.google.com/monkey")));
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
new file mode 100644
index 00000000000..427c04d370e
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/chrome_manifest_url_handlers.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class DevToolsPageManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(DevToolsPageManifestTest, DevToolsExtensions) {
+ LoadAndExpectError("devtools_extension_url_invalid_type.json",
+ extensions::manifest_errors::kInvalidDevToolsPage);
+
+ scoped_refptr<extensions::Extension> extension;
+ extension = LoadAndExpectSuccess("devtools_extension.json");
+ EXPECT_EQ(extension->url().spec() + "devtools.html",
+ extensions::chrome_manifest_urls::GetDevToolsPage(extension.get())
+ .spec());
+ EXPECT_TRUE(extension->permissions_data()->HasEffectiveAccessToAllHosts());
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_dummy_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_dummy_unittest.cc
new file mode 100644
index 00000000000..afc1392fcf7
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_dummy_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+namespace extensions {
+
+TEST_F(ChromeManifestTest, PlatformsKey) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("platforms_key.json");
+ EXPECT_EQ(0u, extension->install_warnings().size());
+}
+
+TEST_F(ChromeManifestTest, UnrecognizedKeyWarning) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectWarning("unrecognized_key.json",
+ "Unrecognized manifest key 'unrecognized_key_1'.");
+}
+
+// Tests that using the deprecated "plugins" key causes an install warning.
+TEST_F(ChromeManifestTest, DeprecatedPluginsKey) {
+ LoadAndExpectWarning("deprecated_plugins_key.json",
+ "Unrecognized manifest key 'plugins'.");
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc
new file mode 100644
index 00000000000..39ff3286e3b
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+#include "base/command_line.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extensions::manifest_errors;
+
+TEST_F(ChromeManifestTest, ExperimentalPermission) {
+ LoadAndExpectWarning(
+ "experimental.json",
+ "'experimental' requires the 'experimental-extension-apis' "
+ "command line switch to be enabled.");
+ LoadAndExpectSuccess("experimental.json", extensions::Manifest::COMPONENT);
+ LoadAndExpectSuccess("experimental.json", extensions::Manifest::INTERNAL,
+ extensions::Extension::FROM_WEBSTORE);
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ extensions::switches::kEnableExperimentalExtensionApis);
+ LoadAndExpectSuccess("experimental.json");
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc
new file mode 100644
index 00000000000..7bc29b6b11d
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extensions::manifest_errors;
+
+class HomepageURLManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(HomepageURLManifestTest, ParseHomepageURLs) {
+ scoped_refptr<extensions::Extension> extension(
+ LoadAndExpectSuccess("homepage_valid.json"));
+
+ Testcase testcases[] = {
+ Testcase("homepage_empty.json",
+ errors::kInvalidHomepageURL),
+ Testcase("homepage_invalid.json",
+ errors::kInvalidHomepageURL),
+ Testcase("homepage_bad_schema.json",
+ errors::kInvalidHomepageURL)
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(HomepageURLManifestTest, GetHomepageURL) {
+ scoped_refptr<extensions::Extension> extension(
+ LoadAndExpectSuccess("homepage_valid.json"));
+ EXPECT_EQ(GURL("http://foo.com#bar"),
+ extensions::ManifestURL::GetHomepageURL(extension.get()));
+
+ // The Google Gallery URL ends with the id, which depends on the path, which
+ // can be different in testing, so we just check the part before id.
+ extension = LoadAndExpectSuccess("homepage_google_hosted.json");
+ EXPECT_TRUE(base::StartsWith(
+ extensions::ManifestURL::GetHomepageURL(extension.get()).spec(),
+ "https://chrome.google.com/webstore/detail/",
+ base::CompareCase::INSENSITIVE_ASCII));
+
+ extension = LoadAndExpectSuccess("homepage_externally_hosted.json");
+ EXPECT_EQ(GURL(), extensions::ManifestURL::GetHomepageURL(extension.get()));
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_icons_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_icons_unittest.cc
new file mode 100644
index 00000000000..fe4c5688148
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_icons_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class IconsManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(IconsManifestTest, NormalizeIconPaths) {
+ scoped_refptr<extensions::Extension> extension(
+ LoadAndExpectSuccess("normalize_icon_paths.json"));
+ const ExtensionIconSet& icons = IconsInfo::GetIcons(extension.get());
+
+ EXPECT_EQ("16.png", icons.Get(extension_misc::EXTENSION_ICON_BITTY,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("48.png", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+ ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(IconsManifestTest, IconSizes) {
+ scoped_refptr<extensions::Extension> extension(
+ LoadAndExpectSuccess("init_icon_size.json"));
+ const ExtensionIconSet& icons = IconsInfo::GetIcons(extension.get());
+
+ EXPECT_EQ("16.png", icons.Get(extension_misc::EXTENSION_ICON_BITTY,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("24.png", icons.Get(extension_misc::EXTENSION_ICON_SMALLISH,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("32.png", icons.Get(extension_misc::EXTENSION_ICON_SMALL,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("48.png", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("128.png", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("256.png", icons.Get(extension_misc::EXTENSION_ICON_EXTRA_LARGE,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("512.png", icons.Get(extension_misc::EXTENSION_ICON_GIGANTOR,
+ ExtensionIconSet::MATCH_EXACTLY));
+
+ // Any old size will be accepted.
+ EXPECT_EQ("300.png", IconsInfo::GetIcons(extension.get())
+ .Get(300, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
new file mode 100644
index 00000000000..fa1de5ddd84
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+// The ID of test manifests requiring allowlisting.
+const char kAllowlistID[] = "lmadimbbgapmngbiclpjjngmdickadpl";
+
+} // namespace
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+class InitValueManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(InitValueManifestTest, InitFromValueInvalid) {
+ SimpleFeature::ScopedThreadUnsafeAllowlistForTest allowlist(kAllowlistID);
+ Testcase testcases[] = {
+ Testcase("init_invalid_version_missing.json", errors::kInvalidVersion),
+ Testcase("init_invalid_version_invalid.json", errors::kInvalidVersion),
+ Testcase("init_invalid_version_name_invalid.json",
+ errors::kInvalidVersionName),
+ Testcase("init_invalid_name_missing.json", errors::kInvalidName),
+ Testcase("init_invalid_name_invalid.json", errors::kInvalidName),
+ Testcase("init_invalid_description_invalid.json",
+ errors::kInvalidDescription),
+ Testcase("init_invalid_icons_invalid.json", errors::kInvalidIcons),
+ Testcase("init_invalid_icons_path_invalid.json",
+ errors::kInvalidIconPath),
+ Testcase("init_invalid_script_invalid.json",
+ errors::kInvalidContentScriptsList),
+ Testcase("init_invalid_script_item_invalid.json",
+ errors::kInvalidContentScript),
+ Testcase("init_invalid_script_matches_missing.json",
+ errors::kInvalidMatches),
+ Testcase("init_invalid_script_matches_invalid.json",
+ errors::kInvalidMatches),
+ Testcase("init_invalid_script_matches_empty.json",
+ errors::kInvalidMatchCount),
+ Testcase("init_invalid_script_match_item_invalid.json",
+ errors::kInvalidMatch),
+ Testcase("init_invalid_script_match_item_invalid_2.json",
+ errors::kInvalidMatch),
+ Testcase("init_invalid_script_files_missing.json", errors::kMissingFile),
+ Testcase("init_invalid_files_js_invalid.json", errors::kInvalidJsList),
+ Testcase("init_invalid_files_empty.json", errors::kMissingFile),
+ Testcase("init_invalid_files_js_empty_css_missing.json",
+ errors::kMissingFile),
+ Testcase("init_invalid_files_js_item_invalid.json", errors::kInvalidJs),
+ Testcase("init_invalid_files_css_invalid.json", errors::kInvalidCssList),
+ Testcase("init_invalid_files_css_item_invalid.json", errors::kInvalidCss),
+ Testcase("init_invalid_permissions_invalid.json",
+ errors::kInvalidPermissions),
+ Testcase("init_invalid_host_permissions_invalid.json",
+ errors::kInvalidHostPermissions),
+ Testcase("init_invalid_permissions_item_invalid.json",
+ errors::kInvalidPermission),
+ Testcase("init_invalid_options_url_invalid.json",
+ errors::kInvalidOptionsPage),
+ Testcase("init_invalid_locale_invalid.json",
+ errors::kInvalidDefaultLocale),
+ Testcase("init_invalid_locale_empty.json", errors::kInvalidDefaultLocale),
+ Testcase("init_invalid_min_chrome_invalid.json",
+ errors::kInvalidMinimumChromeVersion),
+ Testcase("init_invalid_chrome_version_too_low.json",
+ errors::kChromeVersionTooLow),
+ Testcase("init_invalid_short_name_empty.json", errors::kInvalidShortName),
+ Testcase("init_invalid_short_name_type.json", errors::kInvalidShortName),
+ };
+
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(InitValueManifestTest, InitFromValueValid) {
+ scoped_refptr<Extension> extension(LoadAndExpectSuccess(
+ "init_valid_minimal.json"));
+
+ base::FilePath path;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &path);
+ path = path.AppendASCII("extensions");
+
+ EXPECT_TRUE(crx_file::id_util::IdIsValid(extension->id()));
+ EXPECT_EQ("1.0.0.0", extension->VersionString());
+ EXPECT_EQ("my extension", extension->name());
+ EXPECT_EQ(extension->name(), extension->short_name());
+ EXPECT_EQ(extension->id(), extension->url().host());
+ EXPECT_EQ(extension->path(), path);
+ EXPECT_EQ(path, extension->path());
+
+ // Test permissions scheme.
+ // We allow unknown API permissions, so this will be valid until we better
+ // distinguish between API and host permissions.
+ LoadAndExpectSuccess("init_valid_permissions.json");
+
+ // Test with an options page.
+ extension = LoadAndExpectSuccess("init_valid_options.json");
+ EXPECT_EQ(extensions::kExtensionScheme,
+ OptionsPageInfo::GetOptionsPage(extension.get()).scheme());
+ EXPECT_EQ("/options.html",
+ OptionsPageInfo::GetOptionsPage(extension.get()).path());
+
+ // Test optional short_name field.
+ extension = LoadAndExpectSuccess("init_valid_short_name.json");
+ EXPECT_EQ("a very descriptive extension name", extension->name());
+ EXPECT_EQ("concise name", extension->short_name());
+
+ // Test optional version_name field.
+ extension = LoadAndExpectSuccess("init_valid_version_name.json");
+ EXPECT_EQ("1.0.0.0", extension->VersionString());
+ EXPECT_EQ("1.0 alpha", extension->GetVersionForDisplay());
+
+ Testcase testcases[] = {
+ // Test with a minimum_chrome_version.
+ Testcase("init_valid_minimum_chrome.json"),
+
+ // Test a hosted app with a minimum_chrome_version.
+ Testcase("init_valid_app_minimum_chrome.json"),
+
+ // Test a hosted app with a requirements section.
+ Testcase("init_valid_app_requirements.json"),
+
+ // Verify empty permission settings are considered valid.
+ Testcase("init_valid_permissions_empty.json"),
+
+ // We allow unknown API permissions, so this will be valid until we better
+ // distinguish between API and host permissions.
+ Testcase("init_valid_permissions_unknown.json")
+ };
+
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_SUCCESS);
+}
+
+TEST_F(InitValueManifestTest, InitFromValueValidNameInRTL) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ // No strong RTL characters in name.
+ scoped_refptr<Extension> extension(LoadAndExpectSuccess(
+ "init_valid_name_no_rtl.json"));
+
+ base::string16 localized_name(base::ASCIIToUTF16("Dictionary (by Google)"));
+ base::i18n::AdjustStringForLocaleDirection(&localized_name);
+ EXPECT_EQ(localized_name, base::UTF8ToUTF16(extension->name()));
+
+ // Strong RTL characters in name.
+ extension = LoadAndExpectSuccess("init_valid_name_strong_rtl.json");
+
+ localized_name = base::WideToUTF16(L"Dictionary (\x05D1\x05D2" L" Google)");
+ base::i18n::AdjustStringForLocaleDirection(&localized_name);
+ EXPECT_EQ(localized_name, base::UTF8ToUTF16(extension->name()));
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc
new file mode 100644
index 00000000000..984de2d3556
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+#include "base/command_line.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/app_isolation_info.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+class IsolatedAppsManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(IsolatedAppsManifestTest, IsolatedApps) {
+ LoadAndExpectWarning(
+ "isolated_app_valid.json",
+ "'experimental' requires the 'experimental-extension-apis' "
+ "command line switch to be enabled.");
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ extensions::switches::kEnableExperimentalExtensionApis);
+ scoped_refptr<Extension> extension2(
+ LoadAndExpectSuccess("isolated_app_valid.json"));
+ EXPECT_TRUE(AppIsolationInfo::HasIsolatedStorage(extension2.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_kiosk_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_kiosk_unittest.cc
new file mode 100644
index 00000000000..7a45ea4670a
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_kiosk_unittest.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class ExtensionManifestKioskModeTest : public ChromeManifestTest {
+};
+
+TEST_F(ExtensionManifestKioskModeTest, InvalidKioskEnabled) {
+ LoadAndExpectError("kiosk_enabled_invalid.json",
+ manifest_errors::kInvalidKioskEnabled);
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskEnabledHostedApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_enabled_hosted_app.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskEnabled(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskEnabledPackagedApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_enabled_packaged_app.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskEnabled(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskEnabledExtension) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_enabled_extension.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskEnabled(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskEnabledPlatformApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_enabled_platform_app.json"));
+ EXPECT_TRUE(KioskModeInfo::IsKioskEnabled(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskDisabledPlatformApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_disabled_platform_app.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskEnabled(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskDefaultPlatformApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_default_platform_app.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskEnabled(extension.get()));
+ EXPECT_FALSE(KioskModeInfo::IsKioskOnly(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskEnabledDefaultRequired) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_enabled_platform_app.json"));
+ EXPECT_TRUE(KioskModeInfo::IsKioskEnabled(extension.get()));
+ EXPECT_FALSE(KioskModeInfo::IsKioskOnly(extension.get()));
+}
+
+// 'kiosk_only' key should be set only from ChromeOS.
+#if defined(OS_CHROMEOS)
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyPlatformApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_only_platform_app.json"));
+ EXPECT_TRUE(KioskModeInfo::IsKioskOnly(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyInvalid) {
+ LoadAndExpectError("kiosk_only_invalid.json",
+ manifest_errors::kInvalidKioskOnly);
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyButNotEnabled) {
+ LoadAndExpectError("kiosk_only_not_enabled.json",
+ manifest_errors::kInvalidKioskOnlyButNotEnabled);
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyHostedApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_only_hosted_app.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskOnly(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyPackagedApp) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_only_packaged_app.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskOnly(extension.get()));
+}
+
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyExtension) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_only_extension.json"));
+ EXPECT_FALSE(KioskModeInfo::IsKioskOnly(extension.get()));
+}
+#else
+TEST_F(ExtensionManifestKioskModeTest, KioskOnlyFromNonChromeos) {
+ LoadAndExpectWarning("kiosk_only_platform_app.json",
+ "'kiosk_only' is not allowed for specified platform.");
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
new file mode 100644
index 00000000000..9da6ddb14d7
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/stl_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+class AppLaunchManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(AppLaunchManifestTest, AppLaunchContainer) {
+ scoped_refptr<Extension> extension;
+
+ extension = LoadAndExpectSuccess("launch_tab.json");
+ EXPECT_EQ(LaunchContainer::kLaunchContainerTab,
+ AppLaunchInfo::GetLaunchContainer(extension.get()));
+
+ extension = LoadAndExpectSuccess("launch_panel.json");
+ EXPECT_EQ(LaunchContainer::kLaunchContainerPanelDeprecated,
+ AppLaunchInfo::GetLaunchContainer(extension.get()));
+
+ extension = LoadAndExpectSuccess("launch_default.json");
+ EXPECT_EQ(LaunchContainer::kLaunchContainerTab,
+ AppLaunchInfo::GetLaunchContainer(extension.get()));
+
+ extension = LoadAndExpectSuccess("launch_width.json");
+ EXPECT_EQ(640, AppLaunchInfo::GetLaunchWidth(extension.get()));
+
+ extension = LoadAndExpectSuccess("launch_height.json");
+ EXPECT_EQ(480, AppLaunchInfo::GetLaunchHeight(extension.get()));
+
+ Testcase testcases[] = {
+ Testcase("launch_window.json", errors::kInvalidLaunchContainer),
+ Testcase("launch_container_invalid_type.json",
+ errors::kInvalidLaunchContainer),
+ Testcase("launch_container_invalid_value.json",
+ errors::kInvalidLaunchContainer),
+ Testcase("launch_container_without_launch_url.json",
+ errors::kLaunchURLRequired),
+ Testcase("launch_width_invalid.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValueContainer,
+ keys::kLaunchWidth)),
+ Testcase("launch_width_negative.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWidth)),
+ Testcase("launch_height_invalid.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValueContainer,
+ keys::kLaunchHeight)),
+ Testcase("launch_height_negative.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchHeight))
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(AppLaunchManifestTest, AppLaunchURL) {
+ Testcase testcases[] = {
+ Testcase("launch_path_and_url.json",
+ errors::kLaunchPathAndURLAreExclusive),
+ Testcase("launch_path_and_extent.json",
+ errors::kLaunchPathAndExtentAreExclusive),
+ Testcase("launch_path_invalid_type.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchLocalPath)),
+ Testcase("launch_path_invalid_value.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchLocalPath)),
+ Testcase("launch_path_invalid_localized.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchLocalPath)),
+ Testcase("launch_url_invalid_type_1.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL)),
+ Testcase("launch_url_invalid_type_2.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL)),
+ Testcase("launch_url_invalid_type_3.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL)),
+ Testcase("launch_url_invalid_localized.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL))
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+
+ scoped_refptr<Extension> extension;
+ extension = LoadAndExpectSuccess("launch_local_path.json");
+ EXPECT_EQ(extension->url().spec() + "launch.html",
+ AppLaunchInfo::GetFullLaunchURL(extension.get()).spec());
+
+ extension = LoadAndExpectSuccess("launch_local_path_localized.json");
+ EXPECT_EQ(extension->url().spec() + "launch.html",
+ AppLaunchInfo::GetFullLaunchURL(extension.get()).spec());
+
+ LoadAndExpectError("launch_web_url_relative.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidLaunchValue,
+ keys::kLaunchWebURL));
+
+ extension = LoadAndExpectSuccess("launch_web_url_absolute.json");
+ EXPECT_EQ(GURL("http://www.google.com/launch.html"),
+ AppLaunchInfo::GetFullLaunchURL(extension.get()));
+ extension = LoadAndExpectSuccess("launch_web_url_localized.json");
+ EXPECT_EQ(GURL("http://www.google.com/launch.html"),
+ AppLaunchInfo::GetFullLaunchURL(extension.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc
new file mode 100644
index 00000000000..6d4809a4bd0
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extensions::manifest_errors;
+
+TEST_F(ChromeManifestTest, ManifestVersionError) {
+ std::unique_ptr<base::DictionaryValue> manifest1(new base::DictionaryValue());
+ manifest1->SetString("name", "Miles");
+ manifest1->SetString("version", "0.55");
+
+ std::unique_ptr<base::DictionaryValue> manifest2(manifest1->DeepCopy());
+ manifest2->SetInteger("manifest_version", 1);
+
+ std::unique_ptr<base::DictionaryValue> manifest3(manifest1->DeepCopy());
+ manifest3->SetInteger("manifest_version", 2);
+
+ struct {
+ const char* test_name;
+ bool require_modern_manifest_version;
+ base::DictionaryValue* manifest;
+ bool expect_error;
+ } test_data[] = {
+ {"require_modern_with_default", true, manifest1.get(), true},
+ {"require_modern_with_v1", true, manifest2.get(), true},
+ {"require_modern_with_v2", true, manifest3.get(), false},
+ {"dont_require_modern_with_default", false, manifest1.get(), true},
+ {"dont_require_modern_with_v1", false, manifest2.get(), true},
+ {"dont_require_modern_with_v2", false, manifest3.get(), false},
+ };
+
+ for (auto& entry : test_data) {
+ int create_flags = Extension::NO_FLAGS;
+ if (entry.require_modern_manifest_version)
+ create_flags |= Extension::REQUIRE_MODERN_MANIFEST_VERSION;
+ if (entry.expect_error) {
+ LoadAndExpectError(ManifestData(entry.manifest, entry.test_name),
+ errors::kInvalidManifestVersionOld,
+ extensions::Manifest::UNPACKED, create_flags);
+ } else {
+ LoadAndExpectSuccess(ManifestData(entry.manifest, entry.test_name),
+ extensions::Manifest::UNPACKED, create_flags);
+ }
+ }
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc
new file mode 100644
index 00000000000..67934e43d9e
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/offline_enabled_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+class ExtensionManifestOfflineEnabledTest : public ChromeManifestTest {
+};
+
+TEST_F(ExtensionManifestOfflineEnabledTest, OfflineEnabled) {
+ LoadAndExpectError("offline_enabled_invalid.json",
+ errors::kInvalidOfflineEnabled);
+ scoped_refptr<Extension> extension_0(
+ LoadAndExpectSuccess("offline_enabled_extension.json"));
+ EXPECT_TRUE(OfflineEnabledInfo::IsOfflineEnabled(extension_0.get()));
+ scoped_refptr<Extension> extension_1(
+ LoadAndExpectSuccess("offline_enabled_packaged_app.json"));
+ EXPECT_TRUE(OfflineEnabledInfo::IsOfflineEnabled(extension_1.get()));
+ scoped_refptr<Extension> extension_2(
+ LoadAndExpectSuccess("offline_disabled_packaged_app.json"));
+ EXPECT_FALSE(OfflineEnabledInfo::IsOfflineEnabled(extension_2.get()));
+ scoped_refptr<Extension> extension_3(
+ LoadAndExpectSuccess("offline_default_packaged_app.json"));
+ EXPECT_FALSE(OfflineEnabledInfo::IsOfflineEnabled(extension_3.get()));
+ scoped_refptr<Extension> extension_4(
+ LoadAndExpectSuccess("offline_enabled_hosted_app.json"));
+ EXPECT_TRUE(OfflineEnabledInfo::IsOfflineEnabled(extension_4.get()));
+ scoped_refptr<Extension> extension_5(
+ LoadAndExpectSuccess("offline_default_platform_app.json"));
+ EXPECT_TRUE(OfflineEnabledInfo::IsOfflineEnabled(extension_5.get()));
+ scoped_refptr<Extension> extension_6(
+ LoadAndExpectSuccess("offline_default_platform_app_with_webview.json"));
+ EXPECT_FALSE(OfflineEnabledInfo::IsOfflineEnabled(extension_6.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc
new file mode 100644
index 00000000000..e9fd914e173
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Tests that the old permission name "unlimited_storage" still works for
+// backwards compatibility (we renamed it to "unlimitedStorage").
+TEST_F(ChromeManifestTest, OldUnlimitedStoragePermission) {
+ scoped_refptr<extensions::Extension> extension = LoadAndExpectSuccess(
+ "old_unlimited_storage.json", extensions::Manifest::INTERNAL,
+ extensions::Extension::NO_FLAGS);
+ EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kUnlimitedStorage));
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
new file mode 100644
index 00000000000..e6d0e819466
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::FeatureSwitch;
+using extensions::OptionsPageInfo;
+
+namespace {
+
+class OptionsPageManifestTest : public ChromeManifestTest {
+ protected:
+ // Tests how the options_ui manifest key affects the open-in-tab and
+ // chromes-style behaviour.
+ testing::AssertionResult TestOptionsUIChromeStyleAndOpenInTab() {
+ // Explicitly specifying true in the manifest for options_ui.chrome_style
+ // and options_ui.open_in_tab sets them both to true.
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess("options_ui_flags_true.json");
+ EXPECT_TRUE(OptionsPageInfo::ShouldUseChromeStyle(extension.get()));
+ EXPECT_TRUE(OptionsPageInfo::ShouldOpenInTab(extension.get()));
+
+ // Explicitly specifying false in the manifest for options_ui.chrome_style
+ // and options_ui.open_in_tab sets them both to false.
+ extension = LoadAndExpectSuccess("options_ui_flags_false.json");
+ EXPECT_FALSE(OptionsPageInfo::ShouldUseChromeStyle(extension.get()));
+ EXPECT_FALSE(OptionsPageInfo::ShouldOpenInTab(extension.get()));
+
+ // Specifying an options_ui key but neither options_ui.chrome_style nor
+ // options_ui.open_in_tab uses the default values: false for open-in-tab,
+ // false for use-chrome-style.
+ extension = LoadAndExpectSuccess("options_ui_page_basic.json");
+ EXPECT_FALSE(OptionsPageInfo::ShouldUseChromeStyle(extension.get()));
+ EXPECT_FALSE(OptionsPageInfo::ShouldOpenInTab(extension.get()));
+
+ // This extension has both options_page and options_ui specified. The
+ // options_ui key should take precedence.
+ extension = LoadAndExpectSuccess("options_ui_page_with_legacy_page.json");
+ EXPECT_FALSE(OptionsPageInfo::ShouldUseChromeStyle(extension.get()));
+ EXPECT_FALSE(OptionsPageInfo::ShouldOpenInTab(extension.get()));
+
+ return testing::AssertionSuccess();
+ }
+
+ // Tests how the options_page manifest key affects the open-in-tab and
+ // chromes-style behaviour.
+ testing::AssertionResult TestOptionsPageChromeStyleAndOpenInTab(
+ bool expect_open_in_tab) {
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess("init_valid_options.json");
+ EXPECT_FALSE(OptionsPageInfo::ShouldUseChromeStyle(extension.get()));
+ if (expect_open_in_tab) {
+ EXPECT_TRUE(OptionsPageInfo::ShouldOpenInTab(extension.get()));
+ } else {
+ EXPECT_FALSE(OptionsPageInfo::ShouldOpenInTab(extension.get()));
+ }
+ return testing::AssertionSuccess();
+ }
+};
+
+TEST_F(OptionsPageManifestTest, OptionsPageInApps) {
+ // Allow options page with absolute URL in hosted apps.
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess("hosted_app_absolute_options.json");
+ EXPECT_EQ("http://example.com/options.html",
+ OptionsPageInfo::GetOptionsPage(extension.get()).spec());
+
+ extension = LoadAndExpectSuccess("platform_app_with_options_page.json");
+ EXPECT_TRUE(!OptionsPageInfo::HasOptionsPage(extension.get()));
+
+ Testcase testcases[] = {
+ // Forbid options page with relative URL in hosted apps.
+ Testcase("hosted_app_relative_options.json",
+ extensions::manifest_errors::kInvalidOptionsPageInHostedApp),
+
+ // Forbid options page with non-(http|https) scheme in hosted app.
+ Testcase("hosted_app_file_options.json",
+ extensions::manifest_errors::kInvalidOptionsPageInHostedApp),
+
+ // Forbid absolute URL for options page in packaged apps.
+ Testcase(
+ "packaged_app_absolute_options.json",
+ extensions::manifest_errors::kInvalidOptionsPageExpectUrlInPackage)};
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
+
+// Tests for the options_ui.page manifest field.
+TEST_F(OptionsPageManifestTest, OptionsUIPage) {
+ FeatureSwitch::ScopedOverride enable_flag(
+ FeatureSwitch::embedded_extension_options(), true);
+
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess("options_ui_page_basic.json");
+ EXPECT_EQ(base::StringPrintf("chrome-extension://%s/options.html",
+ extension->id().c_str()),
+ OptionsPageInfo::GetOptionsPage(extension.get()).spec());
+
+ extension = LoadAndExpectSuccess("options_ui_page_with_legacy_page.json");
+ EXPECT_EQ(base::StringPrintf("chrome-extension://%s/newoptions.html",
+ extension->id().c_str()),
+ OptionsPageInfo::GetOptionsPage(extension.get()).spec());
+
+ Testcase testcases[] = {Testcase("options_ui_page_bad_url.json",
+ "'page': expected page, got null")};
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_WARNING);
+}
+
+// Runs TestOptionsUIChromeStyleAndOpenInTab with and without the
+// embedded-extension-options flag. The results should always be the same.
+TEST_F(OptionsPageManifestTest, OptionsUIChromeStyleAndOpenInTab) {
+ ASSERT_FALSE(FeatureSwitch::embedded_extension_options()->IsEnabled());
+ EXPECT_TRUE(TestOptionsUIChromeStyleAndOpenInTab());
+ {
+ FeatureSwitch::ScopedOverride enable_flag(
+ FeatureSwitch::embedded_extension_options(), true);
+ EXPECT_TRUE(TestOptionsUIChromeStyleAndOpenInTab());
+ }
+}
+
+// Runs TestOptionsPageChromeStyleAndOpenInTab with and without the
+// embedded-extension-options flag. The default value of open-in-tab differs
+// depending on the flag's value.
+TEST_F(OptionsPageManifestTest, OptionsPageChromeStyleAndOpenInTab) {
+ ASSERT_FALSE(FeatureSwitch::embedded_extension_options()->IsEnabled());
+ EXPECT_TRUE(TestOptionsPageChromeStyleAndOpenInTab(true));
+ {
+ FeatureSwitch::ScopedOverride enable_flag(
+ FeatureSwitch::embedded_extension_options(), true);
+ EXPECT_TRUE(TestOptionsPageChromeStyleAndOpenInTab(false));
+ }
+}
+
+} // namespace
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc
new file mode 100644
index 00000000000..f3df018d00f
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/chrome_manifest_url_handlers.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extensions::manifest_errors;
+
+class URLOverridesManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(URLOverridesManifestTest, Override) {
+ Testcase testcases[] = {
+ Testcase("override_newtab_and_history.json", errors::kMultipleOverrides),
+ Testcase("override_invalid_page.json", errors::kInvalidChromeURLOverrides)
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+
+ scoped_refptr<extensions::Extension> extension;
+
+ extension = LoadAndExpectSuccess("override_new_tab.json");
+ EXPECT_EQ(extension->url().spec() + "newtab.html",
+ extensions::URLOverrides::GetChromeURLOverrides(extension.get())
+ .find("newtab")->second.spec());
+
+ extension = LoadAndExpectSuccess("override_history.json");
+ EXPECT_EQ(extension->url().spec() + "history.html",
+ extensions::URLOverrides::GetChromeURLOverrides(extension.get())
+ .find("history")->second.spec());
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
new file mode 100644
index 00000000000..4d1d897167c
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/app_isolation_info.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+class PlatformAppsManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(PlatformAppsManifestTest, PlatformApps) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("init_valid_platform_app.json");
+ EXPECT_TRUE(AppIsolationInfo::HasIsolatedStorage(extension.get()));
+ EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension.get()));
+
+ extension =
+ LoadAndExpectSuccess("init_valid_platform_app_no_manifest_version.json");
+ EXPECT_EQ(2, extension->manifest_version());
+
+ extension = LoadAndExpectSuccess("incognito_valid_platform_app.json");
+ EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension.get()));
+
+ Testcase error_testcases[] = {
+ Testcase("init_invalid_platform_app_2.json",
+ errors::kBackgroundRequiredForPlatformApps),
+ Testcase("init_invalid_platform_app_3.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidManifestVersionOld, "2", "apps")),
+ };
+ RunTestcases(error_testcases, base::size(error_testcases), EXPECT_TYPE_ERROR);
+
+ Testcase warning_testcases[] = {
+ Testcase(
+ "init_invalid_platform_app_1.json",
+ "'app.launch' is only allowed for legacy packaged apps and hosted "
+ "apps, but this is a packaged app."),
+ Testcase("init_invalid_platform_app_4.json",
+ "'background' is only allowed for extensions, legacy packaged "
+ "apps, hosted apps, and login screen extensions, but this is a "
+ "packaged app."),
+ Testcase("init_invalid_platform_app_5.json",
+ "'background' is only allowed for extensions, legacy packaged "
+ "apps, hosted apps, and login screen extensions, but this is a "
+ "packaged app."),
+ Testcase("incognito_invalid_platform_app.json",
+ "'incognito' is only allowed for extensions and legacy packaged "
+ "apps, "
+ "but this is a packaged app."),
+ };
+ RunTestcases(warning_testcases, base::size(warning_testcases),
+ EXPECT_TYPE_WARNING);
+}
+
+TEST_F(PlatformAppsManifestTest, PlatformAppContentSecurityPolicy) {
+ // Normal platform apps can't specify a CSP value.
+ Testcase warning_testcases[] = {
+ Testcase(
+ "init_platform_app_csp_warning_1.json",
+ "'content_security_policy' is only allowed for extensions and legacy "
+ "packaged apps, but this is a packaged app."),
+ Testcase(
+ "init_platform_app_csp_warning_2.json",
+ "'app.content_security_policy' is not allowed for specified extension "
+ "ID.")
+ };
+ RunTestcases(warning_testcases, base::size(warning_testcases),
+ EXPECT_TYPE_WARNING);
+
+ // Allowlisted ones can (this is the ID corresponding to the base 64 encoded
+ // key in the init_platform_app_csp.json manifest.)
+ SimpleFeature::ScopedThreadUnsafeAllowlistForTest allowlist(
+ "ahplfneplbnjcflhdgkkjeiglkkfeelb");
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("init_platform_app_csp.json");
+ EXPECT_EQ(0U, extension->install_warnings().size())
+ << "Unexpected warning " << extension->install_warnings()[0].message;
+ EXPECT_TRUE(extension->is_platform_app());
+ EXPECT_EQ("default-src 'self' https://www.google.com;",
+ CSPInfo::GetResourceContentSecurityPolicy(extension.get(),
+ std::string()));
+
+ // But even allowlisted ones must specify a secure policy.
+ LoadAndExpectWarning(
+ "init_platform_app_csp_insecure.json",
+ ErrorUtils::FormatErrorMessage(errors::kInvalidCSPInsecureValueIgnored,
+ keys::kPlatformAppContentSecurityPolicy,
+ "http://www.google.com", "default-src"));
+}
+
+TEST_F(PlatformAppsManifestTest, CertainApisRequirePlatformApps) {
+ // Put APIs here that should be restricted to platform apps, but that haven't
+ // yet graduated from experimental.
+ const char* const kPlatformAppExperimentalApis[] = {
+ "dns",
+ "serial",
+ };
+ // TODO(miket): When the first platform-app API leaves experimental, write
+ // similar code that tests without the experimental flag.
+
+ // This manifest is a skeleton used to build more specific manifests for
+ // testing. The requirements are that (1) it be a valid platform app, and (2)
+ // it contain no permissions dictionary.
+ std::string error;
+ base::Value manifest = LoadManifest("init_valid_platform_app.json", &error);
+
+ std::vector<std::unique_ptr<ManifestData>> manifests;
+ // Create each manifest.
+ for (const char* api_name : kPlatformAppExperimentalApis) {
+ base::Value permissions(base::Value::Type::LIST);
+ permissions.Append(base::Value("experimental"));
+ permissions.Append(base::Value(api_name));
+ manifest.SetKey("permissions", std::move(permissions));
+ manifests.push_back(
+ std::make_unique<ManifestData>(manifest.CreateDeepCopy(), ""));
+ }
+ // First try to load without any flags. This should warn for every API.
+ for (const std::unique_ptr<ManifestData>& manifest : manifests) {
+ LoadAndExpectWarning(
+ *manifest,
+ "'experimental' requires the 'experimental-extension-apis' "
+ "command line switch to be enabled.");
+ }
+
+ // Now try again with the experimental flag set.
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableExperimentalExtensionApis);
+ for (const std::unique_ptr<ManifestData>& manifest : manifests)
+ LoadAndExpectSuccess(*manifest);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc
new file mode 100644
index 00000000000..874d7e80130
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST_F(ChromeManifestTest, PortsInPermissions) {
+ // Loading as a user would shoud not trigger an error.
+ LoadAndExpectSuccess("ports_in_permissions.json");
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc
new file mode 100644
index 00000000000..bd7e5aa4d1e
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/requirements_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+class RequirementsManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(RequirementsManifestTest, RequirementsInvalid) {
+ Testcase testcases[] = {
+ Testcase("requirements_invalid_requirements.json",
+ errors::kInvalidRequirements),
+ Testcase("requirements_invalid_keys.json", errors::kInvalidRequirements),
+ Testcase("requirements_invalid_3d.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidRequirement, "3D")),
+ Testcase("requirements_invalid_3d_features.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidRequirement, "3D")),
+ Testcase("requirements_invalid_3d_features_value.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidRequirement, "3D")),
+ Testcase("requirements_invalid_3d_no_features.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidRequirement, "3D")),
+ };
+
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(RequirementsManifestTest, RequirementsValid) {
+ // Test the defaults.
+ scoped_refptr<Extension> extension(LoadAndExpectSuccess(
+ "requirements_valid_empty.json"));
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ(RequirementsInfo::GetRequirements(extension.get()).webgl, false);
+
+ // Test loading all the requirements.
+ extension = LoadAndExpectSuccess("requirements_valid_full.json");
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ(RequirementsInfo::GetRequirements(extension.get()).webgl, true);
+}
+
+// Tests the deprecated plugin requirement.
+TEST_F(RequirementsManifestTest, RequirementsPlugin) {
+ // Using the plugins requirement should cause an install warning.
+ RunTestcase({"requirements_invalid_plugins_value.json",
+ errors::kPluginsRequirementDeprecated},
+ EXPECT_TYPE_WARNING);
+ RunTestcase(
+ {"requirements_npapi_false.json", errors::kPluginsRequirementDeprecated},
+ EXPECT_TYPE_WARNING);
+
+ // Explicitly requesting the npapi requirement should cause an error.
+ RunTestcase(
+ {"requirements_npapi_true.json", errors::kNPAPIPluginsNotSupported},
+ EXPECT_TYPE_ERROR);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc
new file mode 100644
index 00000000000..264eb9d8ce4
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class UIManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(UIManifestTest, DisallowMultipleUISurfaces) {
+ LoadAndExpectError("multiple_ui_surfaces.json",
+ manifest_errors::kOneUISurfaceOnly);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc
new file mode 100644
index 00000000000..383b8f41916
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extensions::manifest_errors;
+
+class UpdateURLManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(UpdateURLManifestTest, UpdateUrls) {
+ // Test several valid update urls
+ Testcase testcases[] = {
+ Testcase("update_url_valid_1.json", extensions::Manifest::INTERNAL,
+ Extension::NO_FLAGS),
+ Testcase("update_url_valid_2.json", extensions::Manifest::INTERNAL,
+ Extension::NO_FLAGS),
+ Testcase("update_url_valid_3.json", extensions::Manifest::INTERNAL,
+ Extension::NO_FLAGS),
+ Testcase("update_url_valid_4.json", extensions::Manifest::INTERNAL,
+ Extension::NO_FLAGS)
+ };
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_SUCCESS);
+
+ // Test some invalid update urls
+ Testcase testcases2[] = {
+ Testcase("update_url_invalid_1.json", errors::kInvalidUpdateURL,
+ extensions::Manifest::INTERNAL, Extension::NO_FLAGS),
+ Testcase("update_url_invalid_2.json", errors::kInvalidUpdateURL,
+ extensions::Manifest::INTERNAL, Extension::NO_FLAGS),
+ Testcase("update_url_invalid_3.json", errors::kInvalidUpdateURL,
+ extensions::Manifest::INTERNAL, Extension::NO_FLAGS)
+ };
+ RunTestcases(testcases2, base::size(testcases2), EXPECT_TYPE_ERROR);
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc
new file mode 100644
index 00000000000..6b53be3f325
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "base/values.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef ChromeManifestTest ValidAppManifestTest;
+
+TEST_F(ValidAppManifestTest, ValidApp) {
+ scoped_refptr<extensions::Extension> extension(
+ LoadAndExpectSuccess("valid_app.json"));
+ extensions::URLPatternSet expected_patterns;
+ AddPattern(&expected_patterns, "http://www.google.com/mail/*");
+ AddPattern(&expected_patterns, "http://www.google.com/foobar/*");
+ EXPECT_EQ(expected_patterns, extension->web_extent());
+ EXPECT_EQ(extensions::LaunchContainer::kLaunchContainerTab,
+ extensions::AppLaunchInfo::GetLaunchContainer(extension.get()));
+ EXPECT_EQ(GURL("http://www.google.com/mail/"),
+ extensions::AppLaunchInfo::GetLaunchWebURL(extension.get()));
+}
+
+TEST_F(ValidAppManifestTest, AllowUnrecognizedPermissions) {
+ std::string error;
+ base::Value manifest = LoadManifest("valid_app.json", &error);
+ base::Value* permissions =
+ manifest.FindKeyOfType("permissions", base::Value::Type::LIST);
+ ASSERT_TRUE(permissions);
+ permissions->Append("not-a-valid-permission");
+ LoadAndExpectSuccess(ManifestData(std::move(manifest), ""));
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_accessible_resources_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_accessible_resources_unittest.cc
new file mode 100644
index 00000000000..7dcd8b9b911
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_accessible_resources_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+using extensions::WebAccessibleResourcesInfo;
+
+class WebAccessibleResourcesManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(WebAccessibleResourcesManifestTest, WebAccessibleResources) {
+ // No web_accessible_resources.
+ scoped_refptr<Extension> none(
+ LoadAndExpectSuccess("web_accessible_resources_none.json"));
+ EXPECT_FALSE(
+ WebAccessibleResourcesInfo::HasWebAccessibleResources(none.get()));
+ EXPECT_FALSE(
+ WebAccessibleResourcesInfo::IsResourceWebAccessible(none.get(), "test"));
+
+ // web_accessible_resources: ["test"].
+ scoped_refptr<Extension> single(
+ LoadAndExpectSuccess("web_accessible_resources_single.json"));
+ EXPECT_TRUE(
+ WebAccessibleResourcesInfo::HasWebAccessibleResources(single.get()));
+ EXPECT_TRUE(WebAccessibleResourcesInfo::IsResourceWebAccessible(single.get(),
+ "test"));
+ EXPECT_FALSE(WebAccessibleResourcesInfo::IsResourceWebAccessible(single.get(),
+ "other"));
+
+ // web_accessible_resources: ["*"].
+ scoped_refptr<Extension> wildcard(
+ LoadAndExpectSuccess("web_accessible_resources_wildcard.json"));
+ EXPECT_TRUE(
+ WebAccessibleResourcesInfo::HasWebAccessibleResources(wildcard.get()));
+ EXPECT_TRUE(WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ wildcard.get(), "anything"));
+ EXPECT_TRUE(WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ wildcard.get(), "path/anything"));
+
+ // web_accessible_resources: ["path/*.ext"].
+ scoped_refptr<Extension> pattern(
+ LoadAndExpectSuccess("web_accessible_resources_pattern.json"));
+ EXPECT_TRUE(
+ WebAccessibleResourcesInfo::HasWebAccessibleResources(pattern.get()));
+ EXPECT_TRUE(WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ pattern.get(), "path/anything.ext"));
+ EXPECT_FALSE(WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ pattern.get(), "anything.ext"));
+ EXPECT_FALSE(WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ pattern.get(), "path/anything.badext"));
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
new file mode 100644
index 00000000000..b491b978ec8
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::ErrorUtils;
+using extensions::Extension;
+
+namespace errors = extensions::manifest_errors;
+
+TEST_F(ChromeManifestTest, AppWebUrls) {
+ Testcase testcases[] = {
+ Testcase("web_urls_wrong_type.json", errors::kInvalidWebURLs),
+ Testcase("web_urls_invalid_1.json",
+ ErrorUtils::FormatErrorMessage(errors::kInvalidWebURL,
+ base::NumberToString(0),
+ errors::kExpectString)),
+ Testcase("web_urls_invalid_2.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidWebURL, base::NumberToString(0),
+ URLPattern::GetParseResultString(
+ URLPattern::ParseResult::kMissingSchemeSeparator))),
+ Testcase("web_urls_invalid_3.json",
+ ErrorUtils::FormatErrorMessage(errors::kInvalidWebURL,
+ base::NumberToString(0),
+ errors::kNoWildCardsInPaths)),
+ Testcase("web_urls_invalid_4.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidWebURL, base::NumberToString(0),
+ errors::kCannotClaimAllURLsInExtent)),
+ Testcase("web_urls_invalid_5.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kInvalidWebURL, base::NumberToString(1),
+ errors::kCannotClaimAllHostsInExtent))};
+ RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+
+ LoadAndExpectSuccess("web_urls_has_port.json");
+
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("web_urls_default.json"));
+ ASSERT_EQ(1u, extension->web_extent().patterns().size());
+ EXPECT_EQ("*://www.google.com/*",
+ extension->web_extent().patterns().begin()->GetAsString());
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/extension_manifests_webview_accessible_resources_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_webview_accessible_resources_unittest.cc
new file mode 100644
index 00000000000..8d1826812e2
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/extension_manifests_webview_accessible_resources_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/webview_info.h"
+
+using extensions::ErrorUtils;
+using extensions::Extension;
+using extensions::WebviewInfo;
+namespace errors = extensions::manifest_errors;
+
+class WebviewAccessibleResourcesManifestTest : public ChromeManifestTest {
+};
+
+TEST_F(WebviewAccessibleResourcesManifestTest, WebviewAccessibleResources) {
+ // Manifest version 2 with webview accessible resources specified.
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("webview_accessible_resources_1.json"));
+
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "fail", "a.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "fail", "b.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "fail", "c.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "fail", "d.html"));
+
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foo", "a.html"));
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foo", "b.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foo", "c.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foo", "d.html"));
+
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "bar", "a.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "bar", "b.html"));
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "bar", "c.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "bar", "d.html"));
+
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foobar", "a.html"));
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foobar", "b.html"));
+ EXPECT_TRUE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foobar", "c.html"));
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(extension.get(),
+ "foobar", "d.html"));
+
+ EXPECT_FALSE(WebviewInfo::IsResourceWebviewAccessible(nullptr,
+ "foobar", "a.html"));
+}
+
+TEST_F(WebviewAccessibleResourcesManifestTest, InvalidManifest) {
+ LoadAndExpectError("webview_accessible_resources_invalid1.json",
+ errors::kInvalidWebview);
+ LoadAndExpectError("webview_accessible_resources_invalid2.json",
+ errors::kInvalidWebviewPartitionsList);
+ LoadAndExpectError("webview_accessible_resources_invalid3.json",
+ errors::kInvalidWebviewPartitionsList);
+ LoadAndExpectError(
+ "webview_accessible_resources_invalid4.json",
+ ErrorUtils::FormatErrorMessage(errors::kInvalidWebviewPartition,
+ base::NumberToString(0)));
+ LoadAndExpectError("webview_accessible_resources_invalid5.json",
+ errors::kInvalidWebviewPartitionName);
+ LoadAndExpectError("webview_accessible_resources_invalid6.json",
+ errors::kInvalidWebviewAccessibleResourcesList);
+ LoadAndExpectError("webview_accessible_resources_invalid7.json",
+ errors::kInvalidWebviewAccessibleResourcesList);
+ LoadAndExpectError(
+ "webview_accessible_resources_invalid8.json",
+ ErrorUtils::FormatErrorMessage(errors::kInvalidWebviewAccessibleResource,
+ base::NumberToString(0)));
+}
diff --git a/chromium/chrome/common/extensions/manifest_tests/permissions_parser_unittest.cc b/chromium/chrome/common/extensions/manifest_tests/permissions_parser_unittest.cc
new file mode 100644
index 00000000000..a87649ba8f3
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_tests/permissions_parser_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+// Install warning for tests running Manifest v3. The current highest
+// supported manifest version is 2.
+constexpr char kManifestVersionWarning[] =
+ "The maximum currently-supported manifest version is 2, but this is 3. "
+ "Certain features may not work as expected.";
+} // namespace
+
+using PermissionsParserTest = ChromeManifestTest;
+
+TEST_F(PermissionsParserTest, RemoveOverlappingAPIPermissions) {
+ scoped_refptr<Extension> extension(LoadAndExpectWarning(
+ "permissions_overlapping_api_permissions.json",
+ ErrorUtils::FormatErrorMessage(
+ manifest_errors::kPermissionMarkedOptionalAndRequired, "tabs")));
+
+ std::set<std::string> required_api_names =
+ PermissionsParser::GetRequiredPermissions(extension.get())
+ .GetAPIsAsStrings();
+
+ std::set<std::string> optional_api_names =
+ PermissionsParser::GetOptionalPermissions(extension.get())
+ .GetAPIsAsStrings();
+
+ // Check that required api permissions have not changed while "tabs" is
+ // removed from optional permissions as it is specified as required.
+ EXPECT_THAT(required_api_names,
+ testing::UnorderedElementsAre("tabs", "storage"));
+ EXPECT_THAT(optional_api_names, testing::UnorderedElementsAre("geolocation"));
+}
+
+TEST_F(PermissionsParserTest, RemoveOverlappingHostPermissions) {
+ scoped_refptr<Extension> extension(LoadAndExpectWarning(
+ "permissions_overlapping_host_permissions.json",
+ ErrorUtils::FormatErrorMessage(
+ manifest_errors::kPermissionMarkedOptionalAndRequired,
+ "https://google.com/*")));
+
+ const URLPatternSet& required_hosts =
+ PermissionsParser::GetRequiredPermissions(extension.get())
+ .explicit_hosts();
+
+ const URLPatternSet& optional_hosts =
+ PermissionsParser::GetOptionalPermissions(extension.get())
+ .explicit_hosts();
+
+ // Check that required hosts have not changed at all while
+ // "https://google.com/maps" is removed from optional hosts as it is a strict
+ // subset of hosts specified as required.
+ EXPECT_THAT(*required_hosts.ToStringVector(),
+ testing::UnorderedElementsAre("https://example.com/*",
+ "https://*.google.com/*"));
+ EXPECT_THAT(*optional_hosts.ToStringVector(),
+ testing::UnorderedElementsAre("*://chromium.org/*"));
+}
+
+TEST_F(PermissionsParserTest, RequiredHostPermissionsAllURLs) {
+ scoped_refptr<Extension> extension(LoadAndExpectWarning(
+ "permissions_all_urls_host_permissions.json",
+ ErrorUtils::FormatErrorMessage(
+ manifest_errors::kPermissionMarkedOptionalAndRequired,
+ "http://*/*")));
+
+ const URLPatternSet& optional_hosts =
+ PermissionsParser::GetOptionalPermissions(extension.get())
+ .explicit_hosts();
+
+ // Since we specified <all_urls> as a required host permission,
+ // there should be no optional host permissions.
+ EXPECT_THAT(*optional_hosts.ToStringVector(), testing::IsEmpty());
+}
+
+TEST_F(PermissionsParserTest, OptionalHostPermissionsAllURLs) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("permissions_optional_all_urls_permissions.json"));
+
+ const URLPatternSet& required_hosts =
+ PermissionsParser::GetRequiredPermissions(extension.get())
+ .explicit_hosts();
+
+ const URLPatternSet& optional_hosts =
+ PermissionsParser::GetOptionalPermissions(extension.get())
+ .explicit_hosts();
+
+ // This time, required permissions are a subset of optional permissions
+ // so we make sure that permissions remain the same
+ // as what's specified in the manifest.
+ EXPECT_THAT(*required_hosts.ToStringVector(),
+ testing::UnorderedElementsAre("https://*.google.com/*"));
+
+ EXPECT_THAT(*optional_hosts.ToStringVector(),
+ testing::UnorderedElementsAre("*://*/*"));
+}
+
+TEST_F(PermissionsParserTest, HostPermissionsKey) {
+ std::vector<std::string> expected_warnings;
+ expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
+ manifest_errors::kPermissionUnknownOrMalformed, "https://google.com/*"));
+
+ expected_warnings.push_back(kManifestVersionWarning);
+
+ scoped_refptr<Extension> extension(
+ LoadAndExpectWarnings("host_permissions_key.json", expected_warnings));
+
+ // Expect that the host specified in |host_permissions| is parsed.
+ const URLPatternSet& required_hosts =
+ PermissionsParser::GetRequiredPermissions(extension.get())
+ .explicit_hosts();
+
+ EXPECT_THAT(*required_hosts.ToStringVector(),
+ testing::UnorderedElementsAre("https://example.com/*"));
+}
+
+TEST_F(PermissionsParserTest, HostPermissionsKeyInvalidHosts) {
+ std::vector<std::string> expected_warnings;
+ expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
+ manifest_errors::kPermissionUnknownOrMalformed, "malformed_host"));
+
+ expected_warnings.push_back(kManifestVersionWarning);
+
+ scoped_refptr<Extension> extension(LoadAndExpectWarnings(
+ "host_permissions_key_invalid_hosts.json", expected_warnings));
+}
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/manifest_unittest.cc b/chromium/chrome/common/extensions/manifest_unittest.cc
new file mode 100644
index 00000000000..ea51906eb54
--- /dev/null
+++ b/chromium/chrome/common/extensions/manifest_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest.h"
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+// Not named "ManifestTest" because a test utility class has that name.
+class ManifestUnitTest : public testing::Test {
+ public:
+ ManifestUnitTest() : default_value_("test") {}
+
+ protected:
+ void AssertType(Manifest* manifest, Manifest::Type type) {
+ EXPECT_EQ(type, manifest->type());
+ EXPECT_EQ(type == Manifest::TYPE_THEME, manifest->is_theme());
+ EXPECT_EQ(type == Manifest::TYPE_PLATFORM_APP,
+ manifest->is_platform_app());
+ EXPECT_EQ(type == Manifest::TYPE_LEGACY_PACKAGED_APP,
+ manifest->is_legacy_packaged_app());
+ EXPECT_EQ(type == Manifest::TYPE_HOSTED_APP, manifest->is_hosted_app());
+ EXPECT_EQ(type == Manifest::TYPE_SHARED_MODULE,
+ manifest->is_shared_module());
+ EXPECT_EQ(type == Manifest::TYPE_LOGIN_SCREEN_EXTENSION,
+ manifest->is_login_screen_extension());
+ }
+
+ // Helper function that replaces the Manifest held by |manifest| with a copy
+ // with its |key| changed to |value|. If |value| is nullptr, then |key| will
+ // instead be deleted.
+ void MutateManifest(std::unique_ptr<Manifest>* manifest,
+ const std::string& key,
+ std::unique_ptr<base::Value> value) {
+ auto manifest_value = manifest->get()->value()->CreateDeepCopy();
+ if (value)
+ manifest_value->Set(key, std::move(value));
+ else
+ manifest_value->Remove(key, nullptr);
+ manifest->reset(
+ new Manifest(Manifest::INTERNAL, std::move(manifest_value)));
+ }
+
+ // Helper function that replaces the manifest held by |manifest| with a copy
+ // and uses the |for_login_screen| during creation to determine its type.
+ void MutateManifestForLoginScreen(std::unique_ptr<Manifest>* manifest,
+ bool for_login_screen) {
+ auto manifest_value = manifest->get()->value()->CreateDeepCopy();
+ if (for_login_screen) {
+ *manifest = Manifest::CreateManifestForLoginScreen(
+ Manifest::EXTERNAL_POLICY, std::move(manifest_value));
+ } else {
+ *manifest = std::make_unique<Manifest>(Manifest::INTERNAL,
+ std::move(manifest_value));
+ }
+ }
+
+ std::string default_value_;
+};
+
+// Verifies that extensions can access the correct keys.
+TEST_F(ManifestUnitTest, Extension) {
+ std::unique_ptr<base::DictionaryValue> manifest_value(
+ new base::DictionaryValue());
+ manifest_value->SetString(keys::kName, "extension");
+ manifest_value->SetString(keys::kVersion, "1");
+ manifest_value->SetInteger(keys::kManifestVersion, 2);
+ manifest_value->SetString(keys::kBackgroundPage, "bg.html");
+ manifest_value->SetString("unknown_key", "foo");
+
+ std::unique_ptr<Manifest> manifest(
+ new Manifest(Manifest::INTERNAL, std::move(manifest_value)));
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ EXPECT_TRUE(manifest->ValidateManifest(&error, &warnings));
+ EXPECT_TRUE(error.empty());
+ ASSERT_EQ(1u, warnings.size());
+ AssertType(manifest.get(), Manifest::TYPE_EXTENSION);
+
+ // The known key 'background.page' should be accessible.
+ std::string value;
+ EXPECT_TRUE(manifest->GetString(keys::kBackgroundPage, &value));
+ EXPECT_EQ("bg.html", value);
+
+ // The unknown key 'unknown_key' should be accesible.
+ value.clear();
+ EXPECT_TRUE(manifest->GetString("unknown_key", &value));
+ EXPECT_EQ("foo", value);
+
+ // Test CreateDeepCopy and Equals.
+ std::unique_ptr<Manifest> manifest2 = manifest->CreateDeepCopy();
+ EXPECT_TRUE(manifest->Equals(manifest2.get()));
+ EXPECT_TRUE(manifest2->Equals(manifest.get()));
+ MutateManifest(&manifest, "foo", std::make_unique<base::Value>("blah"));
+ EXPECT_FALSE(manifest->Equals(manifest2.get()));
+}
+
+// Verifies that key restriction based on type works.
+TEST_F(ManifestUnitTest, ExtensionTypes) {
+ std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetString(keys::kName, "extension");
+ value->SetString(keys::kVersion, "1");
+
+ std::unique_ptr<Manifest> manifest(
+ new Manifest(Manifest::INTERNAL, std::move(value)));
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ EXPECT_TRUE(manifest->ValidateManifest(&error, &warnings));
+ EXPECT_TRUE(error.empty());
+ EXPECT_TRUE(warnings.empty());
+
+ // By default, the type is Extension.
+ AssertType(manifest.get(), Manifest::TYPE_EXTENSION);
+
+ // Login screen extension
+ MutateManifestForLoginScreen(&manifest, true);
+ AssertType(manifest.get(), Manifest::TYPE_LOGIN_SCREEN_EXTENSION);
+ MutateManifestForLoginScreen(&manifest, false);
+
+ // Theme.
+ MutateManifest(&manifest, keys::kTheme,
+ std::make_unique<base::DictionaryValue>());
+ AssertType(manifest.get(), Manifest::TYPE_THEME);
+ MutateManifest(&manifest, keys::kTheme, nullptr);
+
+ // Shared module.
+ MutateManifest(&manifest, keys::kExport,
+ std::make_unique<base::DictionaryValue>());
+ AssertType(manifest.get(), Manifest::TYPE_SHARED_MODULE);
+ MutateManifest(&manifest, keys::kExport, nullptr);
+
+ // Packaged app.
+ MutateManifest(&manifest, keys::kApp,
+ std::make_unique<base::DictionaryValue>());
+ AssertType(manifest.get(), Manifest::TYPE_LEGACY_PACKAGED_APP);
+
+ // Packaged app for login screen remains a packaged app.
+ MutateManifestForLoginScreen(&manifest, true);
+ AssertType(manifest.get(), Manifest::TYPE_LEGACY_PACKAGED_APP);
+ MutateManifestForLoginScreen(&manifest, false);
+
+ // Platform app with event page.
+ MutateManifest(&manifest, keys::kPlatformAppBackground,
+ std::make_unique<base::DictionaryValue>());
+ AssertType(manifest.get(), Manifest::TYPE_PLATFORM_APP);
+ MutateManifest(&manifest, keys::kPlatformAppBackground, nullptr);
+
+ // Hosted app.
+ MutateManifest(&manifest, keys::kWebURLs,
+ std::make_unique<base::ListValue>());
+ AssertType(manifest.get(), Manifest::TYPE_HOSTED_APP);
+ MutateManifest(&manifest, keys::kWebURLs, nullptr);
+ MutateManifest(&manifest, keys::kLaunchWebURL,
+ std::make_unique<base::Value>("foo"));
+ AssertType(manifest.get(), Manifest::TYPE_HOSTED_APP);
+ MutateManifest(&manifest, keys::kLaunchWebURL, nullptr);
+}
+
+// Verifies that the getters filter restricted keys.
+TEST_F(ManifestUnitTest, RestrictedKeys) {
+ std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetString(keys::kName, "extension");
+ value->SetString(keys::kVersion, "1");
+
+ std::unique_ptr<Manifest> manifest(
+ new Manifest(Manifest::INTERNAL, std::move(value)));
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ EXPECT_TRUE(manifest->ValidateManifest(&error, &warnings));
+ EXPECT_TRUE(error.empty());
+ EXPECT_TRUE(warnings.empty());
+
+ // "Commands" requires manifest version 2.
+ const base::Value* output = nullptr;
+ MutateManifest(&manifest, keys::kCommands,
+ std::make_unique<base::DictionaryValue>());
+ EXPECT_FALSE(manifest->HasKey(keys::kCommands));
+ EXPECT_FALSE(manifest->Get(keys::kCommands, &output));
+
+ MutateManifest(&manifest, keys::kManifestVersion,
+ std::make_unique<base::Value>(2));
+ EXPECT_TRUE(manifest->HasKey(keys::kCommands));
+ EXPECT_TRUE(manifest->Get(keys::kCommands, &output));
+
+ MutateManifest(&manifest, keys::kPageAction,
+ std::make_unique<base::DictionaryValue>());
+ AssertType(manifest.get(), Manifest::TYPE_EXTENSION);
+ EXPECT_TRUE(manifest->HasKey(keys::kPageAction));
+ EXPECT_TRUE(manifest->Get(keys::kPageAction, &output));
+
+ // Platform apps cannot have a "page_action" key.
+ MutateManifest(&manifest, keys::kPlatformAppBackground,
+ std::make_unique<base::DictionaryValue>());
+ AssertType(manifest.get(), Manifest::TYPE_PLATFORM_APP);
+ EXPECT_FALSE(manifest->HasKey(keys::kPageAction));
+ EXPECT_FALSE(manifest->Get(keys::kPageAction, &output));
+ MutateManifest(&manifest, keys::kPlatformAppBackground, nullptr);
+
+ // Platform apps also can't have a "Commands" key.
+ EXPECT_FALSE(manifest->HasKey(keys::kCommands));
+ EXPECT_FALSE(manifest->Get(keys::kCommands, &output));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/sync_helper.cc b/chromium/chrome/common/extensions/sync_helper.cc
new file mode 100644
index 00000000000..05a8df68c47
--- /dev/null
+++ b/chromium/chrome/common/extensions/sync_helper.cc
@@ -0,0 +1,79 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/sync_helper.h"
+
+#include "base/logging.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_url_handlers.h"
+
+namespace extensions {
+namespace sync_helper {
+
+bool IsSyncable(const Extension* extension) {
+ const Feature* feature =
+ FeatureProvider::GetBehaviorFeature(behavior_feature::kDoNotSync);
+ if (feature && feature->IsAvailableToExtension(extension).is_available())
+ return false;
+
+ // Default apps are not synced because otherwise they will pollute profiles
+ // that don't already have them. Specially, if a user doesn't have default
+ // apps, creates a new profile (which get default apps) and then enables sync
+ // for it, then their profile everywhere gets the default apps.
+ bool is_syncable = (extension->location() == Manifest::INTERNAL &&
+ !extension->was_installed_by_default());
+ if (!is_syncable && !IsSyncableComponentExtension(extension)) {
+ // We have a non-standard location.
+ return false;
+ }
+
+ // Disallow extensions with non-gallery auto-update URLs for now.
+ //
+ // TODO(akalin): Relax this restriction once we've put in UI to
+ // approve synced extensions.
+ if (!ManifestURL::GetUpdateURL(extension).is_empty() &&
+ !ManifestURL::UpdatesFromGallery(extension)) {
+ return false;
+ }
+
+ switch (extension->GetType()) {
+ case Manifest::TYPE_EXTENSION:
+ case Manifest::TYPE_HOSTED_APP:
+ case Manifest::TYPE_LEGACY_PACKAGED_APP:
+ case Manifest::TYPE_PLATFORM_APP:
+ case Manifest::TYPE_THEME:
+ return true;
+
+ case Manifest::TYPE_USER_SCRIPT:
+ // We only want to sync user scripts with gallery update URLs.
+ if (ManifestURL::UpdatesFromGallery(extension))
+ return true;
+ return false;
+
+ case Manifest::TYPE_UNKNOWN:
+ case Manifest::TYPE_SHARED_MODULE:
+ case Manifest::TYPE_LOGIN_SCREEN_EXTENSION:
+ return false;
+
+ case Manifest::NUM_LOAD_TYPES:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool IsSyncableComponentExtension(const Extension* extension) {
+ if (!Manifest::IsComponentLocation(extension->location()))
+ return false;
+ return (extension->id() == extensions::kWebStoreAppId) ||
+ (extension->id() == extension_misc::kChromeAppId);
+}
+
+} // namespace sync_helper
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/sync_helper.h b/chromium/chrome/common/extensions/sync_helper.h
new file mode 100644
index 00000000000..c3c3741a3b8
--- /dev/null
+++ b/chromium/chrome/common/extensions/sync_helper.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_SYNC_HELPER_H_
+#define CHROME_COMMON_EXTENSIONS_SYNC_HELPER_H_
+
+namespace extensions {
+
+class Extension;
+
+namespace sync_helper {
+
+// NOTE: The check in the functions here only considers the data in extension
+// itself, not the environment it is in. To determine whether an extension
+// should be synced, you probably want to use util::ShouldSync.
+
+// Returns true if |extension| should be synced.
+bool IsSyncable(const Extension* extension);
+
+// Component extensions usually aren't synced, but some are so that they'll
+// retain their position in the app list. Returns true for component extensions
+// on that whitelist.
+bool IsSyncableComponentExtension(const Extension* extension);
+
+} // namespace sync_helper
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_SYNC_HELPER_H_
diff --git a/chromium/chrome/common/extensions/sync_type_unittest.cc b/chromium/chrome/common/extensions/sync_type_unittest.cc
new file mode 100644
index 00000000000..2150a485ca1
--- /dev/null
+++ b/chromium/chrome/common/extensions/sync_type_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/values.h"
+#include "chrome/common/extensions/sync_helper.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+class ExtensionSyncTypeTest : public testing::Test {
+ protected:
+ enum SyncTestExtensionType {
+ EXTENSION,
+ APP,
+ USER_SCRIPT,
+ THEME
+ };
+
+ static scoped_refptr<Extension> MakeSyncTestExtension(
+ SyncTestExtensionType type,
+ const GURL& update_url,
+ const GURL& launch_url,
+ Manifest::Location location,
+ const base::FilePath& extension_path,
+ int creation_flags) {
+ base::DictionaryValue source;
+ source.SetString(keys::kName, "PossiblySyncableExtension");
+ source.SetString(keys::kVersion, "0.0.0.0");
+ source.SetInteger(keys::kManifestVersion, 2);
+ if (type == APP)
+ source.SetString(keys::kApp, "true");
+ if (type == THEME)
+ source.Set(keys::kTheme, std::make_unique<base::DictionaryValue>());
+ if (!update_url.is_empty()) {
+ source.SetString(keys::kUpdateURL, update_url.spec());
+ }
+ if (!launch_url.is_empty()) {
+ source.SetString(keys::kLaunchWebURL, launch_url.spec());
+ }
+ if (type != THEME)
+ source.SetBoolean(keys::kConvertedFromUserScript, type == USER_SCRIPT);
+
+ std::string error;
+ scoped_refptr<Extension> extension = Extension::Create(
+ extension_path, location, source, creation_flags, &error);
+ EXPECT_TRUE(extension.get());
+ EXPECT_TRUE(error.empty());
+ return extension;
+ }
+
+ static const char kValidUpdateUrl1[];
+ static const char kValidUpdateUrl2[];
+};
+
+const char ExtensionSyncTypeTest::kValidUpdateUrl1[] =
+ "http://clients2.google.com/service/update2/crx";
+const char ExtensionSyncTypeTest::kValidUpdateUrl2[] =
+ "https://clients2.google.com/service/update2/crx";
+
+TEST_F(ExtensionSyncTypeTest, NormalExtensionNoUpdateUrl) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_extension());
+ EXPECT_TRUE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, UserScriptValidUpdateUrl) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(USER_SCRIPT, GURL(kValidUpdateUrl1), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_extension());
+ EXPECT_TRUE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, UserScriptNoUpdateUrl) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(USER_SCRIPT, GURL(), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_extension());
+ EXPECT_FALSE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, ThemeNoUpdateUrl) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(THEME, GURL(), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_theme());
+ EXPECT_TRUE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, AppWithLaunchUrl) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL("http://www.google.com"),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_app());
+ EXPECT_TRUE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, ExtensionExternal) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+ Manifest::EXTERNAL_PREF, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_extension());
+ EXPECT_FALSE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, UserScriptThirdPartyUpdateUrl) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(
+ USER_SCRIPT, GURL("http://third-party.update_url.com"), GURL(),
+ Manifest::INTERNAL, base::FilePath(), Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_extension());
+ EXPECT_FALSE(sync_helper::IsSyncable(extension.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, OnlyDisplayAppsInLauncher) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+
+ EXPECT_FALSE(extension->ShouldDisplayInAppLauncher());
+ EXPECT_FALSE(extension->ShouldDisplayInNewTabPage());
+
+ scoped_refptr<Extension> app(
+ MakeSyncTestExtension(APP, GURL(), GURL("http://www.google.com"),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(app->ShouldDisplayInAppLauncher());
+ EXPECT_TRUE(app->ShouldDisplayInNewTabPage());
+}
+
+TEST_F(ExtensionSyncTypeTest, DisplayInXManifestProperties) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "TestComponentApp");
+ manifest.SetString(keys::kVersion, "0.0.0.0");
+ manifest.SetString(keys::kApp, "true");
+ manifest.SetString(keys::kPlatformAppBackgroundPage, std::string());
+
+ std::string error;
+ scoped_refptr<Extension> app;
+
+ // Default to true.
+ app = Extension::Create(
+ base::FilePath(), Manifest::COMPONENT, manifest, 0, &error);
+ EXPECT_EQ(error, std::string());
+ EXPECT_TRUE(app->ShouldDisplayInAppLauncher());
+ EXPECT_TRUE(app->ShouldDisplayInNewTabPage());
+
+ // Value display_in_NTP defaults to display_in_launcher.
+ manifest.SetBoolean(keys::kDisplayInLauncher, false);
+ app = Extension::Create(
+ base::FilePath(), Manifest::COMPONENT, manifest, 0, &error);
+ EXPECT_EQ(error, std::string());
+ EXPECT_FALSE(app->ShouldDisplayInAppLauncher());
+ EXPECT_FALSE(app->ShouldDisplayInNewTabPage());
+
+ // Value display_in_NTP = true overriding display_in_launcher = false.
+ manifest.SetBoolean(keys::kDisplayInNewTabPage, true);
+ app = Extension::Create(
+ base::FilePath(), Manifest::COMPONENT, manifest, 0, &error);
+ EXPECT_EQ(error, std::string());
+ EXPECT_FALSE(app->ShouldDisplayInAppLauncher());
+ EXPECT_TRUE(app->ShouldDisplayInNewTabPage());
+
+ // Value display_in_NTP = false only, overrides default = true.
+ manifest.Remove(keys::kDisplayInLauncher, NULL);
+ manifest.SetBoolean(keys::kDisplayInNewTabPage, false);
+ app = Extension::Create(
+ base::FilePath(), Manifest::COMPONENT, manifest, 0, &error);
+ EXPECT_EQ(error, std::string());
+ EXPECT_TRUE(app->ShouldDisplayInAppLauncher());
+ EXPECT_FALSE(app->ShouldDisplayInNewTabPage());
+
+ // Error checking.
+ manifest.SetString(keys::kDisplayInNewTabPage, "invalid");
+ app = Extension::Create(
+ base::FilePath(), Manifest::COMPONENT, manifest, 0, &error);
+ EXPECT_EQ(error, std::string(errors::kInvalidDisplayInNewTabPage));
+}
+
+TEST_F(ExtensionSyncTypeTest, OnlySyncInternal) {
+ scoped_refptr<Extension> extension_internal(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_TRUE(sync_helper::IsSyncable(extension_internal.get()));
+
+ scoped_refptr<Extension> extension_noninternal(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+ Manifest::COMPONENT, base::FilePath(),
+ Extension::NO_FLAGS));
+ EXPECT_FALSE(sync_helper::IsSyncable(extension_noninternal.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, DontSyncDefault) {
+ scoped_refptr<Extension> extension_default(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+ Manifest::INTERNAL, base::FilePath(),
+ Extension::WAS_INSTALLED_BY_DEFAULT));
+ EXPECT_FALSE(sync_helper::IsSyncable(extension_default.get()));
+}
+
+TEST_F(ExtensionSyncTypeTest, DontSyncExtensionInDoNotSyncList) {
+ scoped_refptr<Extension> extension(
+ MakeSyncTestExtension(EXTENSION, GURL(), GURL(), Manifest::INTERNAL,
+ base::FilePath(), Extension::NO_FLAGS));
+ EXPECT_TRUE(extension->is_extension());
+ EXPECT_TRUE(sync_helper::IsSyncable(extension.get()));
+ SimpleFeature::ScopedThreadUnsafeAllowlistForTest allowlist(extension->id());
+ EXPECT_FALSE(sync_helper::IsSyncable(extension.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/webstore_install_result.cc b/chromium/chrome/common/extensions/webstore_install_result.cc
new file mode 100644
index 00000000000..25194190ea7
--- /dev/null
+++ b/chromium/chrome/common/extensions/webstore_install_result.cc
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/webstore_install_result.h"
+
+namespace extensions {
+namespace webstore_install {
+
+const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
+const char kWebstoreRequestError[] =
+ "Could not fetch data from the Chrome Web Store";
+const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
+const char kInvalidManifestError[] = "Invalid manifest";
+const char kUserCancelledError[] = "User cancelled install";
+const char kExtensionIsBlacklisted[] = "Extension is blacklisted";
+const char kInstallInProgressError[] = "An install is already in progress";
+
+} // namespace webstore_install
+} // namespace extensions
diff --git a/chromium/chrome/common/extensions/webstore_install_result.h b/chromium/chrome/common/extensions/webstore_install_result.h
new file mode 100644
index 00000000000..74d81848567
--- /dev/null
+++ b/chromium/chrome/common/extensions/webstore_install_result.h
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_WEBSTORE_INSTALL_RESULT_H_
+#define CHROME_COMMON_EXTENSIONS_WEBSTORE_INSTALL_RESULT_H_
+
+namespace extensions {
+
+namespace webstore_install {
+
+extern const char kInvalidWebstoreItemId[];
+extern const char kWebstoreRequestError[];
+extern const char kInvalidWebstoreResponseError[];
+extern const char kInvalidManifestError[];
+extern const char kUserCancelledError[];
+extern const char kExtensionIsBlacklisted[];
+extern const char kInstallInProgressError[];
+
+// Result codes returned by WebstoreStandaloneInstaller and its subclasses.
+enum Result {
+ // Successful operation.
+ SUCCESS,
+
+ // Unknown error.
+ OTHER_ERROR,
+
+ // The operation was aborted as the requestor is no longer alive.
+ ABORTED,
+
+ // An installation of the same extension is in progress.
+ INSTALL_IN_PROGRESS,
+
+ // The installation is not permitted.
+ NOT_PERMITTED,
+
+ // Invalid Chrome Web Store item ID.
+ INVALID_ID,
+
+ // Failed to retrieve extension metadata from the Web Store.
+ WEBSTORE_REQUEST_ERROR,
+
+ // The extension metadata retrieved from the Web Store was invalid.
+ INVALID_WEBSTORE_RESPONSE,
+
+ // An error occurred while parsing the extension manifest retrieved from the
+ // Web Store.
+ INVALID_MANIFEST,
+
+ // Failed to retrieve the extension's icon from the Web Store, or the icon
+ // was invalid.
+ ICON_ERROR,
+
+ // The user cancelled the operation.
+ USER_CANCELLED,
+
+ // The extension is blacklisted.
+ BLACKLISTED,
+
+ // Unsatisfied dependencies, such as shared modules.
+ MISSING_DEPENDENCIES,
+
+ // Unsatisfied requirements, such as webgl.
+ REQUIREMENT_VIOLATIONS,
+
+ // The extension is blocked by management policies.
+ BLOCKED_BY_POLICY,
+
+ // The launch feature is not available.
+ LAUNCH_FEATURE_DISABLED,
+
+ // The launch feature is not supported for the extension type.
+ LAUNCH_UNSUPPORTED_EXTENSION_TYPE,
+
+ // A launch of the same extension is in progress.
+ LAUNCH_IN_PROGRESS,
+
+ // The final (and unused) result type for enum verification.
+ // New results should go above this entry, and this entry should be updated.
+ RESULT_LAST = LAUNCH_IN_PROGRESS,
+};
+
+} // namespace webstore_install
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_WEBSTORE_INSTALL_RESULT_H_
diff --git a/chromium/chrome/common/extra_defines.vsprops b/chromium/chrome/common/extra_defines.vsprops
new file mode 100644
index 00000000000..6db717f1ea1
--- /dev/null
+++ b/chromium/chrome/common/extra_defines.vsprops
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="extra"
+ >
+</VisualStudioPropertySheet>
diff --git a/chromium/chrome/common/google_url_loader_throttle.cc b/chromium/chrome/common/google_url_loader_throttle.cc
new file mode 100644
index 00000000000..0a565575f36
--- /dev/null
+++ b/chromium/chrome/common/google_url_loader_throttle.cc
@@ -0,0 +1,114 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/google_url_loader_throttle.h"
+
+#include "chrome/common/net/safe_search_util.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/extension_urls.h"
+#endif
+
+GoogleURLLoaderThrottle::GoogleURLLoaderThrottle(
+ bool is_off_the_record,
+ chrome::mojom::DynamicParams dynamic_params)
+ : is_off_the_record_(is_off_the_record),
+ dynamic_params_(std::move(dynamic_params)) {}
+
+GoogleURLLoaderThrottle::~GoogleURLLoaderThrottle() {}
+
+void GoogleURLLoaderThrottle::DetachFromCurrentSequence() {}
+
+void GoogleURLLoaderThrottle::WillStartRequest(
+ network::ResourceRequest* request,
+ bool* defer) {
+ variations::AppendVariationsHeaderWithCustomValue(
+ request->url,
+ is_off_the_record_ ? variations::InIncognito::kYes
+ : variations::InIncognito::kNo,
+ dynamic_params_.variation_ids_header, request);
+
+ if (dynamic_params_.force_safe_search) {
+ GURL new_url;
+ safe_search_util::ForceGoogleSafeSearch(request->url, &new_url);
+ if (!new_url.is_empty())
+ request->url = new_url;
+ }
+
+ static_assert(safe_search_util::YOUTUBE_RESTRICT_OFF == 0,
+ "OFF must be first");
+ if (dynamic_params_.youtube_restrict >
+ safe_search_util::YOUTUBE_RESTRICT_OFF &&
+ dynamic_params_.youtube_restrict <
+ safe_search_util::YOUTUBE_RESTRICT_COUNT) {
+ safe_search_util::ForceYouTubeRestrict(
+ request->url, &request->headers,
+ static_cast<safe_search_util::YouTubeRestrictMode>(
+ dynamic_params_.youtube_restrict));
+ }
+
+ if (!dynamic_params_.allowed_domains_for_apps.empty() &&
+ request->url.DomainIs("google.com")) {
+ request->headers.SetHeader(safe_search_util::kGoogleAppsAllowedDomains,
+ dynamic_params_.allowed_domains_for_apps);
+ }
+}
+
+void GoogleURLLoaderThrottle::WillRedirectRequest(
+ net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* /* defer */,
+ std::vector<std::string>* to_be_removed_headers,
+ net::HttpRequestHeaders* modified_headers) {
+ network::mojom::URLResponseHeadPtr response_head_ptr = response_head;
+ variations::RemoveVariationsHeaderIfNeeded(*redirect_info, *response_head_ptr,
+ to_be_removed_headers);
+
+ // URLLoaderThrottles can only change the redirect URL when the network
+ // service is enabled. The non-network service path handles this in
+ // ChromeNetworkDelegate.
+ if (dynamic_params_.force_safe_search) {
+ safe_search_util::ForceGoogleSafeSearch(redirect_info->new_url,
+ &redirect_info->new_url);
+ }
+
+ if (dynamic_params_.youtube_restrict >
+ safe_search_util::YOUTUBE_RESTRICT_OFF &&
+ dynamic_params_.youtube_restrict <
+ safe_search_util::YOUTUBE_RESTRICT_COUNT) {
+ safe_search_util::ForceYouTubeRestrict(
+ redirect_info->new_url, modified_headers,
+ static_cast<safe_search_util::YouTubeRestrictMode>(
+ dynamic_params_.youtube_restrict));
+ }
+
+ if (!dynamic_params_.allowed_domains_for_apps.empty() &&
+ redirect_info->new_url.DomainIs("google.com")) {
+ modified_headers->SetHeader(safe_search_util::kGoogleAppsAllowedDomains,
+ dynamic_params_.allowed_domains_for_apps);
+ }
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+void GoogleURLLoaderThrottle::WillProcessResponse(
+ const GURL& response_url,
+ network::ResourceResponseHead* response_head,
+ bool* defer) {
+ // Built-in additional protection for the chrome web store origin.
+ GURL webstore_url(extension_urls::GetWebstoreLaunchURL());
+ if (response_url.SchemeIsHTTPOrHTTPS() &&
+ response_url.DomainIs(webstore_url.host_piece())) {
+ if (response_head && response_head->headers &&
+ !response_head->headers->HasHeaderValue("x-frame-options", "deny") &&
+ !response_head->headers->HasHeaderValue("x-frame-options",
+ "sameorigin")) {
+ response_head->headers->RemoveHeader("x-frame-options");
+ response_head->headers->AddHeader("x-frame-options: sameorigin");
+ }
+ }
+}
+#endif
diff --git a/chromium/chrome/common/google_url_loader_throttle.h b/chromium/chrome/common/google_url_loader_throttle.h
new file mode 100644
index 00000000000..2bc4b573b71
--- /dev/null
+++ b/chromium/chrome/common/google_url_loader_throttle.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_GOOGLE_URL_LOADER_THROTTLE_H_
+#define CHROME_COMMON_GOOGLE_URL_LOADER_THROTTLE_H_
+
+#include "chrome/common/renderer_configuration.mojom.h"
+#include "extensions/buildflags/buildflags.h"
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+
+// This class changes requests for Google-specific features (e.g. adding &
+// removing Varitaions headers, Safe Search & Restricted YouTube & restricting
+// consumer accounts through group policy.
+class GoogleURLLoaderThrottle
+ : public blink::URLLoaderThrottle,
+ public base::SupportsWeakPtr<GoogleURLLoaderThrottle> {
+ public:
+ GoogleURLLoaderThrottle(bool is_off_the_record,
+ chrome::mojom::DynamicParams dynamic_params);
+ ~GoogleURLLoaderThrottle() override;
+
+ private:
+ // blink::URLLoaderThrottle:
+ void DetachFromCurrentSequence() override;
+ void WillStartRequest(network::ResourceRequest* request,
+ bool* defer) override;
+ void WillRedirectRequest(net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* defer,
+ std::vector<std::string>* to_be_removed_headers,
+ net::HttpRequestHeaders* modified_headers) override;
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ void WillProcessResponse(const GURL& response_url,
+ network::ResourceResponseHead* response_head,
+ bool* defer) override;
+#endif
+
+ bool is_off_the_record_;
+ const chrome::mojom::DynamicParams dynamic_params_;
+};
+
+#endif // CHROME_COMMON_GOOGLE_URL_LOADER_THROTTLE_H_
diff --git a/chromium/chrome/common/heap_profiler_controller.cc b/chromium/chrome/common/heap_profiler_controller.cc
new file mode 100644
index 00000000000..bfa3b11f72f
--- /dev/null
+++ b/chromium/chrome/common/heap_profiler_controller.cc
@@ -0,0 +1,114 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/heap_profiler_controller.h"
+
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/process/process_metrics.h"
+#include "base/rand_util.h"
+#include "base/sampling_heap_profiler/module_cache.h"
+#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/metrics/call_stack_profile_builder.h"
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+namespace {
+
+// Sets heap sampling interval in bytes.
+const char kHeapProfilerSamplingRate[] = "sampling-rate";
+
+constexpr base::TimeDelta kHeapCollectionInterval =
+ base::TimeDelta::FromHours(24);
+
+base::TimeDelta RandomInterval(base::TimeDelta mean) {
+ // Time intervals between profile collections form a Poisson stream with
+ // given mean interval.
+ return -std::log(base::RandDouble()) * mean;
+}
+
+} // namespace
+
+HeapProfilerController::HeapProfilerController()
+ : stopped_(base::MakeRefCounted<StoppedFlag>()) {}
+
+HeapProfilerController::~HeapProfilerController() {
+ stopped_->data.Set();
+}
+
+void HeapProfilerController::Start() {
+ if (!base::FeatureList::IsEnabled(
+ metrics::CallStackProfileMetricsProvider::kHeapProfilerReporting)) {
+ return;
+ }
+ int sampling_rate = base::GetFieldTrialParamByFeatureAsInt(
+ metrics::CallStackProfileMetricsProvider::kHeapProfilerReporting,
+ kHeapProfilerSamplingRate, 0);
+ if (sampling_rate > 0)
+ base::SamplingHeapProfiler::Get()->SetSamplingInterval(sampling_rate);
+ base::SamplingHeapProfiler::Get()->Start();
+ ScheduleNextSnapshot(stopped_);
+}
+
+// static
+void HeapProfilerController::ScheduleNextSnapshot(
+ scoped_refptr<StoppedFlag> stopped) {
+ base::PostDelayedTask(
+ FROM_HERE, {base::ThreadPool(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(&HeapProfilerController::TakeSnapshot, std::move(stopped)),
+ RandomInterval(kHeapCollectionInterval));
+}
+
+// static
+void HeapProfilerController::TakeSnapshot(
+ scoped_refptr<StoppedFlag> stopped) {
+ if (stopped->data.IsSet())
+ return;
+ RetrieveAndSendSnapshot();
+ ScheduleNextSnapshot(std::move(stopped));
+}
+
+// static
+void HeapProfilerController::RetrieveAndSendSnapshot() {
+ std::vector<base::SamplingHeapProfiler::Sample> samples =
+ base::SamplingHeapProfiler::Get()->GetSamples(0);
+ if (samples.empty())
+ return;
+
+ size_t malloc_usage =
+ base::ProcessMetrics::CreateCurrentProcessMetrics()->GetMallocUsage();
+ int malloc_usage_mb = static_cast<int>(malloc_usage >> 20);
+ base::UmaHistogramMemoryLargeMB("Memory.HeapProfiler.Browser.Malloc",
+ malloc_usage_mb);
+
+ base::ModuleCache module_cache;
+ metrics::CallStackProfileParams params(
+ metrics::CallStackProfileParams::BROWSER_PROCESS,
+ metrics::CallStackProfileParams::UNKNOWN_THREAD,
+ metrics::CallStackProfileParams::PERIODIC_HEAP_COLLECTION);
+ metrics::CallStackProfileBuilder profile_builder(params);
+
+ for (const base::SamplingHeapProfiler::Sample& sample : samples) {
+ std::vector<base::Frame> frames;
+ frames.reserve(sample.stack.size());
+ for (const void* frame : sample.stack) {
+ uintptr_t address = reinterpret_cast<uintptr_t>(frame);
+ const base::ModuleCache::Module* module =
+ module_cache.GetModuleForAddress(address);
+ frames.emplace_back(address, module);
+ }
+ size_t count = std::max<size_t>(
+ static_cast<size_t>(
+ std::llround(static_cast<double>(sample.total) / sample.size)),
+ 1);
+ profile_builder.OnSampleCompleted(std::move(frames), sample.total, count);
+ }
+
+ profile_builder.OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+}
diff --git a/chromium/chrome/common/heap_profiler_controller.h b/chromium/chrome/common/heap_profiler_controller.h
new file mode 100644
index 00000000000..a2c1511fc98
--- /dev/null
+++ b/chromium/chrome/common/heap_profiler_controller.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_HEAP_PROFILER_CONTROLLER_H_
+#define CHROME_COMMON_HEAP_PROFILER_CONTROLLER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/atomic_flag.h"
+
+// HeapProfilerController controls collection of sampled heap allocation
+// snapshots for the current process.
+class HeapProfilerController {
+ public:
+ HeapProfilerController();
+ ~HeapProfilerController();
+
+ // Starts periodic heap snapshot collection.
+ void Start();
+
+ private:
+ using StoppedFlag = base::RefCountedData<base::AtomicFlag>;
+
+ static void ScheduleNextSnapshot(scoped_refptr<StoppedFlag> stopped);
+ static void TakeSnapshot(scoped_refptr<StoppedFlag> stopped);
+ static void RetrieveAndSendSnapshot();
+
+ scoped_refptr<StoppedFlag> stopped_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeapProfilerController);
+};
+
+#endif // CHROME_COMMON_HEAP_PROFILER_CONTROLLER_H_
diff --git a/chromium/chrome/common/heap_profiler_controller_unittest.cc b/chromium/chrome/common/heap_profiler_controller_unittest.cc
new file mode 100644
index 00000000000..c0ff97dd2f6
--- /dev/null
+++ b/chromium/chrome/common/heap_profiler_controller_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/heap_profiler_controller.h"
+
+#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "components/metrics/call_stack_profile_builder.h"
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+// TODO(crbug.com/961073): Fix memory leaks in tests and re-enable on LSAN.
+#ifdef LEAK_SANITIZER
+#define MAYBE_EmptyProfileIsNotEmitted DISABLED_EmptyProfileIsNotEmitted
+#else
+#define MAYBE_EmptyProfileIsNotEmitted EmptyProfileIsNotEmitted
+#endif
+
+class HeapProfilerControllerTest : public testing::Test {
+ protected:
+ base::test::TaskEnvironment task_environment{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_F(HeapProfilerControllerTest, MAYBE_EmptyProfileIsNotEmitted) {
+ HeapProfilerController controller;
+ metrics::CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
+ base::BindLambdaForTesting(
+ [](base::TimeTicks time, metrics::SampledProfile profile) {
+ ADD_FAILURE();
+ }));
+ controller.Start();
+
+ task_environment.FastForwardBy(base::TimeDelta::FromDays(365));
+}
+
+// Sampling profiler is not capable of unwinding stack on Android under tests.
+#if !defined(OS_ANDROID)
+TEST_F(HeapProfilerControllerTest, ProfileCollectionsScheduler) {
+ constexpr size_t kAllocationSize = 42 * 1024;
+ constexpr int kSnapshotsToCollect = 3;
+
+ auto controller = std::make_unique<HeapProfilerController>();
+ int profile_count = 0;
+
+ auto check_profile = [&](base::TimeTicks time,
+ metrics::SampledProfile profile) {
+ EXPECT_EQ(metrics::SampledProfile::PERIODIC_HEAP_COLLECTION,
+ profile.trigger_event());
+ EXPECT_LT(0, profile.call_stack_profile().stack_sample_size());
+
+ bool found = false;
+ for (const metrics::CallStackProfile::StackSample& sample :
+ profile.call_stack_profile().stack_sample()) {
+ if (sample.has_weight() &&
+ static_cast<size_t>(sample.weight()) >= kAllocationSize) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+
+ if (++profile_count == kSnapshotsToCollect)
+ controller.reset();
+ };
+
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ metrics::CallStackProfileMetricsProvider::kHeapProfilerReporting);
+ metrics::CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
+ base::BindLambdaForTesting(check_profile));
+ base::SamplingHeapProfiler::Get()->SetSamplingInterval(1024);
+
+ controller->Start();
+
+ auto* sampler = base::PoissonAllocationSampler::Get();
+ sampler->SuppressRandomnessForTest(true);
+ sampler->RecordAlloc(reinterpret_cast<void*>(0x1337), kAllocationSize,
+ base::PoissonAllocationSampler::kMalloc, nullptr);
+ sampler->RecordAlloc(reinterpret_cast<void*>(0x7331), kAllocationSize,
+ base::PoissonAllocationSampler::kMalloc, nullptr);
+
+ task_environment.FastForwardUntilNoTasksRemain();
+ EXPECT_LE(kSnapshotsToCollect, profile_count);
+}
+#endif
diff --git a/chromium/chrome/common/importer/DEPS b/chromium/chrome/common/importer/DEPS
new file mode 100644
index 00000000000..3daf8ae6239
--- /dev/null
+++ b/chromium/chrome/common/importer/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/favicon_base",
+]
diff --git a/chromium/chrome/common/importer/OWNERS b/chromium/chrome/common/importer/OWNERS
new file mode 100644
index 00000000000..4c26573d6df
--- /dev/null
+++ b/chromium/chrome/common/importer/OWNERS
@@ -0,0 +1,13 @@
+gab@chromium.org
+isherman@chromium.org
+
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
+
+per-file *_param_traits*.*=set noparent
+per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# COMPONENT: UI>Browser>Import
diff --git a/chromium/chrome/common/importer/edge_importer_utils_win.cc b/chromium/chrome/common/importer/edge_importer_utils_win.cc
new file mode 100644
index 00000000000..c92159f1405
--- /dev/null
+++ b/chromium/chrome/common/importer/edge_importer_utils_win.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/edge_importer_utils_win.h"
+
+#include <Shlobj.h>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
+
+namespace {
+
+const base::char16 kEdgeSettingsMainKey[] = L"MicrosoftEdge\\Main";
+
+const base::char16 kEdgePackageName[] =
+ L"microsoft.microsoftedge_8wekyb3d8bbwe";
+
+// We assume at the moment that the package name never changes for Edge.
+base::string16 GetEdgePackageName() {
+ return kEdgePackageName;
+}
+
+base::string16 GetEdgeRegistryKey(const base::string16& key_name) {
+ base::string16 registry_key =
+ L"Software\\Classes\\Local Settings\\"
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\"
+ L"Storage\\";
+ registry_key += GetEdgePackageName();
+ registry_key += L"\\";
+ registry_key += key_name;
+ return registry_key;
+}
+
+base::string16 GetPotentiallyOverridenEdgeKey(
+ const base::string16& desired_key_path) {
+ base::string16 test_registry_override(
+ ImporterTestRegistryOverrider::GetTestRegistryOverride());
+ return test_registry_override.empty() ? GetEdgeRegistryKey(desired_key_path)
+ : test_registry_override;
+}
+
+} // namespace
+
+namespace importer {
+
+base::string16 GetEdgeSettingsKey() {
+ return GetPotentiallyOverridenEdgeKey(kEdgeSettingsMainKey);
+}
+
+base::FilePath GetEdgeDataFilePath() {
+ wchar_t buffer[MAX_PATH];
+ if (::SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT,
+ buffer) != S_OK)
+ return base::FilePath();
+
+ base::FilePath base_path(buffer);
+ base::string16 rel_path = L"Packages\\";
+ rel_path += GetEdgePackageName();
+ rel_path += L"\\AC\\MicrosoftEdge\\User\\Default";
+ return base_path.Append(rel_path);
+}
+
+bool IsEdgeFavoritesLegacyMode() {
+ base::win::RegKey key(HKEY_CURRENT_USER, GetEdgeSettingsKey().c_str(),
+ KEY_READ);
+ DWORD ese_enabled = 0;
+ // Check whether Edge is using the new Extensible Store Engine (ESE) format
+ // for its favorites.
+
+ if (key.ReadValueDW(L"FavoritesESEEnabled", &ese_enabled) == ERROR_SUCCESS)
+ return !ese_enabled;
+ // If the registry key is missing, check the Windows version.
+ // Edge switched to ESE in Windows 10 Build 10565 (somewhere between
+ // Windows 10 RTM and Windows 10 November 1511 Update).
+ return base::win::GetVersion() < base::win::Version::WIN10_TH2;
+}
+
+bool EdgeImporterCanImport() {
+ base::File::Info file_info;
+ if (base::win::GetVersion() < base::win::Version::WIN10)
+ return false;
+ return base::GetFileInfo(GetEdgeDataFilePath(), &file_info) &&
+ file_info.is_directory;
+}
+
+} // namespace importer
diff --git a/chromium/chrome/common/importer/edge_importer_utils_win.h b/chromium/chrome/common/importer/edge_importer_utils_win.h
new file mode 100644
index 00000000000..4d54be7cdc0
--- /dev/null
+++ b/chromium/chrome/common/importer/edge_importer_utils_win.h
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_EDGE_IMPORTER_UTILS_WIN_H_
+#define CHROME_COMMON_IMPORTER_EDGE_IMPORTER_UTILS_WIN_H_
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+
+namespace importer {
+
+// Returns the key to be used in HKCU to look for Edge's settings.
+// Overridable by tests via ImporterTestRegistryOverrider.
+base::string16 GetEdgeSettingsKey();
+
+// Returns the data path for the Edge browser. Returns an empty path on error.
+base::FilePath GetEdgeDataFilePath();
+
+// Returns true if Edge favorites is currently in legacy (pre-Edge 13) mode.
+bool IsEdgeFavoritesLegacyMode();
+
+// Returns true if the Edge browser is installed and available for import.
+bool EdgeImporterCanImport();
+
+} // namespace importer
+
+#endif // CHROME_COMMON_IMPORTER_EDGE_IMPORTER_UTILS_WIN_H_
diff --git a/chromium/chrome/common/importer/firefox_importer_utils.cc b/chromium/chrome/common/importer/firefox_importer_utils.cc
new file mode 100644
index 00000000000..e1f5ada7771
--- /dev/null
+++ b/chromium/chrome/common/importer/firefox_importer_utils.cc
@@ -0,0 +1,330 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/firefox_importer_utils.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/ini_parser.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Retrieves the file system path of the profile name.
+base::FilePath GetProfilePath(const base::DictionaryValue& root,
+ const std::string& profile_name) {
+ base::string16 path16;
+ std::string is_relative;
+ if (!root.GetStringASCII(profile_name + ".IsRelative", &is_relative) ||
+ !root.GetString(profile_name + ".Path", &path16))
+ return base::FilePath();
+
+#if defined(OS_WIN)
+ base::ReplaceSubstringsAfterOffset(
+ &path16, 0, base::ASCIIToUTF16("/"), base::ASCIIToUTF16("\\"));
+#endif
+ base::FilePath path = base::FilePath::FromUTF16Unsafe(path16);
+
+ // IsRelative=1 means the folder path would be relative to the
+ // path of profiles.ini. IsRelative=0 refers to a custom profile
+ // location.
+ if (is_relative == "1")
+ path = GetProfilesINI().DirName().Append(path);
+
+ return path;
+}
+
+// Checks if the named profile is the default profile.
+bool IsDefaultProfile(const base::DictionaryValue& root,
+ const std::string& profile_name) {
+ std::string is_default;
+ root.GetStringASCII(profile_name + ".Default", &is_default);
+ return is_default == "1";
+}
+
+} // namespace
+
+base::FilePath GetFirefoxProfilePath() {
+ base::FilePath ini_file = GetProfilesINI();
+ std::string content;
+ base::ReadFileToString(ini_file, &content);
+ DictionaryValueINIParser ini_parser;
+ ini_parser.Parse(content);
+ return GetFirefoxProfilePathFromDictionary(ini_parser.root());
+}
+
+base::FilePath GetFirefoxProfilePathFromDictionary(
+ const base::DictionaryValue& root) {
+ std::vector<std::string> profiles;
+ for (int i = 0; ; ++i) {
+ std::string current_profile = base::StringPrintf("Profile%d", i);
+ if (root.HasKey(current_profile)) {
+ profiles.push_back(current_profile);
+ } else {
+ // Profiles are continuously numbered. So we exit when we can't
+ // find the i-th one.
+ break;
+ }
+ }
+
+ if (profiles.empty())
+ return base::FilePath();
+
+ // When multiple profiles exist, the path to the default profile is returned,
+ // since the other profiles are used mostly by developers for testing.
+ for (std::vector<std::string>::const_iterator it = profiles.begin();
+ it != profiles.end(); ++it)
+ if (IsDefaultProfile(root, *it))
+ return GetProfilePath(root, *it);
+
+ // If no default profile is found, the path to Profile0 will be returned.
+ return GetProfilePath(root, profiles.front());
+}
+
+#if defined(OS_MACOSX)
+// Find the "*.app" component of the path and build up from there.
+// The resulting path will be .../Firefox.app/Contents/MacOS.
+// We do this because we don't trust LastAppDir to always be
+// this particular path, without any subdirs, and we want to make
+// our assumption about Firefox's root being in that path explicit.
+bool ComposeMacAppPath(const std::string& path_from_file,
+ base::FilePath* output) {
+ base::FilePath path(path_from_file);
+ typedef std::vector<base::FilePath::StringType> ComponentVector;
+ ComponentVector path_components;
+ path.GetComponents(&path_components);
+ if (path_components.empty())
+ return false;
+ // The first path component is special because it may be absolute. Calling
+ // Append with an absolute path component will trigger an assert, so we
+ // must handle it differently and initialize output with it.
+ *output = base::FilePath(path_components[0]);
+ // Append next path components untill we find the *.app component. When we do,
+ // append Contents/MacOS.
+ for (size_t i = 1; i < path_components.size(); ++i) {
+ *output = output->Append(path_components[i]);
+ if (base::EndsWith(path_components[i], ".app",
+ base::CompareCase::SENSITIVE)) {
+ *output = output->Append("Contents");
+ *output = output->Append("MacOS");
+ return true;
+ }
+ }
+ LOG(ERROR) << path_from_file << " doesn't look like a valid Firefox "
+ << "installation path: missing /*.app/ directory.";
+ return false;
+}
+#endif // OS_MACOSX
+
+bool GetFirefoxVersionAndPathFromProfile(const base::FilePath& profile_path,
+ int* version,
+ base::FilePath* app_path) {
+ bool ret = false;
+ base::FilePath compatibility_file =
+ profile_path.AppendASCII("compatibility.ini");
+ std::string content;
+ base::ReadFileToString(compatibility_file, &content);
+ base::ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
+
+ for (const std::string& line : base::SplitString(
+ content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (line.empty() || line[0] == '#' || line[0] == ';')
+ continue;
+ size_t equal = line.find('=');
+ if (equal != std::string::npos) {
+ std::string key = line.substr(0, equal);
+ if (key == "LastVersion") {
+ base::StringToInt(line.substr(equal + 1), version);
+ ret = true;
+ } else if (key == "LastAppDir") {
+ // TODO(evanm): If the path in question isn't convertible to
+ // UTF-8, what does Firefox do? If it puts raw bytes in the
+ // file, we could go straight from bytes -> filepath;
+ // otherwise, we're out of luck here.
+#if defined(OS_MACOSX)
+ // Extract path from "LastAppDir=/actual/path"
+ size_t separator_pos = line.find_first_of('=');
+ const std::string& path_from_ini = line.substr(separator_pos + 1);
+ if (!ComposeMacAppPath(path_from_ini, app_path))
+ return false;
+#else // !OS_MACOSX
+ *app_path = base::FilePath::FromUTF8Unsafe(line.substr(equal + 1));
+#endif // OS_MACOSX
+ }
+ }
+ }
+ return ret;
+}
+
+bool ReadPrefFile(const base::FilePath& path, std::string* content) {
+ if (content == NULL)
+ return false;
+
+ base::ReadFileToString(path, content);
+
+ if (content->empty()) {
+ LOG(WARNING) << "Firefox preference file " << path.value() << " is empty.";
+ return false;
+ }
+
+ return true;
+}
+
+std::string ReadBrowserConfigProp(const base::FilePath& app_path,
+ const std::string& pref_key) {
+ std::string content;
+ if (!ReadPrefFile(app_path.AppendASCII("browserconfig.properties"), &content))
+ return std::string();
+
+ // This file has the syntax: key=value.
+ size_t prop_index = content.find(pref_key + "=");
+ if (prop_index == std::string::npos)
+ return std::string();
+
+ size_t start = prop_index + pref_key.length();
+ size_t stop = std::string::npos;
+ if (start != std::string::npos)
+ stop = content.find("\n", start + 1);
+
+ if (start == std::string::npos ||
+ stop == std::string::npos || (start == stop)) {
+ LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
+ return std::string();
+ }
+
+ return content.substr(start + 1, stop - start - 1);
+}
+
+std::string ReadPrefsJsValue(const base::FilePath& profile_path,
+ const std::string& pref_key) {
+ std::string content;
+ if (!ReadPrefFile(profile_path.AppendASCII("prefs.js"), &content))
+ return std::string();
+
+ return GetPrefsJsValue(content, pref_key);
+}
+
+GURL GetHomepage(const base::FilePath& profile_path) {
+ std::string home_page_list =
+ ReadPrefsJsValue(profile_path, "browser.startup.homepage");
+
+ size_t seperator = home_page_list.find_first_of('|');
+ if (seperator == std::string::npos)
+ return GURL(home_page_list);
+
+ return GURL(home_page_list.substr(0, seperator));
+}
+
+bool IsDefaultHomepage(const GURL& homepage, const base::FilePath& app_path) {
+ if (!homepage.is_valid())
+ return false;
+
+ std::string default_homepages =
+ ReadBrowserConfigProp(app_path, "browser.startup.homepage");
+
+ size_t seperator = default_homepages.find_first_of('|');
+ if (seperator == std::string::npos)
+ return homepage.spec() == GURL(default_homepages).spec();
+
+ // Crack the string into separate homepage urls.
+ for (const std::string& url : base::SplitString(
+ default_homepages, "|",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (homepage.spec() == GURL(url).spec())
+ return true;
+ }
+
+ return false;
+}
+
+std::string GetPrefsJsValue(const std::string& content,
+ const std::string& pref_key) {
+ // This file has the syntax: user_pref("key", value);
+ std::string search_for = std::string("user_pref(\"") + pref_key +
+ std::string("\", ");
+ size_t prop_index = content.find(search_for);
+ if (prop_index == std::string::npos)
+ return std::string();
+
+ size_t start = prop_index + search_for.length();
+ size_t stop = std::string::npos;
+ if (start != std::string::npos) {
+ // Stop at the last ')' on this line.
+ stop = content.find("\n", start + 1);
+ stop = content.rfind(")", stop);
+ }
+
+ if (start == std::string::npos || stop == std::string::npos ||
+ stop < start) {
+ LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
+ return std::string();
+ }
+
+ // String values have double quotes we don't need to return to the caller.
+ if (content[start] == '\"' && content[stop - 1] == '\"') {
+ ++start;
+ --stop;
+ }
+
+ return content.substr(start, stop - start);
+}
+
+// The branding name is obtained from the application.ini file from the Firefox
+// application directory. A sample application.ini file is the following:
+// [App]
+// Vendor=Mozilla
+// Name=Iceweasel
+// Profile=mozilla/firefox
+// Version=3.5.16
+// BuildID=20120421070307
+// Copyright=Copyright (c) 1998 - 2010 mozilla.org
+// ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+// .........................................
+// In this example the function returns "Iceweasel" (or a localized equivalent).
+base::string16 GetFirefoxImporterName(const base::FilePath& app_path) {
+ const base::FilePath app_ini_file = app_path.AppendASCII("application.ini");
+ std::string branding_name;
+ if (base::PathExists(app_ini_file)) {
+ std::string content;
+ base::ReadFileToString(app_ini_file, &content);
+
+ const std::string name_attr("Name=");
+ bool in_app_section = false;
+ for (const base::StringPiece& line : base::SplitStringPiece(
+ content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (line == "[App]") {
+ in_app_section = true;
+ } else if (in_app_section) {
+ if (base::StartsWith(line, name_attr, base::CompareCase::SENSITIVE)) {
+ line.substr(name_attr.size()).CopyToString(&branding_name);
+ break;
+ }
+ if (line.length() > 0 && line[0] == '[') {
+ // No longer in the [App] section.
+ break;
+ }
+ }
+ }
+ }
+
+ branding_name = base::ToLowerASCII(branding_name);
+ if (branding_name.find("iceweasel") != std::string::npos)
+ return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_ICEWEASEL);
+ return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_FIREFOX);
+}
diff --git a/chromium/chrome/common/importer/firefox_importer_utils.h b/chromium/chrome/common/importer/firefox_importer_utils.h
new file mode 100644
index 00000000000..ec57c179271
--- /dev/null
+++ b/chromium/chrome/common/importer/firefox_importer_utils.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
+#define CHROME_COMMON_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+}
+
+#if defined(OS_WIN)
+// Detects which version of Firefox is installed from registry. Returns its
+// major version, and drops the minor version. Returns 0 if failed. If there are
+// indicators of both Firefox 2 and Firefox 3 it is biased to return the biggest
+// version.
+int GetCurrentFirefoxMajorVersionFromRegistry();
+
+// Detects where Firefox lives. Returns an empty path if Firefox is not
+// installed.
+base::FilePath GetFirefoxInstallPathFromRegistry();
+#endif // OS_WIN
+
+#if defined(OS_MACOSX)
+// Get the directory in which the Firefox .dylibs live, we need to load these
+// in order to decoded FF profile passwords.
+// The Path is usuall FF App Bundle/Contents/Mac OS/
+// Returns empty path on failure.
+base::FilePath GetFirefoxDylibPath();
+#endif // OS_MACOSX
+
+// Returns the path to the Firefox profile.
+base::FilePath GetFirefoxProfilePath();
+
+// Returns the path to the Firefox profile, using a custom dictionary.
+// Exposed for testing.
+base::FilePath GetFirefoxProfilePathFromDictionary(
+ const base::DictionaryValue& root);
+
+// Detects version of Firefox and installation path for the given Firefox
+// profile.
+bool GetFirefoxVersionAndPathFromProfile(const base::FilePath& profile_path,
+ int* version,
+ base::FilePath* app_path);
+
+// Gets the full path of the profiles.ini file. This file records the profiles
+// that can be used by Firefox. Returns an empty path if failed.
+base::FilePath GetProfilesINI();
+
+// Parses the profile.ini file, and stores its information in |root|.
+// This file is a plain-text file. Key/value pairs are stored one per line, and
+// they are separated in different sections. For example:
+// [General]
+// StartWithLastProfile=1
+//
+// [Profile0]
+// Name=default
+// IsRelative=1
+// Path=Profiles/abcdefeg.default
+// We set "[value]" in path "<Section>.<Key>". For example, the path
+// "Genenral.StartWithLastProfile" has the value "1".
+void ParseProfileINI(const base::FilePath& file, base::DictionaryValue* root);
+
+// Returns the home page set in Firefox in a particular profile.
+GURL GetHomepage(const base::FilePath& profile_path);
+
+// Checks to see if this home page is a default home page, as specified by
+// the resource file browserconfig.properties in the Firefox application
+// directory.
+bool IsDefaultHomepage(const GURL& homepage, const base::FilePath& app_path);
+
+// Parses the value of a particular firefox preference from a string that is the
+// contents of the prefs file.
+std::string GetPrefsJsValue(const std::string& prefs,
+ const std::string& pref_key);
+
+// Returns the localized Firefox branding name.
+// This is useful to differentiate between Firefox and Iceweasel.
+// If anything goes wrong while trying to obtain the branding name,
+// the function assumes it's Firefox.
+base::string16 GetFirefoxImporterName(const base::FilePath& app_path);
+
+#endif // CHROME_COMMON_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
diff --git a/chromium/chrome/common/importer/firefox_importer_utils_linux.cc b/chromium/chrome/common/importer/firefox_importer_utils_linux.cc
new file mode 100644
index 00000000000..1935dcb3b96
--- /dev/null
+++ b/chromium/chrome/common/importer/firefox_importer_utils_linux.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/firefox_importer_utils.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+
+base::FilePath GetProfilesINI() {
+ base::FilePath ini_file;
+ // The default location of the profile folder containing user data is
+ // under user HOME directory in .mozilla/firefox folder on Linux.
+ base::FilePath home;
+ base::PathService::Get(base::DIR_HOME, &home);
+ if (!home.empty()) {
+ ini_file = home.Append(".mozilla/firefox/profiles.ini");
+ }
+ if (base::PathExists(ini_file))
+ return ini_file;
+
+ return base::FilePath();
+}
diff --git a/chromium/chrome/common/importer/firefox_importer_utils_mac.mm b/chromium/chrome/common/importer/firefox_importer_utils_mac.mm
new file mode 100644
index 00000000000..1152a5645ed
--- /dev/null
+++ b/chromium/chrome/common/importer/firefox_importer_utils_mac.mm
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <Cocoa/Cocoa.h>
+#include <sys/param.h>
+
+#include "chrome/common/importer/firefox_importer_utils.h"
+
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/path_service.h"
+
+base::FilePath GetProfilesINI() {
+ base::FilePath app_data_path;
+ if (!base::PathService::Get(base::DIR_APP_DATA, &app_data_path)) {
+ return base::FilePath();
+ }
+ base::FilePath ini_file =
+ app_data_path.Append("Firefox").Append("profiles.ini");
+ if (!base::PathExists(ini_file)) {
+ return base::FilePath();
+ }
+ return ini_file;
+}
+
+base::FilePath GetFirefoxDylibPath() {
+ base::ScopedCFTypeRef<CFErrorRef> out_err;
+ base::ScopedCFTypeRef<CFArrayRef> app_urls(
+ LSCopyApplicationURLsForBundleIdentifier(CFSTR("org.mozilla.firefox"),
+ out_err.InitializeInto()));
+ if (out_err || CFArrayGetCount(app_urls) == 0) {
+ return base::FilePath();
+ }
+ CFURLRef app_url =
+ base::mac::CFCastStrict<CFURLRef>(CFArrayGetValueAtIndex(app_urls, 0));
+ NSBundle* ff_bundle =
+ [NSBundle bundleWithPath:[base::mac::CFToNSCast(app_url) path]];
+ NSString *ff_library_path =
+ [[ff_bundle executablePath] stringByDeletingLastPathComponent];
+ char buf[MAXPATHLEN];
+ if (![ff_library_path getFileSystemRepresentation:buf maxLength:sizeof(buf)])
+ return base::FilePath();
+ return base::FilePath(buf);
+}
diff --git a/chromium/chrome/common/importer/firefox_importer_utils_unittest.cc b/chromium/chrome/common/importer/firefox_importer_utils_unittest.cc
new file mode 100644
index 00000000000..6fdf99de260
--- /dev/null
+++ b/chromium/chrome/common/importer/firefox_importer_utils_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/firefox_importer_utils.h"
+
+#include <stddef.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "chrome/grit/generated_resources.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+struct GetPrefsJsValueCase {
+ std::string prefs_content;
+ std::string pref_name;
+ std::string pref_value;
+} GetPrefsJsValueCases[] = {
+ // Basic case. Single pref, unquoted value.
+ { "user_pref(\"foo.bar\", 1);", "foo.bar", "1" },
+ // Value is quoted. Quotes should be stripped.
+ { "user_pref(\"foo.bar\", \"1\");", "foo.bar", "1" },
+ // Value has parens.
+ { "user_pref(\"foo.bar\", \"Value (detail)\");",
+ "foo.bar", "Value (detail)" },
+ // Multi-line case.
+ { "user_pref(\"foo.bar\", 1);\n"
+ "user_pref(\"foo.baz\", 2);\n"
+ "user_pref(\"foo.bag\", 3);",
+ "foo.baz", "2" },
+ // Malformed content.
+ { "user_pref(\"foo.bar\", 1);\n"
+ "user_pref(\"foo.baz\", 2;\n"
+ "user_pref(\"foo.bag\", 3);",
+ "foo.baz", "" },
+ // Malformed content.
+ { "uesr_pref(\"foo.bar\", 1);", "foo.bar", "" },
+};
+
+struct GetFirefoxImporterNameCase {
+ std::string app_ini_content;
+ int resource_id;
+} GetFirefoxImporterNameCases[] = {
+ // Basic case
+ { "[App]\n"
+ "Vendor=Mozilla\n"
+ "Name=iceweasel\n"
+ "Version=10.0.6\n"
+ "BuildID=20120717115048\n"
+ "ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ IDS_IMPORT_FROM_ICEWEASEL },
+ // Whitespace
+ { " \t[App] \n"
+ "Vendor=Mozilla\n"
+ " Name=Firefox\t \r\n"
+ "Version=10.0.6\n",
+ IDS_IMPORT_FROM_FIREFOX },
+ // No Name setting
+ { "[App]\n"
+ "Vendor=Mozilla\n"
+ "Version=10.0.6\n"
+ "BuildID=20120717115048\n"
+ "ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ IDS_IMPORT_FROM_FIREFOX },
+ // No [App] section
+ { "[Foo]\n"
+ "Vendor=Mozilla\n"
+ "Name=Foo\n",
+ IDS_IMPORT_FROM_FIREFOX },
+ // Multiple Name settings in different sections
+ { "[Foo]\n"
+ "Vendor=Mozilla\n"
+ "Name=Firefox\n"
+ "[App]\n"
+ "Profile=mozilla/firefox\n"
+ "Name=iceweasel\n"
+ "[Bar]\n"
+ "Name=Bar\n"
+ "ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ IDS_IMPORT_FROM_ICEWEASEL },
+ // Case-insensitivity
+ { "[App]\n"
+ "Vendor=Mozilla\n"
+ "Name=IceWeasel\n"
+ "Version=10.0.6\n",
+ IDS_IMPORT_FROM_ICEWEASEL },
+ // Empty file
+ { "", IDS_IMPORT_FROM_FIREFOX }
+};
+
+} // anonymous namespace
+
+TEST(FirefoxImporterUtilsTest, GetPrefsJsValue) {
+ for (size_t i = 0; i < base::size(GetPrefsJsValueCases); ++i) {
+ EXPECT_EQ(
+ GetPrefsJsValueCases[i].pref_value,
+ GetPrefsJsValue(GetPrefsJsValueCases[i].prefs_content,
+ GetPrefsJsValueCases[i].pref_name));
+ }
+}
+
+TEST(FirefoxImporterUtilsTest, GetFirefoxImporterName) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ const base::FilePath app_ini_file(
+ temp_dir.GetPath().AppendASCII("application.ini"));
+ for (size_t i = 0; i < base::size(GetFirefoxImporterNameCases); ++i) {
+ base::WriteFile(app_ini_file,
+ GetFirefoxImporterNameCases[i].app_ini_content.c_str(),
+ GetFirefoxImporterNameCases[i].app_ini_content.size());
+ EXPECT_EQ(
+ GetFirefoxImporterName(temp_dir.GetPath()),
+ l10n_util::GetStringUTF16(GetFirefoxImporterNameCases[i].resource_id));
+ }
+ EXPECT_EQ(l10n_util::GetStringUTF16(
+ IDS_IMPORT_FROM_FIREFOX),
+ GetFirefoxImporterName(base::FilePath(
+ FILE_PATH_LITERAL("/invalid/path"))));
+}
+
+TEST(FirefoxImporterUtilsTest, GetFirefoxProfilePath) {
+ base::DictionaryValue no_profiles;
+ EXPECT_EQ("",
+ GetFirefoxProfilePathFromDictionary(no_profiles).MaybeAsASCII());
+
+ base::DictionaryValue single_profile;
+ single_profile.SetString("Profile0.Path", "first");
+ single_profile.SetString("Profile0.IsRelative", "0");
+ single_profile.SetString("Profile0.Default", "1");
+ EXPECT_EQ("first",
+ GetFirefoxProfilePathFromDictionary(single_profile).MaybeAsASCII());
+
+ base::DictionaryValue no_default;
+ no_default.SetString("Profile0.Path", "first");
+ no_default.SetString("Profile0.IsRelative", "0");
+ no_default.SetString("Profile1.Path", "second");
+ no_default.SetString("Profile1.IsRelative", "0");
+ EXPECT_EQ("first",
+ GetFirefoxProfilePathFromDictionary(no_default).MaybeAsASCII());
+
+ base::DictionaryValue default_first;
+ default_first.SetString("Profile0.Path", "first");
+ default_first.SetString("Profile0.IsRelative", "0");
+ default_first.SetString("Profile0.Default", "1");
+ default_first.SetString("Profile1.Path", "second");
+ default_first.SetString("Profile1.IsRelative", "0");
+ EXPECT_EQ("first",
+ GetFirefoxProfilePathFromDictionary(default_first).MaybeAsASCII());
+
+ base::DictionaryValue default_second;
+ default_second.SetString("Profile0.Path", "first");
+ default_second.SetString("Profile0.IsRelative", "0");
+ default_second.SetString("Profile1.Path", "second");
+ default_second.SetString("Profile1.IsRelative", "0");
+ default_second.SetString("Profile1.Default", "1");
+ EXPECT_EQ("second",
+ GetFirefoxProfilePathFromDictionary(default_second).MaybeAsASCII());
+}
diff --git a/chromium/chrome/common/importer/firefox_importer_utils_win.cc b/chromium/chrome/common/importer/firefox_importer_utils_win.cc
new file mode 100644
index 00000000000..a44104f9b26
--- /dev/null
+++ b/chromium/chrome/common/importer/firefox_importer_utils_win.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/firefox_importer_utils.h"
+
+#include <shlobj.h>
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/win/registry.h"
+
+namespace {
+
+// NOTE: Keep these in order since we need test all those paths according
+// to priority. For example. One machine has multiple users. One non-admin
+// user installs Firefox 2, which causes there is a Firefox2 entry under HKCU.
+// One admin user installs Firefox 3, which causes there is a Firefox 3 entry
+// under HKLM. So when the non-admin user log in, we should deal with Firefox 2
+// related data instead of Firefox 3.
+const HKEY kFireFoxRegistryPaths[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
+
+constexpr const wchar_t* kFirefoxPath = L"Software\\Mozilla\\Mozilla Firefox";
+constexpr const wchar_t* kCurrentVersion = L"CurrentVersion";
+
+} // namespace
+
+int GetCurrentFirefoxMajorVersionFromRegistry() {
+ TCHAR ver_buffer[128];
+ DWORD ver_buffer_length = sizeof(ver_buffer);
+ int highest_version = 0;
+ // When installing Firefox with admin account, the product keys will be
+ // written under HKLM\Mozilla. Otherwise it the keys will be written under
+ // HKCU\Mozilla.
+ for (const HKEY kFireFoxRegistryPath : kFireFoxRegistryPaths) {
+ base::win::RegKey reg_key(kFireFoxRegistryPath, kFirefoxPath, KEY_READ);
+
+ LONG result = reg_key.ReadValue(kCurrentVersion, ver_buffer,
+ &ver_buffer_length, NULL);
+ if (result != ERROR_SUCCESS)
+ continue;
+ highest_version = std::max(highest_version, _wtoi(ver_buffer));
+ }
+ return highest_version;
+}
+
+base::FilePath GetFirefoxInstallPathFromRegistry() {
+ // Detects the path that Firefox is installed in.
+ base::string16 registry_path = kFirefoxPath;
+ wchar_t buffer[MAX_PATH];
+ DWORD buffer_length = sizeof(buffer);
+ base::win::RegKey reg_key(HKEY_LOCAL_MACHINE, registry_path.c_str(),
+ KEY_READ);
+ LONG result = reg_key.ReadValue(kCurrentVersion, buffer,
+ &buffer_length, NULL);
+ if (result != ERROR_SUCCESS)
+ return base::FilePath();
+
+ registry_path += L"\\" + base::string16(buffer) + L"\\Main";
+ buffer_length = sizeof(buffer);
+ base::win::RegKey reg_key_directory(HKEY_LOCAL_MACHINE,
+ registry_path.c_str(), KEY_READ);
+ result = reg_key_directory.ReadValue(L"Install Directory", buffer,
+ &buffer_length, NULL);
+
+ return (result != ERROR_SUCCESS) ? base::FilePath() : base::FilePath(buffer);
+}
+
+base::FilePath GetProfilesINI() {
+ base::FilePath ini_file;
+ // The default location of the profile folder containing user data is
+ // under the "Application Data" folder in Windows XP, Vista, and 7.
+ if (!base::PathService::Get(base::DIR_APP_DATA, &ini_file))
+ return base::FilePath();
+
+ ini_file = ini_file.AppendASCII("Mozilla");
+ ini_file = ini_file.AppendASCII("Firefox");
+ ini_file = ini_file.AppendASCII("profiles.ini");
+
+ return base::PathExists(ini_file) ? ini_file : base::FilePath();
+}
diff --git a/chromium/chrome/common/importer/ie_importer_utils_win.cc b/chromium/chrome/common/importer/ie_importer_utils_win.cc
new file mode 100644
index 00000000000..726e5b01668
--- /dev/null
+++ b/chromium/chrome/common/importer/ie_importer_utils_win.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/ie_importer_utils_win.h"
+
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
+
+namespace {
+
+const base::char16 kIEFavoritesOrderKey[] =
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\"
+ L"MenuOrder\\Favorites";
+
+const base::char16 kIEStorage2Key[] =
+ L"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
+
+const base::char16 kIESettingsMainKey[] =
+ L"Software\\Microsoft\\Internet Explorer\\Main";
+
+base::string16 GetPotentiallyOverridenIEKey(
+ const base::string16& desired_key_path) {
+ base::string16 test_reg_override(
+ ImporterTestRegistryOverrider::GetTestRegistryOverride());
+ return test_reg_override.empty() ? desired_key_path : test_reg_override;
+}
+
+} // namespace
+
+namespace importer {
+
+base::string16 GetIEFavoritesOrderKey() {
+ // Return kIEFavoritesOrderKey unless an override has been set for tests.
+ return GetPotentiallyOverridenIEKey(kIEFavoritesOrderKey);
+}
+
+base::string16 GetIE7PasswordsKey() {
+ // Return kIEStorage2Key unless an override has been set for tests.
+ return GetPotentiallyOverridenIEKey(kIEStorage2Key);
+}
+
+base::string16 GetIESettingsKey() {
+ // Return kIESettingsMainKey unless an override has been set for tests.
+ return GetPotentiallyOverridenIEKey(kIESettingsMainKey);
+}
+
+} // namespace importer
+
diff --git a/chromium/chrome/common/importer/ie_importer_utils_win.h b/chromium/chrome/common/importer/ie_importer_utils_win.h
new file mode 100644
index 00000000000..8e43bbaee4c
--- /dev/null
+++ b/chromium/chrome/common/importer/ie_importer_utils_win.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IE_IMPORTER_UTILS_WIN_H_
+#define CHROME_COMMON_IMPORTER_IE_IMPORTER_UTILS_WIN_H_
+
+#include "base/strings/string16.h"
+
+namespace importer {
+
+// Returns the key to be used in HKCU to look for IE's favorites order blob.
+// Overridable by tests via ImporterTestRegistryOverrider.
+base::string16 GetIEFavoritesOrderKey();
+
+// Returns the key to be used in HKCU to look for IE7 passwords.
+// Overridable by tests via ImporterTestRegistryOverrider.
+base::string16 GetIE7PasswordsKey();
+
+// Returns the key to be used in HKCU to look for IE settings.
+// Overridable by tests via ImporterTestRegistryOverrider.
+base::string16 GetIESettingsKey();
+
+} // namespace importer
+
+#endif // CHROME_COMMON_IMPORTER_IE_IMPORTER_UTILS_WIN_H_
diff --git a/chromium/chrome/common/importer/imported_bookmark_entry.cc b/chromium/chrome/common/importer/imported_bookmark_entry.cc
new file mode 100644
index 00000000000..a82e1cd85c0
--- /dev/null
+++ b/chromium/chrome/common/importer/imported_bookmark_entry.cc
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/imported_bookmark_entry.h"
+
+ImportedBookmarkEntry::ImportedBookmarkEntry()
+ : in_toolbar(false),
+ is_folder(false) {}
+
+ImportedBookmarkEntry::ImportedBookmarkEntry(
+ const ImportedBookmarkEntry& other) = default;
+
+ImportedBookmarkEntry::~ImportedBookmarkEntry() {}
+
+bool ImportedBookmarkEntry::operator==(
+ const ImportedBookmarkEntry& other) const {
+ return (in_toolbar == other.in_toolbar &&
+ is_folder == other.is_folder &&
+ url == other.url &&
+ path == other.path &&
+ title == other.title &&
+ creation_time == other.creation_time);
+}
diff --git a/chromium/chrome/common/importer/imported_bookmark_entry.h b/chromium/chrome/common/importer/imported_bookmark_entry.h
new file mode 100644
index 00000000000..abb9ea0ba3e
--- /dev/null
+++ b/chromium/chrome/common/importer/imported_bookmark_entry.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTED_BOOKMARK_ENTRY_H_
+#define CHROME_COMMON_IMPORTER_IMPORTED_BOOKMARK_ENTRY_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+struct ImportedBookmarkEntry {
+ ImportedBookmarkEntry();
+ ImportedBookmarkEntry(const ImportedBookmarkEntry& other);
+ ~ImportedBookmarkEntry();
+
+ bool operator==(const ImportedBookmarkEntry& other) const;
+
+ bool in_toolbar;
+ bool is_folder;
+ GURL url;
+ std::vector<base::string16> path;
+ base::string16 title;
+ base::Time creation_time;
+};
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTED_BOOKMARK_ENTRY_H_
diff --git a/chromium/chrome/common/importer/importer_autofill_form_data_entry.cc b/chromium/chrome/common/importer/importer_autofill_form_data_entry.cc
new file mode 100644
index 00000000000..5dd4c5a3742
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_autofill_form_data_entry.cc
@@ -0,0 +1,11 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/importer_autofill_form_data_entry.h"
+
+ImporterAutofillFormDataEntry::ImporterAutofillFormDataEntry() : times_used(0) {
+}
+
+ImporterAutofillFormDataEntry::~ImporterAutofillFormDataEntry() {
+}
diff --git a/chromium/chrome/common/importer/importer_autofill_form_data_entry.h b/chromium/chrome/common/importer/importer_autofill_form_data_entry.h
new file mode 100644
index 00000000000..af35195ae43
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_autofill_form_data_entry.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_AUTOFILL_FORM_DATA_ENTRY_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_AUTOFILL_FORM_DATA_ENTRY_H_
+
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+
+// Used as the target for importing form history from other browsers' profiles
+// in the utility process.
+struct ImporterAutofillFormDataEntry {
+ ImporterAutofillFormDataEntry();
+ ~ImporterAutofillFormDataEntry();
+
+ // Name of input element.
+ base::string16 name;
+
+ // Value of input element.
+ base::string16 value;
+
+ // Number of times this name-value pair has been used.
+ int times_used;
+
+ // The date of the first time when this name-value pair was used.
+ base::Time first_used;
+
+ // The date of the last time when this name-value pair was used.
+ base::Time last_used;
+};
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_AUTOFILL_FORM_DATA_ENTRY_H_
diff --git a/chromium/chrome/common/importer/importer_bridge.cc b/chromium/chrome/common/importer/importer_bridge.cc
new file mode 100644
index 00000000000..15b7f745cfa
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_bridge.cc
@@ -0,0 +1,9 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/importer_bridge.h"
+
+ImporterBridge::ImporterBridge() {}
+
+ImporterBridge::~ImporterBridge() {}
diff --git a/chromium/chrome/common/importer/importer_bridge.h b/chromium/chrome/common/importer/importer_bridge.h
new file mode 100644
index 00000000000..b4250c91d1b
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_bridge.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_BRIDGE_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_BRIDGE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "chrome/common/importer/importer_data_types.h"
+#include "chrome/common/importer/importer_url_row.h"
+#include "components/favicon_base/favicon_usage_data.h"
+
+class GURL;
+struct ImportedBookmarkEntry;
+struct ImporterAutofillFormDataEntry;
+
+namespace autofill {
+struct PasswordForm;
+}
+
+namespace importer {
+struct SearchEngineInfo;
+}
+
+class ImporterBridge : public base::RefCountedThreadSafe<ImporterBridge> {
+ public:
+ ImporterBridge();
+
+ virtual void AddBookmarks(
+ const std::vector<ImportedBookmarkEntry>& bookmarks,
+ const base::string16& first_folder_name) = 0;
+
+ virtual void AddHomePage(const GURL& home_page) = 0;
+
+ virtual void SetFavicons(
+ const favicon_base::FaviconUsageDataList& favicons) = 0;
+
+ virtual void SetHistoryItems(const std::vector<ImporterURLRow>& rows,
+ importer::VisitSource visit_source) = 0;
+
+ virtual void SetKeywords(
+ const std::vector<importer::SearchEngineInfo>& search_engines,
+ bool unique_on_host_and_path) = 0;
+
+ // The search_engine_data vector contains XML data retrieved from the Firefox
+ // profile and its sqlite db.
+ virtual void SetFirefoxSearchEnginesXMLData(
+ const std::vector<std::string>& search_engine_data) = 0;
+
+ virtual void SetPasswordForm(const autofill::PasswordForm& form) = 0;
+
+ virtual void SetAutofillFormData(
+ const std::vector<ImporterAutofillFormDataEntry>& entries) = 0;
+
+ // Notifies the coordinator that the import operation has begun.
+ virtual void NotifyStarted() = 0;
+
+ // Notifies the coordinator that the collection of data for the specified
+ // item has begun.
+ virtual void NotifyItemStarted(importer::ImportItem item) = 0;
+
+ // Notifies the coordinator that the collection of data for the specified
+ // item has completed.
+ virtual void NotifyItemEnded(importer::ImportItem item) = 0;
+
+ // Notifies the coordinator that the entire import operation has completed.
+ virtual void NotifyEnded() = 0;
+
+ // For InProcessImporters this calls l10n_util. For ExternalProcessImporters
+ // this calls the set of strings we've ported over to the external process.
+ // It's good to avoid having to create a separate ResourceBundle for the
+ // external import process, since the importer only needs a few strings.
+ virtual base::string16 GetLocalizedString(int message_id) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ImporterBridge>;
+
+ virtual ~ImporterBridge();
+
+ DISALLOW_COPY_AND_ASSIGN(ImporterBridge);
+};
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_BRIDGE_H_
diff --git a/chromium/chrome/common/importer/importer_data_types.cc b/chromium/chrome/common/importer/importer_data_types.cc
new file mode 100644
index 00000000000..25e4444dd05
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_data_types.cc
@@ -0,0 +1,32 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "chrome/common/importer/importer_data_types.h"
+
+namespace importer {
+
+SourceProfile::SourceProfile()
+ : importer_type(TYPE_UNKNOWN),
+ services_supported(0) {
+}
+
+SourceProfile::SourceProfile(const SourceProfile& other) = default;
+
+SourceProfile::~SourceProfile() {
+}
+
+ImporterIE7PasswordInfo::ImporterIE7PasswordInfo() {
+}
+
+ImporterIE7PasswordInfo::ImporterIE7PasswordInfo(
+ const ImporterIE7PasswordInfo& other) = default;
+
+ImporterIE7PasswordInfo::~ImporterIE7PasswordInfo() {
+}
+
+ImporterIE7PasswordInfo& ImporterIE7PasswordInfo::operator=(
+ const ImporterIE7PasswordInfo& other) = default;
+
+} // namespace importer
diff --git a/chromium/chrome/common/importer/importer_data_types.h b/chromium/chrome/common/importer/importer_data_types.h
new file mode 100644
index 00000000000..0fc90c62398
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_data_types.h
@@ -0,0 +1,90 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_DATA_TYPES_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_DATA_TYPES_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/common/importer/importer_type.h"
+#include "url/gurl.h"
+
+// Types needed for importing data from other browsers and the Google Toolbar.
+namespace importer {
+
+// An enumeration of the type of data that can be imported.
+enum ImportItem {
+ NONE = 0,
+ HISTORY = 1 << 0,
+ FAVORITES = 1 << 1,
+ COOKIES = 1 << 2, // Not supported yet.
+ PASSWORDS = 1 << 3,
+ SEARCH_ENGINES = 1 << 4,
+ HOME_PAGE = 1 << 5,
+ AUTOFILL_FORM_DATA = 1 << 6,
+ ALL = (1 << 7) - 1 // All the bits should be 1, hence the -1.
+};
+
+// Information about a profile needed by an importer to do import work.
+struct SourceProfile {
+ SourceProfile();
+ SourceProfile(const SourceProfile& other);
+ ~SourceProfile();
+
+ base::string16 importer_name;
+ ImporterType importer_type;
+ base::FilePath source_path;
+ base::FilePath app_path;
+ uint16_t services_supported; // Bitmask of ImportItem.
+ // The application locale. Stored because we can only access it from the UI
+ // thread on the browser process. This is only used by the Firefox importer.
+ std::string locale;
+};
+
+// Contains information needed for importing search engine urls.
+struct SearchEngineInfo {
+ // |url| is a string instead of a GURL since it may not actually be a valid
+ // GURL directly (e.g. for "http://%s.com").
+ base::string16 url;
+ base::string16 keyword;
+ base::string16 display_name;
+};
+
+// Contains the information read from the IE7/IE8 Storage2 key in the registry.
+struct ImporterIE7PasswordInfo {
+ ImporterIE7PasswordInfo();
+ ImporterIE7PasswordInfo(const ImporterIE7PasswordInfo& other);
+ ~ImporterIE7PasswordInfo();
+ ImporterIE7PasswordInfo& operator=(const ImporterIE7PasswordInfo& other);
+
+ // Hash of the url.
+ base::string16 url_hash;
+
+ // Encrypted data containing the username, password and some more
+ // undocumented fields.
+ std::vector<unsigned char> encrypted_data;
+
+ // When the login was imported.
+ base::Time date_created;
+};
+
+// Mapped to history::VisitSource after import in the browser.
+enum VisitSource {
+ VISIT_SOURCE_BROWSED = 0,
+ VISIT_SOURCE_FIREFOX_IMPORTED = 1,
+ VISIT_SOURCE_IE_IMPORTED = 2,
+ VISIT_SOURCE_SAFARI_IMPORTED = 3,
+};
+
+} // namespace importer
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_DATA_TYPES_H_
diff --git a/chromium/chrome/common/importer/importer_test_registry_overrider_win.cc b/chromium/chrome/common/importer/importer_test_registry_overrider_win.cc
new file mode 100644
index 00000000000..0f881968259
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_test_registry_overrider_win.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
+
+#include <windows.h>
+
+#include <memory>
+#include <string>
+
+#include "base/environment.h"
+#include "base/guid.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+
+namespace {
+
+// The key to which a random subkey will be appended. This key itself will never
+// be deleted.
+const wchar_t kTestHKCUOverrideKeyPrefix[] = L"SOFTWARE\\Chromium Unit Tests\\";
+const char kTestHKCUOverrideEnvironmentVariable[] =
+ "IE_IMPORTER_TEST_OVERRIDE_HKCU";
+
+// Reads the environment variable set by a previous call to
+// SetTestRegistryOverride() into |key| if it exists and |key| is not NULL.
+// Returns true if the variable was successfully read.
+bool GetTestKeyFromEnvironment(base::string16* key) {
+ std::unique_ptr<base::Environment> env(base::Environment::Create());
+ std::string value;
+ bool result = env->GetVar(kTestHKCUOverrideEnvironmentVariable, &value);
+ if (result)
+ *key = base::UTF8ToUTF16(value);
+ return result;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// ImporterTestRegistryOverrider, public:
+
+ImporterTestRegistryOverrider::ImporterTestRegistryOverrider()
+ : temporary_key_(kTestHKCUOverrideKeyPrefix +
+ base::UTF8ToUTF16(base::GenerateGUID())) {
+ DCHECK(!GetTestKeyFromEnvironment(NULL));
+
+ std::unique_ptr<base::Environment> env(base::Environment::Create());
+ bool success = env->SetVar(kTestHKCUOverrideEnvironmentVariable,
+ base::UTF16ToUTF8(temporary_key_));
+ DCHECK(success);
+}
+
+ImporterTestRegistryOverrider::~ImporterTestRegistryOverrider() {
+ base::win::RegKey reg_key(HKEY_CURRENT_USER, temporary_key_.c_str(),
+ KEY_ALL_ACCESS);
+ DCHECK(reg_key.Valid());
+ reg_key.DeleteKey(L"");
+
+ std::unique_ptr<base::Environment> env(base::Environment::Create());
+ bool success = env->UnSetVar(kTestHKCUOverrideEnvironmentVariable);
+ DCHECK(success);
+}
+
+// static
+base::string16 ImporterTestRegistryOverrider::GetTestRegistryOverride() {
+ base::string16 key;
+ if (!GetTestKeyFromEnvironment(&key))
+ return base::string16();
+ return key;
+}
diff --git a/chromium/chrome/common/importer/importer_test_registry_overrider_win.h b/chromium/chrome/common/importer/importer_test_registry_overrider_win.h
new file mode 100644
index 00000000000..f814382ca3a
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_test_registry_overrider_win.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+
+// A helper class to let tests generate a random registry key to be used in
+// HKEY_CURRENT_USER in tests. After the key has been generated by constructing
+// an ImporterTestRegistryOverrider, consumers in this process (or in any
+// child processes created after the key has been generated) can obtain the key
+// via GetTestRegistryOverride(). ImporterTestRegistryOverrider will delete
+// the temporary key upon being deleted itself. Only one
+// ImporterTestRegistryOverrider should live at once in a given process
+// hiearchy.
+class ImporterTestRegistryOverrider {
+ public:
+ ImporterTestRegistryOverrider();
+ ~ImporterTestRegistryOverrider();
+
+ // Returns a test key if one was chosen and set by a call to
+ // SetTestRegistryOverride(); returns the empty string if none.
+ static base::string16 GetTestRegistryOverride();
+
+ private:
+ base::string16 temporary_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImporterTestRegistryOverrider);
+};
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
diff --git a/chromium/chrome/common/importer/importer_type.h b/chromium/chrome/common/importer/importer_type.h
new file mode 100644
index 00000000000..c172f8a5bc5
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_type.h
@@ -0,0 +1,36 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_TYPE_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_TYPE_H_
+
+#include "build/build_config.h"
+
+namespace importer {
+
+// An enumeration of the type of importers that we support to import
+// settings and data from (browsers, google toolbar and a bookmarks html file).
+// NOTE: Numbers added so that data can be reliably cast to ints and passed
+// across IPC.
+enum ImporterType {
+ TYPE_UNKNOWN = -1,
+#if defined(OS_WIN)
+ TYPE_IE = 0,
+#endif
+ // Value 1 was the (now deleted) Firefox 2 profile importer.
+ TYPE_FIREFOX = 2,
+#if defined(OS_MACOSX)
+ TYPE_SAFARI = 3,
+#endif
+ // Value 4 was the (now deleted) Google Toolbar importer.
+ TYPE_BOOKMARKS_FILE = 5, // Identifies a 'bookmarks.html' file.
+#if defined(OS_WIN)
+ TYPE_EDGE = 6,
+#endif
+};
+
+} // namespace importer
+
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_TYPE_H_
diff --git a/chromium/chrome/common/importer/importer_url_row.cc b/chromium/chrome/common/importer/importer_url_row.cc
new file mode 100644
index 00000000000..26a3414a6c0
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_url_row.cc
@@ -0,0 +1,21 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/importer_url_row.h"
+
+ImporterURLRow::ImporterURLRow()
+ : visit_count(0),
+ typed_count(0),
+ hidden(false) {
+}
+
+ImporterURLRow::ImporterURLRow(const GURL& url)
+ : url(url),
+ visit_count(0),
+ typed_count(0),
+ hidden(false) {
+}
+
+ImporterURLRow::ImporterURLRow(const ImporterURLRow& other) = default;
+
diff --git a/chromium/chrome/common/importer/importer_url_row.h b/chromium/chrome/common/importer/importer_url_row.h
new file mode 100644
index 00000000000..f7e0a6dd8e8
--- /dev/null
+++ b/chromium/chrome/common/importer/importer_url_row.h
@@ -0,0 +1,39 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_URL_ROW_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_URL_ROW_H_
+
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+// Used as the target for importing history URLs from other browser's profiles
+// in the utility process. Converted to history::URLRow after being passed via
+// IPC to the browser.
+struct ImporterURLRow {
+ public:
+ ImporterURLRow();
+ explicit ImporterURLRow(const GURL& url);
+ ImporterURLRow(const ImporterURLRow& other);
+
+ GURL url;
+ base::string16 title;
+
+ // Total number of times this URL has been visited.
+ int visit_count;
+
+ // Number of times this URL has been manually entered in the URL bar.
+ int typed_count;
+
+ // The date of the last visit of this URL, which saves us from having to
+ // loop up in the visit table for things like autocomplete and expiration.
+ base::Time last_visit;
+
+ // Indicates this entry should now be shown in typical UI or queries, this
+ // is usually for subframes.
+ bool hidden;
+};
+
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_URL_ROW_H_
diff --git a/chromium/chrome/common/importer/mock_importer_bridge.cc b/chromium/chrome/common/importer/mock_importer_bridge.cc
new file mode 100644
index 00000000000..69fd927a1fa
--- /dev/null
+++ b/chromium/chrome/common/importer/mock_importer_bridge.cc
@@ -0,0 +1,9 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/mock_importer_bridge.h"
+
+MockImporterBridge::MockImporterBridge() {}
+
+MockImporterBridge::~MockImporterBridge() {}
diff --git a/chromium/chrome/common/importer/mock_importer_bridge.h b/chromium/chrome/common/importer/mock_importer_bridge.h
new file mode 100644
index 00000000000..104c44b4847
--- /dev/null
+++ b/chromium/chrome/common/importer/mock_importer_bridge.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_MOCK_IMPORTER_BRIDGE_H_
+#define CHROME_COMMON_IMPORTER_MOCK_IMPORTER_BRIDGE_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/importer/imported_bookmark_entry.h"
+#include "chrome/common/importer/importer_autofill_form_data_entry.h"
+#include "chrome/common/importer/importer_bridge.h"
+#include "components/autofill/core/common/password_form.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class MockImporterBridge : public ImporterBridge {
+ public:
+ MockImporterBridge();
+
+ MOCK_METHOD2(AddBookmarks,
+ void(const std::vector<ImportedBookmarkEntry>&,
+ const base::string16&));
+ MOCK_METHOD1(AddHomePage, void(const GURL&));
+ MOCK_METHOD1(SetFavicons, void(const favicon_base::FaviconUsageDataList&));
+ MOCK_METHOD2(SetHistoryItems,
+ void(const std::vector<ImporterURLRow>&, importer::VisitSource));
+ MOCK_METHOD2(SetKeywords,
+ void(const std::vector<importer::SearchEngineInfo>&, bool));
+ MOCK_METHOD1(SetFirefoxSearchEnginesXMLData,
+ void(const std::vector<std::string>&));
+ MOCK_METHOD1(SetPasswordForm, void(const autofill::PasswordForm&));
+ MOCK_METHOD1(SetAutofillFormData,
+ void(const std::vector<ImporterAutofillFormDataEntry>&));
+ MOCK_METHOD0(NotifyStarted, void());
+ MOCK_METHOD1(NotifyItemStarted, void(importer::ImportItem));
+ MOCK_METHOD1(NotifyItemEnded, void(importer::ImportItem));
+ MOCK_METHOD0(NotifyEnded, void());
+ MOCK_METHOD1(GetLocalizedString, base::string16(int));
+
+ private:
+ ~MockImporterBridge() override;
+};
+
+#endif // CHROME_COMMON_IMPORTER_MOCK_IMPORTER_BRIDGE_H_
diff --git a/chromium/chrome/common/importer/profile_import_process_param_traits.cc b/chromium/chrome/common/importer/profile_import_process_param_traits.cc
new file mode 100644
index 00000000000..424a17f9e32
--- /dev/null
+++ b/chromium/chrome/common/importer/profile_import_process_param_traits.cc
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/importer/profile_import_process_param_traits_macros.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#undef CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/importer/profile_import_process_param_traits_macros.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/importer/profile_import_process_param_traits_macros.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/importer/profile_import_process_param_traits_macros.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/importer/profile_import_process_param_traits_macros.h"
+} // namespace IPC
diff --git a/chromium/chrome/common/importer/profile_import_process_param_traits.h b/chromium/chrome/common/importer/profile_import_process_param_traits.h
new file mode 100644
index 00000000000..46ed09336af
--- /dev/null
+++ b/chromium/chrome/common/importer/profile_import_process_param_traits.h
@@ -0,0 +1,10 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_H_
+#define CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_H_
+
+#include "chrome/common/importer/profile_import_process_param_traits_macros.h"
+
+#endif // CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_H_
diff --git a/chromium/chrome/common/importer/profile_import_process_param_traits_macros.h b/chromium/chrome/common/importer/profile_import_process_param_traits_macros.h
new file mode 100644
index 00000000000..dd005641f43
--- /dev/null
+++ b/chromium/chrome/common/importer/profile_import_process_param_traits_macros.h
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Singly or Multiply-included shared traits file depending on circumstances.
+// This allows the use of IPC serialization macros in more than one IPC message
+// file.
+#ifndef CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
+#define CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/common_param_traits_macros.h"
+#include "chrome/common/importer/imported_bookmark_entry.h"
+#include "chrome/common/importer/importer_autofill_form_data_entry.h"
+#include "chrome/common/importer/importer_data_types.h"
+#include "chrome/common/importer/importer_url_row.h"
+#include "components/autofill/core/common/password_form.h"
+#include "components/favicon_base/favicon_usage_data.h"
+#include "content/public/common/common_param_traits.h"
+#include "ipc/ipc_message_macros.h"
+
+#if defined(OS_WIN)
+IPC_ENUM_TRAITS_MIN_MAX_VALUE(importer::ImporterType,
+ importer::TYPE_UNKNOWN,
+ importer::TYPE_EDGE)
+#else
+IPC_ENUM_TRAITS_MIN_MAX_VALUE(importer::ImporterType,
+ importer::TYPE_UNKNOWN,
+ importer::TYPE_BOOKMARKS_FILE)
+#endif
+
+IPC_ENUM_TRAITS_MIN_MAX_VALUE(importer::ImportItem,
+ importer::NONE,
+ importer::ALL)
+
+IPC_STRUCT_TRAITS_BEGIN(importer::SourceProfile)
+ IPC_STRUCT_TRAITS_MEMBER(importer_name)
+ IPC_STRUCT_TRAITS_MEMBER(importer_type)
+ IPC_STRUCT_TRAITS_MEMBER(source_path)
+ IPC_STRUCT_TRAITS_MEMBER(app_path)
+ IPC_STRUCT_TRAITS_MEMBER(services_supported)
+ IPC_STRUCT_TRAITS_MEMBER(locale)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ImporterURLRow)
+ IPC_STRUCT_TRAITS_MEMBER(url)
+ IPC_STRUCT_TRAITS_MEMBER(title)
+ IPC_STRUCT_TRAITS_MEMBER(visit_count)
+ IPC_STRUCT_TRAITS_MEMBER(typed_count)
+ IPC_STRUCT_TRAITS_MEMBER(last_visit)
+ IPC_STRUCT_TRAITS_MEMBER(hidden)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ImportedBookmarkEntry)
+ IPC_STRUCT_TRAITS_MEMBER(in_toolbar)
+ IPC_STRUCT_TRAITS_MEMBER(is_folder)
+ IPC_STRUCT_TRAITS_MEMBER(url)
+ IPC_STRUCT_TRAITS_MEMBER(path)
+ IPC_STRUCT_TRAITS_MEMBER(title)
+ IPC_STRUCT_TRAITS_MEMBER(creation_time)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(favicon_base::FaviconUsageData)
+ IPC_STRUCT_TRAITS_MEMBER(favicon_url)
+ IPC_STRUCT_TRAITS_MEMBER(png_data)
+ IPC_STRUCT_TRAITS_MEMBER(urls)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(importer::SearchEngineInfo)
+ IPC_STRUCT_TRAITS_MEMBER(url)
+ IPC_STRUCT_TRAITS_MEMBER(keyword)
+ IPC_STRUCT_TRAITS_MEMBER(display_name)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ImporterAutofillFormDataEntry)
+ IPC_STRUCT_TRAITS_MEMBER(name)
+ IPC_STRUCT_TRAITS_MEMBER(value)
+ IPC_STRUCT_TRAITS_MEMBER(times_used)
+ IPC_STRUCT_TRAITS_MEMBER(first_used)
+ IPC_STRUCT_TRAITS_MEMBER(last_used)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(importer::ImporterIE7PasswordInfo)
+ IPC_STRUCT_TRAITS_MEMBER(url_hash)
+ IPC_STRUCT_TRAITS_MEMBER(encrypted_data)
+ IPC_STRUCT_TRAITS_MEMBER(date_created)
+IPC_STRUCT_TRAITS_END()
+
+#endif // CHROME_COMMON_IMPORTER_PROFILE_IMPORT_PROCESS_PARAM_TRAITS_MACROS_H_
diff --git a/chromium/chrome/common/importer/pstore_declarations.h b/chromium/chrome/common/importer/pstore_declarations.h
new file mode 100644
index 00000000000..74b5954da7c
--- /dev/null
+++ b/chromium/chrome/common/importer/pstore_declarations.h
@@ -0,0 +1,188 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_PSTORE_DECLARATIONS_H_
+#define CHROME_COMMON_IMPORTER_PSTORE_DECLARATIONS_H_
+
+#ifdef __PSTORE_H__
+#error Should not include pstore.h and this file simultaneously.
+#endif
+
+#include <ole2.h>
+
+// pstore.h is no longer shipped in the Windows 8 SDK. Define a minimal set
+// here.
+
+// These types are referenced in interfaces we use, but our code does not use
+// refer to these types, so simply make them opaque.
+class IEnumPStoreTypes;
+struct PST_ACCESSRULESET;
+struct PST_PROMPTINFO;
+struct PST_PROVIDERINFO;
+struct PST_TYPEINFO;
+
+EXTERN_C const IID IID_IPStore;
+EXTERN_C const IID IID_IEnumPStoreItems;
+
+typedef DWORD PST_KEY;
+typedef DWORD PST_ACCESSMODE;
+#define PST_E_OK _HRESULT_TYPEDEF_(0x00000000L)
+
+interface
+#ifdef __clang__
+ [[clang::lto_visibility_public]]
+#endif
+ IEnumPStoreItems : public IUnknown {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE Next(
+ DWORD celt,
+ LPWSTR __RPC_FAR *rgelt,
+ DWORD __RPC_FAR *pceltFetched) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE Skip(DWORD celt) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE Reset(void) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE Clone(
+ IEnumPStoreItems __RPC_FAR *__RPC_FAR *ppenum) = 0;
+};
+
+interface
+#ifdef __clang__
+ [[clang::lto_visibility_public]]
+#endif
+ IPStore : public IUnknown {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetInfo(
+ PST_PROVIDERINFO* __RPC_FAR *ppProperties) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetProvParam(
+ DWORD dwParam,
+ DWORD __RPC_FAR *pcbData,
+ BYTE __RPC_FAR *__RPC_FAR *ppbData,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE SetProvParam(
+ DWORD dwParam,
+ DWORD cbData,
+ BYTE __RPC_FAR *pbData,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE CreateType(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ PST_TYPEINFO* pInfo,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ PST_TYPEINFO* __RPC_FAR *ppInfo,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE DeleteType(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE CreateSubtype(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ const GUID __RPC_FAR *pSubtype,
+ PST_TYPEINFO* pInfo,
+ PST_ACCESSRULESET* pRules,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetSubtypeInfo(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ const GUID __RPC_FAR *pSubtype,
+ PST_TYPEINFO* __RPC_FAR *ppInfo,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE DeleteSubtype(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ const GUID __RPC_FAR *pSubtype,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE ReadAccessRuleset(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ const GUID __RPC_FAR *pSubtype,
+ PST_ACCESSRULESET* __RPC_FAR *ppRules,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE WriteAccessRuleset(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ const GUID __RPC_FAR *pSubtype,
+ PST_ACCESSRULESET* pRules,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE EnumTypes(
+ PST_KEY Key,
+ DWORD dwFlags,
+ IEnumPStoreTypes __RPC_FAR *__RPC_FAR *ppenum) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE EnumSubtypes(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pType,
+ DWORD dwFlags,
+ IEnumPStoreTypes __RPC_FAR *__RPC_FAR *ppenum) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE DeleteItem(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pItemType,
+ const GUID __RPC_FAR *pItemSubtype,
+ LPCWSTR szItemName,
+ PST_PROMPTINFO* pPromptInfo,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE ReadItem(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pItemType,
+ const GUID __RPC_FAR *pItemSubtype,
+ LPCWSTR szItemName,
+ DWORD __RPC_FAR *pcbData,
+ BYTE __RPC_FAR *__RPC_FAR *ppbData,
+ PST_PROMPTINFO* pPromptInfo,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE WriteItem(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pItemType,
+ const GUID __RPC_FAR *pItemSubtype,
+ LPCWSTR szItemName,
+ DWORD cbData,
+ BYTE __RPC_FAR *pbData,
+ PST_PROMPTINFO* pPromptInfo,
+ DWORD dwDefaultConfirmationStyle,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE OpenItem(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pItemType,
+ const GUID __RPC_FAR *pItemSubtype,
+ LPCWSTR szItemName,
+ PST_ACCESSMODE ModeFlags,
+ PST_PROMPTINFO* pPromptInfo,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE CloseItem(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pItemType,
+ const GUID __RPC_FAR *pItemSubtype,
+ LPCWSTR szItemName,
+ DWORD dwFlags) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE EnumItems(
+ PST_KEY Key,
+ const GUID __RPC_FAR *pItemType,
+ const GUID __RPC_FAR *pItemSubtype,
+ DWORD dwFlags,
+ IEnumPStoreItems __RPC_FAR *__RPC_FAR *ppenum) = 0;
+};
+
+#endif // CHROME_COMMON_IMPORTER_PSTORE_DECLARATIONS_H_
diff --git a/chromium/chrome/common/importer/safari_importer_utils.h b/chromium/chrome/common/importer/safari_importer_utils.h
new file mode 100644
index 00000000000..6885251a62b
--- /dev/null
+++ b/chromium/chrome/common/importer/safari_importer_utils.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_SAFARI_IMPORTER_UTILS_H_
+#define CHROME_COMMON_IMPORTER_SAFARI_IMPORTER_UTILS_H_
+
+#include <stdint.h>
+
+namespace base {
+class FilePath;
+}
+
+// Does this user account have a Safari Profile and if so, what items
+// are supported?
+// in: library_dir - ~/Library or a standin for testing purposes.
+// out: services_supported - the service supported for import.
+// Returns true if we can import the Safari profile.
+bool SafariImporterCanImport(const base::FilePath& library_dir,
+ uint16_t* services_supported);
+
+#endif // CHROME_COMMON_IMPORTER_SAFARI_IMPORTER_UTILS_H_
diff --git a/chromium/chrome/common/importer/safari_importer_utils.mm b/chromium/chrome/common/importer/safari_importer_utils.mm
new file mode 100644
index 00000000000..b69506abbae
--- /dev/null
+++ b/chromium/chrome/common/importer/safari_importer_utils.mm
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/safari_importer_utils.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "chrome/common/importer/importer_data_types.h"
+
+bool SafariImporterCanImport(const base::FilePath& library_dir,
+ uint16_t* services_supported) {
+ DCHECK(services_supported);
+ *services_supported = importer::NONE;
+
+ // Import features are toggled by the following:
+ // bookmarks import: existence of ~/Library/Safari/Bookmarks.plist file.
+ // history import: existence of ~/Library/Safari/History.plist file.
+ base::FilePath safari_dir = library_dir.Append("Safari");
+ base::FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist");
+ base::FilePath history_path = safari_dir.Append("History.plist");
+
+ if (base::PathExists(bookmarks_path))
+ *services_supported |= importer::FAVORITES;
+ if (base::PathExists(history_path))
+ *services_supported |= importer::HISTORY;
+
+ return *services_supported != importer::NONE;
+}
diff --git a/chromium/chrome/common/ini_parser.cc b/chromium/chrome/common/ini_parser.cc
new file mode 100644
index 00000000000..91f2d7ad021
--- /dev/null
+++ b/chromium/chrome/common/ini_parser.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/ini_parser.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/strings/string_tokenizer.h"
+
+INIParser::INIParser() : used_(false) {}
+
+INIParser::~INIParser() {}
+
+void INIParser::Parse(const std::string& content) {
+ DCHECK(!used_);
+ used_ = true;
+ base::StringTokenizer tokenizer(content, "\r\n");
+
+ std::string current_section;
+ while (tokenizer.GetNext()) {
+ std::string line = tokenizer.token();
+ if (line.empty()) {
+ // Skips the empty line.
+ continue;
+ }
+ if (line[0] == '#' || line[0] == ';') {
+ // This line is a comment.
+ continue;
+ }
+ if (line[0] == '[') {
+ // It is a section header.
+ current_section = line.substr(1);
+ size_t end = current_section.rfind(']');
+ if (end != std::string::npos)
+ current_section.erase(end);
+ } else {
+ std::string key, value;
+ size_t equal = line.find('=');
+ if (equal != std::string::npos) {
+ key = line.substr(0, equal);
+ value = line.substr(equal + 1);
+ HandleTriplet(current_section, key, value);
+ }
+ }
+ }
+}
+
+DictionaryValueINIParser::DictionaryValueINIParser() {}
+
+DictionaryValueINIParser::~DictionaryValueINIParser() {}
+
+void DictionaryValueINIParser::HandleTriplet(const std::string& section,
+ const std::string& key,
+ const std::string& value) {
+
+ // Checks whether the section and key contain a '.' character.
+ // Those sections and keys break DictionaryValue's path format when not
+ // using the *WithoutPathExpansion methods.
+ if (section.find('.') == std::string::npos &&
+ key.find('.') == std::string::npos)
+ root_.SetString(section + "." + key, value);
+}
diff --git a/chromium/chrome/common/ini_parser.h b/chromium/chrome/common/ini_parser.h
new file mode 100644
index 00000000000..2f8ef16079c
--- /dev/null
+++ b/chromium/chrome/common/ini_parser.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_INI_PARSER_H_
+#define CHROME_COMMON_INI_PARSER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/values.h"
+
+// Parses INI files in a string. Users should in inherit from this class.
+// This is a very basic INI parser with these characteristics:
+// - Ignores blank lines.
+// - Ignores comment lines beginning with '#' or ';'.
+// - Duplicate key names in the same section will simply cause repeated calls
+// to HandleTriplet with the same |section| and |key| parameters.
+// - No escape characters supported.
+// - Global properties result in calls to HandleTriplet with an empty string in
+// the |section| argument.
+// - Section headers begin with a '[' character. It is recommended, but
+// not required to close the header bracket with a ']' character. All
+// characters after a closing ']' character is ignored.
+// - Key value pairs are indicated with an '=' character. Whitespace is not
+// ignored. Quoting is not supported. Everything before the first '='
+// is considered the |key|, and everything after is the |value|.
+class INIParser {
+ public:
+ INIParser();
+ virtual ~INIParser();
+
+ // May only be called once per instance.
+ void Parse(const std::string& content);
+
+ private:
+ virtual void HandleTriplet(const std::string& section,
+ const std::string& key,
+ const std::string& value) = 0;
+
+ bool used_;
+};
+
+// Parsed values are stored as strings at the "section.key" path. Triplets with
+// |section| or |key| parameters containing '.' are ignored.
+class DictionaryValueINIParser : public INIParser {
+ public:
+ DictionaryValueINIParser();
+ ~DictionaryValueINIParser() override;
+
+ const base::DictionaryValue& root() const { return root_; }
+
+ private:
+ // INIParser implementation.
+ void HandleTriplet(const std::string& section,
+ const std::string& key,
+ const std::string& value) override;
+
+ base::DictionaryValue root_;
+
+ DISALLOW_COPY_AND_ASSIGN(DictionaryValueINIParser);
+};
+
+#endif // CHROME_COMMON_INI_PARSER_H_
diff --git a/chromium/chrome/common/ini_parser_unittest.cc b/chromium/chrome/common/ini_parser_unittest.cc
new file mode 100644
index 00000000000..b2a2cb26820
--- /dev/null
+++ b/chromium/chrome/common/ini_parser_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/ini_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct TestTriplet {
+ TestTriplet(const std::string& section,
+ const std::string& key,
+ const std::string& value)
+ : section(section),
+ key(key),
+ value(value) {
+ }
+
+ std::string section;
+ std::string key;
+ std::string value;
+};
+
+class TestINIParser : public INIParser {
+ public:
+ explicit TestINIParser(
+ const std::vector<TestTriplet>& expected_triplets)
+ : expected_triplets_(expected_triplets),
+ pair_i_(0) {
+ }
+ ~TestINIParser() override {}
+
+ size_t pair_i() {
+ return pair_i_;
+ }
+
+ private:
+ void HandleTriplet(const std::string& section,
+ const std::string& key,
+ const std::string& value) override {
+ EXPECT_EQ(expected_triplets_[pair_i_].section, section);
+ EXPECT_EQ(expected_triplets_[pair_i_].key, key);
+ EXPECT_EQ(expected_triplets_[pair_i_].value, value);
+ ++pair_i_;
+ }
+
+ std::vector<TestTriplet> expected_triplets_;
+ size_t pair_i_;
+};
+
+TEST(INIParserTest, BasicValid) {
+ std::vector<TestTriplet> expected_triplets;
+ expected_triplets.push_back(TestTriplet("section1", "key1", "value1"));
+ expected_triplets.push_back(TestTriplet("section1", "key2", "value2"));
+ expected_triplets.push_back(TestTriplet("section1", "key3", "value3"));
+ expected_triplets.push_back(TestTriplet("section2", "key4", "value4"));
+ expected_triplets.push_back(TestTriplet("section2", "key5",
+ "value=with=equals"));
+ expected_triplets.push_back(TestTriplet("section2", "key6", "value6"));
+ TestINIParser test_parser(expected_triplets);
+
+ test_parser.Parse(
+ "[section1]\n"
+ "key1=value1\n"
+ "key2=value2\r\n" // Testing DOS "\r\n" line endings.
+ "key3=value3\n"
+ "[section2\n" // Testing omitted closing bracket.
+ "key4=value4\r" // Testing "\r" line endings.
+ "key5=value=with=equals\n"
+ "key6=value6"); // Testing omitted final line ending.
+}
+
+TEST(INIParserTest, IgnoreBlankLinesAndComments) {
+ std::vector<TestTriplet> expected_triplets;
+ expected_triplets.push_back(TestTriplet("section1", "key1", "value1"));
+ expected_triplets.push_back(TestTriplet("section1", "key2", "value2"));
+ expected_triplets.push_back(TestTriplet("section1", "key3", "value3"));
+ expected_triplets.push_back(TestTriplet("section2", "key4", "value4"));
+ expected_triplets.push_back(TestTriplet("section2", "key5", "value5"));
+ expected_triplets.push_back(TestTriplet("section2", "key6", "value6"));
+ TestINIParser test_parser(expected_triplets);
+
+ test_parser.Parse(
+ "\n"
+ "[section1]\n"
+ "key1=value1\n"
+ "\n"
+ "\n"
+ "key2=value2\n"
+ "key3=value3\n"
+ "\n"
+ ";Comment1"
+ "\n"
+ "[section2]\n"
+ "key4=value4\n"
+ "#Comment2\n"
+ "key5=value5\n"
+ "\n"
+ "key6=value6\n");
+}
+
+TEST(INIParserTest, DictionaryValueINIParser) {
+ DictionaryValueINIParser test_parser;
+
+ test_parser.Parse(
+ "[section1]\n"
+ "key1=value1\n"
+ "key.2=value2\n"
+ "key3=va.lue3\n"
+ "[se.ction2]\n"
+ "key.4=value4\n"
+ "key5=value5\n");
+
+ const base::DictionaryValue& root = test_parser.root();
+ std::string value;
+ EXPECT_TRUE(root.GetString("section1.key1", &value));
+ EXPECT_EQ("value1", value);
+ EXPECT_FALSE(root.GetString("section1.key.2", &value));
+ EXPECT_TRUE(root.GetString("section1.key3", &value));
+ EXPECT_EQ("va.lue3", value);
+ EXPECT_FALSE(root.GetString("se.ction2.key.4", &value));
+ EXPECT_FALSE(root.GetString("se.ction2.key5", &value));
+}
+
+} // namespace
diff --git a/chromium/chrome/common/initialize_extensions_client.cc b/chromium/chrome/common/initialize_extensions_client.cc
new file mode 100644
index 00000000000..3e9d4cf1f1b
--- /dev/null
+++ b/chromium/chrome/common/initialize_extensions_client.cc
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/initialize_extensions_client.h"
+
+#include <memory>
+
+#include "base/no_destructor.h"
+#include "chrome/common/apps/platform_apps/chrome_apps_api_provider.h"
+#include "chrome/common/extensions/chrome_extensions_client.h"
+#include "extensions/common/extensions_client.h"
+
+void EnsureExtensionsClientInitialized() {
+ static bool initialized = false;
+
+ static base::NoDestructor<extensions::ChromeExtensionsClient>
+ extensions_client;
+
+ if (!initialized) {
+ initialized = true;
+ extensions_client->AddAPIProvider(
+ std::make_unique<chrome_apps::ChromeAppsAPIProvider>());
+ extensions::ExtensionsClient::Set(extensions_client.get());
+ }
+
+ // ExtensionsClient::Set() will early-out if the client was already set, so
+ // this allows us to check that this was the only site setting it.
+ DCHECK_EQ(extensions_client.get(), extensions::ExtensionsClient::Get())
+ << "ExtensionsClient should only be initialized through "
+ << "EnsureExtensionsClientInitialized() when using "
+ << "ChromeExtensionsClient.";
+}
diff --git a/chromium/chrome/common/initialize_extensions_client.h b/chromium/chrome/common/initialize_extensions_client.h
new file mode 100644
index 00000000000..188ea65a126
--- /dev/null
+++ b/chromium/chrome/common/initialize_extensions_client.h
@@ -0,0 +1,18 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_INITIALIZE_EXTENSIONS_CLIENT_H_
+#define CHROME_COMMON_INITIALIZE_EXTENSIONS_CLIENT_H_
+
+#include "extensions/buildflags/buildflags.h"
+
+#if !BUILDFLAG(ENABLE_EXTENSIONS)
+#error "Extensions must be enabled"
+#endif
+
+// Initializes the single instance of the ExtensionsClient. Safe to call
+// multiple times.
+void EnsureExtensionsClientInitialized();
+
+#endif // CHROME_COMMON_INITIALIZE_EXTENSIONS_CLIENT_H_
diff --git a/chromium/chrome/common/instant_mojom_traits.h b/chromium/chrome/common/instant_mojom_traits.h
new file mode 100644
index 00000000000..93ee65be138
--- /dev/null
+++ b/chromium/chrome/common/instant_mojom_traits.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// NOLINT(build/header_guard)
+// no-include-guard-because-multiply-included
+#include "chrome/common/search/instant_types.h"
+#include "chrome/common/search/ntp_logging_events.h"
+#include "components/favicon_base/favicon_types.h"
+#include "components/ntp_tiles/ntp_tile_impression.h"
+#include "components/ntp_tiles/tile_source.h"
+#include "components/ntp_tiles/tile_title_source.h"
+#include "components/ntp_tiles/tile_visual_type.h"
+#include "components/omnibox/common/omnibox_focus_state.h"
+#include "ipc/ipc_message_macros.h"
+
+IPC_ENUM_TRAITS_MAX_VALUE(OmniboxFocusState, OMNIBOX_FOCUS_STATE_LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(OmniboxFocusChangeReason,
+ OMNIBOX_FOCUS_CHANGE_REASON_LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(NTPLoggingEventType, NTP_EVENT_TYPE_LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(NTPSuggestionsLoggingEventType,
+ NTPSuggestionsLoggingEventType::kMaxValue)
+
+IPC_ENUM_TRAITS_MAX_VALUE(ntp_tiles::TileTitleSource,
+ ntp_tiles::TileTitleSource::LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(ntp_tiles::TileSource, ntp_tiles::TileSource::LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(ntp_tiles::TileVisualType, ntp_tiles::TILE_TYPE_MAX)
+
+IPC_ENUM_TRAITS_MAX_VALUE(ThemeBackgroundImageAlignment,
+ THEME_BKGRND_IMAGE_ALIGN_LAST)
+IPC_ENUM_TRAITS_MAX_VALUE(ThemeBackgroundImageTiling, THEME_BKGRND_IMAGE_LAST)
+IPC_ENUM_TRAITS_MAX_VALUE(favicon_base::IconType, favicon_base::IconType::kMax)
+
+IPC_STRUCT_TRAITS_BEGIN(ntp_tiles::NTPTileImpression)
+ IPC_STRUCT_TRAITS_MEMBER(index)
+ IPC_STRUCT_TRAITS_MEMBER(source)
+ IPC_STRUCT_TRAITS_MEMBER(title_source)
+ IPC_STRUCT_TRAITS_MEMBER(visual_type)
+ IPC_STRUCT_TRAITS_MEMBER(icon_type)
+ IPC_STRUCT_TRAITS_MEMBER(data_generation_time)
+ IPC_STRUCT_TRAITS_MEMBER(url_for_rappor)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(InstantMostVisitedItem)
+ IPC_STRUCT_TRAITS_MEMBER(url)
+ IPC_STRUCT_TRAITS_MEMBER(title)
+ IPC_STRUCT_TRAITS_MEMBER(favicon)
+ IPC_STRUCT_TRAITS_MEMBER(title_source)
+ IPC_STRUCT_TRAITS_MEMBER(source)
+ IPC_STRUCT_TRAITS_MEMBER(data_generation_time)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(InstantMostVisitedInfo)
+ IPC_STRUCT_TRAITS_MEMBER(items)
+ IPC_STRUCT_TRAITS_MEMBER(items_are_custom_links)
+ IPC_STRUCT_TRAITS_MEMBER(use_most_visited)
+ IPC_STRUCT_TRAITS_MEMBER(is_visible)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ThemeBackgroundInfo)
+ IPC_STRUCT_TRAITS_MEMBER(using_default_theme)
+ IPC_STRUCT_TRAITS_MEMBER(using_dark_colors)
+ IPC_STRUCT_TRAITS_MEMBER(custom_background_url)
+ IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_line_1)
+ IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_line_2)
+ IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_action_url)
+ IPC_STRUCT_TRAITS_MEMBER(collection_id)
+ IPC_STRUCT_TRAITS_MEMBER(background_color)
+ IPC_STRUCT_TRAITS_MEMBER(text_color)
+ IPC_STRUCT_TRAITS_MEMBER(text_color_light)
+ IPC_STRUCT_TRAITS_MEMBER(theme_id)
+ IPC_STRUCT_TRAITS_MEMBER(image_horizontal_alignment)
+ IPC_STRUCT_TRAITS_MEMBER(image_vertical_alignment)
+ IPC_STRUCT_TRAITS_MEMBER(image_tiling)
+ IPC_STRUCT_TRAITS_MEMBER(has_attribution)
+ IPC_STRUCT_TRAITS_MEMBER(logo_alternate)
+ IPC_STRUCT_TRAITS_MEMBER(has_theme_image)
+ IPC_STRUCT_TRAITS_MEMBER(theme_name)
+ IPC_STRUCT_TRAITS_MEMBER(color_id)
+ IPC_STRUCT_TRAITS_MEMBER(color_dark)
+ IPC_STRUCT_TRAITS_MEMBER(color_light)
+ IPC_STRUCT_TRAITS_MEMBER(color_picked)
+ IPC_STRUCT_TRAITS_MEMBER(logo_color)
+ IPC_STRUCT_TRAITS_MEMBER(shortcut_color)
+IPC_STRUCT_TRAITS_END()
diff --git a/chromium/chrome/common/logging_chrome.cc b/chromium/chrome/common/logging_chrome.cc
new file mode 100644
index 00000000000..56c9d572c3e
--- /dev/null
+++ b/chromium/chrome/common/logging_chrome.cc
@@ -0,0 +1,434 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+
+// Need to include this before most other files because it defines
+// IPC_MESSAGE_LOG_ENABLED. We need to use it to define
+// IPC_MESSAGE_MACROS_LOG_ENABLED so render_messages.h will generate the
+// ViewMsgLog et al. functions.
+#include "ipc/ipc_buildflags.h"
+
+// On Windows, the about:ipc dialog shows IPCs; on POSIX, we hook up a
+// logger in this file. (We implement about:ipc on Mac but implement
+// the loggers here anyway). We need to do this real early to be sure
+// IPC_MESSAGE_MACROS_LOG_ENABLED doesn't get undefined.
+#if defined(OS_POSIX) && BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+#define IPC_MESSAGE_MACROS_LOG_ENABLED
+#include "content/public/common/content_ipc_logging.h"
+#define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger) \
+ content::RegisterIPCLogger(msg_id, logger)
+#include "chrome/common/all_messages.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include "chrome/common/logging_chrome.h"
+
+#include <fstream> // NOLINT
+#include <memory> // NOLINT
+#include <string> // NOLINT
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/debugger.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/env_vars.h"
+#include "content/public/common/content_switches.h"
+#include "ipc/ipc_logging.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/constants/chromeos_switches.h"
+#endif
+
+#if defined(OS_WIN)
+#include <initguid.h>
+#include "base/logging_win.h"
+#include "base/syslog_logging.h"
+#include "chrome/common/win/eventlog_messages.h"
+#include "chrome/install_static/install_details.h"
+#endif
+
+namespace logging {
+namespace {
+
+// When true, this means that error dialogs should not be shown.
+bool dialogs_are_suppressed_ = false;
+ScopedLogAssertHandler* assert_handler_ = nullptr;
+
+// This should be true for exactly the period between the end of
+// InitChromeLogging() and the beginning of CleanupChromeLogging().
+bool chrome_logging_initialized_ = false;
+
+// Set if we called InitChromeLogging() but failed to initialize.
+bool chrome_logging_failed_ = false;
+
+// This should be true for exactly the period between the end of
+// InitChromeLogging() and the beginning of CleanupChromeLogging().
+bool chrome_logging_redirected_ = false;
+
+#if defined(OS_WIN)
+// {7FE69228-633E-4f06-80C1-527FEA23E3A7}
+const GUID kChromeTraceProviderName = {
+ 0x7fe69228, 0x633e, 0x4f06,
+ { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } };
+#endif
+
+// Assertion handler for logging errors that occur when dialogs are
+// silenced. To record a new error, pass the log string associated
+// with that error in the str parameter.
+NOINLINE void SilentRuntimeAssertHandler(const char* file,
+ int line,
+ const base::StringPiece message,
+ const base::StringPiece stack_trace) {
+ base::debug::BreakDebugger();
+}
+
+// Suppresses error/assertion dialogs and enables the logging of
+// those errors into silenced_errors_.
+void SuppressDialogs() {
+ if (dialogs_are_suppressed_)
+ return;
+
+ assert_handler_ =
+ new ScopedLogAssertHandler(base::Bind(SilentRuntimeAssertHandler));
+
+#if defined(OS_WIN)
+ UINT new_flags = SEM_FAILCRITICALERRORS |
+ SEM_NOGPFAULTERRORBOX |
+ SEM_NOOPENFILEERRORBOX;
+
+ // Preserve existing error mode, as discussed at http://t/dmea
+ UINT existing_flags = SetErrorMode(new_flags);
+ SetErrorMode(existing_flags | new_flags);
+#endif
+
+ dialogs_are_suppressed_ = true;
+}
+
+} // anonymous namespace
+
+LoggingDestination DetermineLoggingDestination(
+ const base::CommandLine& command_line) {
+// only use OutputDebugString in debug mode
+#ifdef NDEBUG
+ bool enable_logging = false;
+ const char *kInvertLoggingSwitch = switches::kEnableLogging;
+ const LoggingDestination kDefaultLoggingMode = LOG_TO_FILE;
+#else
+ bool enable_logging = true;
+ const char *kInvertLoggingSwitch = switches::kDisableLogging;
+ const LoggingDestination kDefaultLoggingMode = LOG_TO_ALL;
+#endif
+
+ if (command_line.HasSwitch(kInvertLoggingSwitch))
+ enable_logging = !enable_logging;
+
+ LoggingDestination log_mode;
+ if (enable_logging) {
+ // Let --enable-logging=stderr force only stderr, particularly useful for
+ // non-debug builds where otherwise you can't get logs to stderr at all.
+ if (command_line.GetSwitchValueASCII(switches::kEnableLogging) == "stderr")
+ log_mode = LOG_TO_SYSTEM_DEBUG_LOG | LOG_TO_STDERR;
+ else
+ log_mode = kDefaultLoggingMode;
+ } else {
+ log_mode = LOG_NONE;
+ }
+ return log_mode;
+}
+
+#if defined(OS_CHROMEOS)
+base::FilePath SetUpSymlinkIfNeeded(const base::FilePath& symlink_path,
+ bool new_log) {
+ DCHECK(!symlink_path.empty());
+ // For backward compatibility, set up a .../chrome symlink to
+ // .../chrome.LATEST as needed. This code needs to run only
+ // after the migration (i.e. the addition of chrome.LATEST).
+ if (symlink_path.Extension() == ".LATEST") {
+ base::FilePath extensionless_path = symlink_path.ReplaceExtension("");
+ base::FilePath target_path;
+ bool extensionless_symlink_exists =
+ base::ReadSymbolicLink(extensionless_path, &target_path);
+
+ if (target_path != symlink_path) {
+ // No link, or wrong link. Clean up. This should happen only once in
+ // each log directory after the OS version update, but some of those
+ // directories may not be accessed for a long time, so this code needs to
+ // stay in forever :/
+ if (extensionless_symlink_exists &&
+ !base::DeleteFile(extensionless_path, false)) {
+ DPLOG(WARNING) << "Cannot delete " << extensionless_path.value();
+ }
+ // After cleaning up, create the symlink.
+ if (!base::CreateSymbolicLink(symlink_path, extensionless_path)) {
+ DPLOG(ERROR) << "Cannot create " << extensionless_path.value();
+ }
+ }
+ }
+
+ // If not starting a new log, then just log through the existing symlink, but
+ // if the symlink doesn't exist, create it.
+ //
+ // If starting a new log, then rename the old symlink as
+ // symlink_path.PREVIOUS and make a new symlink to a fresh log file.
+
+ // Check for existence of the symlink.
+ base::FilePath target_path;
+ bool symlink_exists = base::ReadSymbolicLink(symlink_path, &target_path);
+
+ if (symlink_exists && !new_log)
+ return target_path;
+
+ // Remove any extension before time-stamping.
+ target_path = GenerateTimestampedName(symlink_path.RemoveExtension(),
+ base::Time::Now());
+
+ if (symlink_exists) {
+ base::FilePath previous_symlink_path =
+ symlink_path.ReplaceExtension(".PREVIOUS");
+ // Rename symlink to .PREVIOUS. This nukes an existing symlink just like
+ // the rename(2) syscall does.
+ if (!base::ReplaceFile(symlink_path, previous_symlink_path, nullptr)) {
+ DPLOG(WARNING) << "Cannot rename " << symlink_path.value() << " to "
+ << previous_symlink_path.value();
+ }
+ }
+ // If all went well, the symlink no longer exists. Recreate it.
+ base::FilePath relative_target_path = target_path.BaseName();
+ if (!base::CreateSymbolicLink(relative_target_path, symlink_path)) {
+ DPLOG(ERROR) << "Unable to create symlink " << symlink_path.value()
+ << " pointing at " << relative_target_path.value();
+ }
+ return target_path;
+}
+
+void RemoveSymlinkAndLog(const base::FilePath& link_path,
+ const base::FilePath& target_path) {
+ if (::unlink(link_path.value().c_str()) == -1)
+ DPLOG(WARNING) << "Unable to unlink symlink " << link_path.value();
+ if (::unlink(target_path.value().c_str()) == -1)
+ DPLOG(WARNING) << "Unable to unlink log file " << target_path.value();
+}
+
+base::FilePath GetSessionLogDir(const base::CommandLine& command_line) {
+ base::FilePath log_dir;
+ std::string log_dir_str;
+ std::unique_ptr<base::Environment> env(base::Environment::Create());
+ if (env->GetVar(env_vars::kSessionLogDir, &log_dir_str) &&
+ !log_dir_str.empty()) {
+ log_dir = base::FilePath(log_dir_str);
+ } else if (command_line.HasSwitch(chromeos::switches::kLoginProfile)) {
+ base::PathService::Get(chrome::DIR_USER_DATA, &log_dir);
+ base::FilePath profile_dir;
+ std::string login_profile_value =
+ command_line.GetSwitchValueASCII(chromeos::switches::kLoginProfile);
+ if (login_profile_value == chrome::kLegacyProfileDir ||
+ login_profile_value == chrome::kTestUserProfileDir) {
+ profile_dir = base::FilePath(login_profile_value);
+ } else {
+ // We could not use g_browser_process > profile_helper() here.
+ std::string profile_dir_str = chrome::kProfileDirPrefix;
+ profile_dir_str.append(login_profile_value);
+ profile_dir = base::FilePath(profile_dir_str);
+ }
+ log_dir = log_dir.Append(profile_dir);
+ }
+ return log_dir;
+}
+
+base::FilePath GetSessionLogFile(const base::CommandLine& command_line) {
+ return GetSessionLogDir(command_line)
+ .Append(GetLogFileName(command_line).BaseName());
+}
+
+#endif // defined(OS_CHROMEOS)
+
+void InitChromeLogging(const base::CommandLine& command_line,
+ OldFileDeletionState delete_old_log_file) {
+ DCHECK(!chrome_logging_initialized_) <<
+ "Attempted to initialize logging when it was already initialized.";
+ LoggingDestination logging_dest = DetermineLoggingDestination(command_line);
+ LogLockingState log_locking_state = LOCK_LOG_FILE;
+ base::FilePath log_path;
+#if defined(OS_CHROMEOS)
+ base::FilePath target_path;
+#endif
+
+ // Don't resolve the log path unless we need to. Otherwise we leave an open
+ // ALPC handle after sandbox lockdown on Windows.
+ if ((logging_dest & LOG_TO_FILE) != 0) {
+ log_path = GetLogFileName(command_line);
+
+#if defined(OS_CHROMEOS)
+ // For BWSI (Incognito) logins, we want to put the logs in the user
+ // profile directory that is created for the temporary session instead
+ // of in the system log directory, for privacy reasons.
+ if (command_line.HasSwitch(chromeos::switches::kGuestSession))
+ log_path = GetSessionLogFile(command_line);
+
+ // On ChromeOS we log to the symlink. We force creation of a new
+ // symlink if we've been asked to delete the old log, since that
+ // indicates the start of a new session.
+ target_path = SetUpSymlinkIfNeeded(
+ log_path, delete_old_log_file == DELETE_OLD_LOG_FILE);
+
+ // Because ChromeOS manages the move to a new session by redirecting
+ // the link, it shouldn't remove the old file in the logging code,
+ // since that will remove the newly created link instead.
+ delete_old_log_file = APPEND_TO_OLD_LOG_FILE;
+#endif // defined(OS_CHROMEOS)
+ } else {
+ log_locking_state = DONT_LOCK_LOG_FILE;
+ }
+
+ LoggingSettings settings;
+ settings.logging_dest = logging_dest;
+ settings.log_file_path = log_path.value().c_str();
+ settings.lock_log = log_locking_state;
+ settings.delete_old = delete_old_log_file;
+ bool success = InitLogging(settings);
+
+#if defined(OS_CHROMEOS)
+ if (!success) {
+ DPLOG(ERROR) << "Unable to initialize logging to " << log_path.value()
+ << " (which should be a link to " << target_path.value() << ")";
+ RemoveSymlinkAndLog(log_path, target_path);
+ chrome_logging_failed_ = true;
+ return;
+ }
+#else // defined(OS_CHROMEOS)
+ if (!success) {
+ DPLOG(ERROR) << "Unable to initialize logging to " << log_path.value();
+ chrome_logging_failed_ = true;
+ return;
+ }
+#endif // defined(OS_CHROMEOS)
+
+ // We call running in unattended mode "headless", and allow headless mode to
+ // be configured either by the Environment Variable or by the Command Line
+ // Switch. This is for automated test purposes.
+ std::unique_ptr<base::Environment> env(base::Environment::Create());
+ const bool is_headless = env->HasVar(env_vars::kHeadless) ||
+ command_line.HasSwitch(switches::kNoErrorDialogs);
+
+ // Show fatal log messages in a dialog in debug builds when not headless.
+ if (!is_headless)
+ SetShowErrorDialogs(true);
+
+ // we want process and thread IDs because we have a lot of things running
+ SetLogItems(true, // enable_process_id
+ true, // enable_thread_id
+ true, // enable_timestamp
+ false); // enable_tickcount
+
+ // Suppress system error dialogs when headless.
+ if (is_headless)
+ SuppressDialogs();
+
+ // Use a minimum log level if the command line asks for one. Ignore this
+ // switch if there's vlog level switch present too (as both of these switches
+ // refer to the same underlying log level, and the vlog level switch has
+ // already been processed inside InitLogging). If there is neither
+ // log level nor vlog level specified, then just leave the default level
+ // (INFO).
+ if (command_line.HasSwitch(switches::kLoggingLevel) &&
+ GetMinLogLevel() >= 0) {
+ std::string log_level =
+ command_line.GetSwitchValueASCII(switches::kLoggingLevel);
+ int level = 0;
+ if (base::StringToInt(log_level, &level) && level >= 0 &&
+ level < LOG_NUM_SEVERITIES) {
+ SetMinLogLevel(level);
+ } else {
+ DLOG(WARNING) << "Bad log level: " << log_level;
+ }
+ }
+
+#if defined(OS_WIN)
+ // Enable trace control and transport through event tracing for Windows.
+ LogEventProvider::Initialize(kChromeTraceProviderName);
+
+ // Enable logging to the Windows Event Log.
+ SetEventSource(base::UTF16ToASCII(
+ install_static::InstallDetails::Get().install_full_name()),
+ BROWSER_CATEGORY, MSG_LOG_MESSAGE);
+#endif
+
+ base::StatisticsRecorder::InitLogOnShutdown();
+
+ chrome_logging_initialized_ = true;
+}
+
+// This is a no-op, but we'll keep it around in case
+// we need to do more cleanup in the future.
+void CleanupChromeLogging() {
+ if (chrome_logging_failed_)
+ return; // We failed to initiailize logging, no cleanup.
+
+ DCHECK(chrome_logging_initialized_) <<
+ "Attempted to clean up logging when it wasn't initialized.";
+
+ CloseLogFile();
+
+ chrome_logging_initialized_ = false;
+ chrome_logging_redirected_ = false;
+}
+
+base::FilePath GetLogFileName(const base::CommandLine& command_line) {
+ std::string filename = command_line.GetSwitchValueASCII(switches::kLogFile);
+ if (filename.empty())
+ base::Environment::Create()->GetVar(env_vars::kLogFileName, &filename);
+ if (!filename.empty())
+ return base::FilePath::FromUTF8Unsafe(filename);
+
+ const base::FilePath log_filename(FILE_PATH_LITERAL("chrome_debug.log"));
+ base::FilePath log_path;
+
+ if (base::PathService::Get(chrome::DIR_LOGS, &log_path)) {
+ log_path = log_path.Append(log_filename);
+ return log_path;
+ } else {
+ // error with path service, just use some default file somewhere
+ return log_filename;
+ }
+}
+
+bool DialogsAreSuppressed() {
+ return dialogs_are_suppressed_;
+}
+
+#if defined(OS_CHROMEOS)
+base::FilePath GenerateTimestampedName(const base::FilePath& base_path,
+ base::Time timestamp) {
+ base::Time::Exploded time_deets;
+ timestamp.LocalExplode(&time_deets);
+ std::string suffix = base::StringPrintf("_%02d%02d%02d-%02d%02d%02d",
+ time_deets.year,
+ time_deets.month,
+ time_deets.day_of_month,
+ time_deets.hour,
+ time_deets.minute,
+ time_deets.second);
+ return base_path.InsertBeforeExtensionASCII(suffix);
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace logging
diff --git a/chromium/chrome/common/logging_chrome.h b/chromium/chrome/common/logging_chrome.h
new file mode 100644
index 00000000000..f727faa5f83
--- /dev/null
+++ b/chromium/chrome/common/logging_chrome.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_LOGGING_CHROME_H_
+#define CHROME_COMMON_LOGGING_CHROME_H_
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace base {
+class CommandLine;
+class FilePath;
+}
+
+namespace logging {
+
+// Call to initialize logging for Chrome. This sets up the chrome-specific
+// logfile naming scheme and might do other things like log modules and
+// setting levels in the future.
+//
+// The main process might want to delete any old log files on startup by
+// setting delete_old_log_file, but the renderer processes should not, or
+// they will delete each others' logs.
+//
+// XXX
+// Setting suppress_error_dialogs to true disables any dialogs that would
+// normally appear for assertions and crashes, and makes any catchable
+// errors (namely assertions) available via GetSilencedErrorCount()
+// and GetSilencedError().
+void InitChromeLogging(const base::CommandLine& command_line,
+ OldFileDeletionState delete_old_log_file);
+
+LoggingDestination DetermineLoggingDestination(
+ const base::CommandLine& command_line);
+
+#if defined(OS_CHROMEOS)
+// Point the logging symlink to the system log or the user session log.
+base::FilePath SetUpSymlinkIfNeeded(const base::FilePath& symlink_path,
+ bool new_log);
+
+// Remove the logging symlink.
+void RemoveSymlinkAndLog(const base::FilePath& link_path,
+ const base::FilePath& target_path);
+
+// Get the log file directory path.
+base::FilePath GetSessionLogDir(const base::CommandLine& command_line);
+
+// Get the log file location.
+base::FilePath GetSessionLogFile(const base::CommandLine& command_line);
+#endif
+
+// Call when done using logging for Chrome.
+void CleanupChromeLogging();
+
+// Returns the fully-qualified name of the log file.
+base::FilePath GetLogFileName(const base::CommandLine& command_line);
+
+// Returns true when error/assertion dialogs are not to be shown, false
+// otherwise.
+bool DialogsAreSuppressed();
+
+#if defined(OS_CHROMEOS)
+// Inserts timestamp before file extension (if any) in the form
+// "_yymmdd-hhmmss".
+base::FilePath GenerateTimestampedName(const base::FilePath& base_path,
+ base::Time timestamp);
+#endif // OS_CHROMEOS
+} // namespace logging
+
+#endif // CHROME_COMMON_LOGGING_CHROME_H_
diff --git a/chromium/chrome/common/mac/DEPS b/chromium/chrome/common/mac/DEPS
new file mode 100644
index 00000000000..d6b1434b64d
--- /dev/null
+++ b/chromium/chrome/common/mac/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/google_toolbox_for_mac/src",
+]
diff --git a/chromium/chrome/common/mac/OWNERS b/chromium/chrome/common/mac/OWNERS
new file mode 100644
index 00000000000..011c2b60a9e
--- /dev/null
+++ b/chromium/chrome/common/mac/OWNERS
@@ -0,0 +1,15 @@
+mark@chromium.org
+rsesek@chromium.org
+tapted@chromium.org
+thomasvl@chromium.org
+thakis@chromium.org
+
+per-file *_param_traits*.*=set noparent
+per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
+# COMPONENT: Internals>PlatformIntegration
diff --git a/chromium/chrome/common/mac/app_mode_chrome_locator.h b/chromium/chrome/common/mac/app_mode_chrome_locator.h
new file mode 100644
index 00000000000..1abe7e07015
--- /dev/null
+++ b/chromium/chrome/common/mac/app_mode_chrome_locator.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_APP_MODE_CHROME_LOCATOR_H_
+#define CHROME_COMMON_MAC_APP_MODE_CHROME_LOCATOR_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/strings/string16.h"
+
+@class NSString;
+
+namespace base {
+class FilePath;
+}
+
+namespace app_mode {
+
+// Given a bundle id, return the path of the corresponding bundle.
+// Returns true if the bundle was found, false otherwise.
+bool FindBundleById(NSString* bundle_id, base::FilePath* out_bundle);
+
+// Given the path to the Chrome bundle, and an optional framework version, read
+// the following information:
+// |executable_path| - Path to the Chrome executable.
+// |framework_path| - Path of the Chrome Framework.framework.
+// |framework_dylib_path| - Path to the Chrome Framework's shared library.
+// If |version_str| is not given, this will return this data for the current
+// Chrome version. Returns true if all information read successfully; false
+// otherwise.
+bool GetChromeBundleInfo(const base::FilePath& chrome_bundle,
+ const std::string& version_str,
+ base::FilePath* executable_path,
+ base::FilePath* framework_path,
+ base::FilePath* framework_dylib_path);
+
+} // namespace app_mode
+
+#endif // CHROME_COMMON_MAC_APP_MODE_CHROME_LOCATOR_H_
diff --git a/chromium/chrome/common/mac/app_mode_chrome_locator.mm b/chromium/chrome/common/mac/app_mode_chrome_locator.mm
new file mode 100644
index 00000000000..7e5f3fa0b7e
--- /dev/null
+++ b/chromium/chrome/common/mac/app_mode_chrome_locator.mm
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/common/mac/app_mode_chrome_locator.h"
+
+#import <AppKit/AppKit.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/optional.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/mac/app_mode_common.h"
+
+namespace app_mode {
+
+namespace {
+
+struct PathAndStructure {
+ NSString* framework_dylib_path; // weak
+ bool is_new_app_structure;
+};
+
+base::Optional<PathAndStructure> GetFrameworkDylibPathAndStructure(
+ NSString* bundle_path,
+ NSString* version) {
+ // NEW STYLE:
+ // Chromium.app/Contents/Frameworks/Chromium Framework.framework/
+ // Versions/<version>/Chromium Framework
+ NSString* path = [NSString pathWithComponents:@[
+ bundle_path, @"Contents", @"Frameworks", @(chrome::kFrameworkName),
+ @"Versions", version, @(chrome::kFrameworkExecutableName)
+ ]];
+
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path])
+ return PathAndStructure{path, true};
+
+ // OLD STYLE:
+ // Chromium.app/Contents/Versions/<version>/Chromium Framework.framework/
+ // Versions/A/Chromium Framework
+ path = [NSString pathWithComponents:@[
+ bundle_path, @"Contents", @"Versions", version, @(chrome::kFrameworkName),
+ @"Versions", @"A", @(chrome::kFrameworkExecutableName)
+ ]];
+
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path])
+ return PathAndStructure{path, false};
+
+ return base::nullopt;
+}
+
+} // namespace
+
+bool FindBundleById(NSString* bundle_id, base::FilePath* out_bundle) {
+ NSWorkspace* ws = [NSWorkspace sharedWorkspace];
+ NSString *bundlePath = [ws absolutePathForAppBundleWithIdentifier:bundle_id];
+ if (!bundlePath)
+ return false;
+
+ *out_bundle = base::mac::NSStringToFilePath(bundlePath);
+ return true;
+}
+
+bool GetChromeBundleInfo(const base::FilePath& chrome_bundle,
+ const std::string& version_str,
+ base::FilePath* executable_path,
+ base::FilePath* framework_path,
+ base::FilePath* framework_dylib_path) {
+ NSString* cr_bundle_path = base::mac::FilePathToNSString(chrome_bundle);
+ NSBundle* cr_bundle = [NSBundle bundleWithPath:cr_bundle_path];
+ if (!cr_bundle)
+ return false;
+
+ // Try to get the version requested, if present.
+ base::Optional<PathAndStructure> framework_path_and_structure;
+ if (!version_str.empty()) {
+ framework_path_and_structure = GetFrameworkDylibPathAndStructure(
+ cr_bundle_path, base::SysUTF8ToNSString(version_str));
+ }
+
+ // If the version requested is not present, or no specific version was
+ // requested, fall back to the "current" version. For new-style bundle
+ // structures, use the "Current" symlink. (This will intentionally return nil
+ // with the old bundle structure.)
+ //
+ // Note that the scenario where a specific version was requested but is not
+ // present is a "should not happen" scenario. Chromium, while it is running,
+ // maintains a link to the currently running version, and this function's
+ // caller checked to see if the Chromium was still running. However, even in
+ // this bizarre case, it's best to find _some_ Chromium.
+ if (!framework_path_and_structure) {
+ framework_path_and_structure =
+ GetFrameworkDylibPathAndStructure(cr_bundle_path, @"Current");
+ if (framework_path_and_structure) {
+ framework_path_and_structure->framework_dylib_path =
+ [framework_path_and_structure
+ ->framework_dylib_path stringByResolvingSymlinksInPath];
+ }
+ }
+
+ // At this point it is known that it is an old-style bundle structure (or a
+ // rather broken new-style bundle). Try explicitly specifying the version of
+ // the framework matching the outer bundle version.
+ if (!framework_path_and_structure) {
+ NSString* cr_version = base::mac::ObjCCast<NSString>([cr_bundle
+ objectForInfoDictionaryKey:app_mode::kCFBundleShortVersionStringKey]);
+ if (cr_version) {
+ framework_path_and_structure =
+ GetFrameworkDylibPathAndStructure(cr_bundle_path, cr_version);
+ }
+ }
+
+ if (!framework_path_and_structure)
+ return false;
+
+ // A few sanity checks.
+ BOOL is_directory;
+ BOOL exists = [[NSFileManager defaultManager]
+ fileExistsAtPath:framework_path_and_structure->framework_dylib_path
+ isDirectory:&is_directory];
+ if (!exists || is_directory)
+ return false;
+
+ NSString* cr_framework_path;
+
+ if (framework_path_and_structure->is_new_app_structure) {
+ // For the path to the framework version itself, remove the framework name.
+ cr_framework_path =
+ [framework_path_and_structure
+ ->framework_dylib_path stringByDeletingLastPathComponent];
+ } else {
+ // For the path to the framework itself, remove the framework name ...
+ cr_framework_path =
+ [framework_path_and_structure
+ ->framework_dylib_path stringByDeletingLastPathComponent];
+ // ... the "A" ...
+ cr_framework_path = [cr_framework_path stringByDeletingLastPathComponent];
+ // ... and the "Versions" directory.
+ cr_framework_path = [cr_framework_path stringByDeletingLastPathComponent];
+ }
+
+ // Everything is OK; copy the output parameters.
+ *executable_path = base::mac::NSStringToFilePath([cr_bundle executablePath]);
+ *framework_path = base::mac::NSStringToFilePath(cr_framework_path);
+ *framework_dylib_path = base::mac::NSStringToFilePath(
+ framework_path_and_structure->framework_dylib_path);
+ return true;
+}
+
+} // namespace app_mode
diff --git a/chromium/chrome/common/mac/app_mode_chrome_locator_browsertest.mm b/chromium/chrome/common/mac/app_mode_chrome_locator_browsertest.mm
new file mode 100644
index 00000000000..e38fa55ee0e
--- /dev/null
+++ b/chromium/chrome/common/mac/app_mode_chrome_locator_browsertest.mm
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/common/mac/app_mode_chrome_locator.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_constants.h"
+#include "components/version_info/version_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// This needs to be a browser test because it expects to find a Chrome.app
+// bundle in the output directory.
+
+// Return the path to the Chrome/Chromium app bundle compiled along with the
+// test executable.
+void GetChromeBundlePath(base::FilePath* chrome_bundle) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_MODULE, &path);
+ path = path.Append(chrome::kBrowserProcessExecutableName);
+ path = path.ReplaceExtension(base::FilePath::StringType("app"));
+ *chrome_bundle = path;
+}
+
+} // namespace
+
+TEST(ChromeLocatorTest, FindBundle) {
+ base::FilePath finder_bundle_path;
+ EXPECT_TRUE(
+ app_mode::FindBundleById(@"com.apple.finder", &finder_bundle_path));
+ EXPECT_TRUE(base::DirectoryExists(finder_bundle_path));
+}
+
+TEST(ChromeLocatorTest, FindNonExistentBundle) {
+ base::FilePath dummy;
+ EXPECT_FALSE(app_mode::FindBundleById(@"this.doesnt.exist", &dummy));
+}
+
+TEST(ChromeLocatorTest, GetNonExistentBundleInfo) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ base::FilePath executable_path;
+ base::FilePath framework_path;
+ base::FilePath framework_dylib_path;
+ EXPECT_FALSE(app_mode::GetChromeBundleInfo(temp_dir.GetPath(), std::string(),
+ &executable_path, &framework_path,
+ &framework_dylib_path));
+}
+
+TEST(ChromeLocatorTest, GetChromeBundleInfo) {
+ base::FilePath chrome_bundle_path;
+ GetChromeBundlePath(&chrome_bundle_path);
+ ASSERT_TRUE(base::DirectoryExists(chrome_bundle_path));
+
+ base::FilePath executable_path;
+ base::FilePath framework_path;
+ base::FilePath framework_dylib_path;
+ EXPECT_TRUE(app_mode::GetChromeBundleInfo(chrome_bundle_path, std::string(),
+ &executable_path, &framework_path,
+ &framework_dylib_path));
+ EXPECT_TRUE(base::PathExists(executable_path));
+ EXPECT_TRUE(base::DirectoryExists(framework_path));
+ EXPECT_TRUE(base::PathExists(framework_dylib_path));
+}
+
+TEST(ChromeLocatorTest, GetChromeBundleInfoWithLatestVersion) {
+ base::FilePath chrome_bundle_path;
+ GetChromeBundlePath(&chrome_bundle_path);
+ ASSERT_TRUE(base::DirectoryExists(chrome_bundle_path));
+
+ base::FilePath executable_path;
+ base::FilePath framework_path;
+ base::FilePath framework_dylib_path;
+ EXPECT_TRUE(app_mode::GetChromeBundleInfo(
+ chrome_bundle_path, version_info::GetVersionNumber(), &executable_path,
+ &framework_path, &framework_dylib_path));
+ EXPECT_TRUE(base::PathExists(executable_path));
+ EXPECT_TRUE(base::DirectoryExists(framework_path));
+ EXPECT_TRUE(base::PathExists(framework_dylib_path));
+}
+
+TEST(ChromeLocatorTest, GetChromeBundleInfoWithInvalidVersion) {
+ base::FilePath chrome_bundle_path;
+ GetChromeBundlePath(&chrome_bundle_path);
+ ASSERT_TRUE(base::DirectoryExists(chrome_bundle_path));
+
+ base::FilePath executable_path;
+ base::FilePath framework_path;
+ base::FilePath framework_dylib_path;
+ // This still passes because it should default to the latest version.
+ EXPECT_TRUE(app_mode::GetChromeBundleInfo(
+ chrome_bundle_path, std::string("invalid_version"), &executable_path,
+ &framework_path, &framework_dylib_path));
+ EXPECT_TRUE(base::PathExists(executable_path));
+ EXPECT_TRUE(base::DirectoryExists(framework_path));
+ EXPECT_TRUE(base::PathExists(framework_dylib_path));
+}
+
+TEST(ChromeLocatorTest, GetChromeBundleInfoWithPreviousVersion) {
+ base::FilePath chrome_bundle_path;
+ GetChromeBundlePath(&chrome_bundle_path);
+ ASSERT_TRUE(base::DirectoryExists(chrome_bundle_path));
+
+ // Make a symlink that pretends to be a previous version.
+ base::FilePath fake_version_directory = chrome_bundle_path.Append("Contents")
+ .Append("Frameworks")
+ .Append(chrome::kFrameworkName)
+ .Append("Versions")
+ .Append("previous_version");
+ EXPECT_TRUE(
+ base::CreateSymbolicLink(base::FilePath(version_info::GetVersionNumber()),
+ fake_version_directory));
+
+ base::FilePath executable_path;
+ base::FilePath framework_path;
+ base::FilePath framework_dylib_path;
+ EXPECT_TRUE(app_mode::GetChromeBundleInfo(
+ chrome_bundle_path, std::string("previous_version"), &executable_path,
+ &framework_path, &framework_dylib_path));
+ EXPECT_TRUE(base::PathExists(executable_path));
+ EXPECT_TRUE(base::DirectoryExists(framework_path));
+ EXPECT_TRUE(base::PathExists(framework_dylib_path));
+
+ base::DeleteFile(fake_version_directory, false);
+}
diff --git a/chromium/chrome/common/mac/app_mode_common.h b/chromium/chrome/common/mac/app_mode_common.h
new file mode 100644
index 00000000000..0fb689d325e
--- /dev/null
+++ b/chromium/chrome/common/mac/app_mode_common.h
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_APP_MODE_COMMON_H_
+#define CHROME_COMMON_MAC_APP_MODE_COMMON_H_
+
+#include <CoreServices/CoreServices.h>
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/strings/stringize_macros.h"
+
+#ifdef __OBJC__
+@class NSString;
+#else
+class NSString;
+#endif
+
+// This file contains constants, interfaces, etc. which are common to the
+// browser application and the app mode loader (a.k.a. shim).
+
+// The version of the ChromeAppModeInfo struct below. If the format of the
+// struct ever changes, be sure to update the APP_SHIM_VERSION_NUMBER here and
+// the corresponding line in //chrome/app/framework.order .
+#define APP_SHIM_VERSION_NUMBER 6
+
+// All the other macro magic to make APP_SHIM_VERSION_NUMBER usable.
+#define APP_MODE_CONCAT(a, b) a##b
+#define APP_MODE_CONCAT2(a, b) APP_MODE_CONCAT(a, b)
+#define APP_SHIM_ENTRY_POINT_NAME \
+ APP_MODE_CONCAT2(ChromeAppModeStart_v, APP_SHIM_VERSION_NUMBER)
+#define APP_SHIM_ENTRY_POINT_NAME_STRING STRINGIZE(APP_SHIM_ENTRY_POINT_NAME)
+
+namespace app_mode {
+
+// Mach message ID used by the shim to connect to Chrome.
+constexpr mach_msg_id_t kBootstrapMsgId = 'apps';
+
+// Name fragment of the Mach server endpoint published in the bootstrap
+// namespace. The full name is "<bundle-id>.apps.<profile_path_hash>".
+// <bundle-id> is the BaseBundleID() and <profile_path_hash> is an MD5 hash
+// of the full profile directory path.
+extern const char kAppShimBootstrapNameFragment[];
+
+// A symlink used to store the version string of the currently running Chrome.
+// The shim will read this to determine which version of the framework to load.
+extern const char kRunningChromeVersionSymlinkName[];
+
+// The process ID of the Chrome process that launched the app shim.
+// The presence of this switch instructs the app shim to send LaunchApp with
+// launch_now = false. This associates the shim without launching the app.
+extern const char kLaunchedByChromeProcessId[];
+
+// Indicates to the shim that it was launched for a test, so don't attempt to
+// launch Chrome.
+extern const char kLaunchedForTest[];
+
+// Indicates to the shim that this Chrome has rebuilt it once already, i.e. if
+// it fails to launch again, don't trigger another rebuild.
+extern const char kLaunchedAfterRebuild[];
+
+// Path to an app shim bundle. Indicates to Chrome that this shim attempted to
+// launch but failed.
+extern const char kAppShimError[];
+
+// Keys for specifying the file types handled by an app.
+extern NSString* const kCFBundleDocumentTypesKey;
+extern NSString* const kCFBundleTypeExtensionsKey;
+extern NSString* const kCFBundleTypeIconFileKey;
+extern NSString* const kCFBundleTypeNameKey;
+extern NSString* const kCFBundleTypeMIMETypesKey;
+extern NSString* const kCFBundleTypeRoleKey;
+extern NSString* const kBundleTypeRoleViewer;
+
+// The display name of the bundle as shown in Finder and the Dock. For localized
+// bundles, this overrides the bundle's file name.
+extern NSString* const kCFBundleDisplayNameKey;
+
+// When Chrome is built, any app bundles (e.g. the app shim template bundle)
+// will have their CFBundleShortVersionString set to the full version string of
+// that build. Since, this string is used by OSX when displaying an app bundle's
+// version, we override it in app shim bundles to show the app's version
+// instead.
+extern NSString* const kCFBundleShortVersionStringKey;
+
+// Key for the Chrome version that built the app shim bundle. This needs to be
+// added since we override CFBundleShortVersionString with the version of the
+// app.
+extern NSString* const kCrBundleVersionKey;
+
+// The key specifying whether the display name should be localized. This makes
+// Finder look in localization folders in the app bundle for a display name.
+// (e.g. Content/Resources/en.lproj/)
+extern NSString* const kLSHasLocalizedDisplayNameKey;
+
+// Key specifying whether or not high DPI display is supported at all. If this
+// is not set to true then all graphics (including system dialogs and display
+// property queries) will behave as though all displays are low DPI.
+extern NSString* const kNSHighResolutionCapableKey;
+
+// The key under which the browser's bundle ID will be stored in the
+// app mode launcher bundle's Info.plist.
+extern NSString* const kBrowserBundleIDKey;
+
+// Key for the shortcut ID.
+extern NSString* const kCrAppModeShortcutIDKey;
+
+// Key for the app's name.
+extern NSString* const kCrAppModeShortcutNameKey;
+
+// Key for the app's URL.
+extern NSString* const kCrAppModeShortcutURLKey;
+
+// Key for the app user data directory.
+extern NSString* const kCrAppModeUserDataDirKey;
+
+// Key for the app's extension path.
+extern NSString* const kCrAppModeProfileDirKey;
+
+// Key for the app's profile display name.
+extern NSString* const kCrAppModeProfileNameKey;
+
+// When the Chrome browser is run, it stores its location in the defaults
+// system using this key.
+extern NSString* const kLastRunAppBundlePathPrefsKey;
+
+// The key for the major and minor version of an app.
+extern NSString* const kCrAppModeMajorVersionKey;
+extern NSString* const kCrAppModeMinorVersionKey;
+
+// Placeholders used in the app mode loader bundle' Info.plist:
+extern NSString* const kShortcutIdPlaceholder; // Extension shortcut ID.
+extern NSString* const kShortcutNamePlaceholder; // Extension name.
+extern NSString* const kShortcutURLPlaceholder;
+// Bundle ID of the Chrome browser bundle.
+extern NSString* const kShortcutBrowserBundleIDPlaceholder;
+
+// The structure used to pass information from the app mode loader to the
+// (browser) framework via the entry point ChromeAppModeStart_vN.
+//
+// As long as the name of the entry point is kept constant and
+// APP_SHIM_VERSION_NUMBER does not change, the layout of this structure
+// **MUST NOT CHANGE**, even across Chromium versions. This implies that no
+// base/ or std:: types may be used in this structure.
+//
+// However, this structure *may* be changed as long as the
+// APP_SHIM_VERSION_NUMBER above is updated; don't forget to also update the
+// corresponding line in //chrome/app/framework.order .
+struct ChromeAppModeInfo {
+ // Original |argc| and |argv| of the App Mode shortcut.
+ int argc;
+ char** argv;
+
+ // Path of the Chromium Framework, as UTF-8. This will be the input to
+ // SetOverrideFrameworkBundlePath().
+ const char* chrome_framework_path;
+
+ // Path to Chromium app bundle, as UTF-8.
+ const char* chrome_outer_bundle_path;
+
+ // Information about the App Mode shortcut:
+
+ // Path to the App Mode Loader application bundle that launched the process,
+ // as UTF-8.
+ const char* app_mode_bundle_path;
+
+ // Short UTF-8 ID string, preferably derived from |app_mode_short_name|.
+ // Should be safe for the file system.
+ const char* app_mode_id;
+
+ // Unrestricted (e.g., several-word) UTF-8-encoded name for the shortcut.
+ const char* app_mode_name;
+
+ // URL for the shortcut. Must be a valid UTF-8-encoded URL.
+ const char* app_mode_url;
+
+ // Path to the app's user data directory, as UTF-8.
+ const char* user_data_dir;
+
+ // Directory of the profile associated with the app, as UTF-8.
+ const char* profile_dir;
+};
+
+} // namespace app_mode
+
+#endif // CHROME_COMMON_MAC_APP_MODE_COMMON_H_
diff --git a/chromium/chrome/common/mac/app_mode_common.mm b/chromium/chrome/common/mac/app_mode_common.mm
new file mode 100644
index 00000000000..80fffbd9032
--- /dev/null
+++ b/chromium/chrome/common/mac/app_mode_common.mm
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/mac/app_mode_common.h"
+
+#import <Foundation/Foundation.h>
+#include <type_traits>
+
+namespace app_mode {
+
+const char kAppShimBootstrapNameFragment[] = "apps";
+
+const char kRunningChromeVersionSymlinkName[] = "RunningChromeVersion";
+
+const char kLaunchedByChromeProcessId[] = "launched-by-chrome-process-id";
+const char kLaunchedForTest[] = "launched-for-test";
+const char kLaunchedAfterRebuild[] = "launched-after-rebuild";
+
+const char kAppShimError[] = "app-shim-error";
+
+NSString* const kCFBundleDocumentTypesKey = @"CFBundleDocumentTypes";
+NSString* const kCFBundleTypeExtensionsKey = @"CFBundleTypeExtensions";
+NSString* const kCFBundleTypeIconFileKey = @"CFBundleTypeIconFile";
+NSString* const kCFBundleTypeNameKey = @"CFBundleTypeName";
+NSString* const kCFBundleTypeMIMETypesKey = @"CFBundleTypeMIMETypes";
+NSString* const kCFBundleTypeRoleKey = @"CFBundleTypeRole";
+NSString* const kBundleTypeRoleViewer = @"Viewer";
+
+NSString* const kCFBundleDisplayNameKey = @"CFBundleDisplayName";
+NSString* const kCFBundleShortVersionStringKey = @"CFBundleShortVersionString";
+NSString* const kCrBundleVersionKey = @"CrBundleVersion";
+NSString* const kLSHasLocalizedDisplayNameKey = @"LSHasLocalizedDisplayName";
+NSString* const kNSHighResolutionCapableKey = @"NSHighResolutionCapable";
+NSString* const kBrowserBundleIDKey = @"CrBundleIdentifier";
+NSString* const kCrAppModeShortcutIDKey = @"CrAppModeShortcutID";
+NSString* const kCrAppModeShortcutNameKey = @"CrAppModeShortcutName";
+NSString* const kCrAppModeShortcutURLKey = @"CrAppModeShortcutURL";
+NSString* const kCrAppModeUserDataDirKey = @"CrAppModeUserDataDir";
+NSString* const kCrAppModeProfileDirKey = @"CrAppModeProfileDir";
+NSString* const kCrAppModeProfileNameKey = @"CrAppModeProfileName";
+NSString* const kCrAppModeMajorVersionKey = @"CrAppModeMajorVersionKey";
+NSString* const kCrAppModeMinorVersionKey = @"CrAppModeMinorVersionKey";
+
+NSString* const kLastRunAppBundlePathPrefsKey = @"LastRunAppBundlePath";
+
+NSString* const kShortcutIdPlaceholder = @"APP_MODE_SHORTCUT_ID";
+NSString* const kShortcutNamePlaceholder = @"APP_MODE_SHORTCUT_NAME";
+NSString* const kShortcutURLPlaceholder = @"APP_MODE_SHORTCUT_URL";
+NSString* const kShortcutBrowserBundleIDPlaceholder =
+ @"APP_MODE_BROWSER_BUNDLE_ID";
+
+static_assert(std::is_pod<ChromeAppModeInfo>::value == true,
+ "ChromeAppModeInfo must be a POD type");
+
+// ChromeAppModeInfo is built into the app_shim_loader binary that is not
+// updated with Chrome. If the layout of this structure changes, then Chrome
+// must rebuild all app shims. See https://crrev.com/362634 as an example.
+static_assert(
+ offsetof(ChromeAppModeInfo, argc) == 0x0 &&
+ offsetof(ChromeAppModeInfo, argv) == 0x8 &&
+ offsetof(ChromeAppModeInfo, chrome_framework_path) == 0x10 &&
+ offsetof(ChromeAppModeInfo, chrome_outer_bundle_path) == 0x18 &&
+ offsetof(ChromeAppModeInfo, app_mode_bundle_path) == 0x20 &&
+ offsetof(ChromeAppModeInfo, app_mode_id) == 0x28 &&
+ offsetof(ChromeAppModeInfo, app_mode_name) == 0x30 &&
+ offsetof(ChromeAppModeInfo, app_mode_url) == 0x38 &&
+ offsetof(ChromeAppModeInfo, user_data_dir) == 0x40 &&
+ offsetof(ChromeAppModeInfo, profile_dir) == 0x48,
+ "ChromeAppModeInfo layout has changed; bump the APP_SHIM_VERSION_NUMBER "
+ "in chrome/common/mac/app_mode_common.h. (And fix this static_assert.)");
+
+} // namespace app_mode
diff --git a/chromium/chrome/common/mac/app_shim_launch.h b/chromium/chrome/common/mac/app_shim_launch.h
new file mode 100644
index 00000000000..c2a8c58772c
--- /dev/null
+++ b/chromium/chrome/common/mac/app_shim_launch.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_APP_SHIM_LAUNCH_H_
+#define CHROME_COMMON_MAC_APP_SHIM_LAUNCH_H_
+
+namespace apps {
+
+enum AppShimLaunchType {
+ // Process the app shim's LaunchAppmessage and associate the shim with the
+ // given profile and app id.
+ APP_SHIM_LAUNCH_REGISTER_ONLY = 0,
+ // Do the above and launch the app.
+ APP_SHIM_LAUNCH_NORMAL,
+ // Counter and end marker.
+ APP_SHIM_LAUNCH_NUM_TYPES
+};
+
+enum AppShimLaunchResult {
+ // App launched successfully.
+ APP_SHIM_LAUNCH_SUCCESS = 0,
+ // There is already a host registered for this app.
+ APP_SHIM_LAUNCH_DUPLICATE_HOST,
+ // The profile was not found.
+ APP_SHIM_LAUNCH_PROFILE_NOT_FOUND,
+ // The app was not found.
+ APP_SHIM_LAUNCH_APP_NOT_FOUND,
+ // The profile was locked.
+ APP_SHIM_LAUNCH_PROFILE_LOCKED,
+ // The app shim did not pass code signing validation.
+ APP_SHIM_LAUNCH_FAILED_VALIDATION,
+ // Counter and end marker.
+ APP_SHIM_LAUNCH_NUM_RESULTS
+};
+
+enum AppShimFocusType {
+ // Just focus the app.
+ APP_SHIM_FOCUS_NORMAL = 0,
+ // Focus the app or launch it if it has no windows open.
+ APP_SHIM_FOCUS_REOPEN,
+ // Open the given file in the app.
+ APP_SHIM_FOCUS_OPEN_FILES,
+ // Counter and end marker.
+ APP_SHIM_FOCUS_NUM_TYPES
+};
+
+enum AppShimAttentionType {
+ // Removes any active attention request.
+ APP_SHIM_ATTENTION_CANCEL = 0,
+ // Bounces the shim in the dock briefly.
+ APP_SHIM_ATTENTION_INFORMATIONAL,
+ // Bounces the shim in the dock continuously.
+ APP_SHIM_ATTENTION_CRITICAL,
+ // Counter and end marker.
+ APP_SHIM_ATTENTION_NUM_TYPES
+};
+
+} // namespace apps
+
+#endif // CHROME_COMMON_MAC_APP_SHIM_LAUNCH_H_
diff --git a/chromium/chrome/common/mac/app_shim_param_traits.h b/chromium/chrome/common/mac/app_shim_param_traits.h
new file mode 100644
index 00000000000..0cbae58f299
--- /dev/null
+++ b/chromium/chrome/common/mac/app_shim_param_traits.h
@@ -0,0 +1,26 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_APP_SHIM_PARAM_TRAITS_H_
+#define CHROME_COMMON_MAC_APP_SHIM_PARAM_TRAITS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "chrome/common/mac/app_shim_launch.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/param_traits_macros.h"
+
+IPC_ENUM_TRAITS_MAX_VALUE(apps::AppShimLaunchType,
+ apps::APP_SHIM_LAUNCH_NUM_TYPES - 1)
+IPC_ENUM_TRAITS_MAX_VALUE(apps::AppShimLaunchResult,
+ apps::APP_SHIM_LAUNCH_NUM_RESULTS - 1)
+IPC_ENUM_TRAITS_MAX_VALUE(apps::AppShimFocusType,
+ apps::APP_SHIM_FOCUS_NUM_TYPES - 1)
+IPC_ENUM_TRAITS_MAX_VALUE(apps::AppShimAttentionType,
+ apps::APP_SHIM_ATTENTION_NUM_TYPES - 1)
+
+#endif // CHROME_COMMON_MAC_APP_SHIM_PARAM_TRAITS_H_
diff --git a/chromium/chrome/common/mac/launchd.h b/chromium/chrome/common/mac/launchd.h
new file mode 100644
index 00000000000..2eb4dad5d1b
--- /dev/null
+++ b/chromium/chrome/common/mac/launchd.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_LAUNCHD_H_
+#define CHROME_COMMON_MAC_LAUNCHD_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "chrome/common/mac/service_management.h"
+
+class Launchd {
+ public:
+ enum Type {
+ Agent, // LaunchAgent
+ Daemon // LaunchDaemon
+ };
+
+ // Domains map to NSSearchPathDomainMask so Foundation does not need to be
+ // included.
+ enum Domain {
+ User = 1, // ~/Library/Launch*
+ Local = 2, // /Library/Launch*
+ Network = 4, // /Network/Library/Launch*
+ System = 8 // /System/Library/Launch*
+ };
+
+ // TODO(dmaclach): Get rid of this pseudo singleton, and inject it
+ // appropriately wherever it is used.
+ // http://crbug.com/76925
+ static Launchd* GetInstance();
+
+ virtual ~Launchd();
+
+ virtual bool GetJobInfo(const std::string& label,
+ mac::services::JobInfo* info);
+
+ // Remove a launchd process from launchd.
+ virtual bool RemoveJob(const std::string& label);
+
+ // Used by a process controlled by launchd to restart itself.
+ // |session_type| can be "Aqua", "LoginWindow", "Background", "StandardIO" or
+ // "System".
+ // RestartLaunchdJob starts up a separate process to tell launchd to
+ // send this process a SIGTERM. This call will return, but a SIGTERM will be
+ // received shortly.
+ virtual bool RestartJob(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFStringRef session_type);
+
+ // Read a launchd plist from disk.
+ // |name| should not have an extension.
+ virtual CFMutableDictionaryRef CreatePlistFromFile(Domain domain,
+ Type type,
+ CFStringRef name);
+ // Write a launchd plist to disk.
+ // |name| should not have an extension.
+ virtual bool WritePlistToFile(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFDictionaryRef dict);
+
+ // Delete a launchd plist.
+ // |name| should not have an extension.
+ virtual bool DeletePlist(Domain domain, Type type, CFStringRef name);
+
+ // TODO(dmaclach): remove this once http://crbug.com/76925 is fixed.
+ // Scaffolding for doing unittests with our singleton.
+ static void SetInstance(Launchd* instance);
+ class ScopedInstance {
+ public:
+ explicit ScopedInstance(Launchd* instance) {
+ Launchd::SetInstance(instance);
+ }
+ ~ScopedInstance() {
+ Launchd::SetInstance(NULL);
+ }
+ };
+
+ protected:
+ Launchd() { }
+
+ private:
+ // TODO(dmaclach): remove this once http://crbug.com/76925 is fixed.
+ // Scaffolding for doing unittests with our singleton.
+ friend struct base::DefaultSingletonTraits<Launchd>;
+ static Launchd* g_instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(Launchd);
+};
+
+#endif // CHROME_COMMON_MAC_LAUNCHD_H_
diff --git a/chromium/chrome/common/mac/launchd.mm b/chromium/chrome/common/mac/launchd.mm
new file mode 100644
index 00000000000..5eab6f98467
--- /dev/null
+++ b/chromium/chrome/common/mac/launchd.mm
@@ -0,0 +1,165 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/mac/launchd.h"
+
+#import <Foundation/Foundation.h>
+#include <launch.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/process/launch.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/common/mac/service_management.h"
+
+namespace {
+
+NSString* SanitizeShellArgument(NSString* arg) {
+ if (!arg) {
+ return nil;
+ }
+ NSString *sanitize = [arg stringByReplacingOccurrencesOfString:@"'"
+ withString:@"'\''"];
+ return [NSString stringWithFormat:@"'%@'", sanitize];
+}
+
+NSURL* GetPlistURL(Launchd::Domain domain,
+ Launchd::Type type,
+ CFStringRef name) {
+ NSArray* library_paths =
+ NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, domain, YES);
+ DCHECK_EQ([library_paths count], 1U);
+ NSString* library_path = [library_paths objectAtIndex:0];
+
+ NSString *launch_dir_name = (type == Launchd::Daemon) ? @"LaunchDaemons"
+ : @"LaunchAgents";
+ NSString* launch_dir =
+ [library_path stringByAppendingPathComponent:launch_dir_name];
+
+ NSError* err;
+ if (![[NSFileManager defaultManager] createDirectoryAtPath:launch_dir
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&err]) {
+ DLOG(ERROR) << "GetPlistURL " << base::mac::NSToCFCast(err);
+ return nil;
+ }
+
+ NSString* plist_file_path =
+ [launch_dir stringByAppendingPathComponent:base::mac::CFToNSCast(name)];
+ plist_file_path = [plist_file_path stringByAppendingPathExtension:@"plist"];
+ return [NSURL fileURLWithPath:plist_file_path isDirectory:NO];
+}
+
+} // namespace
+
+static_assert(static_cast<int>(Launchd::User) ==
+ static_cast<int>(NSUserDomainMask),
+ "NSUserDomainMask value changed");
+static_assert(static_cast<int>(Launchd::Local) ==
+ static_cast<int>(NSLocalDomainMask),
+ "NSLocalDomainMask value changed");
+static_assert(static_cast<int>(Launchd::Network) ==
+ static_cast<int>(NSNetworkDomainMask),
+ "NSNetworkDomainMask value changed");
+static_assert(static_cast<int>(Launchd::System) ==
+ static_cast<int>(NSSystemDomainMask),
+ "NSSystemDomainMask value changed");
+
+Launchd* Launchd::g_instance_ = NULL;
+
+Launchd* Launchd::GetInstance() {
+ if (!g_instance_) {
+ g_instance_ = base::Singleton<Launchd>::get();
+ }
+ return g_instance_;
+}
+
+void Launchd::SetInstance(Launchd* instance) {
+ if (instance) {
+ CHECK(!g_instance_);
+ }
+ g_instance_ = instance;
+}
+
+Launchd::~Launchd() { }
+
+bool Launchd::GetJobInfo(const std::string& label,
+ mac::services::JobInfo* info) {
+ return mac::services::GetJobInfo(label, info);
+}
+
+bool Launchd::RemoveJob(const std::string& label) {
+ return mac::services::RemoveJob(label);
+}
+
+bool Launchd::RestartJob(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFStringRef cf_session_type) {
+ @autoreleasepool {
+ NSURL* url = GetPlistURL(domain, type, name);
+ NSString* ns_path = [url path];
+ ns_path = SanitizeShellArgument(ns_path);
+ const char* file_path = [ns_path fileSystemRepresentation];
+
+ NSString* ns_session_type =
+ SanitizeShellArgument(base::mac::CFToNSCast(cf_session_type));
+ if (!file_path || !ns_session_type) {
+ return false;
+ }
+
+ std::vector<std::string> argv;
+ argv.push_back("/bin/bash");
+ argv.push_back("--noprofile");
+ argv.push_back("-c");
+ std::string command =
+ base::StringPrintf("/bin/launchctl unload -S %s %s;"
+ "/bin/launchctl load -S %s %s;",
+ [ns_session_type UTF8String], file_path,
+ [ns_session_type UTF8String], file_path);
+ argv.push_back(command);
+
+ base::LaunchOptions options;
+ options.new_process_group = true;
+ return base::LaunchProcess(argv, options).IsValid();
+ }
+}
+
+CFMutableDictionaryRef Launchd::CreatePlistFromFile(Domain domain,
+ Type type,
+ CFStringRef name) {
+ @autoreleasepool {
+ NSURL* ns_url = GetPlistURL(domain, type, name);
+ NSMutableDictionary* plist =
+ [[NSMutableDictionary alloc] initWithContentsOfURL:ns_url];
+ return base::mac::NSToCFCast(plist);
+ }
+}
+
+bool Launchd::WritePlistToFile(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFDictionaryRef dict) {
+ @autoreleasepool {
+ NSURL* ns_url = GetPlistURL(domain, type, name);
+ return [base::mac::CFToNSCast(dict) writeToURL:ns_url atomically:YES];
+ }
+}
+
+bool Launchd::DeletePlist(Domain domain, Type type, CFStringRef name) {
+ @autoreleasepool {
+ NSURL* ns_url = GetPlistURL(domain, type, name);
+ NSError* err = nil;
+ if (![[NSFileManager defaultManager] removeItemAtPath:[ns_url path]
+ error:&err]) {
+ if ([err code] != NSFileNoSuchFileError) {
+ DLOG(ERROR) << "DeletePlist: " << base::mac::NSToCFCast(err);
+ }
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/chromium/chrome/common/mac/mock_launchd.h b/chromium/chrome/common/mac/mock_launchd.h
new file mode 100644
index 00000000000..9909703e1d0
--- /dev/null
+++ b/chromium/chrome/common/mac/mock_launchd.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_MOCK_LAUNCHD_H_
+#define CHROME_COMMON_MAC_MOCK_LAUNCHD_H_
+
+#include <launch.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_refptr.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/multi_process_lock.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+// TODO(dmaclach): Write this in terms of a real mock.
+// http://crbug.com/76923
+class MockLaunchd : public Launchd {
+ public:
+ static bool MakeABundle(const base::FilePath& dst,
+ const std::string& name,
+ base::FilePath* bundle_root,
+ base::FilePath* executable);
+
+ MockLaunchd(const base::FilePath& file,
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ base::OnceClosure quit_closure,
+ bool as_service);
+ ~MockLaunchd() override;
+
+ bool GetJobInfo(const std::string& label,
+ mac::services::JobInfo* info) override;
+ bool RemoveJob(const std::string& label) override;
+ bool RestartJob(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFStringRef session_type) override;
+ CFMutableDictionaryRef CreatePlistFromFile(Domain domain,
+ Type type,
+ CFStringRef name) override;
+ bool WritePlistToFile(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFDictionaryRef dict) override;
+ bool DeletePlist(Domain domain, Type type, CFStringRef name) override;
+
+ void SignalReady();
+
+ bool restart_called() const { return restart_called_; }
+ bool remove_called() const { return remove_called_; }
+ bool checkin_called() const { return checkin_called_; }
+ bool write_called() const { return write_called_; }
+ bool delete_called() const { return delete_called_; }
+
+ private:
+ base::FilePath file_;
+ std::string pipe_name_;
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+ base::OnceClosure quit_closure_;
+ std::unique_ptr<MultiProcessLock> running_lock_;
+ bool as_service_;
+ bool restart_called_;
+ bool remove_called_;
+ bool checkin_called_;
+ bool write_called_;
+ bool delete_called_;
+};
+
+#endif // CHROME_COMMON_MAC_MOCK_LAUNCHD_H_
diff --git a/chromium/chrome/common/mac/mock_launchd.mm b/chromium/chrome/common/mac/mock_launchd.mm
new file mode 100644
index 00000000000..ac3b913b974
--- /dev/null
+++ b/chromium/chrome/common/mac/mock_launchd.mm
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/mac/mock_launchd.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <errno.h>
+#include <stddef.h>
+#include <sys/un.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/service_process_util.h"
+#include "components/version_info/version_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// static
+bool MockLaunchd::MakeABundle(const base::FilePath& dst,
+ const std::string& name,
+ base::FilePath* bundle_root,
+ base::FilePath* executable) {
+ *bundle_root = dst.Append(name + std::string(".app"));
+ base::FilePath contents = bundle_root->AppendASCII("Contents");
+ base::FilePath mac_os = contents.AppendASCII("MacOS");
+ *executable = mac_os.Append(name);
+ base::FilePath info_plist = contents.Append("Info.plist");
+
+ if (!base::CreateDirectory(mac_os)) {
+ return false;
+ }
+ const char* data = "#! testbundle\n";
+ int len = strlen(data);
+ if (base::WriteFile(*executable, data, len) != len) {
+ return false;
+ }
+ if (chmod(executable->value().c_str(), 0555) != 0) {
+ return false;
+ }
+
+ const char info_plist_format[] =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
+ "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<dict>\n"
+ " <key>CFBundleDevelopmentRegion</key>\n"
+ " <string>English</string>\n"
+ " <key>CFBundleExecutable</key>\n"
+ " <string>%s</string>\n"
+ " <key>CFBundleIdentifier</key>\n"
+ " <string>com.test.%s</string>\n"
+ " <key>CFBundleInfoDictionaryVersion</key>\n"
+ " <string>6.0</string>\n"
+ " <key>CFBundleShortVersionString</key>\n"
+ " <string>%s</string>\n"
+ " <key>CFBundleVersion</key>\n"
+ " <string>1</string>\n"
+ "</dict>\n"
+ "</plist>\n";
+ std::string info_plist_data =
+ base::StringPrintf(info_plist_format, name.c_str(), name.c_str(),
+ version_info::GetVersionNumber().c_str());
+ len = info_plist_data.length();
+ if (base::WriteFile(info_plist, info_plist_data.c_str(), len) != len) {
+ return false;
+ }
+ const UInt8* bundle_root_path =
+ reinterpret_cast<const UInt8*>(bundle_root->value().c_str());
+ base::ScopedCFTypeRef<CFURLRef> url(CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault, bundle_root_path, bundle_root->value().length(),
+ true));
+ base::ScopedCFTypeRef<CFBundleRef> bundle(
+ CFBundleCreate(kCFAllocatorDefault, url));
+ return bundle.get();
+}
+
+MockLaunchd::MockLaunchd(
+ const base::FilePath& file,
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ base::OnceClosure quit_closure,
+ bool as_service)
+ : file_(file),
+ pipe_name_(GetServiceProcessServerName()),
+ main_task_runner_(std::move(main_task_runner)),
+ quit_closure_(std::move(quit_closure)),
+ as_service_(as_service),
+ restart_called_(false),
+ remove_called_(false),
+ checkin_called_(false),
+ write_called_(false),
+ delete_called_(false) {}
+
+MockLaunchd::~MockLaunchd() {}
+
+bool MockLaunchd::GetJobInfo(const std::string& label,
+ mac::services::JobInfo* info) {
+ if (!as_service_) {
+ std::unique_ptr<MultiProcessLock> running_lock(
+ TakeNamedLock(pipe_name_, false));
+ if (running_lock.get())
+ return false;
+ }
+
+ info->program = file_.value();
+ info->pid = base::GetCurrentProcId();
+ return true;
+}
+
+bool MockLaunchd::RemoveJob(const std::string& label) {
+ remove_called_ = true;
+ std::move(quit_closure_).Run();
+ return true;
+}
+
+bool MockLaunchd::RestartJob(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFStringRef session_type) {
+ restart_called_ = true;
+ std::move(quit_closure_).Run();
+ return true;
+}
+
+CFMutableDictionaryRef MockLaunchd::CreatePlistFromFile(Domain domain,
+ Type type,
+ CFStringRef name) {
+ NSString* ns_program = base::SysUTF8ToNSString(file_.value());
+
+ NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithDictionary:@{
+ @LAUNCH_JOBKEY_PROGRAM : ns_program,
+ @LAUNCH_JOBKEY_PROGRAMARGUMENTS : @[ ns_program ],
+ }];
+
+ // Callers expect to be given a reference but dictionaryWithDictionary: is
+ // autoreleased, so it's necessary to do a manual retain here.
+ return base::mac::NSToCFCast([dict retain]);
+}
+
+bool MockLaunchd::WritePlistToFile(Domain domain,
+ Type type,
+ CFStringRef name,
+ CFDictionaryRef dict) {
+ write_called_ = true;
+ return true;
+}
+
+bool MockLaunchd::DeletePlist(Domain domain, Type type, CFStringRef name) {
+ delete_called_ = true;
+ return true;
+}
+
+void MockLaunchd::SignalReady() {
+ ASSERT_TRUE(as_service_);
+ running_lock_.reset(TakeNamedLock(pipe_name_, true));
+}
diff --git a/chromium/chrome/common/mac/service_management.h b/chromium/chrome/common/mac/service_management.h
new file mode 100644
index 00000000000..8867df6fe63
--- /dev/null
+++ b/chromium/chrome/common/mac/service_management.h
@@ -0,0 +1,72 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_SERVICE_MANAGEMENT_H_
+#define CHROME_COMMON_MAC_SERVICE_MANAGEMENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+
+namespace mac {
+namespace services {
+
+struct JobInfo {
+ JobInfo();
+ JobInfo(const JobInfo& other);
+ ~JobInfo();
+
+ std::string program;
+ base::Optional<int> pid;
+};
+
+struct JobCheckinInfo {
+ JobCheckinInfo();
+ JobCheckinInfo(const JobCheckinInfo& info);
+ ~JobCheckinInfo();
+
+ std::string program;
+};
+
+struct JobOptions {
+ JobOptions();
+ JobOptions(const JobOptions& other);
+ ~JobOptions();
+
+ std::string label;
+ // See launchd.plist(5) for details about these two fields. In short:
+ // - executable_path, if non-empty, is the absolute path to the executable
+ // for this job;
+ // - arguments[0] is the absolute *or relative* path to the executable if
+ // executable_path is empty; in either case, all of arguments is passed
+ // directly as argv to the job, meaning arguments[0] becomes argv[0]
+ // There are important caveats about the argument vector documented in the
+ // launchd.plist(5) man page.
+ std::string executable_path;
+ std::vector<std::string> arguments;
+
+ // See launchd.plist(5) "MachServices" for details about this field. The
+ // mach_service_ field corresponds to a key in the MachServices dictionary,
+ // whose value will be YES.
+ std::string mach_service_name;
+
+ // Whether to run this job immediately once it is loaded.
+ bool run_at_load;
+
+ // Whether to restart the job whenever it exits successfully. This also
+ // implicitly limits the job to interactive sessions only (i.e., the job will
+ // not run in system sessions).
+ bool auto_launch;
+};
+
+bool GetJobInfo(const std::string& label, JobInfo* info);
+
+bool SubmitJob(const JobOptions& options);
+bool RemoveJob(const std::string& label);
+
+} // namespace services
+} // namespace mac
+
+#endif // CHROME_COMMON_MAC_SERVICE_MANAGEMENT_H_
diff --git a/chromium/chrome/common/mac/service_management.mm b/chromium/chrome/common/mac/service_management.mm
new file mode 100644
index 00000000000..20fa32749f7
--- /dev/null
+++ b/chromium/chrome/common/mac/service_management.mm
@@ -0,0 +1,217 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/mac/service_management.h"
+
+#import <CoreServices/CoreServices.h>
+#import <Foundation/Foundation.h>
+#import <ServiceManagement/ServiceManagement.h>
+
+#include <errno.h>
+#include <launch.h>
+
+#include "base/compiler_specific.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/macros.h"
+#include "base/strings/sys_string_conversions.h"
+
+// This entire file is written in terms of the launch_data_t API, which is
+// deprecated with no replacement, so just ignore the warnings for now.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+namespace {
+
+class ScopedLaunchData {
+ public:
+ explicit ScopedLaunchData(launch_data_type_t type)
+ : data_(launch_data_alloc(type)) {}
+ explicit ScopedLaunchData(launch_data_t data) : data_(data) {}
+ ScopedLaunchData(ScopedLaunchData&& other) : data_(other.release()) {}
+ ~ScopedLaunchData() { reset(); }
+
+ void reset() {
+ if (data_)
+ launch_data_free(data_);
+ data_ = nullptr;
+ }
+
+ launch_data_t release() WARN_UNUSED_RESULT {
+ launch_data_t val = data_;
+ data_ = nullptr;
+ return val;
+ }
+
+ launch_data_t get() { return data_; }
+ operator launch_data_t() const { return data_; }
+ operator bool() const { return !!data_; }
+
+ private:
+ launch_data_t data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedLaunchData);
+};
+
+ScopedLaunchData SendLaunchMessage(ScopedLaunchData&& msg) {
+ return ScopedLaunchData(launch_msg(msg));
+}
+
+ScopedLaunchData LaunchDataFromString(const std::string& string) {
+ ScopedLaunchData result(LAUNCH_DATA_STRING);
+ // launch_data_set_string() will make a copy of the passed-in string.
+ launch_data_set_string(result, string.c_str());
+ return result;
+}
+
+int ErrnoFromLaunchData(launch_data_t data) {
+ if (launch_data_get_type(data) != LAUNCH_DATA_ERRNO)
+ return EINVAL;
+ return launch_data_get_errno(data);
+}
+
+bool StringFromLaunchDataDictEntry(launch_data_t dict,
+ const char* key,
+ std::string* value) {
+ launch_data_t entry = launch_data_dict_lookup(dict, key);
+ if (!entry || launch_data_get_type(entry) != LAUNCH_DATA_STRING)
+ return false;
+ *value = std::string(launch_data_get_string(entry));
+ return true;
+}
+
+bool IntFromLaunchDataDictEntry(launch_data_t dict,
+ const char* key,
+ int* value) {
+ launch_data_t entry = launch_data_dict_lookup(dict, key);
+ if (!entry || launch_data_get_type(entry) != LAUNCH_DATA_INTEGER)
+ return false;
+ *value = launch_data_get_integer(entry);
+ return true;
+}
+
+ScopedLaunchData DoServiceOp(const char* verb,
+ const std::string& label,
+ int* error) {
+ ScopedLaunchData msg(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(msg, LaunchDataFromString(label).release(), verb);
+
+ ScopedLaunchData result(SendLaunchMessage(std::move(msg)));
+ if (!result)
+ *error = errno;
+ return result;
+}
+
+NSArray* NSArrayFromStringVector(const std::vector<std::string>& vec) {
+ NSMutableArray* args = [NSMutableArray arrayWithCapacity:vec.size()];
+ for (const auto& item : vec) {
+ [args addObject:base::SysUTF8ToNSString(item)];
+ }
+ return args;
+}
+
+base::scoped_nsobject<NSDictionary> DictionaryForJobOptions(
+ const mac::services::JobOptions& options) {
+ base::scoped_nsobject<NSMutableDictionary> opts(
+ [[NSMutableDictionary alloc] init]);
+
+ [opts setObject:base::SysUTF8ToNSString(options.label)
+ forKey:@LAUNCH_JOBKEY_LABEL];
+
+ if (!options.executable_path.empty()) {
+ [opts setObject:base::SysUTF8ToNSString(options.executable_path)
+ forKey:@LAUNCH_JOBKEY_PROGRAM];
+ }
+
+ if (!options.arguments.empty()) {
+ [opts setObject:NSArrayFromStringVector(options.arguments)
+ forKey:@LAUNCH_JOBKEY_PROGRAMARGUMENTS];
+ }
+
+ if (!options.mach_service_name.empty()) {
+ NSDictionary* service_entry =
+ @{base::SysUTF8ToNSString(options.mach_service_name) : @YES};
+ [opts setObject:service_entry forKey:@LAUNCH_JOBKEY_MACHSERVICES];
+ }
+
+ if (options.run_at_load || options.auto_launch) {
+ [opts setObject:@YES forKey:@LAUNCH_JOBKEY_RUNATLOAD];
+ }
+
+ if (options.auto_launch) {
+ [opts setObject:@{
+ @LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT : @NO
+ }
+ forKey:@LAUNCH_JOBKEY_KEEPALIVE];
+ [opts setObject:@"Aqua" forKey:@LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE];
+ }
+
+ return base::scoped_nsobject<NSDictionary>(opts.release());
+}
+
+} // namespace
+
+namespace mac {
+namespace services {
+
+JobInfo::JobInfo() = default;
+JobInfo::JobInfo(const JobInfo& other) = default;
+JobInfo::~JobInfo() = default;
+
+JobCheckinInfo::JobCheckinInfo() = default;
+JobCheckinInfo::JobCheckinInfo(const JobCheckinInfo& other) = default;
+JobCheckinInfo::~JobCheckinInfo() = default;
+
+JobOptions::JobOptions() = default;
+JobOptions::JobOptions(const JobOptions& other) = default;
+JobOptions::~JobOptions() = default;
+
+bool GetJobInfo(const std::string& label, JobInfo* info) {
+ int error = 0;
+ ScopedLaunchData resp = DoServiceOp(LAUNCH_KEY_GETJOB, label, &error);
+
+ if (error)
+ return false;
+
+ std::string program;
+ if (!StringFromLaunchDataDictEntry(resp.get(), LAUNCH_JOBKEY_PROGRAM,
+ &program))
+ return false;
+
+ info->program = program;
+ int pid;
+ if (IntFromLaunchDataDictEntry(resp.get(), LAUNCH_JOBKEY_PID, &pid))
+ info->pid = pid;
+
+ return true;
+}
+
+bool SubmitJob(const JobOptions& options) {
+ base::scoped_nsobject<NSDictionary> options_dict =
+ DictionaryForJobOptions(options);
+ return SMJobSubmit(kSMDomainUserLaunchd,
+ base::mac::NSToCFCast(options_dict.get()), nullptr,
+ nullptr);
+}
+
+bool RemoveJob(const std::string& label) {
+ int error = 0;
+ ScopedLaunchData resp = DoServiceOp(LAUNCH_KEY_REMOVEJOB, label, &error);
+
+ if (!error)
+ error = ErrnoFromLaunchData(resp.get());
+
+ // On macOS 10.10+, removing a running job yields EINPROGRESS but the
+ // operation completes eventually (but not necessarily by the time RemoveJob
+ // is done). See rdar://18398683 for details.
+ if (error == EINPROGRESS)
+ error = 0;
+
+ return !error;
+}
+
+} // namespace services
+} // namespace mac
+
+#pragma clang diagnostic pop
diff --git a/chromium/chrome/common/mac/staging_watcher.h b/chromium/chrome/common/mac/staging_watcher.h
new file mode 100644
index 00000000000..14d9acc4ca3
--- /dev/null
+++ b/chromium/chrome/common/mac/staging_watcher.h
@@ -0,0 +1,72 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MAC_STAGING_WATCHER_H_
+#define CHROME_COMMON_MAC_STAGING_WATCHER_H_
+
+#import <Foundation/Foundation.h>
+
+// Chrome update works by staging a copy of Chrome near to the current bundle,
+// and then applying it when Chrome restarts to apply the update. Currently,
+// this state of "update pending" is indicated outside of Keystone by a key in
+// the CFPreferences.
+
+using StagingKeyChangedObserver = void (^)(BOOL stagingKeySet);
+
+// An object to observe the staging key. It can be used in either of two ways:
+// 1. To wait for the staging key to clear.
+// 2. To notify when the staging key changes state.
+@interface CrStagingKeyWatcher : NSObject
+
+// On macOS 10.11 and earlier, polling is used, and |pollingTime| specifies the
+// frequency of the polling. On macOS 10.12 and later, no polling is performed
+// and |pollingTime| is ignored.
+- (instancetype)initWithPollingTime:(NSTimeInterval)pollingTime;
+
+// Returns a boolean indicating whether or not the staging key is set.
+- (BOOL)isStagingKeySet;
+
+// Returns a boolean indicating whether or not the staging key is set. This will
+// not return the correct answer in testing.
++ (BOOL)isStagingKeySet;
+
+// Returns the path to the staged update, or nil if there is no staging key set.
+- (NSString*)stagingLocation;
+
+// Returns the path to the staged update, or nil if there is no staging key set.
+// This will not return the correct answer in testing.
++ (NSString*)stagingLocation;
+
+// Sleeps until the staging key is clear. If there is no staging key set,
+// returns immediately.
+- (void)waitForStagingKeyToClear;
+
+// Sets a block to be called when the staging key changes, and starts observing.
+// Only one observer may be set for a given CrStagingKeyWatcher; calling this
+// method again replaces the current observer.
+- (void)setStagingKeyChangedObserver:(StagingKeyChangedObserver)block;
+
+@end
+
+@interface CrStagingKeyWatcher (TestingInterface)
+
+// The designated initializer. Allows a non-default NSUserDefaults to be
+// specified. Also allows the use of KVO to be disabled to allow the macOS 10.11
+// and earlier code path to be tested on 10.12 and later.
+- (instancetype)initWithUserDefaults:(NSUserDefaults*)defaults
+ pollingTime:(NSTimeInterval)pollingTime
+ disableKVOForTesting:(BOOL)disableKVOForTesting;
+
+// Returns whether the last call to -waitForStagingKeyToClear blocked or
+// returned immediately.
+- (BOOL)lastWaitWasBlockedForTesting;
+
+// Returns the NSUserDefaults key that is used to indicate staging. The value to
+// be used is a dictionary of strings, with the key being the file path to the
+// existing bundle, and the value being the file path to the staged bundle.
++ (NSString*)stagingKeyForTesting;
+
+@end
+
+#endif // CHROME_COMMON_MAC_STAGING_WATCHER_H_
diff --git a/chromium/chrome/common/mac/staging_watcher.mm b/chromium/chrome/common/mac/staging_watcher.mm
new file mode 100644
index 00000000000..12fed3c9e0e
--- /dev/null
+++ b/chromium/chrome/common/mac/staging_watcher.mm
@@ -0,0 +1,191 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/mac/staging_watcher.h"
+
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+
+// Best documentation / Is unofficial documentation
+//
+// Required reading for CFPreferences/NSUserDefaults is at
+// <http://dscoder.com/defaults.html>, a post by David "Catfish Man" Smith, who
+// re-wrote NSUserDefaults for 10.12 and iPad Classroom support. It is important
+// to note that KVO only notifies for changes made by other programs starting
+// with that rewrite in 10.12. In macOS 10.11 and earlier, polling is the only
+// option. Note that NSUserDefaultsDidChangeNotification never notifies about
+// changes made by other programs, not even in 10.12 and later.
+//
+// On the other hand, KVO notification was broken in the NSUserDefaults rewrite
+// for 10.14; see:
+// - https://twitter.com/Catfish_Man/status/1116185288257105925
+// - rdar://49812220
+// The bug is that a change from "no value" to "value present" doesn't notify.
+// To work around that, a default is registered to ensure that there never is
+// a "no value" situation.
+
+namespace {
+
+NSString* const kStagingKey = @"UpdatePending";
+
+} // namespace
+
+@interface CrStagingKeyWatcher () {
+ base::scoped_nsobject<NSUserDefaults> defaults_;
+ NSTimeInterval pollingTime_;
+ base::scoped_nsobject<NSTimer> pollingTimer_;
+ BOOL observing_;
+ base::mac::ScopedBlock<StagingKeyChangedObserver> callback_;
+ BOOL lastStagingKeyValue_;
+
+ BOOL lastWaitWasBlockedForTesting_;
+}
+
++ (NSString*)stagingLocationWithUserDefaults:(NSUserDefaults*)defaults;
+
+@end
+
+@implementation CrStagingKeyWatcher
+
+- (instancetype)initWithPollingTime:(NSTimeInterval)pollingTime {
+ return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]
+ pollingTime:pollingTime
+ disableKVOForTesting:NO];
+}
+
+- (instancetype)initWithUserDefaults:(NSUserDefaults*)defaults
+ pollingTime:(NSTimeInterval)pollingTime
+ disableKVOForTesting:(BOOL)disableKVOForTesting {
+ if ((self = [super init])) {
+ pollingTime_ = pollingTime;
+ defaults_.reset(defaults, base::scoped_policy::RETAIN);
+ [defaults_ registerDefaults:@{kStagingKey : @[]}];
+ lastStagingKeyValue_ = [self isStagingKeySet];
+ if (base::mac::IsAtLeastOS10_12() && !disableKVOForTesting) {
+ // If a change is made in another process (which is the use case here),
+ // the prior value is never provided in the observation callback change
+ // dictionary, whether or not NSKeyValueObservingOptionPrior is specified.
+ // Therefore, pass in 0 for the NSKeyValueObservingOptions and rely on
+ // keeping the previous value in |lastStagingKeyValue_|.
+ [defaults_ addObserver:self
+ forKeyPath:kStagingKey
+ options:0
+ context:nullptr];
+ observing_ = YES;
+ }
+ }
+ return self;
+}
+
++ (NSString*)stagingLocationWithUserDefaults:(NSUserDefaults*)defaults {
+ NSDictionary<NSString*, id>* stagedPathPairs =
+ [defaults dictionaryForKey:kStagingKey];
+ if (!stagedPathPairs)
+ return nil;
+
+ NSString* appPath = [base::mac::OuterBundle() bundlePath];
+
+ return base::mac::ObjCCast<NSString>([stagedPathPairs objectForKey:appPath]);
+}
+
+- (BOOL)isStagingKeySet {
+ return [self stagingLocation] != nil;
+}
+
++ (BOOL)isStagingKeySet {
+ return [self stagingLocation] != nil;
+}
+
+- (NSString*)stagingLocation {
+ return [CrStagingKeyWatcher stagingLocationWithUserDefaults:defaults_];
+}
+
++ (NSString*)stagingLocation {
+ return [self
+ stagingLocationWithUserDefaults:[NSUserDefaults standardUserDefaults]];
+}
+
+- (void)waitForStagingKeyToClear {
+ if (![self isStagingKeySet]) {
+ lastWaitWasBlockedForTesting_ = NO;
+ return;
+ }
+
+ NSRunLoop* runloop = [NSRunLoop currentRunLoop];
+ if (observing_) {
+ callback_.reset(
+ ^(BOOL stagingKeySet) {
+ CFRunLoopStop([runloop getCFRunLoop]);
+ },
+ base::scoped_policy::RETAIN);
+
+ while ([self isStagingKeySet] && [runloop runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]]) {
+ /* run! */
+ }
+ } else {
+ while ([self isStagingKeySet] &&
+ [runloop
+ runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate dateWithTimeIntervalSinceNow:pollingTime_]]) {
+ /* run! */
+ }
+ }
+
+ lastWaitWasBlockedForTesting_ = YES;
+}
+
+- (void)setStagingKeyChangedObserver:(StagingKeyChangedObserver)block {
+ callback_.reset(block, base::scoped_policy::RETAIN);
+
+ if (observing_) {
+ // Nothing to be done; the observation is already started.
+ } else {
+ pollingTimer_.reset(
+ [NSTimer scheduledTimerWithTimeInterval:pollingTime_
+ target:self
+ selector:@selector(timerFired:)
+ userInfo:nil
+ repeats:YES],
+ base::scoped_policy::RETAIN);
+ }
+}
+
+- (void)timerFired:(NSTimer*)timer {
+ [self observeValueForKeyPath:nil ofObject:nil change:nil context:nil];
+}
+
+- (void)dealloc {
+ if (observing_)
+ [defaults_ removeObserver:self forKeyPath:kStagingKey context:nullptr];
+ if (pollingTimer_)
+ [pollingTimer_ invalidate];
+
+ [super dealloc];
+}
+
+- (BOOL)lastWaitWasBlockedForTesting {
+ return lastWaitWasBlockedForTesting_;
+}
+
++ (NSString*)stagingKeyForTesting {
+ return kStagingKey;
+}
+
+- (void)observeValueForKeyPath:(NSString*)keyPath
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context {
+ BOOL isStagingKeySet = [self isStagingKeySet];
+ if (isStagingKeySet == lastStagingKeyValue_)
+ return;
+
+ lastStagingKeyValue_ = isStagingKeySet;
+ callback_.get()([self isStagingKeySet]);
+}
+
+@end
diff --git a/chromium/chrome/common/mac/staging_watcher_unittest.mm b/chromium/chrome/common/mac/staging_watcher_unittest.mm
new file mode 100644
index 00000000000..e3eb86ab950
--- /dev/null
+++ b/chromium/chrome/common/mac/staging_watcher_unittest.mm
@@ -0,0 +1,181 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/mac/staging_watcher.h"
+
+#include <dispatch/dispatch.h>
+
+#include "base/mac/bundle_locations.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+enum class KVOOrNot { kUseKVO, kDontUseKVO };
+
+class StagingKeyWatcherTest : public testing::TestWithParam<KVOOrNot> {
+ public:
+ StagingKeyWatcherTest() = default;
+ ~StagingKeyWatcherTest() = default;
+
+ protected:
+ void SetUp() override {
+ testingBundleID_.reset([[NSString alloc]
+ initWithFormat:@"org.chromium.StagingKeyWatcherTest.%d", getpid()]);
+ defaults_.reset(
+ [[NSUserDefaults alloc] initWithSuiteName:testingBundleID_]);
+
+ [defaults_ removeObjectForKey:[CrStagingKeyWatcher stagingKeyForTesting]];
+ }
+
+ void TearDown() override {
+ [defaults_ removeObjectForKey:[CrStagingKeyWatcher stagingKeyForTesting]];
+ }
+
+ base::scoped_nsobject<CrStagingKeyWatcher> CreateKeyWatcher() {
+ base::scoped_nsobject<CrStagingKeyWatcher> keyWatcher(
+ [[CrStagingKeyWatcher alloc]
+ initWithUserDefaults:defaults_
+ pollingTime:0.5
+ disableKVOForTesting:(GetParam() == KVOOrNot::kDontUseKVO)]);
+
+ return keyWatcher;
+ }
+
+ void SetDefaultsValue(id value) {
+ [defaults_ setObject:value
+ forKey:[CrStagingKeyWatcher stagingKeyForTesting]];
+ }
+
+ void ClearDefaultsValueInSeparateProcess() {
+ [NSTask launchedTaskWithLaunchPath:@"/usr/bin/defaults"
+ arguments:@[
+ @"delete", testingBundleID_.get(),
+ [CrStagingKeyWatcher stagingKeyForTesting]
+ ]];
+ }
+
+ void SetDefaultsValueInSeparateProcess() {
+ NSString* appPath = [base::mac::OuterBundle() bundlePath];
+
+ [NSTask launchedTaskWithLaunchPath:@"/usr/bin/defaults"
+ arguments:@[
+ @"write", testingBundleID_.get(),
+ [CrStagingKeyWatcher stagingKeyForTesting],
+ @"-dict", appPath, appPath
+ ]];
+ }
+
+ private:
+ base::scoped_nsobject<NSString> testingBundleID_;
+ base::scoped_nsobject<NSUserDefaults> defaults_;
+};
+
+INSTANTIATE_TEST_SUITE_P(KVOandNot,
+ StagingKeyWatcherTest,
+ testing::Values(KVOOrNot::kUseKVO,
+ KVOOrNot::kDontUseKVO));
+
+} // namespace
+
+TEST_P(StagingKeyWatcherTest, NoBlockingWhenNoKey) {
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ [watcher waitForStagingKeyToClear];
+ ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
+}
+
+TEST_P(StagingKeyWatcherTest, NoBlockingWhenWrongKeyType) {
+ SetDefaultsValue(@"this is not a dictionary");
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ [watcher waitForStagingKeyToClear];
+ ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
+}
+
+TEST_P(StagingKeyWatcherTest, NoBlockingWhenArrayType) {
+ SetDefaultsValue(@[ @3, @1, @4, @1, @5 ]);
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ [watcher waitForStagingKeyToClear];
+ ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
+}
+
+TEST_P(StagingKeyWatcherTest, NoBlockingWhenEmptyArray) {
+ SetDefaultsValue(@[]);
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ [watcher waitForStagingKeyToClear];
+ ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
+}
+
+TEST_P(StagingKeyWatcherTest, NoBlockingWhenEmptyDictionary) {
+ SetDefaultsValue(@{});
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ [watcher waitForStagingKeyToClear];
+ ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
+}
+
+TEST_P(StagingKeyWatcherTest, BlockFunctionality) {
+ NSString* appPath = [base::mac::OuterBundle() bundlePath];
+ SetDefaultsValue(@{appPath : appPath});
+
+ NSRunLoop* runloop = [NSRunLoop currentRunLoop];
+ ASSERT_EQ(nil, [runloop currentMode]);
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ ASSERT_NE(nil, [runloop currentMode]);
+ ClearDefaultsValueInSeparateProcess();
+ });
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ [watcher waitForStagingKeyToClear];
+ ASSERT_TRUE([watcher lastWaitWasBlockedForTesting]);
+}
+
+TEST_P(StagingKeyWatcherTest, CallbackOnKeySet) {
+ // The staging key begins not set.
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ NSRunLoop* runloop = [NSRunLoop currentRunLoop];
+
+ __block bool observerCalled = false;
+ [watcher setStagingKeyChangedObserver:^(BOOL stagingKeySet) {
+ observerCalled = true;
+ CFRunLoopStop([runloop getCFRunLoop]);
+ }];
+
+ SetDefaultsValueInSeparateProcess();
+ ASSERT_FALSE([watcher isStagingKeySet]);
+ while (!observerCalled && [runloop runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]]) {
+ /* run! */
+ }
+
+ EXPECT_TRUE([watcher isStagingKeySet]);
+}
+
+TEST_P(StagingKeyWatcherTest, CallbackOnKeyUnset) {
+ NSString* appPath = [base::mac::OuterBundle() bundlePath];
+ SetDefaultsValue(@{appPath : appPath});
+
+ base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
+ NSRunLoop* runloop = [NSRunLoop currentRunLoop];
+
+ __block bool observerCalled = false;
+ [watcher setStagingKeyChangedObserver:^(BOOL stagingKeySet) {
+ observerCalled = true;
+ CFRunLoopStop([runloop getCFRunLoop]);
+ }];
+
+ ClearDefaultsValueInSeparateProcess();
+ ASSERT_TRUE([watcher isStagingKeySet]);
+ while (!observerCalled && [runloop runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]]) {
+ /* run! */
+ }
+
+ EXPECT_FALSE([watcher isStagingKeySet]);
+}
diff --git a/chromium/chrome/common/media/OWNERS b/chromium/chrome/common/media/OWNERS
new file mode 100644
index 00000000000..64f68d00825
--- /dev/null
+++ b/chromium/chrome/common/media/OWNERS
@@ -0,0 +1,9 @@
+file://media/OWNERS
+
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# COMPONENT: Internals>Media>Encrypted
diff --git a/chromium/chrome/common/media/cdm_host_file_path.cc b/chromium/chrome/common/media/cdm_host_file_path.cc
new file mode 100644
index 00000000000..b62e7834f09
--- /dev/null
+++ b/chromium/chrome/common/media/cdm_host_file_path.cc
@@ -0,0 +1,123 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media/cdm_host_file_path.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_version.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/bundle_locations.h"
+#endif
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+namespace {
+
+// TODO(xhwang): Move this to a common place if needed.
+const base::FilePath::CharType kSignatureFileExtension[] =
+ FILE_PATH_LITERAL(".sig");
+
+// Returns the signature file path given the |file_path|. This function should
+// only be used when the signature file and the file are located in the same
+// directory.
+base::FilePath GetSigFilePath(const base::FilePath& file_path) {
+ return file_path.AddExtension(kSignatureFileExtension);
+}
+
+} // namespace
+
+void AddCdmHostFilePaths(
+ std::vector<media::CdmHostFilePath>* cdm_host_file_paths) {
+ DVLOG(1) << __func__;
+ DCHECK(cdm_host_file_paths);
+ DCHECK(cdm_host_file_paths->empty());
+
+#if defined(OS_WIN)
+
+ static const base::FilePath::CharType* const kUnversionedFiles[] = {
+ chrome::kBrowserProcessExecutableName};
+
+ static const base::FilePath::CharType* const kVersionedFiles[] = {
+#if defined(CHROME_MULTIPLE_DLL)
+ chrome::kBrowserResourcesDll,
+ chrome::kChildDll
+#else
+ chrome::kBrowserResourcesDll
+#endif // defined(CHROME_MULTIPLE_DLL)
+ };
+
+ // Find where chrome.exe is installed.
+ base::FilePath chrome_exe_dir;
+ if (!base::PathService::Get(base::DIR_EXE, &chrome_exe_dir))
+ NOTREACHED();
+ base::FilePath version_dir(chrome_exe_dir.AppendASCII(CHROME_VERSION_STRING));
+
+ cdm_host_file_paths->reserve(base::size(kUnversionedFiles) +
+ base::size(kVersionedFiles));
+
+ // Signature files are always in the version directory.
+ for (size_t i = 0; i < base::size(kUnversionedFiles); ++i) {
+ base::FilePath file_path = chrome_exe_dir.Append(kUnversionedFiles[i]);
+ base::FilePath sig_path =
+ GetSigFilePath(version_dir.Append(kUnversionedFiles[i]));
+ DVLOG(2) << __func__ << ": unversioned file " << i << " at "
+ << file_path.value() << ", signature file " << sig_path.value();
+ cdm_host_file_paths->emplace_back(file_path, sig_path);
+ }
+
+ for (size_t i = 0; i < base::size(kVersionedFiles); ++i) {
+ base::FilePath file_path = version_dir.Append(kVersionedFiles[i]);
+ DVLOG(2) << __func__ << ": versioned file " << i << " at "
+ << file_path.value();
+ cdm_host_file_paths->emplace_back(file_path, GetSigFilePath(file_path));
+ }
+
+#elif defined(OS_MACOSX)
+
+ base::FilePath framework_dir = base::mac::FrameworkBundlePath();
+ base::FilePath chrome_framework_path =
+ framework_dir.Append(chrome::kFrameworkExecutableName);
+ // The signature file lives inside
+ // Google Chrome Framework.framework/Versions/X/Resources/.
+ base::FilePath widevine_signature_path = framework_dir.Append("Resources");
+ base::FilePath chrome_framework_sig_path = GetSigFilePath(
+ widevine_signature_path.Append(chrome::kFrameworkExecutableName));
+
+ DVLOG(2) << __func__
+ << ": chrome_framework_path=" << chrome_framework_path.value()
+ << ", signature_path=" << chrome_framework_sig_path.value();
+ cdm_host_file_paths->emplace_back(chrome_framework_path,
+ chrome_framework_sig_path);
+
+#elif defined(OS_LINUX)
+
+ base::FilePath chrome_exe_dir;
+ if (!base::PathService::Get(base::DIR_EXE, &chrome_exe_dir))
+ NOTREACHED();
+
+ base::FilePath chrome_path =
+ chrome_exe_dir.Append(FILE_PATH_LITERAL("chrome"));
+ DVLOG(2) << __func__ << ": chrome_path=" << chrome_path.value();
+ cdm_host_file_paths->emplace_back(chrome_path, GetSigFilePath(chrome_path));
+
+#endif // defined(OS_WIN)
+}
+
+#else // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+void AddCdmHostFilePaths(
+ std::vector<media::CdmHostFilePath>* cdm_host_file_paths) {
+ NOTIMPLEMENTED() << "CDM host file paths need to be provided for the CDM to "
+ "verify the host.";
+}
+
+#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chromium/chrome/common/media/cdm_host_file_path.h b/chromium/chrome/common/media/cdm_host_file_path.h
new file mode 100644
index 00000000000..4548f39dd36
--- /dev/null
+++ b/chromium/chrome/common/media/cdm_host_file_path.h
@@ -0,0 +1,16 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_CDM_HOST_FILE_PATH_H_
+#define CHROME_COMMON_MEDIA_CDM_HOST_FILE_PATH_H_
+
+#include <vector>
+
+#include "media/cdm/cdm_host_file.h"
+
+// Gets a list of CDM host file paths and put them in |cdm_host_file_paths|.
+void AddCdmHostFilePaths(
+ std::vector<media::CdmHostFilePath>* cdm_host_file_paths);
+
+#endif // CHROME_COMMON_MEDIA_CDM_HOST_FILE_PATH_H_
diff --git a/chromium/chrome/common/media/cdm_manifest.cc b/chromium/chrome/common/media/cdm_manifest.cc
new file mode 100644
index 00000000000..e7147156dec
--- /dev/null
+++ b/chromium/chrome/common/media/cdm_manifest.cc
@@ -0,0 +1,372 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media/cdm_manifest.h"
+
+#include <stddef.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/containers/span.h"
+#include "base/files/file_path.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "content/public/common/cdm_info.h"
+#include "extensions/common/manifest_constants.h"
+#include "media/base/content_decryption_module.h"
+#include "media/base/decrypt_config.h"
+#include "media/base/video_codecs.h"
+#include "media/cdm/cdm_proxy.h"
+#include "media/cdm/supported_cdm_versions.h"
+#include "media/media_buildflags.h"
+
+#if !BUILDFLAG(ENABLE_LIBRARY_CDMS)
+#error This file should only be compiled when library CDMs are enabled
+#endif
+
+namespace {
+
+// The CDM manifest includes several custom values, all beginning with "x-cdm-".
+// They are:
+// x-cdm-module-versions
+// x-cdm-interface-versions
+// x-cdm-host-versions
+// x-cdm-codecs
+// x-cdm-persistent-license-support
+// x-cdm-supported-encryption-schemes
+// x-cdm-supported-cdm-proxy-protocols
+// What they represent is listed below. They should never have non-backwards
+// compatible changes. All values are strings. All values that are lists are
+// delimited by commas. No trailing commas. For example, "1,2,4".
+const char kCdmValueDelimiter[] = ",";
+
+// The following entries are required.
+// Interface versions are lists of integers (e.g. "1" or "1,2,4").
+// All match the interface versions from content_decryption_module.h that the
+// CDM supports.
+// Matches CDM_MODULE_VERSION.
+const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
+// Matches supported ContentDecryptionModule_* version(s).
+const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
+// Matches supported Host_* version(s).
+const char kCdmHostVersionsName[] = "x-cdm-host-versions";
+// The codecs list is a list of simple codec names (e.g. "vp8,vorbis").
+const char kCdmCodecsListName[] = "x-cdm-codecs";
+// Whether persistent license is supported by the CDM: "true" or "false".
+const char kCdmPersistentLicenseSupportName[] =
+ "x-cdm-persistent-license-support";
+// The list of supported encryption schemes (e.g. ["cenc","cbcs"]).
+const char kCdmSupportedEncryptionSchemesName[] =
+ "x-cdm-supported-encryption-schemes";
+// The list of supported proxy protocols (e.g. ["intel"]).
+const char kCdmSupportedCdmProxyProtocolsName[] =
+ "x-cdm-supported-cdm-proxy-protocols";
+
+// The following strings are used to specify supported codecs in the
+// parameter |kCdmCodecsListName|.
+const char kCdmSupportedCodecVp8[] = "vp8";
+// Legacy VP9, which is equivalent to VP9 profile 0.
+// TODO(xhwang): Newer CDMs should support "vp09" below. Remove this after older
+// CDMs are obsolete.
+const char kCdmSupportedCodecLegacyVp9[] = "vp9.0";
+// Supports at least VP9 profile 0 and profile 2.
+const char kCdmSupportedCodecVp9[] = "vp09";
+const char kCdmSupportedCodecAv1[] = "av01";
+#if BUILDFLAG(USE_PROPRIETARY_CODECS)
+const char kCdmSupportedCodecAvc1[] = "avc1";
+#endif
+
+// The following strings are used to specify supported encryption schemes in
+// the parameter |kCdmSupportedEncryptionSchemesName|.
+const char kCdmSupportedEncryptionSchemeCenc[] = "cenc";
+const char kCdmSupportedEncryptionSchemeCbcs[] = "cbcs";
+
+// The following string(s) are used to specify supported CdmProxy protocols in
+// the parameter |kCdmSupportedCdmProxyProtocolsName|.
+const char kCdmSupportedCdmProxyProtocolIntel[] = "intel";
+
+typedef bool (*VersionCheckFunc)(int version);
+
+// Returns whether the CDM's API version, as specified in the manifest by
+// |version_name|, is supported in this Chrome binary and not disabled at run
+// time by calling |version_check_func|. If the manifest entry contains multiple
+// values, each one is checked sequentially, and if any one is supported, this
+// function returns true. If all values in the manifest entry are not supported,
+// then return false.
+bool CheckForCompatibleVersion(const base::Value& manifest,
+ const std::string version_name,
+ VersionCheckFunc version_check_func) {
+ DCHECK(manifest.is_dict());
+
+ auto* version_string = manifest.FindStringKey(version_name);
+ if (!version_string) {
+ DVLOG(1) << "CDM manifest missing " << version_name;
+ return false;
+ }
+
+ DVLOG_IF(1, version_string->empty())
+ << "CDM manifest has empty " << version_name;
+
+ for (const base::StringPiece& ver_str :
+ base::SplitStringPiece(*version_string, kCdmValueDelimiter,
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ int version = 0;
+ if (base::StringToInt(ver_str, &version) && version_check_func(version))
+ return true;
+ }
+
+ DVLOG(1) << "CDM manifest has no supported " << version_name << " in '"
+ << *version_string << "'";
+ return false;
+}
+
+// Returns true and updates |video_codecs| if the appropriate manifest entry is
+// valid. When VP9 is supported, sets |supports_vp9_profile2| if profile 2 is
+// supported. Older CDMs may only support profile 0. Returns false and does not
+// modify |video_codecs| if the manifest entry is incorrectly formatted.
+bool GetCodecs(const base::Value& manifest,
+ std::vector<media::VideoCodec>* video_codecs,
+ bool* supports_vp9_profile2) {
+ DCHECK(manifest.is_dict());
+ DCHECK(video_codecs);
+
+ const base::Value* value = manifest.FindKey(kCdmCodecsListName);
+ if (!value) {
+ DLOG(WARNING) << "CDM manifest is missing codecs.";
+ return true;
+ }
+
+ if (!value->is_string()) {
+ DLOG(ERROR) << "CDM manifest entry " << kCdmCodecsListName
+ << " is not a string.";
+ return false;
+ }
+
+ const std::string& codecs = value->GetString();
+ if (codecs.empty()) {
+ DLOG(WARNING) << "CDM manifest has empty codecs list.";
+ return true;
+ }
+
+ std::vector<media::VideoCodec> result;
+ const std::vector<base::StringPiece> supported_codecs =
+ base::SplitStringPiece(codecs, kCdmValueDelimiter, base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+
+ // Assuming VP9 profile 2 is not supported by default. Will only be set when
+ // kCdmSupportedCodecVp9 is available below.
+ *supports_vp9_profile2 = false;
+
+ for (const auto& codec : supported_codecs) {
+ if (codec == kCdmSupportedCodecVp8) {
+ result.push_back(media::VideoCodec::kCodecVP8);
+ } else if (codec == kCdmSupportedCodecLegacyVp9) {
+ result.push_back(media::VideoCodec::kCodecVP9);
+ } else if (codec == kCdmSupportedCodecVp9) {
+ result.push_back(media::VideoCodec::kCodecVP9);
+ *supports_vp9_profile2 = true;
+ } else if (codec == kCdmSupportedCodecAv1) {
+ result.push_back(media::VideoCodec::kCodecAV1);
+#if BUILDFLAG(USE_PROPRIETARY_CODECS)
+ } else if (codec == kCdmSupportedCodecAvc1) {
+ result.push_back(media::VideoCodec::kCodecH264);
+#endif
+ }
+ }
+
+ video_codecs->swap(result);
+ return true;
+}
+
+// Returns true and updates |session_types| if the appropriate manifest entry is
+// valid. Returns false if the manifest entry is incorrectly formatted.
+bool GetSessionTypes(const base::Value& manifest,
+ base::flat_set<media::CdmSessionType>* session_types) {
+ DCHECK(manifest.is_dict());
+ DCHECK(session_types);
+
+ bool is_persistent_license_supported = false;
+ const base::Value* value = manifest.FindKey(kCdmPersistentLicenseSupportName);
+ if (value) {
+ if (!value->is_bool())
+ return false;
+ is_persistent_license_supported = value->GetBool();
+ }
+
+ // Temporary session is always supported.
+ session_types->insert(media::CdmSessionType::kTemporary);
+
+ if (is_persistent_license_supported)
+ session_types->insert(media::CdmSessionType::kPersistentLicense);
+
+ return true;
+}
+
+// Returns true and updates |encryption_schemes| if the appropriate manifest
+// entry is valid. Returns false and does not modify |encryption_schemes| if the
+// manifest entry is incorrectly formatted. It is assumed that all CDMs support
+// 'cenc', so if the manifest entry is missing, the result will indicate support
+// for 'cenc' only. Incorrect types in the manifest entry will log the error and
+// fail. Unrecognized values will be reported but otherwise ignored.
+bool GetEncryptionSchemes(
+ const base::Value& manifest,
+ base::flat_set<media::EncryptionMode>* encryption_schemes) {
+ DCHECK(manifest.is_dict());
+ DCHECK(encryption_schemes);
+
+ const base::Value* value =
+ manifest.FindKey(kCdmSupportedEncryptionSchemesName);
+ if (!value) {
+ // No manifest entry found, so assume only 'cenc' supported for backwards
+ // compatibility.
+ encryption_schemes->insert(media::EncryptionMode::kCenc);
+ return true;
+ }
+
+ if (!value->is_list()) {
+ DLOG(ERROR) << "CDM manifest entry " << kCdmSupportedEncryptionSchemesName
+ << " is not a list.";
+ return false;
+ }
+
+ base::span<const base::Value> list = value->GetList();
+ base::flat_set<media::EncryptionMode> result;
+ for (const auto& item : list) {
+ if (!item.is_string()) {
+ DLOG(ERROR) << "Unrecognized item type in CDM manifest entry "
+ << kCdmSupportedEncryptionSchemesName;
+ return false;
+ }
+
+ const std::string& scheme = item.GetString();
+ if (scheme == kCdmSupportedEncryptionSchemeCenc) {
+ result.insert(media::EncryptionMode::kCenc);
+ } else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
+ result.insert(media::EncryptionMode::kCbcs);
+ } else {
+ DLOG(WARNING) << "Unrecognized encryption scheme '" << scheme
+ << "' in CDM manifest entry "
+ << kCdmSupportedEncryptionSchemesName;
+ }
+ }
+
+ // As the manifest entry exists, it must specify at least one valid value.
+ if (result.empty())
+ return false;
+
+ encryption_schemes->swap(result);
+ return true;
+}
+
+// Returns true and updates |cdm_proxy_protocols| if the appropriate manifest
+// entry is valid. Returns false and does not modify |cdm_proxy_protocols| if
+// the manifest entry is incorrectly formatted. Incorrect types in the manifest
+// entry will log the error and fail. Unrecognized values will be reported but
+// otherwise ignored.
+bool GetCdmProxyProtocols(
+ const base::Value& manifest,
+ base::flat_set<media::CdmProxy::Protocol>* cdm_proxy_protocols) {
+ DCHECK(manifest.is_dict());
+ const auto* value = manifest.FindKey(kCdmSupportedCdmProxyProtocolsName);
+ if (!value)
+ return true;
+
+ if (!value->is_list()) {
+ DLOG(ERROR) << "CDM manifest entry " << kCdmSupportedCdmProxyProtocolsName
+ << " is not a list.";
+ return false;
+ }
+
+ base::span<const base::Value> list = value->GetList();
+ base::flat_set<media::CdmProxy::Protocol> result;
+ for (const auto& item : list) {
+ if (!item.is_string()) {
+ DLOG(ERROR) << "Unrecognized item type in CDM manifest entry "
+ << kCdmSupportedCdmProxyProtocolsName;
+ return false;
+ }
+
+ const std::string& protocol = item.GetString();
+ if (protocol == kCdmSupportedCdmProxyProtocolIntel) {
+ result.insert(media::CdmProxy::Protocol::kIntel);
+ } else {
+ DLOG(WARNING) << "Unrecognized CdmProxy protocol '" << protocol
+ << "' in CDM manifest entry "
+ << kCdmSupportedCdmProxyProtocolsName;
+ }
+ }
+
+ cdm_proxy_protocols->swap(result);
+ return true;
+}
+
+bool GetVersion(const base::Value& manifest, base::Version* version) {
+ DCHECK(manifest.is_dict());
+ auto* version_string =
+ manifest.FindStringKey(extensions::manifest_keys::kVersion);
+ if (!version_string) {
+ DLOG(ERROR) << "CDM manifest missing "
+ << extensions::manifest_keys::kVersion;
+ return false;
+ }
+
+ *version = base::Version(*version_string);
+ if (!version->IsValid()) {
+ DLOG(ERROR) << "CDM manifest version " << version_string << " is invalid.";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool IsCdmManifestCompatibleWithChrome(const base::Value& manifest) {
+ DCHECK(manifest.is_dict());
+
+ return CheckForCompatibleVersion(manifest, kCdmModuleVersionsName,
+ media::IsSupportedCdmModuleVersion) &&
+ CheckForCompatibleVersion(
+ manifest, kCdmInterfaceVersionsName,
+ media::IsSupportedAndEnabledCdmInterfaceVersion) &&
+ CheckForCompatibleVersion(manifest, kCdmHostVersionsName,
+ media::IsSupportedCdmHostVersion);
+}
+
+bool ParseCdmManifest(const base::Value& manifest,
+ content::CdmCapability* capability) {
+ DCHECK(manifest.is_dict());
+
+ return GetCodecs(manifest, &capability->video_codecs,
+ &capability->supports_vp9_profile2) &&
+ GetEncryptionSchemes(manifest, &capability->encryption_schemes) &&
+ GetSessionTypes(manifest, &capability->session_types) &&
+ GetCdmProxyProtocols(manifest, &capability->cdm_proxy_protocols);
+}
+
+bool ParseCdmManifestFromPath(const base::FilePath& manifest_path,
+ base::Version* version,
+ content::CdmCapability* capability) {
+ JSONFileValueDeserializer deserializer(manifest_path);
+ int error_code;
+ std::string error_message;
+ std::unique_ptr<base::Value> manifest =
+ deserializer.Deserialize(&error_code, &error_message);
+ if (!manifest || !manifest->is_dict()) {
+ DLOG(ERROR) << "Could not deserialize CDM manifest from " << manifest_path
+ << ". Error: " << error_code << " / " << error_message;
+ return false;
+ }
+
+ return IsCdmManifestCompatibleWithChrome(*manifest) &&
+ GetVersion(*manifest, version) &&
+ ParseCdmManifest(*manifest, capability);
+}
diff --git a/chromium/chrome/common/media/cdm_manifest.h b/chromium/chrome/common/media/cdm_manifest.h
new file mode 100644
index 00000000000..6ed4b5a307c
--- /dev/null
+++ b/chromium/chrome/common/media/cdm_manifest.h
@@ -0,0 +1,42 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_CDM_MANIFEST_H_
+#define CHROME_COMMON_MEDIA_CDM_MANIFEST_H_
+
+namespace base {
+class FilePath;
+class Value;
+class Version;
+}
+
+namespace content {
+struct CdmCapability;
+}
+
+// Returns whether the CDM's API versions, as specified in the manifest, are
+// supported in this Chrome binary and not disabled at run time.
+// Checks the module API, CDM interface API, and Host API.
+// This should never fail except in rare cases where the component has not been
+// updated recently or the user downgrades Chrome.
+bool IsCdmManifestCompatibleWithChrome(const base::Value& manifest);
+
+// Extracts the necessary information from |manifest| and updates |capability|.
+// Returns true on success, false if there are errors in the manifest.
+// If this method returns false, |capability| may or may not be updated.
+bool ParseCdmManifest(const base::Value& manifest,
+ content::CdmCapability* capability);
+
+// Reads the file |manifest_path| which is assumed to be a CDM manifest and
+// extracts the necessary information from it to update |version| and
+// |capability|. This also verifies that the read CDM manifest is compatible
+// with Chrome (by calling IsCdmManifestCompatibleWithChrome()). Returns true on
+// success, false if there are errors in the file or the manifest is not
+// compatible with this version of Chrome. If this method returns false,
+// |version| and |capability| may or may not be updated.
+bool ParseCdmManifestFromPath(const base::FilePath& manifest_path,
+ base::Version* version,
+ content::CdmCapability* capability);
+
+#endif // CHROME_COMMON_MEDIA_CDM_MANIFEST_H_
diff --git a/chromium/chrome/common/media/cdm_manifest_unittest.cc b/chromium/chrome/common/media/cdm_manifest_unittest.cc
new file mode 100644
index 00000000000..b12ee879fd8
--- /dev/null
+++ b/chromium/chrome/common/media/cdm_manifest_unittest.cc
@@ -0,0 +1,542 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media/cdm_manifest.h"
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "content/public/common/cdm_info.h"
+#include "extensions/common/manifest_constants.h"
+#include "media/cdm/api/content_decryption_module.h"
+#include "media/cdm/supported_cdm_versions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::CdmCapability;
+
+namespace {
+
+// These names must match what is used in cdm_manifest.cc.
+const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
+const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
+const char kCdmHostVersionsName[] = "x-cdm-host-versions";
+const char kCdmCodecsListName[] = "x-cdm-codecs";
+const char kCdmPersistentLicenseSupportName[] =
+ "x-cdm-persistent-license-support";
+const char kCdmSupportedEncryptionSchemesName[] =
+ "x-cdm-supported-encryption-schemes";
+const char kCdmSupportedCdmProxyProtocolsName[] =
+ "x-cdm-supported-cdm-proxy-protocols";
+
+// Version checking does change over time. Deriving these values from constants
+// in the code to ensure they change when the CDM interface changes.
+// |kSupportedCdmInterfaceVersion| and |kSupportedCdmHostVersion| are the
+// minimum versions supported. There may be versions after them that are also
+// supported.
+constexpr int kSupportedCdmModuleVersion = CDM_MODULE_VERSION;
+constexpr int kSupportedCdmInterfaceVersion =
+ media::kSupportedCdmInterfaceVersions[0].version;
+static_assert(media::kSupportedCdmInterfaceVersions[0].enabled,
+ "kSupportedCdmInterfaceVersion is not enabled by default.");
+constexpr int kSupportedCdmHostVersion = media::kMinSupportedCdmHostVersion;
+
+// Make a string of the values from 0 up to and including |item|.
+std::string MakeStringList(int item) {
+ DCHECK_GT(item, 0);
+ std::vector<std::string> parts;
+ for (int i = 0; i <= item; ++i) {
+ parts.push_back(base::NumberToString(i));
+ }
+ return base::JoinString(parts, ",");
+}
+
+base::Value MakeListValue(const std::string& item) {
+ base::Value list(base::Value::Type::LIST);
+ list.GetList().push_back(base::Value(item));
+ return list;
+}
+
+base::Value MakeListValue(const std::string& item1, const std::string& item2) {
+ base::Value list(base::Value::Type::LIST);
+ list.GetList().push_back(base::Value(item1));
+ list.GetList().push_back(base::Value(item2));
+ return list;
+}
+
+// Create a default manifest with valid values for all entries.
+base::Value DefaultManifest() {
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetStringKey(kCdmCodecsListName, "vp8,vp9.0,av01");
+ dict.SetBoolKey(kCdmPersistentLicenseSupportName, true);
+ dict.SetKey(kCdmSupportedEncryptionSchemesName,
+ MakeListValue("cenc", "cbcs"));
+ dict.SetKey(kCdmSupportedCdmProxyProtocolsName, MakeListValue("intel"));
+
+ // The following are dependent on what the current code supports.
+ EXPECT_TRUE(media::IsSupportedCdmModuleVersion(kSupportedCdmModuleVersion));
+ EXPECT_TRUE(media::IsSupportedAndEnabledCdmInterfaceVersion(
+ kSupportedCdmInterfaceVersion));
+ EXPECT_TRUE(media::IsSupportedCdmHostVersion(kSupportedCdmHostVersion));
+ dict.SetStringKey(kCdmModuleVersionsName,
+ base::NumberToString(kSupportedCdmModuleVersion));
+ dict.SetStringKey(kCdmInterfaceVersionsName,
+ base::NumberToString(kSupportedCdmInterfaceVersion));
+ dict.SetStringKey(kCdmHostVersionsName,
+ base::NumberToString(kSupportedCdmHostVersion));
+ return dict;
+}
+
+void CheckCodecs(const std::vector<media::VideoCodec>& actual,
+ const std::vector<media::VideoCodec>& expected) {
+ EXPECT_EQ(expected.size(), actual.size());
+ for (const auto& codec : expected) {
+ EXPECT_TRUE(base::Contains(actual, codec));
+ }
+}
+
+void CheckEncryptionSchemes(
+ const base::flat_set<media::EncryptionMode>& actual,
+ const std::vector<media::EncryptionMode>& expected) {
+ EXPECT_EQ(expected.size(), actual.size());
+ for (const auto& encryption_scheme : expected) {
+ EXPECT_TRUE(base::Contains(actual, encryption_scheme));
+ }
+}
+
+void CheckSessionTypes(const base::flat_set<media::CdmSessionType>& actual,
+ const std::vector<media::CdmSessionType>& expected) {
+ EXPECT_EQ(expected.size(), actual.size());
+ for (const auto& session_type : expected) {
+ EXPECT_TRUE(base::Contains(actual, session_type));
+ }
+}
+
+void CheckProxyProtocols(
+ const base::flat_set<media::CdmProxy::Protocol>& actual,
+ const std::vector<media::CdmProxy::Protocol>& expected) {
+ EXPECT_EQ(expected.size(), actual.size());
+ for (const auto& proxy_protocol : expected) {
+ EXPECT_TRUE(base::Contains(actual, proxy_protocol));
+ }
+}
+
+void WriteManifestToFile(const base::Value& manifest,
+ const base::FilePath& file_path) {
+ EXPECT_FALSE(base::PathExists(file_path));
+ JSONFileValueSerializer serializer(file_path);
+ EXPECT_TRUE(serializer.Serialize(manifest));
+ EXPECT_TRUE(base::PathExists(file_path));
+}
+
+} // namespace
+
+TEST(CdmManifestTest, IsCompatibleWithChrome) {
+ base::Value manifest(DefaultManifest());
+ EXPECT_TRUE(IsCdmManifestCompatibleWithChrome(manifest));
+}
+
+TEST(CdmManifestTest, InCompatibleModuleVersion) {
+ const int kUnsupportedModuleVersion = 0;
+ EXPECT_FALSE(media::IsSupportedCdmModuleVersion(kUnsupportedModuleVersion));
+
+ auto manifest = DefaultManifest();
+ manifest.SetStringKey(kCdmModuleVersionsName,
+ base::NumberToString(kUnsupportedModuleVersion));
+ EXPECT_FALSE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
+}
+
+TEST(CdmManifestTest, InCompatibleInterfaceVersion) {
+ const int kUnsupportedInterfaceVersion = kSupportedCdmInterfaceVersion - 1;
+ EXPECT_FALSE(media::IsSupportedAndEnabledCdmInterfaceVersion(
+ kUnsupportedInterfaceVersion));
+
+ auto manifest = DefaultManifest();
+ manifest.SetStringKey(kCdmInterfaceVersionsName,
+ base::NumberToString(kUnsupportedInterfaceVersion));
+ EXPECT_FALSE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
+}
+
+TEST(CdmManifestTest, InCompatibleHostVersion) {
+ const int kUnsupportedHostVersion = kSupportedCdmHostVersion - 1;
+ EXPECT_FALSE(media::IsSupportedCdmHostVersion(kUnsupportedHostVersion));
+
+ auto manifest = DefaultManifest();
+ manifest.SetStringKey(kCdmHostVersionsName,
+ base::NumberToString(kUnsupportedHostVersion));
+ EXPECT_FALSE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
+}
+
+TEST(CdmManifestTest, IsCompatibleWithMultipleValues) {
+ auto manifest = DefaultManifest();
+ manifest.SetStringKey(kCdmModuleVersionsName,
+ MakeStringList(kSupportedCdmModuleVersion));
+ manifest.SetStringKey(kCdmInterfaceVersionsName,
+ MakeStringList(kSupportedCdmInterfaceVersion));
+ manifest.SetStringKey(kCdmHostVersionsName,
+ MakeStringList(kSupportedCdmHostVersion));
+ EXPECT_TRUE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
+}
+
+TEST(CdmManifestTest, ValidManifest) {
+ auto manifest = DefaultManifest();
+ CdmCapability capability;
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs,
+ {media::VideoCodec::kCodecVP8, media::VideoCodec::kCodecVP9,
+ media::VideoCodec::kCodecAV1});
+ CheckEncryptionSchemes(
+ capability.encryption_schemes,
+ {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs});
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary,
+ media::CdmSessionType::kPersistentLicense});
+ CheckProxyProtocols(capability.cdm_proxy_protocols,
+ {media::CdmProxy::Protocol::kIntel});
+}
+
+TEST(CdmManifestTest, EmptyManifest) {
+ base::Value manifest(base::Value::Type::DICTIONARY);
+ CdmCapability capability;
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {});
+ CheckEncryptionSchemes(capability.encryption_schemes,
+ {media::EncryptionMode::kCenc});
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary});
+ CheckProxyProtocols(capability.cdm_proxy_protocols, {});
+}
+
+TEST(CdmManifestTest, ManifestCodecs) {
+ auto manifest = DefaultManifest();
+
+ // Try each valid value individually.
+ {
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "vp8");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecVP8});
+ }
+ {
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "vp9.0");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecVP9});
+ EXPECT_FALSE(capability.supports_vp9_profile2);
+ }
+ {
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "vp09");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecVP9});
+ EXPECT_TRUE(capability.supports_vp9_profile2);
+ }
+ {
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "av01");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecAV1});
+ }
+#if BUILDFLAG(USE_PROPRIETARY_CODECS)
+ {
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "avc1");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecH264});
+ }
+#endif
+ {
+ // Try list of everything (except proprietary codecs).
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "vp8,vp9.0,vp09,av01");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ // Note that kCodecVP9 is returned twice in the list.
+ CheckCodecs(capability.video_codecs,
+ {media::VideoCodec::kCodecVP8, media::VideoCodec::kCodecVP9,
+ media::VideoCodec::kCodecVP9, media::VideoCodec::kCodecAV1});
+ EXPECT_TRUE(capability.supports_vp9_profile2);
+ }
+ {
+ // Note that invalid codec values are simply skipped.
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmCodecsListName, "invalid,av01");
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecAV1});
+ }
+ {
+ // Wrong types are an error.
+ CdmCapability capability;
+ manifest.SetBoolKey(kCdmCodecsListName, true);
+ EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
+ }
+ {
+ // Missing entry is OK, but list is empty.
+ CdmCapability capability;
+ EXPECT_TRUE(manifest.RemoveKey(kCdmCodecsListName));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckCodecs(capability.video_codecs, {});
+ }
+}
+
+TEST(CdmManifestTest, ManifestEncryptionSchemes) {
+ auto manifest = DefaultManifest();
+
+ // Try each valid value individually.
+ {
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedEncryptionSchemesName, MakeListValue("cenc"));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckEncryptionSchemes(capability.encryption_schemes,
+ {media::EncryptionMode::kCenc});
+ }
+ {
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedEncryptionSchemesName, MakeListValue("cbcs"));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckEncryptionSchemes(capability.encryption_schemes,
+ {media::EncryptionMode::kCbcs});
+ }
+ {
+ // Try multiple valid entries.
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedEncryptionSchemesName,
+ MakeListValue("cenc", "cbcs"));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckEncryptionSchemes(
+ capability.encryption_schemes,
+ {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs});
+ }
+ {
+ // Invalid encryption schemes are ignored. However, if value specified then
+ // there must be at least 1 valid value.
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedEncryptionSchemesName,
+ MakeListValue("invalid"));
+ EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
+ }
+ {
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedEncryptionSchemesName,
+ MakeListValue("invalid", "cenc"));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckEncryptionSchemes(capability.encryption_schemes,
+ {media::EncryptionMode::kCenc});
+ }
+ {
+ // Wrong types are an error.
+ CdmCapability capability;
+ manifest.SetBoolKey(kCdmSupportedEncryptionSchemesName, true);
+ EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
+ }
+ {
+ // Missing values default to "cenc".
+ CdmCapability capability;
+ EXPECT_TRUE(manifest.RemoveKey(kCdmSupportedEncryptionSchemesName));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckEncryptionSchemes(capability.encryption_schemes,
+ {media::EncryptionMode::kCenc});
+ }
+}
+
+TEST(CdmManifestTest, ManifestSessionTypes) {
+ auto manifest = DefaultManifest();
+
+ {
+ // Try false (persistent license not supported).
+ CdmCapability capability;
+ manifest.SetBoolKey(kCdmPersistentLicenseSupportName, false);
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary});
+ }
+ {
+ // Try true (persistent license is supported).
+ CdmCapability capability;
+ manifest.SetBoolKey(kCdmPersistentLicenseSupportName, true);
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary,
+ media::CdmSessionType::kPersistentLicense});
+ }
+ {
+ // Wrong types are an error.
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmPersistentLicenseSupportName, "true");
+ EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
+ }
+ {
+ // Missing values default to "temporary".
+ CdmCapability capability;
+ EXPECT_TRUE(manifest.RemoveKey(kCdmPersistentLicenseSupportName));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary});
+ }
+}
+
+TEST(CdmManifestTest, ManifestProxyProtocols) {
+ auto manifest = DefaultManifest();
+
+ {
+ // Try only supported value.
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedCdmProxyProtocolsName, MakeListValue("intel"));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckProxyProtocols(capability.cdm_proxy_protocols,
+ {media::CdmProxy::Protocol::kIntel});
+ }
+ {
+ // Unrecognized values are ignored.
+ CdmCapability capability;
+ manifest.SetKey(kCdmSupportedCdmProxyProtocolsName,
+ MakeListValue("unknown", "intel"));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckProxyProtocols(capability.cdm_proxy_protocols,
+ {media::CdmProxy::Protocol::kIntel});
+ }
+ {
+ // Wrong types are an error.
+ CdmCapability capability;
+ manifest.SetStringKey(kCdmSupportedCdmProxyProtocolsName, "intel");
+ EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
+ }
+ {
+ // Missing values are OK.
+ CdmCapability capability;
+ EXPECT_TRUE(manifest.RemoveKey(kCdmSupportedCdmProxyProtocolsName));
+ EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
+ CheckProxyProtocols(capability.cdm_proxy_protocols, {});
+ }
+}
+
+TEST(CdmManifestTest, FileManifest) {
+ const char kVersion[] = "1.2.3.4";
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ // Manifests read from a file also need a version.
+ auto manifest = DefaultManifest();
+ manifest.SetStringKey(extensions::manifest_keys::kVersion, kVersion);
+ WriteManifestToFile(manifest, manifest_path);
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_TRUE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+ EXPECT_TRUE(version.IsValid());
+ EXPECT_EQ(version.GetString(), kVersion);
+ CheckCodecs(capability.video_codecs,
+ {media::VideoCodec::kCodecVP8, media::VideoCodec::kCodecVP9,
+ media::VideoCodec::kCodecAV1});
+ CheckEncryptionSchemes(
+ capability.encryption_schemes,
+ {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs});
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary,
+ media::CdmSessionType::kPersistentLicense});
+ CheckProxyProtocols(capability.cdm_proxy_protocols,
+ {media::CdmProxy::Protocol::kIntel});
+}
+
+TEST(CdmManifestTest, FileManifestNoVersion) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ auto manifest = DefaultManifest();
+ WriteManifestToFile(manifest, manifest_path);
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_FALSE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+}
+
+TEST(CdmManifestTest, FileManifestBadVersion) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ auto manifest = DefaultManifest();
+ manifest.SetStringKey(extensions::manifest_keys::kVersion, "bad version");
+ WriteManifestToFile(manifest, manifest_path);
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_FALSE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+}
+
+TEST(CdmManifestTest, FileManifestDoesNotExist) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_FALSE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+}
+
+TEST(CdmManifestTest, FileManifestEmpty) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ base::Value manifest(base::Value::Type::DICTIONARY);
+ WriteManifestToFile(manifest, manifest_path);
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_FALSE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+}
+
+TEST(CdmManifestTest, FileManifestLite) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ // Only a version plus fields to satisfy compatibility are required in the
+ // manifest to parse correctly.
+ base::Value manifest(base::Value::Type::DICTIONARY);
+ manifest.SetStringKey(extensions::manifest_keys::kVersion, "1.2.3.4");
+ manifest.SetStringKey(kCdmModuleVersionsName,
+ base::NumberToString(kSupportedCdmModuleVersion));
+ manifest.SetStringKey(kCdmInterfaceVersionsName,
+ base::NumberToString(kSupportedCdmInterfaceVersion));
+ manifest.SetStringKey(kCdmHostVersionsName,
+ base::NumberToString(kSupportedCdmHostVersion));
+ WriteManifestToFile(manifest, manifest_path);
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_TRUE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+ CheckCodecs(capability.video_codecs, {});
+ CheckEncryptionSchemes(capability.encryption_schemes,
+ {media::EncryptionMode::kCenc});
+ CheckSessionTypes(capability.session_types,
+ {media::CdmSessionType::kTemporary});
+ CheckProxyProtocols(capability.cdm_proxy_protocols, {});
+}
+
+TEST(CdmManifestTest, FileManifestNotDictionary) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ auto manifest_path = temp_dir.GetPath().AppendASCII("manifest.json");
+
+ base::Value manifest("not a dictionary");
+ WriteManifestToFile(manifest, manifest_path);
+
+ base::Version version;
+ CdmCapability capability;
+ EXPECT_FALSE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
+}
diff --git a/chromium/chrome/common/media/chrome_media_drm_bridge_client.cc b/chromium/chrome/common/media/chrome_media_drm_bridge_client.cc
new file mode 100644
index 00000000000..54f3b45d350
--- /dev/null
+++ b/chromium/chrome/common/media/chrome_media_drm_bridge_client.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media/chrome_media_drm_bridge_client.h"
+
+ChromeMediaDrmBridgeClient::ChromeMediaDrmBridgeClient() {}
+
+ChromeMediaDrmBridgeClient::~ChromeMediaDrmBridgeClient() {}
+
+media::MediaDrmBridgeDelegate*
+ChromeMediaDrmBridgeClient::GetMediaDrmBridgeDelegate(
+ const std::vector<uint8_t>& scheme_uuid) {
+ if (scheme_uuid == widevine_delegate_.GetUUID())
+ return &widevine_delegate_;
+ return nullptr;
+}
diff --git a/chromium/chrome/common/media/chrome_media_drm_bridge_client.h b/chromium/chrome/common/media/chrome_media_drm_bridge_client.h
new file mode 100644
index 00000000000..e7e49ec5d2b
--- /dev/null
+++ b/chromium/chrome/common/media/chrome_media_drm_bridge_client.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_CHROME_MEDIA_DRM_BRIDGE_CLIENT_H_
+#define CHROME_COMMON_MEDIA_CHROME_MEDIA_DRM_BRIDGE_CLIENT_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "components/cdm/common/widevine_drm_delegate_android.h"
+#include "media/base/android/media_drm_bridge_client.h"
+
+class ChromeMediaDrmBridgeClient : public media::MediaDrmBridgeClient {
+ public:
+ ChromeMediaDrmBridgeClient();
+ ~ChromeMediaDrmBridgeClient() override;
+
+ private:
+ // media::MediaDrmBridgeClient implementation:
+ media::MediaDrmBridgeDelegate* GetMediaDrmBridgeDelegate(
+ const std::vector<uint8_t>& scheme_uuid) override;
+
+ cdm::WidevineDrmDelegateAndroid widevine_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeMediaDrmBridgeClient);
+};
+
+#endif // CHROME_COMMON_MEDIA_CHROME_MEDIA_DRM_BRIDGE_CLIENT_H_
diff --git a/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.cc b/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.cc
new file mode 100644
index 00000000000..0fdfc56c725
--- /dev/null
+++ b/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.cc
@@ -0,0 +1,89 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media/component_widevine_cdm_hint_file_linux.h"
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+
+namespace {
+
+// Fields used inside the hint file.
+const char kPath[] = "Path";
+
+base::FilePath GetPath(const base::Value& dict) {
+ DCHECK(dict.is_dict());
+
+ auto* path_str = dict.FindStringKey(kPath);
+ if (!path_str) {
+ DLOG(ERROR) << "CDM hint file missing " << kPath;
+ return base::FilePath();
+ }
+
+ const base::FilePath path(*path_str);
+ DLOG_IF(ERROR, path.empty())
+ << "CDM hint file path " << path_str << " is invalid.";
+ return path;
+}
+
+} // namespace
+
+bool UpdateWidevineCdmHintFile(const base::FilePath& cdm_base_path) {
+ DCHECK(!cdm_base_path.empty());
+
+ base::FilePath hint_file_path;
+ CHECK(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
+ &hint_file_path));
+
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetStringPath(kPath, cdm_base_path.value());
+
+ std::string json_string;
+ JSONStringValueSerializer serializer(&json_string);
+ if (!serializer.Serialize(dict)) {
+ DLOG(ERROR) << "Could not serialize the CDM hint file.";
+ return false;
+ }
+
+ return base::ImportantFileWriter::WriteFileAtomically(hint_file_path,
+ json_string);
+}
+
+base::FilePath GetLatestComponentUpdatedWidevineCdmDirectory() {
+ base::FilePath hint_file_path;
+ CHECK(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
+ &hint_file_path));
+
+ if (!base::PathExists(hint_file_path)) {
+ DVLOG(2) << "CDM hint file at " << hint_file_path << " does not exist.";
+ return base::FilePath();
+ }
+
+ std::string json_string;
+ if (!base::ReadFileToString(hint_file_path, &json_string)) {
+ DLOG(ERROR) << "Could not read the CDM hint file at " << hint_file_path;
+ return base::FilePath();
+ }
+
+ std::string error_message;
+ JSONStringValueDeserializer deserializer(json_string);
+ std::unique_ptr<base::Value> dict =
+ deserializer.Deserialize(/*error_code=*/nullptr, &error_message);
+
+ if (!dict || !dict->is_dict()) {
+ DLOG(ERROR) << "Could not deserialize the CDM hint file. Error: "
+ << error_message;
+ return base::FilePath();
+ }
+
+ return GetPath(*dict);
+}
diff --git a/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.h b/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.h
new file mode 100644
index 00000000000..122ba441144
--- /dev/null
+++ b/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.h
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_COMPONENT_WIDEVINE_CDM_HINT_FILE_LINUX_H_
+#define CHROME_COMMON_MEDIA_COMPONENT_WIDEVINE_CDM_HINT_FILE_LINUX_H_
+
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+#include "third_party/widevine/cdm/buildflags.h"
+
+#if !BUILDFLAG(ENABLE_WIDEVINE)
+#error "This file only applies when Widevine used."
+#endif
+
+#if !defined(OS_LINUX) || defined(OS_CHROMEOS)
+#error "This file only applies to desktop Linux."
+#endif
+
+namespace base {
+class FilePath;
+} // namespace base
+
+// The APIs here wrap the component updated Widevine hint file, which lives
+// inside the WidevineCdm folder of the user-data-dir, so that the Linux zygote
+// process can preload the latest version of Widevine.
+//
+// The hint file will be a dictionary with one key:
+// {
+// "Path": $path_to_WidevineCdm_directory
+// }
+//
+// $path_to_WidevineCdm_directory will point to a directory structure
+// containing:
+// LICENSE
+// manifest.json
+// _platform_specific/
+// linux_x64/
+// libwidevinecdm.so
+// The actual executable (and directory containing it) will be platform
+// specific. There may be additional files as well as the ones listed above.
+
+// Records a new Widevine path into the hint file, replacing the current
+// contents if any. |cdm_base_path| is the directory containing the new
+// instance. Returns true if the hint file has been successfully updated,
+// otherwise false.
+bool UpdateWidevineCdmHintFile(const base::FilePath& cdm_base_path)
+ WARN_UNUSED_RESULT;
+
+// Returns the latest component updated Widevine CDM directory. If the hint file
+// exists and is valid, returns the CDM base_path with the value loaded from the
+// file. Otherwise returns empty base::FilePath(). This function does not verify
+// that the path returned exists or not.
+base::FilePath GetLatestComponentUpdatedWidevineCdmDirectory()
+ WARN_UNUSED_RESULT;
+
+#endif // CHROME_COMMON_MEDIA_COMPONENT_WIDEVINE_CDM_HINT_FILE_LINUX_H_
diff --git a/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux_unittest.cc b/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux_unittest.cc
new file mode 100644
index 00000000000..125bf50d21a
--- /dev/null
+++ b/chromium/chrome/common/media/component_widevine_cdm_hint_file_linux_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media/component_widevine_cdm_hint_file_linux.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/path_service.h"
+#include "base/test/scoped_path_override.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Name of the widevine directory. Does not have to exist for testing.
+const char kWidevineDirectory[] = "WidevineCdmInstalledHere";
+
+base::FilePath CreateWidevineComponentUpdatedDirectory() {
+ base::FilePath widevine_dir;
+ CHECK(base::PathService::Get(chrome::DIR_COMPONENT_UPDATED_WIDEVINE_CDM,
+ &widevine_dir));
+
+ base::File::Error error;
+ EXPECT_TRUE(base::CreateDirectoryAndGetError(widevine_dir, &error));
+ return widevine_dir;
+}
+
+} // namespace
+
+TEST(ComponentWidevineHintFileTest, RecordUpdate) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+ CreateWidevineComponentUpdatedDirectory();
+ EXPECT_TRUE(UpdateWidevineCdmHintFile(base::FilePath(kWidevineDirectory)));
+}
+
+TEST(ComponentWidevineHintFileTest, RecordUpdateNoDir) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+ EXPECT_FALSE(UpdateWidevineCdmHintFile(base::FilePath(kWidevineDirectory)));
+}
+
+TEST(ComponentWidevineHintFileTest, Read) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+
+ CreateWidevineComponentUpdatedDirectory();
+ EXPECT_TRUE(UpdateWidevineCdmHintFile(base::FilePath(kWidevineDirectory)));
+
+ EXPECT_EQ(GetLatestComponentUpdatedWidevineCdmDirectory().value(),
+ kWidevineDirectory);
+}
+
+TEST(ComponentWidevineHintFileTest, ReadNoDir) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+
+ EXPECT_TRUE(GetLatestComponentUpdatedWidevineCdmDirectory().empty());
+}
+
+TEST(ComponentWidevineHintFileTest, ReadNoFile) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+
+ CreateWidevineComponentUpdatedDirectory();
+ EXPECT_TRUE(GetLatestComponentUpdatedWidevineCdmDirectory().empty());
+}
+
+TEST(ComponentWidevineHintFileTest, ReplaceFile) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+ const char kAltWidevineDirectory[] = "WidevineCdmInstalledOverThere";
+
+ CreateWidevineComponentUpdatedDirectory();
+ EXPECT_TRUE(UpdateWidevineCdmHintFile(base::FilePath(kWidevineDirectory)));
+ EXPECT_EQ(GetLatestComponentUpdatedWidevineCdmDirectory().value(),
+ kWidevineDirectory);
+
+ // Now write the hint file a second time.
+ EXPECT_TRUE(UpdateWidevineCdmHintFile(base::FilePath(kAltWidevineDirectory)));
+ EXPECT_EQ(GetLatestComponentUpdatedWidevineCdmDirectory().value(),
+ kAltWidevineDirectory);
+}
+
+TEST(ComponentWidevineHintFileTest, CorruptHintFile) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+
+ CreateWidevineComponentUpdatedDirectory();
+ base::FilePath hint_file_path;
+ EXPECT_TRUE(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
+ &hint_file_path));
+ EXPECT_TRUE(base::ImportantFileWriter::WriteFileAtomically(
+ hint_file_path, "{not_what_is_expected}"));
+ EXPECT_TRUE(GetLatestComponentUpdatedWidevineCdmDirectory().empty());
+}
+
+TEST(ComponentWidevineHintFileTest, MissingPath) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+
+ CreateWidevineComponentUpdatedDirectory();
+ base::FilePath hint_file_path;
+ EXPECT_TRUE(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
+ &hint_file_path));
+ EXPECT_TRUE(base::ImportantFileWriter::WriteFileAtomically(
+ hint_file_path, "{\"One\": true}"));
+ EXPECT_TRUE(GetLatestComponentUpdatedWidevineCdmDirectory().empty());
+}
+
+TEST(ComponentWidevineHintFileTest, ExtraFields) {
+ const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA);
+
+ CreateWidevineComponentUpdatedDirectory();
+ base::FilePath hint_file_path;
+ EXPECT_TRUE(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
+ &hint_file_path));
+ EXPECT_TRUE(base::ImportantFileWriter::WriteFileAtomically(
+ hint_file_path,
+ "{\"One\": true, \"Path\": \"WidevineCdmInstalledHere\", \"Two\": {}}"));
+ EXPECT_FALSE(GetLatestComponentUpdatedWidevineCdmDirectory().empty());
+}
diff --git a/chromium/chrome/common/media/media_resource_provider.cc b/chromium/chrome/common/media/media_resource_provider.cc
new file mode 100644
index 00000000000..6bea66ca267
--- /dev/null
+++ b/chromium/chrome/common/media/media_resource_provider.cc
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "chrome/common/media/media_resource_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+int MediaMessageIdToGrdId(media::MessageId message_id) {
+ switch (message_id) {
+ case media::DEFAULT_AUDIO_DEVICE_NAME:
+ return IDS_DEFAULT_AUDIO_DEVICE_NAME;
+#if defined(OS_WIN)
+ case media::COMMUNICATIONS_AUDIO_DEVICE_NAME:
+ return IDS_COMMUNICATIONS_AUDIO_DEVICE_NAME;
+#endif
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+} // namespace
+
+base::string16 ChromeMediaLocalizedStringProvider(media::MessageId message_id) {
+ return l10n_util::GetStringUTF16(MediaMessageIdToGrdId(message_id));
+}
diff --git a/chromium/chrome/common/media/media_resource_provider.h b/chromium/chrome/common/media/media_resource_provider.h
new file mode 100644
index 00000000000..46f5a47da02
--- /dev/null
+++ b/chromium/chrome/common/media/media_resource_provider.h
@@ -0,0 +1,15 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_MEDIA_RESOURCE_PROVIDER_H_
+#define CHROME_COMMON_MEDIA_MEDIA_RESOURCE_PROVIDER_H_
+
+#include "base/strings/string16.h"
+#include "media/base/localized_strings.h"
+
+// This is called indirectly by the media layer to access resources.
+base::string16 ChromeMediaLocalizedStringProvider(
+ media::MessageId media_message_id);
+
+#endif // CHROME_COMMON_MEDIA_MEDIA_RESOURCE_PROVIDER_H_
diff --git a/chromium/chrome/common/media_galleries/OWNERS b/chromium/chrome/common/media_galleries/OWNERS
new file mode 100644
index 00000000000..65216a6bcad
--- /dev/null
+++ b/chromium/chrome/common/media_galleries/OWNERS
@@ -0,0 +1,2 @@
+file://chrome/browser/media_galleries/OWNERS
+# COMPONENT: Platform>Apps>MediaGalleries
diff --git a/chromium/chrome/common/media_galleries/metadata_types.h b/chromium/chrome/common/media_galleries/metadata_types.h
new file mode 100644
index 00000000000..d9aa515b6fa
--- /dev/null
+++ b/chromium/chrome/common/media_galleries/metadata_types.h
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_GALLERIES_METADATA_TYPES_H_
+#define CHROME_COMMON_MEDIA_GALLERIES_METADATA_TYPES_H_
+
+#include <string>
+
+namespace metadata {
+
+struct AttachedImage {
+ std::string type;
+ std::string data;
+};
+
+} // namespace metadata
+
+#endif // CHROME_COMMON_MEDIA_GALLERIES_METADATA_TYPES_H_
diff --git a/chromium/chrome/common/media_router/OWNERS b/chromium/chrome/common/media_router/OWNERS
new file mode 100644
index 00000000000..19600fbc8b8
--- /dev/null
+++ b/chromium/chrome/common/media_router/OWNERS
@@ -0,0 +1,2 @@
+file://chrome/browser/media/router/OWNERS
+# COMPONENT: Internals>Cast
diff --git a/chromium/chrome/common/media_router/discovery/DEPS b/chromium/chrome/common/media_router/discovery/DEPS
new file mode 100644
index 00000000000..f57b34b230c
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/cast_channel"
+]
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_internal.cc b/chromium/chrome/common/media_router/discovery/media_sink_internal.cc
new file mode 100644
index 00000000000..e39d0ebddbb
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_internal.cc
@@ -0,0 +1,211 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/discovery/media_sink_internal.h"
+
+#include <new>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+
+namespace media_router {
+
+MediaSinkInternal::MediaSinkInternal(const MediaSink& sink,
+ const DialSinkExtraData& dial_data)
+ : sink_(sink), sink_type_(SinkType::DIAL), dial_data_(dial_data) {}
+
+MediaSinkInternal::MediaSinkInternal(const MediaSink& sink,
+ const CastSinkExtraData& cast_data)
+ : sink_(sink), sink_type_(SinkType::CAST), cast_data_(cast_data) {}
+
+MediaSinkInternal::MediaSinkInternal() : sink_type_(SinkType::GENERIC) {}
+
+MediaSinkInternal::MediaSinkInternal(const MediaSinkInternal& other) {
+ InternalCopyConstructFrom(other);
+}
+
+MediaSinkInternal::MediaSinkInternal(MediaSinkInternal&& other) noexcept {
+ InternalMoveConstructFrom(std::move(other));
+}
+
+MediaSinkInternal::~MediaSinkInternal() {
+ InternalCleanup();
+}
+
+MediaSinkInternal& MediaSinkInternal::operator=(
+ const MediaSinkInternal& other) {
+ if (this != &other) {
+ InternalCleanup();
+ InternalCopyConstructFrom(other);
+ }
+ return *this;
+}
+
+MediaSinkInternal& MediaSinkInternal::operator=(
+ MediaSinkInternal&& other) noexcept {
+ if (this != &other) {
+ InternalCleanup();
+ InternalMoveConstructFrom(std::move(other));
+ }
+ return *this;
+}
+
+bool MediaSinkInternal::operator==(const MediaSinkInternal& other) const {
+ if (sink_type_ != other.sink_type_)
+ return false;
+
+ if (sink_ != other.sink_)
+ return false;
+
+ switch (sink_type_) {
+ case SinkType::DIAL:
+ return dial_data_ == other.dial_data_;
+ case SinkType::CAST:
+ return cast_data_ == other.cast_data_;
+ case SinkType::GENERIC:
+ return true;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool MediaSinkInternal::operator!=(const MediaSinkInternal& other) const {
+ return !operator==(other);
+}
+
+bool MediaSinkInternal::operator<(const MediaSinkInternal& other) const {
+ return sink_.id() < other.sink().id();
+}
+
+void MediaSinkInternal::set_sink(const MediaSink& sink) {
+ sink_ = sink;
+}
+
+void MediaSinkInternal::set_dial_data(const DialSinkExtraData& dial_data) {
+ DCHECK(sink_type_ != SinkType::CAST);
+ InternalCleanup();
+
+ sink_type_ = SinkType::DIAL;
+ new (&dial_data_) DialSinkExtraData(dial_data);
+}
+
+const DialSinkExtraData& MediaSinkInternal::dial_data() const {
+ DCHECK(is_dial_sink());
+ return dial_data_;
+}
+
+void MediaSinkInternal::set_cast_data(const CastSinkExtraData& cast_data) {
+ DCHECK(sink_type_ != SinkType::DIAL);
+ InternalCleanup();
+
+ sink_type_ = SinkType::CAST;
+ new (&cast_data_) CastSinkExtraData(cast_data);
+}
+
+const CastSinkExtraData& MediaSinkInternal::cast_data() const {
+ DCHECK(is_cast_sink());
+ return cast_data_;
+}
+
+CastSinkExtraData& MediaSinkInternal::cast_data() {
+ DCHECK(is_cast_sink());
+ return cast_data_;
+}
+
+// static
+bool MediaSinkInternal::IsValidSinkId(const std::string& sink_id) {
+ if (sink_id.empty() || !base::IsStringASCII(sink_id)) {
+ DLOG(WARNING) << "Invalid [sink_id]: " << sink_id;
+ return false;
+ }
+
+ return true;
+}
+
+// static
+std::string MediaSinkInternal::ProcessDeviceUUID(
+ const std::string& device_uuid) {
+ if (device_uuid.empty())
+ return std::string();
+
+ std::string result = device_uuid;
+ if (base::StartsWith(device_uuid, "uuid:", base::CompareCase::SENSITIVE))
+ result = device_uuid.substr(5);
+
+ base::RemoveChars(result, "-", &result);
+ return base::ToLowerASCII(result);
+}
+
+void MediaSinkInternal::InternalCopyConstructFrom(
+ const MediaSinkInternal& other) {
+ sink_ = other.sink_;
+ sink_type_ = other.sink_type_;
+
+ switch (sink_type_) {
+ case SinkType::DIAL:
+ new (&dial_data_) DialSinkExtraData(other.dial_data_);
+ return;
+ case SinkType::CAST:
+ new (&cast_data_) CastSinkExtraData(other.cast_data_);
+ return;
+ case SinkType::GENERIC:
+ return;
+ }
+ NOTREACHED();
+}
+
+void MediaSinkInternal::InternalMoveConstructFrom(MediaSinkInternal&& other) {
+ sink_ = std::move(other.sink_);
+ sink_type_ = other.sink_type_;
+
+ switch (sink_type_) {
+ case SinkType::DIAL:
+ new (&dial_data_) DialSinkExtraData(std::move(other.dial_data_));
+ return;
+ case SinkType::CAST:
+ new (&cast_data_) CastSinkExtraData(std::move(other.cast_data_));
+ return;
+ case SinkType::GENERIC:
+ return;
+ }
+ NOTREACHED();
+}
+
+void MediaSinkInternal::InternalCleanup() {
+ switch (sink_type_) {
+ case SinkType::DIAL:
+ dial_data_.~DialSinkExtraData();
+ return;
+ case SinkType::CAST:
+ cast_data_.~CastSinkExtraData();
+ return;
+ case SinkType::GENERIC:
+ return;
+ }
+ NOTREACHED();
+}
+
+DialSinkExtraData::DialSinkExtraData() = default;
+DialSinkExtraData::DialSinkExtraData(const DialSinkExtraData& other) = default;
+DialSinkExtraData::DialSinkExtraData(DialSinkExtraData&& other) = default;
+DialSinkExtraData::~DialSinkExtraData() = default;
+
+bool DialSinkExtraData::operator==(const DialSinkExtraData& other) const {
+ return ip_address == other.ip_address && model_name == other.model_name &&
+ app_url == other.app_url;
+}
+
+CastSinkExtraData::CastSinkExtraData() = default;
+CastSinkExtraData::CastSinkExtraData(const CastSinkExtraData& other) = default;
+CastSinkExtraData::CastSinkExtraData(CastSinkExtraData&& other) = default;
+CastSinkExtraData::~CastSinkExtraData() = default;
+
+bool CastSinkExtraData::operator==(const CastSinkExtraData& other) const {
+ return ip_endpoint == other.ip_endpoint && model_name == other.model_name &&
+ capabilities == other.capabilities &&
+ cast_channel_id == other.cast_channel_id &&
+ discovered_by_dial == other.discovered_by_dial;
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_internal.h b/chromium/chrome/common/media_router/discovery/media_sink_internal.h
new file mode 100644
index 00000000000..5dce50cdfa0
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_internal.h
@@ -0,0 +1,142 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_INTERNAL_H_
+#define CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_INTERNAL_H_
+
+#include <utility>
+
+#include "chrome/common/media_router/media_sink.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "url/gurl.h"
+
+namespace media_router {
+
+// Extra data for DIAL media sink.
+struct DialSinkExtraData {
+ net::IPAddress ip_address;
+
+ // Model name of the sink.
+ std::string model_name;
+
+ // The base URL used for DIAL operations.
+ GURL app_url;
+
+ DialSinkExtraData();
+ DialSinkExtraData(const DialSinkExtraData& other);
+ DialSinkExtraData(DialSinkExtraData&& other);
+ ~DialSinkExtraData();
+
+ bool operator==(const DialSinkExtraData& other) const;
+};
+
+// Extra data for Cast media sink.
+struct CastSinkExtraData {
+ net::IPEndPoint ip_endpoint;
+
+ int port = 0;
+
+ // Model name of the sink.
+ std::string model_name;
+
+ // A bit vector representing the capabilities of the sink. The values are
+ // defined in media_router.mojom.
+ uint8_t capabilities = 0;
+
+ // ID of Cast channel opened for the sink. The caller must set this value to a
+ // valid cast_channel_id. The cast_channel_id may change over time as the
+ // browser reconnects to a device.
+ int cast_channel_id = 0;
+
+ // True if Cast channel is opened from DIAL sink.
+ bool discovered_by_dial = false;
+
+ CastSinkExtraData();
+ CastSinkExtraData(const CastSinkExtraData& other);
+ CastSinkExtraData(CastSinkExtraData&& other);
+ ~CastSinkExtraData();
+
+ bool operator==(const CastSinkExtraData& other) const;
+};
+
+// Represents a media sink discovered by MediaSinkService. It is used by
+// MediaSinkService to push MediaSinks with extra data to the
+// MediaRouteProvider, and it is not exposed to users of MediaRouter.
+class MediaSinkInternal {
+ public:
+ // Used by mojo.
+ MediaSinkInternal();
+
+ // Used by MediaSinkService to create media sinks.
+ MediaSinkInternal(const MediaSink& sink, const DialSinkExtraData& dial_data);
+ MediaSinkInternal(const MediaSink& sink, const CastSinkExtraData& cast_data);
+
+ // Used to push instance of this class into vector.
+ MediaSinkInternal(const MediaSinkInternal& other);
+ MediaSinkInternal(MediaSinkInternal&& other) noexcept;
+
+ ~MediaSinkInternal();
+
+ MediaSinkInternal& operator=(const MediaSinkInternal& other);
+ MediaSinkInternal& operator=(MediaSinkInternal&& other) noexcept;
+ bool operator==(const MediaSinkInternal& other) const;
+ bool operator!=(const MediaSinkInternal& other) const;
+ // Sorted by sink id.
+ bool operator<(const MediaSinkInternal& other) const;
+
+ void set_sink(const MediaSink& sink);
+ const MediaSink& sink() const { return sink_; }
+ MediaSink& sink() { return sink_; }
+
+ // TOOD(jrw): Use this method where appropriate.
+ const MediaSink::Id& id() const { return sink_.id(); }
+
+ void set_dial_data(const DialSinkExtraData& dial_data);
+
+ // Must only be called if the sink is a DIAL sink.
+ const DialSinkExtraData& dial_data() const;
+
+ void set_cast_data(const CastSinkExtraData& cast_data);
+
+ // Must only be called if the sink is a Cast sink.
+ const CastSinkExtraData& cast_data() const;
+ CastSinkExtraData& cast_data();
+
+ // TOOD(jrw): Use this method where appropriate.
+ int cast_channel_id() const { return cast_data().cast_channel_id; }
+
+ bool is_dial_sink() const { return sink_type_ == SinkType::DIAL; }
+ bool is_cast_sink() const { return sink_type_ == SinkType::CAST; }
+
+ static bool IsValidSinkId(const std::string& sink_id);
+
+ // Returns processed device id without "uuid:" and "-", e.g. input
+ // "uuid:6d238518-a574-eab1-017e-d0975c039081" and output
+ // "6d238518a574eab1017ed0975c039081"
+ static std::string ProcessDeviceUUID(const std::string& device_uuid);
+
+ private:
+ void InternalCopyConstructFrom(const MediaSinkInternal& other);
+ void InternalMoveConstructFrom(MediaSinkInternal&& other);
+ void InternalCleanup();
+
+ enum class SinkType { GENERIC, DIAL, CAST };
+
+ MediaSink sink_;
+
+ SinkType sink_type_;
+
+ union {
+ // Set if sink is DIAL sink.
+ DialSinkExtraData dial_data_;
+
+ // Set if sink is Cast sink.
+ CastSinkExtraData cast_data_;
+ };
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_INTERNAL_H_
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_internal_unittest.cc b/chromium/chrome/common/media_router/discovery/media_sink_internal_unittest.cc
new file mode 100644
index 00000000000..ebbb1659d9c
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_internal_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/discovery/media_sink_internal.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+constexpr char kSinkId[] = "sinkId123";
+constexpr char kSinkName[] = "The sink";
+constexpr char kIPAddress[] = "192.168.1.2";
+constexpr char kModelName[] = "model name";
+constexpr char kAppUrl[] = "https://example.com";
+
+media_router::DialSinkExtraData CreateDialSinkExtraData(
+ const std::string& model_name,
+ const std::string& ip_address,
+ const std::string& app_url) {
+ media_router::DialSinkExtraData dial_extra_data;
+ EXPECT_TRUE(dial_extra_data.ip_address.AssignFromIPLiteral(ip_address));
+ dial_extra_data.model_name = model_name;
+ dial_extra_data.app_url = GURL(app_url);
+
+ return dial_extra_data;
+}
+
+media_router::CastSinkExtraData CreateCastSinkExtraData(
+ const std::string& model_name,
+ const std::string& ip_address,
+ uint8_t capabilities,
+ int cast_channel_id) {
+ media_router::CastSinkExtraData cast_extra_data;
+ net::IPAddress ip;
+ EXPECT_TRUE(ip.AssignFromIPLiteral(ip_address));
+ cast_extra_data.ip_endpoint = net::IPEndPoint(ip, 1234);
+ cast_extra_data.model_name = model_name;
+ cast_extra_data.capabilities = 2;
+ cast_extra_data.cast_channel_id = 3;
+ return cast_extra_data;
+}
+
+// static
+media_router::DialSinkExtraData CreateDialSinkExtraData() {
+ return CreateDialSinkExtraData(kModelName, kIPAddress, kAppUrl);
+}
+
+// static
+media_router::CastSinkExtraData CreateCastSinkExtraData() {
+ return CreateCastSinkExtraData(kModelName, kIPAddress, 2, 3);
+}
+
+} // namespace
+
+namespace media_router {
+
+TEST(MediaSinkInternalTest, TestIsValidSinkId) {
+ EXPECT_FALSE(MediaSinkInternal::IsValidSinkId(""));
+ EXPECT_TRUE(MediaSinkInternal::IsValidSinkId("rjuKv_yxhY4jg7QBIp0kbngLjR6A"));
+}
+
+TEST(MediaSinkInternalTest, TestConstructorAndAssignment) {
+ MediaSink sink(kSinkId, kSinkName, SinkIconType::CAST);
+ DialSinkExtraData dial_extra_data = CreateDialSinkExtraData();
+ CastSinkExtraData cast_extra_data = CreateCastSinkExtraData();
+
+ MediaSinkInternal generic_sink;
+ generic_sink.set_sink(sink);
+ MediaSinkInternal dial_sink(sink, dial_extra_data);
+ MediaSinkInternal cast_sink(sink, cast_extra_data);
+
+ MediaSinkInternal copied_generic_sink(generic_sink);
+ MediaSinkInternal copied_dial_sink(dial_sink);
+ MediaSinkInternal copied_cast_sink(cast_sink);
+
+ ASSERT_TRUE(generic_sink == copied_generic_sink);
+ ASSERT_TRUE(dial_sink == copied_dial_sink);
+ ASSERT_TRUE(cast_sink == copied_cast_sink);
+
+ MediaSinkInternal assigned_empty_sink;
+ MediaSinkInternal assigned_generic_sink = generic_sink;
+ MediaSinkInternal assigned_dial_sink = dial_sink;
+ MediaSinkInternal assigned_cast_sink = cast_sink;
+
+ std::vector<MediaSinkInternal> assigned_sinks(
+ {assigned_empty_sink, assigned_generic_sink, assigned_dial_sink,
+ assigned_cast_sink});
+ std::vector<MediaSinkInternal> original_sinks(
+ {generic_sink, dial_sink, cast_sink});
+
+ for (auto& actual_sink : assigned_sinks) {
+ for (const auto& original_sink : original_sinks) {
+ actual_sink = original_sink;
+ EXPECT_EQ(original_sink, actual_sink);
+ }
+ }
+}
+
+TEST(MediaSinkInternalTest, TestSetExtraData) {
+ MediaSink sink(kSinkId, kSinkName, SinkIconType::CAST);
+ DialSinkExtraData dial_extra_data = CreateDialSinkExtraData();
+ CastSinkExtraData cast_extra_data = CreateCastSinkExtraData();
+
+ MediaSinkInternal dial_sink1;
+ dial_sink1.set_dial_data(dial_extra_data);
+ ASSERT_EQ(dial_extra_data, dial_sink1.dial_data());
+
+ MediaSinkInternal cast_sink1;
+ cast_sink1.set_cast_data(cast_extra_data);
+ ASSERT_EQ(cast_extra_data, cast_sink1.cast_data());
+
+ DialSinkExtraData dial_extra_data2 = CreateDialSinkExtraData(
+ "new_dial_model_name", "192.1.2.100", "https://example2.com");
+ CastSinkExtraData cast_extra_data2 =
+ CreateCastSinkExtraData("new_cast_model_name", "192.1.2.101", 4, 5);
+
+ MediaSinkInternal dial_sink2(sink, dial_extra_data);
+ dial_sink2.set_dial_data(dial_extra_data2);
+ ASSERT_EQ(dial_extra_data2, dial_sink2.dial_data());
+
+ MediaSinkInternal cast_sink2(sink, cast_extra_data);
+ cast_sink2.set_cast_data(cast_extra_data2);
+ ASSERT_EQ(cast_extra_data2, cast_sink2.cast_data());
+}
+
+TEST(MediaSinkInternalTest, TestProcessDeviceUUID) {
+ EXPECT_EQ("de51d94921f15f8af6dbf65592bb3610",
+ MediaSinkInternal::ProcessDeviceUUID(
+ "uuid:de51d949-21f1-5f8a-f6db-f65592bb3610"));
+ EXPECT_EQ("de51d94921f15f8af6dbf65592bb3610",
+ MediaSinkInternal::ProcessDeviceUUID(
+ "de51d94921f1-5f8a-f6db-f65592bb3610"));
+ EXPECT_EQ(
+ "de51d94921f15f8af6dbf65592bb3610",
+ MediaSinkInternal::ProcessDeviceUUID("DE51D94921F15F8AF6DBF65592BB3610"));
+ EXPECT_EQ("abc:de51d94921f15f8af6dbf65592bb3610",
+ MediaSinkInternal::ProcessDeviceUUID(
+ "abc:de51d949-21f1-5f8a-f6db-f65592bb3610"));
+ EXPECT_EQ("", MediaSinkInternal::ProcessDeviceUUID(""));
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_service_base.cc b/chromium/chrome/common/media_router/discovery/media_sink_service_base.cc
new file mode 100644
index 00000000000..009517d7f86
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_service_base.cc
@@ -0,0 +1,125 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/discovery/media_sink_service_base.h"
+#include "base/bind.h"
+#include "chrome/common/media_router/media_route.h"
+
+#include <vector>
+
+namespace {
+// Timeout amount for |discovery_timer_|.
+const constexpr base::TimeDelta kDiscoveryTimeout =
+ base::TimeDelta::FromSeconds(3);
+} // namespace
+
+namespace media_router {
+
+MediaSinkServiceBase::MediaSinkServiceBase(
+ const OnSinksDiscoveredCallback& callback)
+ : discovery_timer_(std::make_unique<base::OneShotTimer>()),
+ on_sinks_discovered_cb_(callback) {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+MediaSinkServiceBase::~MediaSinkServiceBase() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void MediaSinkServiceBase::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.AddObserver(observer);
+}
+
+void MediaSinkServiceBase::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+const base::flat_map<MediaSink::Id, MediaSinkInternal>&
+MediaSinkServiceBase::GetSinks() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return sinks_;
+}
+
+const MediaSinkInternal* MediaSinkServiceBase::GetSinkById(
+ const MediaSink::Id& sink_id) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto it = sinks_.find(sink_id);
+ return it != sinks_.end() ? &it->second : nullptr;
+}
+
+const MediaSinkInternal* MediaSinkServiceBase::GetSinkByRoute(
+ const MediaRoute& route) const {
+ return GetSinkById(route.media_sink_id());
+}
+
+void MediaSinkServiceBase::AddOrUpdateSink(const MediaSinkInternal& sink) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ sinks_.insert_or_assign(sink.sink().id(), sink);
+ for (auto& observer : observers_)
+ observer.OnSinkAddedOrUpdated(sink);
+
+ StartTimer();
+}
+
+void MediaSinkServiceBase::RemoveSink(const MediaSinkInternal& sink) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ RemoveSinkById(sink.sink().id());
+}
+
+void MediaSinkServiceBase::RemoveSinkById(const MediaSink::Id& sink_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto it = sinks_.find(sink_id);
+ if (it == sinks_.end())
+ return;
+
+ MediaSinkInternal sink = std::move(it->second);
+ sinks_.erase(it);
+ for (auto& observer : observers_)
+ observer.OnSinkRemoved(sink);
+
+ StartTimer();
+}
+
+void MediaSinkServiceBase::SetTimerForTest(
+ std::unique_ptr<base::OneShotTimer> timer) {
+ discovery_timer_ = std::move(timer);
+}
+
+void MediaSinkServiceBase::StartTimer() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (discovery_timer_->IsRunning())
+ return;
+
+ discovery_timer_->Start(
+ FROM_HERE, kDiscoveryTimeout,
+ base::BindRepeating(&MediaSinkServiceBase::OnDiscoveryComplete,
+ base::Unretained(this)));
+}
+
+void MediaSinkServiceBase::OnDiscoveryComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ discovery_timer_->Stop();
+ RecordDeviceCounts();
+
+ // Only send discovered sinks back to MediaRouter if the list changed.
+ if (sinks_ == previous_sinks_) {
+ DVLOG(2) << "No update to sink list.";
+ return;
+ }
+
+ DVLOG(2) << "Send sinks to media router, [size]: " << sinks_.size();
+
+ std::vector<MediaSinkInternal> sinks;
+ for (const auto& sink_it : sinks_)
+ sinks.push_back(sink_it.second);
+
+ for (auto& observer : observers_)
+ observer.OnSinksDiscovered(sinks);
+ on_sinks_discovered_cb_.Run(std::move(sinks));
+ previous_sinks_ = sinks_;
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_service_base.h b/chromium/chrome/common/media_router/discovery/media_sink_service_base.h
new file mode 100644
index 00000000000..df9547f1e28
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_service_base.h
@@ -0,0 +1,135 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_BASE_H_
+#define CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_BASE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/gtest_prod_util.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
+#include "chrome/common/media_router/discovery/media_sink_internal.h"
+#include "chrome/common/media_router/discovery/media_sink_service_util.h"
+
+namespace media_router {
+
+class MediaRoute;
+
+// Base class for discovering MediaSinks. Responsible for bookkeeping of
+// current set of discovered sinks, and notifying observers when there are
+// updates.
+// In addition, this class maintains a "discovery timer", used for batching
+// updates in quick succession. The timer fires when it is assumed that
+// discovery has reached a relatively steady state. When the timer fires:
+// - The batched updated sink list will be sent back to the Media Router
+// extension via |callback|. This back-channel is necessary until all logic
+// dependent on MediaSinks are moved out of the extension.
+// - Subclasses may record discovered related metrics.
+// This class may be created on any thread, but all subsequent methods must be
+// invoked on the same thread.
+class MediaSinkServiceBase {
+ public:
+ // Listens for sink updates in MediaSinkServiceBase.
+ class Observer {
+ public:
+ virtual ~Observer() = default;
+
+ // Invoked when the list of discovered sinks changes.
+ virtual void OnSinksDiscovered(
+ const std::vector<MediaSinkInternal>& sinks) {}
+
+ // Invoked when |sink| is added or updated.
+ virtual void OnSinkAddedOrUpdated(const MediaSinkInternal& sink) {}
+
+ // Invoked when |sink| is removed.
+ virtual void OnSinkRemoved(const MediaSinkInternal& sink) {}
+ };
+
+ // |callback|: Callback to inform the MediaRouter extension of discovered
+ // sinks updates. Other uses should implement Observer::OnSinksDiscovered().
+ explicit MediaSinkServiceBase(const OnSinksDiscoveredCallback& callback);
+ virtual ~MediaSinkServiceBase();
+
+ // Adds |observer| to observe |this| for sink updates.
+ // Caller is responsible for calling |RemoveObserver| before it is destroyed.
+ // Both methods are safe to call on any thread.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Overridden by subclass to initiate action triggered by user gesture, e.g.
+ // start one-off round of discovery.
+ virtual void OnUserGesture() {}
+
+ // Adds or updates, or removes a sink.
+ // Notifies |observers_| that the sink has been added, updated, or removed.
+ // Also invokes |StartTimer()|.
+ void AddOrUpdateSink(const MediaSinkInternal& sink);
+ void RemoveSink(const MediaSinkInternal& sink);
+ void RemoveSinkById(const MediaSink::Id& sink_id);
+
+ const base::flat_map<MediaSink::Id, MediaSinkInternal>& GetSinks() const;
+
+ // These methods return nullptr when no sink is found.
+ const MediaSinkInternal* GetSinkById(const MediaSink::Id& sink_id) const;
+ const MediaSinkInternal* GetSinkByRoute(const MediaRoute& route) const;
+
+ void SetTimerForTest(std::unique_ptr<base::OneShotTimer> timer);
+
+ protected:
+ // Called when |discovery_timer_| expires. Informs subclass to report device
+ // counts. Also informs Media Router of updated list of discovered sinks.
+ // May be overridden by subclass to perform additional operations, such as
+ // pruning old sinks.
+ virtual void OnDiscoveryComplete();
+
+ // Starts |discovery_timer_| to invoke |OnDiscoveryComplete()|. Subclasses
+ // may call this at the start of a round of discovery.
+ void StartTimer();
+
+ private:
+ friend class MediaSinkServiceBaseTest;
+ FRIEND_TEST_ALL_PREFIXES(MediaSinkServiceBaseTest,
+ TestOnDiscoveryComplete_SameSink);
+ FRIEND_TEST_ALL_PREFIXES(MediaSinkServiceBaseTest,
+ TestOnDiscoveryComplete_SameSinkDifferentOrders);
+
+ // Overriden by subclass to report device counts.
+ virtual void RecordDeviceCounts() {}
+
+ // The current set of discovered sinks keyed by MediaSink ID.
+ base::flat_map<MediaSink::Id, MediaSinkInternal> sinks_;
+
+ // Observers to notify when a sink is added, updated, or removed.
+ base::ObserverList<Observer>::Unchecked observers_;
+
+ // Timer for recording device counts after a sink list has changed. To ensure
+ // the metrics are recorded accurately, a small delay is introduced after a
+ // sink list change in order for the discovery process to reach a steady
+ // state before the metrics are recorded.
+ std::unique_ptr<base::OneShotTimer> discovery_timer_;
+
+ // The following fields exist temporarily for sending back discovered sinks to
+ // the Media Router extension.
+ // TODO(https://crbug.com/809249): Remove once the extension no longer need
+ // the sinks.
+
+ // Callback to MediaRouter to provide sinks to the MR extension.
+ OnSinksDiscoveredCallback on_sinks_discovered_cb_;
+
+ // Sinks saved in the previous |OnDiscoveryComplete()| invocation. Checked
+ // against |sinks_| during |OnDiscoveryComplete()| before invoking
+ // |on_sinks_discovered_cb_|.
+ base::flat_map<MediaSink::Id, MediaSinkInternal> previous_sinks_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ DISALLOW_COPY_AND_ASSIGN(MediaSinkServiceBase);
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_BASE_H_
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_service_base_unittest.cc b/chromium/chrome/common/media_router/discovery/media_sink_service_base_unittest.cc
new file mode 100644
index 00000000000..6f3bc33248e
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_service_base_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/discovery/media_sink_service_base.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/test/mock_callback.h"
+#include "base/timer/mock_timer.h"
+#include "chrome/common/media_router/test/test_helper.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace {
+
+media_router::DialSinkExtraData CreateDialSinkExtraData(
+ const std::string& model_name,
+ const std::string& ip_address,
+ const std::string& app_url) {
+ media_router::DialSinkExtraData dial_extra_data;
+ EXPECT_TRUE(dial_extra_data.ip_address.AssignFromIPLiteral(ip_address));
+ dial_extra_data.model_name = model_name;
+ dial_extra_data.app_url = GURL(app_url);
+ return dial_extra_data;
+}
+
+std::vector<media_router::MediaSinkInternal> CreateDialMediaSinks() {
+ media_router::MediaSink sink1("sink1", "sink_name_1",
+ media_router::SinkIconType::CAST);
+ media_router::DialSinkExtraData extra_data1 = CreateDialSinkExtraData(
+ "model_name1", "192.168.1.1", "https://example1.com");
+
+ media_router::MediaSink sink2("sink2", "sink_name_2",
+ media_router::SinkIconType::CAST);
+ media_router::DialSinkExtraData extra_data2 = CreateDialSinkExtraData(
+ "model_name2", "192.168.1.2", "https://example2.com");
+
+ std::vector<media_router::MediaSinkInternal> sinks;
+ sinks.push_back(media_router::MediaSinkInternal(sink1, extra_data1));
+ sinks.push_back(media_router::MediaSinkInternal(sink2, extra_data2));
+ return sinks;
+}
+
+} // namespace
+
+namespace media_router {
+
+class MediaSinkServiceBaseTest : public ::testing::Test {
+ public:
+ MediaSinkServiceBaseTest()
+ : media_sink_service_(mock_sink_discovered_cb_.Get()) {}
+ ~MediaSinkServiceBaseTest() override {}
+
+ void PopulateSinks(const std::vector<MediaSinkInternal>& old_sinks,
+ const std::vector<MediaSinkInternal>& new_sinks) {
+ media_sink_service_.previous_sinks_.clear();
+ for (const auto& old_sink : old_sinks)
+ media_sink_service_.previous_sinks_.emplace(old_sink.sink().id(),
+ old_sink);
+
+ media_sink_service_.sinks_.clear();
+ for (const auto& new_sink : new_sinks)
+ media_sink_service_.sinks_.emplace(new_sink.sink().id(), new_sink);
+ }
+
+ void TestOnDiscoveryComplete(
+ const std::vector<MediaSinkInternal>& old_sinks,
+ const std::vector<MediaSinkInternal>& new_sinks) {
+ PopulateSinks(old_sinks, new_sinks);
+ EXPECT_CALL(mock_sink_discovered_cb_, Run(new_sinks));
+ media_sink_service_.OnDiscoveryComplete();
+ }
+
+ protected:
+ base::MockCallback<OnSinksDiscoveredCallback> mock_sink_discovered_cb_;
+ TestMediaSinkService media_sink_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaSinkServiceBaseTest);
+};
+
+TEST_F(MediaSinkServiceBaseTest, TestOnDiscoveryComplete_SameSink) {
+ std::vector<MediaSinkInternal> old_sinks;
+ std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks();
+ TestOnDiscoveryComplete(old_sinks, new_sinks);
+
+ // Same sink
+ EXPECT_CALL(mock_sink_discovered_cb_, Run(new_sinks)).Times(0);
+ media_sink_service_.OnDiscoveryComplete();
+}
+
+TEST_F(MediaSinkServiceBaseTest,
+ TestOnDiscoveryComplete_SameSinkDifferentOrders) {
+ std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks();
+ std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks();
+ std::reverse(new_sinks.begin(), new_sinks.end());
+
+ PopulateSinks(old_sinks, new_sinks);
+ EXPECT_CALL(mock_sink_discovered_cb_, Run(new_sinks)).Times(0);
+ media_sink_service_.OnDiscoveryComplete();
+}
+
+TEST_F(MediaSinkServiceBaseTest, TestOnDiscoveryComplete_OneNewSink) {
+ std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks();
+ std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks();
+ MediaSink sink3("sink3", "sink_name_3", SinkIconType::CAST);
+ DialSinkExtraData extra_data3 = CreateDialSinkExtraData(
+ "model_name3", "192.168.1.3", "https://example3.com");
+ new_sinks.push_back(MediaSinkInternal(sink3, extra_data3));
+ TestOnDiscoveryComplete(old_sinks, new_sinks);
+}
+
+TEST_F(MediaSinkServiceBaseTest, TestOnDiscoveryComplete_RemovedOneSink) {
+ std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks();
+ std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks();
+ new_sinks.erase(new_sinks.begin());
+ TestOnDiscoveryComplete(old_sinks, new_sinks);
+}
+
+TEST_F(MediaSinkServiceBaseTest, TestOnDiscoveryComplete_UpdatedOneSink) {
+ std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks();
+ std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks();
+ new_sinks[0].sink().set_name("sink_name_4");
+ TestOnDiscoveryComplete(old_sinks, new_sinks);
+}
+
+TEST_F(MediaSinkServiceBaseTest, TestOnDiscoveryComplete_Mixed) {
+ std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks();
+
+ MediaSink sink1("sink1", "sink_name_1", SinkIconType::CAST);
+ DialSinkExtraData extra_data2 = CreateDialSinkExtraData(
+ "model_name2", "192.168.1.2", "https://example2.com");
+
+ MediaSink sink3("sink3", "sink_name_3", SinkIconType::CAST);
+ DialSinkExtraData extra_data3 = CreateDialSinkExtraData(
+ "model_name3", "192.168.1.3", "https://example3.com");
+
+ std::vector<MediaSinkInternal> new_sinks;
+ new_sinks.push_back(MediaSinkInternal(sink1, extra_data2));
+ new_sinks.push_back(MediaSinkInternal(sink3, extra_data3));
+
+ TestOnDiscoveryComplete(old_sinks, new_sinks);
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_service_util.cc b/chromium/chrome/common/media_router/discovery/media_sink_service_util.cc
new file mode 100644
index 00000000000..5c3b63238f3
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_service_util.cc
@@ -0,0 +1,29 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/discovery/media_sink_service_util.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/sequenced_task_runner.h"
+
+namespace media_router {
+
+void RunSinksDiscoveredCallbackOnSequence(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ const OnSinksDiscoveredCallback& callback,
+ std::vector<MediaSinkInternal> sinks) {
+ task_runner->PostTask(FROM_HERE, base::BindOnce(callback, std::move(sinks)));
+}
+
+void RunAvailableSinksUpdatedCallbackOnSequence(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ const OnAvailableSinksUpdatedCallback& callback,
+ const std::string& app_name,
+ std::vector<MediaSinkInternal> available_sinks) {
+ task_runner->PostTask(FROM_HERE, base::BindOnce(callback, app_name,
+ std::move(available_sinks)));
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/discovery/media_sink_service_util.h b/chromium/chrome/common/media_router/discovery/media_sink_service_util.h
new file mode 100644
index 00000000000..0cd98f32efb
--- /dev/null
+++ b/chromium/chrome/common/media_router/discovery/media_sink_service_util.h
@@ -0,0 +1,44 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_UTIL_H_
+#define CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_UTIL_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "chrome/common/media_router/discovery/media_sink_internal.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace media_router {
+
+using OnSinksDiscoveredCallback =
+ base::RepeatingCallback<void(std::vector<MediaSinkInternal>)>;
+
+// Called when a new sink becomes available or an available sink becomes
+// unavailable for |app_name|.
+// |app_name|: app name on receiver device (e.g. YouTube)
+// |available_sinks|: list of currently available sinks for |app_name|
+using OnAvailableSinksUpdatedCallback = base::RepeatingCallback<void(
+ const std::string& app_name,
+ std::vector<MediaSinkInternal> available_sinks)>;
+
+// Runs |sinks_discovered_cb| with |sinks| on |task_runner|.
+void RunSinksDiscoveredCallbackOnSequence(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ const OnSinksDiscoveredCallback& callback,
+ std::vector<MediaSinkInternal> sinks);
+
+void RunAvailableSinksUpdatedCallbackOnSequence(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ const OnAvailableSinksUpdatedCallback& callback,
+ const std::string& app_name,
+ std::vector<MediaSinkInternal> available_sinks);
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_UTIL_H_
diff --git a/chromium/chrome/common/media_router/issue.cc b/chromium/chrome/common/media_router/issue.cc
new file mode 100644
index 00000000000..76972970aea
--- /dev/null
+++ b/chromium/chrome/common/media_router/issue.cc
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/issue.h"
+
+#include "base/atomic_sequence_num.h"
+
+namespace media_router {
+
+namespace {
+// ID generator for Issue.
+base::AtomicSequenceNumber g_next_issue_id;
+}
+
+IssueInfo::IssueInfo()
+ : default_action(IssueInfo::Action::DISMISS),
+ severity(IssueInfo::Severity::NOTIFICATION),
+ is_blocking(false),
+ help_page_id(IssueInfo::kUnknownHelpPageId) {}
+
+IssueInfo::IssueInfo(const std::string& title,
+ const Action default_action,
+ Severity severity)
+ : title(title),
+ default_action(default_action),
+ severity(severity),
+ is_blocking(severity == IssueInfo::Severity::FATAL),
+ help_page_id(IssueInfo::kUnknownHelpPageId) {}
+
+IssueInfo::IssueInfo(const IssueInfo& other) = default;
+
+IssueInfo::~IssueInfo() = default;
+
+IssueInfo& IssueInfo::operator=(const IssueInfo& other) = default;
+
+bool IssueInfo::operator==(const IssueInfo& other) const {
+ return title == other.title && default_action == other.default_action &&
+ severity == other.severity && message == other.message &&
+ secondary_actions == other.secondary_actions &&
+ route_id == other.route_id && is_blocking == other.is_blocking &&
+ help_page_id == other.help_page_id;
+}
+
+Issue::Issue(const IssueInfo& info)
+ : id_(g_next_issue_id.GetNext()), info_(info) {}
+
+Issue::~Issue() = default;
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/issue.h b/chromium/chrome/common/media_router/issue.h
new file mode 100644
index 00000000000..8b790b6cf72
--- /dev/null
+++ b/chromium/chrome/common/media_router/issue.h
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_ISSUE_H_
+#define CHROME_COMMON_MEDIA_ROUTER_ISSUE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "chrome/common/media_router/media_route.h"
+#include "chrome/common/media_router/media_sink.h"
+
+namespace media_router {
+
+// Contains the information relevant to an issue.
+struct IssueInfo {
+ public:
+ // Possible actions for an issue.
+ enum class Action {
+ DISMISS,
+ // NOTE: If LEARN_MORE is set as a possible action for an issue, then its
+ // |help_page_id_| must also be set to a valid value.
+ LEARN_MORE,
+
+ // Denotes enum value boundary. New values should be added above.
+ NUM_VALUES = LEARN_MORE
+ };
+
+ // Severity type of an issue. A FATAL issue is considered blocking. Although
+ // issues of other severity levels may also be blocking.
+ enum class Severity { FATAL, WARNING, NOTIFICATION };
+
+ static const int kUnknownHelpPageId = 0;
+
+ // Used by Mojo and testing only.
+ IssueInfo();
+
+ // |title|: The title for the issue.
+ // |default_action|: Default action user can take to resolve the issue.
+ // |severity|: The severity of the issue. If FATAL, then |is_blocking| is set
+ // to |true|.
+ IssueInfo(const std::string& title, Action default_action, Severity severity);
+ IssueInfo(const IssueInfo& other);
+ ~IssueInfo();
+
+ IssueInfo& operator=(const IssueInfo& other);
+ bool operator==(const IssueInfo& other) const;
+
+ // Fields set with values provided to the constructor.
+ std::string title;
+ Action default_action;
+ Severity severity;
+
+ // Description message for the issue.
+ std::string message;
+
+ // Options the user can take to resolve the issue in addition to the
+ // default action. Can be empty. If non-empty, currently only one secondary
+ // action is supported.
+ std::vector<Action> secondary_actions;
+
+ // ID of route associated with the Issue, or empty if no route is associated
+ // with it.
+ MediaRoute::Id route_id;
+
+ // ID of the sink associated with this issue, or empty if no sink is
+ // associated with it.
+ MediaSink::Id sink_id;
+
+ // |true| if the issue needs to be resolved before continuing. Note that a
+ // Issue of severity FATAL is considered blocking by default.
+ bool is_blocking;
+
+ // ID of help page to link to, if one of the actions is LEARN_MORE.
+ // Defaults to |kUnknownHelpPageId|.
+ int help_page_id;
+};
+
+// An issue that is associated with a globally unique ID. Created by
+// IssueManager when an IssueInfo is added to it.
+class Issue {
+ public:
+ using Id = int;
+ // ID is generated during construction.
+ explicit Issue(const IssueInfo& info);
+ Issue(const Issue& other) = default;
+ Issue& operator=(const Issue& other) = default;
+ ~Issue();
+
+ const Id& id() const { return id_; }
+ const IssueInfo& info() const { return info_; }
+
+ private:
+ Id id_;
+ IssueInfo info_;
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_ISSUE_H_
diff --git a/chromium/chrome/common/media_router/issue_unittest.cc b/chromium/chrome/common/media_router/issue_unittest.cc
new file mode 100644
index 00000000000..8b6235c1a2c
--- /dev/null
+++ b/chromium/chrome/common/media_router/issue_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/issue.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media_router {
+
+namespace {
+
+IssueInfo CreateWarningIssueInfo(IssueInfo::Action action_type) {
+ IssueInfo issue("title", action_type, IssueInfo::Severity::WARNING);
+ issue.message = "message";
+ issue.help_page_id = 12345;
+ return issue;
+}
+
+IssueInfo CreateFatalRouteIssueInfoWithMessage(IssueInfo::Action action_type) {
+ IssueInfo issue("title", action_type, IssueInfo::Severity::FATAL);
+ issue.message = "message";
+ issue.route_id = "routeid";
+ issue.help_page_id = 12345;
+ return issue;
+}
+
+IssueInfo CreateFatalRouteIssueInfo(IssueInfo::Action action_type) {
+ IssueInfo issue("title", action_type, IssueInfo::Severity::FATAL);
+ issue.route_id = "routeid";
+ issue.help_page_id = 12345;
+ return issue;
+}
+
+} // namespace
+
+// Tests Issues without any secondary actions.
+TEST(IssueInfoUnitTest, CustomIssueConstructionWithNoSecondaryActions) {
+ IssueInfo issue1 = CreateWarningIssueInfo(IssueInfo::Action::DISMISS);
+
+ EXPECT_EQ("title", issue1.title);
+ EXPECT_EQ("message", issue1.message);
+ EXPECT_EQ(IssueInfo::Action::DISMISS, issue1.default_action);
+ EXPECT_TRUE(issue1.secondary_actions.empty());
+ EXPECT_EQ(IssueInfo::Severity::WARNING, issue1.severity);
+ EXPECT_EQ("", issue1.route_id);
+ EXPECT_FALSE(issue1.is_blocking);
+ EXPECT_EQ(12345, issue1.help_page_id);
+
+ IssueInfo issue2 =
+ CreateFatalRouteIssueInfoWithMessage(IssueInfo::Action::DISMISS);
+
+ EXPECT_EQ("title", issue2.title);
+ EXPECT_EQ("message", issue2.message);
+ EXPECT_EQ(IssueInfo::Action::DISMISS, issue1.default_action);
+ EXPECT_TRUE(issue2.secondary_actions.empty());
+ EXPECT_EQ(IssueInfo::Severity::FATAL, issue2.severity);
+ EXPECT_EQ("routeid", issue2.route_id);
+ EXPECT_TRUE(issue2.is_blocking);
+ EXPECT_EQ(12345, issue2.help_page_id);
+
+ IssueInfo issue3 = CreateFatalRouteIssueInfo(IssueInfo::Action::DISMISS);
+
+ EXPECT_EQ("title", issue3.title);
+ EXPECT_EQ("", issue3.message);
+ EXPECT_EQ(IssueInfo::Action::DISMISS, issue1.default_action);
+ EXPECT_TRUE(issue3.secondary_actions.empty());
+ EXPECT_EQ(IssueInfo::Severity::FATAL, issue3.severity);
+ EXPECT_EQ("routeid", issue3.route_id);
+ EXPECT_TRUE(issue3.is_blocking);
+ EXPECT_EQ(12345, issue3.help_page_id);
+}
+
+// Tests Issues with secondary actions.
+TEST(IssueInfoUnitTest, CustomIssueConstructionWithSecondaryActions) {
+ std::vector<IssueInfo::Action> secondary_actions;
+ secondary_actions.push_back(IssueInfo::Action::DISMISS);
+
+ IssueInfo issue1 = CreateWarningIssueInfo(IssueInfo::Action::LEARN_MORE);
+ issue1.secondary_actions = secondary_actions;
+
+ EXPECT_EQ("title", issue1.title);
+ EXPECT_EQ("message", issue1.message);
+ EXPECT_EQ(IssueInfo::Action::LEARN_MORE, issue1.default_action);
+ EXPECT_FALSE(issue1.secondary_actions.empty());
+ EXPECT_EQ(1u, issue1.secondary_actions.size());
+ EXPECT_EQ(IssueInfo::Severity::WARNING, issue1.severity);
+ EXPECT_EQ("", issue1.route_id);
+ EXPECT_FALSE(issue1.is_blocking);
+
+ IssueInfo issue2 =
+ CreateFatalRouteIssueInfoWithMessage(IssueInfo::Action::LEARN_MORE);
+ issue2.secondary_actions = secondary_actions;
+
+ EXPECT_EQ("title", issue2.title);
+ EXPECT_EQ("message", issue2.message);
+ EXPECT_EQ(IssueInfo::Action::LEARN_MORE, issue2.default_action);
+ EXPECT_FALSE(issue2.secondary_actions.empty());
+ EXPECT_EQ(1u, issue2.secondary_actions.size());
+ EXPECT_EQ(IssueInfo::Severity::FATAL, issue2.severity);
+ EXPECT_EQ("routeid", issue2.route_id);
+ EXPECT_TRUE(issue2.is_blocking);
+
+ IssueInfo issue3 = CreateFatalRouteIssueInfo(IssueInfo::Action::LEARN_MORE);
+ issue3.secondary_actions = secondary_actions;
+
+ EXPECT_EQ("title", issue3.title);
+ EXPECT_EQ("", issue3.message);
+ EXPECT_EQ(IssueInfo::Action::LEARN_MORE, issue3.default_action);
+ EXPECT_FALSE(issue3.secondary_actions.empty());
+ EXPECT_EQ(1u, issue3.secondary_actions.size());
+ EXPECT_EQ(IssueInfo::Severity::FATAL, issue3.severity);
+ EXPECT_EQ("routeid", issue3.route_id);
+ EXPECT_TRUE(issue3.is_blocking);
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_route.cc b/chromium/chrome/common/media_router/media_route.cc
new file mode 100644
index 00000000000..6e9b40c6d26
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_route.cc
@@ -0,0 +1,64 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_route.h"
+
+#include <ostream>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/media_router/media_source.h"
+
+namespace media_router {
+
+// static
+MediaRoute::Id MediaRoute::GetMediaRouteId(const std::string& presentation_id,
+ const MediaSink::Id& sink_id,
+ const MediaSource& source) {
+ // TODO(https://crbug.com/816628): Can the route ID just be the presentation
+ // id?
+ return base::StringPrintf("urn:x-org.chromium:media:route:%s/%s/%s",
+ presentation_id.c_str(), sink_id.c_str(),
+ source.id().c_str());
+}
+
+MediaRoute::MediaRoute(const MediaRoute::Id& media_route_id,
+ const MediaSource& media_source,
+ const MediaSink::Id& media_sink_id,
+ const std::string& description,
+ bool is_local,
+ bool for_display)
+ : media_route_id_(media_route_id),
+ media_source_(media_source),
+ media_sink_id_(media_sink_id),
+ description_(description),
+ is_local_(is_local),
+ for_display_(for_display),
+ is_incognito_(false),
+ is_local_presentation_(false) {}
+
+MediaRoute::MediaRoute(const MediaRoute& other) = default;
+
+MediaRoute::MediaRoute() {}
+MediaRoute::~MediaRoute() = default;
+
+bool MediaRoute::operator==(const MediaRoute& other) const {
+ return media_route_id_ == other.media_route_id_ &&
+ presentation_id_ == other.presentation_id_ &&
+ media_source_ == other.media_source_ &&
+ media_sink_id_ == other.media_sink_id_ &&
+ description_ == other.description_ && is_local_ == other.is_local_ &&
+ controller_type_ == other.controller_type_ &&
+ for_display_ == other.for_display_ &&
+ is_incognito_ == other.is_incognito_ &&
+ is_local_presentation_ == other.is_local_presentation_;
+}
+
+std::ostream& operator<<(std::ostream& stream, const MediaRoute& route) {
+ return stream << "MediaRoute{id=" << route.media_route_id_
+ << ",source=" << route.media_source_.id()
+ << ",sink=" << route.media_sink_id_ << "}";
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_route.h b/chromium/chrome/common/media_router/media_route.h
new file mode 100644
index 00000000000..0a6daf84ca5
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_route.h
@@ -0,0 +1,144 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_MEDIA_ROUTE_H_
+#define CHROME_COMMON_MEDIA_ROUTER_MEDIA_ROUTE_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/common/media_router/media_sink.h"
+#include "chrome/common/media_router/media_source.h"
+
+namespace media_router {
+
+// TODO(imcheng): Use the Mojo enum directly once we Mojo-ified
+// MediaRouterAndroid.
+enum class RouteControllerType { kNone, kGeneric, kMirroring };
+
+// MediaRoute objects contain the status and metadata of a routing
+// operation. The fields are immutable and reflect the route status
+// only at the time of object creation. Updated route statuses must
+// be retrieved as new MediaRoute objects from the Media Router.
+//
+// TODO(mfoltz): Convert to a simple struct and remove uncommon parameters from
+// the ctor.
+class MediaRoute {
+ public:
+ using Id = std::string;
+
+ static MediaRoute::Id GetMediaRouteId(const std::string& presentation_id,
+ const MediaSink::Id& sink_id,
+ const MediaSource& source);
+
+ // |media_route_id|: ID of the route.
+ // |media_source|: Description of source of the route.
+ // |media_sink|: The sink that is receiving the media.
+ // |description|: Human readable description of the casting activity.
+ // |is_local|: true if the route was created from this browser.
+ // provider. empty otherwise.
+ // |for_display|: Set to true if this route should be displayed for
+ // |media_sink_id| in UI.
+ MediaRoute(const MediaRoute::Id& media_route_id,
+ const MediaSource& media_source,
+ const MediaSink::Id& media_sink_id,
+ const std::string& description,
+ bool is_local,
+ bool for_display);
+ MediaRoute(const MediaRoute& other);
+ MediaRoute();
+
+ ~MediaRoute();
+
+ void set_media_route_id(const MediaRoute::Id& media_route_id) {
+ media_route_id_ = media_route_id;
+ }
+ const MediaRoute::Id& media_route_id() const { return media_route_id_; }
+
+ void set_presentation_id(const std::string& presentation_id) {
+ presentation_id_ = presentation_id;
+ }
+ const std::string& presentation_id() const { return presentation_id_; }
+
+ void set_media_source(const MediaSource& media_source) {
+ media_source_ = media_source;
+ }
+ const MediaSource& media_source() const { return media_source_; }
+
+ void set_media_sink_id(const MediaSink::Id& media_sink_id) {
+ media_sink_id_ = media_sink_id;
+ }
+ const MediaSink::Id& media_sink_id() const { return media_sink_id_; }
+
+ void set_description(const std::string& description) {
+ description_ = description;
+ }
+
+ // TODO(kmarshall): Do we need to pass locale for bidi rendering?
+ const std::string& description() const { return description_; }
+
+ void set_local(bool is_local) { is_local_ = is_local; }
+ bool is_local() const { return is_local_; }
+
+ void set_controller_type(RouteControllerType controller_type) {
+ controller_type_ = controller_type;
+ }
+ RouteControllerType controller_type() const { return controller_type_; }
+
+ void set_for_display(bool for_display) { for_display_ = for_display; }
+ bool for_display() const { return for_display_; }
+
+ void set_incognito(bool is_incognito) { is_incognito_ = is_incognito; }
+ bool is_incognito() const { return is_incognito_; }
+
+ void set_local_presentation(bool is_local_presentation) {
+ is_local_presentation_ = is_local_presentation;
+ }
+ bool is_local_presentation() const { return is_local_presentation_; }
+
+ bool operator==(const MediaRoute& other) const;
+
+ private:
+ friend std::ostream& operator<<(std::ostream& stream,
+ const MediaRoute& route);
+
+ // The media route identifier.
+ MediaRoute::Id media_route_id_;
+
+ // The ID of the presentation that this route is associated with.
+ std::string presentation_id_;
+
+ // The media source being routed.
+ MediaSource media_source_;
+
+ // The ID of sink being routed to.
+ MediaSink::Id media_sink_id_;
+
+ // Human readable description of the casting activity. Examples:
+ // "Mirroring tab (www.example.com)", "Casting media", "Casting YouTube"
+ std::string description_;
+
+ // |true| if the route is created locally (versus discovered by a media route
+ // provider.)
+ bool is_local_ = false;
+
+ // The type of MediaRouteController supported by this route.
+ RouteControllerType controller_type_ = RouteControllerType::kNone;
+
+ // |true| if the route can be displayed in the UI.
+ bool for_display_ = false;
+
+ // |true| if the route was created by an incognito profile.
+ bool is_incognito_ = false;
+
+ // |true| if the presentation associated with this route is a local
+ // presentation.
+ bool is_local_presentation_ = false;
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_MEDIA_ROUTE_H_
diff --git a/chromium/chrome/common/media_router/media_route_provider_helper.cc b/chromium/chrome/common/media_router/media_route_provider_helper.cc
new file mode 100644
index 00000000000..8190e2603ec
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_route_provider_helper.cc
@@ -0,0 +1,29 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_route_provider_helper.h"
+
+#include "base/logging.h"
+
+namespace media_router {
+
+const char* ProviderIdToString(MediaRouteProviderId provider_id) {
+ switch (provider_id) {
+ case EXTENSION:
+ return "EXTENSION";
+ case WIRED_DISPLAY:
+ return "WIRED_DISPLAY";
+ case CAST:
+ return "CAST";
+ case DIAL:
+ return "DIAL";
+ case UNKNOWN:
+ return "UNKNOWN";
+ }
+
+ NOTREACHED() << "Unknown provider_id " << static_cast<int>(provider_id);
+ return "Unknown provider_id";
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_route_provider_helper.h b/chromium/chrome/common/media_router/media_route_provider_helper.h
new file mode 100644
index 00000000000..5f33bc6eea0
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_route_provider_helper.h
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_MEDIA_ROUTE_PROVIDER_HELPER_H_
+#define CHROME_COMMON_MEDIA_ROUTER_MEDIA_ROUTE_PROVIDER_HELPER_H_
+
+#include <string>
+
+namespace media_router {
+
+// Each MediaRouteProvider is associated with a unique ID. This enum must be
+// kept in sync with mojom::MediaRouteProvider::Id, except for |UNKNOWN|, which
+// is not present in the Mojo enum.
+enum MediaRouteProviderId {
+ EXTENSION,
+ WIRED_DISPLAY,
+ CAST,
+ DIAL,
+ UNKNOWN // New values must be added above this value.
+};
+
+const char* ProviderIdToString(MediaRouteProviderId provider_id);
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_MEDIA_ROUTE_PROVIDER_HELPER_H_
diff --git a/chromium/chrome/common/media_router/media_route_unittest.cc b/chromium/chrome/common/media_router/media_route_unittest.cc
new file mode 100644
index 00000000000..01a94be6b1e
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_route_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_route.h"
+
+#include "chrome/common/media_router/media_sink.h"
+#include "chrome/common/media_router/media_source.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+constexpr char kRouteId1[] =
+ "urn:x-org.chromium:media:route:1/cast-sink1/http://foo.com";
+constexpr char kRouteId2[] =
+ "urn:x-org.chromium:media:route:2/cast-sink2/http://foo.com";
+constexpr char kPresentationUrl[] = "http://www.example.com/presentation.html";
+} // namespace
+
+namespace media_router {
+
+TEST(MediaRouteTest, TestEquals) {
+ const MediaSource& media_source =
+ MediaSource::ForPresentationUrl(GURL(kPresentationUrl));
+ MediaRoute route1(kRouteId1, media_source, "sinkId", "Description", false,
+ false);
+
+ MediaRoute route1_copy(route1);
+ EXPECT_EQ(route1, route1_copy);
+
+ // Same as route1 with different sink ID.
+ MediaRoute route2(kRouteId1, media_source, "differentSinkId", "Description",
+ false, false);
+ EXPECT_FALSE(route1 == route2);
+
+ // Same as route1 with different description.
+ MediaRoute route3(kRouteId1, media_source, "sinkId", "differentDescription",
+ false, false);
+ EXPECT_FALSE(route1 == route3);
+
+ // Same as route1 with different is_local.
+ MediaRoute route4(kRouteId1, media_source, "sinkId", "Description", true,
+ false);
+ EXPECT_FALSE(route1 == route4);
+
+ // The ID is different from route1's.
+ MediaRoute route5(kRouteId2, media_source, "sinkId", "Description", false,
+ false);
+ EXPECT_FALSE(route1 == route5);
+
+ // Same as route1 with different incognito.
+ MediaRoute route6(kRouteId1, media_source, "sinkId", "Description", true,
+ false);
+ route6.set_incognito(true);
+ EXPECT_FALSE(route1 == route6);
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_sink.cc b/chromium/chrome/common/media_router/media_sink.cc
new file mode 100644
index 00000000000..36f2183d355
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_sink.cc
@@ -0,0 +1,73 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_sink.h"
+#include "base/i18n/string_compare.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+
+namespace media_router {
+
+MediaSink::MediaSink(const MediaSink::Id& sink_id,
+ const std::string& name,
+ SinkIconType icon_type,
+ MediaRouteProviderId provider_id)
+ : sink_id_(sink_id),
+ name_(name),
+ icon_type_(icon_type),
+ provider_id_(provider_id) {}
+
+MediaSink::MediaSink(const MediaSink& other) = default;
+MediaSink::MediaSink(MediaSink&& other) noexcept = default;
+MediaSink::MediaSink() = default;
+MediaSink::~MediaSink() = default;
+
+MediaSink& MediaSink::operator=(const MediaSink& other) = default;
+MediaSink& MediaSink::operator=(MediaSink&& other) noexcept = default;
+
+bool MediaSink::IsMaybeCloudSink() const {
+ switch (icon_type_) {
+ case SinkIconType::MEETING:
+ case SinkIconType::HANGOUT:
+ case SinkIconType::EDUCATION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool MediaSink::operator==(const MediaSink& other) const {
+ return sink_id_ == other.sink_id_ && name_ == other.name_ &&
+ description_ == other.description_ && domain_ == other.domain_ &&
+ icon_type_ == other.icon_type_ && provider_id_ == other.provider_id_;
+}
+
+bool MediaSink::operator!=(const MediaSink& other) const {
+ return !operator==(other);
+}
+
+bool MediaSink::CompareUsingCollator(const MediaSink& other,
+ const icu::Collator* collator) const {
+ if (icon_type_ != other.icon_type_)
+ return icon_type_ < other.icon_type_;
+
+ if (collator) {
+ base::string16 this_name = base::UTF8ToUTF16(name_);
+ base::string16 other_name = base::UTF8ToUTF16(other.name_);
+ UCollationResult result = base::i18n::CompareString16WithCollator(
+ *collator, this_name, other_name);
+ if (result != UCOL_EQUAL)
+ return result == UCOL_LESS;
+ } else {
+ // Fall back to simple string comparison if collator is not
+ // available.
+ int val = name_.compare(other.name_);
+ if (val)
+ return val < 0;
+ }
+
+ return sink_id_ < other.sink_id_;
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_sink.h b/chromium/chrome/common/media_router/media_sink.h
new file mode 100644
index 00000000000..d2546249f57
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_sink.h
@@ -0,0 +1,117 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_MEDIA_SINK_H_
+#define CHROME_COMMON_MEDIA_ROUTER_MEDIA_SINK_H_
+
+#include <string>
+
+#include "base/optional.h"
+#include "chrome/common/media_router/media_route_provider_helper.h"
+#include "third_party/icu/source/common/unicode/uversion.h"
+
+namespace U_ICU_NAMESPACE {
+class Collator;
+} // namespace U_ICU_NAMESPACE
+
+namespace media_router {
+
+// IconTypes are listed in the order in which sinks should be sorted.
+// The order must stay in sync with
+// chrome/browser/resources/media_router/media_router_data.js.
+//
+// NOTE: This enum is used for recording the MediaRouter.Sink.SelectedType
+// metrics, so if we want to reorder it, we must create a separate enum that
+// preserves the ordering, and map from this enum to the new one in
+// MediaRouterMetrics::RecordMediaSinkType().
+enum class SinkIconType {
+ CAST = 0,
+ CAST_AUDIO_GROUP = 1,
+ CAST_AUDIO = 2,
+ MEETING = 3,
+ HANGOUT = 4,
+ EDUCATION = 5,
+ WIRED_DISPLAY = 6,
+ GENERIC = 7,
+ TOTAL_COUNT = 8 // Add new types above this line.
+};
+
+// Represents a sink to which media can be routed.
+// TODO(zhaobin): convert MediaSink into a struct.
+class MediaSink {
+ public:
+ using Id = std::string;
+
+ // TODO(takumif): Remove the default argument for |provider_id|.
+ MediaSink(const MediaSink::Id& sink_id,
+ const std::string& name,
+ SinkIconType icon_type,
+ MediaRouteProviderId provider_id = MediaRouteProviderId::UNKNOWN);
+ MediaSink(const MediaSink& other);
+ MediaSink(MediaSink&& other) noexcept;
+ MediaSink();
+ ~MediaSink();
+
+ MediaSink& operator=(const MediaSink& other);
+ MediaSink& operator=(MediaSink&& other) noexcept;
+
+ void set_sink_id(const MediaSink::Id& sink_id) { sink_id_ = sink_id; }
+ const MediaSink::Id& id() const { return sink_id_; }
+
+ void set_name(const std::string& name) { name_ = name; }
+ const std::string& name() const { return name_; }
+
+ void set_description(const std::string& description) {
+ description_ = description;
+ }
+ const base::Optional<std::string>& description() const {
+ return description_;
+ }
+
+ void set_domain(const std::string& domain) { domain_ = domain; }
+ const base::Optional<std::string>& domain() const { return domain_; }
+
+ void set_icon_type(SinkIconType icon_type) { icon_type_ = icon_type; }
+ SinkIconType icon_type() const { return icon_type_; }
+
+ void set_provider_id(MediaRouteProviderId provider_id) {
+ provider_id_ = provider_id;
+ }
+ MediaRouteProviderId provider_id() const { return provider_id_; }
+
+ // Returns true if the sink is from the Cloud MRP; however, as this is based
+ // solely on the icon type, is not guaranteed to be correct 100% of the time.
+ bool IsMaybeCloudSink() const;
+
+ bool operator==(const MediaSink& other) const;
+ bool operator!=(const MediaSink& other) const;
+
+ // Compares |this| to |other| first by their icon types, then their names
+ // using |collator|, and finally their IDs.
+ bool CompareUsingCollator(const MediaSink& other,
+ const icu::Collator* collator) const;
+
+ private:
+ // Unique identifier for the MediaSink.
+ MediaSink::Id sink_id_;
+
+ // Descriptive name of the MediaSink.
+ std::string name_;
+
+ // Optional description of the MediaSink.
+ base::Optional<std::string> description_;
+
+ // Optional domain of the MediaSink.
+ base::Optional<std::string> domain_;
+
+ // The type of icon that corresponds with the MediaSink.
+ SinkIconType icon_type_ = SinkIconType::GENERIC;
+
+ // The ID of the MediaRouteProvider that the MediaSink belongs to.
+ MediaRouteProviderId provider_id_ = MediaRouteProviderId::UNKNOWN;
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_MEDIA_SINK_H_
diff --git a/chromium/chrome/common/media_router/media_sink_unittest.cc b/chromium/chrome/common/media_router/media_sink_unittest.cc
new file mode 100644
index 00000000000..30b5dacb32b
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_sink_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_sink.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media_router {
+
+TEST(MediaSinkTest, IsMaybeCloudSink) {
+ MediaSink meeting("sinkId", "Sink", SinkIconType::MEETING,
+ MediaRouteProviderId::EXTENSION);
+ MediaSink eduReceiver("sinkId2", "Sink", SinkIconType::EDUCATION,
+ MediaRouteProviderId::EXTENSION);
+ MediaSink chromeCast("sinkId3", "Sink", SinkIconType::CAST,
+ MediaRouteProviderId::EXTENSION);
+
+ EXPECT_TRUE(meeting.IsMaybeCloudSink());
+ EXPECT_TRUE(eduReceiver.IsMaybeCloudSink());
+ EXPECT_FALSE(chromeCast.IsMaybeCloudSink());
+}
+
+TEST(MediaSinkTest, TestEquals) {
+ MediaSink sink1("sinkId", "Sink", SinkIconType::CAST,
+ MediaRouteProviderId::EXTENSION);
+
+ MediaSink sink1_copy(sink1);
+ EXPECT_EQ(sink1, sink1_copy);
+
+ // No name.
+ MediaSink sink2("sinkId", "", SinkIconType::CAST,
+ MediaRouteProviderId::EXTENSION);
+ EXPECT_FALSE(sink1 == sink2);
+
+ // Sink name is different from sink1's.
+ MediaSink sink3("sinkId", "Other Sink", SinkIconType::CAST,
+ MediaRouteProviderId::EXTENSION);
+ EXPECT_FALSE(sink1 == sink3);
+
+ // Sink ID is diffrent from sink1's.
+ MediaSink sink4("otherSinkId", "Sink", SinkIconType::CAST,
+ MediaRouteProviderId::EXTENSION);
+ EXPECT_FALSE(sink1 == sink4);
+
+ // Sink icon type is diffrent from sink1's.
+ MediaSink sink5("otherSinkId", "Sink", SinkIconType::GENERIC,
+ MediaRouteProviderId::EXTENSION);
+ EXPECT_FALSE(sink1 == sink5);
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_source.cc b/chromium/chrome/common/media_router/media_source.cc
new file mode 100644
index 00000000000..dbad598d35f
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_source.cc
@@ -0,0 +1,131 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_source.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdio>
+#include <ostream>
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/media_router/media_source.h"
+#include "url/gurl.h"
+
+namespace media_router {
+
+namespace {
+
+// Prefixes used to format and detect various protocols' media source URNs.
+// See: https://www.ietf.org/rfc/rfc3406.txt
+constexpr char kTabMediaUrnFormat[] = "urn:x-org.chromium.media:source:tab:%d";
+constexpr char kDesktopMediaUrn[] = "urn:x-org.chromium.media:source:desktop";
+constexpr char kTabRemotingUrnFormat[] =
+ "urn:x-org.chromium.media:source:tab_content_remoting:%d";
+
+// List of non-http(s) schemes that are allowed in a Presentation URL.
+constexpr std::array<const char* const, 5> kAllowedSchemes{
+ {kCastPresentationUrlScheme, kCastDialPresentationUrlScheme,
+ kDialPresentationUrlScheme, kRemotePlaybackPresentationUrlScheme, "test"}};
+
+bool IsSchemeAllowed(const GURL& url) {
+ return url.SchemeIsHTTPOrHTTPS() ||
+ std::any_of(
+ kAllowedSchemes.begin(), kAllowedSchemes.end(),
+ [&url](const char* const scheme) { return url.SchemeIs(scheme); });
+}
+
+} // namespace
+
+bool IsLegacyCastPresentationUrl(const GURL& url) {
+ return base::StartsWith(url.spec(), kLegacyCastPresentationUrlPrefix,
+ base::CompareCase::INSENSITIVE_ASCII);
+}
+
+bool IsValidPresentationUrl(const GURL& url) {
+ return url.is_valid() && IsSchemeAllowed(url);
+}
+
+bool IsAutoJoinPresentationId(const std::string& presentation_id) {
+ return presentation_id == kAutoJoinPresentationId;
+}
+
+MediaSource::MediaSource() = default;
+
+MediaSource::MediaSource(const MediaSource::Id& source_id) : id_(source_id) {
+ GURL url(source_id);
+ if (IsValidPresentationUrl(url))
+ url_ = url;
+}
+
+MediaSource::MediaSource(const GURL& presentation_url)
+ : id_(presentation_url.spec()), url_(presentation_url) {}
+
+MediaSource::~MediaSource() = default;
+
+// static
+MediaSource MediaSource::ForTab(int tab_id) {
+ return MediaSource(base::StringPrintf(kTabMediaUrnFormat, tab_id));
+}
+
+// static
+MediaSource MediaSource::ForTabContentRemoting(int tab_id) {
+ return MediaSource(base::StringPrintf(kTabRemotingUrnFormat, tab_id));
+}
+
+// static
+MediaSource MediaSource::ForDesktop() {
+ return MediaSource(std::string(kDesktopMediaUrn));
+}
+
+// static
+MediaSource MediaSource::ForPresentationUrl(const GURL& presentation_url) {
+ return MediaSource(presentation_url);
+}
+
+bool MediaSource::IsDesktopMirroringSource() const {
+ return base::StartsWith(id(), kDesktopMediaUrn, base::CompareCase::SENSITIVE);
+}
+
+bool MediaSource::IsTabMirroringSource() const {
+ int tab_id;
+ return std::sscanf(id_.c_str(), kTabMediaUrnFormat, &tab_id) == 1 &&
+ tab_id > 0;
+}
+
+bool MediaSource::IsMirroringSource() const {
+ return IsDesktopMirroringSource() || IsTabMirroringSource();
+}
+
+bool MediaSource::IsCastPresentationUrl() const {
+ return url_.SchemeIs(kCastPresentationUrlScheme) ||
+ IsLegacyCastPresentationUrl(url_);
+}
+
+int MediaSource::TabId() const {
+ int tab_id;
+ if (sscanf(id_.c_str(), kTabMediaUrnFormat, &tab_id) == 1)
+ return tab_id;
+ else if (sscanf(id_.c_str(), kTabRemotingUrnFormat, &tab_id) == 1)
+ return tab_id;
+ else
+ return -1;
+}
+
+bool MediaSource::IsValid() const {
+ return TabId() > 0 || IsDesktopMirroringSource() ||
+ IsValidPresentationUrl(GURL(id_));
+}
+
+bool MediaSource::IsDialSource() const {
+ return url_.SchemeIs(kCastDialPresentationUrlScheme);
+}
+
+std::string MediaSource::AppNameFromDialSource() const {
+ return IsDialSource() ? url_.path() : "";
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/media_source.h b/chromium/chrome/common/media_router/media_source.h
new file mode 100644
index 00000000000..15fd6fd0590
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_source.h
@@ -0,0 +1,122 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_MEDIA_SOURCE_H_
+#define CHROME_COMMON_MEDIA_ROUTER_MEDIA_SOURCE_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "base/hash/hash.h"
+#include "url/gurl.h"
+
+namespace media_router {
+
+// URL schemes used by Presentation URLs for Cast and DIAL.
+constexpr char kCastPresentationUrlScheme[] = "cast";
+constexpr char kCastDialPresentationUrlScheme[] = "cast-dial";
+constexpr char kDialPresentationUrlScheme[] = "dial";
+constexpr char kRemotePlaybackPresentationUrlScheme[] = "remote-playback";
+
+// URL prefix used by legacy Cast presentations.
+constexpr char kLegacyCastPresentationUrlPrefix[] =
+ "https://google.com/cast#__castAppId__=";
+
+// Strings used in presentation IDs by the Cast SDK implementation.
+// TODO(takumif): Move them out of this file, since they are not directly
+// related to MediaSource.
+//
+// This value must be the same as |chrome.cast.AUTO_JOIN_PRESENTATION_ID| in the
+// component extension.
+constexpr char kAutoJoinPresentationId[] = "auto-join";
+// This value must be the same as |chrome.cast.PRESENTATION_ID_PREFIX| in the
+// component extension.
+constexpr char kCastPresentationIdPrefix[] = "cast-session_";
+
+// Returns true if |url| represents a legacy Cast presentation URL, i.e., it
+// starts with |kLegacyCastPresentationUrlPrefix|.
+bool IsLegacyCastPresentationUrl(const GURL& url);
+
+// Returns true if |url| is a valid presentation URL.
+bool IsValidPresentationUrl(const GURL& url);
+
+// Returns true if |presentation_id| is an ID used by auto-join requests.
+bool IsAutoJoinPresentationId(const std::string& presentation_id);
+
+class MediaSource {
+ public:
+ using Id = std::string;
+
+ MediaSource();
+ explicit MediaSource(const MediaSource::Id& id);
+ explicit MediaSource(const GURL& presentation_url);
+ ~MediaSource();
+
+ // Gets the ID of the media source.
+ const Id& id() const { return id_; }
+
+ // If MediaSource is created from a URL, return the URL; otherwise return an
+ // empty GURL.
+ const GURL& url() const { return url_; }
+
+ // Returns true if two MediaSource objects use the same media ID.
+ bool operator==(const MediaSource& other) const { return id_ == other.id(); }
+
+ bool operator<(const MediaSource& other) const { return id_ < other.id(); }
+
+ // Hash operator for hash containers.
+ struct Hash {
+ uint32_t operator()(const MediaSource& source) const {
+ return base::Hash(source.id());
+ }
+ };
+
+ // Protocol-specific media source object creation.
+ // Returns MediaSource URI depending on the type of source.
+ static MediaSource ForTab(int tab_id);
+ static MediaSource ForTabContentRemoting(int tab_id);
+ static MediaSource ForDesktop();
+ static MediaSource ForPresentationUrl(const GURL& presentation_url);
+
+ // Returns true if source outputs its content via mirroring.
+ bool IsDesktopMirroringSource() const;
+ bool IsTabMirroringSource() const;
+ bool IsMirroringSource() const;
+
+ // Returns true if this is represents a Cast Presentation URL.
+ bool IsCastPresentationUrl() const;
+
+ // Parses the ID and returns the SessionTabHelper tab ID referencing a source
+ // tab. Returns a non-positive value on error.
+ int TabId() const;
+
+ // Checks that this is a parseable URN and is of a known type.
+ // Does not deeper protocol-level syntax checks.
+ bool IsValid() const;
+
+ // Returns true this source outputs its content via DIAL.
+ // TODO(crbug.com/804419): Move this to in-browser DIAL/Cast MRP when we have
+ // one.
+ bool IsDialSource() const;
+
+ // Returns empty string if this source is not DIAL media source, or is not a
+ // valid DIAL media source.
+ std::string AppNameFromDialSource() const;
+
+ private:
+ MediaSource::Id id_;
+ GURL url_;
+};
+
+// Only for debug logging. This operator is defined inline so it doesn't add
+// any code in release builds. (Omitting the definition entirely when NDEBUG is
+// defined causes linker errors on Android.)
+inline std::ostream& operator<<(std::ostream& stream,
+ const MediaSource& source) {
+ return stream << "MediaSource[" << source.id() << "]";
+}
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_MEDIA_SOURCE_H_
diff --git a/chromium/chrome/common/media_router/media_source_unittest.cc b/chromium/chrome/common/media_router/media_source_unittest.cc
new file mode 100644
index 00000000000..7bb4dff686c
--- /dev/null
+++ b/chromium/chrome/common/media_router/media_source_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/media_source.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media_router {
+
+TEST(MediaSourceTest, IsLegacyCastPresentationUrl) {
+ EXPECT_TRUE(IsLegacyCastPresentationUrl(
+ GURL("https://google.com/cast#__castAppId__=theAppId")));
+ EXPECT_TRUE(IsLegacyCastPresentationUrl(
+ GURL("HTTPS://GOOGLE.COM/CAST#__CASTAPPID__=theAppId")));
+ EXPECT_FALSE(IsLegacyCastPresentationUrl(
+ GURL("https://google.com/cast#__castAppId__")));
+}
+
+TEST(MediaSourceTest, IsValidPresentationUrl) {
+ EXPECT_FALSE(IsValidPresentationUrl(GURL()));
+ EXPECT_FALSE(IsValidPresentationUrl(GURL("unsupported-scheme://foo")));
+
+ EXPECT_TRUE(IsValidPresentationUrl(GURL("https://google.com")));
+ EXPECT_TRUE(IsValidPresentationUrl(GURL("cast://foo")));
+ EXPECT_TRUE(IsValidPresentationUrl(GURL("cast:foo")));
+}
+
+TEST(MediaSourceTest, IsAutoJoinPresentationId) {
+ EXPECT_TRUE(IsAutoJoinPresentationId("auto-join"));
+ EXPECT_FALSE(IsAutoJoinPresentationId("not-auto-join"));
+}
+
+TEST(MediaSourceTest, Constructor) {
+ // Test that the object's getters match the constructor parameters.
+ MediaSource source1("urn:x-com.google.cast:application:DEADBEEF");
+ EXPECT_EQ("urn:x-com.google.cast:application:DEADBEEF", source1.id());
+ EXPECT_EQ(GURL(""), source1.url());
+}
+
+TEST(MediaSourceTest, ConstructorWithGURL) {
+ GURL test_url = GURL("http://google.com");
+ MediaSource source1(test_url);
+ EXPECT_EQ(test_url.spec(), source1.id());
+ EXPECT_EQ(test_url, source1.url());
+}
+
+TEST(MediaSourceTest, ConstructorWithURLString) {
+ GURL test_url = GURL("http://google.com");
+ MediaSource source1(test_url.spec());
+ EXPECT_EQ(test_url.spec(), source1.id());
+ EXPECT_EQ(test_url, source1.url());
+}
+
+TEST(MediaSourceTest, ForTab) {
+ auto source = MediaSource::ForTab(123);
+ EXPECT_EQ("urn:x-org.chromium.media:source:tab:123", source.id());
+ EXPECT_EQ(123, source.TabId());
+ EXPECT_TRUE(source.IsValid());
+ EXPECT_FALSE(source.IsDesktopMirroringSource());
+ EXPECT_TRUE(source.IsTabMirroringSource());
+ EXPECT_TRUE(source.IsMirroringSource());
+ EXPECT_FALSE(source.IsCastPresentationUrl());
+ EXPECT_FALSE(source.IsDialSource());
+}
+
+TEST(MediaSourceTest, ForTabContentRemoting) {
+ auto source = MediaSource::ForTabContentRemoting(123);
+ EXPECT_EQ(123, source.TabId());
+ EXPECT_TRUE(source.IsValid());
+ EXPECT_FALSE(source.IsDesktopMirroringSource());
+ EXPECT_FALSE(source.IsTabMirroringSource());
+ EXPECT_FALSE(source.IsMirroringSource());
+ EXPECT_FALSE(source.IsCastPresentationUrl());
+ EXPECT_FALSE(source.IsDialSource());
+}
+
+TEST(MediaSourceTest, ForDesktop) {
+ auto source = MediaSource::ForDesktop();
+ EXPECT_EQ("urn:x-org.chromium.media:source:desktop", source.id());
+ EXPECT_TRUE(source.IsValid());
+ EXPECT_TRUE(source.IsDesktopMirroringSource());
+ EXPECT_FALSE(source.IsTabMirroringSource());
+ EXPECT_TRUE(source.IsMirroringSource());
+ EXPECT_FALSE(source.IsCastPresentationUrl());
+ EXPECT_FALSE(source.IsDialSource());
+}
+
+TEST(MediaSourceTest, ForPresentationUrl) {
+ constexpr char kPresentationUrl[] =
+ "https://www.example.com/presentation.html";
+ auto source = MediaSource::ForPresentationUrl(GURL(kPresentationUrl));
+ EXPECT_EQ(kPresentationUrl, source.id());
+ EXPECT_TRUE(source.IsValid());
+ EXPECT_FALSE(source.IsDesktopMirroringSource());
+ EXPECT_FALSE(source.IsTabMirroringSource());
+ EXPECT_FALSE(source.IsMirroringSource());
+ EXPECT_FALSE(source.IsCastPresentationUrl());
+ EXPECT_FALSE(source.IsDialSource());
+}
+
+TEST(MediaSourceTest, IsValid) {
+ // Disallowed scheme
+ EXPECT_FALSE(MediaSource::ForPresentationUrl(GURL("file:///some/local/path"))
+ .IsValid());
+ // Not a URL
+ EXPECT_FALSE(
+ MediaSource::ForPresentationUrl(GURL("totally not a url")).IsValid());
+}
+
+TEST(MediaSourceTest, IsCastPresentationUrl) {
+ EXPECT_TRUE(MediaSource(GURL("cast:233637DE")).IsCastPresentationUrl());
+ EXPECT_TRUE(
+ MediaSource(GURL("https://google.com/cast#__castAppId__=233637DE"))
+ .IsCastPresentationUrl());
+ // false scheme
+ EXPECT_FALSE(
+ MediaSource(GURL("http://google.com/cast#__castAppId__=233637DE"))
+ .IsCastPresentationUrl());
+ // false domain
+ EXPECT_FALSE(
+ MediaSource(GURL("https://google2.com/cast#__castAppId__=233637DE"))
+ .IsCastPresentationUrl());
+ // empty path
+ EXPECT_FALSE(
+ MediaSource(GURL("https://www.google.com")).IsCastPresentationUrl());
+ // false path
+ EXPECT_FALSE(
+ MediaSource(GURL("https://www.google.com/path")).IsCastPresentationUrl());
+
+ EXPECT_FALSE(MediaSource(GURL("")).IsCastPresentationUrl());
+}
+
+TEST(MediaSourceTest, IsDialSource) {
+ EXPECT_TRUE(
+ MediaSource("cast-dial:YouTube?dialPostData=postData&clientId=1234")
+ .IsDialSource());
+ // false scheme
+ EXPECT_FALSE(MediaSource("https://google.com/cast#__castAppId__=233637DE")
+ .IsDialSource());
+}
+
+TEST(MediaSourceTest, AppNameFromDialSource) {
+ MediaSource media_source(
+ "cast-dial:YouTube?dialPostData=postData&clientId=1234");
+ EXPECT_EQ("YouTube", media_source.AppNameFromDialSource());
+
+ media_source = MediaSource("dial:YouTube");
+ EXPECT_TRUE(media_source.AppNameFromDialSource().empty());
+
+ media_source = MediaSource("https://google.com/cast#__castAppId__=233637DE");
+ EXPECT_TRUE(media_source.AppNameFromDialSource().empty());
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/mojom/OWNERS b/chromium/chrome/common/media_router/mojom/OWNERS
new file mode 100644
index 00000000000..ae29a36aac8
--- /dev/null
+++ b/chromium/chrome/common/media_router/mojom/OWNERS
@@ -0,0 +1,6 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/chromium/chrome/common/media_router/mojom/media_router_mojom_traits.cc b/chromium/chrome/common/media_router/mojom/media_router_mojom_traits.cc
new file mode 100644
index 00000000000..d6b4a10a932
--- /dev/null
+++ b/chromium/chrome/common/media_router/mojom/media_router_mojom_traits.cc
@@ -0,0 +1,226 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/mojom/media_router_mojom_traits.h"
+
+#include "chrome/common/media_router/media_source.h"
+#include "services/network/public/cpp/ip_address_mojom_traits.h"
+#include "services/network/public/cpp/ip_endpoint_mojom_traits.h"
+#include "url/mojom/url_gurl_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<media_router::mojom::IssueDataView, media_router::IssueInfo>::
+ Read(media_router::mojom::IssueDataView data,
+ media_router::IssueInfo* out) {
+ if (!data.ReadTitle(&out->title))
+ return false;
+
+ if (!data.ReadDefaultAction(&out->default_action))
+ return false;
+
+ if (!data.ReadSeverity(&out->severity))
+ return false;
+
+ base::Optional<std::string> message;
+ if (!data.ReadMessage(&message))
+ return false;
+
+ out->message = message.value_or(std::string());
+
+ if (!data.ReadSecondaryActions(&out->secondary_actions))
+ return false;
+
+ if (!data.ReadRouteId(&out->route_id))
+ return false;
+
+ if (!data.ReadSinkId(&out->sink_id))
+ return false;
+
+ out->is_blocking = data.is_blocking();
+ out->help_page_id = data.help_page_id();
+
+ return true;
+}
+
+// static
+media_router::mojom::MediaSinkExtraDataDataView::Tag
+UnionTraits<media_router::mojom::MediaSinkExtraDataDataView,
+ media_router::MediaSinkInternal>::
+ GetTag(const media_router::MediaSinkInternal& sink) {
+ if (sink.is_dial_sink()) {
+ return media_router::mojom::MediaSinkExtraDataDataView::Tag::
+ DIAL_MEDIA_SINK;
+ } else if (sink.is_cast_sink()) {
+ return media_router::mojom::MediaSinkExtraDataDataView::Tag::
+ CAST_MEDIA_SINK;
+ }
+ NOTREACHED();
+ return media_router::mojom::MediaSinkExtraDataDataView::Tag::CAST_MEDIA_SINK;
+}
+
+// static
+bool StructTraits<media_router::mojom::MediaSinkDataView,
+ media_router::MediaSinkInternal>::
+ Read(media_router::mojom::MediaSinkDataView data,
+ media_router::MediaSinkInternal* out) {
+ media_router::MediaSink::Id id;
+ if (!data.ReadSinkId(&id) ||
+ !media_router::MediaSinkInternal::IsValidSinkId(id)) {
+ return false;
+ }
+
+ out->sink().set_sink_id(id);
+
+ std::string name;
+ if (!data.ReadName(&name))
+ return false;
+
+ out->sink().set_name(name);
+
+ base::Optional<std::string> description;
+ if (!data.ReadDescription(&description))
+ return false;
+
+ if (description)
+ out->sink().set_description(*description);
+
+ base::Optional<std::string> domain;
+ if (!data.ReadDomain(&domain))
+ return false;
+
+ if (domain)
+ out->sink().set_domain(*domain);
+
+ media_router::SinkIconType icon_type;
+ if (!data.ReadIconType(&icon_type))
+ return false;
+
+ out->sink().set_icon_type(icon_type);
+
+ media_router::MediaRouteProviderId provider_id;
+ if (!data.ReadProviderId(&provider_id))
+ return false;
+
+ out->sink().set_provider_id(provider_id);
+
+ if (!data.ReadExtraData(out))
+ return false;
+
+ return true;
+}
+
+// static
+bool UnionTraits<media_router::mojom::MediaSinkExtraDataDataView,
+ media_router::MediaSinkInternal>::
+ Read(media_router::mojom::MediaSinkExtraDataDataView data,
+ media_router::MediaSinkInternal* out) {
+ switch (data.tag()) {
+ case media_router::mojom::MediaSinkExtraDataDataView::Tag::
+ DIAL_MEDIA_SINK: {
+ media_router::DialSinkExtraData extra_data;
+ if (!data.ReadDialMediaSink(&extra_data))
+ return false;
+ out->set_dial_data(extra_data);
+ return true;
+ }
+ case media_router::mojom::MediaSinkExtraDataDataView::Tag::
+ CAST_MEDIA_SINK: {
+ media_router::CastSinkExtraData extra_data;
+ if (!data.ReadCastMediaSink(&extra_data))
+ return false;
+ out->set_cast_data(extra_data);
+ return true;
+ }
+ }
+ NOTREACHED();
+ return false;
+}
+
+// static
+bool StructTraits<media_router::mojom::DialMediaSinkDataView,
+ media_router::DialSinkExtraData>::
+ Read(media_router::mojom::DialMediaSinkDataView data,
+ media_router::DialSinkExtraData* out) {
+ if (!data.ReadIpAddress(&out->ip_address))
+ return false;
+
+ if (!data.ReadModelName(&out->model_name))
+ return false;
+
+ if (!data.ReadAppUrl(&out->app_url))
+ return false;
+
+ return true;
+}
+
+// static
+bool StructTraits<media_router::mojom::CastMediaSinkDataView,
+ media_router::CastSinkExtraData>::
+ Read(media_router::mojom::CastMediaSinkDataView data,
+ media_router::CastSinkExtraData* out) {
+ if (!data.ReadIpEndpoint(&out->ip_endpoint))
+ return false;
+
+ if (!data.ReadModelName(&out->model_name))
+ return false;
+
+ out->capabilities = data.capabilities();
+ out->cast_channel_id = data.cast_channel_id();
+
+ return true;
+}
+
+// static
+bool StructTraits<media_router::mojom::MediaRouteDataView,
+ media_router::MediaRoute>::
+ Read(media_router::mojom::MediaRouteDataView data,
+ media_router::MediaRoute* out) {
+ media_router::MediaRoute::Id media_route_id;
+ if (!data.ReadMediaRouteId(&media_route_id))
+ return false;
+
+ out->set_media_route_id(media_route_id);
+
+ std::string presentation_id;
+ if (!data.ReadPresentationId(&presentation_id))
+ return false;
+
+ out->set_presentation_id(presentation_id);
+
+ base::Optional<media_router::MediaSource::Id> media_source_id;
+ if (!data.ReadMediaSource(&media_source_id))
+ return false;
+
+ if (media_source_id)
+ out->set_media_source(media_router::MediaSource(*media_source_id));
+
+ media_router::MediaSink::Id media_sink_id;
+ if (!data.ReadMediaSinkId(&media_sink_id))
+ return false;
+
+ out->set_media_sink_id(media_sink_id);
+
+ std::string description;
+ if (!data.ReadDescription(&description))
+ return false;
+
+ out->set_description(description);
+
+ media_router::RouteControllerType controller_type;
+ if (!data.ReadControllerType(&controller_type))
+ return false;
+
+ out->set_controller_type(controller_type);
+
+ out->set_local(data.is_local());
+ out->set_for_display(data.for_display());
+ out->set_incognito(data.is_incognito());
+ out->set_local_presentation(data.is_local_presentation());
+
+ return true;
+}
+
+} // namespace mojo
diff --git a/chromium/chrome/common/media_router/mojom/media_router_mojom_traits.h b/chromium/chrome/common/media_router/mojom/media_router_mojom_traits.h
new file mode 100644
index 00000000000..21e06d7c8fe
--- /dev/null
+++ b/chromium/chrome/common/media_router/mojom/media_router_mojom_traits.h
@@ -0,0 +1,525 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_MOJOM_MEDIA_ROUTER_MOJOM_TRAITS_H_
+#define CHROME_COMMON_MEDIA_ROUTER_MOJOM_MEDIA_ROUTER_MOJOM_TRAITS_H_
+
+#include <string>
+
+#include "chrome/common/media_router/discovery/media_sink_internal.h"
+#include "chrome/common/media_router/issue.h"
+#include "chrome/common/media_router/mojom/media_router.mojom.h"
+#include "chrome/common/media_router/route_request_result.h"
+#include "net/base/ip_endpoint.h"
+
+namespace mojo {
+
+// Issue
+
+template <>
+struct EnumTraits<media_router::mojom::Issue::ActionType,
+ media_router::IssueInfo::Action> {
+ static media_router::mojom::Issue::ActionType ToMojom(
+ media_router::IssueInfo::Action action) {
+ switch (action) {
+ case media_router::IssueInfo::Action::DISMISS:
+ return media_router::mojom::Issue::ActionType::DISMISS;
+ case media_router::IssueInfo::Action::LEARN_MORE:
+ return media_router::mojom::Issue::ActionType::LEARN_MORE;
+ }
+ NOTREACHED() << "Unknown issue action type " << static_cast<int>(action);
+ return media_router::mojom::Issue::ActionType::DISMISS;
+ }
+
+ static bool FromMojom(media_router::mojom::Issue::ActionType input,
+ media_router::IssueInfo::Action* output) {
+ switch (input) {
+ case media_router::mojom::Issue::ActionType::DISMISS:
+ *output = media_router::IssueInfo::Action::DISMISS;
+ return true;
+ case media_router::mojom::Issue::ActionType::LEARN_MORE:
+ *output = media_router::IssueInfo::Action::LEARN_MORE;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct EnumTraits<media_router::mojom::Issue::Severity,
+ media_router::IssueInfo::Severity> {
+ static media_router::mojom::Issue::Severity ToMojom(
+ media_router::IssueInfo::Severity severity) {
+ switch (severity) {
+ case media_router::IssueInfo::Severity::FATAL:
+ return media_router::mojom::Issue::Severity::FATAL;
+ case media_router::IssueInfo::Severity::WARNING:
+ return media_router::mojom::Issue::Severity::WARNING;
+ case media_router::IssueInfo::Severity::NOTIFICATION:
+ return media_router::mojom::Issue::Severity::NOTIFICATION;
+ }
+ NOTREACHED() << "Unknown issue severity " << static_cast<int>(severity);
+ return media_router::mojom::Issue::Severity::WARNING;
+ }
+
+ static bool FromMojom(media_router::mojom::Issue::Severity input,
+ media_router::IssueInfo::Severity* output) {
+ switch (input) {
+ case media_router::mojom::Issue::Severity::FATAL:
+ *output = media_router::IssueInfo::Severity::FATAL;
+ return true;
+ case media_router::mojom::Issue::Severity::WARNING:
+ *output = media_router::IssueInfo::Severity::WARNING;
+ return true;
+ case media_router::mojom::Issue::Severity::NOTIFICATION:
+ *output = media_router::IssueInfo::Severity::NOTIFICATION;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct UnionTraits<media_router::mojom::MediaSinkExtraDataDataView,
+ media_router::MediaSinkInternal> {
+ static media_router::mojom::MediaSinkExtraDataDataView::Tag GetTag(
+ const media_router::MediaSinkInternal& sink);
+
+ static bool IsNull(const media_router::MediaSinkInternal& sink) {
+ return !sink.is_cast_sink() && !sink.is_dial_sink();
+ }
+
+ static void SetToNull(media_router::MediaSinkInternal* out) {}
+
+ static const media_router::DialSinkExtraData& dial_media_sink(
+ const media_router::MediaSinkInternal& sink) {
+ return sink.dial_data();
+ }
+
+ static const media_router::CastSinkExtraData& cast_media_sink(
+ const media_router::MediaSinkInternal& sink) {
+ return sink.cast_data();
+ }
+
+ static bool Read(media_router::mojom::MediaSinkExtraDataDataView data,
+ media_router::MediaSinkInternal* out);
+};
+
+template <>
+struct StructTraits<media_router::mojom::DialMediaSinkDataView,
+ media_router::DialSinkExtraData> {
+ static const std::string& model_name(
+ const media_router::DialSinkExtraData& extra_data) {
+ return extra_data.model_name;
+ }
+
+ static const net::IPAddress& ip_address(
+ const media_router::DialSinkExtraData& extra_data) {
+ return extra_data.ip_address;
+ }
+
+ static const GURL& app_url(
+ const media_router::DialSinkExtraData& extra_data) {
+ return extra_data.app_url;
+ }
+
+ static bool Read(media_router::mojom::DialMediaSinkDataView data,
+ media_router::DialSinkExtraData* out);
+};
+
+template <>
+struct StructTraits<media_router::mojom::CastMediaSinkDataView,
+ media_router::CastSinkExtraData> {
+ static const std::string& model_name(
+ const media_router::CastSinkExtraData& extra_data) {
+ return extra_data.model_name;
+ }
+
+ static const net::IPEndPoint& ip_endpoint(
+ const media_router::CastSinkExtraData& extra_data) {
+ return extra_data.ip_endpoint;
+ }
+
+ static uint8_t capabilities(
+ const media_router::CastSinkExtraData& extra_data) {
+ return extra_data.capabilities;
+ }
+
+ static int32_t cast_channel_id(
+ const media_router::CastSinkExtraData& extra_data) {
+ return extra_data.cast_channel_id;
+ }
+
+ static bool Read(media_router::mojom::CastMediaSinkDataView data,
+ media_router::CastSinkExtraData* out);
+};
+
+template <>
+struct StructTraits<media_router::mojom::IssueDataView,
+ media_router::IssueInfo> {
+ static bool Read(media_router::mojom::IssueDataView data,
+ media_router::IssueInfo* out);
+
+ static const std::string& route_id(const media_router::IssueInfo& issue) {
+ return issue.route_id;
+ }
+
+ static const std::string& sink_id(const media_router::IssueInfo& issue) {
+ return issue.sink_id;
+ }
+
+ static media_router::IssueInfo::Severity severity(
+ const media_router::IssueInfo& issue) {
+ return issue.severity;
+ }
+
+ static bool is_blocking(const media_router::IssueInfo& issue) {
+ return issue.is_blocking;
+ }
+
+ static const std::string& title(const media_router::IssueInfo& issue) {
+ return issue.title;
+ }
+
+ static const std::string& message(const media_router::IssueInfo& issue) {
+ return issue.message;
+ }
+
+ static media_router::IssueInfo::Action default_action(
+ const media_router::IssueInfo& issue) {
+ return issue.default_action;
+ }
+
+ static const std::vector<media_router::IssueInfo::Action>& secondary_actions(
+ const media_router::IssueInfo& issue) {
+ return issue.secondary_actions;
+ }
+
+ static int32_t help_page_id(const media_router::IssueInfo& issue) {
+ return issue.help_page_id;
+ }
+};
+
+// MediaSink
+
+template <>
+struct EnumTraits<media_router::mojom::SinkIconType,
+ media_router::SinkIconType> {
+ static media_router::mojom::SinkIconType ToMojom(
+ media_router::SinkIconType icon_type) {
+ switch (icon_type) {
+ case media_router::SinkIconType::CAST:
+ return media_router::mojom::SinkIconType::CAST;
+ case media_router::SinkIconType::CAST_AUDIO_GROUP:
+ return media_router::mojom::SinkIconType::CAST_AUDIO_GROUP;
+ case media_router::SinkIconType::CAST_AUDIO:
+ return media_router::mojom::SinkIconType::CAST_AUDIO;
+ case media_router::SinkIconType::MEETING:
+ return media_router::mojom::SinkIconType::MEETING;
+ case media_router::SinkIconType::HANGOUT:
+ return media_router::mojom::SinkIconType::HANGOUT;
+ case media_router::SinkIconType::EDUCATION:
+ return media_router::mojom::SinkIconType::EDUCATION;
+ case media_router::SinkIconType::WIRED_DISPLAY:
+ return media_router::mojom::SinkIconType::WIRED_DISPLAY;
+ case media_router::SinkIconType::GENERIC:
+ return media_router::mojom::SinkIconType::GENERIC;
+ case media_router::SinkIconType::TOTAL_COUNT:
+ break;
+ }
+ NOTREACHED() << "Unknown sink icon type " << static_cast<int>(icon_type);
+ return media_router::mojom::SinkIconType::GENERIC;
+ }
+
+ static bool FromMojom(media_router::mojom::SinkIconType input,
+ media_router::SinkIconType* output) {
+ switch (input) {
+ case media_router::mojom::SinkIconType::CAST:
+ *output = media_router::SinkIconType::CAST;
+ return true;
+ case media_router::mojom::SinkIconType::CAST_AUDIO_GROUP:
+ *output = media_router::SinkIconType::CAST_AUDIO_GROUP;
+ return true;
+ case media_router::mojom::SinkIconType::CAST_AUDIO:
+ *output = media_router::SinkIconType::CAST_AUDIO;
+ return true;
+ case media_router::mojom::SinkIconType::MEETING:
+ *output = media_router::SinkIconType::MEETING;
+ return true;
+ case media_router::mojom::SinkIconType::HANGOUT:
+ *output = media_router::SinkIconType::HANGOUT;
+ return true;
+ case media_router::mojom::SinkIconType::EDUCATION:
+ *output = media_router::SinkIconType::EDUCATION;
+ return true;
+ case media_router::mojom::SinkIconType::WIRED_DISPLAY:
+ *output = media_router::SinkIconType::WIRED_DISPLAY;
+ return true;
+ case media_router::mojom::SinkIconType::GENERIC:
+ *output = media_router::SinkIconType::GENERIC;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct StructTraits<media_router::mojom::MediaSinkDataView,
+ media_router::MediaSinkInternal> {
+ static bool Read(media_router::mojom::MediaSinkDataView data,
+ media_router::MediaSinkInternal* out);
+
+ static const std::string& sink_id(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal.sink().id();
+ }
+
+ static const std::string& name(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal.sink().name();
+ }
+
+ static const base::Optional<std::string>& description(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal.sink().description();
+ }
+
+ static const base::Optional<std::string>& domain(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal.sink().domain();
+ }
+
+ static media_router::SinkIconType icon_type(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal.sink().icon_type();
+ }
+
+ static media_router::MediaRouteProviderId provider_id(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal.sink().provider_id();
+ }
+
+ static const media_router::MediaSinkInternal& extra_data(
+ const media_router::MediaSinkInternal& sink_internal) {
+ return sink_internal;
+ }
+};
+
+// MediaRoute
+
+template <>
+struct EnumTraits<media_router::mojom::RouteControllerType,
+ media_router::RouteControllerType> {
+ static media_router::mojom::RouteControllerType ToMojom(
+ media_router::RouteControllerType controller_type) {
+ switch (controller_type) {
+ case media_router::RouteControllerType::kNone:
+ return media_router::mojom::RouteControllerType::kNone;
+ case media_router::RouteControllerType::kGeneric:
+ return media_router::mojom::RouteControllerType::kGeneric;
+ case media_router::RouteControllerType::kMirroring:
+ return media_router::mojom::RouteControllerType::kMirroring;
+ }
+ NOTREACHED() << "Unknown controller type "
+ << static_cast<int>(controller_type);
+ return media_router::mojom::RouteControllerType::kNone;
+ }
+
+ static bool FromMojom(media_router::mojom::RouteControllerType input,
+ media_router::RouteControllerType* output) {
+ switch (input) {
+ case media_router::mojom::RouteControllerType::kNone:
+ *output = media_router::RouteControllerType::kNone;
+ return true;
+ case media_router::mojom::RouteControllerType::kGeneric:
+ *output = media_router::RouteControllerType::kGeneric;
+ return true;
+ case media_router::mojom::RouteControllerType::kMirroring:
+ *output = media_router::RouteControllerType::kMirroring;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct StructTraits<media_router::mojom::MediaRouteDataView,
+ media_router::MediaRoute> {
+ static bool Read(media_router::mojom::MediaRouteDataView data,
+ media_router::MediaRoute* out);
+
+ static const std::string& media_route_id(
+ const media_router::MediaRoute& route) {
+ return route.media_route_id();
+ }
+
+ static const std::string& presentation_id(
+ const media_router::MediaRoute& route) {
+ return route.presentation_id();
+ }
+
+ static base::Optional<std::string> media_source(
+ const media_router::MediaRoute& route) {
+ // TODO(imcheng): If we ever convert from C++ to Mojo outside of unit tests,
+ // it would be better to make the |media_source_| field on MediaRoute a
+ // base::Optional<MediaSource::Id> instead so it can be returned directly
+ // here.
+ return route.media_source().id().empty()
+ ? base::Optional<std::string>()
+ : base::make_optional(route.media_source().id());
+ }
+
+ static const std::string& media_sink_id(
+ const media_router::MediaRoute& route) {
+ return route.media_sink_id();
+ }
+
+ static const std::string& description(const media_router::MediaRoute& route) {
+ return route.description();
+ }
+
+ static bool is_local(const media_router::MediaRoute& route) {
+ return route.is_local();
+ }
+
+ static media_router::RouteControllerType controller_type(
+ const media_router::MediaRoute& route) {
+ return route.controller_type();
+ }
+
+ static bool for_display(const media_router::MediaRoute& route) {
+ return route.for_display();
+ }
+
+ static bool is_incognito(const media_router::MediaRoute& route) {
+ return route.is_incognito();
+ }
+
+ static bool is_local_presentation(const media_router::MediaRoute& route) {
+ return route.is_local_presentation();
+ }
+};
+
+// RouteRequestResultCode
+
+template <>
+struct EnumTraits<media_router::mojom::RouteRequestResultCode,
+ media_router::RouteRequestResult::ResultCode> {
+ static media_router::mojom::RouteRequestResultCode ToMojom(
+ media_router::RouteRequestResult::ResultCode code) {
+ switch (code) {
+ case media_router::RouteRequestResult::UNKNOWN_ERROR:
+ return media_router::mojom::RouteRequestResultCode::UNKNOWN_ERROR;
+ case media_router::RouteRequestResult::OK:
+ return media_router::mojom::RouteRequestResultCode::OK;
+ case media_router::RouteRequestResult::TIMED_OUT:
+ return media_router::mojom::RouteRequestResultCode::TIMED_OUT;
+ case media_router::RouteRequestResult::ROUTE_NOT_FOUND:
+ return media_router::mojom::RouteRequestResultCode::ROUTE_NOT_FOUND;
+ case media_router::RouteRequestResult::SINK_NOT_FOUND:
+ return media_router::mojom::RouteRequestResultCode::SINK_NOT_FOUND;
+ case media_router::RouteRequestResult::INVALID_ORIGIN:
+ return media_router::mojom::RouteRequestResultCode::INVALID_ORIGIN;
+ case media_router::RouteRequestResult::INCOGNITO_MISMATCH:
+ return media_router::mojom::RouteRequestResultCode::INCOGNITO_MISMATCH;
+ case media_router::RouteRequestResult::NO_SUPPORTED_PROVIDER:
+ return media_router::mojom::RouteRequestResultCode::
+ NO_SUPPORTED_PROVIDER;
+ case media_router::RouteRequestResult::CANCELLED:
+ return media_router::mojom::RouteRequestResultCode::CANCELLED;
+ case media_router::RouteRequestResult::ROUTE_ALREADY_EXISTS:
+ return media_router::mojom::RouteRequestResultCode::
+ ROUTE_ALREADY_EXISTS;
+ default:
+ NOTREACHED() << "Unknown RouteRequestResultCode "
+ << static_cast<int>(code);
+ return media_router::mojom::RouteRequestResultCode::UNKNOWN_ERROR;
+ }
+ }
+
+ static bool FromMojom(media_router::mojom::RouteRequestResultCode input,
+ media_router::RouteRequestResult::ResultCode* output) {
+ switch (input) {
+ case media_router::mojom::RouteRequestResultCode::UNKNOWN_ERROR:
+ *output = media_router::RouteRequestResult::UNKNOWN_ERROR;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::OK:
+ *output = media_router::RouteRequestResult::OK;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::TIMED_OUT:
+ *output = media_router::RouteRequestResult::TIMED_OUT;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::ROUTE_NOT_FOUND:
+ *output = media_router::RouteRequestResult::ROUTE_NOT_FOUND;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::SINK_NOT_FOUND:
+ *output = media_router::RouteRequestResult::SINK_NOT_FOUND;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::INVALID_ORIGIN:
+ *output = media_router::RouteRequestResult::INVALID_ORIGIN;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::INCOGNITO_MISMATCH:
+ *output = media_router::RouteRequestResult::INCOGNITO_MISMATCH;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER:
+ *output = media_router::RouteRequestResult::NO_SUPPORTED_PROVIDER;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::CANCELLED:
+ *output = media_router::RouteRequestResult::CANCELLED;
+ return true;
+ case media_router::mojom::RouteRequestResultCode::ROUTE_ALREADY_EXISTS:
+ *output = media_router::RouteRequestResult::ROUTE_ALREADY_EXISTS;
+ return true;
+ }
+ return false;
+ }
+};
+
+// MediaRouteProvider
+
+template <>
+struct EnumTraits<media_router::mojom::MediaRouteProvider::Id,
+ media_router::MediaRouteProviderId> {
+ static media_router::mojom::MediaRouteProvider::Id ToMojom(
+ media_router::MediaRouteProviderId provider_id) {
+ switch (provider_id) {
+ case media_router::MediaRouteProviderId::EXTENSION:
+ return media_router::mojom::MediaRouteProvider::Id::EXTENSION;
+ case media_router::MediaRouteProviderId::WIRED_DISPLAY:
+ return media_router::mojom::MediaRouteProvider::Id::WIRED_DISPLAY;
+ case media_router::MediaRouteProviderId::CAST:
+ return media_router::mojom::MediaRouteProvider::Id::CAST;
+ case media_router::MediaRouteProviderId::DIAL:
+ return media_router::mojom::MediaRouteProvider::Id::DIAL;
+ case media_router::MediaRouteProviderId::UNKNOWN:
+ break;
+ }
+ NOTREACHED() << "Invalid MediaRouteProvider::Id: "
+ << static_cast<int>(provider_id);
+ return media_router::mojom::MediaRouteProvider::Id::EXTENSION;
+ }
+
+ static bool FromMojom(media_router::mojom::MediaRouteProvider::Id input,
+ media_router::MediaRouteProviderId* provider_id) {
+ switch (input) {
+ case media_router::mojom::MediaRouteProvider::Id::EXTENSION:
+ *provider_id = media_router::MediaRouteProviderId::EXTENSION;
+ return true;
+ case media_router::mojom::MediaRouteProvider::Id::WIRED_DISPLAY:
+ *provider_id = media_router::MediaRouteProviderId::WIRED_DISPLAY;
+ return true;
+ case media_router::mojom::MediaRouteProvider::Id::CAST:
+ *provider_id = media_router::MediaRouteProviderId::CAST;
+ return true;
+ case media_router::mojom::MediaRouteProvider::Id::DIAL:
+ *provider_id = media_router::MediaRouteProviderId::DIAL;
+ return true;
+ }
+ return false;
+ }
+};
+
+} // namespace mojo
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_MOJOM_MEDIA_ROUTER_MOJOM_TRAITS_H_
diff --git a/chromium/chrome/common/media_router/mojom/media_router_mojom_traits_unittest.cc b/chromium/chrome/common/media_router/mojom/media_router_mojom_traits_unittest.cc
new file mode 100644
index 00000000000..b00e1bcb1fe
--- /dev/null
+++ b/chromium/chrome/common/media_router/mojom/media_router_mojom_traits_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/mojom/media_router_mojom_traits.h"
+
+#include <utility>
+
+#include "base/test/task_environment.h"
+#include "chrome/common/media_router/discovery/media_sink_internal.h"
+#include "chrome/common/media_router/mojom/media_router.mojom.h"
+#include "chrome/common/media_router/mojom/media_router_traits_test_service.mojom.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media_router {
+
+class MediaRouterStructTraitsTest
+ : public testing::Test,
+ public media_router::mojom::MediaRouterTraitsTestService {
+ public:
+ MediaRouterStructTraitsTest() {}
+
+ protected:
+ mojo::Remote<mojom::MediaRouterTraitsTestService> GetTraitsTestRemote() {
+ mojo::Remote<mojom::MediaRouterTraitsTestService> remote;
+ traits_test_receivers_.Add(this, remote.BindNewPipeAndPassReceiver());
+ return remote;
+ }
+
+ private:
+ // MediaRouterTraitsTestService Impl
+ void EchoMediaSink(const MediaSinkInternal& sink,
+ EchoMediaSinkCallback callback) override {
+ std::move(callback).Run(sink);
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ mojo::ReceiverSet<MediaRouterTraitsTestService> traits_test_receivers_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaRouterStructTraitsTest);
+};
+
+TEST_F(MediaRouterStructTraitsTest, DialMediaSink) {
+ MediaSink::Id sink_id("sinkId123");
+ std::string sink_name("The sink");
+ SinkIconType icon_type(SinkIconType::CAST);
+ MediaRouteProviderId provider_id(MediaRouteProviderId::EXTENSION);
+ std::string ip_address("192.168.1.2");
+ std::string model_name("model name");
+ GURL app_url("https://example.com");
+
+ MediaSink sink(sink_id, sink_name, icon_type, provider_id);
+ DialSinkExtraData extra_data;
+ EXPECT_TRUE(extra_data.ip_address.AssignFromIPLiteral(ip_address));
+ extra_data.model_name = model_name;
+ extra_data.app_url = app_url;
+
+ MediaSinkInternal dial_sink(sink, extra_data);
+
+ mojo::Remote<mojom::MediaRouterTraitsTestService> remote =
+ GetTraitsTestRemote();
+ MediaSinkInternal output;
+ remote->EchoMediaSink(dial_sink, &output);
+
+ EXPECT_EQ(dial_sink, output);
+}
+
+TEST_F(MediaRouterStructTraitsTest, CastMediaSink) {
+ MediaSink::Id sink_id("sinkId123");
+ std::string sink_name("The sink");
+ SinkIconType icon_type(SinkIconType::CAST);
+ MediaRouteProviderId provider_id(MediaRouteProviderId::EXTENSION);
+ std::string model_name("model name");
+
+ MediaSink sink(sink_id, sink_name, icon_type, provider_id);
+ CastSinkExtraData extra_data;
+ extra_data.ip_endpoint = net::IPEndPoint(net::IPAddress(192, 168, 1, 2), 0);
+ extra_data.model_name = model_name;
+ extra_data.capabilities = 2;
+ extra_data.cast_channel_id = 3;
+
+ MediaSinkInternal cast_sink(sink, extra_data);
+
+ mojo::Remote<mojom::MediaRouterTraitsTestService> remote =
+ GetTraitsTestRemote();
+ MediaSinkInternal output;
+ remote->EchoMediaSink(cast_sink, &output);
+
+ EXPECT_EQ(cast_sink, output);
+}
+
+TEST_F(MediaRouterStructTraitsTest, GenericMediaSink) {
+ MediaSink::Id sink_id("sinkId123");
+ std::string sink_name("The sink");
+ SinkIconType icon_type(SinkIconType::CAST);
+ MediaRouteProviderId provider_id(MediaRouteProviderId::EXTENSION);
+
+ MediaSink sink(sink_id, sink_name, icon_type, provider_id);
+ MediaSinkInternal generic_sink;
+ generic_sink.set_sink(sink);
+
+ mojo::Remote<mojom::MediaRouterTraitsTestService> remote =
+ GetTraitsTestRemote();
+ MediaSinkInternal output;
+ remote->EchoMediaSink(generic_sink, &output);
+
+ EXPECT_EQ(generic_sink, output);
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/providers/cast/DEPS b/chromium/chrome/common/media_router/providers/cast/DEPS
new file mode 100644
index 00000000000..b2288536d23
--- /dev/null
+++ b/chromium/chrome/common/media_router/providers/cast/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/cast_channel",
+]
diff --git a/chromium/chrome/common/media_router/providers/cast/cast_media_source.cc b/chromium/chrome/common/media_router/providers/cast/cast_media_source.cc
new file mode 100644
index 00000000000..e5acec59b84
--- /dev/null
+++ b/chromium/chrome/common/media_router/providers/cast/cast_media_source.cc
@@ -0,0 +1,388 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/providers/cast/cast_media_source.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/common/media_router/media_source.h"
+#include "components/cast_channel/enum_table.h"
+#include "net/base/escape.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/url_util.h"
+
+using cast_channel::BroadcastRequest;
+using cast_channel::CastDeviceCapability;
+
+namespace cast_util {
+
+using media_router::AutoJoinPolicy;
+using media_router::DefaultActionPolicy;
+
+template <>
+const EnumTable<AutoJoinPolicy> EnumTable<AutoJoinPolicy>::instance(
+ {
+ {AutoJoinPolicy::kPageScoped, "page_scoped"},
+ {AutoJoinPolicy::kTabAndOriginScoped, "tab_and_origin_scoped"},
+ {AutoJoinPolicy::kOriginScoped, "origin_scoped"},
+ },
+ AutoJoinPolicy::kMaxValue);
+
+template <>
+const EnumTable<DefaultActionPolicy> EnumTable<DefaultActionPolicy>::instance(
+ {
+ {DefaultActionPolicy::kCreateSession, "create_session"},
+ {DefaultActionPolicy::kCastThisTab, "cast_this_tab"},
+ },
+ DefaultActionPolicy::kMaxValue);
+
+template <>
+const EnumTable<CastDeviceCapability> EnumTable<CastDeviceCapability>::instance(
+ {
+ {CastDeviceCapability::MULTIZONE_GROUP, "multizone_group"},
+ {CastDeviceCapability::DEV_MODE, "dev_mode"},
+ {CastDeviceCapability::AUDIO_IN, "audio_in"},
+ {CastDeviceCapability::AUDIO_OUT, "audio_out"},
+ {CastDeviceCapability::VIDEO_IN, "video_in"},
+ {CastDeviceCapability::VIDEO_OUT, "video_out"},
+ // NONE deliberately omitted
+ },
+ NonConsecutiveEnumTable);
+
+} // namespace cast_util
+
+namespace media_router {
+
+namespace {
+
+// A nonmember version of base::Optional::value_or that works on pointers as
+// well as instance of base::Optional.
+template <typename T>
+inline auto value_or(const T& optional,
+ const std::decay_t<decltype(*optional)>& default_value)
+ -> std::decay_t<decltype(*optional)> {
+ return optional ? *optional : default_value;
+}
+
+// FindValue() looks up the value associated with a key |key| in a map-like
+// object |map| and returns a pointer to the value if |key| is found, or nullptr
+// otherwise.
+//
+// The type of |map| can be anything that supports a find() method like
+// std::map::find, or any iterable object whose values are key/value pairs.
+//
+// See also FindValueOr().
+
+// Overload for types with a find() method.
+template <typename Map, typename = typename Map::key_type>
+inline const typename Map::mapped_type* FindValue(
+ const Map& map,
+ const typename Map::key_type& key) {
+ auto it = map.find(key);
+ if (it == map.end())
+ return nullptr;
+ return &it->second;
+}
+
+// Overload for types without a find() method.
+template <typename Map, typename Key>
+auto FindValue(const Map& map, const Key& key) -> const
+ decltype(begin(map)->second)* {
+ for (const auto& item : map) {
+ if (item.first == key)
+ return &item.second;
+ }
+ return nullptr;
+}
+
+// Looks up the value associated with a key |key| in a map-like object |map| and
+// returns a reference to the value if |key| is found, or |default_value|
+// otherwise.
+//
+// The type of |map| can be anything that supports a find() method like
+// std::map::find, or any iterable object whose values are key/value pairs.
+template <typename Map, typename Key, typename T>
+inline auto FindValueOr(const Map& map, const Key& key, const T& default_value)
+ -> std::decay_t<decltype(*FindValue(map, key))> {
+ return value_or(FindValue(map, key), default_value);
+}
+
+// Creates a map from the query parameters of |url|. If |url| contains multiple
+// values for the same parameter, the last value is used.
+base::flat_map<std::string, std::string> MakeQueryMap(const GURL& url) {
+ base::flat_map<std::string, std::string> result;
+ for (net::QueryIterator query_it(url); !query_it.IsAtEnd();
+ query_it.Advance()) {
+ result[query_it.GetKey()] = query_it.GetUnescapedValue();
+ }
+ return result;
+}
+
+// TODO(jrw): Move to common utils?
+//
+// TODO(jrw): Should this use net::UnescapeURLComponent instead of
+// url::DecodeURLEscapeSequences?
+std::string DecodeURLComponent(const std::string& encoded) {
+ url::RawCanonOutputT<base::char16> unescaped;
+ std::string output;
+ url::DecodeURLEscapeSequences(encoded.data(), encoded.size(),
+ url::DecodeURLMode::kUTF8OrIsomorphic,
+ &unescaped);
+ if (base::UTF16ToUTF8(unescaped.data(), unescaped.length(), &output))
+ return output;
+
+ return std::string();
+}
+
+// Converts a string containing a comma-separated list of capabilities into a
+// bitwise OR of CastDeviceCapability values.
+BitwiseOr<CastDeviceCapability> CastDeviceCapabilitiesFromString(
+ const base::StringPiece& s) {
+ BitwiseOr<CastDeviceCapability> result{};
+ for (const auto& capability_str : base::SplitStringPiece(
+ s, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
+ const auto capability =
+ cast_util::StringToEnum<CastDeviceCapability>(capability_str);
+ if (capability) {
+ result.Add(*capability);
+ } else {
+ DLOG(ERROR) << "Unkown capability name: " << capability_str;
+ }
+ }
+ return result;
+}
+
+std::unique_ptr<CastMediaSource> CastMediaSourceForTabMirroring(
+ const MediaSource::Id& source_id) {
+ return std::make_unique<CastMediaSource>(
+ source_id,
+ std::vector<CastAppInfo>({CastAppInfo(kCastStreamingAppId),
+ CastAppInfo(kCastStreamingAudioAppId)}));
+}
+
+std::unique_ptr<CastMediaSource> CastMediaSourceForDesktopMirroring(
+ const MediaSource::Id& source_id) {
+ // TODO(https://crbug.com/849335): Add back audio-only devices for desktop
+ // mirroring when proper support is implemented.
+ return std::make_unique<CastMediaSource>(
+ source_id, std::vector<CastAppInfo>({CastAppInfo(kCastStreamingAppId)}));
+}
+
+// The logic shared by ParseCastUrl() and ParseLegacyCastUrl().
+std::unique_ptr<CastMediaSource> CreateFromURLParams(
+ const MediaSource::Id& source_id,
+ const std::vector<CastAppInfo>& app_infos,
+ const std::string& auto_join_policy_str,
+ const std::string& default_action_policy_str,
+ const std::string& client_id,
+ const std::string& broadcast_namespace,
+ const std::string& encoded_broadcast_message,
+ const std::string& launch_timeout_str) {
+ if (app_infos.empty())
+ return nullptr;
+
+ auto cast_source = std::make_unique<CastMediaSource>(
+ source_id, app_infos,
+ cast_util::StringToEnum<AutoJoinPolicy>(auto_join_policy_str)
+ .value_or(AutoJoinPolicy::kPageScoped),
+ cast_util::StringToEnum<DefaultActionPolicy>(default_action_policy_str)
+ .value_or(DefaultActionPolicy::kCreateSession));
+ cast_source->set_client_id(client_id);
+ if (!broadcast_namespace.empty() && !encoded_broadcast_message.empty()) {
+ cast_source->set_broadcast_request(BroadcastRequest(
+ broadcast_namespace, DecodeURLComponent(encoded_broadcast_message)));
+ }
+
+ int launch_timeout_millis;
+ if (base::StringToInt(launch_timeout_str, &launch_timeout_millis) &&
+ launch_timeout_millis > 0) {
+ cast_source->set_launch_timeout(
+ base::TimeDelta::FromMilliseconds(launch_timeout_millis));
+ }
+
+ return cast_source;
+}
+
+std::unique_ptr<CastMediaSource> ParseCastUrl(const MediaSource::Id& source_id,
+ const GURL& url) {
+ std::string app_id = url.path();
+ // App ID must be non-empty.
+ if (app_id.empty())
+ return nullptr;
+
+ auto params{MakeQueryMap(url)};
+ return CreateFromURLParams(
+ source_id,
+ {CastAppInfo(app_id, CastDeviceCapabilitiesFromString(
+ FindValueOr(params, "capabilities", "")))},
+ FindValueOr(params, "autoJoinPolicy", ""),
+ FindValueOr(params, "defaultActionPolicy", ""),
+ FindValueOr(params, "clientId", ""),
+ FindValueOr(params, "broadcastNamespace", ""),
+ FindValueOr(params, "broadcastMessage", ""),
+ FindValueOr(params, "launchTimeout", ""));
+}
+
+std::unique_ptr<CastMediaSource> ParseLegacyCastUrl(
+ const MediaSource::Id& source_id,
+ const GURL& url) {
+ base::StringPairs params;
+ base::SplitStringIntoKeyValuePairs(url.ref(), '=', '/', &params);
+ for (auto& pair : params) {
+ pair.second = net::UnescapeURLComponent(
+ pair.second,
+ net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
+ net::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
+ }
+
+ // Legacy URLs can specify multiple apps.
+ std::vector<std::string> app_id_params;
+ for (const auto& param : params) {
+ if (param.first == "__castAppId__")
+ app_id_params.push_back(param.second);
+ }
+
+ std::vector<CastAppInfo> app_infos;
+ for (const auto& app_id_param : app_id_params) {
+ std::string app_id;
+ std::string capabilities;
+ auto cap_start_index = app_id_param.find('(');
+ // If |cap_start_index| is |npos|, then this will return the entire string.
+ app_id = app_id_param.substr(0, cap_start_index);
+ if (cap_start_index != std::string::npos) {
+ auto cap_end_index = app_id_param.find(')', cap_start_index);
+ if (cap_end_index != std::string::npos &&
+ cap_end_index > cap_start_index) {
+ capabilities = app_id_param.substr(cap_start_index + 1,
+ cap_end_index - cap_start_index - 1);
+ }
+ }
+
+ if (app_id.empty())
+ continue;
+
+ CastAppInfo app_info(app_id,
+ CastDeviceCapabilitiesFromString(capabilities));
+
+ app_infos.push_back(app_info);
+ }
+
+ if (app_infos.empty())
+ return nullptr;
+
+ return CreateFromURLParams(
+ source_id, app_infos, FindValueOr(params, "__castAutoJoinPolicy__", ""),
+ FindValueOr(params, "__castDefaultActionPolicy__", ""),
+ FindValueOr(params, "__castClientId__", ""),
+ FindValueOr(params, "__castBroadcastNamespace__", ""),
+ FindValueOr(params, "__castBroadcastMessage__", ""),
+ FindValueOr(params, "__castLaunchTimeout__", ""));
+}
+
+} // namespace
+
+bool IsAutoJoinAllowed(AutoJoinPolicy policy,
+ const url::Origin& origin1,
+ int tab_id1,
+ const url::Origin& origin2,
+ int tab_id2) {
+ switch (policy) {
+ case AutoJoinPolicy::kPageScoped:
+ return false;
+ case AutoJoinPolicy::kTabAndOriginScoped:
+ return origin1 == origin2 && tab_id1 == tab_id2;
+ case AutoJoinPolicy::kOriginScoped:
+ return origin1 == origin2;
+ }
+}
+
+CastAppInfo::CastAppInfo(
+ const std::string& app_id,
+ BitwiseOr<cast_channel::CastDeviceCapability> required_capabilities)
+ : app_id(app_id), required_capabilities(required_capabilities) {}
+CastAppInfo::~CastAppInfo() = default;
+
+CastAppInfo::CastAppInfo(const CastAppInfo& other) = default;
+
+// static
+std::unique_ptr<CastMediaSource> CastMediaSource::FromMediaSource(
+ const MediaSource& source) {
+ if (source.IsTabMirroringSource())
+ return CastMediaSourceForTabMirroring(source.id());
+
+ if (source.IsDesktopMirroringSource())
+ return CastMediaSourceForDesktopMirroring(source.id());
+
+ const GURL& url = source.url();
+ if (!url.is_valid())
+ return nullptr;
+
+ if (url.SchemeIs(kCastPresentationUrlScheme)) {
+ return ParseCastUrl(source.id(), url);
+ } else if (IsLegacyCastPresentationUrl(url)) {
+ return ParseLegacyCastUrl(source.id(), url);
+ } else if (url.SchemeIsHTTPOrHTTPS()) {
+ // Arbitrary https URLs are supported via 1-UA mode which uses tab
+ // mirroring.
+ return CastMediaSourceForTabMirroring(source.id());
+ }
+
+ return nullptr;
+}
+
+// static
+std::unique_ptr<CastMediaSource> CastMediaSource::FromMediaSourceId(
+ const MediaSource::Id& source_id) {
+ return FromMediaSource(MediaSource(source_id));
+}
+
+// static
+std::unique_ptr<CastMediaSource> CastMediaSource::FromAppId(
+ const std::string& app_id) {
+ return FromMediaSourceId(kCastPresentationUrlScheme + (":" + app_id));
+}
+
+CastMediaSource::CastMediaSource(const MediaSource::Id& source_id,
+ const std::vector<CastAppInfo>& app_infos,
+ AutoJoinPolicy auto_join_policy,
+ DefaultActionPolicy default_action_policy)
+ : source_id_(source_id),
+ app_infos_(app_infos),
+ auto_join_policy_(auto_join_policy),
+ default_action_policy_(default_action_policy) {}
+CastMediaSource::CastMediaSource(const CastMediaSource& other) = default;
+CastMediaSource::~CastMediaSource() = default;
+
+bool CastMediaSource::ContainsApp(const std::string& app_id) const {
+ for (const auto& info : app_infos_) {
+ if (info.app_id == app_id)
+ return true;
+ }
+ return false;
+}
+
+bool CastMediaSource::ContainsAnyAppFrom(
+ const std::vector<std::string>& app_ids) const {
+ return std::any_of(
+ app_ids.begin(), app_ids.end(),
+ [this](const std::string& app_id) { return ContainsApp(app_id); });
+}
+
+std::vector<std::string> CastMediaSource::GetAppIds() const {
+ std::vector<std::string> app_ids;
+ for (const auto& info : app_infos_)
+ app_ids.push_back(info.app_id);
+
+ return app_ids;
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/providers/cast/cast_media_source.h b/chromium/chrome/common/media_router/providers/cast/cast_media_source.h
new file mode 100644
index 00000000000..b0c2552dc33
--- /dev/null
+++ b/chromium/chrome/common/media_router/providers/cast/cast_media_source.h
@@ -0,0 +1,178 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_SOURCE_H_
+#define CHROME_COMMON_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_SOURCE_H_
+
+#include <initializer_list>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/optional.h"
+#include "chrome/common/media_router/media_source.h"
+#include "components/cast_channel/cast_message_util.h"
+#include "components/cast_channel/cast_socket.h"
+
+namespace media_router {
+
+static constexpr char kCastStreamingAppId[] = "0F5096E8";
+static constexpr char kCastStreamingAudioAppId[] = "85CDB22F";
+
+// Placeholder app ID advertised by the multizone leader in a receiver status
+// message.
+static constexpr char kMultizoneLeaderAppId[] = "MultizoneLeader";
+
+static constexpr base::TimeDelta kDefaultLaunchTimeout =
+ base::TimeDelta::FromSeconds(60);
+
+// Class for storing a bitwise OR of enum values.
+//
+// TODO(jrw): Make values of cast_channel::CastDeviceCapability consecutive and
+// store sets of values using a class like v8::base::EnumSet instead of this
+// monstrosity.
+template <typename E, typename T = std::underlying_type_t<E>>
+class BitwiseOr {
+ public:
+ constexpr BitwiseOr() : bits_(0) {}
+ constexpr BitwiseOr(std::initializer_list<E> values) : bits_(0) {
+ for (E e : values)
+ Add(e);
+ }
+ bool empty() const { return bits_ == 0; }
+ void Add(E value) { bits_ |= Mask(value); }
+ bool operator==(const BitwiseOr& other) const { return bits_ == other.bits_; }
+ bool operator!=(const BitwiseOr& other) const { return *this != other; }
+
+ private:
+ static T Mask(E value) {
+ const T result = static_cast<T>(value);
+ DCHECK(static_cast<E>(result) == value);
+ return result;
+ }
+ T bits_;
+};
+
+// Represents a Cast app and its capabilitity requirements.
+struct CastAppInfo {
+ explicit CastAppInfo(const std::string& app_id,
+ BitwiseOr<cast_channel::CastDeviceCapability> = {});
+ ~CastAppInfo();
+
+ CastAppInfo(const CastAppInfo& other);
+
+ std::string app_id;
+
+ // A bitset of capabilities required by the app.
+ BitwiseOr<cast_channel::CastDeviceCapability> required_capabilities;
+};
+
+// Auto-join policy determines when the SDK will automatically connect a sender
+// application to an existing session after API initialization.
+enum class AutoJoinPolicy {
+ // No automatic connection. This is the default when no policy is specified.
+ kPageScoped,
+
+ // Automatically connects when the session was started with the same app ID,
+ // in the same tab and page origin.
+ kTabAndOriginScoped,
+
+ // Automatically connects when the session was started with the same app ID
+ // and the same page origin (regardless of tab).
+ kOriginScoped,
+
+ kMaxValue = kOriginScoped,
+};
+
+// Default action policy determines when the SDK will automatically create a
+// session after initializing the API. This also controls the default action
+// for the tab in the Cast dialog.
+enum class DefaultActionPolicy {
+ // If the tab containing the app is being cast when the API initializes, the
+ // SDK stops tab casting and automatically launches the app. The Cast dialog
+ // prompts the user to cast the app.
+ kCreateSession,
+
+ // No automatic launch is done after initializing the API, even if the tab is
+ // being cast. The Cast dialog prompts the user to mirror the tab (mirror,
+ // not cast, despite the name).
+ kCastThisTab,
+
+ kMaxValue = kCastThisTab,
+};
+
+// Tests whether a sender specified by (origin1, tab_id1) is allowed by |policy|
+// to join (origin2, tab_id2).
+bool IsAutoJoinAllowed(AutoJoinPolicy policy,
+ const url::Origin& origin1,
+ int tab_id1,
+ const url::Origin& origin2,
+ int tab_id2);
+
+// Represents a MediaSource parsed into structured, Cast specific data. The
+// following MediaSources can be parsed into CastMediaSource:
+// - Cast Presentation URLs
+// - HTTP(S) Presentation URLs
+// - Desktop / tab mirroring URNs
+class CastMediaSource {
+ public:
+ // Returns the parsed form of |source|, or nullptr if it cannot be parsed.
+ static std::unique_ptr<CastMediaSource> FromMediaSource(
+ const MediaSource& source);
+ static std::unique_ptr<CastMediaSource> FromMediaSourceId(
+ const MediaSource::Id& source);
+
+ static std::unique_ptr<CastMediaSource> FromAppId(const std::string& app_id);
+
+ CastMediaSource(const MediaSource::Id& source_id,
+ const std::vector<CastAppInfo>& app_infos,
+ AutoJoinPolicy auto_join_policy = AutoJoinPolicy::kPageScoped,
+ DefaultActionPolicy default_action_policy =
+ DefaultActionPolicy::kCreateSession);
+ CastMediaSource(const CastMediaSource& other);
+ ~CastMediaSource();
+
+ // Returns |true| if |app_infos| contain |app_id|.
+ bool ContainsApp(const std::string& app_id) const;
+ bool ContainsAnyAppFrom(const std::vector<std::string>& app_ids) const;
+
+ // Returns a list of App IDs in this CastMediaSource.
+ std::vector<std::string> GetAppIds() const;
+
+ const MediaSource::Id& source_id() const { return source_id_; }
+ const std::vector<CastAppInfo>& app_infos() const { return app_infos_; }
+ const std::string& client_id() const { return client_id_; }
+ void set_client_id(const std::string& client_id) { client_id_ = client_id; }
+ base::TimeDelta launch_timeout() const { return launch_timeout_; }
+ void set_launch_timeout(base::TimeDelta launch_timeout) {
+ launch_timeout_ = launch_timeout;
+ }
+ const base::Optional<cast_channel::BroadcastRequest>& broadcast_request()
+ const {
+ return broadcast_request_;
+ }
+ void set_broadcast_request(const cast_channel::BroadcastRequest& request) {
+ broadcast_request_ = request;
+ }
+ AutoJoinPolicy auto_join_policy() const { return auto_join_policy_; }
+ DefaultActionPolicy default_action_policy() const {
+ return default_action_policy_;
+ }
+
+ private:
+ MediaSource::Id source_id_;
+ std::vector<CastAppInfo> app_infos_;
+ AutoJoinPolicy auto_join_policy_;
+ DefaultActionPolicy default_action_policy_;
+ base::TimeDelta launch_timeout_ = kDefaultLaunchTimeout;
+ // Empty if not set.
+ std::string client_id_;
+ base::Optional<cast_channel::BroadcastRequest> broadcast_request_;
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_SOURCE_H_
diff --git a/chromium/chrome/common/media_router/providers/cast/cast_media_source_unittest.cc b/chromium/chrome/common/media_router/providers/cast/cast_media_source_unittest.cc
new file mode 100644
index 00000000000..7073e8e2dc7
--- /dev/null
+++ b/chromium/chrome/common/media_router/providers/cast/cast_media_source_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/providers/cast/cast_media_source.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using cast_channel::CastDeviceCapability;
+
+namespace media_router {
+
+TEST(CastMediaSourceTest, FromCastURLWithDefaults) {
+ MediaSource::Id source_id("cast:ABCDEFAB");
+ std::unique_ptr<CastMediaSource> source =
+ CastMediaSource::FromMediaSourceId(source_id);
+ ASSERT_TRUE(source);
+ EXPECT_EQ(source_id, source->source_id());
+ ASSERT_EQ(1u, source->app_infos().size());
+ const CastAppInfo& app_info = source->app_infos()[0];
+ EXPECT_EQ("ABCDEFAB", app_info.app_id);
+ EXPECT_TRUE(app_info.required_capabilities.empty());
+ const auto& broadcast_request = source->broadcast_request();
+ EXPECT_FALSE(broadcast_request);
+ EXPECT_EQ("", source->client_id());
+ EXPECT_EQ(kDefaultLaunchTimeout, source->launch_timeout());
+ EXPECT_EQ(AutoJoinPolicy::kPageScoped, source->auto_join_policy());
+ EXPECT_EQ(DefaultActionPolicy::kCreateSession,
+ source->default_action_policy());
+}
+
+TEST(CastMediaSourceTest, FromCastURL) {
+ MediaSource::Id source_id(
+ "cast:ABCDEFAB?capabilities=video_out,audio_out"
+ "&broadcastNamespace=namespace"
+ "&broadcastMessage=message%25"
+ "&clientId=12345"
+ "&launchTimeout=30000"
+ "&autoJoinPolicy=tab_and_origin_scoped"
+ "&defaultActionPolicy=cast_this_tab");
+ std::unique_ptr<CastMediaSource> source =
+ CastMediaSource::FromMediaSourceId(source_id);
+ ASSERT_TRUE(source);
+ EXPECT_EQ(source_id, source->source_id());
+ ASSERT_EQ(1u, source->app_infos().size());
+ const CastAppInfo& app_info = source->app_infos()[0];
+ EXPECT_EQ("ABCDEFAB", app_info.app_id);
+ EXPECT_EQ((BitwiseOr<CastDeviceCapability>{CastDeviceCapability::VIDEO_OUT,
+ CastDeviceCapability::AUDIO_OUT}),
+ app_info.required_capabilities);
+ const auto& broadcast_request = source->broadcast_request();
+ ASSERT_TRUE(broadcast_request);
+ EXPECT_EQ("namespace", broadcast_request->broadcast_namespace);
+ EXPECT_EQ("message%", broadcast_request->message);
+ EXPECT_EQ("12345", source->client_id());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(30000), source->launch_timeout());
+ EXPECT_EQ(AutoJoinPolicy::kTabAndOriginScoped, source->auto_join_policy());
+ EXPECT_EQ(DefaultActionPolicy::kCastThisTab, source->default_action_policy());
+}
+
+TEST(CastMediaSourceTest, FromLegacyCastURL) {
+ MediaSource::Id source_id(
+ "https://google.com/cast"
+ "#__castAppId__=ABCDEFAB(video_out,audio_out)"
+ "/__castAppId__=otherAppId"
+ "/__castBroadcastNamespace__=namespace"
+ "/__castBroadcastMessage__=message%25"
+ "/__castClientId__=12345"
+ "/__castLaunchTimeout__=30000"
+ "/__castAutoJoinPolicy__=origin_scoped"
+ "/__castDefaultActionPolicy__=cast_this_tab");
+ std::unique_ptr<CastMediaSource> source =
+ CastMediaSource::FromMediaSourceId(source_id);
+ ASSERT_TRUE(source);
+ EXPECT_EQ(source_id, source->source_id());
+ ASSERT_EQ(2u, source->app_infos().size());
+ const CastAppInfo& app_info = source->app_infos()[0];
+ EXPECT_EQ("ABCDEFAB", app_info.app_id);
+ EXPECT_EQ((BitwiseOr<CastDeviceCapability>{CastDeviceCapability::VIDEO_OUT,
+ CastDeviceCapability::AUDIO_OUT}),
+ app_info.required_capabilities);
+ EXPECT_EQ("otherAppId", source->app_infos()[1].app_id);
+ const auto& broadcast_request = source->broadcast_request();
+ ASSERT_TRUE(broadcast_request);
+ EXPECT_EQ("namespace", broadcast_request->broadcast_namespace);
+ EXPECT_EQ("message%", broadcast_request->message);
+ EXPECT_EQ("12345", source->client_id());
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(30000), source->launch_timeout());
+ EXPECT_EQ(AutoJoinPolicy::kOriginScoped, source->auto_join_policy());
+ EXPECT_EQ(DefaultActionPolicy::kCastThisTab, source->default_action_policy());
+}
+
+TEST(CastMediaSourceTest, FromPresentationURL) {
+ MediaSource::Id source_id("https://google.com");
+ std::unique_ptr<CastMediaSource> source =
+ CastMediaSource::FromMediaSourceId(source_id);
+ ASSERT_TRUE(source);
+ EXPECT_EQ(source_id, source->source_id());
+ ASSERT_EQ(2u, source->app_infos().size());
+ EXPECT_EQ(kCastStreamingAppId, source->app_infos()[0].app_id);
+ EXPECT_EQ(kCastStreamingAudioAppId, source->app_infos()[1].app_id);
+ EXPECT_TRUE(source->client_id().empty());
+ EXPECT_EQ(kDefaultLaunchTimeout, source->launch_timeout());
+ EXPECT_EQ(AutoJoinPolicy::kPageScoped, source->auto_join_policy());
+ EXPECT_EQ(DefaultActionPolicy::kCreateSession,
+ source->default_action_policy());
+}
+
+TEST(CastMediaSourceTest, FromMirroringURN) {
+ MediaSource::Id source_id("urn:x-org.chromium.media:source:tab:5");
+ std::unique_ptr<CastMediaSource> source =
+ CastMediaSource::FromMediaSourceId(source_id);
+ ASSERT_TRUE(source);
+ EXPECT_EQ(source_id, source->source_id());
+ ASSERT_EQ(2u, source->app_infos().size());
+ EXPECT_EQ(kCastStreamingAppId, source->app_infos()[0].app_id);
+ EXPECT_EQ(kCastStreamingAudioAppId, source->app_infos()[1].app_id);
+ EXPECT_TRUE(source->client_id().empty());
+ EXPECT_EQ(kDefaultLaunchTimeout, source->launch_timeout());
+ EXPECT_EQ(AutoJoinPolicy::kPageScoped, source->auto_join_policy());
+ EXPECT_EQ(DefaultActionPolicy::kCreateSession,
+ source->default_action_policy());
+}
+
+TEST(CastMediaSourceTest, FromDesktopUrn) {
+ MediaSource::Id source_id("urn:x-org.chromium.media:source:desktop");
+ std::unique_ptr<CastMediaSource> source =
+ CastMediaSource::FromMediaSourceId(source_id);
+ ASSERT_TRUE(source);
+ EXPECT_EQ(source_id, source->source_id());
+ ASSERT_EQ(1u, source->app_infos().size());
+ EXPECT_EQ(kCastStreamingAppId, source->app_infos()[0].app_id);
+ EXPECT_TRUE(source->client_id().empty());
+ EXPECT_EQ(kDefaultLaunchTimeout, source->launch_timeout());
+ EXPECT_EQ(AutoJoinPolicy::kPageScoped, source->auto_join_policy());
+ EXPECT_EQ(DefaultActionPolicy::kCreateSession,
+ source->default_action_policy());
+}
+
+TEST(CastMediaSourceTest, FromInvalidSource) {
+ EXPECT_FALSE(CastMediaSource::FromMediaSourceId("invalid:source"));
+ EXPECT_FALSE(CastMediaSource::FromMediaSourceId("file:///foo.mp4"));
+ EXPECT_FALSE(CastMediaSource::FromMediaSourceId(""));
+ EXPECT_FALSE(CastMediaSource::FromMediaSourceId("cast:"));
+
+ // Missing app ID.
+ EXPECT_FALSE(CastMediaSource::FromMediaSourceId("cast:?param=foo"));
+ EXPECT_FALSE(CastMediaSource::FromMediaSourceId(
+ "https://google.com/cast#__castAppId__=/param=foo"));
+}
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/route_request_result.cc b/chromium/chrome/common/media_router/route_request_result.cc
new file mode 100644
index 00000000000..538bff4fdf2
--- /dev/null
+++ b/chromium/chrome/common/media_router/route_request_result.cc
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/media_router/route_request_result.h"
+
+#include "chrome/common/media_router/media_route.h"
+
+namespace media_router {
+
+// static
+std::unique_ptr<RouteRequestResult> RouteRequestResult::FromSuccess(
+ const MediaRoute& route,
+ const std::string& presentation_id) {
+ return std::make_unique<RouteRequestResult>(
+ std::make_unique<MediaRoute>(route), presentation_id, std::string(),
+ RouteRequestResult::OK);
+}
+
+// static
+std::unique_ptr<RouteRequestResult> RouteRequestResult::FromError(
+ const std::string& error,
+ ResultCode result_code) {
+ return std::make_unique<RouteRequestResult>(nullptr, std::string(), error,
+ result_code);
+}
+
+RouteRequestResult::RouteRequestResult(std::unique_ptr<MediaRoute> route,
+ const std::string& presentation_id,
+ const std::string& error,
+ ResultCode result_code)
+ : route_(std::move(route)),
+ presentation_id_(presentation_id),
+ error_(error),
+ result_code_(result_code) {
+ if (route_)
+ presentation_url_ = route_->media_source().url();
+}
+
+RouteRequestResult::~RouteRequestResult() = default;
+
+} // namespace media_router
diff --git a/chromium/chrome/common/media_router/route_request_result.h b/chromium/chrome/common/media_router/route_request_result.h
new file mode 100644
index 00000000000..8b6a251f1d2
--- /dev/null
+++ b/chromium/chrome/common/media_router/route_request_result.h
@@ -0,0 +1,87 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MEDIA_ROUTER_ROUTE_REQUEST_RESULT_H_
+#define CHROME_COMMON_MEDIA_ROUTER_ROUTE_REQUEST_RESULT_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "url/gurl.h"
+
+namespace media_router {
+
+class MediaRoute;
+
+// Holds the result of a successful or failed route request.
+// On success:
+// |route|: The route created or joined.
+// |presentation_id|:
+// The presentation ID of the route created or joined. In the case of
+// |CreateRoute()|, the ID is generated by MediaRouter and is guaranteed to
+// be unique.
+// |error|: Empty string.
+// |result_code|: RouteRequestResult::OK
+// On failure:
+// |route|: nullptr
+// |presentation_id|: Empty string.
+// |error|: Non-empty string describing the error.
+// |result_code|: A value from RouteRequestResult describing the error.
+class RouteRequestResult {
+ public:
+ // Keep in sync with:
+ // - RouteRequestResultCode in media_router.mojom
+ // - MediaRouteProviderResult enum in tools/metrics/histograms.xml
+ // - mr.RouteRequestResultCode in route_request_error.js
+ // - media_router_mojom_traits.h
+ enum ResultCode {
+ UNKNOWN_ERROR = 0,
+ OK = 1,
+ TIMED_OUT = 2,
+ ROUTE_NOT_FOUND = 3,
+ SINK_NOT_FOUND = 4,
+ INVALID_ORIGIN = 5,
+ INCOGNITO_MISMATCH = 6,
+ NO_SUPPORTED_PROVIDER = 7,
+ CANCELLED = 8,
+ ROUTE_ALREADY_EXISTS = 9,
+ // New values must be added here.
+
+ TOTAL_COUNT = 10 // The total number of values.
+ };
+
+ static std::unique_ptr<RouteRequestResult> FromSuccess(
+ const MediaRoute& route,
+ const std::string& presentation_id);
+ static std::unique_ptr<RouteRequestResult> FromError(const std::string& error,
+ ResultCode result_code);
+ RouteRequestResult(std::unique_ptr<MediaRoute> route,
+ const std::string& presentation_id,
+ const std::string& error,
+ ResultCode result_code);
+
+ ~RouteRequestResult();
+
+ // Note the caller does not own the returned MediaRoute. The caller must
+ // create a copy if they wish to use it after this object is destroyed.
+ const MediaRoute* route() const { return route_.get(); }
+ std::string presentation_id() const { return presentation_id_; }
+ GURL presentation_url() const { return presentation_url_; }
+ std::string error() const { return error_; }
+ ResultCode result_code() const { return result_code_; }
+
+ private:
+ std::unique_ptr<MediaRoute> route_;
+ std::string presentation_id_;
+ GURL presentation_url_;
+ std::string error_;
+ ResultCode result_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(RouteRequestResult);
+};
+
+} // namespace media_router
+
+#endif // CHROME_COMMON_MEDIA_ROUTER_ROUTE_REQUEST_RESULT_H_
diff --git a/chromium/chrome/common/metrics_constants_util_win.cc b/chromium/chrome/common/metrics_constants_util_win.cc
new file mode 100644
index 00000000000..8c789ebbfb2
--- /dev/null
+++ b/chromium/chrome/common/metrics_constants_util_win.cc
@@ -0,0 +1,15 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/metrics_constants_util_win.h"
+
+#include "chrome/install_static/install_util.h"
+
+namespace chrome {
+
+base::string16 GetBrowserExitCodesRegistryPath() {
+ return install_static::GetRegistryPath().append(L"\\BrowserExitCodes");
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/common/metrics_constants_util_win.h b/chromium/chrome/common/metrics_constants_util_win.h
new file mode 100644
index 00000000000..349191360f0
--- /dev/null
+++ b/chromium/chrome/common/metrics_constants_util_win.h
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A handful of resource-like constants related to the Chrome application.
+
+#ifndef CHROME_COMMON_METRICS_CONSTANTS_UTIL_WIN_H_
+#define CHROME_COMMON_METRICS_CONSTANTS_UTIL_WIN_H_
+
+#include "base/strings/string16.h"
+
+namespace chrome {
+
+// Returns the registry path where exit code are stored for this product. This
+// is used by browser exit code metrics reporting.
+base::string16 GetBrowserExitCodesRegistryPath();
+
+} // namespace chrome
+
+#endif // CHROME_COMMON_METRICS_CONSTANTS_UTIL_WIN_H_
diff --git a/chromium/chrome/common/multi_process_lock.h b/chromium/chrome/common/multi_process_lock.h
new file mode 100644
index 00000000000..622c58fba3b
--- /dev/null
+++ b/chromium/chrome/common/multi_process_lock.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_MULTI_PROCESS_LOCK_H_
+#define CHROME_COMMON_MULTI_PROCESS_LOCK_H_
+
+#include <sys/types.h>
+#include <memory>
+#include <string>
+
+// Platform abstraction for a lock that can be shared between processes.
+// The process that owns the lock will release it on exit even if
+// the exit is due to a crash. Locks are not recursive.
+class MultiProcessLock {
+ public:
+ // Factory method for creating a multi-process lock.
+ // |name| is the name of the lock. The name has special meaning on Windows
+ // where the prefix can determine the namespace of the lock.
+ // See http://msdn.microsoft.com/en-us/library/aa382954(v=VS.85).aspx for
+ // details.
+ static std::unique_ptr<MultiProcessLock> Create(const std::string& name);
+
+ virtual ~MultiProcessLock() { }
+
+ // Try to grab ownership of the lock.
+ virtual bool TryLock() = 0;
+
+ // Release ownership of the lock.
+ virtual void Unlock() = 0;
+};
+
+#endif // CHROME_COMMON_MULTI_PROCESS_LOCK_H_
diff --git a/chromium/chrome/common/multi_process_lock_linux.cc b/chromium/chrome/common/multi_process_lock_linux.cc
new file mode 100644
index 00000000000..b057bc72296
--- /dev/null
+++ b/chromium/chrome/common/multi_process_lock_linux.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/posix/eintr_wrapper.h"
+
+class MultiProcessLockLinux : public MultiProcessLock {
+ public:
+ explicit MultiProcessLockLinux(const std::string& name)
+ : name_(name), fd_(-1) { }
+
+ ~MultiProcessLockLinux() override {
+ if (fd_ != -1) {
+ Unlock();
+ }
+ }
+
+ bool TryLock() override {
+ struct sockaddr_un address;
+
+ // +1 for terminator, +1 for 0 in position 0 that makes it an
+ // abstract named socket.
+ const size_t max_len = sizeof(address.sun_path) - 2;
+
+ if (fd_ != -1) {
+ DLOG(ERROR) << "MultiProcessLock is already locked - " << name_;
+ return true;
+ }
+
+ if (name_.length() > max_len) {
+ LOG(ERROR) << "Socket name too long (" << name_.length()
+ << " > " << max_len << ") - " << name_;
+ return false;
+ }
+
+ memset(&address, 0, sizeof(address));
+ int print_length = snprintf(&address.sun_path[1],
+ max_len + 1,
+ "%s", name_.c_str());
+
+ if (print_length < 0 ||
+ print_length > static_cast<int>(max_len)) {
+ PLOG(ERROR) << "Couldn't create sun_path - " << name_;
+ return false;
+ }
+
+ // Must set the first character of the path to something non-zero
+ // before we call SUN_LEN which depends on strcpy working.
+ address.sun_path[0] = '@';
+ size_t length = SUN_LEN(&address);
+
+ // Reset the first character of the path back to zero so that
+ // bind returns an abstract name socket.
+ address.sun_path[0] = 0;
+ address.sun_family = AF_LOCAL;
+
+ int socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (socket_fd < 0) {
+ PLOG(ERROR) << "Couldn't create socket - " << name_;
+ return false;
+ }
+
+ if (bind(socket_fd,
+ reinterpret_cast<sockaddr *>(&address),
+ length) == 0) {
+ fd_ = socket_fd;
+ return true;
+ } else {
+ DVLOG(1) << "Couldn't bind socket - "
+ << &(address.sun_path[1])
+ << " Length: " << length;
+ if (IGNORE_EINTR(close(socket_fd)) < 0) {
+ PLOG(ERROR) << "close";
+ }
+ return false;
+ }
+ }
+
+ void Unlock() override {
+ if (fd_ == -1) {
+ DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_;
+ return;
+ }
+ if (IGNORE_EINTR(close(fd_)) < 0) {
+ DPLOG(ERROR) << "close";
+ }
+ fd_ = -1;
+ }
+
+ private:
+ std::string name_;
+ int fd_;
+ DISALLOW_COPY_AND_ASSIGN(MultiProcessLockLinux);
+};
+
+std::unique_ptr<MultiProcessLock> MultiProcessLock::Create(
+ const std::string& name) {
+ return std::make_unique<MultiProcessLockLinux>(name);
+}
diff --git a/chromium/chrome/common/multi_process_lock_mac.cc b/chromium/chrome/common/multi_process_lock_mac.cc
new file mode 100644
index 00000000000..3ec3e3236ca
--- /dev/null
+++ b/chromium/chrome/common/multi_process_lock_mac.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/macros.h"
+#include "base/strings/sys_string_conversions.h"
+
+#include <servers/bootstrap.h>
+
+class MultiProcessLockMac : public MultiProcessLock {
+ public:
+ explicit MultiProcessLockMac(const std::string& name) : name_(name) { }
+
+ ~MultiProcessLockMac() override {
+ if (port_ != NULL) {
+ Unlock();
+ }
+ }
+
+ bool TryLock() override {
+ if (port_ != NULL) {
+ DLOG(ERROR) << "MultiProcessLock is already locked - " << name_;
+ return true;
+ }
+
+ if (name_.length() >= BOOTSTRAP_MAX_NAME_LEN) {
+ LOG(ERROR) << "Socket name too long (" << name_.length()
+ << " >= " << BOOTSTRAP_MAX_NAME_LEN << ") - " << name_;
+ return false;
+ }
+
+ CFStringRef cf_name(base::SysUTF8ToCFStringRef(name_));
+ base::ScopedCFTypeRef<CFStringRef> scoped_cf_name(cf_name);
+ port_.reset(CFMessagePortCreateLocal(NULL, cf_name, NULL, NULL, NULL));
+ return port_ != NULL;
+ }
+
+ void Unlock() override {
+ if (port_ == NULL) {
+ DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_;
+ return;
+ }
+ port_.reset();
+ }
+
+ private:
+ std::string name_;
+ base::ScopedCFTypeRef<CFMessagePortRef> port_;
+ DISALLOW_COPY_AND_ASSIGN(MultiProcessLockMac);
+};
+
+std::unique_ptr<MultiProcessLock> MultiProcessLock::Create(
+ const std::string& name) {
+ return std::make_unique<MultiProcessLockMac>(name);
+}
diff --git a/chromium/chrome/common/multi_process_lock_unittest.cc b/chromium/chrome/common/multi_process_lock_unittest.cc
new file mode 100644
index 00000000000..4d59c5ad471
--- /dev/null
+++ b/chromium/chrome/common/multi_process_lock_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include <memory>
+
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/process/kill.h"
+#include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_environment_variable_override.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/multiprocess_func_list.h"
+
+class MultiProcessLockTest : public base::MultiProcessTest {
+ public:
+ static const char kLockEnvironmentVarName[];
+
+ std::string GenerateLockName();
+ void ExpectLockIsLocked(const std::string &name);
+ void ExpectLockIsUnlocked(const std::string &name);
+};
+
+const char MultiProcessLockTest::kLockEnvironmentVarName[] =
+ "MULTI_PROCESS_TEST_LOCK_NAME";
+
+std::string MultiProcessLockTest::GenerateLockName() {
+ base::Time now = base::Time::NowFromSystemTime();
+ return base::StringPrintf("multi_process_test_lock %lf%lf",
+ now.ToDoubleT(), base::RandDouble());
+}
+
+void MultiProcessLockTest::ExpectLockIsLocked(const std::string &name) {
+ base::test::ScopedEnvironmentVariableOverride var(kLockEnvironmentVarName,
+ name);
+ EXPECT_FALSE(var.WasSet());
+
+ base::Process process = SpawnChild("MultiProcessLockTryFailMain");
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = -1;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+void MultiProcessLockTest::ExpectLockIsUnlocked(
+ const std::string &name) {
+ base::test::ScopedEnvironmentVariableOverride var(kLockEnvironmentVarName,
+ name);
+ EXPECT_FALSE(var.WasSet());
+ base::Process process = SpawnChild("MultiProcessLockTrySucceedMain");
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = -1;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+TEST_F(MultiProcessLockTest, BasicCreationTest) {
+ // Test basic creation/destruction with no lock taken
+ std::string name = GenerateLockName();
+ std::unique_ptr<MultiProcessLock> scoped = MultiProcessLock::Create(name);
+ ExpectLockIsUnlocked(name);
+ scoped.reset(NULL);
+}
+
+TEST_F(MultiProcessLockTest, LongNameTest) {
+ // Every platform has has it's own max path name size,
+ // so different checks are needed for them.
+ // POSIX: sizeof(address.sun_path) - 2
+ // Mac OS X: BOOTSTRAP_MAX_NAME_LEN
+ // Windows: MAX_PATH
+ LOG(INFO) << "Following error log due to long name is expected";
+#if defined(OS_MACOSX)
+ std::string name("This is a name that is longer than one hundred and "
+ "twenty-eight characters to make sure that we fail appropriately on "
+ "Mac OS X when we have a path that is too long for Mac OS X to handle");
+#elif defined(OS_POSIX)
+ std::string name("This is a name that is longer than one hundred and eight "
+ "characters to make sure that we fail appropriately on POSIX systems "
+ "when we have a path that is too long for the system to handle");
+#elif defined(OS_WIN)
+ std::string name("This is a name that is longer than two hundred and sixty "
+ "characters to make sure that we fail appropriately on Windows when we "
+ "have a path that is too long for Windows to handle "
+ "This limitation comes from the MAX_PATH definition which is obviously "
+ "defined to be a maximum of two hundred and sixty characters ");
+#endif
+ std::unique_ptr<MultiProcessLock> test_lock = MultiProcessLock::Create(name);
+ EXPECT_FALSE(test_lock->TryLock());
+}
+
+TEST_F(MultiProcessLockTest, SimpleLock) {
+ std::string name = GenerateLockName();
+ std::unique_ptr<MultiProcessLock> test_lock = MultiProcessLock::Create(name);
+ EXPECT_TRUE(test_lock->TryLock());
+ ExpectLockIsLocked(name);
+ test_lock->Unlock();
+ ExpectLockIsUnlocked(name);
+}
+
+TEST_F(MultiProcessLockTest, RecursiveLock) {
+ std::string name = GenerateLockName();
+ std::unique_ptr<MultiProcessLock> test_lock = MultiProcessLock::Create(name);
+ EXPECT_TRUE(test_lock->TryLock());
+ ExpectLockIsLocked(name);
+ LOG(INFO) << "Following error log "
+ << "'MultiProcessLock is already locked' is expected";
+ EXPECT_TRUE(test_lock->TryLock());
+ ExpectLockIsLocked(name);
+ test_lock->Unlock();
+ ExpectLockIsUnlocked(name);
+ LOG(INFO) << "Following error log "
+ << "'Over-unlocked MultiProcessLock' is expected";
+ test_lock->Unlock();
+ ExpectLockIsUnlocked(name);
+ test_lock.reset();
+}
+
+TEST_F(MultiProcessLockTest, LockScope) {
+ // Check to see that lock is released when it goes out of scope.
+ std::string name = GenerateLockName();
+ {
+ std::unique_ptr<MultiProcessLock> test_lock =
+ MultiProcessLock::Create(name);
+ EXPECT_TRUE(test_lock->TryLock());
+ ExpectLockIsLocked(name);
+ }
+ ExpectLockIsUnlocked(name);
+}
+
+MULTIPROCESS_TEST_MAIN(MultiProcessLockTryFailMain) {
+ std::string name;
+ std::unique_ptr<base::Environment> environment(base::Environment::Create());
+ EXPECT_TRUE(environment->GetVar(MultiProcessLockTest::kLockEnvironmentVarName,
+ &name));
+#if defined(OS_MACOSX)
+ // OS X sends out a log if a lock fails.
+ // Hopefully this will keep people from panicking about it when they
+ // are perusing the build logs.
+ LOG(INFO) << "Following error log "
+ << "\"CFMessagePort: bootstrap_register(): failed 1100 (0x44c) "
+ << "'Permission denied'\" is expected";
+#endif // defined(OS_MACOSX)
+ std::unique_ptr<MultiProcessLock> test_lock = MultiProcessLock::Create(name);
+
+ // Expect locking to fail because it is claimed by another process.
+ bool locked_successfully = test_lock->TryLock();
+ EXPECT_FALSE(locked_successfully);
+ return locked_successfully;
+}
+
+MULTIPROCESS_TEST_MAIN(MultiProcessLockTrySucceedMain) {
+ std::string name;
+ std::unique_ptr<base::Environment> environment(base::Environment::Create());
+ EXPECT_TRUE(environment->GetVar(MultiProcessLockTest::kLockEnvironmentVarName,
+ &name));
+ std::unique_ptr<MultiProcessLock> test_lock = MultiProcessLock::Create(name);
+
+ // Expect locking to succeed because it is not claimed yet.
+ bool locked_successfully = test_lock->TryLock();
+ EXPECT_TRUE(locked_successfully);
+ return !locked_successfully;
+}
diff --git a/chromium/chrome/common/multi_process_lock_win.cc b/chromium/chrome/common/multi_process_lock_win.cc
new file mode 100644
index 00000000000..d7819b7ebbe
--- /dev/null
+++ b/chromium/chrome/common/multi_process_lock_win.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_handle.h"
+
+#include <windows.h>
+
+class MultiProcessLockWin : public MultiProcessLock {
+ public:
+ explicit MultiProcessLockWin(const std::string& name) : name_(name) { }
+
+ ~MultiProcessLockWin() override {
+ if (event_.Get() != NULL) {
+ Unlock();
+ }
+ }
+
+ bool TryLock() override {
+ if (event_.Get() != NULL) {
+ DLOG(ERROR) << "MultiProcessLock is already locked - " << name_;
+ return true;
+ }
+
+ if (name_.length() >= MAX_PATH) {
+ LOG(ERROR) << "Socket name too long (" << name_.length()
+ << " >= " << MAX_PATH << ") - " << name_;
+ return false;
+ }
+
+ base::string16 wname = base::UTF8ToUTF16(name_);
+ event_.Set(CreateEvent(NULL, FALSE, FALSE, wname.c_str()));
+ if (event_.Get() && GetLastError() != ERROR_ALREADY_EXISTS) {
+ return true;
+ } else {
+ event_.Set(NULL);
+ return false;
+ }
+ }
+
+ void Unlock() override {
+ if (event_.Get() == NULL) {
+ DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_;
+ return;
+ }
+ event_.Set(NULL);
+ }
+
+ private:
+ std::string name_;
+ base::win::ScopedHandle event_;
+ DISALLOW_COPY_AND_ASSIGN(MultiProcessLockWin);
+};
+
+std::unique_ptr<MultiProcessLock> MultiProcessLock::Create(
+ const std::string& name) {
+ return std::make_unique<MultiProcessLockWin>(name);
+}
diff --git a/chromium/chrome/common/net/DEPS b/chromium/chrome/common/net/DEPS
new file mode 100644
index 00000000000..1b4d86377c7
--- /dev/null
+++ b/chromium/chrome/common/net/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ # Other libraries.
+ "+chrome/third_party/mozilla_security_manager",
+ "+components/google/core/common",
+]
diff --git a/chromium/chrome/common/net/OWNERS b/chromium/chrome/common/net/OWNERS
new file mode 100644
index 00000000000..b77292902d2
--- /dev/null
+++ b/chromium/chrome/common/net/OWNERS
@@ -0,0 +1,4 @@
+file://net/OWNERS
+
+# COMPONENT: Internals>Network
+# TEAM: net-dev@chromium.org
diff --git a/chromium/chrome/common/net/net_resource_provider.cc b/chromium/chrome/common/net/net_resource_provider.cc
new file mode 100644
index 00000000000..1ee21324c6c
--- /dev/null
+++ b/chromium/chrome/common/net/net_resource_provider.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/net_resource_provider.h"
+
+#include <string>
+
+#include "base/i18n/rtl.h"
+#include "base/no_destructor.h"
+#include "base/values.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "net/grit/net_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/webui/jstemplate_builder.h"
+
+namespace {
+
+// The net module doesn't have access to this HTML or the strings that need to
+// be localized. The Chrome locale will never change while we're running, so
+// it's safe to have a static string that we always return a pointer into.
+struct LazyDirectoryListerCacher {
+ LazyDirectoryListerCacher() {
+ base::DictionaryValue value;
+ value.SetString("header",
+ l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_HEADER));
+ value.SetString("parentDirText",
+ l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_PARENT));
+ value.SetString("headerName",
+ l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_NAME));
+ value.SetString("headerSize",
+ l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_SIZE));
+ value.SetString("headerDateModified",
+ l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_DATE_MODIFIED));
+ value.SetString("language",
+ l10n_util::GetLanguage(base::i18n::GetConfiguredLocale()));
+ value.SetString("listingParsingErrorBoxText",
+ l10n_util::GetStringFUTF16(IDS_DIRECTORY_LISTING_PARSING_ERROR_BOX_TEXT,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+ value.SetString("textdirection", base::i18n::IsRTL() ? "rtl" : "ltr");
+ std::string str = webui::GetI18nTemplateHtml(
+ ui::ResourceBundle::GetSharedInstance().DecompressDataResource(
+ IDR_DIR_HEADER_HTML),
+ &value);
+
+ html_data = base::RefCountedString::TakeString(&str);
+ }
+
+ scoped_refptr<base::RefCountedMemory> html_data;
+};
+
+} // namespace
+
+scoped_refptr<base::RefCountedMemory> ChromeNetResourceProvider(int key) {
+ static base::NoDestructor<LazyDirectoryListerCacher> lazy_dir_lister;
+
+ if (IDR_DIR_HEADER_HTML == key)
+ return lazy_dir_lister->html_data;
+
+ return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(key);
+}
diff --git a/chromium/chrome/common/net/net_resource_provider.h b/chromium/chrome/common/net/net_resource_provider.h
new file mode 100644
index 00000000000..7a2d5453e13
--- /dev/null
+++ b/chromium/chrome/common/net/net_resource_provider.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
+#define CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
+
+#include "base/memory/ref_counted_memory.h"
+
+// This is called indirectly by the network layer to access resources.
+scoped_refptr<base::RefCountedMemory> ChromeNetResourceProvider(int key);
+
+#endif // CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
diff --git a/chromium/chrome/common/net/safe_search_util.cc b/chromium/chrome/common/net/safe_search_util.cc
new file mode 100644
index 00000000000..60fdaa97229
--- /dev/null
+++ b/chromium/chrome/common/net/safe_search_util.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/safe_search_util.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/google/core/common/google_util.h"
+#include "net/cookies/cookie_util.h"
+#include "net/http/http_request_headers.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Returns whether a URL parameter, |first_parameter| (e.g. foo=bar), has the
+// same key as the the |second_parameter| (e.g. foo=baz). Both parameters
+// must be in key=value form.
+bool HasSameParameterKey(base::StringPiece first_parameter,
+ base::StringPiece second_parameter) {
+ DCHECK(second_parameter.find("=") != std::string::npos);
+ // Prefix for "foo=bar" is "foo=".
+ base::StringPiece parameter_prefix =
+ second_parameter.substr(0, second_parameter.find("=") + 1);
+ return base::StartsWith(first_parameter, parameter_prefix,
+ base::CompareCase::INSENSITIVE_ASCII);
+}
+
+// Examines the query string containing parameters and adds the necessary ones
+// so that SafeSearch is active. |query| is the string to examine and the
+// return value is the |query| string modified such that SafeSearch is active.
+std::string AddSafeSearchParameters(const std::string& query) {
+ std::vector<base::StringPiece> new_parameters;
+ std::string safe_parameter = safe_search_util::kSafeSearchSafeParameter;
+ std::string ssui_parameter = safe_search_util::kSafeSearchSsuiParameter;
+
+ for (const base::StringPiece& param : base::SplitStringPiece(
+ query, "&", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (!HasSameParameterKey(param, safe_parameter) &&
+ !HasSameParameterKey(param, ssui_parameter)) {
+ new_parameters.push_back(param);
+ }
+ }
+
+ new_parameters.push_back(safe_parameter);
+ new_parameters.push_back(ssui_parameter);
+ return base::JoinString(new_parameters, "&");
+}
+
+} // namespace
+
+namespace safe_search_util {
+
+const char kSafeSearchSafeParameter[] = "safe=active";
+const char kSafeSearchSsuiParameter[] = "ssui=on";
+const char kYouTubeRestrictHeaderName[] = "YouTube-Restrict";
+const char kYouTubeRestrictHeaderValueModerate[] = "Moderate";
+const char kYouTubeRestrictHeaderValueStrict[] = "Strict";
+const char kGoogleAppsAllowedDomains[] = "X-GoogApps-Allowed-Domains";
+
+// If |request| is a request to Google Web Search the function
+// enforces that the SafeSearch query parameters are set to active.
+// Sets the query part of |new_url| with the new value of the parameters.
+void ForceGoogleSafeSearch(const GURL& url, GURL* new_url) {
+ if (!google_util::IsGoogleSearchUrl(url) &&
+ !google_util::IsGoogleHomePageUrl(url))
+ return;
+
+ std::string query = url.query();
+ std::string new_query = AddSafeSearchParameters(query);
+ if (query == new_query)
+ return;
+
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(new_query);
+ *new_url = url.ReplaceComponents(replacements);
+}
+
+void ForceYouTubeRestrict(const GURL& url,
+ net::HttpRequestHeaders* headers,
+ YouTubeRestrictMode mode) {
+ if (!google_util::IsYoutubeDomainUrl(
+ url, google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS))
+ return;
+
+ switch (mode) {
+ case YOUTUBE_RESTRICT_OFF:
+ case YOUTUBE_RESTRICT_COUNT:
+ NOTREACHED();
+ break;
+
+ case YOUTUBE_RESTRICT_MODERATE:
+ headers->SetHeader(kYouTubeRestrictHeaderName,
+ kYouTubeRestrictHeaderValueModerate);
+ break;
+
+ case YOUTUBE_RESTRICT_STRICT:
+ headers->SetHeader(kYouTubeRestrictHeaderName,
+ kYouTubeRestrictHeaderValueStrict);
+ break;
+ }
+}
+
+} // namespace safe_search_util
diff --git a/chromium/chrome/common/net/safe_search_util.h b/chromium/chrome/common/net/safe_search_util.h
new file mode 100644
index 00000000000..f52b08274f9
--- /dev/null
+++ b/chromium/chrome/common/net/safe_search_util.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_NET_SAFE_SEARCH_UTIL_H_
+#define CHROME_COMMON_NET_SAFE_SEARCH_UTIL_H_
+
+class GURL;
+
+namespace net {
+class HttpRequestHeaders;
+}
+
+namespace safe_search_util {
+
+// Parameters that get appended to force SafeSearch.
+extern const char kSafeSearchSafeParameter[];
+extern const char kSafeSearchSsuiParameter[];
+
+// Headers set for restricted YouTube.
+extern const char kYouTubeRestrictHeaderName[];
+extern const char kYouTubeRestrictHeaderValueModerate[];
+extern const char kYouTubeRestrictHeaderValueStrict[];
+
+// Header set when restricting allowed domains for apps.
+extern const char kGoogleAppsAllowedDomains[];
+
+// Values for YouTube Restricted Mode.
+// VALUES MUST COINCIDE WITH ForceYouTubeRestrict POLICY.
+enum YouTubeRestrictMode {
+ YOUTUBE_RESTRICT_OFF = 0, // Do not restrict YouTube content. YouTube
+ // might still restrict content based on its
+ // user settings.
+ YOUTUBE_RESTRICT_MODERATE = 1, // Enforce at least a moderately strict
+ // content filter for YouTube.
+ YOUTUBE_RESTRICT_STRICT = 2, // Enforce a strict content filter for YouTube.
+ YOUTUBE_RESTRICT_COUNT = 3 // Enum counter
+};
+
+// If |url| is a url to Google Web Search, enforces that the SafeSearch
+// query parameters are set to active. Sets |new_url| to a copy of the request
+// url in which the query part contains the new values of the parameters.
+void ForceGoogleSafeSearch(const GURL& url, GURL* new_url);
+
+// Does nothing if |url| is not a url to YouTube. Otherwise, if |mode|
+// is not |YOUTUBE_RESTRICT_OFF|, enforces a minimum YouTube Restrict mode
+// by setting YouTube Restrict header. Setting |YOUTUBE_RESTRICT_OFF| is not
+// supported and will do nothing in production.
+void ForceYouTubeRestrict(const GURL& url,
+ net::HttpRequestHeaders* headers,
+ YouTubeRestrictMode mode);
+
+} // namespace safe_search_util
+
+#endif // CHROME_COMMON_NET_SAFE_SEARCH_UTIL_H_
diff --git a/chromium/chrome/common/net/safe_search_util_unittest.cc b/chromium/chrome/common/net/safe_search_util_unittest.cc
new file mode 100644
index 00000000000..0dcf9466710
--- /dev/null
+++ b/chromium/chrome/common/net/safe_search_util_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/safe_search_util.h"
+
+#include "base/strings/string_piece.h"
+#include "chrome/common/url_constants.h"
+#include "net/http/http_request_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+// Does a request using the |url_string| URL and verifies that the expected
+// string is equal to the query part (between ? and #) of the final url of
+// that request.
+void CheckAddedParameters(const std::string& url_string,
+ const std::string& expected_query_parameters) {
+ // Show the URL in the trace so we know where we failed.
+ SCOPED_TRACE(url_string);
+
+ GURL result(url_string);
+ safe_search_util::ForceGoogleSafeSearch(GURL(url_string), &result);
+
+ EXPECT_EQ(expected_query_parameters, result.query());
+}
+
+TEST(SafeSearchUtilTest, AddGoogleSafeSearchParams) {
+ const std::string kSafeParameter = safe_search_util::kSafeSearchSafeParameter;
+ const std::string kSsuiParameter = safe_search_util::kSafeSearchSsuiParameter;
+ const std::string kBothParameters = kSafeParameter + "&" + kSsuiParameter;
+
+ // Test the home page.
+ CheckAddedParameters("http://google.com/", kBothParameters);
+
+ // Test the search home page.
+ CheckAddedParameters("http://google.com/webhp", kBothParameters);
+
+ // Test different valid search pages with parameters.
+ CheckAddedParameters("http://google.com/search?q=google",
+ "q=google&" + kBothParameters);
+
+ CheckAddedParameters("http://google.com/?q=google",
+ "q=google&" + kBothParameters);
+
+ CheckAddedParameters("http://google.com/webhp?q=google",
+ "q=google&" + kBothParameters);
+
+ // Test the valid pages with safe set to off.
+ CheckAddedParameters("http://google.com/search?q=google&safe=off",
+ "q=google&" + kBothParameters);
+
+ CheckAddedParameters("http://google.com/?q=google&safe=off",
+ "q=google&" + kBothParameters);
+
+ CheckAddedParameters("http://google.com/webhp?q=google&safe=off",
+ "q=google&" + kBothParameters);
+
+ CheckAddedParameters("http://google.com/webhp?q=google&%73afe=off",
+ "q=google&%73afe=off&" + kBothParameters);
+
+ // Test the home page, different TLDs.
+ CheckAddedParameters("http://google.de/", kBothParameters);
+ CheckAddedParameters("http://google.ro/", kBothParameters);
+ CheckAddedParameters("http://google.nl/", kBothParameters);
+
+ // Test the search home page, different TLD.
+ CheckAddedParameters("http://google.de/webhp", kBothParameters);
+
+ // Test the search page with parameters, different TLD.
+ CheckAddedParameters("http://google.de/search?q=google",
+ "q=google&" + kBothParameters);
+
+ // Test the home page with parameters, different TLD.
+ CheckAddedParameters("http://google.de/?q=google",
+ "q=google&" + kBothParameters);
+
+ // Test the search page with the parameters set.
+ CheckAddedParameters("http://google.de/?q=google&" + kBothParameters,
+ "q=google&" + kBothParameters);
+
+ // Test some possibly tricky combinations.
+ CheckAddedParameters(
+ "http://google.com/?q=goog&" + kSafeParameter + "&ssui=one",
+ "q=goog&" + kBothParameters);
+
+ CheckAddedParameters(
+ "http://google.de/?q=goog&unsafe=active&" + kSsuiParameter,
+ "q=goog&unsafe=active&" + kBothParameters);
+
+ CheckAddedParameters("http://google.de/?q=goog&safe=off&ssui=off",
+ "q=goog&" + kBothParameters);
+
+ CheckAddedParameters("http://google.de/?q=&tbs=rimg:",
+ "q=&tbs=rimg:&" + kBothParameters);
+
+ // Test various combinations where we should not add anything.
+ CheckAddedParameters(
+ "http://google.com/?q=goog&" + kSsuiParameter + "&" + kSafeParameter,
+ "q=goog&" + kBothParameters);
+
+ CheckAddedParameters(
+ "http://google.com/?" + kSsuiParameter + "&q=goog&" + kSafeParameter,
+ "q=goog&" + kBothParameters);
+
+ CheckAddedParameters(
+ "http://google.com/?" + kSsuiParameter + "&" + kSafeParameter + "&q=goog",
+ "q=goog&" + kBothParameters);
+
+ // Test that another website is not affected, without parameters.
+ CheckAddedParameters("http://google.com/finance", std::string());
+
+ // Test that another website is not affected, with parameters.
+ CheckAddedParameters("http://google.com/finance?q=goog", "q=goog");
+
+ // Test with percent-encoded data (%26 is &)
+ CheckAddedParameters("http://google.com/?q=%26%26%26&" + kSsuiParameter +
+ "&" + kSafeParameter + "&param=%26%26%26",
+ "q=%26%26%26&param=%26%26%26&" + kBothParameters);
+}
+
+TEST(SafeSearchUtilTest, SetYoutubeHeader) {
+ net::HttpRequestHeaders headers;
+ safe_search_util::ForceYouTubeRestrict(
+ GURL("http://www.youtube.com"), &headers,
+ safe_search_util::YOUTUBE_RESTRICT_MODERATE);
+ std::string value;
+ EXPECT_TRUE(headers.GetHeader("Youtube-Restrict", &value));
+ EXPECT_EQ("Moderate", value);
+}
+
+TEST(SafeSearchUtilTest, OverrideYoutubeHeader) {
+ net::HttpRequestHeaders headers;
+ headers.SetHeader("Youtube-Restrict", "Off");
+ safe_search_util::ForceYouTubeRestrict(
+ GURL("http://www.youtube.com"), &headers,
+ safe_search_util::YOUTUBE_RESTRICT_MODERATE);
+ std::string value;
+ EXPECT_TRUE(headers.GetHeader("Youtube-Restrict", &value));
+ EXPECT_EQ("Moderate", value);
+}
+
+TEST(SafeSearchUtilTest, DoesntTouchNonYoutubeURL) {
+ net::HttpRequestHeaders headers;
+ headers.SetHeader("Youtube-Restrict", "Off");
+ safe_search_util::ForceYouTubeRestrict(
+ GURL("http://www.notyoutube.com"), &headers,
+ safe_search_util::YOUTUBE_RESTRICT_MODERATE);
+ std::string value;
+ EXPECT_TRUE(headers.GetHeader("Youtube-Restrict", &value));
+ EXPECT_EQ("Off", value);
+}
+
+} // namespace
diff --git a/chromium/chrome/common/net/x509_certificate_model_nss.cc b/chromium/chrome/common/net/x509_certificate_model_nss.cc
new file mode 100644
index 00000000000..5932c3a0f17
--- /dev/null
+++ b/chromium/chrome/common/net/x509_certificate_model_nss.cc
@@ -0,0 +1,365 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/x509_certificate_model_nss.h"
+
+#include <cert.h>
+#include <cms.h>
+#include <hasht.h>
+#include <keyhi.h> // SECKEY_DestroyPrivateKey
+#include <keythi.h> // SECKEYPrivateKey
+#include <pk11pub.h> // PK11_FindKeyByAnyCert
+#include <seccomon.h> // SECItem
+#include <sechash.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <unicode/uidna.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/third_party/mozilla_security_manager/nsNSSCertHelper.h"
+#include "chrome/third_party/mozilla_security_manager/nsNSSCertificate.h"
+#include "chrome/third_party/mozilla_security_manager/nsUsageArrayHelper.h"
+#include "components/url_formatter/url_formatter.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/cert/x509_util_nss.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace psm = mozilla_security_manager;
+
+namespace {
+
+// Convert a char* return value from NSS into a std::string and free the NSS
+// memory. If the arg is NULL, an empty string will be returned instead.
+std::string Stringize(char* nss_text, const std::string& alternative_text) {
+ if (!nss_text)
+ return alternative_text;
+
+ std::string s = nss_text;
+ PORT_Free(nss_text);
+ return s;
+}
+
+// Hash a certificate using the given algorithm, return the result as a
+// colon-seperated hex string. The len specified is the number of bytes
+// required for storing the raw fingerprint.
+// (It's a bit redundant that the caller needs to specify len in addition to the
+// algorithm, but given the limited uses, not worth fixing.)
+std::string HashCert(CERTCertificate* cert, HASH_HashType algorithm, int len) {
+ unsigned char fingerprint[HASH_LENGTH_MAX];
+
+ DCHECK(NULL != cert->derCert.data);
+ DCHECK_NE(0U, cert->derCert.len);
+ DCHECK_LE(len, HASH_LENGTH_MAX);
+ memset(fingerprint, 0, len);
+ SECStatus rv = HASH_HashBuf(algorithm, fingerprint, cert->derCert.data,
+ cert->derCert.len);
+ DCHECK_EQ(rv, SECSuccess);
+ return x509_certificate_model::ProcessRawBytes(fingerprint, len);
+}
+
+std::string ProcessSecAlgorithmInternal(SECAlgorithmID* algorithm_id) {
+ return psm::GetOIDText(&algorithm_id->algorithm);
+}
+
+std::string ProcessExtension(
+ const std::string& critical_label,
+ const std::string& non_critical_label,
+ CERTCertExtension* extension) {
+ std::string criticality =
+ extension->critical.data && extension->critical.data[0] ?
+ critical_label : non_critical_label;
+ return criticality + "\n" + psm::ProcessExtensionData(extension);
+}
+
+std::string GetNickname(CERTCertificate* cert_handle) {
+ std::string name;
+ if (cert_handle->nickname) {
+ name = cert_handle->nickname;
+ // Hack copied from mozilla: Cut off text before first :, which seems to
+ // just be the token name.
+ size_t colon_pos = name.find(':');
+ if (colon_pos != std::string::npos)
+ name = name.substr(colon_pos + 1);
+ }
+ return name;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NSS certificate export functions.
+
+struct NSSCMSMessageDeleter {
+ inline void operator()(NSSCMSMessage* x) const {
+ NSS_CMSMessage_Destroy(x);
+ }
+};
+typedef std::unique_ptr<NSSCMSMessage, NSSCMSMessageDeleter>
+ ScopedNSSCMSMessage;
+
+struct FreeNSSCMSSignedData {
+ inline void operator()(NSSCMSSignedData* x) const {
+ NSS_CMSSignedData_Destroy(x);
+ }
+};
+typedef std::unique_ptr<NSSCMSSignedData, FreeNSSCMSSignedData>
+ ScopedNSSCMSSignedData;
+
+} // namespace
+
+namespace x509_certificate_model {
+
+using std::string;
+
+string GetCertNameOrNickname(CERTCertificate* cert_handle) {
+ string name = ProcessIDN(
+ Stringize(CERT_GetCommonName(&cert_handle->subject), std::string()));
+ if (!name.empty())
+ return name;
+ return GetNickname(cert_handle);
+}
+
+string GetVersion(CERTCertificate* cert_handle) {
+ // If the version field is omitted from the certificate, the default
+ // value is v1(0).
+ unsigned long version = 0;
+ if (cert_handle->version.len == 0 ||
+ SEC_ASN1DecodeInteger(&cert_handle->version, &version) == SECSuccess) {
+ return base::NumberToString(base::strict_cast<uint64_t>(version + 1));
+ }
+ return std::string();
+}
+
+net::CertType GetType(CERTCertificate* cert_handle) {
+ return psm::GetCertType(cert_handle);
+}
+
+void GetUsageStrings(CERTCertificate* cert_handle,
+ std::vector<string>* usages) {
+ psm::GetCertUsageStrings(cert_handle, usages);
+}
+
+string GetSerialNumberHexified(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_Hexify(&cert_handle->serialNumber, true),
+ alternative_text);
+}
+
+string GetIssuerCommonName(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_GetCommonName(&cert_handle->issuer), alternative_text);
+}
+
+string GetIssuerOrgName(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_GetOrgName(&cert_handle->issuer), alternative_text);
+}
+
+string GetIssuerOrgUnitName(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_GetOrgUnitName(&cert_handle->issuer), alternative_text);
+}
+
+string GetSubjectOrgName(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_GetOrgName(&cert_handle->subject), alternative_text);
+}
+
+string GetSubjectOrgUnitName(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_GetOrgUnitName(&cert_handle->subject),
+ alternative_text);
+}
+
+string GetSubjectCommonName(CERTCertificate* cert_handle,
+ const string& alternative_text) {
+ return Stringize(CERT_GetCommonName(&cert_handle->subject), alternative_text);
+}
+
+bool GetTimes(CERTCertificate* cert_handle,
+ base::Time* issued,
+ base::Time* expires) {
+ return net::x509_util::GetValidityTimes(cert_handle, issued, expires);
+}
+
+string GetTitle(CERTCertificate* cert_handle) {
+ return psm::GetCertTitle(cert_handle);
+}
+
+string GetIssuerName(CERTCertificate* cert_handle) {
+ return psm::ProcessName(&cert_handle->issuer);
+}
+
+string GetSubjectName(CERTCertificate* cert_handle) {
+ return psm::ProcessName(&cert_handle->subject);
+}
+
+std::string GetIssuerDisplayName(CERTCertificate* cert_handle) {
+ return net::x509_util::GetCERTNameDisplayName(&cert_handle->issuer);
+}
+
+std::string GetSubjectDisplayName(CERTCertificate* cert_handle) {
+ return net::x509_util::GetCERTNameDisplayName(&cert_handle->subject);
+}
+
+void GetExtensions(const string& critical_label,
+ const string& non_critical_label,
+ CERTCertificate* cert_handle,
+ Extensions* extensions) {
+ if (cert_handle->extensions) {
+ for (size_t i = 0; cert_handle->extensions[i] != NULL; ++i) {
+ Extension extension;
+ extension.name = psm::GetOIDText(&cert_handle->extensions[i]->id);
+ extension.value = ProcessExtension(
+ critical_label, non_critical_label, cert_handle->extensions[i]);
+ extensions->push_back(extension);
+ }
+ }
+}
+
+string HashCertSHA256(CERTCertificate* cert_handle) {
+ return HashCert(cert_handle, HASH_AlgSHA256, SHA256_LENGTH);
+}
+
+string HashCertSHA1(CERTCertificate* cert_handle) {
+ return HashCert(cert_handle, HASH_AlgSHA1, SHA1_LENGTH);
+}
+
+string GetCMSString(const net::ScopedCERTCertificateList& cert_chain,
+ size_t start,
+ size_t end) {
+ crypto::ScopedPLArenaPool arena(PORT_NewArena(1024));
+ DCHECK(arena.get());
+
+ ScopedNSSCMSMessage message(NSS_CMSMessage_Create(arena.get()));
+ DCHECK(message.get());
+
+ // First, create SignedData with the certificate only (no chain).
+ ScopedNSSCMSSignedData signed_data(NSS_CMSSignedData_CreateCertsOnly(
+ message.get(), cert_chain[start].get(), PR_FALSE));
+ if (!signed_data.get()) {
+ DLOG(ERROR) << "NSS_CMSSignedData_Create failed";
+ return std::string();
+ }
+ // Add the rest of the chain (if any).
+ for (size_t i = start + 1; i < end; ++i) {
+ if (NSS_CMSSignedData_AddCertificate(signed_data.get(),
+ cert_chain[i].get()) != SECSuccess) {
+ DLOG(ERROR) << "NSS_CMSSignedData_AddCertificate failed on " << i;
+ return std::string();
+ }
+ }
+
+ NSSCMSContentInfo *cinfo = NSS_CMSMessage_GetContentInfo(message.get());
+ if (NSS_CMSContentInfo_SetContent_SignedData(
+ message.get(), cinfo, signed_data.get()) == SECSuccess) {
+ ignore_result(signed_data.release());
+ } else {
+ DLOG(ERROR) << "NSS_CMSMessage_GetContentInfo failed";
+ return std::string();
+ }
+
+ SECItem cert_p7 = { siBuffer, NULL, 0 };
+ NSSCMSEncoderContext *ecx = NSS_CMSEncoder_Start(message.get(), NULL, NULL,
+ &cert_p7, arena.get(), NULL,
+ NULL, NULL, NULL, NULL,
+ NULL);
+ if (!ecx) {
+ DLOG(ERROR) << "NSS_CMSEncoder_Start failed";
+ return std::string();
+ }
+
+ if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) {
+ DLOG(ERROR) << "NSS_CMSEncoder_Finish failed";
+ return std::string();
+ }
+
+ return string(reinterpret_cast<const char*>(cert_p7.data), cert_p7.len);
+}
+
+string ProcessSecAlgorithmSignature(CERTCertificate* cert_handle) {
+ return ProcessSecAlgorithmInternal(&cert_handle->signature);
+}
+
+string ProcessSecAlgorithmSubjectPublicKey(CERTCertificate* cert_handle) {
+ return ProcessSecAlgorithmInternal(
+ &cert_handle->subjectPublicKeyInfo.algorithm);
+}
+
+string ProcessSecAlgorithmSignatureWrap(CERTCertificate* cert_handle) {
+ return ProcessSecAlgorithmInternal(
+ &cert_handle->signatureWrap.signatureAlgorithm);
+}
+
+string ProcessSubjectPublicKeyInfo(CERTCertificate* cert_handle) {
+ return psm::ProcessSubjectPublicKeyInfo(&cert_handle->subjectPublicKeyInfo);
+}
+
+string ProcessRawBitsSignatureWrap(CERTCertificate* cert_handle) {
+ return ProcessRawBits(cert_handle->signatureWrap.signature.data,
+ cert_handle->signatureWrap.signature.len);
+}
+
+std::string ProcessIDN(const std::string& input) {
+ // Convert the ASCII input to a string16 for ICU.
+ base::string16 input16;
+ input16.reserve(input.length());
+ input16.insert(input16.end(), input.begin(), input.end());
+
+ base::string16 output16 = url_formatter::IDNToUnicode(input);
+ if (input16 == output16)
+ return input; // Input did not contain any encoded data.
+
+ // Input contained encoded data, return formatted string showing original and
+ // decoded forms.
+ return l10n_util::GetStringFUTF8(IDS_CERT_INFO_IDN_VALUE_FORMAT, input16,
+ output16);
+}
+
+std::string ProcessRawBytesWithSeparators(const unsigned char* data,
+ size_t data_length,
+ char hex_separator,
+ char line_separator) {
+ static const char kHexChars[] = "0123456789ABCDEF";
+
+ // Each input byte creates two output hex characters + a space or newline,
+ // except for the last byte.
+ std::string ret;
+ size_t kMin = 0U;
+
+ if (!data_length)
+ return std::string();
+
+ ret.reserve(std::max(kMin, data_length * 3 - 1));
+
+ for (size_t i = 0; i < data_length; ++i) {
+ unsigned char b = data[i];
+ ret.push_back(kHexChars[(b >> 4) & 0xf]);
+ ret.push_back(kHexChars[b & 0xf]);
+ if (i + 1 < data_length) {
+ if ((i + 1) % 16 == 0)
+ ret.push_back(line_separator);
+ else
+ ret.push_back(hex_separator);
+ }
+ }
+ return ret;
+}
+
+std::string ProcessRawBytes(const unsigned char* data, size_t data_length) {
+ return ProcessRawBytesWithSeparators(data, data_length, ' ', '\n');
+}
+
+std::string ProcessRawBits(const unsigned char* data, size_t data_length) {
+ return ProcessRawBytes(data, (data_length + 7) / 8);
+}
+
+} // namespace x509_certificate_model
diff --git a/chromium/chrome/common/net/x509_certificate_model_nss.h b/chromium/chrome/common/net/x509_certificate_model_nss.h
new file mode 100644
index 00000000000..c3a5c24e739
--- /dev/null
+++ b/chromium/chrome/common/net/x509_certificate_model_nss.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_NET_X509_CERTIFICATE_MODEL_NSS_H_
+#define CHROME_COMMON_NET_X509_CERTIFICATE_MODEL_NSS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "net/cert/cert_type.h"
+#include "net/cert/scoped_nss_types.h"
+
+typedef struct CERTCertificateStr CERTCertificate;
+
+namespace base {
+class Time;
+}
+
+// This namespace defines a set of functions to be used in UI-related bits of
+// X509 certificates.
+namespace x509_certificate_model {
+
+std::string GetCertNameOrNickname(CERTCertificate* cert_handle);
+
+std::string GetVersion(CERTCertificate* cert_handle);
+
+net::CertType GetType(CERTCertificate* cert_handle);
+
+void GetUsageStrings(CERTCertificate* cert_handle,
+ std::vector<std::string>* usages);
+
+std::string GetSerialNumberHexified(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetIssuerCommonName(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetIssuerOrgName(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetIssuerOrgUnitName(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetSubjectOrgName(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetSubjectOrgUnitName(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetSubjectCommonName(CERTCertificate* cert_handle,
+ const std::string& alternative_text);
+
+std::string GetIssuerDisplayName(CERTCertificate* cert_handle);
+std::string GetSubjectDisplayName(CERTCertificate* cert_handle);
+
+bool GetTimes(CERTCertificate* cert_handle,
+ base::Time* issued,
+ base::Time* expires);
+
+std::string GetTitle(CERTCertificate* cert_handle);
+std::string GetIssuerName(CERTCertificate* cert_handle);
+std::string GetSubjectName(CERTCertificate* cert_handle);
+
+struct Extension {
+ std::string name;
+ std::string value;
+};
+
+typedef std::vector<Extension> Extensions;
+
+void GetExtensions(const std::string& critical_label,
+ const std::string& non_critical_label,
+ CERTCertificate* cert_handle,
+ Extensions* extensions);
+
+// Hash a certificate using the given algorithm, return the result as a
+// colon-seperated hex string.
+std::string HashCertSHA256(CERTCertificate* cert_handle);
+std::string HashCertSHA1(CERTCertificate* cert_handle);
+
+std::string GetCMSString(const net::ScopedCERTCertificateList& cert_chain,
+ size_t start,
+ size_t end);
+
+std::string ProcessSecAlgorithmSignature(CERTCertificate* cert_handle);
+std::string ProcessSecAlgorithmSubjectPublicKey(CERTCertificate* cert_handle);
+std::string ProcessSecAlgorithmSignatureWrap(CERTCertificate* cert_handle);
+
+std::string ProcessSubjectPublicKeyInfo(CERTCertificate* cert_handle);
+
+std::string ProcessRawBitsSignatureWrap(CERTCertificate* cert_handle);
+
+// For host values, if they contain IDN Punycode-encoded A-labels, this will
+// return a string suitable for display that contains both the original and the
+// decoded U-label form. Otherwise, the string will be returned as is.
+std::string ProcessIDN(const std::string& input);
+
+// Format a buffer as |hex_separator| separated string, with 16 bytes on each
+// line separated using |line_separator|.
+std::string ProcessRawBytesWithSeparators(const unsigned char* data,
+ size_t data_length,
+ char hex_separator,
+ char line_separator);
+
+// Format a buffer as a space separated string, with 16 bytes on each line.
+std::string ProcessRawBytes(const unsigned char* data, size_t data_length);
+
+// Format a buffer as a space separated string, with 16 bytes on each line.
+// |data_length| is the length in bits.
+std::string ProcessRawBits(const unsigned char* data, size_t data_length);
+
+} // namespace x509_certificate_model
+
+#endif // CHROME_COMMON_NET_X509_CERTIFICATE_MODEL_NSS_H_
diff --git a/chromium/chrome/common/net/x509_certificate_model_nss_unittest.cc b/chromium/chrome/common/net/x509_certificate_model_nss_unittest.cc
new file mode 100644
index 00000000000..3bc0cc84cb2
--- /dev/null
+++ b/chromium/chrome/common/net/x509_certificate_model_nss_unittest.cc
@@ -0,0 +1,403 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/x509_certificate_model_nss.h"
+
+#include <stddef.h>
+
+#include "base/files/file_path.h"
+#include "crypto/scoped_test_nss_db.h"
+#include "net/cert/nss_cert_database.h"
+#include "net/cert/scoped_nss_types.h"
+#include "net/cert/x509_util_nss.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(X509CertificateModelTest, GetCertNameOrNicknameAndGetTitle) {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "root_ca_cert.pem"));
+ ASSERT_TRUE(cert.get());
+ EXPECT_EQ("Test Root CA",
+ x509_certificate_model::GetCertNameOrNickname(cert.get()));
+
+ net::ScopedCERTCertificate punycode_cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "punycodetest.pem"));
+ ASSERT_TRUE(punycode_cert.get());
+ EXPECT_EQ("xn--wgv71a119e.com (日本語.com)",
+ x509_certificate_model::GetCertNameOrNickname(punycode_cert.get()));
+
+ net::ScopedCERTCertificate no_cn_cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "no_subject_common_name_cert.pem"));
+ ASSERT_TRUE(no_cn_cert.get());
+ // Temp cert has no nickname.
+ EXPECT_EQ("",
+ x509_certificate_model::GetCertNameOrNickname(no_cn_cert.get()));
+
+ EXPECT_EQ("xn--wgv71a119e.com",
+ x509_certificate_model::GetTitle(punycode_cert.get()));
+
+ EXPECT_EQ("E=wtc@google.com",
+ x509_certificate_model::GetTitle(no_cn_cert.get()));
+
+ net::ScopedCERTCertificate no_cn_cert2(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "ct-test-embedded-cert.pem"));
+ ASSERT_TRUE(no_cn_cert2.get());
+ EXPECT_EQ("L=Erw Wen,ST=Wales,O=Certificate Transparency,C=GB",
+ x509_certificate_model::GetTitle(no_cn_cert2.get()));
+}
+
+TEST(X509CertificateModelTest, GetExtensions) {
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "root_ca_cert.pem"));
+ ASSERT_TRUE(cert.get());
+
+ x509_certificate_model::Extensions extensions;
+ x509_certificate_model::GetExtensions("critical", "notcrit", cert.get(),
+ &extensions);
+ ASSERT_EQ(3U, extensions.size());
+
+ EXPECT_EQ("Certificate Basic Constraints", extensions[0].name);
+ EXPECT_EQ(
+ "critical\nIs a Certification Authority\n"
+ "Maximum number of intermediate CAs: unlimited",
+ extensions[0].value);
+
+ EXPECT_EQ("Certificate Subject Key ID", extensions[1].name);
+ EXPECT_EQ(
+ "notcrit\nKey ID: 9B 26 0B 8A 98 A9 BB 1D B9 1F 1C E3 1A 40 33 ED\n8E "
+ "17 88 AB",
+ extensions[1].value);
+
+ EXPECT_EQ("Certificate Key Usage", extensions[2].name);
+ EXPECT_EQ("critical\nCertificate Signer\nCRL Signer", extensions[2].value);
+ }
+
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "subjectAltName_sanity_check.pem"));
+ ASSERT_TRUE(cert.get());
+
+ x509_certificate_model::Extensions extensions;
+ x509_certificate_model::GetExtensions("critical", "notcrit", cert.get(),
+ &extensions);
+ ASSERT_EQ(2U, extensions.size());
+ EXPECT_EQ("Certificate Subject Alternative Name", extensions[1].name);
+ EXPECT_EQ(
+ "notcrit\nIP Address: 127.0.0.2\nIP Address: fe80::1\nDNS Name: "
+ "test.example\nEmail Address: test@test.example\nOID.1.2.3.4: 0C 09 69 "
+ "67 6E 6F 72 65 20 6D 65\nX.500 Name: CN = 127.0.0.3\n\n",
+ extensions[1].value);
+ }
+
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "foaf.me.chromium-test-cert.der"));
+ ASSERT_TRUE(cert.get());
+
+ x509_certificate_model::Extensions extensions;
+ x509_certificate_model::GetExtensions("critical", "notcrit", cert.get(),
+ &extensions);
+ ASSERT_EQ(5U, extensions.size());
+ EXPECT_EQ("Netscape Certificate Comment", extensions[1].name);
+ EXPECT_EQ("notcrit\nOpenSSL Generated Certificate", extensions[1].value);
+ }
+
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "2029_globalsign_com_cert.pem"));
+ ASSERT_TRUE(cert.get());
+
+ x509_certificate_model::Extensions extensions;
+ x509_certificate_model::GetExtensions("critical", "notcrit", cert.get(),
+ &extensions);
+ ASSERT_EQ(9U, extensions.size());
+
+ EXPECT_EQ("Certificate Subject Key ID", extensions[0].name);
+ EXPECT_EQ(
+ "notcrit\nKey ID: 59 BC D9 69 F7 B0 65 BB C8 34 C5 D2 C2 EF 17 78\nA6 "
+ "47 1E 8B",
+ extensions[0].value);
+
+ EXPECT_EQ("Certification Authority Key ID", extensions[1].name);
+ EXPECT_EQ(
+ "notcrit\nKey ID: 8A FC 14 1B 3D A3 59 67 A5 3B E1 73 92 A6 62 91\n7F "
+ "E4 78 30\n",
+ extensions[1].value);
+
+ EXPECT_EQ("Authority Information Access", extensions[2].name);
+ EXPECT_EQ(
+ "notcrit\nCA Issuers: "
+ "URI: http://secure.globalsign.net/cacert/SHA256extendval1.crt\n",
+ extensions[2].value);
+
+ EXPECT_EQ("CRL Distribution Points", extensions[3].name);
+ EXPECT_EQ("notcrit\nURI: http://crl.globalsign.net/SHA256ExtendVal1.crl\n",
+ extensions[3].value);
+
+ EXPECT_EQ("Certificate Basic Constraints", extensions[4].name);
+ EXPECT_EQ("notcrit\nIs not a Certification Authority\n",
+ extensions[4].value);
+
+ EXPECT_EQ("Certificate Key Usage", extensions[5].name);
+ EXPECT_EQ(
+ "critical\nSigning\nNon-repudiation\nKey Encipherment\n"
+ "Data Encipherment",
+ extensions[5].value);
+
+ EXPECT_EQ("Extended Key Usage", extensions[6].name);
+ EXPECT_EQ(
+ "notcrit\nTLS WWW Server Authentication (OID.1.3.6.1.5.5.7.3.1)\n"
+ "TLS WWW Client Authentication (OID.1.3.6.1.5.5.7.3.2)\n",
+ extensions[6].value);
+
+ EXPECT_EQ("Certificate Policies", extensions[7].name);
+ EXPECT_EQ(
+ "notcrit\nOID.1.3.6.1.4.1.4146.1.1:\n"
+ " Certification Practice Statement Pointer:"
+ " http://www.globalsign.net/repository/\n",
+ extensions[7].value);
+
+ EXPECT_EQ("Netscape Certificate Type", extensions[8].name);
+ EXPECT_EQ("notcrit\nSSL Client Certificate\nSSL Server Certificate",
+ extensions[8].value);
+ }
+
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "diginotar_public_ca_2025.pem"));
+ ASSERT_TRUE(cert.get());
+
+ x509_certificate_model::Extensions extensions;
+ x509_certificate_model::GetExtensions("critical", "notcrit", cert.get(),
+ &extensions);
+ ASSERT_EQ(7U, extensions.size());
+
+ EXPECT_EQ("Authority Information Access", extensions[0].name);
+ EXPECT_EQ(
+ "notcrit\nOCSP Responder: "
+ "URI: http://validation.diginotar.nl\n",
+ extensions[0].value);
+
+ EXPECT_EQ("Certificate Basic Constraints", extensions[2].name);
+ EXPECT_EQ(
+ "critical\nIs a Certification Authority\n"
+ "Maximum number of intermediate CAs: 0",
+ extensions[2].value);
+ EXPECT_EQ("Certificate Policies", extensions[3].name);
+ EXPECT_EQ(
+ "notcrit\nOID.2.16.528.1.1001.1.1.1.1.5.2.6.4:\n"
+ " Certification Practice Statement Pointer:"
+ " http://www.diginotar.nl/cps\n"
+ " User Notice:\n"
+ " Conditions, as mentioned on our website (www.diginotar.nl), are "
+ "applicable to all our products and services.\n",
+ extensions[3].value);
+ }
+}
+
+TEST(X509CertificateModelTest, GetTypeCA) {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "root_ca_cert.pem"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ(net::CA_CERT, x509_certificate_model::GetType(cert.get()));
+
+ crypto::ScopedTestNSSDB test_nssdb;
+ net::NSSCertDatabase db(crypto::ScopedPK11Slot(PK11_ReferenceSlot(
+ test_nssdb.slot())) /* public slot */,
+ crypto::ScopedPK11Slot(PK11_ReferenceSlot(
+ test_nssdb.slot())) /* private slot */);
+
+ // Test that explicitly distrusted CA certs are still returned as CA_CERT
+ // type. See http://crbug.com/96654.
+ EXPECT_TRUE(db.SetCertTrust(cert.get(), net::CA_CERT,
+ net::NSSCertDatabase::DISTRUSTED_SSL));
+
+ EXPECT_EQ(net::CA_CERT, x509_certificate_model::GetType(cert.get()));
+}
+
+TEST(X509CertificateModelTest, GetTypeServer) {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "google.single.der"));
+ ASSERT_TRUE(cert.get());
+
+ // Test mozilla_security_manager::GetCertType with server certs and default
+ // trust. Currently this doesn't work.
+ // TODO(mattm): make mozilla_security_manager::GetCertType smarter so we can
+ // tell server certs even if they have no trust bits set.
+ EXPECT_EQ(net::OTHER_CERT, x509_certificate_model::GetType(cert.get()));
+
+ crypto::ScopedTestNSSDB test_nssdb;
+ net::NSSCertDatabase db(crypto::ScopedPK11Slot(PK11_ReferenceSlot(
+ test_nssdb.slot())) /* public slot */,
+ crypto::ScopedPK11Slot(PK11_ReferenceSlot(
+ test_nssdb.slot())) /* private slot */);
+
+ // Test GetCertType with server certs and explicit trust.
+ EXPECT_TRUE(db.SetCertTrust(cert.get(), net::SERVER_CERT,
+ net::NSSCertDatabase::TRUSTED_SSL));
+
+ EXPECT_EQ(net::SERVER_CERT, x509_certificate_model::GetType(cert.get()));
+
+ // Test GetCertType with server certs and explicit distrust.
+ EXPECT_TRUE(db.SetCertTrust(cert.get(), net::SERVER_CERT,
+ net::NSSCertDatabase::DISTRUSTED_SSL));
+
+ EXPECT_EQ(net::SERVER_CERT, x509_certificate_model::GetType(cert.get()));
+}
+
+// An X.509 v1 certificate with the version field omitted should get
+// the default value v1.
+TEST(X509CertificateModelTest, GetVersionOmitted) {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "ndn.ca.crt"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ("1", x509_certificate_model::GetVersion(cert.get()));
+}
+
+TEST(X509CertificateModelTest, GetCMSString) {
+ net::ScopedCERTCertificateList certs = CreateCERTCertificateListFromFile(
+ net::GetTestCertsDirectory(), "multi-root-chain1.pem",
+ net::X509Certificate::FORMAT_AUTO);
+
+ {
+ // Write the full chain.
+ std::string pkcs7_string =
+ x509_certificate_model::GetCMSString(certs, 0, certs.size());
+
+ ASSERT_FALSE(pkcs7_string.empty());
+
+ net::ScopedCERTCertificateList decoded_certs =
+ net::x509_util::CreateCERTCertificateListFromBytes(
+ pkcs7_string.data(), pkcs7_string.size(),
+ net::X509Certificate::FORMAT_PKCS7);
+
+ ASSERT_EQ(certs.size(), decoded_certs.size());
+
+ // NSS sorts the certs before writing the file.
+ EXPECT_TRUE(net::x509_util::IsSameCertificate(certs[0].get(),
+ decoded_certs.back().get()));
+ for (size_t i = 1; i < certs.size(); ++i)
+ EXPECT_TRUE(net::x509_util::IsSameCertificate(
+ certs[i].get(), decoded_certs[i - 1].get()));
+ }
+
+ {
+ // Write only the first cert.
+ std::string pkcs7_string =
+ x509_certificate_model::GetCMSString(certs, 0, 1);
+
+ net::ScopedCERTCertificateList decoded_certs =
+ net::x509_util::CreateCERTCertificateListFromBytes(
+ pkcs7_string.data(), pkcs7_string.size(),
+ net::X509Certificate::FORMAT_PKCS7);
+
+ ASSERT_EQ(1U, decoded_certs.size());
+ EXPECT_TRUE(net::x509_util::IsSameCertificate(certs[0].get(),
+ decoded_certs[0].get()));
+ }
+}
+
+TEST(X509CertificateModelTest, ProcessSecAlgorithms) {
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "root_ca_cert.pem"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ("PKCS #1 SHA-256 With RSA Encryption",
+ x509_certificate_model::ProcessSecAlgorithmSignature(cert.get()));
+ EXPECT_EQ(
+ "PKCS #1 SHA-256 With RSA Encryption",
+ x509_certificate_model::ProcessSecAlgorithmSignatureWrap(cert.get()));
+ EXPECT_EQ("PKCS #1 RSA Encryption",
+ x509_certificate_model::ProcessSecAlgorithmSubjectPublicKey(
+ cert.get()));
+ }
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "weak_digest_md5_root.pem"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ("PKCS #1 MD5 With RSA Encryption",
+ x509_certificate_model::ProcessSecAlgorithmSignature(cert.get()));
+ EXPECT_EQ(
+ "PKCS #1 MD5 With RSA Encryption",
+ x509_certificate_model::ProcessSecAlgorithmSignatureWrap(cert.get()));
+ EXPECT_EQ("PKCS #1 RSA Encryption",
+ x509_certificate_model::ProcessSecAlgorithmSubjectPublicKey(
+ cert.get()));
+ }
+}
+
+TEST(X509CertificateModelTest, ProcessSubjectPublicKeyInfo) {
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "root_ca_cert.pem"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ(
+ "Modulus (2048 bits):\n"
+ " C6 81 1F 92 73 B6 58 85 D9 8D AC B7 20 FD C7 BF\n"
+ "40 B2 EA FA E5 0B 52 01 8F 9A C1 EB 7A 80 C1 F3\n"
+ "89 A4 3E D5 1B 61 CC B5 CF 80 B1 1A DB BB 25 E0\n"
+ "18 BF 92 69 26 50 CD E7 3F FF 0D 3C B4 1F 14 12\n"
+ "AB 67 37 DE 07 03 6C 12 74 82 36 AC C3 D4 D3 64\n"
+ "9F 91 ED 5B F6 A9 7A A4 9C 98 E8 65 6C 94 E1 CB\n"
+ "55 73 AE F8 1D 50 B0 78 E5 74 FF B1 37 2C CB 19\n"
+ "3D A4 8C E7 76 4E 86 5C 3F DF B3 ED 45 23 4F 54\n"
+ "9B 33 C6 89 5E 13 1D DD 7D 59 A5 07 34 28 86 27\n"
+ "1F FA 9E 53 4F 2A B6 42 AD 37 12 62 F5 72 36 B6\n"
+ "02 12 40 44 FE C7 9E 95 89 43 51 5E B4 6E C7 67\n"
+ "80 58 43 BE CC 07 28 BD 59 FF 1C 4C 8D 90 42 F4\n"
+ "CF FD 54 00 4F 48 72 2B E1 67 3C 84 17 68 95 BF\n"
+ "CA 07 7B DF 86 9D 56 E3 32 E3 70 87 B7 F8 3A F7\n"
+ "E3 6E 65 14 7C BB 76 B7 17 F1 42 8C 6F 2A 34 64\n"
+ "10 35 14 8C 85 F6 57 BF F3 5C 55 9D AD 03 10 F3\n"
+ "\n"
+ " Public Exponent (24 bits):\n"
+ " 01 00 01",
+ x509_certificate_model::ProcessSubjectPublicKeyInfo(cert.get()));
+ }
+ {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "prime256v1-ecdsa-intermediate.pem"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ(
+ "04 D5 C1 4A 32 95 95 C5 88 FA 01 FA C5 9E DC E2\n"
+ "99 62 EB 13 E5 35 42 B3 7A FC 46 C0 FA 29 12 C8\n"
+ "2D EA 30 0F D2 9A 47 97 2C 7E 89 E6 EF 49 55 06\n"
+ "C9 37 C7 99 56 16 B2 2B C9 7C 69 8E 10 7A DD 1F\n"
+ "42",
+ x509_certificate_model::ProcessSubjectPublicKeyInfo(cert.get()));
+ }
+}
+
+TEST(X509CertificateModelTest, ProcessRawBitsSignatureWrap) {
+ net::ScopedCERTCertificate cert(net::ImportCERTCertificateFromFile(
+ net::GetTestCertsDirectory(), "root_ca_cert.pem"));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ(
+ "5B 53 FF 6D D5 0A 43 A5 0F D4 7D C6 5D 88 E3 98\n"
+ "9D 67 EB 32 82 B3 0F F5 C1 78 F8 05 4A BF BC 21\n"
+ "05 EE 21 08 2C B2 15 A1 B8 B2 F6 A3 15 61 E4 C1\n"
+ "AD 84 A4 A7 40 0C 87 09 5F 2B 1B F9 4D 6C 92 7D\n"
+ "CB 7E 2B B0 01 0A ED 40 E5 4E AF 1A F1 0D EC 1D\n"
+ "9E 96 C7 D4 61 64 39 23 FA 5F 29 C4 2A 3A B8 ED\n"
+ "8A 72 50 6A AC 45 04 76 09 A8 3D 57 D7 F0 4B AE\n"
+ "46 B4 83 C1 14 50 2A 19 59 53 B2 4D AE FC 2F 40\n"
+ "49 C8 AD 4D 9D C8 22 8D 8C 01 DB 31 33 5A F4 BC\n"
+ "4C 9B ED D7 E3 43 D9 E8 1D 53 8B 30 D8 81 9E 72\n"
+ "AB 9E CE B8 F5 83 93 F2 72 DB DE CD B0 52 9A 45\n"
+ "4D CF E7 21 D8 CE 16 64 8F 42 AF C1 87 A8 F9 D5\n"
+ "E2 03 DD BA 6B 1B 7C 7D A0 38 33 61 39 B4 DD 5C\n"
+ "69 17 79 02 3A EC 1D 6F 5E BB 13 FB A6 82 5D 07\n"
+ "20 FC 86 FE 6E 8B AC E1 C2 18 A2 FE 3F 95 66 D3\n"
+ "69 8A 00 06 2C 56 37 34 B9 B6 31 DE 0F F6 44 39",
+ x509_certificate_model::ProcessRawBitsSignatureWrap(cert.get()));
+}
diff --git a/chromium/chrome/common/origin_trials/OWNERS b/chromium/chrome/common/origin_trials/OWNERS
new file mode 100644
index 00000000000..de0e62d14ac
--- /dev/null
+++ b/chromium/chrome/common/origin_trials/OWNERS
@@ -0,0 +1,3 @@
+file://third_party/blink/common/origin_trials/OWNERS
+# COMPONENT: Internals>OriginTrials
+# TEAM: experimentation-dev@chromium.org
diff --git a/chromium/chrome/common/origin_trials/chrome_origin_trial_policy.cc b/chromium/chrome/common/origin_trials/chrome_origin_trial_policy.cc
new file mode 100644
index 00000000000..deacef0b0db
--- /dev/null
+++ b/chromium/chrome/common/origin_trials/chrome_origin_trial_policy.cc
@@ -0,0 +1,117 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/origin_trials/chrome_origin_trial_policy.h"
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/origin_util.h"
+
+// This is the default public key used for validating signatures.
+// TODO(iclelland): Provide a mechanism to allow for multiple signing keys.
+// https://crbug.com/584737
+static const uint8_t kDefaultPublicKey[] = {
+ 0x7c, 0xc4, 0xb8, 0x9a, 0x93, 0xba, 0x6e, 0xe2, 0xd0, 0xfd, 0x03,
+ 0x1d, 0xfb, 0x32, 0x66, 0xc7, 0x3b, 0x72, 0xfd, 0x54, 0x3a, 0x07,
+ 0x51, 0x14, 0x66, 0xaa, 0x02, 0x53, 0x4e, 0x33, 0xa1, 0x15,
+};
+
+ChromeOriginTrialPolicy::ChromeOriginTrialPolicy()
+ : public_key_(std::string(reinterpret_cast<const char*>(kDefaultPublicKey),
+ base::size(kDefaultPublicKey))) {
+ // Set the public key and disabled feature list for the origin trial key
+ // manager, based on the command line flags which were passed to this process.
+ // If the flags are not present, or are incorrectly formatted, the defaults
+ // will remain active.
+ if (base::CommandLine::InitializedForCurrentProcess()) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kOriginTrialPublicKey)) {
+ SetPublicKeyFromASCIIString(
+ command_line->GetSwitchValueASCII(switches::kOriginTrialPublicKey));
+ }
+ if (command_line->HasSwitch(switches::kOriginTrialDisabledFeatures)) {
+ SetDisabledFeatures(command_line->GetSwitchValueASCII(
+ switches::kOriginTrialDisabledFeatures));
+ }
+ if (command_line->HasSwitch(switches::kOriginTrialDisabledTokens)) {
+ SetDisabledTokens(command_line->GetSwitchValueASCII(
+ switches::kOriginTrialDisabledTokens));
+ }
+ }
+}
+
+ChromeOriginTrialPolicy::~ChromeOriginTrialPolicy() {}
+
+bool ChromeOriginTrialPolicy::IsOriginTrialsSupported() const {
+ return true;
+}
+
+base::StringPiece ChromeOriginTrialPolicy::GetPublicKey() const {
+ return base::StringPiece(public_key_);
+}
+
+bool ChromeOriginTrialPolicy::IsFeatureDisabled(
+ base::StringPiece feature) const {
+ return disabled_features_.count(feature.as_string()) > 0;
+}
+
+bool ChromeOriginTrialPolicy::IsTokenDisabled(
+ base::StringPiece token_signature) const {
+ return disabled_tokens_.count(token_signature.as_string()) > 0;
+}
+
+bool ChromeOriginTrialPolicy::IsOriginSecure(const GURL& url) const {
+ return content::IsOriginSecure(url);
+}
+
+bool ChromeOriginTrialPolicy::SetPublicKeyFromASCIIString(
+ const std::string& ascii_public_key) {
+ // Base64-decode the incoming string. Set the key if it is correctly formatted
+ std::string new_public_key;
+ if (!base::Base64Decode(ascii_public_key, &new_public_key))
+ return false;
+ if (new_public_key.size() != 32)
+ return false;
+ public_key_.swap(new_public_key);
+ return true;
+}
+
+bool ChromeOriginTrialPolicy::SetDisabledFeatures(
+ const std::string& disabled_feature_list) {
+ std::set<std::string> new_disabled_features;
+ const std::vector<std::string> features =
+ base::SplitString(disabled_feature_list, "|", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ for (const std::string& feature : features)
+ new_disabled_features.insert(feature);
+ disabled_features_.swap(new_disabled_features);
+ return true;
+}
+
+bool ChromeOriginTrialPolicy::SetDisabledTokens(
+ const std::string& disabled_token_list) {
+ std::set<std::string> new_disabled_tokens;
+ const std::vector<std::string> tokens =
+ base::SplitString(disabled_token_list, "|", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ for (const std::string& ascii_token : tokens) {
+ std::string token_signature;
+ if (!base::Base64Decode(ascii_token, &token_signature))
+ continue;
+ if (token_signature.size() != 64)
+ continue;
+ new_disabled_tokens.insert(token_signature);
+ }
+ disabled_tokens_.swap(new_disabled_tokens);
+ return true;
+}
diff --git a/chromium/chrome/common/origin_trials/chrome_origin_trial_policy.h b/chromium/chrome/common/origin_trials/chrome_origin_trial_policy.h
new file mode 100644
index 00000000000..ebb892ee667
--- /dev/null
+++ b/chromium/chrome/common/origin_trials/chrome_origin_trial_policy.h
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_ORIGIN_TRIALS_CHROME_ORIGIN_TRIAL_POLICY_H_
+#define CHROME_COMMON_ORIGIN_TRIALS_CHROME_ORIGIN_TRIAL_POLICY_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "third_party/blink/public/common/origin_trials/origin_trial_policy.h"
+
+// This class is instantiated on the main/ui thread, but its methods can be
+// accessed from any thread.
+class ChromeOriginTrialPolicy : public blink::OriginTrialPolicy {
+ public:
+ ChromeOriginTrialPolicy();
+ ~ChromeOriginTrialPolicy() override;
+
+ // blink::OriginTrialPolicy interface
+ bool IsOriginTrialsSupported() const override;
+ base::StringPiece GetPublicKey() const override;
+ bool IsFeatureDisabled(base::StringPiece feature) const override;
+ bool IsTokenDisabled(base::StringPiece token_signature) const override;
+ bool IsOriginSecure(const GURL& url) const override;
+
+ bool SetPublicKeyFromASCIIString(const std::string& ascii_public_key);
+ bool SetDisabledFeatures(const std::string& disabled_feature_list);
+ bool SetDisabledTokens(const std::string& disabled_token_list);
+
+ private:
+ std::string public_key_;
+ std::set<std::string> disabled_features_;
+ std::set<std::string> disabled_tokens_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeOriginTrialPolicy);
+};
+
+#endif // CHROME_COMMON_ORIGIN_TRIALS_CHROME_ORIGIN_TRIAL_POLICY_H_
diff --git a/chromium/chrome/common/origin_trials/chrome_origin_trial_policy_unittest.cc b/chromium/chrome/common/origin_trials/chrome_origin_trial_policy_unittest.cc
new file mode 100644
index 00000000000..831c8b6d086
--- /dev/null
+++ b/chromium/chrome/common/origin_trials/chrome_origin_trial_policy_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/origin_trials/chrome_origin_trial_policy.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const uint8_t kTestPublicKey[] = {
+ 0x75, 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2,
+ 0x9a, 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f,
+ 0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0,
+};
+
+// Base64 encoding of the above sample public key
+const char kTestPublicKeyString[] =
+ "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
+const char kBadEncodingPublicKeyString[] = "Not even base64!";
+// Base64-encoded, 31 bytes long
+const char kTooShortPublicKeyString[] =
+ "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BN==";
+// Base64-encoded, 33 bytes long
+const char kTooLongPublicKeyString[] =
+ "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNAA";
+
+const char kOneDisabledFeature[] = "A";
+const char kTwoDisabledFeatures[] = "A|B";
+const char kThreeDisabledFeatures[] = "A|B|C";
+const char kSpacesInDisabledFeatures[] = "A|B C";
+
+// Various tokens, each provide the command (in tools/origin_trials) used for
+// generation.
+// generate_token.py example.com A --expire-timestamp=2000000000
+const uint8_t kToken1Signature[] = {
+ 0x43, 0xdd, 0xd3, 0x2b, 0x12, 0x09, 0x59, 0x52, 0x17, 0xf3, 0x60,
+ 0x44, 0xab, 0xae, 0x18, 0xcd, 0xcd, 0x20, 0xf4, 0x0f, 0x37, 0x8c,
+ 0x04, 0x98, 0x8b, 0x8e, 0xf5, 0x7f, 0x56, 0xe3, 0x22, 0xa8, 0xe5,
+ 0x02, 0x08, 0xfc, 0x2b, 0xd8, 0x6e, 0x91, 0x1f, 0x8f, 0xf1, 0xec,
+ 0x61, 0xbc, 0x0d, 0xb2, 0x96, 0xcf, 0xc3, 0xf0, 0xc2, 0xc3, 0x23,
+ 0xe9, 0x34, 0x4f, 0x55, 0x62, 0x46, 0xcb, 0x57, 0x0b};
+const char kToken1SignatureEncoded[] =
+ "Q93TKxIJWVIX82BEq64Yzc0g9A83jASYi471f1bjIqjlAgj8K9hukR+P8exhvA2yls/"
+ "D8MLDI+k0T1ViRstXCw==";
+// generate_token.py example.com A --expire-timestamp=2500000000
+const uint8_t kToken2Signature[] = {
+ 0xcd, 0x7f, 0x73, 0xb4, 0x49, 0xf5, 0xff, 0xef, 0xf3, 0x71, 0x4e,
+ 0x3d, 0xbd, 0x07, 0xcb, 0x94, 0xd7, 0x25, 0x6f, 0x48, 0x14, 0x2f,
+ 0xb6, 0x9a, 0xc1, 0x33, 0xf6, 0x8f, 0x8f, 0x72, 0xab, 0xd8, 0xeb,
+ 0x52, 0x5a, 0x20, 0x49, 0xad, 0xf0, 0x84, 0x49, 0x22, 0x64, 0x65,
+ 0x25, 0xa2, 0xb4, 0xc8, 0x5d, 0xc3, 0xa4, 0x24, 0xaf, 0xac, 0xcd,
+ 0x48, 0x22, 0xa4, 0x21, 0x1f, 0x2b, 0xf0, 0xb1, 0x02};
+const char kToken2SignatureEncoded[] =
+ "zX9ztEn1/+/"
+ "zcU49vQfLlNclb0gUL7aawTP2j49yq9jrUlogSa3whEkiZGUlorTIXcOkJK+szUgipCEfK/"
+ "CxAg==";
+// generate_token.py example.com B --expire-timestamp=2000000000
+const uint8_t kToken3Signature[] = {
+ 0x33, 0x49, 0x37, 0x0e, 0x92, 0xbc, 0xf8, 0xf6, 0x71, 0xa9, 0x7a,
+ 0x46, 0xd5, 0x35, 0x6d, 0x30, 0xd6, 0x89, 0xe3, 0xa4, 0x5b, 0x0b,
+ 0xae, 0x6c, 0x77, 0x47, 0xe9, 0x5a, 0x20, 0x14, 0x0d, 0x6f, 0xde,
+ 0xb4, 0x20, 0xe6, 0xce, 0x3a, 0xf1, 0xcb, 0x92, 0xf9, 0xaf, 0xb2,
+ 0x89, 0x19, 0xce, 0x35, 0xcc, 0x63, 0x5f, 0x59, 0xd9, 0xef, 0x8f,
+ 0xf9, 0xa1, 0x92, 0xda, 0x8b, 0xda, 0xfd, 0xf1, 0x08};
+const char kToken3SignatureEncoded[] =
+ "M0k3DpK8+PZxqXpG1TVtMNaJ46RbC65sd0fpWiAUDW/etCDmzjrxy5L5r7KJGc41zGNfWdnvj/"
+ "mhktqL2v3xCA==";
+const char kTokenSeparator[] = "|";
+
+class ChromeOriginTrialPolicyTest : public testing::Test {
+ protected:
+ ChromeOriginTrialPolicyTest()
+ : token1_signature_(
+ std::string(reinterpret_cast<const char*>(kToken1Signature),
+ base::size(kToken1Signature))),
+ token2_signature_(
+ std::string(reinterpret_cast<const char*>(kToken2Signature),
+ base::size(kToken2Signature))),
+ token3_signature_(
+ std::string(reinterpret_cast<const char*>(kToken3Signature),
+ base::size(kToken3Signature))),
+ two_disabled_tokens_(
+ base::JoinString({kToken1SignatureEncoded, kToken2SignatureEncoded},
+ kTokenSeparator)),
+ three_disabled_tokens_(
+ base::JoinString({kToken1SignatureEncoded, kToken2SignatureEncoded,
+ kToken3SignatureEncoded},
+ kTokenSeparator)),
+ manager_(base::WrapUnique(new ChromeOriginTrialPolicy())),
+ default_key_(manager_->GetPublicKey().as_string()),
+ test_key_(std::string(reinterpret_cast<const char*>(kTestPublicKey),
+ base::size(kTestPublicKey))) {}
+
+ ChromeOriginTrialPolicy* manager() { return manager_.get(); }
+ base::StringPiece default_key() { return default_key_; }
+ base::StringPiece test_key() { return test_key_; }
+ std::string token1_signature_;
+ std::string token2_signature_;
+ std::string token3_signature_;
+ std::string two_disabled_tokens_;
+ std::string three_disabled_tokens_;
+
+ private:
+ std::unique_ptr<ChromeOriginTrialPolicy> manager_;
+ std::string default_key_;
+ std::string test_key_;
+};
+
+TEST_F(ChromeOriginTrialPolicyTest, DefaultConstructor) {
+ // We don't specify here what the key should be, but make sure that it is
+ // returned, is valid, and is consistent.
+ base::StringPiece key = manager()->GetPublicKey();
+ EXPECT_EQ(32UL, key.size());
+ EXPECT_EQ(default_key(), key);
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DefaultKeyIsConsistent) {
+ ChromeOriginTrialPolicy manager2;
+ EXPECT_EQ(manager()->GetPublicKey(), manager2.GetPublicKey());
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, OverridePublicKey) {
+ EXPECT_TRUE(manager()->SetPublicKeyFromASCIIString(kTestPublicKeyString));
+ EXPECT_NE(default_key(), manager()->GetPublicKey());
+ EXPECT_EQ(test_key(), manager()->GetPublicKey());
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, OverrideKeyNotBase64) {
+ EXPECT_FALSE(
+ manager()->SetPublicKeyFromASCIIString(kBadEncodingPublicKeyString));
+ EXPECT_EQ(default_key(), manager()->GetPublicKey());
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, OverrideKeyTooShort) {
+ EXPECT_FALSE(
+ manager()->SetPublicKeyFromASCIIString(kTooShortPublicKeyString));
+ EXPECT_EQ(default_key(), manager()->GetPublicKey());
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, OverrideKeyTooLong) {
+ EXPECT_FALSE(manager()->SetPublicKeyFromASCIIString(kTooLongPublicKeyString));
+ EXPECT_EQ(default_key(), manager()->GetPublicKey());
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, NoDisabledFeatures) {
+ EXPECT_FALSE(manager()->IsFeatureDisabled("A"));
+ EXPECT_FALSE(manager()->IsFeatureDisabled("B"));
+ EXPECT_FALSE(manager()->IsFeatureDisabled("C"));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableOneFeature) {
+ EXPECT_TRUE(manager()->SetDisabledFeatures(kOneDisabledFeature));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("A"));
+ EXPECT_FALSE(manager()->IsFeatureDisabled("B"));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableTwoFeatures) {
+ EXPECT_TRUE(manager()->SetDisabledFeatures(kTwoDisabledFeatures));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("A"));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("B"));
+ EXPECT_FALSE(manager()->IsFeatureDisabled("C"));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableThreeFeatures) {
+ EXPECT_TRUE(manager()->SetDisabledFeatures(kThreeDisabledFeatures));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("A"));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("B"));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("C"));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableFeatureWithSpace) {
+ EXPECT_TRUE(manager()->SetDisabledFeatures(kSpacesInDisabledFeatures));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("A"));
+ EXPECT_TRUE(manager()->IsFeatureDisabled("B C"));
+ EXPECT_FALSE(manager()->IsFeatureDisabled("B"));
+ EXPECT_FALSE(manager()->IsFeatureDisabled("C"));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, NoDisabledTokens) {
+ EXPECT_FALSE(manager()->IsTokenDisabled(token1_signature_));
+ EXPECT_FALSE(manager()->IsTokenDisabled(token2_signature_));
+ EXPECT_FALSE(manager()->IsTokenDisabled(token3_signature_));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableOneToken) {
+ EXPECT_TRUE(manager()->SetDisabledTokens(kToken1SignatureEncoded));
+ EXPECT_TRUE(manager()->IsTokenDisabled(token1_signature_));
+ EXPECT_FALSE(manager()->IsTokenDisabled(token2_signature_));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableTwoTokens) {
+ EXPECT_TRUE(manager()->SetDisabledTokens(two_disabled_tokens_));
+ EXPECT_TRUE(manager()->IsTokenDisabled(token1_signature_));
+ EXPECT_TRUE(manager()->IsTokenDisabled(token2_signature_));
+ EXPECT_FALSE(manager()->IsTokenDisabled(token3_signature_));
+}
+
+TEST_F(ChromeOriginTrialPolicyTest, DisableThreeTokens) {
+ EXPECT_TRUE(manager()->SetDisabledTokens(three_disabled_tokens_));
+ EXPECT_TRUE(manager()->IsTokenDisabled(token1_signature_));
+ EXPECT_TRUE(manager()->IsTokenDisabled(token2_signature_));
+ EXPECT_TRUE(manager()->IsTokenDisabled(token3_signature_));
+}
+
+// Tests for initialization from command line
+class ChromeOriginTrialPolicyInitializationTest
+ : public ChromeOriginTrialPolicyTest {
+ protected:
+ ChromeOriginTrialPolicyInitializationTest() {}
+
+ ChromeOriginTrialPolicy* initialized_manager() {
+ return initialized_manager_.get();
+ }
+
+ void SetUp() override {
+ ChromeOriginTrialPolicyTest::SetUp();
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ ASSERT_FALSE(command_line->HasSwitch(switches::kOriginTrialPublicKey));
+ ASSERT_FALSE(
+ command_line->HasSwitch(switches::kOriginTrialDisabledFeatures));
+ ASSERT_FALSE(command_line->HasSwitch(switches::kOriginTrialDisabledTokens));
+
+ // Setup command line with various updated values
+ // New public key
+ command_line->AppendSwitchASCII(switches::kOriginTrialPublicKey,
+ kTestPublicKeyString);
+ // One disabled feature
+ command_line->AppendSwitchASCII(switches::kOriginTrialDisabledFeatures,
+ kOneDisabledFeature);
+ // One disabled token
+ command_line->AppendSwitchASCII(switches::kOriginTrialDisabledTokens,
+ kToken1SignatureEncoded);
+
+ initialized_manager_ = base::WrapUnique(new ChromeOriginTrialPolicy());
+ }
+
+ private:
+ std::unique_ptr<ChromeOriginTrialPolicy> initialized_manager_;
+};
+
+TEST_F(ChromeOriginTrialPolicyInitializationTest, PublicKeyInitialized) {
+ EXPECT_NE(default_key(), initialized_manager()->GetPublicKey());
+ EXPECT_EQ(test_key(), initialized_manager()->GetPublicKey());
+}
+
+TEST_F(ChromeOriginTrialPolicyInitializationTest, DisabledFeaturesInitialized) {
+ EXPECT_TRUE(initialized_manager()->IsFeatureDisabled("A"));
+ EXPECT_FALSE(initialized_manager()->IsFeatureDisabled("B"));
+}
+
+TEST_F(ChromeOriginTrialPolicyInitializationTest, DisabledTokensInitialized) {
+ EXPECT_TRUE(initialized_manager()->IsTokenDisabled(token1_signature_));
+ EXPECT_FALSE(initialized_manager()->IsTokenDisabled(token2_signature_));
+}
diff --git a/chromium/chrome/common/pdf_util.cc b/chromium/chrome/common/pdf_util.cc
new file mode 100644
index 00000000000..cda76216d46
--- /dev/null
+++ b/chromium/chrome/common/pdf_util.cc
@@ -0,0 +1,33 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/pdf_util.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "chrome/grit/renderer_resources.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/webui/jstemplate_builder.h"
+#include "ui/base/webui/web_ui_util.h"
+#include "url/gurl.h"
+
+void ReportPDFLoadStatus(PDFLoadStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PDF.LoadStatus", status,
+ PDFLoadStatus::kPdfLoadStatusCount);
+}
+
+std::string GetPDFPlaceholderHTML(const GURL& pdf_url) {
+ std::string template_html = ui::ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_PDF_PLUGIN_HTML)
+ .as_string();
+ webui::AppendWebUiCssTextDefaults(&template_html);
+
+ base::DictionaryValue values;
+ values.SetString("fileName", pdf_url.ExtractFileName());
+ values.SetString("open", l10n_util::GetStringUTF8(IDS_ACCNAME_OPEN));
+ values.SetString("pdfUrl", pdf_url.spec());
+
+ return webui::GetI18nTemplateHtml(template_html, &values);
+}
diff --git a/chromium/chrome/common/pdf_util.h b/chromium/chrome/common/pdf_util.h
new file mode 100644
index 00000000000..2eed9090acf
--- /dev/null
+++ b/chromium/chrome/common/pdf_util.h
@@ -0,0 +1,31 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PDF_UTIL_H_
+#define CHROME_COMMON_PDF_UTIL_H_
+
+#include <string>
+
+class GURL;
+
+// Must be kept in sync with PDFLoadStatus enum in histograms.xml.
+// This enum should be treated as append-only.
+enum class PDFLoadStatus {
+ kLoadedFullPagePdfWithPdfium = 0,
+ kLoadedEmbeddedPdfWithPdfium = 1,
+ kShowedDisabledPluginPlaceholderForEmbeddedPdf = 2,
+ kTriggeredNoGestureDriveByDownload = 3,
+ kLoadedIframePdfWithNoPdfViewer = 4,
+ kViewPdfClickedInPdfPluginPlaceholder = 5,
+ kPdfLoadStatusCount
+};
+
+void ReportPDFLoadStatus(PDFLoadStatus status);
+
+// Returns the HTML contents of the placeholder.
+std::string GetPDFPlaceholderHTML(const GURL& pdf_url);
+
+constexpr char kPDFMimeType[] = "application/pdf";
+
+#endif // CHROME_COMMON_PDF_UTIL_H_
diff --git a/chromium/chrome/common/pepper_flash.cc b/chromium/chrome/common/pepper_flash.cc
new file mode 100644
index 00000000000..2cdac4a8914
--- /dev/null
+++ b/chromium/chrome/common/pepper_flash.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/pepper_flash.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_split.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "chrome/common/ppapi_utils.h"
+#include "ppapi/c/private/ppb_pdf.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+
+#if defined(OS_WIN)
+#include "base/win/registry.h"
+#endif
+
+const int32_t kPepperFlashPermissions =
+ ppapi::PERMISSION_DEFAULT | ppapi::PERMISSION_DEV |
+ ppapi::PERMISSION_PRIVATE | ppapi::PERMISSION_BYPASS_USER_GESTURE |
+ ppapi::PERMISSION_FLASH;
+
+namespace {
+
+// File name of the Pepper Flash component manifest on different platforms.
+const char kPepperFlashManifestName[] = "Flapper";
+
+// Name of the Pepper Flash OS in the component manifest.
+const char kPepperFlashOperatingSystem[] =
+#if defined(OS_MACOSX)
+ "mac";
+#elif defined(OS_WIN)
+ "win";
+#elif defined(OS_CHROMEOS)
+ "chromeos";
+#else // OS_LINUX,
+ "linux";
+#endif
+
+// Name of the Pepper Flash architecture in the component manifest.
+const char kPepperFlashArch[] =
+#if defined(ARCH_CPU_X86)
+ "ia32";
+#elif defined(ARCH_CPU_X86_64)
+ "x64";
+#elif defined(ARCH_CPU_ARMEL)
+ "arm";
+#else
+ "???";
+#endif
+
+// Returns true if the Pepper |interface_name| is implemented by this browser.
+// It does not check if the interface is proxied.
+bool SupportsPepperInterface(const char* interface_name) {
+ if (IsSupportedPepperInterface(interface_name))
+ return true;
+ // The PDF interface is invisible to SupportsInterface() on the browser
+ // process because it is provided using PpapiInterfaceFactoryManager. We need
+ // to check for that as well.
+ // TODO(cpu): make this more sane.
+ return (strcmp(interface_name, PPB_PDF_INTERFACE) == 0);
+}
+
+// Returns true if this browser implements one of the interfaces given in
+// |interface_string|, which is a '|'-separated string of interface names.
+bool CheckPepperFlashInterfaceString(const std::string& interface_string) {
+ for (const std::string& name : base::SplitString(
+ interface_string, "|",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (SupportsPepperInterface(name.c_str()))
+ return true;
+ }
+ return false;
+}
+
+// Returns true if this browser implements all the interfaces that Flash
+// specifies in its component installer manifest.
+bool CheckPepperFlashInterfaces(const base::DictionaryValue& manifest) {
+ const base::ListValue* interface_list = NULL;
+
+ // We don't *require* an interface list, apparently.
+ if (!manifest.GetList("x-ppapi-required-interfaces", &interface_list))
+ return true;
+
+ for (size_t i = 0; i < interface_list->GetSize(); i++) {
+ std::string interface_string;
+ if (!interface_list->GetString(i, &interface_string))
+ return false;
+ if (!CheckPepperFlashInterfaceString(interface_string))
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool CheckPepperFlashManifest(const base::DictionaryValue& manifest,
+ base::Version* version_out) {
+ std::string name;
+ manifest.GetStringASCII("name", &name);
+ if (name != kPepperFlashManifestName)
+ return false;
+
+ std::string proposed_version;
+ manifest.GetStringASCII("version", &proposed_version);
+ base::Version version(proposed_version);
+ if (!version.IsValid())
+ return false;
+
+ if (!CheckPepperFlashInterfaces(manifest))
+ return false;
+
+ std::string os;
+ manifest.GetStringASCII("x-ppapi-os", &os);
+ if (os != kPepperFlashOperatingSystem)
+ return false;
+
+ std::string arch;
+ manifest.GetStringASCII("x-ppapi-arch", &arch);
+ if (arch != kPepperFlashArch) {
+#if defined(OS_MACOSX)
+ // On Mac OS X the arch is 'x64' for component updated Flash but 'mac' for
+ // system Flash, so accept both variations.
+ if (arch != kPepperFlashOperatingSystem)
+ return false;
+#else
+ return false;
+#endif
+ }
+
+ *version_out = version;
+ return true;
+}
+
+bool IsSystemFlashScriptDebuggerPresent() {
+#if defined(OS_WIN)
+ const wchar_t kFlashRegistryRoot[] =
+ L"SOFTWARE\\Macromedia\\FlashPlayerPepper";
+ const wchar_t kIsDebuggerValueName[] = L"isScriptDebugger";
+
+ base::win::RegKey path_key(HKEY_LOCAL_MACHINE, kFlashRegistryRoot, KEY_READ);
+ DWORD debug_value;
+ if (path_key.ReadValueDW(kIsDebuggerValueName, &debug_value) != ERROR_SUCCESS)
+ return false;
+
+ return (debug_value == 1);
+#else
+ // TODO(wfh): implement this on OS X and Linux. crbug.com/497996.
+ return false;
+#endif
+}
diff --git a/chromium/chrome/common/pepper_flash.h b/chromium/chrome/common/pepper_flash.h
new file mode 100644
index 00000000000..e404dac24ad
--- /dev/null
+++ b/chromium/chrome/common/pepper_flash.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PEPPER_FLASH_H_
+#define CHROME_COMMON_PEPPER_FLASH_H_
+
+#include <stdint.h>
+
+#include "base/values.h"
+#include "base/version.h"
+
+// Permission bits for Pepper Flash.
+extern const int32_t kPepperFlashPermissions;
+
+// Returns true if this browser is compatible with the given Pepper Flash
+// manifest, with the version specified in the manifest in |version_out|.
+bool CheckPepperFlashManifest(const base::DictionaryValue& manifest,
+ base::Version* version_out);
+
+// Returns true if the version of Flash installed on the system is the Flash
+// Script debugger.
+bool IsSystemFlashScriptDebuggerPresent();
+
+#endif // CHROME_COMMON_PEPPER_FLASH_H_
diff --git a/chromium/chrome/common/pepper_permission_util.cc b/chromium/chrome/common/pepper_permission_util.cc
new file mode 100644
index 00000000000..c448d4653f4
--- /dev/null
+++ b/chromium/chrome/common/pepper_permission_util.cc
@@ -0,0 +1,101 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/pepper_permission_util.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/hash/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+
+using extensions::Extension;
+using extensions::Manifest;
+using extensions::SharedModuleInfo;
+
+namespace {
+
+std::string HashHost(const std::string& host) {
+ const std::string id_hash = base::SHA1HashString(host);
+ DCHECK_EQ(id_hash.length(), base::kSHA1Length);
+ return base::HexEncode(id_hash.c_str(), id_hash.length());
+}
+
+bool HostIsInSet(const std::string& host, const std::set<std::string>& set) {
+ return set.count(host) > 0 || set.count(HashHost(host)) > 0;
+}
+
+} // namespace
+
+bool IsExtensionOrSharedModuleWhitelisted(
+ const GURL& url,
+ const extensions::ExtensionSet* extension_set,
+ const std::set<std::string>& whitelist) {
+ if (!url.is_valid() || !url.SchemeIs(extensions::kExtensionScheme))
+ return false;
+
+ const std::string host = url.host();
+ if (HostIsInSet(host, whitelist))
+ return true;
+
+ // Check the modules that are imported by this extension to see if any of them
+ // is whitelisted.
+ const Extension* extension = extension_set ? extension_set->GetByID(host)
+ : NULL;
+ if (!extension)
+ return false;
+
+ typedef std::vector<SharedModuleInfo::ImportInfo> ImportInfoVector;
+ const ImportInfoVector& imports = SharedModuleInfo::GetImports(extension);
+ for (auto it = imports.begin(); it != imports.end(); ++it) {
+ const Extension* imported_extension =
+ extension_set->GetByID(it->extension_id);
+ if (imported_extension &&
+ SharedModuleInfo::IsSharedModule(imported_extension) &&
+ HostIsInSet(it->extension_id, whitelist)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool IsHostAllowedByCommandLine(const GURL& url,
+ const extensions::ExtensionSet* extension_set,
+ const char* command_line_switch) {
+ if (!url.is_valid())
+ return false;
+
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+ const std::string allowed_list =
+ command_line.GetSwitchValueASCII(command_line_switch);
+ if (allowed_list.empty())
+ return false;
+
+ const std::string host = url.host();
+ if (allowed_list == "*") {
+ // For now, we only allow packaged and platform apps in this wildcard.
+ if (!extension_set || !url.SchemeIs(extensions::kExtensionScheme))
+ return false;
+
+ const Extension* extension = extension_set->GetByID(host);
+ return extension &&
+ (extension->GetType() == Manifest::TYPE_LEGACY_PACKAGED_APP ||
+ extension->GetType() == Manifest::TYPE_PLATFORM_APP);
+ }
+
+ base::StringTokenizer t(allowed_list, ",");
+ while (t.GetNext()) {
+ if (t.token() == host)
+ return true;
+ }
+
+ return false;
+}
diff --git a/chromium/chrome/common/pepper_permission_util.h b/chromium/chrome/common/pepper_permission_util.h
new file mode 100644
index 00000000000..b2793e80c35
--- /dev/null
+++ b/chromium/chrome/common/pepper_permission_util.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PEPPER_PERMISSION_UTIL_H_
+#define CHROME_COMMON_PEPPER_PERMISSION_UTIL_H_
+
+#include <set>
+#include <string>
+
+class GURL;
+
+namespace extensions {
+class ExtensionSet;
+}
+
+// Returns true if the extension (or an imported module if any) is whitelisted.
+// Module imports are at most one level deep (ie, a module that exports cannot
+// import another extension). The extension is identified by the host of |url|
+// (if it is a chrome-extension URL). |extension_set| is the list of installed
+// and enabled extensions for a given profile. |whitelist| is a set of
+// (possibly hashed) extension IDs to check against.
+bool IsExtensionOrSharedModuleWhitelisted(
+ const GURL& url,
+ const extensions::ExtensionSet* extension_set,
+ const std::set<std::string>& whitelist);
+
+// Checks whether the host of |url| is allowed by |command_line_switch|.
+//
+// If the value of |command_line_switch| is:
+// (1) '*': returns true for any packaged or platform apps;
+// (2) a list of host names separated by ',': returns true if |host| is in the
+// list. (NOTE: In this case, |url| doesn't have to belong to an extension.)
+bool IsHostAllowedByCommandLine(const GURL& url,
+ const extensions::ExtensionSet* extension_set,
+ const char* command_line_switch);
+#endif // CHROME_COMMON_PEPPER_PERMISSION_UTIL_H_
diff --git a/chromium/chrome/common/pepper_permission_util_unittest.cc b/chromium/chrome/common/pepper_permission_util_unittest.cc
new file mode 100644
index 00000000000..35f2c484b60
--- /dev/null
+++ b/chromium/chrome/common/pepper_permission_util_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/pepper_permission_util.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "components/crx_file/id_util.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/features/feature_channel.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+// Return an extension with |id| which imports a module with the given
+// |import_id|.
+scoped_refptr<const Extension> CreateExtensionImportingModule(
+ const std::string& import_id,
+ const std::string& id) {
+ std::unique_ptr<base::DictionaryValue> manifest =
+ DictionaryBuilder()
+ .Set("name", "Has Dependent Modules")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Set("import",
+ ListBuilder()
+ .Append(DictionaryBuilder().Set("id", import_id).Build())
+ .Build())
+ .Build();
+
+ return ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .AddFlags(Extension::FROM_WEBSTORE)
+ .SetID(id)
+ .Build();
+}
+
+} // namespace
+
+TEST(PepperPermissionUtilTest, ExtensionWhitelisting) {
+ ScopedCurrentChannel current_channel(version_info::Channel::UNKNOWN);
+ ExtensionSet extensions;
+ std::string whitelisted_id =
+ crx_file::id_util::GenerateId("whitelisted_extension");
+ std::unique_ptr<base::DictionaryValue> manifest =
+ DictionaryBuilder()
+ .Set("name", "Whitelisted Extension")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Build();
+ scoped_refptr<const Extension> ext = ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .SetID(whitelisted_id)
+ .Build();
+ extensions.Insert(ext);
+ std::set<std::string> whitelist;
+ std::string url = std::string("chrome-extension://") + whitelisted_id +
+ std::string("/manifest.nmf");
+ std::string bad_scheme_url =
+ std::string("http://") + whitelisted_id + std::string("/manifest.nmf");
+ std::string bad_host_url = std::string("chrome-extension://") +
+ crx_file::id_util::GenerateId("bad_host");
+ std::string("/manifest.nmf");
+
+ EXPECT_FALSE(
+ IsExtensionOrSharedModuleWhitelisted(GURL(url), &extensions, whitelist));
+ whitelist.insert(whitelisted_id);
+ EXPECT_TRUE(
+ IsExtensionOrSharedModuleWhitelisted(GURL(url), &extensions, whitelist));
+ EXPECT_FALSE(IsExtensionOrSharedModuleWhitelisted(
+ GURL(bad_scheme_url), &extensions, whitelist));
+ EXPECT_FALSE(IsExtensionOrSharedModuleWhitelisted(
+ GURL(bad_host_url), &extensions, whitelist));
+}
+
+TEST(PepperPermissionUtilTest, SharedModuleWhitelisting) {
+ ScopedCurrentChannel current_channel(version_info::Channel::UNKNOWN);
+ ExtensionSet extensions;
+ std::string whitelisted_id = crx_file::id_util::GenerateId("extension_id");
+ std::string bad_id = crx_file::id_util::GenerateId("bad_id");
+
+ std::unique_ptr<base::DictionaryValue> shared_module_manifest =
+ DictionaryBuilder()
+ .Set("name", "Whitelisted Shared Module")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Set("export",
+ DictionaryBuilder()
+ .Set("resources", ListBuilder().Append("*").Build())
+ // Add the extension to the whitelist. This
+ // restricts import to |whitelisted_id| only.
+ .Set("whitelist",
+ ListBuilder().Append(whitelisted_id).Build())
+ .Build())
+ .Build();
+ scoped_refptr<const Extension> shared_module =
+ ExtensionBuilder().SetManifest(std::move(shared_module_manifest)).Build();
+
+ scoped_refptr<const Extension> ext =
+ CreateExtensionImportingModule(shared_module->id(), whitelisted_id);
+ std::string extension_url =
+ std::string("chrome-extension://") + ext->id() + std::string("/foo.html");
+
+ std::set<std::string> whitelist;
+ // Important: whitelist *only* the shared module.
+ whitelist.insert(shared_module->id());
+
+ extensions.Insert(ext);
+ // This should fail because shared_module is not in the set of extensions.
+ EXPECT_FALSE(IsExtensionOrSharedModuleWhitelisted(
+ GURL(extension_url), &extensions, whitelist));
+ extensions.Insert(shared_module);
+ EXPECT_TRUE(IsExtensionOrSharedModuleWhitelisted(
+ GURL(extension_url), &extensions, whitelist));
+ scoped_refptr<const Extension> not_in_sm_whitelist =
+ CreateExtensionImportingModule(shared_module->id(), bad_id);
+ std::string not_in_sm_whitelist_url = std::string("chrome-extension://") +
+ not_in_sm_whitelist->id() +
+ std::string("/foo.html");
+
+ extensions.Insert(not_in_sm_whitelist);
+ // This should succeed, even though |not_in_sm_whitelist| is not whitelisted
+ // to use shared_module, because the pepper permission utility does not care
+ // about that whitelist. It is possible to install against the whitelist as
+ // an unpacked extension.
+ EXPECT_TRUE(IsExtensionOrSharedModuleWhitelisted(
+ GURL(not_in_sm_whitelist_url), &extensions, whitelist));
+
+ // Note that the whitelist should be empty after this call, so tests checking
+ // for failure to import will fail because of this.
+ whitelist.erase(shared_module->id());
+ EXPECT_FALSE(IsExtensionOrSharedModuleWhitelisted(
+ GURL(extension_url), &extensions, whitelist));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/common/performance_manager/OWNERS b/chromium/chrome/common/performance_manager/OWNERS
new file mode 100644
index 00000000000..0a5168e1964
--- /dev/null
+++ b/chromium/chrome/common/performance_manager/OWNERS
@@ -0,0 +1 @@
+file://chrome/browser/performance_manager/OWNERS
diff --git a/chromium/chrome/common/performance_manager/mojom/OWNERS b/chromium/chrome/common/performance_manager/mojom/OWNERS
new file mode 100644
index 00000000000..08850f42120
--- /dev/null
+++ b/chromium/chrome/common/performance_manager/mojom/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chromium/chrome/common/plugin_utils.cc b/chromium/chrome/common/plugin_utils.cc
new file mode 100644
index 00000000000..9c143751282
--- /dev/null
+++ b/chromium/chrome/common/plugin_utils.cc
@@ -0,0 +1,23 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/plugin_utils.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/webplugininfo.h"
+
+bool ShouldUseJavaScriptSettingForPlugin(const content::WebPluginInfo& plugin) {
+ if (plugin.name == base::ASCIIToUTF16(content::kFlashPluginName))
+ return false;
+
+ // Since all the UI surfaces for Plugin content settings display "Flash",
+ // treat all other plugins as JavaScript. These include all of:
+ // - Internally registered plugins such as:
+ // - NaCl
+ // - Widevine
+ // - PDF
+ // - Custom plugins loaded from the command line
+ return true;
+}
diff --git a/chromium/chrome/common/plugin_utils.h b/chromium/chrome/common/plugin_utils.h
new file mode 100644
index 00000000000..4cc9f7a3048
--- /dev/null
+++ b/chromium/chrome/common/plugin_utils.h
@@ -0,0 +1,15 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PLUGIN_UTILS_H_
+#define CHROME_COMMON_PLUGIN_UTILS_H_
+
+namespace content {
+struct WebPluginInfo;
+}
+
+// Returns true if |plugin| should use the JavaScript Content Settings.
+bool ShouldUseJavaScriptSettingForPlugin(const content::WebPluginInfo& plugin);
+
+#endif // CHROME_COMMON_PLUGIN_UTILS_H_
diff --git a/chromium/chrome/common/ppapi_utils.cc b/chromium/chrome/common/ppapi_utils.cc
new file mode 100644
index 00000000000..acfb297b9fd
--- /dev/null
+++ b/chromium/chrome/common/ppapi_utils.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "chrome/common/ppapi_utils.h"
+
+#include <cstring>
+
+#include "build/build_config.h"
+#include "ppapi/c/dev/ppb_audio_input_dev.h"
+#include "ppapi/c/dev/ppb_audio_output_dev.h"
+#include "ppapi/c/dev/ppb_buffer_dev.h"
+#include "ppapi/c/dev/ppb_char_set_dev.h"
+#include "ppapi/c/dev/ppb_crypto_dev.h"
+#include "ppapi/c/dev/ppb_cursor_control_dev.h"
+#include "ppapi/c/dev/ppb_device_ref_dev.h"
+#include "ppapi/c/dev/ppb_file_chooser_dev.h"
+#include "ppapi/c/dev/ppb_gles_chromium_texture_mapping_dev.h"
+#include "ppapi/c/dev/ppb_ime_input_event_dev.h"
+#include "ppapi/c/dev/ppb_memory_dev.h"
+#include "ppapi/c/dev/ppb_opengles2ext_dev.h"
+#include "ppapi/c/dev/ppb_printing_dev.h"
+#include "ppapi/c/dev/ppb_text_input_dev.h"
+#include "ppapi/c/dev/ppb_trace_event_dev.h"
+#include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/c/dev/ppb_url_util_dev.h"
+#include "ppapi/c/dev/ppb_var_deprecated.h"
+#include "ppapi/c/dev/ppb_video_capture_dev.h"
+#include "ppapi/c/dev/ppb_video_decoder_dev.h"
+#include "ppapi/c/dev/ppb_view_dev.h"
+#include "ppapi/c/ppb_audio.h"
+#include "ppapi/c/ppb_audio_buffer.h"
+#include "ppapi/c/ppb_audio_config.h"
+#include "ppapi/c/ppb_audio_encoder.h"
+#include "ppapi/c/ppb_console.h"
+#include "ppapi/c/ppb_core.h"
+#include "ppapi/c/ppb_file_io.h"
+#include "ppapi/c/ppb_file_ref.h"
+#include "ppapi/c/ppb_file_system.h"
+#include "ppapi/c/ppb_fullscreen.h"
+#include "ppapi/c/ppb_gamepad.h"
+#include "ppapi/c/ppb_graphics_2d.h"
+#include "ppapi/c/ppb_graphics_3d.h"
+#include "ppapi/c/ppb_host_resolver.h"
+#include "ppapi/c/ppb_image_data.h"
+#include "ppapi/c/ppb_input_event.h"
+#include "ppapi/c/ppb_instance.h"
+#include "ppapi/c/ppb_media_stream_audio_track.h"
+#include "ppapi/c/ppb_media_stream_video_track.h"
+#include "ppapi/c/ppb_messaging.h"
+#include "ppapi/c/ppb_mouse_cursor.h"
+#include "ppapi/c/ppb_mouse_lock.h"
+#include "ppapi/c/ppb_net_address.h"
+#include "ppapi/c/ppb_network_list.h"
+#include "ppapi/c/ppb_network_monitor.h"
+#include "ppapi/c/ppb_network_proxy.h"
+#include "ppapi/c/ppb_opengles2.h"
+#include "ppapi/c/ppb_tcp_socket.h"
+#include "ppapi/c/ppb_text_input_controller.h"
+#include "ppapi/c/ppb_udp_socket.h"
+#include "ppapi/c/ppb_url_loader.h"
+#include "ppapi/c/ppb_url_request_info.h"
+#include "ppapi/c/ppb_url_response_info.h"
+#include "ppapi/c/ppb_var.h"
+#include "ppapi/c/ppb_var_array.h"
+#include "ppapi/c/ppb_var_array_buffer.h"
+#include "ppapi/c/ppb_var_dictionary.h"
+#include "ppapi/c/ppb_video_decoder.h"
+#include "ppapi/c/ppb_video_encoder.h"
+#include "ppapi/c/ppb_video_frame.h"
+#include "ppapi/c/ppb_view.h"
+#include "ppapi/c/ppb_vpn_provider.h"
+#include "ppapi/c/ppb_websocket.h"
+#include "ppapi/c/private/ppb_camera_capabilities_private.h"
+#include "ppapi/c/private/ppb_camera_device_private.h"
+#include "ppapi/c/private/ppb_ext_crx_file_system_private.h"
+#include "ppapi/c/private/ppb_file_io_private.h"
+#include "ppapi/c/private/ppb_file_ref_private.h"
+#include "ppapi/c/private/ppb_find_private.h"
+#include "ppapi/c/private/ppb_flash.h"
+#include "ppapi/c/private/ppb_flash_clipboard.h"
+#include "ppapi/c/private/ppb_flash_drm.h"
+#include "ppapi/c/private/ppb_flash_file.h"
+#include "ppapi/c/private/ppb_flash_font_file.h"
+#include "ppapi/c/private/ppb_flash_fullscreen.h"
+#include "ppapi/c/private/ppb_flash_menu.h"
+#include "ppapi/c/private/ppb_flash_message_loop.h"
+#include "ppapi/c/private/ppb_flash_print.h"
+#include "ppapi/c/private/ppb_host_resolver_private.h"
+#include "ppapi/c/private/ppb_isolated_file_system_private.h"
+#include "ppapi/c/private/ppb_pdf.h"
+#include "ppapi/c/private/ppb_proxy_private.h"
+#include "ppapi/c/private/ppb_tcp_server_socket_private.h"
+#include "ppapi/c/private/ppb_tcp_socket_private.h"
+#include "ppapi/c/private/ppb_testing_private.h"
+#include "ppapi/c/private/ppb_udp_socket_private.h"
+#include "ppapi/c/private/ppb_uma_private.h"
+#include "ppapi/c/private/ppb_x509_certificate_private.h"
+#include "ppapi/c/trusted/ppb_broker_trusted.h"
+#include "ppapi/c/trusted/ppb_browser_font_trusted.h"
+#include "ppapi/c/trusted/ppb_char_set_trusted.h"
+#include "ppapi/c/trusted/ppb_file_chooser_trusted.h"
+#include "ppapi/c/trusted/ppb_url_loader_trusted.h"
+#include "ppapi/thunk/thunk.h"
+
+bool IsSupportedPepperInterface(const char* name) {
+// TODO(brettw) put these in a hash map for better performance.
+#define PROXIED_IFACE(iface_str, iface_struct) \
+ if (strcmp(name, iface_str) == 0) \
+ return true;
+
+#include "ppapi/thunk/interfaces_ppb_private.h"
+#include "ppapi/thunk/interfaces_ppb_private_flash.h"
+#include "ppapi/thunk/interfaces_ppb_private_no_permissions.h"
+#include "ppapi/thunk/interfaces_ppb_private_pdf.h"
+#include "ppapi/thunk/interfaces_ppb_public_dev.h"
+#include "ppapi/thunk/interfaces_ppb_public_dev_channel.h"
+#include "ppapi/thunk/interfaces_ppb_public_socket.h"
+#include "ppapi/thunk/interfaces_ppb_public_stable.h"
+
+#undef PROXIED_IFACE
+
+#define LEGACY_IFACE(iface_str, dummy) \
+ if (strcmp(name, iface_str) == 0) \
+ return true;
+
+#include "ppapi/thunk/interfaces_legacy.h"
+
+#undef LEGACY_IFACE
+ return false;
+}
diff --git a/chromium/chrome/common/ppapi_utils.h b/chromium/chrome/common/ppapi_utils.h
new file mode 100644
index 00000000000..7e286273489
--- /dev/null
+++ b/chromium/chrome/common/ppapi_utils.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PPAPI_UTILS_H_
+#define CHROME_COMMON_PPAPI_UTILS_H_
+
+// Returns true if the interface name passed in is supported by the
+// browser.
+bool IsSupportedPepperInterface(const char* name);
+
+#endif // CHROME_COMMON_PPAPI_UTILS_H_
diff --git a/chromium/chrome/common/pref_font_script_names-inl.h b/chromium/chrome/common/pref_font_script_names-inl.h
new file mode 100644
index 00000000000..8c5532f30b5
--- /dev/null
+++ b/chromium/chrome/common/pref_font_script_names-inl.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included file, hence no include guard.
+// This file defines all font scripts in a manner that can be re-used to
+// generate the various data tables required in RO memory, and it can not
+// contain any header guards. Callers need to
+// #define EXPAND_SCRIPT_FONTS to their needs and call ALL_FONT_SCRIPTS.
+
+#define ALL_FONT_SCRIPTS(x) \
+ EXPAND_SCRIPT_FONT(x, "Afak") \
+ EXPAND_SCRIPT_FONT(x, "Arab") \
+ EXPAND_SCRIPT_FONT(x, "Armi") \
+ EXPAND_SCRIPT_FONT(x, "Armn") \
+ EXPAND_SCRIPT_FONT(x, "Avst") \
+ EXPAND_SCRIPT_FONT(x, "Bali") \
+ EXPAND_SCRIPT_FONT(x, "Bamu") \
+ EXPAND_SCRIPT_FONT(x, "Bass") \
+ EXPAND_SCRIPT_FONT(x, "Batk") \
+ EXPAND_SCRIPT_FONT(x, "Beng") \
+ EXPAND_SCRIPT_FONT(x, "Blis") \
+ EXPAND_SCRIPT_FONT(x, "Bopo") \
+ EXPAND_SCRIPT_FONT(x, "Brah") \
+ EXPAND_SCRIPT_FONT(x, "Brai") \
+ EXPAND_SCRIPT_FONT(x, "Bugi") \
+ EXPAND_SCRIPT_FONT(x, "Buhd") \
+ EXPAND_SCRIPT_FONT(x, "Cakm") \
+ EXPAND_SCRIPT_FONT(x, "Cans") \
+ EXPAND_SCRIPT_FONT(x, "Cari") \
+ EXPAND_SCRIPT_FONT(x, "Cham") \
+ EXPAND_SCRIPT_FONT(x, "Cher") \
+ EXPAND_SCRIPT_FONT(x, "Cirt") \
+ EXPAND_SCRIPT_FONT(x, "Copt") \
+ EXPAND_SCRIPT_FONT(x, "Cprt") \
+ EXPAND_SCRIPT_FONT(x, "Cyrl") \
+ EXPAND_SCRIPT_FONT(x, "Cyrs") \
+ EXPAND_SCRIPT_FONT(x, "Deva") \
+ EXPAND_SCRIPT_FONT(x, "Dsrt") \
+ EXPAND_SCRIPT_FONT(x, "Dupl") \
+ EXPAND_SCRIPT_FONT(x, "Egyd") \
+ EXPAND_SCRIPT_FONT(x, "Egyh") \
+ EXPAND_SCRIPT_FONT(x, "Egyp") \
+ EXPAND_SCRIPT_FONT(x, "Elba") \
+ EXPAND_SCRIPT_FONT(x, "Ethi") \
+ EXPAND_SCRIPT_FONT(x, "Geor") \
+ EXPAND_SCRIPT_FONT(x, "Geok") \
+ EXPAND_SCRIPT_FONT(x, "Glag") \
+ EXPAND_SCRIPT_FONT(x, "Goth") \
+ EXPAND_SCRIPT_FONT(x, "Gran") \
+ EXPAND_SCRIPT_FONT(x, "Grek") \
+ EXPAND_SCRIPT_FONT(x, "Gujr") \
+ EXPAND_SCRIPT_FONT(x, "Guru") \
+ EXPAND_SCRIPT_FONT(x, "Hang") \
+ EXPAND_SCRIPT_FONT(x, "Hani") \
+ EXPAND_SCRIPT_FONT(x, "Hano") \
+ EXPAND_SCRIPT_FONT(x, "Hans") \
+ EXPAND_SCRIPT_FONT(x, "Hant") \
+ EXPAND_SCRIPT_FONT(x, "Hebr") \
+ EXPAND_SCRIPT_FONT(x, "Hluw") \
+ EXPAND_SCRIPT_FONT(x, "Hmng") \
+ EXPAND_SCRIPT_FONT(x, "Hung") \
+ EXPAND_SCRIPT_FONT(x, "Inds") \
+ EXPAND_SCRIPT_FONT(x, "Ital") \
+ EXPAND_SCRIPT_FONT(x, "Java") \
+ EXPAND_SCRIPT_FONT(x, "Jpan") \
+ EXPAND_SCRIPT_FONT(x, "Jurc") \
+ EXPAND_SCRIPT_FONT(x, "Kali") \
+ EXPAND_SCRIPT_FONT(x, "Khar") \
+ EXPAND_SCRIPT_FONT(x, "Khmr") \
+ EXPAND_SCRIPT_FONT(x, "Khoj") \
+ EXPAND_SCRIPT_FONT(x, "Knda") \
+ EXPAND_SCRIPT_FONT(x, "Kpel") \
+ EXPAND_SCRIPT_FONT(x, "Kthi") \
+ EXPAND_SCRIPT_FONT(x, "Lana") \
+ EXPAND_SCRIPT_FONT(x, "Laoo") \
+ EXPAND_SCRIPT_FONT(x, "Latf") \
+ EXPAND_SCRIPT_FONT(x, "Latg") \
+ EXPAND_SCRIPT_FONT(x, "Latn") \
+ EXPAND_SCRIPT_FONT(x, "Lepc") \
+ EXPAND_SCRIPT_FONT(x, "Limb") \
+ EXPAND_SCRIPT_FONT(x, "Lina") \
+ EXPAND_SCRIPT_FONT(x, "Linb") \
+ EXPAND_SCRIPT_FONT(x, "Lisu") \
+ EXPAND_SCRIPT_FONT(x, "Loma") \
+ EXPAND_SCRIPT_FONT(x, "Lyci") \
+ EXPAND_SCRIPT_FONT(x, "Lydi") \
+ EXPAND_SCRIPT_FONT(x, "Mand") \
+ EXPAND_SCRIPT_FONT(x, "Mani") \
+ EXPAND_SCRIPT_FONT(x, "Maya") \
+ EXPAND_SCRIPT_FONT(x, "Mend") \
+ EXPAND_SCRIPT_FONT(x, "Merc") \
+ EXPAND_SCRIPT_FONT(x, "Mero") \
+ EXPAND_SCRIPT_FONT(x, "Mlym") \
+ EXPAND_SCRIPT_FONT(x, "Moon") \
+ EXPAND_SCRIPT_FONT(x, "Mong") \
+ EXPAND_SCRIPT_FONT(x, "Mroo") \
+ EXPAND_SCRIPT_FONT(x, "Mtei") \
+ EXPAND_SCRIPT_FONT(x, "Mymr") \
+ EXPAND_SCRIPT_FONT(x, "Narb") \
+ EXPAND_SCRIPT_FONT(x, "Nbat") \
+ EXPAND_SCRIPT_FONT(x, "Nkgb") \
+ EXPAND_SCRIPT_FONT(x, "Nkoo") \
+ EXPAND_SCRIPT_FONT(x, "Nshu") \
+ EXPAND_SCRIPT_FONT(x, "Ogam") \
+ EXPAND_SCRIPT_FONT(x, "Olck") \
+ EXPAND_SCRIPT_FONT(x, "Orkh") \
+ EXPAND_SCRIPT_FONT(x, "Orya") \
+ EXPAND_SCRIPT_FONT(x, "Osma") \
+ EXPAND_SCRIPT_FONT(x, "Palm") \
+ EXPAND_SCRIPT_FONT(x, "Perm") \
+ EXPAND_SCRIPT_FONT(x, "Phag") \
+ EXPAND_SCRIPT_FONT(x, "Phli") \
+ EXPAND_SCRIPT_FONT(x, "Phlp") \
+ EXPAND_SCRIPT_FONT(x, "Phlv") \
+ EXPAND_SCRIPT_FONT(x, "Phnx") \
+ EXPAND_SCRIPT_FONT(x, "Plrd") \
+ EXPAND_SCRIPT_FONT(x, "Prti") \
+ EXPAND_SCRIPT_FONT(x, "Rjng") \
+ EXPAND_SCRIPT_FONT(x, "Roro") \
+ EXPAND_SCRIPT_FONT(x, "Runr") \
+ EXPAND_SCRIPT_FONT(x, "Samr") \
+ EXPAND_SCRIPT_FONT(x, "Sara") \
+ EXPAND_SCRIPT_FONT(x, "Sarb") \
+ EXPAND_SCRIPT_FONT(x, "Saur") \
+ EXPAND_SCRIPT_FONT(x, "Sgnw") \
+ EXPAND_SCRIPT_FONT(x, "Shaw") \
+ EXPAND_SCRIPT_FONT(x, "Shrd") \
+ EXPAND_SCRIPT_FONT(x, "Sind") \
+ EXPAND_SCRIPT_FONT(x, "Sinh") \
+ EXPAND_SCRIPT_FONT(x, "Sora") \
+ EXPAND_SCRIPT_FONT(x, "Sund") \
+ EXPAND_SCRIPT_FONT(x, "Sylo") \
+ EXPAND_SCRIPT_FONT(x, "Syrc") \
+ EXPAND_SCRIPT_FONT(x, "Syre") \
+ EXPAND_SCRIPT_FONT(x, "Syrj") \
+ EXPAND_SCRIPT_FONT(x, "Syrn") \
+ EXPAND_SCRIPT_FONT(x, "Tagb") \
+ EXPAND_SCRIPT_FONT(x, "Takr") \
+ EXPAND_SCRIPT_FONT(x, "Tale") \
+ EXPAND_SCRIPT_FONT(x, "Talu") \
+ EXPAND_SCRIPT_FONT(x, "Taml") \
+ EXPAND_SCRIPT_FONT(x, "Tang") \
+ EXPAND_SCRIPT_FONT(x, "Tavt") \
+ EXPAND_SCRIPT_FONT(x, "Telu") \
+ EXPAND_SCRIPT_FONT(x, "Teng") \
+ EXPAND_SCRIPT_FONT(x, "Tfng") \
+ EXPAND_SCRIPT_FONT(x, "Tglg") \
+ EXPAND_SCRIPT_FONT(x, "Thaa") \
+ EXPAND_SCRIPT_FONT(x, "Thai") \
+ EXPAND_SCRIPT_FONT(x, "Tibt") \
+ EXPAND_SCRIPT_FONT(x, "Tirh") \
+ EXPAND_SCRIPT_FONT(x, "Ugar") \
+ EXPAND_SCRIPT_FONT(x, "Vaii") \
+ EXPAND_SCRIPT_FONT(x, "Visp") \
+ EXPAND_SCRIPT_FONT(x, "Wara") \
+ EXPAND_SCRIPT_FONT(x, "Wole") \
+ EXPAND_SCRIPT_FONT(x, "Xpeo") \
+ EXPAND_SCRIPT_FONT(x, "Xsux") \
+ EXPAND_SCRIPT_FONT(x, "Yiii") \
+ EXPAND_SCRIPT_FONT(x, "Zmth") \
+ EXPAND_SCRIPT_FONT(x, "Zsym") \
+ EXPAND_SCRIPT_FONT(x, "Zyyy")
diff --git a/chromium/chrome/common/pref_font_webkit_names.h b/chromium/chrome/common/pref_font_webkit_names.h
new file mode 100644
index 00000000000..a9827622caa
--- /dev/null
+++ b/chromium/chrome/common/pref_font_webkit_names.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PREF_FONT_WEBKIT_NAMES_H__
+#define CHROME_COMMON_PREF_FONT_WEBKIT_NAMES_H__
+
+// This file defines the font pref names that are used in conjunction with
+// pref_font_script_names-inl.h.
+
+#define WEBKIT_WEBPREFS_FONTS_CURSIVE "webkit.webprefs.fonts.cursive"
+#define WEBKIT_WEBPREFS_FONTS_FANTASY "webkit.webprefs.fonts.fantasy"
+#define WEBKIT_WEBPREFS_FONTS_FIXED "webkit.webprefs.fonts.fixed"
+#define WEBKIT_WEBPREFS_FONTS_PICTOGRAPH "webkit.webprefs.fonts.pictograph"
+#define WEBKIT_WEBPREFS_FONTS_SANSERIF "webkit.webprefs.fonts.sansserif"
+#define WEBKIT_WEBPREFS_FONTS_SERIF "webkit.webprefs.fonts.serif"
+#define WEBKIT_WEBPREFS_FONTS_STANDARD "webkit.webprefs.fonts.standard"
+
+#endif // CHROME_COMMON_PREF_FONT_WEBKIT_NAMES_H__
diff --git a/chromium/chrome/common/pref_names.cc b/chromium/chrome/common/pref_names.cc
index 04eb256d186..86cfea5d7cd 100644
--- a/chromium/chrome/common/pref_names.cc
+++ b/chromium/chrome/common/pref_names.cc
@@ -4,12 +4,1150 @@
#include "chrome/common/pref_names.h"
+#include "base/stl_util.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/pref_font_webkit_names.h"
+#include "extensions/buildflags/buildflags.h"
+#include "media/media_buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+
namespace prefs {
-const char kAcceptLanguages[] = "intl.accept_languages";
-// Integer that holds the value of the next persistent notification ID to be
-// used.
-const char kNotificationNextPersistentId[] = "persistent_notifications.next_id";
+// *************** PROFILE PREFS ***************
+// These are attached to the user profile
+
+// A bool pref that indicates whether interventions for abusive experiences
+// should be enforced.
+const char kAbusiveExperienceInterventionEnforce[] =
+ "abusive_experience_intervention_enforce";
+
+// A bool pref that keeps whether the child status for this profile was already
+// successfully checked via ChildAccountService.
+const char kChildAccountStatusKnown[] = "child_account_status_known";
+
+// A string property indicating whether default apps should be installed
+// in this profile. Use the value "install" to enable defaults apps, or
+// "noinstall" to disable them. This property is usually set in the
+// master_preferences and copied into the profile preferences on first run.
+// Defaults apps are installed only when creating a new profile.
+const char kDefaultApps[] = "default_apps";
+
+// Disable SafeBrowsing checks for files coming from trusted URLs when false.
+const char kSafeBrowsingForTrustedSourcesEnabled[] =
+ "safebrowsing_for_trusted_sources_enabled";
+
+// Disables screenshot accelerators and extension APIs.
+// This setting resides both in profile prefs and local state. Accelerator
+// handling code reads local state, while extension APIs use profile pref.
+const char kDisableScreenshots[] = "disable_screenshots";
+
+// Prevents certain types of downloads based on integer value, which corresponds
+// to DownloadPrefs::DownloadRestriction.
+// 0 - No special restrictions (default)
+// 1 - Block dangerous downloads
+// 2 - Block potentially dangerous downloads
+// 3 - Block all downloads
+// 4 - Block malicious downloads
+const char kDownloadRestrictions[] = "download_restrictions";
+
+// If set to true profiles are created in ephemeral mode and do not store their
+// data in the profile folder on disk but only in memory.
+const char kForceEphemeralProfiles[] = "profile.ephemeral_mode";
+
+// A boolean specifying whether the New Tab page is the home page or not.
+const char kHomePageIsNewTabPage[] = "homepage_is_newtabpage";
+
+// This is the URL of the page to load when opening new tabs.
+const char kHomePage[] = "homepage";
+
+// Stores information about the important sites dialog, including the time and
+// frequency it has been ignored.
+const char kImportantSitesDialogHistory[] = "important_sites_dialog";
+
+#if defined(OS_WIN)
+// This is a timestamp of the last time this profile was reset by a third party
+// tool. On Windows, a third party tool may set a registry value that will be
+// compared to this value and if different will result in a profile reset
+// prompt. See triggered_profile_resetter.h for more information.
+const char kLastProfileResetTimestamp[] = "profile.last_reset_timestamp";
+
+// A boolean indicating if settings should be reset for this profile once a
+// run of the Chrome Cleanup Tool has completed.
+const char kChromeCleanerResetPending[] = "chrome_cleaner.reset_pending";
+#endif
+
+// The URL to open the new tab page to. Only set by Group Policy.
+const char kNewTabPageLocationOverride[] = "newtab_page_location_override";
+
+// An integer that keeps track of the profile icon version. This allows us to
+// determine the state of the profile icon for icon format changes.
+const char kProfileIconVersion[] = "profile.icon_version";
+
+// Used to determine if the last session exited cleanly. Set to false when
+// first opened, and to true when closing. On startup if the value is false,
+// it means the profile didn't exit cleanly.
+// DEPRECATED: this is replaced by kSessionExitType and exists for backwards
+// compatibility.
+const char kSessionExitedCleanly[] = "profile.exited_cleanly";
+
+// A string pref whose values is one of the values defined by
+// |ProfileImpl::kPrefExitTypeXXX|. Set to |kPrefExitTypeCrashed| on startup and
+// one of |kPrefExitTypeNormal| or |kPrefExitTypeSessionEnded| during
+// shutdown. Used to determine the exit type the last time the profile was open.
+const char kSessionExitType[] = "profile.exit_type";
+
+// Stores the total amount of observed active session time for the user while
+// in-product help is active. Observed time is active session time in seconds.
+const char kObservedSessionTime[] = "profile.observed_session_time";
+
+// Stores counts and timestamps of SSL certificate errors that have occurred.
+// When the same error recurs within some period of time, a message is added to
+// the SSL interstitial.
+const char kRecurrentSSLInterstitial[] = "profile.ssl_recurrent_interstitial";
+
+// The last time that the site engagement service recorded an engagement event
+// for this profile for any URL. Recorded only during shutdown. Used to prevent
+// the service from decaying engagement when a user does not use Chrome at all
+// for an extended period of time.
+const char kSiteEngagementLastUpdateTime[] = "profile.last_engagement_time";
+
+// An integer pref. Holds one of several values:
+// 0: unused, previously indicated to open the homepage on startup
+// 1: restore the last session.
+// 2: this was used to indicate a specific session should be restored. It is
+// no longer used, but saved to avoid conflict with old preferences.
+// 3: unused, previously indicated the user wants to restore a saved session.
+// 4: restore the URLs defined in kURLsToRestoreOnStartup.
+// 5: open the New Tab Page on startup.
+const char kRestoreOnStartup[] = "session.restore_on_startup";
+
+// The URLs to restore on startup or when the home button is pressed. The URLs
+// are only restored on startup if kRestoreOnStartup is 4.
+const char kURLsToRestoreOnStartup[] = "session.startup_urls";
+
+// Boolean that is true when user feedback to Google is allowed.
+const char kUserFeedbackAllowed[] = "feedback_allowed";
+
+// Stores the email address associated with the google account of the custodian
+// of the supervised user, set when the supervised user is created.
+const char kSupervisedUserCustodianEmail[] = "profile.managed.custodian_email";
+
+// Stores the display name associated with the google account of the custodian
+// of the supervised user, updated (if possible) each time the supervised user
+// starts a session.
+const char kSupervisedUserCustodianName[] = "profile.managed.custodian_name";
+
+// Stores the obfuscated gaia id associated with the google account of the
+// custodian of the supervised user, updated (if possible) each time the
+// supervised user starts a session.
+const char kSupervisedUserCustodianObfuscatedGaiaId[] =
+ "profile.managed.custodian_obfuscated_gaia_id";
+
+// Stores the URL of the profile image associated with the google account of the
+// custodian of the supervised user.
+const char kSupervisedUserCustodianProfileImageURL[] =
+ "profile.managed.custodian_profile_image_url";
+
+// Stores the URL of the profile associated with the google account of the
+// custodian of the supervised user.
+const char kSupervisedUserCustodianProfileURL[] =
+ "profile.managed.custodian_profile_url";
+
+// Maps host names to whether the host is manually allowed or blocked.
+const char kSupervisedUserManualHosts[] = "profile.managed.manual_hosts";
+
+// Maps URLs to whether the URL is manually allowed or blocked.
+const char kSupervisedUserManualURLs[] = "profile.managed.manual_urls";
+
+// Maps extension ids to the approved version of this extension for a
+// supervised user. Missing extensions are not approved.
+const char kSupervisedUserApprovedExtensions[] =
+ "profile.managed.approved_extensions";
+
+// Stores whether the SafeSites filter is enabled.
+const char kSupervisedUserSafeSites[] = "profile.managed.safe_sites";
+
+// Stores the email address associated with the google account of the secondary
+// custodian of the supervised user, set when the supervised user is created.
+const char kSupervisedUserSecondCustodianEmail[] =
+ "profile.managed.second_custodian_email";
+
+// Stores the display name associated with the google account of the secondary
+// custodian of the supervised user, updated (if possible) each time the
+// supervised user starts a session.
+const char kSupervisedUserSecondCustodianName[] =
+ "profile.managed.second_custodian_name";
+
+// Stores the obfuscated gaia id associated with the google account of the
+// secondary custodian of the supervised user, updated (if possible) each time
+// the supervised user starts a session.
+const char kSupervisedUserSecondCustodianObfuscatedGaiaId[] =
+ "profile.managed.second_custodian_obfuscated_gaia_id";
+
+// Stores the URL of the profile image associated with the google account of the
+// secondary custodian of the supervised user.
+const char kSupervisedUserSecondCustodianProfileImageURL[] =
+ "profile.managed.second_custodian_profile_image_url";
+
+// Stores the URL of the profile associated with the google account of the
+// secondary custodian of the supervised user.
+const char kSupervisedUserSecondCustodianProfileURL[] =
+ "profile.managed.second_custodian_profile_url";
+
+// Stores settings that can be modified both by a supervised user and their
+// manager. See SupervisedUserSharedSettingsService for a description of
+// the format.
+const char kSupervisedUserSharedSettings[] = "profile.managed.shared_settings";
+
+// A dictionary storing whitelists for a supervised user. The key is the CRX ID
+// of the whitelist, the value a dictionary containing whitelist properties
+// (currently the name).
+const char kSupervisedUserWhitelists[] = "profile.managed.whitelists";
+
+#if BUILDFLAG(ENABLE_RLZ)
+// Integer. RLZ ping delay in seconds.
+const char kRlzPingDelaySeconds[] = "rlz_ping_delay";
+#endif // BUILDFLAG(ENABLE_RLZ)
+
+#if defined(OS_CHROMEOS)
+// Locale preference of device' owner. ChromeOS device appears in this locale
+// after startup/wakeup/signout.
+const char kOwnerLocale[] = "intl.owner_locale";
+// Locale accepted by user. Non-syncable.
+// Used to determine whether we need to show Locale Change notification.
+const char kApplicationLocaleAccepted[] = "intl.app_locale_accepted";
+// Non-syncable item.
+// It is used in two distinct ways.
+// (1) Used for two-step initialization of locale in ChromeOS
+// because synchronization of kApplicationLocale is not instant.
+// (2) Used to detect locale change. Locale change is detected by
+// LocaleChangeGuard in case values of kApplicationLocaleBackup and
+// kApplicationLocale are both non-empty and differ.
+// Following is a table showing how state of those prefs may change upon
+// common real-life use cases:
+// AppLocale Backup Accepted
+// Initial login - A -
+// Sync B A -
+// Accept (B) B B B
+// -----------------------------------------------------------
+// Initial login - A -
+// No sync and second login A A -
+// Change options B B -
+// -----------------------------------------------------------
+// Initial login - A -
+// Sync A A -
+// Locale changed on login screen A C -
+// Accept (A) A A A
+// -----------------------------------------------------------
+// Initial login - A -
+// Sync B A -
+// Revert A A -
+const char kApplicationLocaleBackup[] = "intl.app_locale_backup";
+
+// List of locales the UI is allowed to be displayed in by policy. The list is
+// empty if no restriction is being enforced.
+const char kAllowedLanguages[] = "intl.allowed_languages";
+#endif
+
+// The default character encoding to assume for a web page in the
+// absence of MIME charset specification
+const char kDefaultCharset[] = "intl.charset_default";
+
+// If these change, the corresponding enums in the extension API
+// experimental.fontSettings.json must also change.
+const char* const kWebKitScriptsForFontFamilyMaps[] = {
+#define EXPAND_SCRIPT_FONT(x, script_name) script_name ,
+#include "chrome/common/pref_font_script_names-inl.h"
+ALL_FONT_SCRIPTS("unused param")
+#undef EXPAND_SCRIPT_FONT
+};
+
+const size_t kWebKitScriptsForFontFamilyMapsLength =
+ base::size(kWebKitScriptsForFontFamilyMaps);
+
+// Strings for WebKit font family preferences. If these change, the pref prefix
+// in pref_names_util.cc and the pref format in font_settings_api.cc must also
+// change.
+const char kWebKitStandardFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_STANDARD;
+const char kWebKitFixedFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_FIXED;
+const char kWebKitSerifFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_SERIF;
+const char kWebKitSansSerifFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_SANSERIF;
+const char kWebKitCursiveFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_CURSIVE;
+const char kWebKitFantasyFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_FANTASY;
+const char kWebKitPictographFontFamilyMap[] =
+ WEBKIT_WEBPREFS_FONTS_PICTOGRAPH;
+const char kWebKitStandardFontFamilyArabic[] =
+ "webkit.webprefs.fonts.standard.Arab";
+#if defined(OS_WIN)
+const char kWebKitFixedFontFamilyArabic[] =
+ "webkit.webprefs.fonts.fixed.Arab";
+#endif
+const char kWebKitSerifFontFamilyArabic[] =
+ "webkit.webprefs.fonts.serif.Arab";
+const char kWebKitSansSerifFontFamilyArabic[] =
+ "webkit.webprefs.fonts.sansserif.Arab";
+#if defined(OS_WIN)
+const char kWebKitStandardFontFamilyCyrillic[] =
+ "webkit.webprefs.fonts.standard.Cyrl";
+const char kWebKitFixedFontFamilyCyrillic[] =
+ "webkit.webprefs.fonts.fixed.Cyrl";
+const char kWebKitSerifFontFamilyCyrillic[] =
+ "webkit.webprefs.fonts.serif.Cyrl";
+const char kWebKitSansSerifFontFamilyCyrillic[] =
+ "webkit.webprefs.fonts.sansserif.Cyrl";
+const char kWebKitStandardFontFamilyGreek[] =
+ "webkit.webprefs.fonts.standard.Grek";
+const char kWebKitFixedFontFamilyGreek[] =
+ "webkit.webprefs.fonts.fixed.Grek";
+const char kWebKitSerifFontFamilyGreek[] =
+ "webkit.webprefs.fonts.serif.Grek";
+const char kWebKitSansSerifFontFamilyGreek[] =
+ "webkit.webprefs.fonts.sansserif.Grek";
+#endif
+const char kWebKitStandardFontFamilyJapanese[] =
+ "webkit.webprefs.fonts.standard.Jpan";
+const char kWebKitFixedFontFamilyJapanese[] =
+ "webkit.webprefs.fonts.fixed.Jpan";
+const char kWebKitSerifFontFamilyJapanese[] =
+ "webkit.webprefs.fonts.serif.Jpan";
+const char kWebKitSansSerifFontFamilyJapanese[] =
+ "webkit.webprefs.fonts.sansserif.Jpan";
+const char kWebKitStandardFontFamilyKorean[] =
+ "webkit.webprefs.fonts.standard.Hang";
+const char kWebKitFixedFontFamilyKorean[] =
+ "webkit.webprefs.fonts.fixed.Hang";
+const char kWebKitSerifFontFamilyKorean[] =
+ "webkit.webprefs.fonts.serif.Hang";
+const char kWebKitSansSerifFontFamilyKorean[] =
+ "webkit.webprefs.fonts.sansserif.Hang";
+#if defined(OS_WIN)
+const char kWebKitCursiveFontFamilyKorean[] =
+ "webkit.webprefs.fonts.cursive.Hang";
+#endif
+const char kWebKitStandardFontFamilySimplifiedHan[] =
+ "webkit.webprefs.fonts.standard.Hans";
+const char kWebKitFixedFontFamilySimplifiedHan[] =
+ "webkit.webprefs.fonts.fixed.Hans";
+const char kWebKitSerifFontFamilySimplifiedHan[] =
+ "webkit.webprefs.fonts.serif.Hans";
+const char kWebKitSansSerifFontFamilySimplifiedHan[] =
+ "webkit.webprefs.fonts.sansserif.Hans";
+const char kWebKitStandardFontFamilyTraditionalHan[] =
+ "webkit.webprefs.fonts.standard.Hant";
+const char kWebKitFixedFontFamilyTraditionalHan[] =
+ "webkit.webprefs.fonts.fixed.Hant";
+const char kWebKitSerifFontFamilyTraditionalHan[] =
+ "webkit.webprefs.fonts.serif.Hant";
+const char kWebKitSansSerifFontFamilyTraditionalHan[] =
+ "webkit.webprefs.fonts.sansserif.Hant";
+#if defined(OS_WIN) || defined(OS_MACOSX)
+const char kWebKitCursiveFontFamilySimplifiedHan[] =
+ "webkit.webprefs.fonts.cursive.Hans";
+const char kWebKitCursiveFontFamilyTraditionalHan[] =
+ "webkit.webprefs.fonts.cursive.Hant";
+#endif
+
+// WebKit preferences.
+const char kWebKitWebSecurityEnabled[] = "webkit.webprefs.web_security_enabled";
+const char kWebKitDomPasteEnabled[] = "webkit.webprefs.dom_paste_enabled";
+const char kWebKitTextAreasAreResizable[] =
+ "webkit.webprefs.text_areas_are_resizable";
+const char kWebKitJavascriptCanAccessClipboard[] =
+ "webkit.webprefs.javascript_can_access_clipboard";
+const char kWebkitTabsToLinks[] = "webkit.webprefs.tabs_to_links";
+const char kWebKitAllowRunningInsecureContent[] =
+ "webkit.webprefs.allow_running_insecure_content";
+#if defined(OS_ANDROID)
+const char kWebKitFontScaleFactor[] = "webkit.webprefs.font_scale_factor";
+const char kWebKitForceEnableZoom[] = "webkit.webprefs.force_enable_zoom";
+const char kWebKitPasswordEchoEnabled[] =
+ "webkit.webprefs.password_echo_enabled";
+#endif
+const char kWebKitForceDarkModeEnabled[] =
+ "webkit.webprefs.force_dark_mode_enabled";
+
+const char kWebKitCommonScript[] = "Zyyy";
+const char kWebKitStandardFontFamily[] = "webkit.webprefs.fonts.standard.Zyyy";
+const char kWebKitFixedFontFamily[] = "webkit.webprefs.fonts.fixed.Zyyy";
+const char kWebKitSerifFontFamily[] = "webkit.webprefs.fonts.serif.Zyyy";
+const char kWebKitSansSerifFontFamily[] =
+ "webkit.webprefs.fonts.sansserif.Zyyy";
+const char kWebKitCursiveFontFamily[] = "webkit.webprefs.fonts.cursive.Zyyy";
+const char kWebKitFantasyFontFamily[] = "webkit.webprefs.fonts.fantasy.Zyyy";
+const char kWebKitPictographFontFamily[] =
+ "webkit.webprefs.fonts.pictograph.Zyyy";
+const char kWebKitDefaultFontSize[] = "webkit.webprefs.default_font_size";
+const char kWebKitDefaultFixedFontSize[] =
+ "webkit.webprefs.default_fixed_font_size";
+const char kWebKitMinimumFontSize[] = "webkit.webprefs.minimum_font_size";
+const char kWebKitMinimumLogicalFontSize[] =
+ "webkit.webprefs.minimum_logical_font_size";
+const char kWebKitJavascriptEnabled[] = "webkit.webprefs.javascript_enabled";
+const char kWebKitLoadsImagesAutomatically[] =
+ "webkit.webprefs.loads_images_automatically";
+const char kWebKitPluginsEnabled[] = "webkit.webprefs.plugins_enabled";
+
+// Boolean that is true when the SSL interstitial should allow users to
+// proceed anyway. Otherwise, proceeding is not possible.
+const char kSSLErrorOverrideAllowed[] = "ssl.error_override_allowed";
+
+// Enum that specifies whether Incognito mode is:
+// 0 - Enabled. Default behaviour. Default mode is available on demand.
+// 1 - Disabled. Used cannot browse pages in Incognito mode.
+// 2 - Forced. All pages/sessions are forced into Incognito.
+const char kIncognitoModeAvailability[] = "incognito.mode_availability";
+
+// Boolean that is true when Suggest support is enabled.
+const char kSearchSuggestEnabled[] = "search.suggest_enabled";
+
+#if defined(OS_ANDROID)
+// String indicating the Contextual Search enabled state.
+// "false" - opt-out (disabled)
+// "" (empty string) - undecided
+// "true" - opt-in (enabled)
+const char kContextualSearchEnabled[] = "search.contextual_search_enabled";
+const char kContextualSearchDisabledValue[] = "false";
+const char kContextualSearchEnabledValue[] = "true";
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_MACOSX)
+// Boolean that indicates whether the browser should put up a confirmation
+// window when the user is attempting to quit. Only on Mac.
+const char kConfirmToQuitEnabled[] = "browser.confirm_to_quit";
+
+// Boolean that indicates whether the browser should show the toolbar when it's
+// in fullscreen. Mac only.
+const char kShowFullscreenToolbar[] = "browser.show_fullscreen_toolbar";
+
+// Boolean that indicates whether the browser should allow Javascript injection
+// via Apple Events. Mac only.
+const char kAllowJavascriptAppleEvents[] =
+ "browser.allow_javascript_apple_events";
+
+#endif
+
+// Boolean which specifies whether we should ask the user if we should download
+// a file (true) or just download it automatically.
+const char kPromptForDownload[] = "download.prompt_for_download";
+
+// A boolean pref set to true if we're using Link Doctor error pages.
+const char kAlternateErrorPagesEnabled[] = "alternate_error_pages.enabled";
+
+// Controls if the QUIC protocol is allowed.
+const char kQuicAllowed[] = "net.quic_allowed";
+
+// Prefs for persisting network qualities.
+const char kNetworkQualities[] = "net.network_qualities";
+
+// Pref storing the user's network easter egg game high score.
+const char kNetworkEasterEggHighScore[] = "net.easter_egg_high_score";
+
+#if defined(OS_ANDROID)
+// Last time that a check for cloud policy management was done. This time is
+// recorded on Android so that retries aren't attempted on every startup.
+// Instead the cloud policy registration is retried at least 1 or 3 days later.
+const char kLastPolicyCheckTime[] = "policy.last_policy_check_time";
+#endif
+
+// A preference of enum chrome_browser_net::NetworkPredictionOptions shows
+// if prediction of network actions is allowed, depending on network type.
+// Actions include DNS prefetching, TCP and SSL preconnection, prerendering
+// of web pages, and resource prefetching.
+// TODO(bnc): Implement this preference as per crbug.com/334602.
+const char kNetworkPredictionOptions[] = "net.network_prediction_options";
+
+// An integer representing the state of the default apps installation process.
+// This value is persisted in the profile's user preferences because the process
+// is async, and the user may have stopped chrome in the middle. The next time
+// the profile is opened, the process will continue from where it left off.
+//
+// See possible values in external_provider_impl.cc.
+const char kDefaultAppsInstallState[] = "default_apps_install_state";
+
+// A boolean pref set to true if the Chrome Web Store icons should be hidden
+// from the New Tab Page and app launcher.
+const char kHideWebStoreIcon[] = "hide_web_store_icon";
+
+#if defined(OS_CHROMEOS)
+// An integer preference to store the number of times the Chrome OS Account
+// Manager migration flow ran successfully.
+const char kAccountManagerNumTimesMigrationRanSuccessfully[] =
+ "account_manager.num_times_migration_ran_successfully";
+
+// An integer preference to store the number of times the Chrome OS Account
+// Manager welcome screen has been shown.
+const char kAccountManagerNumTimesWelcomeScreenShown[] =
+ "account_manager.num_times_welcome_screen_shown";
+
+// A boolean pref set to true if touchpad tap-to-click is enabled.
+const char kTapToClickEnabled[] = "settings.touchpad.enable_tap_to_click";
+
+// A boolean pref set to true if touchpad three-finger-click is enabled.
+const char kEnableTouchpadThreeFingerClick[] =
+ "settings.touchpad.enable_three_finger_click";
+
+// A boolean pref set to true if touchpad natural scrolling is enabled.
+const char kNaturalScroll[] = "settings.touchpad.natural_scroll";
+
+// A boolean pref set to true if primary mouse button is the left button.
+const char kPrimaryMouseButtonRight[] = "settings.mouse.primary_right";
+
+// A boolean pref set to true if turning the mouse wheel toward the user should
+// result in scrolling up instead of the more common scrolling down.
+const char kMouseReverseScroll[] = "settings.mouse.reverse_scroll";
+
+// A boolean pref set to true if mouse acceleration is enabled. When disabled
+// only simple linear scaling is applied based on sensitivity.
+const char kMouseAcceleration[] = "settings.mouse.acceleration";
+
+// A boolean pref set to true if touchpad acceleration is enabled. When
+// disabled only simple linear scaling is applied based on sensitivity.
+const char kTouchpadAcceleration[] = "settings.touchpad.acceleration";
+
+// A integer pref for the touchpad sensitivity.
+const char kMouseSensitivity[] = "settings.mouse.sensitivity2";
+
+// A integer pref for the touchpad sensitivity.
+const char kTouchpadSensitivity[] = "settings.touchpad.sensitivity2";
+
+// A boolean pref set to true if time should be displayed in 24-hour clock.
+const char kUse24HourClock[] = "settings.clock.use_24hour_clock";
+
+// A string pref containing Timezone ID for this user.
+const char kUserTimezone[] = "settings.timezone";
+
+// This setting disables manual timezone selection and starts periodic timezone
+// refresh.
+// Deprecated. Replaced with kResolveTimezoneByGeolocationMethod.
+// TODO(alemate): https://crbug.com/783367 Remove outdated prefs.
+const char kResolveTimezoneByGeolocation[] =
+ "settings.resolve_timezone_by_geolocation";
+
+// This setting controls what information is sent to the server to get
+// device location to resolve time zone in user session. Values must
+// match TimeZoneResolverManager::TimeZoneResolveMethod enum.
+const char kResolveTimezoneByGeolocationMethod[] =
+ "settings.resolve_timezone_by_geolocation_method";
+
+// This setting is true when kResolveTimezoneByGeolocation value
+// has been migrated to kResolveTimezoneByGeolocationMethod.
+const char kResolveTimezoneByGeolocationMigratedToMethod[] =
+ "settings.resolve_timezone_by_geolocation_migrated_to_method";
+
+// A string pref set to the current input method.
+const char kLanguageCurrentInputMethod[] =
+ "settings.language.current_input_method";
+
+// A string pref set to the previous input method.
+const char kLanguagePreviousInputMethod[] =
+ "settings.language.previous_input_method";
+
+// A list pref set to the allowed input methods (see policy
+// "AllowedInputMethods").
+const char kLanguageAllowedInputMethods[] =
+ "settings.language.allowed_input_methods";
+
+// A string pref (comma-separated list) set to the preloaded (active) input
+// method IDs (ex. "pinyin,mozc").
+const char kLanguagePreloadEngines[] = "settings.language.preload_engines";
+const char kLanguagePreloadEnginesSyncable[] =
+ "settings.language.preload_engines_syncable";
+
+// A string pref (comma-separated list) set to the extension and ARC IMEs to be
+// enabled.
+const char kLanguageEnabledImes[] = "settings.language.enabled_extension_imes";
+const char kLanguageEnabledImesSyncable[] =
+ "settings.language.enabled_extension_imes_syncable";
+
+// A boolean pref set to true if the IME menu is activated.
+const char kLanguageImeMenuActivated[] = "settings.language.ime_menu_activated";
+
+// A dictionary of input method IDs and their settings. Each value is itself a
+// dictionary of key / value string pairs, with each pair representing a setting
+// and its value.
+const char kLanguageInputMethodSpecificSettings[] =
+ "settings.language.input_method_specific_settings";
+
+// A boolean pref to indicate whether we still need to add the globally synced
+// input methods. False after the initial post-OOBE sync.
+const char kLanguageShouldMergeInputMethods[] =
+ "settings.language.merge_input_methods";
+
+// A boolean pref that causes top-row keys to be interpreted as function keys
+// instead of as media keys.
+const char kLanguageSendFunctionKeys[] =
+ "settings.language.send_function_keys";
+
+// A boolean pref which determines whether key repeat is enabled.
+const char kLanguageXkbAutoRepeatEnabled[] =
+ "settings.language.xkb_auto_repeat_enabled_r2";
+// A integer pref which determines key repeat delay (in ms).
+const char kLanguageXkbAutoRepeatDelay[] =
+ "settings.language.xkb_auto_repeat_delay_r2";
+// A integer pref which determines key repeat interval (in ms).
+const char kLanguageXkbAutoRepeatInterval[] =
+ "settings.language.xkb_auto_repeat_interval_r2";
+// "_r2" suffixes were added to the three prefs above when we changed the
+// preferences to not be user-configurable or sync with the cloud. The prefs are
+// now user-configurable and syncable again, but we don't want to overwrite the
+// current values with the old synced values, so we continue to use this suffix.
+
+// A boolean pref which turns on Advanced Filesystem
+// (USB support, SD card, etc).
+const char kLabsAdvancedFilesystemEnabled[] =
+ "settings.labs.advanced_filesystem";
+
+// A boolean pref which turns on the mediaplayer.
+const char kLabsMediaplayerEnabled[] = "settings.labs.mediaplayer";
+
+// A boolean pref of whether to show mobile data first-use warning notification.
+// Note: 3g in the name is for legacy reasons. The pref was added while only 3G
+// mobile data was supported.
+const char kShowMobileDataNotification[] =
+ "settings.internet.mobile.show_3g_promo_notification";
+
+// An integer pref counting times Data Saver prompt has been shown.
+const char kDataSaverPromptsShown[] =
+ "settings.internet.mobile.datasaver_prompts_shown";
+
+// A string pref that contains version where "What's new" promo was shown.
+const char kChromeOSReleaseNotesVersion[] = "settings.release_notes.version";
+
+// A string pref that contains either a Chrome app ID (see
+// extensions::ExtensionId) or an Android package name (using Java package
+// naming conventions) of the preferred note-taking app. An empty value
+// indicates that the user hasn't selected an app yet.
+const char kNoteTakingAppId[] = "settings.note_taking_app_id";
+
+// A boolean pref indicating whether preferred note-taking app (see
+// |kNoteTakingAppId|) is allowed to handle note taking actions on the lock
+// screen.
+const char kNoteTakingAppEnabledOnLockScreen[] =
+ "settings.note_taking_app_enabled_on_lock_screen";
+
+// List of note taking aps that can be enabled to run on the lock screen.
+// The intended usage is to whitelist the set of apps that the user can enable
+// to run on lock screen, not to actually enable the apps to run on lock screen.
+const char kNoteTakingAppsLockScreenWhitelist[] =
+ "settings.note_taking_apps_lock_screen_whitelist";
+
+// Dictionary pref that maps lock screen app ID to a boolean indicating whether
+// the toast dialog has been show and dismissed as the app was being launched
+// on the lock screen.
+const char kNoteTakingAppsLockScreenToastShown[] =
+ "settings.note_taking_apps_lock_screen_toast_shown";
+
+// Whether the preferred note taking app should be requested to restore the last
+// note created on lock screen when launched on lock screen.
+const char kRestoreLastLockScreenNote[] =
+ "settings.restore_last_lock_screen_note";
+
+// A boolean pref indicating whether user activity has been observed in the
+// current session already. The pref is used to restore information about user
+// activity after browser crashes.
+const char kSessionUserActivitySeen[] = "session.user_activity_seen";
+
+// A preference to keep track of the session start time. If the session length
+// limit is configured to start running after initial user activity has been
+// observed, the pref is set after the first user activity in a session.
+// Otherwise, it is set immediately after session start. The pref is used to
+// restore the session start time after browser crashes. The time is expressed
+// as the serialization obtained from base::TimeTicks::ToInternalValue().
+const char kSessionStartTime[] = "session.start_time";
+
+// Holds the maximum session time in milliseconds. If this pref is set, the
+// user is logged out when the maximum session time is reached. The user is
+// informed about the remaining time by a countdown timer shown in the ash
+// system tray.
+const char kSessionLengthLimit[] = "session.length_limit";
+
+// Whether the session length limit should start running only after the first
+// user activity has been observed in a session.
+const char kSessionWaitForInitialUserActivity[] =
+ "session.wait_for_initial_user_activity";
+
+// A preference of the last user session type. It is used with the
+// kLastSessionLength pref below to store the last user session info
+// on shutdown so that it could be reported on the next run.
+const char kLastSessionType[] = "session.last_session_type";
+
+// A preference of the last user session length.
+const char kLastSessionLength[] = "session.last_session_length";
+
+// The URL from which the Terms of Service can be downloaded. The value is only
+// honored for public accounts.
+const char kTermsOfServiceURL[] = "terms_of_service.url";
+
+// Indicates whether the remote attestation is enabled for the user.
+const char kAttestationEnabled[] = "attestation.enabled";
+// The list of extensions allowed to use the platformKeysPrivate API for
+// remote attestation.
+const char kAttestationExtensionWhitelist[] = "attestation.extension_whitelist";
+
+// A boolean pref recording whether user has dismissed the multiprofile
+// itroduction dialog show.
+const char kMultiProfileNeverShowIntro[] =
+ "settings.multi_profile_never_show_intro";
+
+// A boolean pref recording whether user has dismissed the multiprofile
+// teleport warning dialog show.
+const char kMultiProfileWarningShowDismissed[] =
+ "settings.multi_profile_warning_show_dismissed";
+
+// A string pref that holds string enum values of how the user should behave
+// in a multiprofile session. See ChromeOsMultiProfileUserBehavior policy
+// for more details of the valid values.
+const char kMultiProfileUserBehavior[] = "settings.multiprofile_user_behavior";
+
+// A boolean preference indicating whether user has seen first-run tutorial
+// already.
+const char kFirstRunTutorialShown[] = "settings.first_run_tutorial_shown";
+
+// Indicates the amount of time for which a user authenticated via SAML can use
+// offline authentication against a cached password before being forced to go
+// through online authentication against GAIA again. The time is expressed in
+// seconds. A value of -1 indicates no limit, allowing the user to use offline
+// authentication indefinitely. The limit is in effect only if GAIA redirected
+// the user to a SAML IdP during the last online authentication.
+const char kSAMLOfflineSigninTimeLimit[] = "saml.offline_signin_time_limit";
+
+// A preference to keep track of the last time the user authenticated against
+// GAIA using SAML. The preference is updated whenever the user authenticates
+// against GAIA: If GAIA redirects to a SAML IdP, the preference is set to the
+// current time. If GAIA performs the authentication itself, the preference is
+// cleared. The time is expressed as the serialization obtained from
+// base::Time::ToInternalValue().
+const char kSAMLLastGAIASignInTime[] = "saml.last_gaia_sign_in_time";
+
+// The total number of seconds that the machine has spent sitting on the
+// OOBE screen.
+const char kTimeOnOobe[] = "settings.time_on_oobe";
+
+// List of mounted file systems via the File System Provider API. Used to
+// restore them after a reboot.
+const char kFileSystemProviderMounted[] = "file_system_provider.mounted";
+
+// A boolean pref set to true if the virtual keyboard should be enabled.
+const char kTouchVirtualKeyboardEnabled[] = "ui.touch_virtual_keyboard_enabled";
+
+// A boolean pref that controls whether the dark connect feature is enabled.
+// The dark connect feature allows a Chrome OS device to periodically wake
+// from suspend in a low-power state to maintain WiFi connectivity.
+const char kWakeOnWifiDarkConnect[] =
+ "settings.internet.wake_on_wifi_darkconnect";
+
+// This is the policy CaptivePortalAuthenticationIgnoresProxy that allows to
+// open captive portal authentication pages in a separate window under
+// a temporary incognito profile ("signin profile" is used for this purpose),
+// which allows to bypass the user's proxy for captive portal authentication.
+const char kCaptivePortalAuthenticationIgnoresProxy[] =
+ "proxy.captive_portal_ignores_proxy";
+
+// This boolean controls whether the first window shown on first run should be
+// unconditionally maximized, overriding the heuristic that normally chooses the
+// window size.
+const char kForceMaximizeOnFirstRun[] = "ui.force_maximize_on_first_run";
+
+// A dictionary pref mapping public keys that identify platform keys to its
+// properties like whether it's meant for corporate usage.
+const char kPlatformKeys[] = "platform_keys";
+
+// A boolean pref. If set to true, the Unified Desktop feature is made
+// available and turned on by default, which allows applications to span
+// multiple screens. Users may turn the feature off and on in the settings
+// while this is set to true.
+const char kUnifiedDesktopEnabledByDefault[] =
+ "settings.display.unified_desktop_enabled_by_default";
+
+// An int64 pref. This is a timestamp of the most recent time the profile took
+// or dismissed HaTS (happiness-tracking) survey.
+const char kHatsLastInteractionTimestamp[] = "hats_last_interaction_timestamp";
+
+// An int64 pref. This is the timestamp that indicates the end of the most
+// recent survey cycle.
+const char kHatsSurveyCycleEndTimestamp[] = "hats_survey_cycle_end_timestamp";
+
+// A boolean pref. Indicates if the device is selected for HaTS in the current
+// survey cycle.
+const char kHatsDeviceIsSelected[] = "hats_device_is_selected";
+
+// A boolean pref. Indicates if we've already shown a notification to inform the
+// current user about the quick unlock feature.
+const char kPinUnlockFeatureNotificationShown[] =
+ "pin_unlock_feature_notification_shown";
+// A boolean pref. Indicates if we've already shown a notification to inform the
+// current user about the fingerprint unlock feature.
+const char kFingerprintUnlockFeatureNotificationShown[] =
+ "fingerprint_unlock_feature_notification_shown";
+
+// The hash for the pin quick unlock mechanism.
+const char kQuickUnlockPinSecret[] = "quick_unlock.pin.secret";
+
+// An integer pref. Indicates the number of fingerprint records registered.
+const char kQuickUnlockFingerprintRecord[] = "quick_unlock.fingerprint.record";
+
+// Deprecated (crbug/998983) in favor of kEndOfLifeDate.
+// An integer pref. Holds one of several values:
+// 0: Supported. Device is in supported state.
+// 1: Security Only. Device is in Security-Only update (after initial 5 years).
+// 2: EOL. Device is End of Life(No more updates expected).
+// This value needs to be consistent with EndOfLifeStatus enum.
+const char kEolStatus[] = "eol_status";
+
+// A Time pref. Holds the last used Eol Date and is compared to the latest Eol
+// Date received to make changes to Eol notifications accordingly.
+const char kEndOfLifeDate[] = "eol_date";
+
+// Boolean pref indicating that the first warning End Of Life month and year
+// notification was dismissed by the user.
+const char kFirstEolWarningDismissed[] = "first_eol_warning_dismissed";
+
+// Boolean pref indicating that the second warning End Of Life month and year
+// notification was dismissed by the user.
+const char kSecondEolWarningDismissed[] = "second_eol_warning_dismissed";
+
+// Boolean pref indicating that the End Of Life final update notification was
+// dismissed by the user.
+const char kEolNotificationDismissed[] = "eol_notification_dismissed";
+
+// A list of allowed quick unlock modes. A quick unlock mode can only be used if
+// its type is on this list, or if type all (all quick unlock modes enabled) is
+// on this list.
+const char kQuickUnlockModeWhitelist[] = "quick_unlock_mode_whitelist";
+// Enum that specifies how often a user has to enter their password to continue
+// using quick unlock. These values are the same as the ones in
+// chromeos::QuickUnlockPasswordConfirmationFrequency.
+// 0 - six hours. Users will have to enter their password every six hours.
+// 1 - twelve hours. Users will have to enter their password every twelve hours.
+// 2 - day. Users will have to enter their password every day.
+// 3 - week. Users will have to enter their password every week.
+const char kQuickUnlockTimeout[] = "quick_unlock_timeout";
+// Integer prefs indicating the minimum and maximum lengths of the lock screen
+// pin.
+const char kPinUnlockMinimumLength[] = "pin_unlock_minimum_length";
+const char kPinUnlockMaximumLength[] = "pin_unlock_maximum_length";
+// Boolean pref indicating whether users are allowed to set easy pins.
+const char kPinUnlockWeakPinsAllowed[] = "pin_unlock_weak_pins_allowed";
+
+// Boolean pref indicating whether this device supports BLE advertising.
+const char kInstantTetheringBleAdvertisingSupported[] =
+ "tether.ble_advertising_supported";
+
+// Boolean pref indicating whether someone can cast to the device.
+const char kCastReceiverEnabled[] = "cast_receiver.enabled";
+
+// String pref indicating what is the minimum version of Chrome required to
+// allow user sign in. If the string is empty or blank no restrictions will
+// be applied. See base::Version for exact string format.
+const char kMinimumAllowedChromeVersion[] = "minimum_req.version";
+
+// Boolean preference that triggers chrome://settings/androidApps/details to be
+// opened on user session start.
+const char kShowArcSettingsOnSessionStart[] =
+ "start_arc_settings_on_session_start";
+
+// Boolean preference that triggers chrome://settings/syncSetup to be opened
+// on user session start.
+const char kShowSyncSettingsOnSessionStart[] =
+ "start_sync_settings_on_session_start";
+
+// Dictionary preference that maps language to default voice name preferences
+// for the users's text-to-speech settings. For example, this might map
+// 'en-US' to 'Chrome OS US English'.
+const char kTextToSpeechLangToVoiceName[] = "settings.tts.lang_to_voice_name";
+
+// Double preference that controls the default text-to-speech voice rate,
+// where 1.0 is an unchanged rate, and for example, 0.5 is half as fast,
+// and 2.0 is twice as fast.
+const char kTextToSpeechRate[] = "settings.tts.speech_rate";
+
+// Double preference that controls the default text-to-speech voice pitch,
+// where 1.0 is unchanged, and for example 0.5 is lower, and 2.0 is
+// higher-pitched.
+const char kTextToSpeechPitch[] = "settings.tts.speech_pitch";
+
+// Double preference that controls the default text-to-speech voice volume
+// relative to the system volume, where lower than 1.0 is quieter than the
+// system volume, and higher than 1.0 is louder.
+const char kTextToSpeechVolume[] = "settings.tts.speech_volume";
+
+// A dictionary containing the latest Time Limits override authorized by parent
+// access code.
+const char kTimeLimitLocalOverride[] = "screen_time.local_override";
+
+// A dictionary preference holding the usage time limit definitions for a user.
+const char kUsageTimeLimit[] = "screen_time.limit";
+
+// Last state of the screen time limit.
+const char kScreenTimeLastState[] = "screen_time.last_state";
+
+// Boolean controlling whether showing Sync Consent during sign-in is enabled.
+// Controlled by policy.
+const char kEnableSyncConsent[] = "sync_consent.enabled";
+
+// Boolean pref indicating whether a user is allowed to use the Network File
+// Shares for Chrome OS feature.
+const char kNetworkFileSharesAllowed[] = "network_file_shares.allowed";
+
+// Boolean pref indicating whether the currently running public session runs in
+// the old standard "public session" mode (false), or in the new "managed
+// session" mode which has lifted restrictions (true).
+const char kManagedSessionEnabled[] = "managed_session.enabled";
+
+// Boolean pref indicating whether the user has previously dismissed the
+// one-time notification indicating the need for a cleanup powerwash after TPM
+// firmware update that didn't flush the TPM SRK.
+const char kTPMFirmwareUpdateCleanupDismissed[] =
+ "tpm_firmware_update.cleanup_dismissed";
+
+// Int64 pref indicating the time in microseconds since Windows epoch
+// (1601-01-01 00:00:00 UTC) when the notification informing the user about a
+// planned TPM update that will clear all user data was shown. If the
+// notification was not yet shown the pref holds the value Time::Min().
+const char kTPMUpdatePlannedNotificationShownTime[] =
+ "tpm_auto_update.planned_notification_shown_time";
+
+// Boolean pref indicating whether the notification informing the user that an
+// auto-update that will clear all the user data at next reboot was shown.
+const char kTPMUpdateOnNextRebootNotificationShown[] =
+ "tpm_auto_update.update_on_reboot_notification_shown";
+
+// Boolean pref indicating whether the NetBios Name Query Request Protocol is
+// used for discovering shares on the user's network by the Network File
+// Shares for Chrome OS feature.
+const char kNetBiosShareDiscoveryEnabled[] =
+ "network_file_shares.netbios_discovery.enabled";
+
+// Amount of screen time that a child user has used in the current day.
+const char kChildScreenTimeMilliseconds[] = "child_screen_time";
+
+// Last time the kChildScreenTimeMilliseconds was saved.
+const char kLastChildScreenTimeSaved[] = "last_child_screen_time_saved";
+
+// Last time that the kChildScreenTime pref was reset.
+const char kLastChildScreenTimeReset[] = "last_child_screen_time_reset";
+
+// Last patch on which release notes were shown.
+const char kReleaseNotesLastShownMilestone[] =
+ "last_release_notes_shown_milestone";
+
+// Amount of times the release notes suggestion chip should be
+// shown before it disappears.
+const char kReleaseNotesSuggestionChipTimesLeftToShow[] =
+ "times_left_to_show_release_notes_suggestion_chip";
+
+// Boolean pref indicating whether the NTLM authentication protocol should be
+// enabled when mounting an SMB share with a user credential by the Network File
+// Shares for Chrome OS feature.
+const char kNTLMShareAuthenticationEnabled[] =
+ "network_file_shares.ntlm_share_authentication.enabled";
+
+// Dictionary pref containing configuration used to verify Parent Access Code.
+// Controlled by ParentAccessCodeConfig policy.
+const char kParentAccessCodeConfig[] = "child_user.parent_access_code.config";
+
+// List of preconfigured network file shares.
+const char kNetworkFileSharesPreconfiguredShares[] =
+ "network_file_shares.preconfigured_shares";
+
+// URL path string of the most recently used SMB NetworkFileShare path.
+const char kMostRecentlyUsedNetworkFileShareURL[] =
+ "network_file_shares.most_recently_used_url";
+
+// A string pref storing the path of device wallpaper image file.
+const char kDeviceWallpaperImageFilePath[] =
+ "policy.device_wallpaper_image_file_path";
+
+// Boolean whether Kerberos daemon supports remembering passwords.
+// Tied to KerberosRememberPasswordEnabled policy.
+const char kKerberosRememberPasswordEnabled[] =
+ "kerberos.remember_password_enabled";
+// Boolean whether users may add new Kerberos accounts.
+// Tied to KerberosAddAccountsAllowed policy.
+const char kKerberosAddAccountsAllowed[] = "kerberos.add_accounts_allowed";
+// Dictionary specifying a pre-set list of Kerberos accounts.
+// Tied to KerberosAccounts policy.
+const char kKerberosAccounts[] = "kerberos.accounts";
+// Used by KerberosCredentialsManager to remember which account is currently
+// active (empty if none) and to determine whether to wake up the Kerberos
+// daemon on session startup.
+const char kKerberosActivePrincipalName[] = "kerberos.active_principal_name";
+
+// A boolean pref for enabling/disabling App reinstall recommendations in Zero
+// State Launcher by policy.
+const char kAppReinstallRecommendationEnabled[] =
+ "zero_state_app_install_recommendation.enabled";
+
+// A boolean pref that when set to true, prevents the browser window from
+// launching at the start of the session.
+const char kStartupBrowserWindowLaunchSuppressed[] =
+ "startup_browser_window_launch_suppressed";
+
+// A string pref stored in local state. Set and read by extensions using the
+// chrome.login API.
+const char kLoginExtensionApiDataForNextLoginAttempt[] =
+ "extensions_api.login.data_for_next_login_attempt";
+
+// String containing last RSU lookup key uploaded. Empty until first upload.
+const char kLastRsuDeviceIdUploaded[] = "rsu.last_rsu_device_id_uploaded";
+
+// Boolean that determines whether to show a banner in OS Settings that links
+// to Browser settings.
+const char kSettingsShowBrowserBanner[] = "settings.cros.show_browser_banner";
+
+// Boolean user profile pref that determines whether to show a banner in browser
+// settings that links to OS settings.
+const char kSettingsShowOSBanner[] = "settings.cros.show_os_banner";
+
+// A JSON pref for controlling which USB devices are whitelisted for certain
+// urls to be used via the WebUSB API on the login screen.
+const char kDeviceLoginScreenWebUsbAllowDevicesForUrls[] =
+ "device_login_screen_webusb_allow_devices_for_urls";
+#endif // defined(OS_CHROMEOS)
+
+// A boolean pref set to true if a Home button to open the Home pages should be
+// visible on the toolbar.
+const char kShowHomeButton[] = "browser.show_home_button";
+
+// Boolean pref to define the default setting for "block offensive words".
+// The old key value is kept to avoid unnecessary migration code.
+const char kSpeechRecognitionFilterProfanities[] =
+ "browser.speechinput_censor_results";
+
+// Boolean controlling whether deleting browsing and download history is
+// permitted.
+const char kAllowDeletingBrowserHistory[] = "history.deleting_enabled";
+
+#if !defined(OS_ANDROID)
+// Whether the "Click here to clear your browsing data" tooltip promo has been
+// shown on the History page.
+const char kHistoryMenuPromoShown[] = "history.menu_promo_shown";
+#endif
+
+// Boolean controlling whether SafeSearch is mandatory for Google Web Searches.
+const char kForceGoogleSafeSearch[] = "settings.force_google_safesearch";
+
+// Integer controlling whether Restrict Mode (moderate/strict) is mandatory on
+// YouTube. See |safe_search_util::YouTubeRestrictMode| for possible values.
+const char kForceYouTubeRestrict[] = "settings.force_youtube_restrict";
+
+// Comma separated list of domain names (e.g. "google.com,school.edu").
+// When this pref is set, the user will be able to access Google Apps
+// only using an account that belongs to one of the domains from this pref.
+const char kAllowedDomainsForApps[] = "settings.allowed_domains_for_apps";
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// Linux specific preference on whether we should match the system theme.
+const char kUsesSystemTheme[] = "extensions.theme.use_system";
+#endif
+const char kCurrentThemePackFilename[] = "extensions.theme.pack";
+const char kCurrentThemeID[] = "extensions.theme.id";
+const char kAutogeneratedThemeColor[] = "autogenerated.theme.color";
+
+// Boolean pref which persists whether the extensions_ui is in developer mode
+// (showing developer packing tools and extensions details)
+const char kExtensionsUIDeveloperMode[] = "extensions.ui.developer_mode";
+
+// Dictionary pref that tracks which command belongs to which
+// extension + named command pair.
+const char kExtensionCommands[] = "extensions.commands";
+
+// Pref containing the directory for internal plugins as written to the plugins
+// list (below).
+const char kPluginsLastInternalDirectory[] = "plugins.last_internal_directory";
+
+// List pref containing information (dictionaries) on plugins.
+const char kPluginsPluginsList[] = "plugins.plugins_list";
+
+// List pref containing names of plugins that are disabled by policy.
+const char kPluginsDisabledPlugins[] = "plugins.plugins_disabled";
+
+// List pref containing exceptions to the list of plugins disabled by policy.
+const char kPluginsDisabledPluginsExceptions[] =
+ "plugins.plugins_disabled_exceptions";
+
+// List pref containing names of plugins that are enabled by policy.
+const char kPluginsEnabledPlugins[] = "plugins.plugins_enabled";
+
+// Whether Chrome should use its internal PDF viewer or not.
+const char kPluginsAlwaysOpenPdfExternally[] =
+ "plugins.always_open_pdf_externally";
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// Whether about:plugins is shown in the details mode or not.
+const char kPluginsShowDetails[] = "plugins.show_details";
+#endif
+
+// Boolean that indicates whether outdated plugins are allowed or not.
+const char kPluginsAllowOutdated[] = "plugins.allow_outdated";
+
+// Boolean that indicates whether all Flash content (including cross-origin and
+// small content) is allowed to run when it is explicitly allowed via content
+// settings.
+const char kRunAllFlashInAllowMode[] = "plugins.run_all_flash_in_allow_mode";
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// Dictionary holding plugins metadata.
+const char kPluginsMetadata[] = "plugins.metadata";
+
+// Last update time of plugins resource cache.
+const char kPluginsResourceCacheUpdate[] = "plugins.resource_cache_update";
+#endif
+
+// Last time the flash deprecation message was dismissed. Used to ensure a
+// cooldown period passes before the deprecation message is displayed again.
+const char kPluginsDeprecationInfobarLastShown[] =
+ "plugins.deprecation_infobar_last_shown";
+
+// Int64 containing the internal value of the time at which the default browser
+// infobar was last dismissed by the user.
+const char kDefaultBrowserLastDeclined[] =
+ "browser.default_browser_infobar_last_declined";
+// Boolean that indicates whether the kDefaultBrowserLastDeclined preference
+// should be reset on start-up.
+const char kResetCheckDefaultBrowser[] =
+ "browser.should_reset_check_default_browser";
+
+// Policy setting whether default browser check should be disabled and default
+// browser registration should take place.
+const char kDefaultBrowserSettingEnabled[] =
+ "browser.default_browser_setting_enabled";
+
+// String indicating the size of the captions text as a percentage.
+const char kAccessibilityCaptionsTextSize[] =
+ "accessibility.captions.text_size";
+
+// String indicating the font of the captions text.
+const char kAccessibilityCaptionsTextFont[] =
+ "accessibility.captions.text_font";
+
+// Comma-separated string indicating the RGB values of the captions text color.
+const char kAccessibilityCaptionsTextColor[] =
+ "accessibility.captions.text_color";
+
+// Integer indicating the opacity of the captions text from 0 - 100.
+const char kAccessibilityCaptionsTextOpacity[] =
+ "accessibility.captions.text_opacity";
+
+// Comma-separated string indicating the RGB values of the background color.
+const char kAccessibilityCaptionsBackgroundColor[] =
+ "accessibility.captions.background_color";
+
+// CSS string indicating the shadow of the captions text.
+const char kAccessibilityCaptionsTextShadow[] =
+ "accessibility.captions.text_shadow";
+
+// Integer indicating the opacity of the captions text background from 0 - 100.
+const char kAccessibilityCaptionsBackgroundOpacity[] =
+ "accessibility.captions.background_opacity";
// Boolean that indicates whether chrome://accessibility should show the
// internal accessibility tree.
@@ -25,4 +1163,1673 @@ const char kAccessibilityImageLabelsEnabled[] =
const char kAccessibilityImageLabelsOptInAccepted[] =
"settings.a11y.enable_accessibility_image_labels_opt_in_accepted";
+#if defined(OS_MACOSX)
+// Boolean that indicates whether the application should show the info bar
+// asking the user to set up automatic updates when Keystone promotion is
+// required.
+const char kShowUpdatePromotionInfoBar[] =
+ "browser.show_update_promotion_info_bar";
+#endif
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// Boolean that is false if we should show window manager decorations. If
+// true, we draw a custom chrome frame (thicker title bar and blue border).
+const char kUseCustomChromeFrame[] = "browser.custom_chrome_frame";
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// Which plugins have been whitelisted manually by the user.
+const char kContentSettingsPluginWhitelist[] =
+ "profile.content_settings.plugin_whitelist";
+#endif
+
+#if !defined(OS_ANDROID)
+// Double that indicates the default zoom level.
+const char kPartitionDefaultZoomLevel[] = "partition.default_zoom_level";
+
+// Dictionary that maps hostnames to zoom levels. Hosts not in this pref will
+// be displayed at the default zoom level.
+const char kPartitionPerHostZoomLevels[] = "partition.per_host_zoom_levels";
+
+const char kPinnedTabs[] = "pinned_tabs";
+#endif // !defined(OS_ANDROID)
+
+// Preference to disable 3D APIs (WebGL, Pepper 3D).
+const char kDisable3DAPIs[] = "disable_3d_apis";
+
+const char kEnableDeprecatedWebPlatformFeatures[] =
+ "enable_deprecated_web_platform_features";
+
+// Whether to enable hyperlink auditing ("<a ping>").
+const char kEnableHyperlinkAuditing[] = "enable_a_ping";
+
+// Whether to enable sending referrers.
+const char kEnableReferrers[] = "enable_referrers";
+
+// Whether to send the DNT header.
+const char kEnableDoNotTrack[] = "enable_do_not_track";
+
+// Whether to allow the use of Encrypted Media Extensions (EME), except for the
+// use of Clear Key key sytems, which is always allowed as required by the spec.
+// TODO(crbug.com/784675): This pref was used as a WebPreference which is why
+// the string is prefixed with "webkit.webprefs". Now this is used in
+// blink::mojom::RendererPreferences and we should migrate the pref to use a new
+// non-webkit-prefixed string.
+const char kEnableEncryptedMedia[] = "webkit.webprefs.encrypted_media_enabled";
+
+// Boolean that specifies whether to import the form data for autofill from the
+// default browser on first run.
+const char kImportAutofillFormData[] = "import_autofill_form_data";
+
+// Boolean that specifies whether to import bookmarks from the default browser
+// on first run.
+const char kImportBookmarks[] = "import_bookmarks";
+
+// Boolean that specifies whether to import the browsing history from the
+// default browser on first run.
+const char kImportHistory[] = "import_history";
+
+// Boolean that specifies whether to import the homepage from the default
+// browser on first run.
+const char kImportHomepage[] = "import_home_page";
+
+// Boolean that specifies whether to import the saved passwords from the default
+// browser on first run.
+const char kImportSavedPasswords[] = "import_saved_passwords";
+
+// Boolean that specifies whether to import the search engine from the default
+// browser on first run.
+const char kImportSearchEngine[] = "import_search_engine";
+
+// Prefs used to remember selections in the "Import data" dialog on the settings
+// page (chrome://settings/importData).
+const char kImportDialogAutofillFormData[] = "import_dialog_autofill_form_data";
+const char kImportDialogBookmarks[] = "import_dialog_bookmarks";
+const char kImportDialogHistory[] = "import_dialog_history";
+const char kImportDialogSavedPasswords[] = "import_dialog_saved_passwords";
+const char kImportDialogSearchEngine[] = "import_dialog_search_engine";
+
+// Profile avatar and name
+const char kProfileAvatarIndex[] = "profile.avatar_index";
+const char kProfileName[] = "profile.name";
+// Whether a profile is using a default avatar name (eg. Pickles or Person 1)
+// because it was randomly assigned at profile creation time.
+const char kProfileUsingDefaultName[] = "profile.using_default_name";
+// Whether a profile is using an avatar without having explicitely chosen it
+// (i.e. was assigned by default by legacy profile creation).
+const char kProfileUsingDefaultAvatar[] = "profile.using_default_avatar";
+const char kProfileUsingGAIAAvatar[] = "profile.using_gaia_avatar";
+
+// The supervised user ID.
+const char kSupervisedUserId[] = "profile.managed_user_id";
+
+// 64-bit integer serialization of the base::Time when the user's GAIA info
+// was last updated.
+const char kProfileGAIAInfoUpdateTime[] = "profile.gaia_info_update_time";
+
+// The URL from which the GAIA profile picture was downloaded. This is cached to
+// prevent the same picture from being downloaded multiple times.
+const char kProfileGAIAInfoPictureURL[] = "profile.gaia_info_picture_url";
+
+// Integer that specifies the number of times that we have shown the upgrade
+// tutorial card in the avatar menu bubble.
+const char kProfileAvatarTutorialShown[] =
+ "profile.avatar_bubble_tutorial_shown";
+
+// Indicates if we've already shown a notification that high contrast
+// mode is on, recommending high-contrast extensions and themes.
+const char kInvertNotificationShown[] = "invert_notification_version_2_shown";
+
+// Boolean controlling whether printing is enabled.
+const char kPrintingEnabled[] = "printing.enabled";
+
+// Boolean controlling whether print preview is disabled.
+const char kPrintPreviewDisabled[] = "printing.print_preview_disabled";
+
+// A pref holding the value of the policy used to control default destination
+// selection in the Print Preview. See DefaultPrinterSelection policy.
+const char kPrintPreviewDefaultDestinationSelectionRules[] =
+ "printing.default_destination_selection_rules";
+
+// The default value for the 'Headers and footers' checkbox, in Print Preview.
+// Takes priority over kPrintPreviewStickySettings if set.
+const char kPrintHeaderFooter[] = "printing.print_header_footer";
+
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+// A pref that sets the default destination in Print Preview to always be the
+// OS default printer instead of the most recently used destination.
+const char kPrintPreviewUseSystemDefaultPrinter[] =
+ "printing.use_system_default_printer";
+#endif // !OS_CHROMEOS && !OS_ANDROID
+
+#if defined(OS_CHROMEOS)
+// List of print servers ids that are allowed. List of strings.
+const char kExternalPrintServersWhitelist[] =
+ "native_printing.external_print_servers_whitelist";
+
+// List of printers configured by policy.
+const char kRecommendedNativePrinters[] =
+ "native_printing.recommended_printers";
+
+// Enum designating the type of restrictions bulk printers are using.
+const char kRecommendedNativePrintersAccessMode[] =
+ "native_printing.recommended_printers_access_mode";
+
+// List of printer ids which are explicitly disallowed. List of strings.
+const char kRecommendedNativePrintersBlacklist[] =
+ "native_printing.recommended_printers_blacklist";
+
+// List of printer ids that are allowed. List of strings.
+const char kRecommendedNativePrintersWhitelist[] =
+ "native_printing.recommended_printers_whitelist";
+
+// A Boolean flag which represents whether or not users are allowed to configure
+// and use their own native printers.
+const char kUserNativePrintersAllowed[] =
+ "native_printing.user_native_printers_allowed";
+
+// A pref holding the list of allowed printing color mode as a bitmask composed
+// of |printing::ColorModeRestriction| values. 0 is no restriction.
+const char kPrintingAllowedColorModes[] = "printing.allowed_color_modes";
+
+// A pref holding the list of allowed printing duplex mode as a bitmask composed
+// of |printing::DuplexModeRestriction| values. 0 is no restriction.
+const char kPrintingAllowedDuplexModes[] = "printing.allowed_duplex_modes";
+
+// A pref holding the allowed PIN printing modes.
+const char kPrintingAllowedPinModes[] = "printing.allowed_pin_modes";
+
+// A pref holding the allowed background graphics printing modes.
+const char kPrintingAllowedBackgroundGraphicsModes[] =
+ "printing.allowed_background_graphics_modes";
+
+// A pref holding the list of allowed printing duplex mode.
+// Empty list is no restriction.
+const char kPrintingAllowedPageSizes[] = "printing.allowed_page_sizes";
+
+// A pref holding the default color mode.
+const char kPrintingColorDefault[] = "printing.color_default";
+
+// A pref holding the default duplex mode.
+const char kPrintingDuplexDefault[] = "printing.duplex_default";
+
+// A pref holding the default PIN mode.
+const char kPrintingPinDefault[] = "printing.pin_default";
+
+// A pref holding the default background graphics mode.
+const char kPrintingBackgroundGraphicsDefault[] =
+ "printing.background_graphics_default";
+
+// A pref holding the default page size.
+const char kPrintingSizeDefault[] = "printing.size_default";
+
+// Boolean flag which represents whether username and filename should be sent
+// to print server.
+const char kPrintingSendUsernameAndFilenameEnabled[] =
+ "printing.send_username_and_filename_enabled";
+
+// Indicates how long print jobs metadata is stored on the device, in days.
+const char kPrintJobHistoryExpirationPeriod[] =
+ "printing.print_job_history_expiration_period";
+#endif // OS_CHROMEOS
+
+// An integer pref specifying the fallback behavior for sites outside of content
+// packs. One of:
+// 0: Allow (does nothing)
+// 1: Warn.
+// 2: Block.
+const char kDefaultSupervisedUserFilteringBehavior[] =
+ "profile.managed.default_filtering_behavior";
+
+// List pref containing the users supervised by this user.
+const char kSupervisedUsers[] = "profile.managed_users";
+
+// List pref containing the extension ids which are not allowed to send
+// notifications to the message center.
+const char kMessageCenterDisabledExtensionIds[] =
+ "message_center.disabled_extension_ids";
+
+// Boolean pref that determines whether the user can enter fullscreen mode.
+// Disabling fullscreen mode also makes kiosk mode unavailable on desktop
+// platforms.
+const char kFullscreenAllowed[] = "fullscreen.allowed";
+
+// Enable notifications for new devices on the local network that can be
+// registered to the user's account, e.g. Google Cloud Print printers.
+const char kLocalDiscoveryNotificationsEnabled[] =
+ "local_discovery.notifications_enabled";
+
+#if defined(OS_ANDROID)
+// Enable vibration for web notifications.
+const char kNotificationsVibrateEnabled[] = "notifications.vibrate_enabled";
+
+// Boolean pref indicating whether notification permissions were migrated to
+// notification channels (on Android O+ we use channels to store notification
+// permission, so any existing permissions must be migrated).
+const char kMigratedToSiteNotificationChannels[] =
+ "notifications.migrated_to_channels";
+
+// Boolean pref indicating whether blocked site notification channels underwent
+// a one-time reset yet for https://crbug.com/835232.
+// TODO(https://crbug.com/837614): Remove this after a few releases (M69?).
+const char kClearedBlockedSiteNotificationChannels[] =
+ "notifications.cleared_blocked_channels";
+
+// Usage stats reporting opt-in.
+const char kUsageStatsEnabled[] = "usage_stats_reporting.enabled";
+
+#endif // defined(OS_ANDROID)
+
+// Maps from app ids to origin + Service Worker registration ID.
+const char kPushMessagingAppIdentifierMap[] =
+ "gcm.push_messaging_application_id_map";
+
+// A string like "com.chrome.macosx" that should be used as the GCM category
+// when an app_id is sent as a subtype instead of as a category.
+const char kGCMProductCategoryForSubtypes[] =
+ "gcm.product_category_for_subtypes";
+
+// Whether a user is allowed to use Easy Unlock.
+const char kEasyUnlockAllowed[] = "easy_unlock.allowed";
+
+// Preference storing Easy Unlock pairing data.
+const char kEasyUnlockPairing[] = "easy_unlock.pairing";
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+// Used to indicate whether or not the toolbar redesign bubble has been shown
+// and acknowledged, and the last time the bubble was shown.
+const char kToolbarIconSurfacingBubbleAcknowledged[] =
+ "toolbar_icon_surfacing_bubble_acknowledged";
+const char kToolbarIconSurfacingBubbleLastShowTime[] =
+ "toolbar_icon_surfacing_bubble_show_time";
+#endif
+
+// Whether WebRTC should bind to individual NICs to explore all possible routing
+// options. Default is true. This has become obsoleted and replaced by
+// kWebRTCIPHandlingPolicy. TODO(guoweis): Remove this at M50.
+const char kWebRTCMultipleRoutesEnabled[] = "webrtc.multiple_routes_enabled";
+// Whether WebRTC should use non-proxied UDP. If false, WebRTC will not send UDP
+// unless it goes through a proxy (i.e RETURN when it's available). If no UDP
+// proxy is configured, it will not send UDP. If true, WebRTC will send UDP
+// regardless of whether or not a proxy is configured. TODO(guoweis): Remove
+// this at M50.
+const char kWebRTCNonProxiedUdpEnabled[] =
+ "webrtc.nonproxied_udp_enabled";
+// Define the IP handling policy override that WebRTC should follow. When not
+// set, it defaults to "default".
+const char kWebRTCIPHandlingPolicy[] = "webrtc.ip_handling_policy";
+// Define range of UDP ports allowed to be used by WebRTC PeerConnections.
+const char kWebRTCUDPPortRange[] = "webrtc.udp_port_range";
+// Whether WebRTC event log collection by Google domains is allowed.
+const char kWebRtcEventLogCollectionAllowed[] = "webrtc.event_logs_collection";
+// Holds URL patterns that specify URLs for which local IP addresses are exposed
+// in ICE candidates.
+const char kWebRtcLocalIpsAllowedUrls[] = "webrtc.local_ips_allowed_urls";
+
+#if !defined(OS_ANDROID)
+// Whether or not this profile has been shown the Welcome page.
+const char kHasSeenWelcomePage[] = "browser.has_seen_welcome_page";
+#endif
+
+#if defined(OS_WIN)
+// Put the user into an onboarding group that's decided when they go through
+// the first run onboarding experience. Only users in a group will have their
+// finch group pinged to keep track of them for the experiment.
+const char kNaviOnboardGroup[] = "browser.navi_onboard_group";
+#endif // defined(OS_WIN)
+
+// *************** LOCAL STATE ***************
+// These are attached to the machine/installation
+
+// Directory of the last profile used.
+const char kProfileLastUsed[] = "profile.last_used";
+
+// List of directories of the profiles last active.
+const char kProfilesLastActive[] = "profile.last_active_profiles";
+
+// Total number of profiles created for this Chrome build. Used to tag profile
+// directories.
+const char kProfilesNumCreated[] = "profile.profiles_created";
+
+// String containing the version of Chrome that the profile was created by.
+// If profile was created before this feature was added, this pref will default
+// to "1.0.0.0".
+const char kProfileCreatedByVersion[] = "profile.created_by_version";
+
+// A map of profile data directory to cached information. This cache can be
+// used to display information about profiles without actually having to load
+// them.
+const char kProfileInfoCache[] = "profile.info_cache";
+
+// A list of profile paths that should be deleted on shutdown. The deletion does
+// not happen if the browser crashes, so we remove the profile on next start.
+const char kProfilesDeleted[] = "profiles.profiles_deleted";
+
+// Deprecated preference for metric / crash reporting on Android. Use
+// kMetricsReportingEnabled instead.
+#if defined(OS_ANDROID)
+const char kCrashReportingEnabled[] =
+ "user_experience_metrics_crash.reporting_enabled";
+#endif // defined(OS_ANDROID)
+
+// This is the location of a list of dictionaries of plugin stability stats.
+const char kStabilityPluginStats[] =
+ "user_experience_metrics.stability.plugin_stats2";
+
+// On Chrome OS, total number of non-Chrome user process crashes
+// since the last report.
+const char kStabilityOtherUserCrashCount[] =
+ "user_experience_metrics.stability.other_user_crash_count";
+
+// On Chrome OS, total number of kernel crashes since the last report.
+const char kStabilityKernelCrashCount[] =
+ "user_experience_metrics.stability.kernel_crash_count";
+
+// On Chrome OS, total number of unclean system shutdowns since the
+// last report.
+const char kStabilitySystemUncleanShutdownCount[] =
+ "user_experience_metrics.stability.system_unclean_shutdowns";
+
+// The keys below are used for the dictionaries in the
+// kStabilityPluginStats list.
+const char kStabilityPluginName[] = "name";
+const char kStabilityPluginLaunches[] = "launches";
+const char kStabilityPluginInstances[] = "instances";
+const char kStabilityPluginCrashes[] = "crashes";
+const char kStabilityPluginLoadingErrors[] = "loading_errors";
+
+// String containing the version of Chrome for which Chrome will not prompt the
+// user about setting Chrome as the default browser.
+const char kBrowserSuppressDefaultBrowserPrompt[] =
+ "browser.suppress_default_browser_prompt_for_version";
+
+// A collection of position, size, and other data relating to the browser
+// window to restore on startup.
+const char kBrowserWindowPlacement[] = "browser.window_placement";
+
+// Browser window placement for popup windows.
+const char kBrowserWindowPlacementPopup[] = "browser.window_placement_popup";
+
+// A collection of position, size, and other data relating to the task
+// manager window to restore on startup.
+const char kTaskManagerWindowPlacement[] = "task_manager.window_placement";
+
+// The most recent stored column visibility of the task manager table to be
+// restored on startup.
+const char kTaskManagerColumnVisibility[] = "task_manager.column_visibility";
+
+// A boolean indicating if ending processes are enabled or disabled by policy.
+const char kTaskManagerEndProcessEnabled[] = "task_manager.end_process_enabled";
+
+// A collection of position, size, and other data relating to app windows to
+// restore on startup.
+const char kAppWindowPlacement[] = "browser.app_window_placement";
+
+// String which specifies where to download files to by default.
+const char kDownloadDefaultDirectory[] = "download.default_directory";
+
+// Boolean that records if the download directory was changed by an
+// upgrade a unsafe location to a safe location.
+const char kDownloadDirUpgraded[] = "download.directory_upgrade";
+
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
+const char kOpenPdfDownloadInSystemReader[] =
+ "download.open_pdf_in_system_reader";
+#endif
+
+#if defined(OS_ANDROID)
+// Int (as defined by DownloadPromptStatus) which specifies whether we should
+// ask the user where they want to download the file (only for Android).
+const char kPromptForDownloadAndroid[] = "download.prompt_for_download_android";
+
+// Boolean which specifies whether we should display the missing SD card error.
+// This is only applicable for Android.
+const char kShowMissingSdCardErrorAndroid[] =
+ "download.show_missing_sd_card_error_android";
+#endif
+
+// String which specifies where to save html files to by default.
+const char kSaveFileDefaultDirectory[] = "savefile.default_directory";
+
+// The type used to save the page. See the enum SavePackage::SavePackageType in
+// the chrome/browser/download/save_package.h for the possible values.
+const char kSaveFileType[] = "savefile.type";
+
+// String which specifies the last directory that was chosen for uploading
+// or opening a file.
+const char kSelectFileLastDirectory[] = "selectfile.last_directory";
+
+// Boolean that specifies if file selection dialogs are shown.
+const char kAllowFileSelectionDialogs[] = "select_file_dialogs.allowed";
+
+// Map of default tasks, associated by MIME type.
+const char kDefaultTasksByMimeType[] =
+ "filebrowser.tasks.default_by_mime_type";
+
+// Map of default tasks, associated by file suffix.
+const char kDefaultTasksBySuffix[] =
+ "filebrowser.tasks.default_by_suffix";
+
+// A flag to enable/disable the Shared Clipboard feature which enables users to
+// send text across devices.
+const char kSharedClipboardEnabled[] = "browser.shared_clipboard_enabled";
+
+#if BUILDFLAG(ENABLE_CLICK_TO_CALL)
+// A flag to enable/disable the Click to Call feature which enables users to
+// send phone numbers from desktop to Android phones.
+const char kClickToCallEnabled[] = "browser.click_to_call_enabled";
+#endif // BUILDFLAG(ENABLE_CLICK_TO_CALL)
+
+// Extensions which should be opened upon completion.
+const char kDownloadExtensionsToOpen[] = "download.extensions_to_open";
+
+// Dictionary of schemes used by the external protocol handler. If a scheme
+// is present with value |false|, the protocol may be launched without first
+// prompting the user.
+const char kExcludedSchemes[] = "protocol_handler.excluded_schemes";
+
+// String containing the last known intranet redirect URL, if any. See
+// intranet_redirect_detector.h for more information.
+const char kLastKnownIntranetRedirectOrigin[] = "browser.last_redirect_origin";
+
+// An enum value of how the browser was shut down (see browser_shutdown.h).
+const char kShutdownType[] = "shutdown.type";
+// Number of processes that were open when the user shut down.
+const char kShutdownNumProcesses[] = "shutdown.num_processes";
+// Number of processes that were shut down using the slow path.
+const char kShutdownNumProcessesSlow[] = "shutdown.num_processes_slow";
+
+// Whether to restart the current Chrome session automatically as the last thing
+// before shutting everything down.
+const char kRestartLastSessionOnShutdown[] = "restart.last.session.on.shutdown";
+
+#if !defined(OS_ANDROID)
+#if !defined(OS_CHROMEOS)
+// Pref name for the policy controlling presentation of full-tab promotional
+// and/or educational content.
+const char kPromotionalTabsEnabled[] = "browser.promotional_tabs_enabled";
+
+// Boolean that specifies whether or not to show security warnings for some
+// potentially bad command-line flags. True by default. Controlled by the
+// CommandLineFlagSecurityWarningsEnabled policy setting.
+const char kCommandLineFlagSecurityWarningsEnabled[] =
+ "browser.command_line_flag_security_warnings_enabled";
+#endif // !defined(OS_CHROMEOS)
+
+// Boolean that specifies whether or not showing the unsupported OS warning is
+// suppressed. False by default. Controlled by the SuppressUnsupportedOSWarning
+// policy setting.
+const char kSuppressUnsupportedOSWarning[] =
+ "browser.suppress_unsupported_os_warning";
+
+// Set before autorestarting Chrome, cleared on clean exit.
+const char kWasRestarted[] = "was.restarted";
+#endif // !defined(OS_ANDROID)
+
+// Whether Extensions are enabled.
+const char kDisableExtensions[] = "extensions.disabled";
+
+// Customized app page names that appear on the New Tab Page.
+const char kNtpAppPageNames[] = "ntp.app_page_names";
+
+// Keeps track of which sessions are collapsed in the Other Devices menu.
+const char kNtpCollapsedForeignSessions[] = "ntp.collapsed_foreign_sessions";
+
+#if defined(OS_ANDROID)
+// Keeps track of recently closed tabs collapsed state in the Other Devices
+// menu.
+const char kNtpCollapsedRecentlyClosedTabs[] =
+ "ntp.collapsed_recently_closed_tabs";
+
+// Keeps track of snapshot documents collapsed state in the Other Devices menu.
+const char kNtpCollapsedSnapshotDocument[] = "ntp.collapsed_snapshot_document";
+
+// Keeps track of sync promo collapsed state in the Other Devices menu.
+const char kNtpCollapsedSyncPromo[] = "ntp.collapsed_sync_promo";
+#else
+// Holds info for New Tab Page custom background
+const char kNtpCustomBackgroundDict[] = "ntp.custom_background_dict";
+const char kNtpCustomBackgroundLocalToDevice[] =
+ "ntp.custom_background_local_to_device";
+// List of promos that the user has dismissed while on the NTP.
+const char kNtpPromoBlocklist[] = "ntp.promo_blocklist";
+// Data associated with search suggestions that appear on the NTP.
+const char kNtpSearchSuggestionsBlocklist[] =
+ "ntp.search_suggestions_blocklist";
+const char kNtpSearchSuggestionsImpressions[] =
+ "ntp.search_suggestions_impressions";
+const char kNtpSearchSuggestionsOptOut[] = "ntp.search_suggestions_opt_out";
+// Tracks whether the user has chosen to hide the shortcuts tiles on the NTP.
+const char kNtpShortcutsVisible[] = "ntp.shortcust_visible";
+// Tracks whether the user has chosen to use custom links or most visited sites
+// for the shortcut tiles on the NTP.
+const char kNtpUseMostVisitedTiles[] = "ntp.use_most_visited_tiles";
+#endif // defined(OS_ANDROID)
+
+// Which page should be visible on the new tab page v4
+const char kNtpShownPage[] = "ntp.shown_page";
+
+// A private RSA key for ADB handshake.
+const char kDevToolsAdbKey[] = "devtools.adb_key";
+
+// Defines administrator-set availability of developer tools.
+const char kDevToolsAvailability[] = "devtools.availability";
+
+// Dictionary from background service to recording expiration time.
+const char kDevToolsBackgroundServicesExpirationDict[] =
+ "devtools.backgroundserviceexpiration";
+
+// Determines whether devtools should be discovering usb devices for
+// remote debugging at chrome://inspect.
+const char kDevToolsDiscoverUsbDevicesEnabled[] =
+ "devtools.discover_usb_devices";
+
+// Maps of files edited locally using DevTools.
+const char kDevToolsEditedFiles[] = "devtools.edited_files";
+
+// List of file system paths added in DevTools.
+const char kDevToolsFileSystemPaths[] = "devtools.file_system_paths";
+
+// A boolean specifying whether port forwarding should be enabled.
+const char kDevToolsPortForwardingEnabled[] =
+ "devtools.port_forwarding_enabled";
+
+// A boolean specifying whether default port forwarding configuration has been
+// set.
+const char kDevToolsPortForwardingDefaultSet[] =
+ "devtools.port_forwarding_default_set";
+
+// A dictionary of port->location pairs for port forwarding.
+const char kDevToolsPortForwardingConfig[] = "devtools.port_forwarding_config";
+
+// A boolean specifying whether or not Chrome will scan for available remote
+// debugging targets.
+const char kDevToolsDiscoverTCPTargetsEnabled[] =
+ "devtools.discover_tcp_targets";
+
+// A list of strings representing devtools target discovery servers.
+const char kDevToolsTCPDiscoveryConfig[] = "devtools.tcp_discovery_config";
+
+// A dictionary with generic DevTools settings.
+const char kDevToolsPreferences[] = "devtools.preferences";
+
+#if !defined(OS_ANDROID)
+// Tracks the number of times the dice signin promo has been shown in the user
+// menu.
+const char kDiceSigninUserMenuPromoCount[] = "sync_promo.user_menu_show_count";
+#endif
+
+// Create web application shortcut dialog preferences.
+const char kWebAppCreateOnDesktop[] = "browser.web_app.create_on_desktop";
+const char kWebAppCreateInAppsMenu[] = "browser.web_app.create_in_apps_menu";
+const char kWebAppCreateInQuickLaunchBar[] =
+ "browser.web_app.create_in_quick_launch_bar";
+
+// A list of dictionaries for force-installed Web Apps. Each dictionary contains
+// two strings: the URL of the Web App and "tab" or "window" for where the app
+// will be launched.
+const char kWebAppInstallForceList[] = "profile.web_app.install.forcelist";
+
+// Dictionary that maps web app ids to installation metrics used by UMA.
+const char kWebAppInstallMetrics[] = "web_app_install_metrics";
+
+// Dictionary that maps web app URLs to Chrome extension IDs.
+const char kWebAppsExtensionIDs[] = "web_apps.extension_ids";
+
+// A string representing the last version of Chrome that System Web Apps were
+// updated for.
+const char kSystemWebAppLastUpdateVersion[] =
+ "web_apps.system_web_app_last_update";
+
+// The default audio capture device used by the Media content setting.
+const char kDefaultAudioCaptureDevice[] = "media.default_audio_capture_device";
+
+// The default video capture device used by the Media content setting.
+const char kDefaultVideoCaptureDevice[] = "media.default_video_capture_Device";
+
+// The salt used for creating random MediaSource IDs.
+const char kMediaDeviceIdSalt[] = "media.device_id_salt";
+
+// The salt used for creating Storage IDs. The Storage ID is used by encrypted
+// media to bind persistent licenses to the device which is authorized to play
+// the content.
+const char kMediaStorageIdSalt[] = "media.storage_id_salt";
+
+// The last used printer and its settings.
+const char kPrintPreviewStickySettings[] =
+ "printing.print_preview_sticky_settings";
+
+// The list of BackgroundContents that should be loaded when the browser
+// launches.
+const char kRegisteredBackgroundContents[] = "background_contents.registered";
+
+// Integer that specifies the total memory usage, in mb, that chrome will
+// attempt to stay under. Can be specified via policy in addition to the default
+// memory pressure rules applied per platform.
+const char kTotalMemoryLimitMb[] = "total_memory_limit_mb";
+
+// String that lists supported HTTP authentication schemes.
+const char kAuthSchemes[] = "auth.schemes";
+
+// Boolean that specifies whether to disable CNAME lookups when generating
+// Kerberos SPN.
+const char kDisableAuthNegotiateCnameLookup[] =
+ "auth.disable_negotiate_cname_lookup";
+
+// Boolean that specifies whether to include the port in a generated Kerberos
+// SPN.
+const char kEnableAuthNegotiatePort[] = "auth.enable_negotiate_port";
+
+// Whitelist containing servers for which Integrated Authentication is enabled.
+const char kAuthServerWhitelist[] = "auth.server_whitelist";
+
+// Whitelist containing servers Chrome is allowed to do Kerberos delegation
+// with.
+const char kAuthNegotiateDelegateWhitelist[] =
+ "auth.negotiate_delegate_whitelist";
+
+// String that specifies the name of a custom GSSAPI library to load.
+const char kGSSAPILibraryName[] = "auth.gssapi_library_name";
+
+// String that specifies the Android account type to use for Negotiate
+// authentication.
+const char kAuthAndroidNegotiateAccountType[] =
+ "auth.android_negotiate_account_type";
+
+// Boolean that specifies whether to allow basic auth prompting on cross-
+// domain sub-content requests.
+const char kAllowCrossOriginAuthPrompt[] = "auth.allow_cross_origin_prompt";
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+// Boolean that specifies whether OK-AS-DELEGATE flag from KDC is respected
+// along with kAuthNegotiateDelegateWhitelist.
+const char kAuthNegotiateDelegateByKdcPolicy[] =
+ "auth.negotiate_delegate_by_kdc_policy";
+#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+
+#if defined(OS_POSIX)
+// Boolean that specifies whether NTLMv2 is enabled.
+const char kNtlmV2Enabled[] = "auth.ntlm_v2_enabled";
+#endif // defined(OS_POSIX)
+
+#if defined(OS_CHROMEOS)
+// Boolean whether Kerberos functionality is enabled.
+const char kKerberosEnabled[] = "kerberos.enabled";
+#endif
+
+// Boolean that specifies whether to enable revocation checking (best effort)
+// by default.
+const char kCertRevocationCheckingEnabled[] = "ssl.rev_checking.enabled";
+
+// Boolean that specifies whether to require a successful revocation check if
+// a certificate path ends in a locally-trusted (as opposed to publicly
+// trusted) trust anchor.
+const char kCertRevocationCheckingRequiredLocalAnchors[] =
+ "ssl.rev_checking.required_for_local_anchors";
+
+// String specifying the minimum TLS version to negotiate. Supported values
+// are "tls1", "tls1.1", "tls1.2", "tls1.3".
+const char kSSLVersionMin[] = "ssl.version_min";
+
+// String specifying the maximum TLS version to negotiate. Supported values
+// are "tls1.2", "tls1.3"
+const char kSSLVersionMax[] = "ssl.version_max";
+
+// String specifying the TLS ciphersuites to disable. Ciphersuites are
+// specified as a comma-separated list of 16-bit hexadecimal values, with
+// the values being the ciphersuites assigned by the IANA registry (e.g.
+// "0x0004,0x0005").
+const char kCipherSuiteBlacklist[] = "ssl.cipher_suites.blacklist";
+
+// List of strings specifying which hosts are allowed to have H2 connections
+// coalesced when client certs are also used. This follows rules similar to
+// the URLBlacklist format for hostnames: a pattern with a leading dot (e.g.
+// ".example.net") matches exactly the hostname following the dot (i.e. only
+// "example.net"), and a pattern with no leading dot (e.g. "example.com")
+// matches that hostname and all subdomains.
+const char kH2ClientCertCoalescingHosts[] =
+ "ssl.client_certs.h2_coalescing_hosts";
+
+// List of single-label hostnames that will skip the check to possibly upgrade
+// from http to https.
+const char kHSTSPolicyBypassList[] = "hsts.policy.upgrade_bypass_list";
+
+// If true, enables stronger TLS 1.3 downgrade protection for connections using
+// local trust anchors.
+const char kTLS13HardeningForLocalAnchorsEnabled[] =
+ "ssl.tls13_hardening_for_local_anchors";
+
+// Boolean that specifies whether the built-in asynchronous DNS client is used.
+const char kBuiltInDnsClientEnabled[] = "async_dns.enabled";
+
+// String specifying the secure DNS mode to use. Any string other than
+// "secure" or "automatic" will be mapped to the default "off" mode.
+const char kDnsOverHttpsMode[] = "dns_over_https.mode";
+// String containing a space-separated list of DNS over HTTPS templates to use
+// in secure mode or automatic mode. If no templates are specified in automatic
+// mode, we will attempt discovery of DoH servers associated with the configured
+// insecure resolvers.
+const char kDnsOverHttpsTemplates[] = "dns_over_https.templates";
+
+// A pref holding the value of the policy used to explicitly allow or deny
+// access to audio capture devices. When enabled or not set, the user is
+// prompted for device access. When disabled, access to audio capture devices
+// is not allowed and no prompt will be shown.
+// See also kAudioCaptureAllowedUrls.
+const char kAudioCaptureAllowed[] = "hardware.audio_capture_enabled";
+// Holds URL patterns that specify URLs that will be granted access to audio
+// capture devices without prompt.
+const char kAudioCaptureAllowedUrls[] = "hardware.audio_capture_allowed_urls";
+
+// A pref holding the value of the policy used to explicitly allow or deny
+// access to video capture devices. When enabled or not set, the user is
+// prompted for device access. When disabled, access to video capture devices
+// is not allowed and no prompt will be shown.
+const char kVideoCaptureAllowed[] = "hardware.video_capture_enabled";
+// Holds URL patterns that specify URLs that will be granted access to video
+// capture devices without prompt.
+const char kVideoCaptureAllowedUrls[] = "hardware.video_capture_allowed_urls";
+
+#if defined(OS_CHROMEOS)
+// An integer pref that holds enum value of current demo mode configuration.
+// Values are defined by DemoSession::DemoModeConfig enum.
+const char kDemoModeConfig[] = "demo_mode.config";
+
+// A string pref holding the value of the current country for demo sessions.
+const char kDemoModeCountry[] = "demo_mode.country";
+
+// A string pref holding the value of the default locale for demo sessions.
+const char kDemoModeDefaultLocale[] = "demo_mode.default_locale";
+
+// Dictionary for transient storage of settings that should go into device
+// settings storage before owner has been assigned.
+const char kDeviceSettingsCache[] = "signed_settings_cache";
+
+// The hardware keyboard layout of the device. This should look like
+// "xkb:us::eng".
+const char kHardwareKeyboardLayout[] = "intl.hardware_keyboard";
+
+// An integer pref which shows number of times carrier deal promo
+// notification has been shown to user.
+const char kCarrierDealPromoShown[] =
+ "settings.internet.mobile.carrier_deal_promo_shown";
+
+// A boolean pref of the auto-enrollment decision. Its value is only valid if
+// it's not the default value; otherwise, no auto-enrollment decision has been
+// made yet.
+const char kShouldAutoEnroll[] = "ShouldAutoEnroll";
+
+// An integer pref with the maximum number of bits used by the client in a
+// previous auto-enrollment request. If the client goes through an auto update
+// during OOBE and reboots into a version of the OS with a larger maximum
+// modulus, then it will retry auto-enrollment using the updated value.
+const char kAutoEnrollmentPowerLimit[] = "AutoEnrollmentPowerLimit";
+
+// The local state pref that stores device activity times before reporting
+// them to the policy server.
+const char kDeviceActivityTimes[] = "device_status.activity_times";
+
+// A pref that stores user activity times before reporting them to the policy
+// server.
+const char kUserActivityTimes[] = "consumer_device_status.activity_times";
+
+// A pref holding the value of the policy used to disable mounting of external
+// storage for the user.
+const char kExternalStorageDisabled[] = "hardware.external_storage_disabled";
+
+// A pref holding the value of the policy used to limit mounting of external
+// storage to read-only mode for the user.
+const char kExternalStorageReadOnly[] = "hardware.external_storage_read_only";
+
+// Copy of owner swap mouse buttons option to use on login screen.
+const char kOwnerPrimaryMouseButtonRight[] = "owner.mouse.primary_right";
+
+// Copy of owner tap-to-click option to use on login screen.
+const char kOwnerTapToClickEnabled[] = "owner.touchpad.enable_tap_to_click";
+
+// The length of device uptime after which an automatic reboot is scheduled,
+// expressed in seconds.
+const char kUptimeLimit[] = "automatic_reboot.uptime_limit";
+
+// Whether an automatic reboot should be scheduled when an update has been
+// applied and a reboot is required to complete the update process.
+const char kRebootAfterUpdate[] = "automatic_reboot.reboot_after_update";
+
+// An any-api scoped refresh token for enterprise-enrolled devices. Allows
+// for connection to Google APIs when the user isn't logged in. Currently used
+// for for getting a cloudprint scoped token to allow printing in Guest mode,
+// Public Accounts and kiosks.
+const char kDeviceRobotAnyApiRefreshToken[] =
+ "device_robot_refresh_token.any-api";
+
+// Device requisition for enterprise enrollment.
+const char kDeviceEnrollmentRequisition[] = "enrollment.device_requisition";
+
+// Sub organization for enterprise enrollment.
+const char kDeviceEnrollmentSubOrganization[] = "enrollment.sub_organization";
+
+// Whether to automatically start the enterprise enrollment step during OOBE.
+const char kDeviceEnrollmentAutoStart[] = "enrollment.auto_start";
+
+// Whether the user may exit enrollment.
+const char kDeviceEnrollmentCanExit[] = "enrollment.can_exit";
+
+// DM token fetched from the DM server during enrollment. Stored for Active
+// Directory devices only.
+const char kDeviceDMToken[] = "device_dm_token";
+
+// How many times HID detection OOBE dialog was shown.
+const char kTimesHIDDialogShown[] = "HIDDialog.shown_how_many_times";
+
+// Dictionary of per-user last input method (used at login screen). Note that
+// the pref name is UsersLRUInputMethods for compatibility with previous
+// versions.
+const char kUsersLastInputMethod[] = "UsersLRUInputMethod";
+
+// A dictionary pref of the echo offer check flag. It sets offer info when
+// an offer is checked.
+const char kEchoCheckedOffers[] = "EchoCheckedOffers";
+
+// Key name of a dictionary in local state to store cached multiprofle user
+// behavior policy value.
+const char kCachedMultiProfileUserBehavior[] = "CachedMultiProfileUserBehavior";
+
+// A string pref with initial locale set in VPD or manifest.
+const char kInitialLocale[] = "intl.initial_locale";
+
+// A boolean pref of the OOBE complete flag (first OOBE part before login).
+const char kOobeComplete[] = "OobeComplete";
+
+// The name of the screen that has to be shown if OOBE has been interrupted.
+const char kOobeScreenPending[] = "OobeScreenPending";
+
+
+// A boolean pref to indicate if the marketing opt-in screen in OOBE is finished
+// for the user.
+const char kOobeMarketingOptInScreenFinished[] =
+ "OobeMarketingOptInScreenFinished";
+
+// A boolean pref for whether the Goodies promotion webpage has been displayed,
+// or otherwise disqualified for auto-display, on this device.
+const char kCanShowOobeGoodiesPage[] = "CanShowOobeGoodiesPage";
+
+// A boolean pref of the device registered flag (second part after first login).
+const char kDeviceRegistered[] = "DeviceRegistered";
+
+// Boolean pref to signal corrupted enrollment to force the device through
+// enrollment recovery flow upon next boot.
+const char kEnrollmentRecoveryRequired[] = "EnrollmentRecoveryRequired";
+
+// List of usernames that used certificates pushed by policy before.
+// This is used to prevent these users from joining multiprofile sessions.
+const char kUsedPolicyCertificates[] = "policy.used_policy_certificates";
+
+// A dictionary containing server-provided device state pulled form the cloud
+// after recovery.
+const char kServerBackedDeviceState[] = "server_backed_device_state";
+
+// Customized wallpaper URL, which is already downloaded and scaled.
+// The URL from this preference must never be fetched. It is compared to the
+// URL from customization document to check if wallpaper URL has changed
+// since wallpaper was cached.
+const char kCustomizationDefaultWallpaperURL[] =
+ "customization.default_wallpaper_url";
+
+// System uptime, when last logout started.
+// This is saved to file and cleared after chrome process starts.
+const char kLogoutStartedLast[] = "chromeos.logout-started";
+
+
+// A boolean preference controlling Android status reporting.
+const char kReportArcStatusEnabled[] = "arc.status_reporting_enabled";
+
+// A string preference indicating the name of the OS level task scheduler
+// configuration to use.
+const char kSchedulerConfiguration[] = "chromeos.scheduler_configuration";
+
+// Dictionary indicating current network bandwidth throttling settings.
+// Contains a boolean (is throttling enabled) and two integers (upload rate
+// and download rate in kbits/s to throttle to)
+const char kNetworkThrottlingEnabled[] = "net.throttling_enabled";
+
+// Integer pref used by the metrics::DailyEvent owned by
+// chromeos::PowerMetricsReporter.
+const char kPowerMetricsDailySample[] = "power.metrics.daily_sample";
+
+// Integer prefs used to back event counts reported by
+// chromeos::PowerMetricsReporter.
+const char kPowerMetricsIdleScreenDimCount[] =
+ "power.metrics.idle_screen_dim_count";
+const char kPowerMetricsIdleScreenOffCount[] =
+ "power.metrics.idle_screen_off_count";
+const char kPowerMetricsIdleSuspendCount[] = "power.metrics.idle_suspend_count";
+const char kPowerMetricsLidClosedSuspendCount[] =
+ "power.metrics.lid_closed_suspend_count";
+
+// Key for list of users that should be reported.
+const char kReportingUsers[] = "reporting_users";
+
+// Whether to log events for Android app installs.
+const char kArcAppInstallEventLoggingEnabled[] =
+ "arc.app_install_event_logging_enabled";
+
+// Whether we received the remove users remote command, and hence should proceed
+// with removing the users while at the login screen.
+const char kRemoveUsersRemoteCommand[] = "remove_users_remote_command";
+
+// Whether camera-produced media files have been consolidated to one place.
+const char kCameraMediaConsolidated[] = "camera_media_consolidated";
+
+// Integer pref used by the metrics::DailyEvent owned by
+// chromeos::power::auto_screen_brightness::MetricsReporter.
+const char kAutoScreenBrightnessMetricsDailySample[] =
+ "auto_screen_brightness.metrics.daily_sample";
+
+// Integer prefs used to back event counts reported by
+// chromeos::power::auto_screen_brightness::MetricsReporter.
+const char kAutoScreenBrightnessMetricsAtlasUserAdjustmentCount[] =
+ "auto_screen_brightness.metrics.atlas_user_adjustment_count";
+const char kAutoScreenBrightnessMetricsEveUserAdjustmentCount[] =
+ "auto_screen_brightness.metrics.eve_user_adjustment_count";
+const char kAutoScreenBrightnessMetricsNocturneUserAdjustmentCount[] =
+ "auto_screen_brightness.metrics.nocturne_user_adjustment_count";
+const char kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount[] =
+ "auto_screen_brightness.metrics.no_als_user_adjustment_count";
+const char kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount[] =
+ "auto_screen_brightness.metrics.supported_als_user_adjustment_count";
+const char kAutoScreenBrightnessMetricsUnsupportedAlsUserAdjustmentCount[] =
+ "auto_screen_brightness.metrics.unsupported_als_user_adjustment_count";
+
+// Dictionary pref containing the configuration used to verify Parent Access
+// Code. The data is sent through the ParentAccessCodeConfig policy, which is
+// set for child users only, and kept on the known user storage.
+const char kKnownUserParentAccessCodeConfig[] =
+ "child_user.parent_access_code.config";
+
+// Enable chrome://password-change page for in-session change of SAML passwords.
+// Also enables SAML password expiry notifications, if we have that information.
+const char kSamlInSessionPasswordChangeEnabled[] =
+ "saml.in_session_password_change_enabled";
+// The number of days in advance to notify the user that their SAML password
+// will expire (works when kSamlInSessionPasswordChangeEnabled is true).
+const char kSamlPasswordExpirationAdvanceWarningDays[] =
+ "saml.password_expiration_advance_warning_days";
+
+#endif // defined(OS_CHROMEOS)
+
+// Whether there is a Flash version installed that supports clearing LSO data.
+const char kClearPluginLSODataEnabled[] = "browser.clear_lso_data_enabled";
+
+// Whether we should show Pepper Flash-specific settings.
+const char kPepperFlashSettingsEnabled[] =
+ "browser.pepper_flash_settings_enabled";
+
+// String which specifies where to store the disk cache.
+const char kDiskCacheDir[] = "browser.disk_cache_dir";
+// Pref name for the policy specifying the maximal cache size.
+const char kDiskCacheSize[] = "browser.disk_cache_size";
+
+// Specifies the release channel that the device should be locked to.
+// Possible values: "stable-channel", "beta-channel", "dev-channel", or an
+// empty string, in which case the value will be ignored.
+// TODO(dubroy): This preference may not be necessary once
+// http://crosbug.com/17015 is implemented and the update engine can just
+// fetch the correct value from the policy.
+const char kChromeOsReleaseChannel[] = "cros.system.releaseChannel";
+
+const char kPerformanceTracingEnabled[] =
+ "feedback.performance_tracing_enabled";
+
+// Boolean indicating whether tabstrip uses stacked layout (on touch devices).
+// Defaults to false.
+const char kTabStripStackedLayout[] = "tab-strip-stacked-layout";
+
+// Indicates that factory reset was requested from options page or reset screen.
+const char kFactoryResetRequested[] = "FactoryResetRequested";
+
+// Presence of this value indicates that a TPM firmware update has been
+// requested. The value indicates the requested update mode.
+const char kFactoryResetTPMFirmwareUpdateMode[] =
+ "FactoryResetTPMFirmwareUpdateMode";
+
+// Indicates that debugging features were requested from oobe screen.
+const char kDebuggingFeaturesRequested[] = "DebuggingFeaturesRequested";
+
+#if defined(OS_CHROMEOS)
+// This setting controls initial device timezone that is used before user
+// session started. It is controlled by device owner.
+const char kSigninScreenTimezone[] = "settings.signin_screen_timezone";
+
+// This setting starts periodic timezone refresh when not in user session.
+// (user session is controlled by user profile preference
+// kResolveTimezoneByGeolocation)
+//
+// Deprecated. Superseeded by kResolveDeviceTimezoneByGeolocationMethod.
+// TODO(alemate): https://crbug.com/783367 Remove outdated prefs.
+const char kResolveDeviceTimezoneByGeolocation[] =
+ "settings.resolve_device_timezone_by_geolocation";
+
+// This setting controls what information is sent to the server to get
+// device location to resolve time zone outside of user session. Values must
+// match TimeZoneResolverManager::TimeZoneResolveMethod enum.
+const char kResolveDeviceTimezoneByGeolocationMethod[] =
+ "settings.resolve_device_timezone_by_geolocation_method";
+
+// This is policy-controlled preference.
+// It has values defined in policy enum
+// SystemTimezoneAutomaticDetectionProto_AutomaticTimezoneDetectionType;
+const char kSystemTimezoneAutomaticDetectionPolicy[] =
+ "settings.resolve_device_timezone_by_geolocation_policy";
+#endif // defined(OS_CHROMEOS)
+
+// Pref name for the policy controlling whether to enable Media Router.
+const char kEnableMediaRouter[] = "media_router.enable_media_router";
+#if !defined(OS_ANDROID)
+// Pref name for the policy controlling whether to force the Cast icon to be
+// shown in the toolbar/overflow menu.
+const char kShowCastIconInToolbar[] = "media_router.show_cast_icon_in_toolbar";
+#endif // !defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+// Pref name for the policy controlling the way in which users are notified of
+// the need to relaunch the browser for a pending update.
+const char kRelaunchNotification[] = "browser.relaunch_notification";
+// Pref name for the policy controlling the time period over which users are
+// notified of the need to relaunch the browser for a pending update. Values
+// are in milliseconds.
+const char kRelaunchNotificationPeriod[] =
+ "browser.relaunch_notification_period";
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+// Pref name for the policy controlling the time period between the first user
+// notification about need to relaunch and the end of the
+// RelaunchNotificationPeriod. Values are in milliseconds.
+const char kRelaunchHeadsUpPeriod[] = "browser.relaunch_heads_up_period";
+#endif // defined(OS_CHROMEOS)
+
+// *************** SERVICE PREFS ***************
+// These are attached to the service process.
+
+const char kCloudPrintRoot[] = "cloud_print";
+const char kCloudPrintProxyEnabled[] = "cloud_print.enabled";
+// The unique id for this instance of the cloud print proxy.
+const char kCloudPrintProxyId[] = "cloud_print.proxy_id";
+// The GAIA auth token for Cloud Print
+const char kCloudPrintAuthToken[] = "cloud_print.auth_token";
+// The email address of the account used to authenticate with the Cloud Print
+// server.
+const char kCloudPrintEmail[] = "cloud_print.email";
+// Settings specific to underlying print system.
+const char kCloudPrintPrintSystemSettings[] =
+ "cloud_print.print_system_settings";
+// A boolean indicating whether we should poll for print jobs when don't have
+// an XMPP connection (false by default).
+const char kCloudPrintEnableJobPoll[] = "cloud_print.enable_job_poll";
+const char kCloudPrintRobotRefreshToken[] = "cloud_print.robot_refresh_token";
+const char kCloudPrintRobotEmail[] = "cloud_print.robot_email";
+// A boolean indicating whether we should connect to cloud print new printers.
+const char kCloudPrintConnectNewPrinters[] =
+ "cloud_print.user_settings.connectNewPrinters";
+// A boolean indicating whether we should ping XMPP connection.
+const char kCloudPrintXmppPingEnabled[] = "cloud_print.xmpp_ping_enabled";
+// An int value indicating the average timeout between xmpp pings.
+const char kCloudPrintXmppPingTimeout[] = "cloud_print.xmpp_ping_timeout_sec";
+// Dictionary with settings stored by connector setup page.
+const char kCloudPrintUserSettings[] = "cloud_print.user_settings";
+// List of printers settings.
+const char kCloudPrintPrinters[] = "cloud_print.user_settings.printers";
+// A boolean indicating whether submitting jobs to Google Cloud Print is
+// blocked by policy.
+const char kCloudPrintSubmitEnabled[] = "cloud_print.submit_enabled";
+
+// Preference to store proxy settings.
+const char kMaxConnectionsPerProxy[] = "net.max_connections_per_proxy";
+
+#if defined(OS_MACOSX)
+// Set to true if the user removed our login item so we should not create a new
+// one when uninstalling background apps.
+const char kUserRemovedLoginItem[] = "background_mode.user_removed_login_item";
+
+// Set to true if Chrome already created a login item, so there's no need to
+// create another one.
+const char kChromeCreatedLoginItem[] =
+ "background_mode.chrome_created_login_item";
+
+// Set to true once we've initialized kChromeCreatedLoginItem for the first
+// time.
+const char kMigratedLoginItemPref[] =
+ "background_mode.migrated_login_item_pref";
+
+// A boolean that tracks whether to show a notification when trying to quit
+// while there are apps running.
+const char kNotifyWhenAppsKeepChromeAlive[] =
+ "apps.notify-when-apps-keep-chrome-alive";
+#endif
+
+// Set to true if background mode is enabled on this browser.
+const char kBackgroundModeEnabled[] = "background_mode.enabled";
+
+// Set to true if hardware acceleration mode is enabled on this browser.
+const char kHardwareAccelerationModeEnabled[] =
+ "hardware_acceleration_mode.enabled";
+
+// Hardware acceleration mode from previous browser launch.
+const char kHardwareAccelerationModePrevious[] =
+ "hardware_acceleration_mode_previous";
+
+// List of protocol handlers.
+const char kRegisteredProtocolHandlers[] =
+ "custom_handlers.registered_protocol_handlers";
+
+// List of protocol handlers the user has requested not to be asked about again.
+const char kIgnoredProtocolHandlers[] =
+ "custom_handlers.ignored_protocol_handlers";
+
+// List of protocol handlers registered by policy.
+const char kPolicyRegisteredProtocolHandlers[] =
+ "custom_handlers.policy.registered_protocol_handlers";
+
+// List of protocol handlers the policy has requested to be ignored.
+const char kPolicyIgnoredProtocolHandlers[] =
+ "custom_handlers.policy.ignored_protocol_handlers";
+
+// Whether user-specified handlers for protocols and content types can be
+// specified.
+const char kCustomHandlersEnabled[] = "custom_handlers.enabled";
+
+// Integer that specifies the policy refresh rate for device-policy in
+// milliseconds. Not all values are meaningful, so it is clamped to a sane range
+// by the cloud policy subsystem.
+const char kDevicePolicyRefreshRate[] = "policy.device_refresh_rate";
+
+#if !defined(OS_ANDROID)
+// A boolean where true means that the browser has previously attempted to
+// enable autoupdate and failed, so the next out-of-date browser start should
+// not prompt the user to enable autoupdate, it should offer to reinstall Chrome
+// instead.
+const char kAttemptedToEnableAutoupdate[] =
+ "browser.attempted_to_enable_autoupdate";
+
+// The next media gallery ID to assign.
+const char kMediaGalleriesUniqueId[] = "media_galleries.gallery_id";
+
+// A list of dictionaries, where each dictionary represents a known media
+// gallery.
+const char kMediaGalleriesRememberedGalleries[] =
+ "media_galleries.remembered_galleries";
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+const char kPolicyPinnedLauncherApps[] = "policy_pinned_launcher_apps";
+// Keeps names of rolled default pin layouts for shelf in order not to apply
+// this twice. Names are separated by comma.
+const char kShelfDefaultPinLayoutRolls[] = "shelf_default_pin_layout_rolls";
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_WIN)
+// Counts how many more times the 'profile on a network share' warning should be
+// shown to the user before the next silence period.
+const char kNetworkProfileWarningsLeft[] = "network_profile.warnings_left";
+// Tracks the time of the last shown warning. Used to reset
+// |network_profile.warnings_left| after a silence period.
+const char kNetworkProfileLastWarningTime[] =
+ "network_profile.last_warning_time";
+#endif
+
+#if defined(OS_CHROMEOS)
+// The RLZ brand code, if enabled.
+const char kRLZBrand[] = "rlz.brand";
+// Whether RLZ pings are disabled.
+const char kRLZDisabled[] = "rlz.disabled";
+#endif
+
+#if BUILDFLAG(ENABLE_APP_LIST)
+// Keeps local state of app list while sync service is not available.
+const char kAppListLocalState[] = "app_list.local_state";
+#endif
+
+// An integer that is incremented whenever changes are made to app shortcuts.
+// Increasing this causes all app shortcuts to be recreated.
+const char kAppShortcutsVersion[] = "apps.shortcuts_version";
+
+// A string pref for storing the salt used to compute the pepper device ID.
+const char kDRMSalt[] = "settings.privacy.drm_salt";
+// A boolean pref that enables the (private) pepper GetDeviceID() call and
+// enables the use of remote attestation for content protection.
+const char kEnableDRM[] = "settings.privacy.drm_enabled";
+
+// An integer per-profile pref that signals if the watchdog extension is
+// installed and active. We need to know if the watchdog extension active for
+// ActivityLog initialization before the extension system is initialized.
+const char kWatchdogExtensionActive[] =
+ "profile.extensions.activity_log.num_consumers_active";
+
+#if defined(OS_ANDROID)
+// A list of partner bookmark rename/remove mappings.
+// Each list item is a dictionary containing a "url", a "provider_title" and
+// "mapped_title" entries, detailing the bookmark target URL (if any), the title
+// given by the PartnerBookmarksProvider and either the user-visible renamed
+// title or an empty string if the bookmark node was removed.
+const char kPartnerBookmarkMappings[] = "partnerbookmarks.mappings";
+#endif // defined(OS_ANDROID)
+
+// Whether DNS Quick Check is disabled in proxy resolution.
+//
+// This is a performance optimization for WPAD (Web Proxy
+// Auto-Discovery) which places a 1 second timeout on resolving the
+// DNS for PAC script URLs.
+//
+// It is on by default, but can be disabled via the Policy option
+// "WPADQuickCheckEnbled". There is no other UI for changing this
+// preference.
+//
+// For instance, if the DNS resolution for 'wpad' takes longer than 1
+// second, auto-detection will give up and fallback to the next proxy
+// configuration (which could be manually configured proxy server
+// rules, or an implicit fallback to DIRECT connections).
+const char kQuickCheckEnabled[] = "proxy.quick_check_enabled";
+
+// Whether Guest Mode is enabled within the browser.
+const char kBrowserGuestModeEnabled[] = "profile.browser_guest_enabled";
+
+// Whether Guest Mode is enforced within the browser.
+const char kBrowserGuestModeEnforced[] = "profile.browser_guest_enforced";
+
+// Whether Adding a new Person is enabled within the user manager.
+const char kBrowserAddPersonEnabled[] = "profile.add_person_enabled";
+
+// Whether profile can be used before sign in.
+const char kForceBrowserSignin[] = "profile.force_browser_signin";
+
+// Boolean which indicates if the user is allowed to sign into Chrome on the
+// next startup.
+const char kSigninAllowedOnNextStartup[] = "signin.allowed_on_next_startup";
+
+// Device identifier used by CryptAuth stored in local state. This ID is
+// combined with a user ID before being registered with the CryptAuth server,
+// so it can't correlate users on the same device.
+// Note: This constant was previously specific to EasyUnlock, so the string
+// constant contains "easy_unlock".
+const char kCryptAuthDeviceId[] = "easy_unlock.device_id";
+
+// A dictionary that maps user id to hardlock state.
+const char kEasyUnlockHardlockState[] = "easy_unlock.hardlock_state";
+
+// A dictionary that maps user id to public part of RSA key pair used by
+// Easy Sign-in for the user.
+const char kEasyUnlockLocalStateTpmKeys[] = "easy_unlock.public_tpm_keys";
+
+// A dictionary in local state containing each user's Easy Unlock profile
+// preferences, so they can be accessed outside of the user's profile. The value
+// is a dictionary containing an entry for each user. Each user's entry mirrors
+// their profile's Easy Unlock preferences.
+const char kEasyUnlockLocalStateUserPrefs[] = "easy_unlock.user_prefs";
+
+// Boolean that indicates whether elevation is needed to recover Chrome upgrade.
+const char kRecoveryComponentNeedsElevation[] =
+ "recovery_component.needs_elevation";
+
+// A dictionary that maps from supervised user whitelist IDs to their properties
+// (name and a list of clients that registered the whitelist).
+const char kRegisteredSupervisedUserWhitelists[] =
+ "supervised_users.whitelists";
+
+#if !defined(OS_ANDROID)
+// Boolean that indicates whether Chrome enterprise cloud reporting is enabled
+// or not.
+const char kCloudReportingEnabled[] =
+ "enterprise_reporting.chrome_cloud_reporting";
+// Boolean that indicates whether Chrome enterprise extension request is enabled
+// or not.
+const char kCloudExtensionRequestEnabled[] =
+ "enterprise_reporting.extension_request.enabled";
+
+// A list of extension ids represents pending extension request. The ids are
+// stored once user sent the request until the request is canceled, approved or
+// denied.
+const char kCloudExtensionRequestIds[] =
+ "enterprise_reporting.extension_request.ids";
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+// Policy that indicates how to handle animated images.
+const char kAnimationPolicy[] = "settings.a11y.animation_policy";
+
+// A list of URLs (for U2F) or domains (for webauthn) that automatically permit
+// direct attestation of a Security Key.
+const char kSecurityKeyPermitAttestation[] = "securitykey.permit_attestation";
+#endif
+
+const char kBackgroundTracingLastUpload[] = "background_tracing.last_upload";
+
+const char kAllowDinosaurEasterEgg[] =
+ "allow_dinosaur_easter_egg";
+
+#if defined(OS_ANDROID)
+// Whether the update menu item was clicked. Used to facilitate logging whether
+// Chrome was updated after the menu item is clicked.
+const char kClickedUpdateMenuItem[] = "omaha.clicked_update_menu_item";
+// The latest version of Chrome available when the user clicked on the update
+// menu item.
+const char kLatestVersionWhenClickedUpdateMenuItem[] =
+ "omaha.latest_version_when_clicked_upate_menu_item";
+#endif
+
+// Whether or not the user has explicitly set the cloud services preference
+// through the first run flow.
+const char kMediaRouterCloudServicesPrefSet[] =
+ "media_router.cloudservices.prefset";
+// Whether or not the user has enabled cloud services with Media Router.
+const char kMediaRouterEnableCloudServices[] =
+ "media_router.cloudservices.enabled";
+// Whether or not the Media Router first run flow has been acknowledged by the
+// user.
+const char kMediaRouterFirstRunFlowAcknowledged[] =
+ "media_router.firstrunflow.acknowledged";
+// Whether or not the user has enabled Media Remoting. Defaults to true.
+const char kMediaRouterMediaRemotingEnabled[] =
+ "media_router.media_remoting.enabled";
+// A list of website origins on which the user has chosen to use tab mirroring.
+const char kMediaRouterTabMirroringSources[] =
+ "media_router.tab_mirroring_sources";
+
+// The base64-encoded representation of the public key to use to validate origin
+// trial token signatures.
+const char kOriginTrialPublicKey[] = "origin_trials.public_key";
+
+// A list of origin trial features to disable by policy.
+const char kOriginTrialDisabledFeatures[] = "origin_trials.disabled_features";
+
+// A list of origin trial tokens to disable by policy.
+const char kOriginTrialDisabledTokens[] = "origin_trials.disabled_tokens";
+
+// Policy that indicates the state of updates for the binary components.
+const char kComponentUpdatesEnabled[] =
+ "component_updates.component_updates_enabled";
+
+#if defined(OS_ANDROID)
+// The current level of backoff for showing the location settings dialog for the
+// default search engine.
+const char kLocationSettingsBackoffLevelDSE[] =
+ "location_settings_backoff_level_dse";
+
+// The current level of backoff for showing the location settings dialog for
+// sites other than the default search engine.
+const char kLocationSettingsBackoffLevelDefault[] =
+ "location_settings_backoff_level_default";
+
+// The next time the location settings dialog can be shown for the default
+// search engine.
+const char kLocationSettingsNextShowDSE[] = "location_settings_next_show_dse";
+
+// The next time the location settings dialog can be shown for sites other than
+// the default search engine.
+const char kLocationSettingsNextShowDefault[] =
+ "location_settings_next_show_default";
+
+// Whether the search geolocation disclosure has been dismissed by the user.
+const char kSearchGeolocationDisclosureDismissed[] =
+ "search_geolocation_disclosure.dismissed";
+
+// How many times the search geolocation disclosure has been shown.
+const char kSearchGeolocationDisclosureShownCount[] =
+ "search_geolocation_disclosure.shown_count";
+
+// When the disclosure was shown last.
+const char kSearchGeolocationDisclosureLastShowDate[] =
+ "search_geolocation_disclosure.last_show_date";
+
+// Whether the metrics for the state of geolocation pre-disclosure being shown
+// have been recorded.
+const char kSearchGeolocationPreDisclosureMetricsRecorded[] =
+ "search_geolocation_pre_disclosure_metrics_recorded";
+
+// Whether the metrics for the state of geolocation post-disclosure being shown
+// have been recorded.
+const char kSearchGeolocationPostDisclosureMetricsRecorded[] =
+ "search_geolocation_post_disclosure_metrics_recorded";
+#endif
+
+// A dictionary which stores whether location access is enabled for the current
+// default search engine. Deprecated for kDSEPermissionsSetting.
+const char kDSEGeolocationSettingDeprecated[] = "dse_geolocation_setting";
+
+// A dictionary which stores the geolocation and notifications content settings
+// for the default search engine before it became the default search engine so
+// that they can be restored if the DSE is ever changed.
+const char kDSEPermissionsSettings[] = "dse_permissions_settings";
+
+// A boolean indicating whether the DSE was previously disabled by enterprise
+// policy.
+const char kDSEWasDisabledByPolicy[] = "dse_was_disabled_by_policy";
+
+// A dictionary of manifest URLs of Web Share Targets to a dictionary containing
+// attributes of its share_target field found in its manifest. Each key in the
+// dictionary is the name of the attribute, and the value is the corresponding
+// value.
+const char kWebShareVisitedTargets[] = "profile.web_share.visited_targets";
+
+#if defined(OS_WIN)
+// Acts as a cache to remember incompatible applications through restarts. Used
+// for the Incompatible Applications Warning feature.
+const char kIncompatibleApplications[] = "incompatible_applications";
+
+// Contains the MD5 digest of the current module blacklist cache. Used to detect
+// external tampering.
+const char kModuleBlacklistCacheMD5Digest[] =
+ "module_blacklist_cache_md5_digest";
+
+// A boolean value, controlling whether third party software is allowed to
+// inject into Chrome's processes.
+const char kThirdPartyBlockingEnabled[] = "third_party_blocking_enabled";
+#endif // defined(OS_WIN)
+
+#if defined(OS_WIN)
+// A boolean value, controlling whether Chrome renderer processes have the CIG
+// mitigation enabled.
+const char kRendererCodeIntegrityEnabled[] = "renderer_code_integrity_enabled";
+#endif // defined(OS_WIN)
+
+// An integer that keeps track of prompt waves for the settings reset
+// prompt. Users will be prompted to reset settings at most once per prompt wave
+// for each setting that the prompt targets (default search, startup URLs and
+// homepage). The value is obtained via a feature parameter. When the stored
+// value is different from the feature parameter, a new prompt wave begins.
+const char kSettingsResetPromptPromptWave[] =
+ "settings_reset_prompt.prompt_wave";
+
+// Timestamp of the last time the settings reset prompt was shown during the
+// current prompt wave asking the user if they want to restore their search
+// engine.
+const char kSettingsResetPromptLastTriggeredForDefaultSearch[] =
+ "settings_reset_prompt.last_triggered_for_default_search";
+
+// Timestamp of the last time the settings reset prompt was shown during the
+// current prompt wave asking the user if they want to restore their startup
+// settings.
+const char kSettingsResetPromptLastTriggeredForStartupUrls[] =
+ "settings_reset_prompt.last_triggered_for_startup_urls";
+
+// Timestamp of the last time the settings reset prompt was shown during the
+// current prompt wave asking the user if they want to restore their homepage.
+const char kSettingsResetPromptLastTriggeredForHomepage[] =
+ "settings_reset_prompt.last_triggered_for_homepage";
+
+#if defined(OS_ANDROID)
+// Timestamp of the clipboard's last modified time, stored in base::Time's
+// internal format (int64) in local store. (I.e., this is not a per-profile
+// pref.)
+const char kClipboardLastModifiedTime[] = "ui.clipboard.last_modified_time";
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+// The following set of Prefs is used by OfflineMetricsCollectorImpl to
+// backup the current Chrome usage tracking state and accumulated counters
+// of days with specific Chrome usage.
+
+// The boolean flags indicating whether the specific activity was observed
+// in Chrome during the day that started at |kOfflineUsageTrackingDay|. These
+// are used to track usage of Chrome is used while offline and how various
+// offline features affect that.
+const char kOfflineUsageStartObserved[] = "offline_pages.start_observed";
+const char kOfflineUsageOnlineObserved[] = "offline_pages.online_observed";
+const char kOfflineUsageOfflineObserved[] = "offline_pages.offline_observed";
+// Boolean flags indicating state of a prefetch subsystem during a day.
+const char kPrefetchUsageEnabledObserved[] =
+ "offline_pages.prefetch_enabled_observed";
+const char kPrefetchUsageFetchObserved[] =
+ "offline_pages.prefetch_fetch_observed";
+const char kPrefetchUsageOpenObserved[] =
+ "offline_pages.prefetch_open_observed";
+// A time corresponding to a midnight that starts the day for which
+// OfflineMetricsCollector tracks the Chrome usage. Once current time passes
+// 24hrs from this point, the further tracking is attributed to the next day.
+const char kOfflineUsageTrackingDay[] = "offline_pages.tracking_day";
+// Accumulated counters of days with specified Chrome usage. When there is
+// likely a network connection, these counters are reported via UMA and reset.
+const char kOfflineUsageUnusedCount[] = "offline_pages.unused_count";
+const char kOfflineUsageStartedCount[] = "offline_pages.started_count";
+const char kOfflineUsageOfflineCount[] = "offline_pages.offline_count";
+const char kOfflineUsageOnlineCount[] = "offline_pages.online_count";
+const char kOfflineUsageMixedCount[] = "offline_pages.mixed_count";
+// Accumulated counters of days with specified Prefetch usage. When there is
+// likely a network connection, these counters are reported via UMA and reset.
+const char kPrefetchUsageEnabledCount[] =
+ "offline_pages.prefetch_enabled_count";
+const char kPrefetchUsageFetchedCount[] =
+ "offline_pages.prefetch_fetched_count";
+const char kPrefetchUsageOpenedCount[] = "offline_pages.prefetch_opened_count";
+const char kPrefetchUsageMixedCount[] = "offline_pages.prefetch_mixed_count";
+
+#endif
+
+// Stores the Media Engagement Index schema version. If the stored value
+// is lower than the value in MediaEngagementService then the MEI data
+// will be wiped.
+const char kMediaEngagementSchemaVersion[] = "media.engagement.schema_version";
+
+// Maximum number of tabs that has been opened since the last time it has been
+// reported.
+const char kTabStatsTotalTabCountMax[] = "tab_stats.total_tab_count_max";
+
+// Maximum number of tabs that has been opened in a single window since the last
+// time it has been reported.
+const char kTabStatsMaxTabsPerWindow[] = "tab_stats.max_tabs_per_window";
+
+// Maximum number of windows that has been opened since the last time it has
+// been reported.
+const char kTabStatsWindowCountMax[] = "tab_stats.window_count_max";
+
+// Timestamp of the last time the tab stats daily metrics have been reported.
+const char kTabStatsDailySample[] = "tab_stats.last_daily_sample";
+
+// A list of origins (URLs) to treat as "secure origins" for debugging purposes.
+const char kUnsafelyTreatInsecureOriginAsSecure[] =
+ "unsafely_treat_insecure_origin_as_secure";
+
+// A list of origins (URLs) that specifies opting into --isolate-origins=...
+// (selective Site Isolation).
+const char kIsolateOrigins[] = "site_isolation.isolate_origins";
+
+// Boolean that specifies opting into --site-per-process (full Site Isolation).
+const char kSitePerProcess[] = "site_isolation.site_per_process";
+
+// A list of origins that were heuristically determined to need process
+// isolation. For example, an origin may be placed on this list in response to
+// the user typing a password on it.
+const char kUserTriggeredIsolatedOrigins[] =
+ "site_isolation.user_triggered_isolated_origins";
+
+// Boolean that specifies if the web driver flag is allowed to override policies
+// which prevent it from operating normally. (e.g. SitePerProcess.)
+const char kWebDriverOverridesIncompatiblePolicies[] =
+ "webdriver.override_incompatible_policy";
+
+#if !defined(OS_ANDROID)
+// Boolean that specifies whether media (audio/video) autoplay is allowed.
+const char kAutoplayAllowed[] = "media.autoplay_allowed";
+
+// Holds URL patterns that specify URLs that will be allowed to autoplay.
+const char kAutoplayWhitelist[] = "media.autoplay_whitelist";
+
+// Boolean that specifies whether autoplay blocking is enabled.
+const char kBlockAutoplayEnabled[] = "media.block_autoplay";
+#endif // !defined(OS_ANDROID)
+
+// Integer that holds the value of the next persistent notification ID to be
+// used.
+const char kNotificationNextPersistentId[] = "persistent_notifications.next_id";
+
+// Time that holds the value of the next notification trigger timestamp.
+const char kNotificationNextTriggerTime[] =
+ "persistent_notifications.next_trigger";
+
+// Preference for controlling whether tab freezing is enabled.
+const char kTabFreezingEnabled[] = "tab_freezing_enabled";
+
+// Boolean that enables the Enterprise Hardware Platform Extension API for
+// extensions installed by enterprise policy.
+const char kEnterpriseHardwarePlatformAPIEnabled[] =
+ "enterprise_hardware_platform_api.enabled";
+
+// Boolean that specifies whether Signed HTTP Exchange (SXG) loading is enabled.
+const char kSignedHTTPExchangeEnabled[] = "web_package.signed_exchange.enabled";
+
+// Boolean that allows a page to show popups during its unloading.
+// TODO(https://crbug.com/937569): Remove this in Chrome 82.
+const char kAllowPopupsDuringPageUnload[] = "allow_popups_during_page_unload";
+
+// Boolean that allows a page to perform synchronous XHR requests during page
+// dismissal.
+// TODO(https://crbug.com/1003101): Remove this in Chrome 82.
+const char kAllowSyncXHRInPageDismissal[] = "allow_sync_xhr_in_page_dismissal";
+
+#if defined(OS_CHROMEOS)
+// Enum that specifies client certificate management permissions for user. It
+// can have one of the following values.
+// 0: Users can manage all certificates.
+// 1: Users can manage user certificates, but not device certificates.
+// 2: Disallow users from managing certificates
+// Controlled by ClientCertificateManagementAllowed policy.
+const char kClientCertificateManagementAllowed[] =
+ "client_certificate_management_allowed";
+
+// Enum that specifies CA certificate management permissions for user. It
+// can have one of the following values.
+// 0: Users can manage all certificates.
+// 1: Users can manage user certificates, but not built-in certificates.
+// 2: Disallow users from managing certificates
+// Controlled by CACertificateManagementAllowed policy.
+const char kCACertificateManagementAllowed[] =
+ "ca_certificate_management_allowed";
+#endif
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+// Boolean that specifies whether the built-in certificate verifier should be
+// used. If false, Chrome will use the platform certificate verifier. If not
+// set, Chrome will choose the certificate verifier based on experiments.
+const char kBuiltinCertificateVerifierEnabled[] =
+ "builtin_certificate_verifier_enabled";
+#endif
+
+const char kSharingVapidKey[] = "sharing.vapid_key";
+const char kSharingSyncedDevices[] = "sharing.synced_devices";
+const char kSharingFCMRegistration[] = "sharing.fcm_registration";
+const char kSharingLocalSharingInfo[] = "sharing.local_sharing_info";
+
+#if !defined(OS_ANDROID)
+// Dictionary that contains all of the Hats Survey Metadata.
+const char kHatsSurveyMetadata[] = "hats.survey_metadata";
+#endif // !defined(OS_ANDROID)
+
+// TODO(crbug.com/1000977, crbug.com/1000984): Remove this during M81:83.
+const char kCorsMitigationList[] = "cors.mitigation.list";
+// TODO(crbug.com/1001450): Remove this once we fully shipped OOR-CORS.
+const char kCorsLegacyModeEnabled[] = "cors.legacy_mode.enabled";
+
+const char kExternalProtocolDialogShowAlwaysOpenCheckbox[] =
+ "external_protocol_dialog.show_always_open_checkbox";
+
} // namespace prefs
diff --git a/chromium/chrome/common/pref_names.h b/chromium/chrome/common/pref_names.h
index 0c4d73c93ab..7ea87bfb8c2 100644
--- a/chromium/chrome/common/pref_names.h
+++ b/chromium/chrome/common/pref_names.h
@@ -7,12 +7,1007 @@
#ifndef CHROME_COMMON_PREF_NAMES_H_
#define CHROME_COMMON_PREF_NAMES_H_
+#include <stddef.h>
+
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/common/buildflags.h"
+#include "components/offline_pages/buildflags/buildflags.h"
+#include "extensions/buildflags/buildflags.h"
+#include "media/media_buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "rlz/buildflags/buildflags.h"
+
namespace prefs {
-extern const char kAcceptLanguages[];
-extern const char kNotificationNextPersistentId[];
+
+// Profile prefs. Please add Local State prefs below instead.
+extern const char kAbusiveExperienceInterventionEnforce[];
+extern const char kChildAccountStatusKnown[];
+extern const char kDefaultApps[];
+extern const char kSafeBrowsingForTrustedSourcesEnabled[];
+extern const char kDisableScreenshots[];
+extern const char kDownloadRestrictions[];
+extern const char kForceEphemeralProfiles[];
+extern const char kHomePageIsNewTabPage[];
+extern const char kHomePage[];
+extern const char kImportantSitesDialogHistory[];
+#if defined(OS_WIN)
+extern const char kLastProfileResetTimestamp[];
+extern const char kChromeCleanerResetPending[];
+#endif
+extern const char kNewTabPageLocationOverride[];
+extern const char kProfileIconVersion[];
+extern const char kRestoreOnStartup[];
+extern const char kSessionExitedCleanly[];
+extern const char kSessionExitType[];
+extern const char kObservedSessionTime[];
+extern const char kRecurrentSSLInterstitial[];
+extern const char kSiteEngagementLastUpdateTime[];
+extern const char kSupervisedUserApprovedExtensions[];
+extern const char kSupervisedUserCustodianEmail[];
+extern const char kSupervisedUserCustodianName[];
+extern const char kSupervisedUserCustodianObfuscatedGaiaId[];
+extern const char kSupervisedUserCustodianProfileImageURL[];
+extern const char kSupervisedUserCustodianProfileURL[];
+extern const char kSupervisedUserManualHosts[];
+extern const char kSupervisedUserManualURLs[];
+extern const char kSupervisedUserSafeSites[];
+extern const char kSupervisedUserSecondCustodianEmail[];
+extern const char kSupervisedUserSecondCustodianName[];
+extern const char kSupervisedUserSecondCustodianObfuscatedGaiaId[];
+extern const char kSupervisedUserSecondCustodianProfileImageURL[];
+extern const char kSupervisedUserSecondCustodianProfileURL[];
+extern const char kSupervisedUserSharedSettings[];
+extern const char kSupervisedUserWhitelists[];
+extern const char kURLsToRestoreOnStartup[];
+extern const char kUserFeedbackAllowed[];
+
+#if BUILDFLAG(ENABLE_RLZ)
+extern const char kRlzPingDelaySeconds[];
+#endif // BUILDFLAG(ENABLE_RLZ)
+
+// For OS_CHROMEOS we maintain the kApplicationLocale property in both local
+// state and the user's profile. The global property determines the locale of
+// the login screen, while the user's profile determines their personal locale
+// preference.
+#if defined(OS_CHROMEOS)
+extern const char kApplicationLocaleBackup[];
+extern const char kApplicationLocaleAccepted[];
+extern const char kOwnerLocale[];
+extern const char kAllowedLanguages[];
+#endif
+
+extern const char kDefaultCharset[];
+extern const char kWebKitCommonScript[];
+extern const char kWebKitStandardFontFamily[];
+extern const char kWebKitFixedFontFamily[];
+extern const char kWebKitSerifFontFamily[];
+extern const char kWebKitSansSerifFontFamily[];
+extern const char kWebKitCursiveFontFamily[];
+extern const char kWebKitFantasyFontFamily[];
+extern const char kWebKitPictographFontFamily[];
+
+// ISO 15924 four-letter script codes that per-script font prefs are supported
+// for.
+extern const char* const kWebKitScriptsForFontFamilyMaps[];
+extern const size_t kWebKitScriptsForFontFamilyMapsLength;
+
+// Per-script font pref prefixes.
+extern const char kWebKitStandardFontFamilyMap[];
+extern const char kWebKitFixedFontFamilyMap[];
+extern const char kWebKitSerifFontFamilyMap[];
+extern const char kWebKitSansSerifFontFamilyMap[];
+extern const char kWebKitCursiveFontFamilyMap[];
+extern const char kWebKitFantasyFontFamilyMap[];
+extern const char kWebKitPictographFontFamilyMap[];
+
+// Per-script font prefs that have defaults, for easy reference when registering
+// the defaults.
+extern const char kWebKitStandardFontFamilyArabic[];
+#if defined(OS_WIN)
+extern const char kWebKitFixedFontFamilyArabic[];
+#endif
+extern const char kWebKitSerifFontFamilyArabic[];
+extern const char kWebKitSansSerifFontFamilyArabic[];
+#if defined(OS_WIN)
+extern const char kWebKitStandardFontFamilyCyrillic[];
+extern const char kWebKitFixedFontFamilyCyrillic[];
+extern const char kWebKitSerifFontFamilyCyrillic[];
+extern const char kWebKitSansSerifFontFamilyCyrillic[];
+extern const char kWebKitStandardFontFamilyGreek[];
+extern const char kWebKitFixedFontFamilyGreek[];
+extern const char kWebKitSerifFontFamilyGreek[];
+extern const char kWebKitSansSerifFontFamilyGreek[];
+#endif
+extern const char kWebKitStandardFontFamilyJapanese[];
+extern const char kWebKitFixedFontFamilyJapanese[];
+extern const char kWebKitSerifFontFamilyJapanese[];
+extern const char kWebKitSansSerifFontFamilyJapanese[];
+extern const char kWebKitStandardFontFamilyKorean[];
+extern const char kWebKitFixedFontFamilyKorean[];
+extern const char kWebKitSerifFontFamilyKorean[];
+extern const char kWebKitSansSerifFontFamilyKorean[];
+#if defined(OS_WIN)
+extern const char kWebKitCursiveFontFamilyKorean[];
+#endif
+extern const char kWebKitStandardFontFamilySimplifiedHan[];
+extern const char kWebKitFixedFontFamilySimplifiedHan[];
+extern const char kWebKitSerifFontFamilySimplifiedHan[];
+extern const char kWebKitSansSerifFontFamilySimplifiedHan[];
+extern const char kWebKitStandardFontFamilyTraditionalHan[];
+extern const char kWebKitFixedFontFamilyTraditionalHan[];
+extern const char kWebKitSerifFontFamilyTraditionalHan[];
+extern const char kWebKitSansSerifFontFamilyTraditionalHan[];
+#if defined(OS_WIN) || defined(OS_MACOSX)
+extern const char kWebKitCursiveFontFamilySimplifiedHan[];
+extern const char kWebKitCursiveFontFamilyTraditionalHan[];
+#endif
+
+extern const char kWebKitDefaultFontSize[];
+extern const char kWebKitDefaultFixedFontSize[];
+extern const char kWebKitMinimumFontSize[];
+extern const char kWebKitMinimumLogicalFontSize[];
+extern const char kWebKitJavascriptEnabled[];
+extern const char kWebKitWebSecurityEnabled[];
+extern const char kWebKitLoadsImagesAutomatically[];
+extern const char kWebKitPluginsEnabled[];
+extern const char kWebKitDomPasteEnabled[];
+extern const char kWebKitTextAreasAreResizable[];
+extern const char kWebKitJavascriptCanAccessClipboard[];
+extern const char kWebkitTabsToLinks[];
+extern const char kWebKitAllowRunningInsecureContent[];
+extern const char kWebKitForceDarkModeEnabled[];
+#if defined(OS_ANDROID)
+extern const char kWebKitFontScaleFactor[];
+extern const char kWebKitForceEnableZoom[];
+extern const char kWebKitPasswordEchoEnabled[];
+#endif
+extern const char kSSLErrorOverrideAllowed[];
+extern const char kIncognitoModeAvailability[];
+extern const char kSearchSuggestEnabled[];
+#if defined(OS_ANDROID)
+extern const char kContextualSearchEnabled[];
+extern const char kContextualSearchDisabledValue[];
+extern const char kContextualSearchEnabledValue[];
+#endif // defined(OS_ANDROID)
extern const char kShowInternalAccessibilityTree[];
extern const char kAccessibilityImageLabelsEnabled[];
extern const char kAccessibilityImageLabelsOptInAccepted[];
+extern const char kAccessibilityCaptionsTextSize[];
+extern const char kAccessibilityCaptionsTextFont[];
+extern const char kAccessibilityCaptionsTextColor[];
+extern const char kAccessibilityCaptionsTextOpacity[];
+extern const char kAccessibilityCaptionsBackgroundColor[];
+extern const char kAccessibilityCaptionsTextShadow[];
+extern const char kAccessibilityCaptionsBackgroundOpacity[];
+#if defined(OS_MACOSX)
+extern const char kConfirmToQuitEnabled[];
+extern const char kShowFullscreenToolbar[];
+extern const char kAllowJavascriptAppleEvents[];
+#endif
+extern const char kPromptForDownload[];
+extern const char kAlternateErrorPagesEnabled[];
+extern const char kQuicAllowed[];
+extern const char kNetworkQualities[];
+extern const char kNetworkEasterEggHighScore[];
+#if defined(OS_ANDROID)
+extern const char kLastPolicyCheckTime[];
+#endif
+extern const char kNetworkPredictionOptions[];
+extern const char kDefaultAppsInstallState[];
+extern const char kHideWebStoreIcon[];
+#if defined(OS_CHROMEOS)
+extern const char kAccountManagerNumTimesMigrationRanSuccessfully[];
+extern const char kAccountManagerNumTimesWelcomeScreenShown[];
+extern const char kTapToClickEnabled[];
+extern const char kEnableTouchpadThreeFingerClick[];
+extern const char kNaturalScroll[];
+extern const char kPrimaryMouseButtonRight[];
+extern const char kMouseReverseScroll[];
+extern const char kMouseAcceleration[];
+extern const char kTouchpadAcceleration[];
+extern const char kMouseSensitivity[];
+extern const char kTouchpadSensitivity[];
+extern const char kUse24HourClock[];
+extern const char kUserTimezone[];
+extern const char kResolveTimezoneByGeolocation[];
+extern const char kResolveTimezoneByGeolocationMethod[];
+extern const char kResolveTimezoneByGeolocationMigratedToMethod[];
+// TODO(yusukes): Change "kLanguageABC" to "kABC". The current form is too long
+// to remember and confusing. The prefs are actually for input methods and i18n
+// keyboards, not UI languages.
+extern const char kLanguageCurrentInputMethod[];
+extern const char kLanguagePreviousInputMethod[];
+extern const char kLanguageAllowedInputMethods[];
+extern const char kLanguagePreloadEngines[];
+extern const char kLanguagePreloadEnginesSyncable[];
+extern const char kLanguageEnabledImes[];
+extern const char kLanguageEnabledImesSyncable[];
+extern const char kLanguageImeMenuActivated[];
+extern const char kLanguageInputMethodSpecificSettings[];
+extern const char kLanguageShouldMergeInputMethods[];
+extern const char kLanguageSendFunctionKeys[];
+extern const char kLanguageXkbAutoRepeatEnabled[];
+extern const char kLanguageXkbAutoRepeatDelay[];
+extern const char kLanguageXkbAutoRepeatInterval[];
+
+extern const char kLabsAdvancedFilesystemEnabled[];
+extern const char kLabsMediaplayerEnabled[];
+extern const char kShowMobileDataNotification[];
+extern const char kDataSaverPromptsShown[];
+extern const char kChromeOSReleaseNotesVersion[];
+extern const char kNoteTakingAppId[];
+extern const char kNoteTakingAppEnabledOnLockScreen[];
+extern const char kNoteTakingAppsLockScreenWhitelist[];
+extern const char kNoteTakingAppsLockScreenToastShown[];
+extern const char kRestoreLastLockScreenNote[];
+extern const char kSessionUserActivitySeen[];
+extern const char kSessionStartTime[];
+extern const char kSessionLengthLimit[];
+extern const char kSessionWaitForInitialUserActivity[];
+extern const char kLastSessionType[];
+extern const char kLastSessionLength[];
+extern const char kTermsOfServiceURL[];
+extern const char kAttestationEnabled[];
+extern const char kAttestationExtensionWhitelist[];
+extern const char kMultiProfileNeverShowIntro[];
+extern const char kMultiProfileWarningShowDismissed[];
+extern const char kMultiProfileUserBehavior[];
+extern const char kFirstRunTutorialShown[];
+extern const char kSAMLOfflineSigninTimeLimit[];
+extern const char kSAMLLastGAIASignInTime[];
+extern const char kTimeOnOobe[];
+extern const char kFileSystemProviderMounted[];
+extern const char kTouchVirtualKeyboardEnabled[];
+extern const char kWakeOnWifiDarkConnect[];
+extern const char kCaptivePortalAuthenticationIgnoresProxy[];
+extern const char kForceMaximizeOnFirstRun[];
+extern const char kPlatformKeys[];
+extern const char kUnifiedDesktopEnabledByDefault[];
+extern const char kHatsLastInteractionTimestamp[];
+extern const char kHatsSurveyCycleEndTimestamp[];
+extern const char kHatsDeviceIsSelected[];
+extern const char kQuickUnlockPinSecret[];
+extern const char kQuickUnlockFingerprintRecord[];
+extern const char kEolStatus[];
+extern const char kEndOfLifeDate[];
+extern const char kEolNotificationDismissed[];
+extern const char kFirstEolWarningDismissed[];
+extern const char kSecondEolWarningDismissed[];
+extern const char kPinUnlockFeatureNotificationShown[];
+extern const char kFingerprintUnlockFeatureNotificationShown[];
+extern const char kQuickUnlockModeWhitelist[];
+extern const char kQuickUnlockTimeout[];
+extern const char kPinUnlockMinimumLength[];
+extern const char kPinUnlockMaximumLength[];
+extern const char kPinUnlockWeakPinsAllowed[];
+extern const char kInstantTetheringBleAdvertisingSupported[];
+extern const char kCastReceiverEnabled[];
+extern const char kMinimumAllowedChromeVersion[];
+extern const char kShowArcSettingsOnSessionStart[];
+extern const char kShowSyncSettingsOnSessionStart[];
+extern const char kTextToSpeechLangToVoiceName[];
+extern const char kTextToSpeechRate[];
+extern const char kTextToSpeechPitch[];
+extern const char kTextToSpeechVolume[];
+extern const char kTimeLimitLocalOverride[];
+extern const char kUsageTimeLimit[];
+extern const char kScreenTimeLastState[];
+extern const char kEnableSyncConsent[];
+extern const char kNetworkFileSharesAllowed[];
+extern const char kManagedSessionEnabled[];
+extern const char kTPMFirmwareUpdateCleanupDismissed[];
+extern const char kTPMUpdatePlannedNotificationShownTime[];
+extern const char kTPMUpdateOnNextRebootNotificationShown[];
+extern const char kNetBiosShareDiscoveryEnabled[];
+extern const char kChildScreenTimeMilliseconds[];
+extern const char kLastChildScreenTimeSaved[];
+extern const char kLastChildScreenTimeReset[];
+extern const char kReleaseNotesLastShownMilestone[];
+extern const char kReleaseNotesSuggestionChipTimesLeftToShow[];
+extern const char kNTLMShareAuthenticationEnabled[];
+extern const char kNetworkFileSharesPreconfiguredShares[];
+extern const char kMostRecentlyUsedNetworkFileShareURL[];
+extern const char kParentAccessCodeConfig[];
+extern const char kDeviceWallpaperImageFilePath[];
+extern const char kKerberosRememberPasswordEnabled[];
+extern const char kKerberosAddAccountsAllowed[];
+extern const char kKerberosAccounts[];
+extern const char kKerberosActivePrincipalName[];
+extern const char kAppReinstallRecommendationEnabled[];
+extern const char kStartupBrowserWindowLaunchSuppressed[];
+extern const char kLoginExtensionApiDataForNextLoginAttempt[];
+extern const char kSettingsShowBrowserBanner[];
+extern const char kSettingsShowOSBanner[];
+extern const char kDeviceLoginScreenWebUsbAllowDevicesForUrls[];
+#endif // defined(OS_CHROMEOS)
+extern const char kShowHomeButton[];
+extern const char kSpeechRecognitionFilterProfanities[];
+extern const char kAllowDeletingBrowserHistory[];
+#if !defined(OS_ANDROID)
+extern const char kHistoryMenuPromoShown[];
+#endif
+extern const char kForceGoogleSafeSearch[];
+extern const char kForceYouTubeRestrict[];
+extern const char kAllowedDomainsForApps[];
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+extern const char kUsesSystemTheme[];
+#endif
+extern const char kCurrentThemePackFilename[];
+extern const char kCurrentThemeID[];
+extern const char kAutogeneratedThemeColor[];
+extern const char kExtensionsUIDeveloperMode[];
+extern const char kExtensionsUIDismissedADTPromo[];
+extern const char kExtensionCommands[];
+extern const char kPluginsLastInternalDirectory[];
+extern const char kPluginsPluginsList[];
+extern const char kPluginsDisabledPlugins[];
+extern const char kPluginsDisabledPluginsExceptions[];
+extern const char kPluginsEnabledPlugins[];
+extern const char kPluginsAlwaysOpenPdfExternally[];
+#if BUILDFLAG(ENABLE_PLUGINS)
+extern const char kPluginsShowDetails[];
+#endif
+extern const char kPluginsAllowOutdated[];
+extern const char kRunAllFlashInAllowMode[];
+#if BUILDFLAG(ENABLE_PLUGINS)
+extern const char kPluginsMetadata[];
+extern const char kPluginsResourceCacheUpdate[];
+#endif
+extern const char kPluginsDeprecationInfobarLastShown[];
+extern const char kDefaultBrowserLastDeclined[];
+extern const char kResetCheckDefaultBrowser[];
+extern const char kDefaultBrowserSettingEnabled[];
+#if defined(OS_MACOSX)
+extern const char kShowUpdatePromotionInfoBar[];
+#endif
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+extern const char kUseCustomChromeFrame[];
+#endif
+#if BUILDFLAG(ENABLE_PLUGINS)
+extern const char kContentSettingsPluginWhitelist[];
+#endif
+#if !defined(OS_ANDROID)
+extern const char kPartitionDefaultZoomLevel[];
+extern const char kPartitionPerHostZoomLevels[];
+
+extern const char kPinnedTabs[];
+#endif // !defined(OS_ANDROID)
+
+extern const char kDisable3DAPIs[];
+extern const char kEnableDeprecatedWebPlatformFeatures[];
+extern const char kEnableHyperlinkAuditing[];
+extern const char kEnableReferrers[];
+extern const char kEnableDoNotTrack[];
+extern const char kEnableEncryptedMedia[];
+
+extern const char kImportAutofillFormData[];
+extern const char kImportBookmarks[];
+extern const char kImportHistory[];
+extern const char kImportHomepage[];
+extern const char kImportSavedPasswords[];
+extern const char kImportSearchEngine[];
+
+extern const char kImportDialogAutofillFormData[];
+extern const char kImportDialogBookmarks[];
+extern const char kImportDialogHistory[];
+extern const char kImportDialogSavedPasswords[];
+extern const char kImportDialogSearchEngine[];
+
+extern const char kProfileAvatarIndex[];
+extern const char kProfileUsingDefaultName[];
+extern const char kProfileName[];
+extern const char kProfileUsingDefaultAvatar[];
+extern const char kProfileUsingGAIAAvatar[];
+extern const char kSupervisedUserId[];
+
+extern const char kProfileGAIAInfoUpdateTime[];
+extern const char kProfileGAIAInfoPictureURL[];
+
+extern const char kProfileAvatarTutorialShown[];
+
+extern const char kInvertNotificationShown[];
+
+extern const char kPrintingEnabled[];
+extern const char kPrintPreviewDisabled[];
+extern const char kPrintPreviewDefaultDestinationSelectionRules[];
+extern const char kPrintHeaderFooter[];
+
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+extern const char kPrintPreviewUseSystemDefaultPrinter[];
+#endif
+
+#if defined(OS_CHROMEOS)
+extern const char kExternalPrintServersWhitelist[];
+extern const char kRecommendedNativePrinters[];
+extern const char kRecommendedNativePrintersAccessMode[];
+extern const char kRecommendedNativePrintersBlacklist[];
+extern const char kRecommendedNativePrintersWhitelist[];
+extern const char kUserNativePrintersAllowed[];
+
+extern const char kPrintingAllowedColorModes[];
+extern const char kPrintingAllowedDuplexModes[];
+extern const char kPrintingAllowedPinModes[];
+extern const char kPrintingAllowedBackgroundGraphicsModes[];
+extern const char kPrintingAllowedPageSizes[];
+extern const char kPrintingColorDefault[];
+extern const char kPrintingDuplexDefault[];
+extern const char kPrintingPinDefault[];
+extern const char kPrintingBackgroundGraphicsDefault[];
+extern const char kPrintingSizeDefault[];
+extern const char kPrintingSendUsernameAndFilenameEnabled[];
+extern const char kPrintJobHistoryExpirationPeriod[];
+#endif // OS_CHROMEOS
+
+extern const char kDefaultSupervisedUserFilteringBehavior[];
+
+extern const char kSupervisedUsers[];
+
+extern const char kMessageCenterDisabledExtensionIds[];
+
+extern const char kFullscreenAllowed[];
+
+extern const char kLocalDiscoveryNotificationsEnabled[];
+
+#if defined(OS_ANDROID)
+extern const char kNotificationsVibrateEnabled[];
+extern const char kMigratedToSiteNotificationChannels[];
+extern const char kClearedBlockedSiteNotificationChannels[];
+extern const char kUsageStatsEnabled[];
+#endif
+
+extern const char kPushMessagingAppIdentifierMap[];
+
+extern const char kGCMProductCategoryForSubtypes[];
+
+extern const char kEasyUnlockAllowed[];
+extern const char kEasyUnlockPairing[];
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+extern const char kToolbarIconSurfacingBubbleAcknowledged[];
+extern const char kToolbarIconSurfacingBubbleLastShowTime[];
+#endif
+
+extern const char kWebRTCMultipleRoutesEnabled[];
+extern const char kWebRTCNonProxiedUdpEnabled[];
+extern const char kWebRTCIPHandlingPolicy[];
+extern const char kWebRTCUDPPortRange[];
+extern const char kWebRtcEventLogCollectionAllowed[];
+extern const char kWebRtcLocalIpsAllowedUrls[];
+
+#if !defined(OS_ANDROID)
+extern const char kHasSeenWelcomePage[];
+#endif
+
+#if defined(OS_WIN)
+// Only used in branded builds.
+extern const char kNaviOnboardGroup[];
+#endif // defined(OS_WIN)
+
+// Deprecated preference for metric / crash reporting on Android. Use
+// kMetricsReportingEnabled instead.
+#if defined(OS_ANDROID)
+extern const char kCrashReportingEnabled[];
+#endif // defined(OS_ANDROID)
+
+extern const char kProfileLastUsed[];
+extern const char kProfilesLastActive[];
+extern const char kProfilesNumCreated[];
+extern const char kProfileInfoCache[];
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+extern const char kLegacyProfileNamesMigrated[];
+#endif // !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+extern const char kProfileCreatedByVersion[];
+extern const char kProfilesDeleted[];
+
+extern const char kStabilityOtherUserCrashCount[];
+extern const char kStabilityKernelCrashCount[];
+extern const char kStabilitySystemUncleanShutdownCount[];
+
+extern const char kStabilityPluginStats[];
+extern const char kStabilityPluginName[];
+extern const char kStabilityPluginLaunches[];
+extern const char kStabilityPluginInstances[];
+extern const char kStabilityPluginCrashes[];
+extern const char kStabilityPluginLoadingErrors[];
+
+extern const char kBrowserSuppressDefaultBrowserPrompt[];
+
+extern const char kBrowserWindowPlacement[];
+extern const char kBrowserWindowPlacementPopup[];
+extern const char kTaskManagerWindowPlacement[];
+extern const char kTaskManagerColumnVisibility[];
+extern const char kTaskManagerEndProcessEnabled[];
+extern const char kAppWindowPlacement[];
+
+extern const char kDownloadDefaultDirectory[];
+extern const char kDownloadExtensionsToOpen[];
+extern const char kDownloadDirUpgraded[];
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
+extern const char kOpenPdfDownloadInSystemReader[];
+#endif
+#if defined(OS_ANDROID)
+extern const char kPromptForDownloadAndroid[];
+extern const char kShowMissingSdCardErrorAndroid[];
+#endif
+
+extern const char kSaveFileDefaultDirectory[];
+extern const char kSaveFileType[];
+
+extern const char kAllowFileSelectionDialogs[];
+extern const char kDefaultTasksByMimeType[];
+extern const char kDefaultTasksBySuffix[];
+
+extern const char kSharedClipboardEnabled[];
+
+#if BUILDFLAG(ENABLE_CLICK_TO_CALL)
+extern const char kClickToCallEnabled[];
+#endif // BUILDFLAG(ENABLE_CLICK_TO_CALL)
+
+extern const char kSelectFileLastDirectory[];
+
+extern const char kExcludedSchemes[];
+
+extern const char kLastKnownIntranetRedirectOrigin[];
+
+extern const char kShutdownType[];
+extern const char kShutdownNumProcesses[];
+extern const char kShutdownNumProcessesSlow[];
+
+extern const char kRestartLastSessionOnShutdown[];
+#if !defined(OS_ANDROID)
+#if !defined(OS_CHROMEOS)
+extern const char kPromotionalTabsEnabled[];
+extern const char kCommandLineFlagSecurityWarningsEnabled[];
+#endif
+extern const char kSuppressUnsupportedOSWarning[];
+extern const char kWasRestarted[];
+#endif // !defined(OS_ANDROID)
+
+extern const char kDisableExtensions[];
+
+extern const char kNtpAppPageNames[];
+extern const char kNtpCollapsedForeignSessions[];
+#if defined(OS_ANDROID)
+extern const char kNtpCollapsedRecentlyClosedTabs[];
+extern const char kNtpCollapsedSnapshotDocument[];
+extern const char kNtpCollapsedSyncPromo[];
+#else
+extern const char kNtpCustomBackgroundDict[];
+extern const char kNtpCustomBackgroundLocalToDevice[];
+extern const char kNtpPromoBlocklist[];
+extern const char kNtpSearchSuggestionsBlocklist[];
+extern const char kNtpSearchSuggestionsImpressions[];
+extern const char kNtpSearchSuggestionsOptOut[];
+extern const char kNtpShortcutsVisible[];
+extern const char kNtpUseMostVisitedTiles[];
+#endif // defined(OS_ANDROID)
+extern const char kNtpShownPage[];
+
+extern const char kDevToolsAdbKey[];
+extern const char kDevToolsAvailability[];
+extern const char kDevToolsBackgroundServicesExpirationDict[];
+extern const char kDevToolsDiscoverUsbDevicesEnabled[];
+extern const char kDevToolsEditedFiles[];
+extern const char kDevToolsFileSystemPaths[];
+extern const char kDevToolsPortForwardingEnabled[];
+extern const char kDevToolsPortForwardingDefaultSet[];
+extern const char kDevToolsPortForwardingConfig[];
+extern const char kDevToolsPreferences[];
+extern const char kDevToolsDiscoverTCPTargetsEnabled[];
+extern const char kDevToolsTCPDiscoveryConfig[];
+
+#if !defined(OS_ANDROID)
+extern const char kDiceSigninUserMenuPromoCount[];
+#endif
+
+extern const char kWebAppCreateOnDesktop[];
+extern const char kWebAppCreateInAppsMenu[];
+extern const char kWebAppCreateInQuickLaunchBar[];
+
+extern const char kWebAppInstallForceList[];
+extern const char kWebAppInstallMetrics[];
+
+extern const char kWebAppsExtensionIDs[];
+extern const char kSystemWebAppLastUpdateVersion[];
+
+extern const char kDefaultAudioCaptureDevice[];
+extern const char kDefaultVideoCaptureDevice[];
+extern const char kMediaDeviceIdSalt[];
+extern const char kMediaStorageIdSalt[];
+
+extern const char kPrintPreviewStickySettings[];
+extern const char kCloudPrintRoot[];
+extern const char kCloudPrintProxyEnabled[];
+extern const char kCloudPrintProxyId[];
+extern const char kCloudPrintAuthToken[];
+extern const char kCloudPrintEmail[];
+extern const char kCloudPrintPrintSystemSettings[];
+extern const char kCloudPrintEnableJobPoll[];
+extern const char kCloudPrintRobotRefreshToken[];
+extern const char kCloudPrintRobotEmail[];
+extern const char kCloudPrintConnectNewPrinters[];
+extern const char kCloudPrintXmppPingEnabled[];
+extern const char kCloudPrintXmppPingTimeout[];
+extern const char kCloudPrintPrinters[];
+extern const char kCloudPrintSubmitEnabled[];
+extern const char kCloudPrintUserSettings[];
+
+extern const char kMaxConnectionsPerProxy[];
+
+extern const char kAudioCaptureAllowed[];
+extern const char kAudioCaptureAllowedUrls[];
+extern const char kVideoCaptureAllowed[];
+extern const char kVideoCaptureAllowedUrls[];
+
+#if defined(OS_CHROMEOS)
+extern const char kDemoModeConfig[];
+extern const char kDemoModeCountry[];
+extern const char kDemoModeDefaultLocale[];
+extern const char kDeviceSettingsCache[];
+extern const char kHardwareKeyboardLayout[];
+extern const char kCarrierDealPromoShown[];
+extern const char kShouldAutoEnroll[];
+extern const char kAutoEnrollmentPowerLimit[];
+extern const char kDeviceActivityTimes[];
+extern const char kUserActivityTimes[];
+extern const char kExternalStorageDisabled[];
+extern const char kExternalStorageReadOnly[];
+extern const char kOwnerPrimaryMouseButtonRight[];
+extern const char kOwnerTapToClickEnabled[];
+extern const char kUptimeLimit[];
+extern const char kRebootAfterUpdate[];
+extern const char kDeviceRobotAnyApiRefreshToken[];
+extern const char kDeviceEnrollmentRequisition[];
+extern const char kDeviceEnrollmentSubOrganization[];
+extern const char kDeviceEnrollmentAutoStart[];
+extern const char kDeviceEnrollmentCanExit[];
+extern const char kDeviceDMToken[];
+extern const char kTimesHIDDialogShown[];
+extern const char kUsersLastInputMethod[];
+extern const char kEchoCheckedOffers[];
+extern const char kCachedMultiProfileUserBehavior[];
+extern const char kInitialLocale[];
+extern const char kOobeComplete[];
+extern const char kOobeScreenPending[];
+extern const char kOobeMarketingOptInScreenFinished[];
+extern const char kCanShowOobeGoodiesPage[];
+extern const char kDeviceRegistered[];
+extern const char kEnrollmentRecoveryRequired[];
+extern const char kUsedPolicyCertificates[];
+extern const char kServerBackedDeviceState[];
+extern const char kCustomizationDefaultWallpaperURL[];
+extern const char kLogoutStartedLast[];
+extern const char kConsumerManagementStage[];
+extern const char kReportArcStatusEnabled[];
+extern const char kSchedulerConfiguration[];
+extern const char kNetworkThrottlingEnabled[];
+extern const char kPowerMetricsDailySample[];
+extern const char kPowerMetricsIdleScreenDimCount[];
+extern const char kPowerMetricsIdleScreenOffCount[];
+extern const char kPowerMetricsIdleSuspendCount[];
+extern const char kPowerMetricsLidClosedSuspendCount[];
+extern const char kReportingUsers[];
+extern const char kArcAppInstallEventLoggingEnabled[];
+extern const char kRemoveUsersRemoteCommand[];
+extern const char kCameraMediaConsolidated[];
+extern const char kAutoScreenBrightnessMetricsDailySample[];
+extern const char kAutoScreenBrightnessMetricsAtlasUserAdjustmentCount[];
+extern const char kAutoScreenBrightnessMetricsEveUserAdjustmentCount[];
+extern const char kAutoScreenBrightnessMetricsNocturneUserAdjustmentCount[];
+extern const char kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount[];
+extern const char kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount[];
+extern const char
+ kAutoScreenBrightnessMetricsUnsupportedAlsUserAdjustmentCount[];
+extern const char kKnownUserParentAccessCodeConfig[];
+extern const char kSamlInSessionPasswordChangeEnabled[];
+extern const char kSamlPasswordExpirationAdvanceWarningDays[];
+extern const char kLastRsuDeviceIdUploaded[];
+#endif // defined(OS_CHROMEOS)
+
+extern const char kClearPluginLSODataEnabled[];
+extern const char kPepperFlashSettingsEnabled[];
+extern const char kDiskCacheDir[];
+extern const char kDiskCacheSize[];
+
+extern const char kChromeOsReleaseChannel[];
+
+extern const char kPerformanceTracingEnabled[];
+
+extern const char kTabStripStackedLayout[];
+
+extern const char kRegisteredBackgroundContents[];
+
+extern const char kTotalMemoryLimitMb[];
+
+extern const char kAuthSchemes[];
+extern const char kDisableAuthNegotiateCnameLookup[];
+extern const char kEnableAuthNegotiatePort[];
+extern const char kAuthServerWhitelist[];
+extern const char kAuthNegotiateDelegateWhitelist[];
+extern const char kGSSAPILibraryName[];
+extern const char kAuthAndroidNegotiateAccountType[];
+extern const char kAllowCrossOriginAuthPrompt[];
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+extern const char kAuthNegotiateDelegateByKdcPolicy[];
+#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+
+#if defined(OS_POSIX)
+extern const char kNtlmV2Enabled[];
+#endif // defined(OS_POSIX)
+
+#if defined(OS_CHROMEOS)
+extern const char kKerberosEnabled[];
+#endif
+
+extern const char kCertRevocationCheckingEnabled[];
+extern const char kCertRevocationCheckingRequiredLocalAnchors[];
+extern const char kSSLVersionMin[];
+extern const char kSSLVersionMax[];
+extern const char kCipherSuiteBlacklist[];
+extern const char kH2ClientCertCoalescingHosts[];
+extern const char kHSTSPolicyBypassList[];
+extern const char kTLS13HardeningForLocalAnchorsEnabled[];
+
+extern const char kBuiltInDnsClientEnabled[];
+extern const char kDnsOverHttpsMode[];
+extern const char kDnsOverHttpsTemplates[];
+
+extern const char kRegisteredProtocolHandlers[];
+extern const char kIgnoredProtocolHandlers[];
+extern const char kPolicyRegisteredProtocolHandlers[];
+extern const char kPolicyIgnoredProtocolHandlers[];
+extern const char kCustomHandlersEnabled[];
+
+#if defined(OS_MACOSX)
+extern const char kUserRemovedLoginItem[];
+extern const char kChromeCreatedLoginItem[];
+extern const char kMigratedLoginItemPref[];
+extern const char kNotifyWhenAppsKeepChromeAlive[];
+#endif
+
+extern const char kBackgroundModeEnabled[];
+extern const char kHardwareAccelerationModeEnabled[];
+extern const char kHardwareAccelerationModePrevious[];
+
+extern const char kDevicePolicyRefreshRate[];
+
+extern const char kFactoryResetRequested[];
+extern const char kFactoryResetTPMFirmwareUpdateMode[];
+extern const char kDebuggingFeaturesRequested[];
+
+#if defined(OS_CHROMEOS)
+extern const char kSigninScreenTimezone[];
+extern const char kResolveDeviceTimezoneByGeolocation[];
+extern const char kResolveDeviceTimezoneByGeolocationMethod[];
+extern const char kSystemTimezoneAutomaticDetectionPolicy[];
+#endif // defined(OS_CHROMEOS)
+
+extern const char kEnableMediaRouter[];
+#if !defined(OS_ANDROID)
+extern const char kShowCastIconInToolbar[];
+#endif // !defined(OS_ANDROID)
+
+#if !defined(OS_ANDROID)
+extern const char kRelaunchNotification[];
+extern const char kRelaunchNotificationPeriod[];
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+extern const char kRelaunchHeadsUpPeriod[];
+#endif // defined(OS_CHROMEOS)
+
+#if !defined(OS_ANDROID)
+extern const char kAttemptedToEnableAutoupdate[];
+
+extern const char kMediaGalleriesUniqueId[];
+extern const char kMediaGalleriesRememberedGalleries[];
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+extern const char kPolicyPinnedLauncherApps[];
+extern const char kShelfDefaultPinLayoutRolls[];
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_WIN)
+extern const char kNetworkProfileWarningsLeft[];
+extern const char kNetworkProfileLastWarningTime[];
+#endif
+
+#if defined(OS_CHROMEOS)
+extern const char kRLZBrand[];
+extern const char kRLZDisabled[];
+#endif
+
+#if BUILDFLAG(ENABLE_APP_LIST)
+extern const char kAppListLocalState[];
+#endif // BUILDFLAG(ENABLE_APP_LIST)
+
+extern const char kAppShortcutsVersion[];
+
+extern const char kDRMSalt[];
+extern const char kEnableDRM[];
+
+extern const char kWatchdogExtensionActive[];
+
+#if defined(OS_ANDROID)
+extern const char kPartnerBookmarkMappings[];
+#endif // defined(OS_ANDROID)
+
+extern const char kQuickCheckEnabled[];
+extern const char kBrowserGuestModeEnabled[];
+extern const char kBrowserGuestModeEnforced[];
+extern const char kBrowserAddPersonEnabled[];
+extern const char kForceBrowserSignin[];
+extern const char kSigninAllowedOnNextStartup[];
+
+extern const char kCryptAuthDeviceId[];
+extern const char kEasyUnlockHardlockState[];
+extern const char kEasyUnlockLocalStateTpmKeys[];
+extern const char kEasyUnlockLocalStateUserPrefs[];
+
+extern const char kRecoveryComponentNeedsElevation[];
+
+extern const char kRegisteredSupervisedUserWhitelists[];
+
+#if !defined(OS_ANDROID)
+extern const char kCloudReportingEnabled[];
+extern const char kCloudExtensionRequestEnabled[];
+extern const char kCloudExtensionRequestIds[];
+#endif
+
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+extern const char kRestartInBackground[];
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+extern const char kAnimationPolicy[];
+extern const char kSecurityKeyPermitAttestation[];
+#endif
+
+extern const char kBackgroundTracingLastUpload[];
+
+extern const char kAllowDinosaurEasterEgg[];
+
+#if defined(OS_ANDROID)
+extern const char kClickedUpdateMenuItem[];
+extern const char kLatestVersionWhenClickedUpdateMenuItem[];
+#endif
+
+extern const char kMediaRouterCloudServicesPrefSet[];
+extern const char kMediaRouterEnableCloudServices[];
+extern const char kMediaRouterFirstRunFlowAcknowledged[];
+extern const char kMediaRouterMediaRemotingEnabled[];
+extern const char kMediaRouterTabMirroringSources[];
+
+extern const char kOriginTrialPublicKey[];
+extern const char kOriginTrialDisabledFeatures[];
+extern const char kOriginTrialDisabledTokens[];
+
+extern const char kComponentUpdatesEnabled[];
+
+#if defined(OS_ANDROID)
+extern const char kLocationSettingsBackoffLevelDSE[];
+extern const char kLocationSettingsBackoffLevelDefault[];
+extern const char kLocationSettingsNextShowDSE[];
+extern const char kLocationSettingsNextShowDefault[];
+
+extern const char kSearchGeolocationDisclosureDismissed[];
+extern const char kSearchGeolocationDisclosureShownCount[];
+extern const char kSearchGeolocationDisclosureLastShowDate[];
+extern const char kSearchGeolocationPreDisclosureMetricsRecorded[];
+extern const char kSearchGeolocationPostDisclosureMetricsRecorded[];
+#endif
+
+extern const char kDSEGeolocationSettingDeprecated[];
+
+extern const char kDSEPermissionsSettings[];
+extern const char kDSEWasDisabledByPolicy[];
+
+extern const char kWebShareVisitedTargets[];
+
+#if defined(OS_WIN)
+// Only used in branded builds.
+extern const char kIncompatibleApplications[];
+extern const char kModuleBlacklistCacheMD5Digest[];
+extern const char kThirdPartyBlockingEnabled[];
+#endif // defined(OS_WIN)
+
+// Windows mitigation policies.
+#if defined(OS_WIN)
+extern const char kRendererCodeIntegrityEnabled[];
+#endif // defined(OS_WIN)
+
+extern const char kSettingsResetPromptPromptWave[];
+extern const char kSettingsResetPromptLastTriggeredForDefaultSearch[];
+extern const char kSettingsResetPromptLastTriggeredForStartupUrls[];
+extern const char kSettingsResetPromptLastTriggeredForHomepage[];
+
+#if defined(OS_ANDROID)
+extern const char kClipboardLastModifiedTime[];
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+extern const char kOfflineUsageStartObserved[];
+extern const char kOfflineUsageOnlineObserved[];
+extern const char kOfflineUsageOfflineObserved[];
+extern const char kPrefetchUsageEnabledObserved[];
+extern const char kPrefetchUsageFetchObserved[];
+extern const char kPrefetchUsageOpenObserved[];
+extern const char kOfflineUsageTrackingDay[];
+extern const char kOfflineUsageUnusedCount[];
+extern const char kOfflineUsageStartedCount[];
+extern const char kOfflineUsageOfflineCount[];
+extern const char kOfflineUsageOnlineCount[];
+extern const char kOfflineUsageMixedCount[];
+extern const char kPrefetchUsageEnabledCount[];
+extern const char kPrefetchUsageFetchedCount[];
+extern const char kPrefetchUsageOpenedCount[];
+extern const char kPrefetchUsageMixedCount[];
+#endif
+
+extern const char kMediaEngagementSchemaVersion[];
+
+// Preferences for recording metrics about tab and window usage.
+extern const char kTabStatsTotalTabCountMax[];
+extern const char kTabStatsMaxTabsPerWindow[];
+extern const char kTabStatsWindowCountMax[];
+extern const char kTabStatsDailySample[];
+
+extern const char kUnsafelyTreatInsecureOriginAsSecure[];
+
+extern const char kIsolateOrigins[];
+extern const char kSitePerProcess[];
+extern const char kUserTriggeredIsolatedOrigins[];
+extern const char kWebDriverOverridesIncompatiblePolicies[];
+
+#if !defined(OS_ANDROID)
+extern const char kAutoplayAllowed[];
+extern const char kAutoplayWhitelist[];
+extern const char kBlockAutoplayEnabled[];
+#endif
+
+extern const char kNotificationNextPersistentId[];
+extern const char kNotificationNextTriggerTime[];
+
+extern const char kTabFreezingEnabled[];
+
+extern const char kEnterpriseHardwarePlatformAPIEnabled[];
+
+extern const char kSignedHTTPExchangeEnabled[];
+
+extern const char kAllowPopupsDuringPageUnload[];
+
+extern const char kAllowSyncXHRInPageDismissal[];
+
+#if defined(OS_ANDROID)
+extern const char kUsageStatsEnabled[];
+#endif
+
+#if defined(OS_CHROMEOS)
+extern const char kClientCertificateManagementAllowed[];
+extern const char kCACertificateManagementAllowed[];
+#endif
+
+#if BUILDFLAG(BUILTIN_CERT_VERIFIER_POLICY_SUPPORTED)
+extern const char kBuiltinCertificateVerifierEnabled[];
+#endif
+
+extern const char kSharingVapidKey[];
+extern const char kSharingSyncedDevices[];
+extern const char kSharingFCMRegistration[];
+extern const char kSharingLocalSharingInfo[];
+
+#if !defined(OS_ANDROID)
+extern const char kHatsSurveyMetadata[];
+#endif // !defined(OS_ANDROID)
+
+extern const char kCorsMitigationList[];
+extern const char kCorsLegacyModeEnabled[];
+
+extern const char kExternalProtocolDialogShowAlwaysOpenCheckbox[];
+
} // namespace prefs
#endif // CHROME_COMMON_PREF_NAMES_H_
diff --git a/chromium/chrome/common/pref_names_util.cc b/chromium/chrome/common/pref_names_util.cc
new file mode 100644
index 00000000000..3ab666fb480
--- /dev/null
+++ b/chromium/chrome/common/pref_names_util.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/pref_names_util.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "ui/native_theme/native_theme.h"
+
+namespace {
+
+// Adds !important to all captions styles. They should always override any
+// styles added by the video author or by a user stylesheet. This is because in
+// Chrome, there is an option to turn off captions styles, so any time the
+// captions are on, the styles should take priority.
+std::string AddCSSImportant(std::string css_string) {
+ return css_string + " !important";
+}
+
+} // namespace
+
+namespace pref_names_util {
+
+const char kWebKitFontPrefPrefix[] = "webkit.webprefs.fonts.";
+
+bool ParseFontNamePrefPath(const std::string& pref_path,
+ std::string* generic_family,
+ std::string* script) {
+ if (!base::StartsWith(pref_path, kWebKitFontPrefPrefix,
+ base::CompareCase::SENSITIVE))
+ return false;
+
+ size_t start = strlen(kWebKitFontPrefPrefix);
+ size_t pos = pref_path.find('.', start);
+ if (pos == std::string::npos || pos + 1 == pref_path.length())
+ return false;
+ if (generic_family)
+ *generic_family = pref_path.substr(start, pos - start);
+ if (script)
+ *script = pref_path.substr(pos + 1);
+ return true;
+}
+
+base::Optional<ui::CaptionStyle> GetCaptionStyleFromPrefs(PrefService* prefs) {
+ if (!prefs) {
+ return base::nullopt;
+ }
+
+ ui::CaptionStyle style;
+
+ style.text_size =
+ AddCSSImportant(prefs->GetString(prefs::kAccessibilityCaptionsTextSize));
+ style.font_family =
+ AddCSSImportant(prefs->GetString(prefs::kAccessibilityCaptionsTextFont));
+ if (!prefs->GetString(prefs::kAccessibilityCaptionsTextColor).empty()) {
+ std::string text_color = base::StringPrintf(
+ "rgba(%s,%s)",
+ prefs->GetString(prefs::kAccessibilityCaptionsTextColor).c_str(),
+ base::NumberToString(
+ prefs->GetInteger(prefs::kAccessibilityCaptionsTextOpacity) / 100.0)
+ .c_str());
+ style.text_color = AddCSSImportant(text_color);
+ }
+
+ if (!prefs->GetString(prefs::kAccessibilityCaptionsBackgroundColor).empty()) {
+ std::string background_color = base::StringPrintf(
+ "rgba(%s,%s)",
+ prefs->GetString(prefs::kAccessibilityCaptionsBackgroundColor).c_str(),
+ base::NumberToString(
+ prefs->GetInteger(prefs::kAccessibilityCaptionsBackgroundOpacity) /
+ 100.0)
+ .c_str());
+ style.background_color = AddCSSImportant(background_color);
+ }
+
+ style.text_shadow = AddCSSImportant(
+ prefs->GetString(prefs::kAccessibilityCaptionsTextShadow));
+
+ return style;
+}
+
+} // namespace pref_names_util
diff --git a/chromium/chrome/common/pref_names_util.h b/chromium/chrome/common/pref_names_util.h
new file mode 100644
index 00000000000..b0c0177f4f6
--- /dev/null
+++ b/chromium/chrome/common/pref_names_util.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PREF_NAMES_UTIL_H_
+#define CHROME_COMMON_PREF_NAMES_UTIL_H_
+
+#include <string>
+
+#include "components/prefs/pref_service.h"
+#include "ui/native_theme/native_theme.h"
+
+namespace pref_names_util {
+
+// Prefs prefix for all font types. Ends in a period.
+extern const char kWebKitFontPrefPrefix[];
+
+// Extracts the generic family and script from font name pref path |pref_path|.
+// For example, if |pref_path| is "webkit.webprefs.fonts.serif.Hang", returns
+// true and sets |generic_family| to "serif" and |script| to "Hang".
+bool ParseFontNamePrefPath(const std::string& pref_path,
+ std::string* generic_family,
+ std::string* script);
+
+// Constructs the CaptionStyle struct from the caption-related preferences.
+base::Optional<ui::CaptionStyle> GetCaptionStyleFromPrefs(PrefService* prefs);
+
+} // namespace pref_names_util
+
+#endif // CHROME_COMMON_PREF_NAMES_UTIL_H_
diff --git a/chromium/chrome/common/pref_names_util_unittest.cc b/chromium/chrome/common/pref_names_util_unittest.cc
new file mode 100644
index 00000000000..9f215ea5808
--- /dev/null
+++ b/chromium/chrome/common/pref_names_util_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/pref_names_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void ExpectNoParse(const std::string& path) {
+ EXPECT_FALSE(pref_names_util::ParseFontNamePrefPath(path, NULL, NULL));
+}
+
+void ExpectParse(const std::string& path,
+ const std::string& expected_generic_family,
+ const std::string& expected_script)
+{
+ std::string generic_family;
+ std::string script;
+
+ ASSERT_TRUE(pref_names_util::ParseFontNamePrefPath(path, &generic_family,
+ &script));
+ EXPECT_EQ(expected_generic_family, generic_family);
+ EXPECT_EQ(expected_script, script);
+}
+
+} // namespace
+
+TEST(PrefNamesUtilTest, Basic) {
+ ExpectNoParse(std::string());
+ ExpectNoParse(".");
+ ExpectNoParse(".....");
+ ExpectNoParse("webkit.webprefs.fonts.");
+ ExpectNoParse("webkit.webprefs.fonts..");
+ ExpectNoParse("webkit.webprefs.fontsfoobar.standard.Hrkt");
+ ExpectNoParse("foobar.webprefs.fonts.standard.Hrkt");
+ ExpectParse("webkit.webprefs.fonts.standard.Hrkt", "standard", "Hrkt");
+ ExpectParse("webkit.webprefs.fonts.standard.Hrkt.", "standard", "Hrkt.");
+ ExpectParse("webkit.webprefs.fonts.standard.Hrkt.Foobar", "standard",
+ "Hrkt.Foobar");
+
+ // We don't particularly care about the parsed family and script for these
+ // inputs, but just want to make sure it does something reasonable. Returning
+ // false may also be an option.
+ ExpectParse("webkit.webprefs.fonts...", std::string(), ".");
+ ExpectParse("webkit.webprefs.fonts....", std::string(), "..");
+
+ // Check that passing NULL output params is okay.
+ EXPECT_TRUE(pref_names_util::ParseFontNamePrefPath(
+ "webkit.webprefs.fonts.standard.Hrkt", NULL, NULL));
+}
diff --git a/chromium/chrome/common/prerender_messages.h b/chromium/chrome/common/prerender_messages.h
new file mode 100644
index 00000000000..ca7d57d1260
--- /dev/null
+++ b/chromium/chrome/common/prerender_messages.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PRERENDER_MESSAGES_H_
+#define CHROME_COMMON_PRERENDER_MESSAGES_H_
+
+#include <stdint.h>
+
+#include "chrome/common/prerender_types.h"
+#include "content/public/common/referrer.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_param_traits.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+#include "url/ipc/url_param_traits.h"
+#include "url/origin.h"
+
+#define IPC_MESSAGE_START PrerenderMsgStart
+
+IPC_ENUM_TRAITS_MAX_VALUE(prerender::PrerenderMode,
+ prerender::PRERENDER_MODE_COUNT - 1)
+
+// PrerenderLinkManager Messages
+// These are messages sent from the renderer to the browser in
+// relation to <link rel=prerender> elements.
+
+IPC_STRUCT_BEGIN(PrerenderAttributes)
+ IPC_STRUCT_MEMBER(GURL, url)
+ IPC_STRUCT_MEMBER(uint32_t, rel_types)
+IPC_STRUCT_END()
+
+// Notifies of the insertion of a <link rel=prerender> element in the
+// document.
+IPC_MESSAGE_CONTROL(PrerenderHostMsg_AddLinkRelPrerender,
+ int /* prerender_id, assigned by WebPrerendererClient */,
+ PrerenderAttributes,
+ content::Referrer,
+ url::Origin /* initiator_origin */,
+ gfx::Size,
+ int /* render_view_route_id of launcher */)
+
+// Notifies on removal of a <link rel=prerender> element from the document.
+IPC_MESSAGE_CONTROL1(PrerenderHostMsg_CancelLinkRelPrerender,
+ int /* prerender_id, assigned by WebPrerendererClient */)
+
+// Notifies on unloading a <link rel=prerender> element from a frame.
+IPC_MESSAGE_CONTROL1(PrerenderHostMsg_AbandonLinkRelPrerender,
+ int /* prerender_id, assigned by WebPrerendererClient */)
+
+// Sent by the renderer process to notify that the resource prefetcher has
+// discovered all possible subresources and issued requests for them.
+IPC_MESSAGE_CONTROL0(PrerenderHostMsg_PrefetchFinished)
+
+// PrerenderDispatcher Messages
+// These are messages sent from the browser to the renderer in relation to
+// running prerenders.
+
+// Tells a renderer if it's currently being prerendered. Must only be set
+// before any navigation occurs, and only set to NO_PRERENDER at most once after
+// that.
+IPC_MESSAGE_ROUTED2(PrerenderMsg_SetIsPrerendering,
+ prerender::PrerenderMode,
+ std::string /* histogram_prefix */)
+
+#endif // CHROME_COMMON_PRERENDER_MESSAGES_H_
diff --git a/chromium/chrome/common/prerender_types.h b/chromium/chrome/common/prerender_types.h
new file mode 100644
index 00000000000..e6c98dc86e4
--- /dev/null
+++ b/chromium/chrome/common/prerender_types.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PRERENDER_TYPES_H_
+#define CHROME_COMMON_PRERENDER_TYPES_H_
+
+namespace prerender {
+
+enum PrerenderMode {
+ // Neither prefetch nor prerender.
+ NO_PRERENDER = 0,
+
+ // Only used in tests. Can be removed after http://crbug.com/898955 is fixed.
+ DEPRECATED_FULL_PRERENDER = 1,
+
+ // Prefetch some network resources to warm up the cache.
+ PREFETCH_ONLY = 2,
+
+ PRERENDER_MODE_COUNT = 3,
+};
+
+} // namespace prerender
+
+#endif // CHROME_COMMON_PRERENDER_TYPES_H_
diff --git a/chromium/chrome/common/prerender_url_loader_throttle.cc b/chromium/chrome/common/prerender_url_loader_throttle.cc
new file mode 100644
index 00000000000..70c8c8312f6
--- /dev/null
+++ b/chromium/chrome/common/prerender_url_loader_throttle.cc
@@ -0,0 +1,217 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/prerender_url_loader_throttle.h"
+
+#include "base/bind.h"
+#include "build/build_config.h"
+#include "chrome/common/prerender_util.h"
+#include "content/public/common/content_constants.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/redirect_info.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/resource_response.h"
+
+namespace prerender {
+
+namespace {
+
+const char kPurposeHeaderName[] = "Purpose";
+const char kPurposeHeaderValue[] = "prefetch";
+
+void CancelPrerenderForUnsupportedMethod(
+ PrerenderURLLoaderThrottle::CancelerGetterCallback callback) {
+ chrome::mojom::PrerenderCanceler* canceler = std::move(callback).Run();
+ if (canceler)
+ canceler->CancelPrerenderForUnsupportedMethod();
+}
+
+void CancelPrerenderForUnsupportedScheme(
+ PrerenderURLLoaderThrottle::CancelerGetterCallback callback,
+ const GURL& url) {
+ chrome::mojom::PrerenderCanceler* canceler = std::move(callback).Run();
+ if (canceler)
+ canceler->CancelPrerenderForUnsupportedScheme(url);
+}
+
+void CancelPrerenderForSyncDeferredRedirect(
+ PrerenderURLLoaderThrottle::CancelerGetterCallback callback) {
+ chrome::mojom::PrerenderCanceler* canceler = std::move(callback).Run();
+ if (canceler)
+ canceler->CancelPrerenderForSyncDeferredRedirect();
+}
+
+// Returns true if the response has a "no-store" cache control header.
+bool IsNoStoreResponse(const network::ResourceResponseHead& response_head) {
+ return response_head.headers &&
+ response_head.headers->HasHeaderValue("cache-control", "no-store");
+}
+
+} // namespace
+
+PrerenderURLLoaderThrottle::PrerenderURLLoaderThrottle(
+ PrerenderMode mode,
+ const std::string& histogram_prefix,
+ CancelerGetterCallback canceler_getter,
+ scoped_refptr<base::SequencedTaskRunner> canceler_getter_task_runner)
+ : mode_(mode),
+ histogram_prefix_(histogram_prefix),
+ canceler_getter_(std::move(canceler_getter)),
+ canceler_getter_task_runner_(canceler_getter_task_runner) {
+}
+
+PrerenderURLLoaderThrottle::~PrerenderURLLoaderThrottle() {
+ if (destruction_closure_)
+ std::move(destruction_closure_).Run();
+}
+
+void PrerenderURLLoaderThrottle::PrerenderUsed() {
+ if (original_request_priority_)
+ delegate_->SetPriority(original_request_priority_.value());
+ if (deferred_)
+ delegate_->Resume();
+}
+
+void PrerenderURLLoaderThrottle::DetachFromCurrentSequence() {
+ // This method is only called for synchronous XHR from the main thread.
+ sync_xhr_ = true;
+}
+
+void PrerenderURLLoaderThrottle::WillStartRequest(
+ network::ResourceRequest* request,
+ bool* defer) {
+ if (mode_ == PREFETCH_ONLY) {
+ request->load_flags |= net::LOAD_PREFETCH;
+ request->cors_exempt_headers.SetHeader(kPurposeHeaderName,
+ kPurposeHeaderValue);
+ }
+
+ resource_type_ = static_cast<content::ResourceType>(request->resource_type);
+ // Abort any prerenders that spawn requests that use unsupported HTTP
+ // methods or schemes.
+ if (!IsValidHttpMethod(mode_, request->method)) {
+ // If this is a full prerender, cancel the prerender in response to
+ // invalid requests. For prefetches, cancel invalid requests but keep the
+ // prefetch going.
+ delegate_->CancelWithError(net::ERR_ABORTED);
+ if (mode_ == DEPRECATED_FULL_PRERENDER) {
+ canceler_getter_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(CancelPrerenderForUnsupportedMethod,
+ std::move(canceler_getter_)));
+ return;
+ }
+ }
+
+ if (request->resource_type !=
+ static_cast<int>(content::ResourceType::kMainFrame) &&
+ !DoesSubresourceURLHaveValidScheme(request->url)) {
+ // Destroying the prerender for unsupported scheme only for non-main
+ // resource to allow chrome://crash to actually crash in the
+ // *RendererCrash tests instead of being intercepted here. The
+ // unsupported scheme for the main resource is checked in
+ // WillRedirectRequest() and PrerenderContents::CheckURL(). See
+ // http://crbug.com/673771.
+ delegate_->CancelWithError(net::ERR_ABORTED);
+ canceler_getter_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(CancelPrerenderForUnsupportedScheme,
+ std::move(canceler_getter_), request->url));
+ return;
+ }
+
+#if defined(OS_ANDROID)
+ if (request->resource_type ==
+ static_cast<int>(content::ResourceType::kFavicon)) {
+ // Delay icon fetching until the contents are getting swapped in
+ // to conserve network usage in mobile devices.
+ *defer = true;
+ return;
+ }
+#else
+ // Priorities for prerendering requests are lowered, to avoid competing with
+ // other page loads, except on Android where this is less likely to be a
+ // problem. In some cases, this may negatively impact the performance of
+ // prerendering, see https://crbug.com/652746 for details.
+ // Requests with the IGNORE_LIMITS flag set (i.e., sync XHRs)
+ // should remain at MAXIMUM_PRIORITY.
+ if (request->load_flags & net::LOAD_IGNORE_LIMITS) {
+ DCHECK_EQ(request->priority, net::MAXIMUM_PRIORITY);
+ } else if (request->priority != net::IDLE) {
+ original_request_priority_ = request->priority;
+ request->priority = net::IDLE;
+ }
+#endif // OS_ANDROID
+
+ if (mode_ == PREFETCH_ONLY) {
+ detached_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(
+ content::kDefaultDetachableCancelDelayMs),
+ this, &PrerenderURLLoaderThrottle::OnTimedOut);
+ }
+}
+
+void PrerenderURLLoaderThrottle::WillRedirectRequest(
+ net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* defer,
+ std::vector<std::string>* /* to_be_removed_headers */,
+ net::HttpRequestHeaders* /* modified_headers */) {
+ redirect_count_++;
+ if (mode_ == PREFETCH_ONLY) {
+ RecordPrefetchResponseReceived(
+ histogram_prefix_, content::IsResourceTypeFrame(resource_type_),
+ true /* is_redirect */, IsNoStoreResponse(response_head));
+ }
+
+ std::string follow_only_when_prerender_shown_header;
+ if (response_head.headers) {
+ response_head.headers->GetNormalizedHeader(
+ kFollowOnlyWhenPrerenderShown,
+ &follow_only_when_prerender_shown_header);
+ }
+ // Abort any prerenders with requests which redirect to invalid schemes.
+ if (!DoesURLHaveValidScheme(redirect_info->new_url)) {
+ delegate_->CancelWithError(net::ERR_ABORTED);
+ canceler_getter_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(CancelPrerenderForUnsupportedScheme,
+ std::move(canceler_getter_), redirect_info->new_url));
+ } else if (follow_only_when_prerender_shown_header == "1" &&
+ resource_type_ != content::ResourceType::kMainFrame) {
+ // Only defer redirects with the Follow-Only-When-Prerender-Shown
+ // header. Do not defer redirects on main frame loads.
+ if (sync_xhr_) {
+ // Cancel on deferred synchronous requests. Those will
+ // indefinitely hang up a renderer process.
+ canceler_getter_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(CancelPrerenderForSyncDeferredRedirect,
+ std::move(canceler_getter_)));
+ delegate_->CancelWithError(net::ERR_ABORTED);
+ } else {
+ // Defer the redirect until the prerender is used or canceled.
+ *defer = true;
+ deferred_ = true;
+ }
+ }
+}
+
+void PrerenderURLLoaderThrottle::WillProcessResponse(
+ const GURL& response_url,
+ network::ResourceResponseHead* response_head,
+ bool* defer) {
+ if (mode_ != PREFETCH_ONLY)
+ return;
+
+ bool is_main_resource = content::IsResourceTypeFrame(resource_type_);
+ RecordPrefetchResponseReceived(histogram_prefix_, is_main_resource,
+ true /* is_redirect */,
+ IsNoStoreResponse(*response_head));
+ RecordPrefetchRedirectCount(histogram_prefix_, is_main_resource,
+ redirect_count_);
+}
+
+void PrerenderURLLoaderThrottle::OnTimedOut() {
+ delegate_->CancelWithError(net::ERR_ABORTED);
+}
+
+} // namespace prerender
diff --git a/chromium/chrome/common/prerender_url_loader_throttle.h b/chromium/chrome/common/prerender_url_loader_throttle.h
new file mode 100644
index 00000000000..bf8d9a90a65
--- /dev/null
+++ b/chromium/chrome/common/prerender_url_loader_throttle.h
@@ -0,0 +1,83 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PRERENDER_URL_LOADER_THROTTLE_H_
+#define CHROME_COMMON_PRERENDER_URL_LOADER_THROTTLE_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequenced_task_runner.h"
+#include "base/timer/timer.h"
+#include "chrome/common/prerender.mojom.h"
+#include "chrome/common/prerender_types.h"
+#include "content/public/common/resource_type.h"
+#include "net/base/request_priority.h"
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+
+namespace prerender {
+
+class PrerenderURLLoaderThrottle
+ : public blink::URLLoaderThrottle,
+ public base::SupportsWeakPtr<PrerenderURLLoaderThrottle> {
+ public:
+ // If the throttle needs to cancel the prerender, it will run
+ // |canceler_getter| on |canceler_getter_task_runner| to do so.
+ using CancelerGetterCallback =
+ base::OnceCallback<chrome::mojom::PrerenderCanceler*()>;
+ PrerenderURLLoaderThrottle(
+ PrerenderMode mode,
+ const std::string& histogram_prefix,
+ CancelerGetterCallback canceler_getter,
+ scoped_refptr<base::SequencedTaskRunner> canceler_getter_task_runner);
+ ~PrerenderURLLoaderThrottle() override;
+
+ // Called when the prerender is used. This will unpaused requests and set the
+ // priorities to the original value.
+ void PrerenderUsed();
+
+ void set_destruction_closure(base::OnceClosure closure) {
+ destruction_closure_ = std::move(closure);
+ }
+
+ private:
+ // blink::URLLoaderThrottle implementation.
+ void DetachFromCurrentSequence() override;
+ void WillStartRequest(network::ResourceRequest* request,
+ bool* defer) override;
+ void WillRedirectRequest(net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* defer,
+ std::vector<std::string>* to_be_removed_headers,
+ net::HttpRequestHeaders* modified_headers) override;
+ void WillProcessResponse(const GURL& response_url,
+ network::ResourceResponseHead* response_head,
+ bool* defer) override;
+
+ void OnTimedOut();
+
+ PrerenderMode mode_;
+ std::string histogram_prefix_;
+
+ bool deferred_ = false;
+ bool sync_xhr_ = false;
+ int redirect_count_ = 0;
+ content::ResourceType resource_type_;
+
+ CancelerGetterCallback canceler_getter_;
+ scoped_refptr<base::SequencedTaskRunner> canceler_getter_task_runner_;
+
+ // The throttle changes most request priorities to IDLE during prerendering.
+ // The priority is reset back to the original priority when prerendering is
+ // finished.
+ base::Optional<net::RequestPriority> original_request_priority_;
+
+ base::OnceClosure destruction_closure_;
+
+ base::OneShotTimer detached_timer_;
+};
+
+} // namespace prerender
+
+#endif // CHROME_COMMON_PRERENDER_URL_LOADER_THROTTLE_H_
diff --git a/chromium/chrome/common/prerender_util.cc b/chromium/chrome/common/prerender_util.cc
new file mode 100644
index 00000000000..70282cf5107
--- /dev/null
+++ b/chromium/chrome/common/prerender_util.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/prerender_util.h"
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/common/constants.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace prerender {
+
+namespace {
+
+// Valid HTTP methods for both prefetch and prerendering.
+const char* const kValidHttpMethods[] = {
+ "GET", "HEAD",
+};
+
+// Additional valid HTTP methods for prerendering.
+const char* const kValidHttpMethodsForPrerendering[] = {
+ "OPTIONS", "POST", "TRACE",
+};
+
+// This enum is used to define the buckets for the
+// "Prerender.NoStatePrefetchResourceCount" histogram family.
+// Hence, existing enumerated constants should never be deleted or reordered,
+// and new constants should only be appended at the end of the enumeration.
+enum NoStatePrefetchResponseType {
+ NO_STORE = 1 << 0,
+ REDIRECT = 1 << 1,
+ MAIN_RESOURCE = 1 << 2,
+ NO_STATE_PREFETCH_RESPONSE_TYPE_COUNT = 1 << 3
+};
+
+int GetResourceType(bool is_main_resource, bool is_redirect, bool is_no_store) {
+ return (is_no_store * NO_STORE) + (is_redirect * REDIRECT) +
+ (is_main_resource * MAIN_RESOURCE);
+}
+
+} // namespace
+
+const char kFollowOnlyWhenPrerenderShown[] = "follow-only-when-prerender-shown";
+
+bool DoesURLHaveValidScheme(const GURL& url) {
+ return (url.SchemeIsHTTPOrHTTPS() ||
+ url.SchemeIs(extensions::kExtensionScheme) ||
+ url.SchemeIs(url::kDataScheme));
+}
+
+bool DoesSubresourceURLHaveValidScheme(const GURL& url) {
+ return DoesURLHaveValidScheme(url) || url == url::kAboutBlankURL;
+}
+
+bool IsValidHttpMethod(PrerenderMode prerender_mode,
+ const std::string& method) {
+ DCHECK_NE(prerender_mode, NO_PRERENDER);
+ // |method| has been canonicalized to upper case at this point so we can just
+ // compare them.
+ DCHECK_EQ(method, base::ToUpperASCII(method));
+ for (auto* valid_method : kValidHttpMethods) {
+ if (method == valid_method)
+ return true;
+ }
+
+ if (prerender_mode == PREFETCH_ONLY)
+ return false;
+
+ for (auto* valid_method : kValidHttpMethodsForPrerendering) {
+ if (method == valid_method)
+ return true;
+ }
+
+ return false;
+}
+
+std::string ComposeHistogramName(const std::string& prefix_type,
+ const std::string& name) {
+ if (prefix_type.empty())
+ return std::string("Prerender.") + name;
+ return std::string("Prerender.") + prefix_type + std::string("_") + name;
+}
+
+void RecordPrefetchResponseReceived(const std::string& histogram_prefix,
+ bool is_main_resource,
+ bool is_redirect,
+ bool is_no_store) {
+ int sample = GetResourceType(is_main_resource, is_redirect, is_no_store);
+ std::string histogram_name =
+ ComposeHistogramName(histogram_prefix, "NoStatePrefetchResponseTypes");
+ base::UmaHistogramExactLinear(histogram_name, sample,
+ NO_STATE_PREFETCH_RESPONSE_TYPE_COUNT);
+}
+
+void RecordPrefetchRedirectCount(const std::string& histogram_prefix,
+ bool is_main_resource,
+ int redirect_count) {
+ const int kMaxRedirectCount = 10;
+ std::string histogram_base_name = base::StringPrintf(
+ "NoStatePrefetch%sResourceRedirects", is_main_resource ? "Main" : "Sub");
+ std::string histogram_name =
+ ComposeHistogramName(histogram_prefix, histogram_base_name);
+ base::UmaHistogramExactLinear(histogram_name, redirect_count,
+ kMaxRedirectCount);
+}
+
+} // namespace prerender
diff --git a/chromium/chrome/common/prerender_util.h b/chromium/chrome/common/prerender_util.h
new file mode 100644
index 00000000000..4fa1436ccb6
--- /dev/null
+++ b/chromium/chrome/common/prerender_util.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PRERENDER_UTIL_H_
+#define CHROME_COMMON_PRERENDER_UTIL_H_
+
+#include <string>
+
+#include "chrome/common/prerender_types.h"
+
+class GURL;
+
+namespace prerender {
+extern const char kFollowOnlyWhenPrerenderShown[];
+
+// Returns true iff the scheme of the URL given is valid for prerendering.
+bool DoesURLHaveValidScheme(const GURL& url);
+
+// Returns true iff the scheme of the subresource URL given is valid for
+// prerendering.
+bool DoesSubresourceURLHaveValidScheme(const GURL& url);
+
+// Returns true iff the method given is valid for prerendering.
+bool IsValidHttpMethod(PrerenderMode prerender_mode, const std::string& method);
+
+std::string ComposeHistogramName(const std::string& prefix_type,
+ const std::string& name);
+
+// Called when a NoStatePrefetch request has received a response (including
+// redirects). May be called several times per resource, in case of redirects.
+void RecordPrefetchResponseReceived(const std::string& histogram_prefix,
+ bool is_main_resource,
+ bool is_redirect,
+ bool is_no_store);
+
+// Called when a NoStatePrefetch resource has been loaded. This is called only
+// once per resource, when all redirects have been resolved.
+void RecordPrefetchRedirectCount(const std::string& histogram_prefix,
+ bool is_main_resource,
+ int redirect_count);
+
+} // namespace prerender
+
+#endif // CHROME_COMMON_PRERENDER_UTIL_H_
diff --git a/chromium/chrome/common/process_singleton_lock_posix.cc b/chromium/chrome/common/process_singleton_lock_posix.cc
new file mode 100644
index 00000000000..9e44b935e01
--- /dev/null
+++ b/chromium/chrome/common/process_singleton_lock_posix.cc
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/process_singleton_lock_posix.h"
+
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+
+const char kProcessSingletonLockDelimiter = '-';
+
+bool ParseProcessSingletonLock(const base::FilePath& path,
+ std::string* hostname,
+ int* pid) {
+ base::FilePath target;
+ if (!base::ReadSymbolicLink(path, &target)) {
+ // The only errno that should occur is ENOENT.
+ if (errno != 0 && errno != ENOENT)
+ PLOG(ERROR) << "readlink(" << path.value() << ") failed";
+ }
+
+ std::string real_path = target.value();
+ if (real_path.empty())
+ return false;
+
+ std::string::size_type pos = real_path.rfind(kProcessSingletonLockDelimiter);
+
+ // If the path is not a symbolic link, or doesn't contain what we expect,
+ // bail.
+ if (pos == std::string::npos) {
+ *hostname = "";
+ *pid = -1;
+ return true;
+ }
+
+ *hostname = real_path.substr(0, pos);
+
+ const std::string& pid_str = real_path.substr(pos + 1);
+ if (!base::StringToInt(pid_str, pid))
+ *pid = -1;
+
+ return true;
+}
diff --git a/chromium/chrome/common/process_singleton_lock_posix.h b/chromium/chrome/common/process_singleton_lock_posix.h
new file mode 100644
index 00000000000..58584d9950c
--- /dev/null
+++ b/chromium/chrome/common/process_singleton_lock_posix.h
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PROCESS_SINGLETON_LOCK_POSIX_H_
+#define CHROME_COMMON_PROCESS_SINGLETON_LOCK_POSIX_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+
+// Extract the hostname and pid from the lock symlink. Returns true if the lock
+// existed. See ProcessSingleton for additional details.
+bool ParseProcessSingletonLock(const base::FilePath& path,
+ std::string* hostname,
+ int* pid);
+
+extern const char kProcessSingletonLockDelimiter;
+
+#endif // CHROME_COMMON_PROCESS_SINGLETON_LOCK_POSIX_H_
diff --git a/chromium/chrome/common/profiler/OWNERS b/chromium/chrome/common/profiler/OWNERS
new file mode 100644
index 00000000000..3ed2cd88c98
--- /dev/null
+++ b/chromium/chrome/common/profiler/OWNERS
@@ -0,0 +1,4 @@
+# COMPONENT: Internals>Metrics
+
+charliea@chromium.org
+wittman@chromium.org \ No newline at end of file
diff --git a/chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.cc b/chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.cc
new file mode 100644
index 00000000000..6f62d6a55cf
--- /dev/null
+++ b/chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.cc
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/profiler/main_thread_stack_sampling_profiler.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/threading/platform_thread.h"
+#include "chrome/common/thread_profiler.h"
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+#include "content/public/common/content_switches.h"
+
+namespace {
+
+// Returns the profiler appropriate for the current process.
+std::unique_ptr<ThreadProfiler> CreateThreadProfiler() {
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+
+ // The browser process has an empty process type.
+ // TODO(wittman): Do this for other process types too.
+ if (!command_line->HasSwitch(switches::kProcessType)) {
+ ThreadProfiler::SetBrowserProcessReceiverCallback(base::BindRepeating(
+ &metrics::CallStackProfileMetricsProvider::ReceiveProfile));
+ return ThreadProfiler::CreateAndStartOnMainThread();
+ }
+
+ // No other processes are currently supported.
+ return nullptr;
+}
+
+} // namespace
+
+MainThreadStackSamplingProfiler::MainThreadStackSamplingProfiler() {
+ sampling_profiler_ = CreateThreadProfiler();
+}
+
+// Note that it's important for the |sampling_profiler_| destructor to run, as
+// it ensures program correctness on shutdown. Without it, the profiler thread's
+// destruction can race with the profiled thread's destruction, which results in
+// the sampling thread attempting to profile the sampled thread after the
+// sampled thread has already been shut down.
+MainThreadStackSamplingProfiler::~MainThreadStackSamplingProfiler() = default;
diff --git a/chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.h b/chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.h
new file mode 100644
index 00000000000..4e868ae5e72
--- /dev/null
+++ b/chromium/chrome/common/profiler/main_thread_stack_sampling_profiler.h
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_PROFILER_MAIN_THREAD_STACK_SAMPLING_PROFILER_H_
+#define CHROME_COMMON_PROFILER_MAIN_THREAD_STACK_SAMPLING_PROFILER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/profiler/stack_sampling_profiler.h"
+
+class ThreadProfiler;
+
+// A wrapper class that begins profiling stack samples upon construction, and
+// ensures correct shutdown behavior on destruction. Should only be used on the
+// main thread of a process. Samples are collected for the thread of the current
+// process where this object is constructed, and only if profiling is enabled
+// for the thread. This data is used to understand startup performance behavior,
+// and the object should therefore be created as early during initialization as
+// possible.
+class MainThreadStackSamplingProfiler {
+ public:
+ MainThreadStackSamplingProfiler();
+ ~MainThreadStackSamplingProfiler();
+
+ private:
+ // A profiler that periodically samples stack traces. Used to understand
+ // thread and process startup behavior.
+ std::unique_ptr<ThreadProfiler> sampling_profiler_;
+
+ DISALLOW_COPY_AND_ASSIGN(MainThreadStackSamplingProfiler);
+};
+
+#endif // CHROME_COMMON_PROFILER_MAIN_THREAD_STACK_SAMPLING_PROFILER_H_
diff --git a/chromium/chrome/common/ref_counted_util.h b/chromium/chrome/common/ref_counted_util.h
new file mode 100644
index 00000000000..b8dd6e0680a
--- /dev/null
+++ b/chromium/chrome/common/ref_counted_util.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_REF_COUNTED_UTIL_H__
+#define CHROME_COMMON_REF_COUNTED_UTIL_H__
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+// RefCountedVector is just a vector wrapped up with
+// RefCountedThreadSafe.
+template<class T>
+class RefCountedVector
+ : public base::RefCountedThreadSafe<RefCountedVector<T> > {
+ public:
+ RefCountedVector() {}
+ explicit RefCountedVector(const std::vector<T>& initializer)
+ : data(initializer) {}
+
+ std::vector<T> data;
+
+ private:
+ friend class base::RefCountedThreadSafe<RefCountedVector<T>>;
+ ~RefCountedVector() {}
+
+ DISALLOW_COPY_AND_ASSIGN(RefCountedVector<T>);
+};
+
+#endif // CHROME_COMMON_REF_COUNTED_UTIL_H__
diff --git a/chromium/chrome/common/render_messages.h b/chromium/chrome/common/render_messages.h
new file mode 100644
index 00000000000..97bb5f3e7af
--- /dev/null
+++ b/chromium/chrome/common/render_messages.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_RENDER_MESSAGES_H_
+#define CHROME_COMMON_RENDER_MESSAGES_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/web_application_info_provider_param_traits.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "components/offline_pages/buildflags/buildflags.h"
+#include "content/public/common/webplugininfo.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_platform_file.h"
+#include "media/media_buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "url/gurl.h"
+#include "url/ipc/url_param_traits.h"
+#include "url/origin.h"
+
+// Singly-included section for enums and custom IPC traits.
+#ifndef INTERNAL_CHROME_COMMON_RENDER_MESSAGES_H_
+#define INTERNAL_CHROME_COMMON_RENDER_MESSAGES_H_
+
+
+#endif // INTERNAL_CHROME_COMMON_RENDER_MESSAGES_H_
+
+#define IPC_MESSAGE_START ChromeMsgStart
+
+//-----------------------------------------------------------------------------
+// RenderView messages
+// These are messages sent from the browser to the renderer process.
+
+// Tells the render frame to load all blocked plugins with the given identifier.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_LoadBlockedPlugins,
+ std::string /* identifier */)
+
+// Tells the renderer whether or not a file system access has been allowed.
+IPC_MESSAGE_ROUTED2(ChromeViewMsg_RequestFileSystemAccessAsyncResponse,
+ int /* request_id */,
+ bool /* allowed */)
+
+// JavaScript related messages -----------------------------------------------
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+// Message sent from the renderer to the browser to schedule to download the
+// page at a later time.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_DownloadPageLater)
+
+// Message sent from the renderer to the browser to indicate if download button
+// is being shown in error page.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_SetIsShowingDownloadButtonInErrorPage,
+ bool /* showing download button */)
+#endif
+
+//-----------------------------------------------------------------------------
+// Misc messages
+// These are messages sent from the renderer to the browser process.
+
+// Tells the browser that content in the current page was blocked due to the
+// user's content settings.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_ContentBlocked,
+ ContentSettingsType /* type of blocked content */,
+ base::string16 /* details on blocked content */)
+
+// Sent by the renderer process to check whether access to web databases is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_AllowDatabase,
+ int /* render_frame_id */,
+ url::Origin /* origin */,
+ GURL /* site_for_cookies */,
+ url::Origin /* top frame_origin */,
+ bool /* allowed */)
+
+// Sent by the renderer process to check whether access to DOM Storage is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL5_1(ChromeViewHostMsg_AllowDOMStorage,
+ int /* render_frame_id */,
+ url::Origin /* origin */,
+ GURL /* site_for_cookies */,
+ url::Origin /* top frame_origin */,
+ bool /* if true local storage, otherwise session */,
+ bool /* allowed */)
+
+// Sent by the renderer process to check whether access to FileSystem is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_RequestFileSystemAccessSync,
+ int /* render_frame_id */,
+ url::Origin /* origin */,
+ GURL /* site_for_cookies */,
+ url::Origin /* top frame_origin */,
+ bool /* allowed */)
+
+// Sent by the renderer process to check whether access to FileSystem is
+// granted by content settings.
+IPC_MESSAGE_CONTROL5(ChromeViewHostMsg_RequestFileSystemAccessAsync,
+ int /* render_frame_id */,
+ int /* request_id */,
+ url::Origin /* origin */,
+ GURL /* site_for_cookies */,
+ url::Origin /* top frame_origin */)
+
+// Sent by the renderer process to check whether access to Indexed DB is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_AllowIndexedDB,
+ int /* render_frame_id */,
+ url::Origin /* origin */,
+ GURL /* site_for_cookies */,
+ url::Origin /* top frame_origin */,
+ bool /* allowed */)
+
+// Sent by the renderer process to check whether access to CacheStorage is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_AllowCacheStorage,
+ int /* render_frame_id */,
+ url::Origin /* origin */,
+ GURL /* site_for_cookies */,
+ url::Origin /* top frame_origin */,
+ bool /* allowed */)
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// Sent by the renderer to check if crash reporting is enabled.
+IPC_SYNC_MESSAGE_CONTROL0_1(ChromeViewHostMsg_IsCrashReportingEnabled,
+ bool /* enabled */)
+#endif
+
+// Tells the browser to open a PDF file in a new tab. Used when no PDF Viewer is
+// available, and user clicks to view PDF.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_OpenPDF, GURL /* url */)
+
+#endif // CHROME_COMMON_RENDER_MESSAGES_H_
diff --git a/chromium/chrome/common/safe_browsing/DEPS b/chromium/chrome/common/safe_browsing/DEPS
new file mode 100644
index 00000000000..c191816456b
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/safe_browsing",
+ "+third_party/protobuf",
+ "+third_party/unrar",
+ "+third_party/zlib",
+]
diff --git a/chromium/chrome/common/safe_browsing/OWNERS b/chromium/chrome/common/safe_browsing/OWNERS
new file mode 100644
index 00000000000..1b014cbc05c
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/OWNERS
@@ -0,0 +1,23 @@
+drubery@chromium.org
+nparker@chromium.org
+vakh@chromium.org
+
+# This is for the common case of adding or renaming files. If you're doing
+# structural changes, use usual OWNERS rules.
+per-file BUILD.gn=*
+
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
+
+# For security review of IPC-to-protobuf bridge.
+per-file *protobuf_message*.h=set noparent
+per-file *protobuf_message*.h=file://ipc/SECURITY_OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_param_traits*.*=set noparent
+per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
+
+# COMPONENT: Services>Safebrowsing
diff --git a/chromium/chrome/common/safe_browsing/archive_analyzer_results.cc b/chromium/chrome/common/safe_browsing/archive_analyzer_results.cc
new file mode 100644
index 00000000000..686b560aa0b
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/archive_analyzer_results.cc
@@ -0,0 +1,182 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains the archive file analysis implementation for download
+// protection, which runs in a sandboxed utility process.
+
+#include "chrome/common/safe_browsing/archive_analyzer_results.h"
+
+#include "base/files/file.h"
+#include "base/i18n/streaming_utf8_validator.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/stl_util.h"
+#include "build/build_config.h"
+#include "chrome/common/safe_browsing/archive_analyzer_results.h"
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/download_type_util.h"
+#include "chrome/common/safe_browsing/file_type_policies.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+
+#if defined(OS_MACOSX)
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+#include "base/containers/span.h"
+#include "chrome/common/safe_browsing/disk_image_type_sniffer_mac.h"
+#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
+#endif // OS_MACOSX
+
+namespace safe_browsing {
+
+namespace {
+
+void SetLengthAndDigestForContainedFile(
+ const base::FilePath& path,
+ base::File* temp_file,
+ int file_length,
+ ClientDownloadRequest::ArchivedBinary* archived_binary) {
+ std::string file_basename(path.BaseName().AsUTF8Unsafe());
+ if (base::StreamingUtf8Validator::Validate(file_basename))
+ archived_binary->set_file_basename(file_basename);
+ archived_binary->set_length(file_length);
+
+ std::unique_ptr<crypto::SecureHash> hasher =
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256);
+
+ const size_t kReadBufferSize = 4096;
+ char block[kReadBufferSize];
+
+ int bytes_read_previously = 0;
+ temp_file->Seek(base::File::Whence::FROM_BEGIN, 0);
+ while (true) {
+ int bytes_read_now = temp_file->ReadAtCurrentPos(block, kReadBufferSize);
+
+ if (bytes_read_previously + bytes_read_now > file_length)
+ bytes_read_now = file_length - bytes_read_previously;
+
+ if (bytes_read_now <= 0)
+ break;
+
+ hasher->Update(block, bytes_read_now);
+ bytes_read_previously += bytes_read_now;
+ }
+
+ uint8_t digest[crypto::kSHA256Length];
+ hasher->Finish(digest, base::size(digest));
+ archived_binary->mutable_digests()->set_sha256(digest, base::size(digest));
+}
+
+void AnalyzeContainedBinary(
+ const scoped_refptr<BinaryFeatureExtractor>& binary_feature_extractor,
+ base::File* temp_file,
+ ClientDownloadRequest::ArchivedBinary* archived_binary) {
+ if (!binary_feature_extractor->ExtractImageFeaturesFromFile(
+ temp_file->Duplicate(), BinaryFeatureExtractor::kDefaultOptions,
+ archived_binary->mutable_image_headers(),
+ archived_binary->mutable_signature()->mutable_signed_data())) {
+ archived_binary->clear_image_headers();
+ archived_binary->clear_signature();
+ } else if (!archived_binary->signature().signed_data_size()) {
+ // No SignedData blobs were extracted, so clear the
+ // signature field.
+ archived_binary->clear_signature();
+ }
+}
+
+} // namespace
+
+ArchiveAnalyzerResults::ArchiveAnalyzerResults()
+ : success(false),
+ has_executable(false),
+ has_archive(false),
+ file_count(0),
+ directory_count(0) {}
+
+ArchiveAnalyzerResults::ArchiveAnalyzerResults(
+ const ArchiveAnalyzerResults& other) = default;
+
+ArchiveAnalyzerResults::~ArchiveAnalyzerResults() {}
+
+void UpdateArchiveAnalyzerResultsWithFile(base::FilePath path,
+ base::File* file,
+ int file_length,
+ bool is_encrypted,
+ ArchiveAnalyzerResults* results) {
+ scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor(
+ new BinaryFeatureExtractor());
+ bool current_entry_is_executable;
+
+#if defined(OS_MACOSX)
+ uint32_t magic;
+ file->Read(0, reinterpret_cast<char*>(&magic), sizeof(uint32_t));
+
+ char dmg_header[DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()];
+ file->Read(0, dmg_header,
+ DiskImageTypeSnifferMac::AppleDiskImageTrailerSize());
+
+ current_entry_is_executable =
+ FileTypePolicies::GetInstance()->IsCheckedBinaryFile(path) ||
+ MachOImageReader::IsMachOMagicValue(magic) ||
+ DiskImageTypeSnifferMac::IsAppleDiskImageTrailer(
+ base::span<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(dmg_header),
+ DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()));
+
+ // We can skip checking the trailer if we already know the file is executable.
+ if (!current_entry_is_executable) {
+ char trailer[DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()];
+ file->Seek(base::File::Whence::FROM_END,
+ DiskImageTypeSnifferMac::AppleDiskImageTrailerSize());
+ file->ReadAtCurrentPos(
+ trailer, DiskImageTypeSnifferMac::AppleDiskImageTrailerSize());
+ current_entry_is_executable =
+ DiskImageTypeSnifferMac::IsAppleDiskImageTrailer(
+ base::span<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(trailer),
+ DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()));
+ }
+
+#else
+ current_entry_is_executable =
+ FileTypePolicies::GetInstance()->IsCheckedBinaryFile(path);
+#endif // OS_MACOSX
+
+ if (FileTypePolicies::GetInstance()->IsArchiveFile(path)) {
+ DVLOG(2) << "Downloaded a zipped archive: " << path.value();
+ results->has_archive = true;
+ results->archived_archive_filenames.push_back(path.BaseName());
+ ClientDownloadRequest::ArchivedBinary* archived_archive =
+ results->archived_binary.Add();
+ archived_archive->set_download_type(ClientDownloadRequest::ARCHIVE);
+ archived_archive->set_is_encrypted(is_encrypted);
+ SetLengthAndDigestForContainedFile(path, file, file_length,
+ archived_archive);
+ } else if (current_entry_is_executable) {
+#if defined(OS_MACOSX)
+ // This check prevents running analysis on .app files since they are
+ // really just directories and will cause binary feature extraction
+ // to fail.
+ if (path.Extension().compare(".app") == 0) {
+ DVLOG(2) << "Downloaded a zipped .app directory: " << path.value();
+ } else {
+#endif // OS_MACOSX
+ DVLOG(2) << "Downloaded a zipped executable: " << path.value();
+ results->has_executable = true;
+ ClientDownloadRequest::ArchivedBinary* archived_binary =
+ results->archived_binary.Add();
+ archived_binary->set_is_encrypted(is_encrypted);
+ archived_binary->set_download_type(
+ download_type_util::GetDownloadType(path));
+ SetLengthAndDigestForContainedFile(path, file, file_length,
+ archived_binary);
+ AnalyzeContainedBinary(binary_feature_extractor, file, archived_binary);
+#if defined(OS_MACOSX)
+ }
+#endif // OS_MACOSX
+ } else {
+ DVLOG(3) << "Ignoring non-binary file: " << path.value();
+ }
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/archive_analyzer_results.h b/chromium/chrome/common/safe_browsing/archive_analyzer_results.h
new file mode 100644
index 00000000000..09c526dbd3f
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/archive_analyzer_results.h
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains the archive analyzer analysis implementation for download
+// protection, which runs in a sandboxed utility process.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_ARCHIVE_ANALYZER_RESULTS_H_
+#define CHROME_COMMON_SAFE_BROWSING_ARCHIVE_ANALYZER_RESULTS_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+
+namespace base {
+class File;
+}
+
+namespace safe_browsing {
+
+struct ArchiveAnalyzerResults {
+ bool success;
+ bool has_executable;
+ bool has_archive;
+ google::protobuf::RepeatedPtrField<ClientDownloadRequest_ArchivedBinary>
+ archived_binary;
+ std::vector<base::FilePath> archived_archive_filenames;
+#if defined(OS_MACOSX)
+ std::vector<uint8_t> signature_blob;
+ google::protobuf::RepeatedPtrField<
+ ClientDownloadRequest_DetachedCodeSignature>
+ detached_code_signatures;
+#endif // OS_MACOSX
+ int file_count;
+ int directory_count;
+ ArchiveAnalyzerResults();
+ ArchiveAnalyzerResults(const ArchiveAnalyzerResults& other);
+ ~ArchiveAnalyzerResults();
+};
+
+// Updates |results| with the results of inspecting |file|, given that it will
+// be extracted to |path|. Due to complications with the utility process sandbox
+// (see https://crbug.com/944633), the file inspection is limited to the first
+// |file_length| bytes of |file|.
+void UpdateArchiveAnalyzerResultsWithFile(base::FilePath path,
+ base::File* file,
+ int file_length,
+ bool is_encrypted,
+ ArchiveAnalyzerResults* results);
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_ARCHIVE_ANALYZER_RESULTS_H_
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor.cc
new file mode 100644
index 00000000000..896768a6dcb
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+
+namespace safe_browsing {
+
+BinaryFeatureExtractor::BinaryFeatureExtractor() {}
+
+BinaryFeatureExtractor::~BinaryFeatureExtractor() {}
+
+bool BinaryFeatureExtractor::ExtractImageFeatures(
+ const base::FilePath& file_path,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data) {
+ base::MemoryMappedFile mapped_file;
+ if (!mapped_file.Initialize(file_path))
+ return false;
+ return ExtractImageFeaturesFromData(mapped_file.data(), mapped_file.length(),
+ options, image_headers, signed_data);
+}
+
+bool BinaryFeatureExtractor::ExtractImageFeaturesFromFile(
+ base::File file,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data) {
+ base::MemoryMappedFile mapped_file;
+ if (!mapped_file.Initialize(std::move(file)))
+ return false;
+ return ExtractImageFeaturesFromData(mapped_file.data(), mapped_file.length(),
+ options, image_headers, signed_data);
+}
+
+void BinaryFeatureExtractor::ExtractDigest(
+ const base::FilePath& file_path,
+ ClientDownloadRequest_Digests* digests) {
+ base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (file.IsValid()) {
+ const int kBufferSize = 1 << 12;
+ std::unique_ptr<char[]> buf(new char[kBufferSize]);
+ std::unique_ptr<crypto::SecureHash> ctx(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ int len = 0;
+ while (true) {
+ len = file.ReadAtCurrentPos(buf.get(), kBufferSize);
+ if (len <= 0)
+ break;
+ ctx->Update(buf.get(), len);
+ }
+ if (!len) {
+ uint8_t hash[crypto::kSHA256Length];
+ ctx->Finish(hash, sizeof(hash));
+ digests->set_sha256(hash, sizeof(hash));
+ }
+ }
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor.h b/chromium/chrome/common/safe_browsing/binary_feature_extractor.h
new file mode 100644
index 00000000000..b0bf4bf4bf3
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor.h
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Utility functions to extract file features for malicious binary detection.
+// Each platform has its own implementation of this class.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_BINARY_FEATURE_EXTRACTOR_H_
+#define CHROME_COMMON_SAFE_BROWSING_BINARY_FEATURE_EXTRACTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/files/file.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace safe_browsing {
+class ClientDownloadRequest_Digests;
+class ClientDownloadRequest_ImageHeaders;
+class ClientDownloadRequest_SignatureInfo;
+
+class BinaryFeatureExtractor
+ : public base::RefCountedThreadSafe<BinaryFeatureExtractor> {
+ public:
+ // The type and defined values for a bitfield that controls aspects of image
+ // header extraction.
+ typedef uint32_t ExtractHeadersOption;
+ static const ExtractHeadersOption kDefaultOptions = 0;
+ static const ExtractHeadersOption kOmitExports = 1U << 0;
+
+ BinaryFeatureExtractor();
+
+ // Fills in the DownloadRequest_SignatureInfo for the given file path.
+ // This method may be called on any thread.
+ virtual void CheckSignature(
+ const base::FilePath& file_path,
+ ClientDownloadRequest_SignatureInfo* signature_info);
+
+ // Populates |image_headers| with the PE image headers of |file_path| and, if
+ // non-null, |signed_data| with any PKCS#7 SignedData blobs found in the
+ // image's attribute certificate table. |options| is a bitfield controlling
+ // aspects of extraction. Returns true if |image_headers| is populated with
+ // any information.
+ virtual bool ExtractImageFeatures(
+ const base::FilePath& file_path,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data);
+
+ // As above, but works with an already-opened file. BinaryFeatureExtractor
+ // takes ownership of |file| and closes it when done.
+ virtual bool ExtractImageFeaturesFromFile(
+ base::File file,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data);
+
+ // As above, but works on a byte array containing image data. This does not
+ // take ownership of the data.
+ virtual bool ExtractImageFeaturesFromData(
+ const uint8_t* data, size_t data_size,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data);
+
+ // Populates |digests.sha256| with the SHA256 digest of |file_path|.
+ virtual void ExtractDigest(const base::FilePath& file_path,
+ ClientDownloadRequest_Digests* digests);
+
+ protected:
+ friend class base::RefCountedThreadSafe<BinaryFeatureExtractor>;
+ virtual ~BinaryFeatureExtractor();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BinaryFeatureExtractor);
+};
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_BINARY_FEATURE_EXTRACTOR_H_
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_fuzzer.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_fuzzer.cc
new file mode 100644
index 00000000000..d69de8fac86
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_fuzzer.cc
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "components/safe_browsing/proto/csd.pb.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static safe_browsing::BinaryFeatureExtractor* extractor =
+ new safe_browsing::BinaryFeatureExtractor();
+
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+ safe_browsing::ClientDownloadRequest_ImageHeaders image_headers;
+ extractor->ExtractImageFeaturesFromData(
+ data, size, safe_browsing::BinaryFeatureExtractor::kDefaultOptions,
+ &image_headers, &signed_data);
+ return 0;
+}
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_mac.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_mac.cc
new file mode 100644
index 00000000000..9edb6482f4a
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_mac.cc
@@ -0,0 +1,68 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+
+namespace safe_browsing {
+
+bool BinaryFeatureExtractor::ExtractImageFeaturesFromData(
+ const uint8_t* data, size_t data_size,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data) {
+ MachOImageReader image_reader;
+ if (!image_reader.Initialize(data, data_size))
+ return false;
+
+ // If the image is fat, get all its MachO images. Otherwise, just scan
+ // the thin image.
+ std::vector<MachOImageReader*> images;
+ if (image_reader.IsFat())
+ images = image_reader.GetFatImages();
+ else
+ images.push_back(&image_reader);
+
+ for (auto* mach_o_reader : images) {
+ // Record the entire mach_header struct.
+ auto* mach_o_headers = image_headers->mutable_mach_o_headers()->Add();
+ if (mach_o_reader->Is64Bit()) {
+ const mach_header_64* header = mach_o_reader->GetMachHeader64();
+ mach_o_headers->set_mach_header(header, sizeof(*header));
+ } else {
+ const mach_header* header = mach_o_reader->GetMachHeader();
+ mach_o_headers->set_mach_header(header, sizeof(*header));
+ }
+
+ // Store the load commands for the Mach-O binary.
+ auto* proto_load_commands = mach_o_headers->mutable_load_commands();
+ const std::vector<MachOImageReader::LoadCommand>& load_commands =
+ mach_o_reader->GetLoadCommands();
+ for (const auto& load_command : load_commands) {
+ auto* proto_load_command = proto_load_commands->Add();
+ proto_load_command->set_command_id(load_command.cmd());
+ proto_load_command->set_command(&load_command.data[0],
+ load_command.data.size());
+ }
+
+ // Get the signature information.
+ if (signed_data) {
+ std::vector<uint8_t> code_signature;
+ if (mach_o_reader->GetCodeSignatureInfo(&code_signature)) {
+ signed_data->Add()->append(
+ reinterpret_cast<const char*>(&code_signature[0]),
+ code_signature.size());
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc
new file mode 100644
index 00000000000..a1f44455be4
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+namespace {
+
+class BinaryFeatureExtractorMacTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_));
+ feature_extractor_ = new BinaryFeatureExtractor();
+ }
+
+ base::FilePath GetPath(const char* file_name) {
+ return test_data_.AppendASCII("safe_browsing")
+ .AppendASCII("mach_o")
+ .AppendASCII(file_name);
+ }
+
+ BinaryFeatureExtractor* feature_extractor() {
+ return feature_extractor_.get();
+ }
+
+ private:
+ base::FilePath test_data_;
+ scoped_refptr<BinaryFeatureExtractor> feature_extractor_;
+};
+
+TEST_F(BinaryFeatureExtractorMacTest, UnsignedMachOThin) {
+ ClientDownloadRequest_ImageHeaders image_headers;
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+
+ base::FilePath path = GetPath("lib32.dylib");
+ ASSERT_TRUE(feature_extractor()->ExtractImageFeatures(
+ path, 0, &image_headers, &signed_data));
+
+ EXPECT_EQ(1, image_headers.mach_o_headers().size());
+ EXPECT_EQ(0, signed_data.size());
+}
+
+TEST_F(BinaryFeatureExtractorMacTest, SignedMachOFat) {
+ ClientDownloadRequest_ImageHeaders image_headers;
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+
+ base::FilePath path = GetPath("signedexecutablefat");
+ ASSERT_TRUE(feature_extractor()->ExtractImageFeatures(
+ path, 0, &image_headers, &signed_data));
+
+ EXPECT_EQ(2, image_headers.mach_o_headers().size());
+ EXPECT_EQ(2, signed_data.size());
+}
+
+TEST_F(BinaryFeatureExtractorMacTest, NotMachO) {
+ ClientDownloadRequest_ImageHeaders image_headers;
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+
+ base::FilePath path = GetPath("src.c");
+ EXPECT_FALSE(feature_extractor()->ExtractImageFeatures(
+ path, 0, &image_headers, &signed_data));
+
+ EXPECT_EQ(0, image_headers.mach_o_headers().size());
+ EXPECT_EQ(0, signed_data.size());
+}
+
+} // namespace
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_posix.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_posix.cc
new file mode 100644
index 00000000000..03bf9a1494f
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_posix.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This is a stub for the code signing utilities on Mac and Linux.
+// It should eventually be replaced with a real implementation.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "build/build_config.h"
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+namespace safe_browsing {
+
+void BinaryFeatureExtractor::CheckSignature(
+ const base::FilePath& file_path,
+ ClientDownloadRequest_SignatureInfo* signature_info) {}
+
+#if !defined(OS_MACOSX)
+bool BinaryFeatureExtractor::ExtractImageFeaturesFromData(
+ const uint8_t* data, size_t data_size,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data) {
+ return false;
+}
+#endif
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc
new file mode 100644
index 00000000000..8cc98238565
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+
+#include "base/base_paths.h"
+#include "base/files/file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "crypto/sha2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+class BinaryFeatureExtractorTest : public testing::Test {
+ protected:
+ BinaryFeatureExtractorTest() : extractor_(new BinaryFeatureExtractor()) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ path_ = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("file.dll"));
+ }
+
+ // Writes |size| bytes from |data| to |path_|.
+ void WriteFileToHash(const char* data, int size) {
+ base::File file(path_, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_EQ(size, file.WriteAtCurrentPos(data, size));
+ }
+
+ // Verifies that |path_| hashes to |digest|.
+ void ExpectFileDigestEq(const uint8_t* digest) {
+ ClientDownloadRequest_Digests digests;
+ extractor_->ExtractDigest(path_, &digests);
+ EXPECT_TRUE(digests.has_sha256());
+ EXPECT_EQ(std::string(reinterpret_cast<const char*>(digest),
+ crypto::kSHA256Length),
+ digests.sha256());
+ }
+
+ static const int kBlockSize = 1 << 12;
+ scoped_refptr<BinaryFeatureExtractor> extractor_;
+ base::ScopedTempDir temp_dir_;
+
+ // The path to a file that may be hashed.
+ base::FilePath path_;
+};
+
+TEST_F(BinaryFeatureExtractorTest, ExtractDigestNoFile) {
+ base::FilePath no_file =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("does_not_exist.dll"));
+
+ ClientDownloadRequest_Digests digests;
+ extractor_->ExtractDigest(no_file, &digests);
+ EXPECT_FALSE(digests.has_sha256());
+}
+
+// Hash a file that is less than 1 4k block.
+TEST_F(BinaryFeatureExtractorTest, ExtractSmallDigest) {
+ static const uint8_t kDigest[] = {
+ 0x70, 0x27, 0x7b, 0xad, 0xfc, 0xb9, 0x97, 0x6b, 0x24, 0xf9, 0x80,
+ 0x22, 0x26, 0x2c, 0x31, 0xea, 0x8f, 0xb2, 0x1f, 0x54, 0x93, 0x6b,
+ 0x69, 0x8b, 0x5d, 0x54, 0xd4, 0xd4, 0x21, 0x0b, 0x98, 0xb7};
+
+ static const char kFileData[] = {"The mountains are robotic."};
+ static const int kDataLen = sizeof(kFileData) - 1;
+ WriteFileToHash(kFileData, kDataLen);
+ ExpectFileDigestEq(kDigest);
+}
+
+// Hash a file that is exactly 1 4k block.
+TEST_F(BinaryFeatureExtractorTest, ExtractOneBlockDigest) {
+ static const uint8_t kDigest[] = {
+ 0x4f, 0x93, 0x6e, 0xee, 0x89, 0x55, 0xa5, 0xe7, 0x46, 0xd0, 0x61,
+ 0x43, 0x54, 0x5f, 0x33, 0x7b, 0xdc, 0x30, 0x3a, 0x4b, 0x18, 0xb4,
+ 0x82, 0x20, 0xe3, 0x93, 0x4c, 0x65, 0xe0, 0xc1, 0xc0, 0x19};
+
+ const int kDataLen = kBlockSize;
+ std::unique_ptr<char[]> data(new char[kDataLen]);
+ memset(data.get(), 71, kDataLen);
+ WriteFileToHash(data.get(), kDataLen);
+ ExpectFileDigestEq(kDigest);
+}
+
+// Hash a file that is larger than 1 4k block.
+TEST_F(BinaryFeatureExtractorTest, ExtractBigBlockDigest) {
+ static const uint8_t kDigest[] = {
+ 0xda, 0xae, 0xa0, 0xd5, 0x3b, 0xce, 0x0b, 0x4e, 0x5f, 0x5d, 0x0b,
+ 0xc7, 0x6a, 0x69, 0x0e, 0xf1, 0x8b, 0x2d, 0x20, 0xcd, 0xf2, 0x6d,
+ 0x33, 0xa7, 0x70, 0xf3, 0x6b, 0x85, 0xbf, 0xce, 0x9d, 0x5c};
+
+ const int kDataLen = kBlockSize + 1;
+ std::unique_ptr<char[]> data(new char[kDataLen]);
+ memset(data.get(), 71, kDataLen);
+ WriteFileToHash(data.get(), kDataLen);
+ ExpectFileDigestEq(kDigest);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_win.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_win.cc
new file mode 100644
index 00000000000..6178112370a
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_win.cc
@@ -0,0 +1,173 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include <windows.h>
+#include <softpub.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <wintrust.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/threading/scoped_thread_priority.h"
+#include "chrome/common/safe_browsing/pe_image_reader_win.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+
+namespace safe_browsing {
+
+namespace {
+
+// An EnumCertificatesCallback that collects each SignedData blob.
+bool OnCertificateEntry(uint16_t revision,
+ uint16_t certificate_type,
+ const uint8_t* certificate_data,
+ size_t certificate_data_size,
+ void* context) {
+ google::protobuf::RepeatedPtrField<std::string>* signed_data =
+ reinterpret_cast<google::protobuf::RepeatedPtrField<std::string>*>(
+ context);
+
+ if (revision == WIN_CERT_REVISION_2_0 &&
+ certificate_type == WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
+ signed_data->Add()->assign(certificate_data,
+ certificate_data + certificate_data_size);
+ }
+ return true;
+}
+
+} // namespace
+
+void BinaryFeatureExtractor::CheckSignature(
+ const base::FilePath& file_path,
+ ClientDownloadRequest_SignatureInfo* signature_info) {
+ // Mitigate the issues caused by loading DLLs on a background thread
+ // (http://crbug/973868).
+ base::ScopedThreadMayLoadLibraryOnBackgroundThread priority_boost(FROM_HERE);
+
+ DVLOG(2) << "Checking signature for " << file_path.value();
+
+ WINTRUST_FILE_INFO file_info = {0};
+ file_info.cbStruct = sizeof(file_info);
+ file_info.pcwszFilePath = file_path.value().c_str();
+ file_info.hFile = NULL;
+ file_info.pgKnownSubject = NULL;
+
+ WINTRUST_DATA wintrust_data = {0};
+ wintrust_data.cbStruct = sizeof(wintrust_data);
+ wintrust_data.pPolicyCallbackData = NULL;
+ wintrust_data.pSIPClientData = NULL;
+ wintrust_data.dwUIChoice = WTD_UI_NONE;
+ wintrust_data.fdwRevocationChecks = WTD_REVOKE_NONE;
+ wintrust_data.dwUnionChoice = WTD_CHOICE_FILE;
+ wintrust_data.pFile = &file_info;
+ wintrust_data.dwStateAction = WTD_STATEACTION_VERIFY;
+ wintrust_data.hWVTStateData = NULL;
+ wintrust_data.pwszURLReference = NULL;
+ // Disallow revocation checks over the network.
+ wintrust_data.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+ wintrust_data.dwUIContext = WTD_UICONTEXT_EXECUTE;
+
+ // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
+ // chains up to a trusted root CA, and that it has appropriate permission to
+ // sign code.
+ GUID policy_guid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+
+ LONG result = WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE),
+ &policy_guid,
+ &wintrust_data);
+
+ CRYPT_PROVIDER_DATA* prov_data = WTHelperProvDataFromStateData(
+ wintrust_data.hWVTStateData);
+ if (prov_data) {
+ if (prov_data->csSigners > 0) {
+ signature_info->set_trusted(result == ERROR_SUCCESS);
+ }
+ for (DWORD i = 0; i < prov_data->csSigners; ++i) {
+ const CERT_CHAIN_CONTEXT* cert_chain_context =
+ prov_data->pasSigners[i].pChainContext;
+ if (!cert_chain_context)
+ break;
+ for (DWORD j = 0; j < cert_chain_context->cChain; ++j) {
+ CERT_SIMPLE_CHAIN* simple_chain = cert_chain_context->rgpChain[j];
+ ClientDownloadRequest_CertificateChain* chain =
+ signature_info->add_certificate_chain();
+ if (!simple_chain)
+ break;
+ for (DWORD k = 0; k < simple_chain->cElement; ++k) {
+ CERT_CHAIN_ELEMENT* element = simple_chain->rgpElement[k];
+ chain->add_element()->set_certificate(
+ element->pCertContext->pbCertEncoded,
+ element->pCertContext->cbCertEncoded);
+ }
+ }
+ }
+
+ // Free the provider data.
+ wintrust_data.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE),
+ &policy_guid, &wintrust_data);
+ }
+}
+
+bool BinaryFeatureExtractor::ExtractImageFeaturesFromData(
+ const uint8_t* data, size_t data_size,
+ ExtractHeadersOption options,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ google::protobuf::RepeatedPtrField<std::string>* signed_data) {
+ PeImageReader pe_image;
+ if (!pe_image.Initialize(data, data_size))
+ return false;
+
+ // Copy the headers.
+ ClientDownloadRequest_PEImageHeaders* pe_headers =
+ image_headers->mutable_pe_headers();
+ pe_headers->set_dos_header(pe_image.GetDosHeader(), sizeof(IMAGE_DOS_HEADER));
+ pe_headers->set_file_header(pe_image.GetCoffFileHeader(),
+ sizeof(IMAGE_FILE_HEADER));
+ size_t optional_header_size = 0;
+ const uint8_t* optional_header_data =
+ pe_image.GetOptionalHeaderData(&optional_header_size);
+ if (pe_image.GetWordSize() == PeImageReader::WORD_SIZE_32) {
+ pe_headers->set_optional_headers32(optional_header_data,
+ optional_header_size);
+ } else {
+ pe_headers->set_optional_headers64(optional_header_data,
+ optional_header_size);
+ }
+ const size_t number_of_sections = pe_image.GetNumberOfSections();
+ for (size_t i = 0; i != number_of_sections; ++i) {
+ pe_headers->add_section_header(pe_image.GetSectionHeaderAt(i),
+ sizeof(IMAGE_SECTION_HEADER));
+ }
+ if (!(options & BinaryFeatureExtractor::kOmitExports)) {
+ size_t export_size = 0;
+ const uint8_t* export_section = pe_image.GetExportSection(&export_size);
+ if (export_section)
+ pe_headers->set_export_section_data(export_section, export_size);
+ }
+ size_t number_of_debug_entries = pe_image.GetNumberOfDebugEntries();
+ for (size_t i = 0; i != number_of_debug_entries; ++i) {
+ const uint8_t* raw_data = NULL;
+ size_t raw_data_size = 0;
+ const IMAGE_DEBUG_DIRECTORY* directory_entry =
+ pe_image.GetDebugEntry(i, &raw_data, &raw_data_size);
+ if (directory_entry) {
+ ClientDownloadRequest_PEImageHeaders_DebugData* debug_data =
+ pe_headers->add_debug_data();
+ debug_data->set_directory_entry(directory_entry,
+ sizeof(*directory_entry));
+ if (raw_data)
+ debug_data->set_raw_data(raw_data, raw_data_size);
+ }
+ }
+
+ if (signed_data)
+ pe_image.EnumCertificates(&OnCertificateEntry, signed_data);
+
+ return true;
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc b/chromium/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc
new file mode 100644
index 00000000000..619d63c9a43
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+
+#include <string>
+#include <vector>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "net/cert/x509_cert_types.h"
+#include "net/cert/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+class BinaryFeatureExtractorWinTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ base::FilePath source_path;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &source_path));
+ testdata_path_ = source_path
+ .AppendASCII("safe_browsing")
+ .AppendASCII("download_protection");
+
+ binary_feature_extractor_ = new BinaryFeatureExtractor();
+ }
+
+ // Given a certificate chain protobuf, parse it into X509Certificates.
+ void ParseCertificateChain(
+ const ClientDownloadRequest_CertificateChain& chain,
+ std::vector<scoped_refptr<net::X509Certificate> >* certs) {
+ for (int i = 0; i < chain.element_size(); ++i) {
+ scoped_refptr<net::X509Certificate> cert =
+ net::X509Certificate::CreateFromBytes(
+ chain.element(i).certificate().data(),
+ chain.element(i).certificate().size());
+ if (cert)
+ certs->push_back(cert);
+ }
+ }
+
+ base::FilePath testdata_path_;
+ scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor_;
+};
+
+TEST_F(BinaryFeatureExtractorWinTest, UntrustedSignedBinary) {
+ // signed.exe is signed by an untrusted root CA.
+ ClientDownloadRequest_SignatureInfo signature_info;
+ binary_feature_extractor_->CheckSignature(
+ testdata_path_.Append(L"signed.exe"),
+ &signature_info);
+ ASSERT_EQ(1, signature_info.certificate_chain_size());
+ std::vector<scoped_refptr<net::X509Certificate> > certs;
+ ParseCertificateChain(signature_info.certificate_chain(0), &certs);
+ ASSERT_EQ(2u, certs.size());
+ EXPECT_EQ("Joe's-Software-Emporium", certs[0]->subject().common_name);
+ EXPECT_EQ("Root Agency", certs[1]->subject().common_name);
+
+ EXPECT_TRUE(signature_info.has_trusted());
+ EXPECT_FALSE(signature_info.trusted());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, TrustedBinary) {
+ // disable_outdated_build_detector.exe is dual signed using Google's signing
+ // certifiacte.
+ ClientDownloadRequest_SignatureInfo signature_info;
+ binary_feature_extractor_->CheckSignature(
+ testdata_path_.Append(L"disable_outdated_build_detector.exe"),
+ &signature_info);
+ ASSERT_EQ(1, signature_info.certificate_chain_size());
+ std::vector<scoped_refptr<net::X509Certificate> > certs;
+ ParseCertificateChain(signature_info.certificate_chain(0), &certs);
+ ASSERT_EQ(3u, certs.size());
+
+ EXPECT_EQ("Google Inc", certs[0]->subject().common_name);
+ EXPECT_EQ("VeriSign Class 3 Code Signing 2010 CA",
+ certs[1]->subject().common_name);
+ EXPECT_EQ("VeriSign Trust Network",
+ certs[2]->subject().organization_unit_names[0]);
+
+ EXPECT_TRUE(signature_info.trusted());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, UnsignedBinary) {
+ // unsigned.exe has no signature information.
+ ClientDownloadRequest_SignatureInfo signature_info;
+ binary_feature_extractor_->CheckSignature(
+ testdata_path_.Append(L"unsigned.exe"),
+ &signature_info);
+ EXPECT_EQ(0, signature_info.certificate_chain_size());
+ EXPECT_FALSE(signature_info.has_trusted());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, NonExistentBinary) {
+ // Test a file that doesn't exist.
+ ClientDownloadRequest_SignatureInfo signature_info;
+ binary_feature_extractor_->CheckSignature(
+ testdata_path_.Append(L"doesnotexist.exe"),
+ &signature_info);
+ EXPECT_EQ(0, signature_info.certificate_chain_size());
+ EXPECT_FALSE(signature_info.has_trusted());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeaturesNoFile) {
+ // Test extracting headers from a file that doesn't exist.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ ASSERT_FALSE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.AppendASCII("this_file_does_not_exist"),
+ BinaryFeatureExtractor::kDefaultOptions, &image_headers,
+ nullptr /* signed_data */));
+ EXPECT_FALSE(image_headers.has_pe_headers());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeaturesNonImage) {
+ // Test extracting headers from something that is not a PE image.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ ASSERT_FALSE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.AppendASCII("simple_exe.cc"),
+ BinaryFeatureExtractor::kDefaultOptions, &image_headers,
+ nullptr /* signed_data */));
+ EXPECT_FALSE(image_headers.has_pe_headers());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeatures) {
+ // Test extracting features from something that is a PE image.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+ ASSERT_TRUE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.AppendASCII("unsigned.exe"),
+ BinaryFeatureExtractor::kDefaultOptions, &image_headers, &signed_data));
+ EXPECT_TRUE(image_headers.has_pe_headers());
+ const ClientDownloadRequest_PEImageHeaders& pe_headers =
+ image_headers.pe_headers();
+ EXPECT_TRUE(pe_headers.has_dos_header());
+ EXPECT_TRUE(pe_headers.has_file_header());
+ EXPECT_TRUE(pe_headers.has_optional_headers32());
+ EXPECT_FALSE(pe_headers.has_optional_headers64());
+ EXPECT_NE(0, pe_headers.section_header_size());
+ EXPECT_FALSE(pe_headers.has_export_section_data());
+ EXPECT_EQ(0, pe_headers.debug_data_size());
+ EXPECT_EQ(0, signed_data.size());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeaturesWithDebugData) {
+ // Test extracting headers from something that is a PE image with debug data.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ ASSERT_TRUE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.DirName().AppendASCII("module_with_exports_x86.dll"),
+ BinaryFeatureExtractor::kDefaultOptions, &image_headers,
+ nullptr /* signed_data */));
+ EXPECT_TRUE(image_headers.has_pe_headers());
+ const ClientDownloadRequest_PEImageHeaders& pe_headers =
+ image_headers.pe_headers();
+ EXPECT_TRUE(pe_headers.has_dos_header());
+ EXPECT_TRUE(pe_headers.has_file_header());
+ EXPECT_TRUE(pe_headers.has_optional_headers32());
+ EXPECT_FALSE(pe_headers.has_optional_headers64());
+ EXPECT_NE(0, pe_headers.section_header_size());
+ EXPECT_TRUE(pe_headers.has_export_section_data());
+ EXPECT_EQ(1, pe_headers.debug_data_size());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeaturesWithoutExports) {
+ // Test extracting headers from something that is a PE image with debug data.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ ASSERT_TRUE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.DirName().AppendASCII("module_with_exports_x86.dll"),
+ BinaryFeatureExtractor::kOmitExports, &image_headers,
+ nullptr /* signed_data */));
+ EXPECT_TRUE(image_headers.has_pe_headers());
+ const ClientDownloadRequest_PEImageHeaders& pe_headers =
+ image_headers.pe_headers();
+ EXPECT_TRUE(pe_headers.has_dos_header());
+ EXPECT_TRUE(pe_headers.has_file_header());
+ EXPECT_TRUE(pe_headers.has_optional_headers32());
+ EXPECT_FALSE(pe_headers.has_optional_headers64());
+ EXPECT_NE(0, pe_headers.section_header_size());
+ EXPECT_FALSE(pe_headers.has_export_section_data());
+ EXPECT_EQ(1, pe_headers.debug_data_size());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeaturesUntrustedSigned) {
+ // Test extracting features from a signed PE image.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+ ASSERT_TRUE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.AppendASCII("signed.exe"),
+ BinaryFeatureExtractor::kDefaultOptions, &image_headers, &signed_data));
+ ASSERT_EQ(1, signed_data.size());
+ ASSERT_LT(0U, signed_data.Get(0).size());
+}
+
+TEST_F(BinaryFeatureExtractorWinTest, ExtractImageFeaturesTrustedSigned) {
+ // Test extracting features from a signed PE image from a trusted root.
+ ClientDownloadRequest_ImageHeaders image_headers;
+ google::protobuf::RepeatedPtrField<std::string> signed_data;
+ ASSERT_TRUE(binary_feature_extractor_->ExtractImageFeatures(
+ testdata_path_.AppendASCII("disable_outdated_build_detector.exe"),
+ BinaryFeatureExtractor::kDefaultOptions, &image_headers, &signed_data));
+ ASSERT_EQ(1, signed_data.size());
+ ASSERT_LT(0U, signed_data.Get(0).size());
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/client_model.proto b/chromium/chrome/common/safe_browsing/client_model.proto
new file mode 100644
index 00000000000..8216682b2af
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/client_model.proto
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This proto represents a machine learning model which is used to compute
+// the probability that a particular page visited by Chrome is phishing.
+//
+// Note: sine the machine learning model is trained on the server-side and then
+// downloaded onto the client it is important that this proto file stays in
+// sync with the server-side copy. Otherwise, the client may not be able to
+// parse the server generated model anymore. If you want to change this
+// protocol definition or you have questions regarding its format please contact
+// chrome-anti-phishing@googlegroups.com.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package safe_browsing;
+
+// This protocol buffer represents a machine learning model that is used in
+// client-side phishing detection (in Chrome). The client extracts a set
+// of features from every website the user visits. Extracted features map
+// feature names to floating point values (e.g., PageSecureLinksFreq -> 0.9).
+//
+// To compute the phishing score (i.e., the probability that the website is
+// phishing) a scorer will simply compute the sum of all rule scores for a
+// given set of extracted features. The score of a particular rule corresponds
+// to the product of all feature values that are part of the rule times the
+// rule weight. If a feature has no value (i.e., is not part of the extracted
+// features) its value will be set to zero. The overall score is computed
+// by summing up all the rule scores. This overall score is a logodds and can
+// be converted to a probability like this:
+// p = exp(logodds) / (exp(logodds) + 1).
+//
+// To make it harder for phishers to reverse engineer our machine learning model
+// all the features in the model are hashed with a sha256 hash function. The
+// feature extractors also hash the extracted features before scoring happens.
+message ClientSideModel {
+ // In order to save some space we store all the hashed strings in a
+ // single repeated field and then the rules as well as page terms
+ // and page words refer to an index in that repeated field. All
+ // hashes are sha256 hashes stored in binary format.
+ repeated bytes hashes = 1;
+
+ message Rule {
+ // List of indexes into hashes above which are basically hashed
+ // features that form the current rule.
+ repeated int32 feature = 1;
+
+ // The weight for this particular rule.
+ required float weight = 2;
+ }
+
+ // List of rules which make up the model
+ repeated Rule rule = 2;
+
+ // List of indexes that point to the hashed page terms that appear in
+ // the model. The hashes are computed over page terms that are encoded
+ // as lowercase UTF-8 strings.
+ repeated int32 page_term = 3;
+
+ // List of hashed page words. The page words correspond to all words that
+ // appear in page terms. If the term "one two" is in the list of page terms
+ // then "one" and "two" will be in the list of page words. For page words
+ // we don't use SHA256 because it is too expensive. We use MurmurHash3
+ // instead. See: http://code.google.com/p/smhasher.
+ repeated fixed32 page_word = 4;
+
+ // Page terms in page_term contain at most this many page words.
+ required int32 max_words_per_term = 5;
+
+ // Model version number. Every model that we train should have a different
+ // version number and it should always be larger than the previous model
+ // version.
+ optional int32 version = 6;
+
+ // List of known bad IP subnets.
+ message IPSubnet {
+ // The subnet prefix is a valid 16-byte IPv6 address (in network order) that
+ // is hashed using sha256.
+ required bytes prefix = 1;
+
+ // Network prefix size in bits. Default is an exact-host match.
+ optional int32 size = 2 [default = 128];
+ };
+ repeated IPSubnet bad_subnet = 7;
+
+ // Murmur hash seed that was used to hash the page words.
+ optional fixed32 murmur_hash_seed = 8;
+
+ // Maximum number of unique shingle hashes per page.
+ optional int32 max_shingles_per_page = 9 [default = 200];
+
+ // The number of words in a shingle.
+ optional int32 shingle_size = 10 [default = 4];
+}
diff --git a/chromium/chrome/common/safe_browsing/crx_info.proto b/chromium/chrome/common/safe_browsing/crx_info.proto
new file mode 100644
index 00000000000..78065fcc25a
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/crx_info.proto
@@ -0,0 +1,38 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package extensions;
+
+// This is used to request more information on blacklisted CRX packages. The
+// client maintains a local cache of blacklisted ids, and makes requests to our
+// server to get more information, such as the blacklist type.
+message ClientCRXListInfoRequest {
+ // ID of the CRX package.
+ required string id = 1;
+
+ // Locale of the device, eg en, en_US.
+ optional string locale = 2;
+}
+
+message ClientCRXListInfoResponse {
+ enum Verdict {
+ NOT_IN_BLACKLIST = 0;
+ MALWARE = 1;
+ SECURITY_VULNERABILITY = 2;
+ CWS_POLICY_VIOLATION = 3;
+ POTENTIALLY_UNWANTED = 4;
+ }
+ // Although listed as optional, this is required.
+ optional Verdict verdict = 1 [default=NOT_IN_BLACKLIST];
+
+ message UserMessage {
+ // If present, will be appended to disable reason in the details page. We
+ // could use this to send a URL to a blogpost or help article.
+ optional string detail_message = 1;
+ }
+ optional UserMessage user_message = 2;
+}
diff --git a/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.cc b/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.cc
new file mode 100644
index 00000000000..e0b4e34f737
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.cc
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/disk_image_type_sniffer_mac.h"
+#include "base/threading/scoped_blocking_call.h"
+
+namespace safe_browsing {
+
+namespace {
+
+const uint8_t kKolySignature[4] = {'k', 'o', 'l', 'y'};
+constexpr size_t kSizeKolySignatureInBytes = sizeof(kKolySignature);
+const size_t kSizeKolyTrailerInBytes = 512;
+
+} // namespace
+
+DiskImageTypeSnifferMac::DiskImageTypeSnifferMac() {}
+
+// static
+bool DiskImageTypeSnifferMac::IsAppleDiskImage(const base::FilePath& dmg_file) {
+ // TODO(drubery): Macs accept DMGs with koly blocks at the beginning of the
+ // file. Investigate if this is a problem, and if so, update this function.
+ base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+ base::BlockingType::MAY_BLOCK);
+
+ base::File file(dmg_file, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid())
+ return false;
+
+ char data[kSizeKolySignatureInBytes];
+
+ if (file.Seek(base::File::FROM_END, -1 * kSizeKolyTrailerInBytes) == -1)
+ return false;
+
+ if (file.ReadAtCurrentPos(data, kSizeKolySignatureInBytes) !=
+ kSizeKolySignatureInBytes)
+ return false;
+
+ return IsAppleDiskImageTrailer(base::span<uint8_t>(
+ reinterpret_cast<uint8_t*>(data), kSizeKolySignatureInBytes));
+}
+
+// static
+bool DiskImageTypeSnifferMac::IsAppleDiskImageTrailer(
+ const base::span<const uint8_t>& trailer) {
+ if (trailer.size() < kSizeKolySignatureInBytes)
+ return false;
+
+ const base::span<const uint8_t> subspan =
+ trailer.last(kSizeKolySignatureInBytes);
+
+ return (memcmp(subspan.data(), kKolySignature, kSizeKolySignatureInBytes) ==
+ 0);
+}
+
+// static
+size_t DiskImageTypeSnifferMac::AppleDiskImageTrailerSize() {
+ return kSizeKolyTrailerInBytes;
+}
+
+DiskImageTypeSnifferMac::~DiskImageTypeSnifferMac() = default;
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.h b/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.h
new file mode 100644
index 00000000000..8581b9980a3
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_DISK_IMAGE_TYPE_SNIFFER_MAC_H_
+#define CHROME_COMMON_SAFE_BROWSING_DISK_IMAGE_TYPE_SNIFFER_MAC_H_
+
+#include "base/containers/span.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+
+namespace safe_browsing {
+
+// This class is used to determine whether a given file is a Mac archive type,
+// regardless of file extension. It does so by determining whether the file has
+// the 'koly' signature typical of Mac archive files.
+class DiskImageTypeSnifferMac
+ : public base::RefCountedThreadSafe<DiskImageTypeSnifferMac> {
+ public:
+ DiskImageTypeSnifferMac();
+
+ // Reads trailer from file to see if it is a DMG type. Must be called on the
+ // FILE thread.
+ static bool IsAppleDiskImage(const base::FilePath& dmg_file);
+
+ // Returns true when the trailer is a valid trailer for a DMG type.
+ static bool IsAppleDiskImageTrailer(const base::span<const uint8_t>& trailer);
+
+ // Returns the size of a DMG trailer.
+ static size_t AppleDiskImageTrailerSize();
+
+ private:
+ friend class base::RefCountedThreadSafe<DiskImageTypeSnifferMac>;
+
+ ~DiskImageTypeSnifferMac();
+
+ DISALLOW_COPY_AND_ASSIGN(DiskImageTypeSnifferMac);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_DISK_IMAGE_TYPE_SNIFFE_MAC_H_
diff --git a/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac_unittest.cc b/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac_unittest.cc
new file mode 100644
index 00000000000..6974174b3c8
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/disk_image_type_sniffer_mac_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/disk_image_type_sniffer_mac.h"
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+namespace {
+
+struct ArchiveTestCase {
+ // The disk image file to open.
+ const char* file_name;
+
+ // Expectation regarding the file being recognized as a DMG. As the UDIFParser
+ // class currently only supports certain UDIF features, this is used to
+ // properly test expectations.
+ bool expected_results;
+};
+
+std::ostream& operator<<(std::ostream& os, const ArchiveTestCase& test_case) {
+ os << test_case.file_name;
+ return os;
+}
+
+class DiskImageTypeSnifferMacTest
+ : public testing::TestWithParam<ArchiveTestCase> {
+ protected:
+ base::FilePath GetFilePath(const char* file_name) {
+ base::FilePath test_data;
+ EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
+ return test_data.AppendASCII("safe_browsing")
+ .AppendASCII("dmg")
+ .AppendASCII("data")
+ .AppendASCII(file_name);
+ }
+
+ private:
+ content::BrowserTaskEnvironment task_environment_;
+};
+
+TEST_P(DiskImageTypeSnifferMacTest, SniffDiskImage) {
+ const ArchiveTestCase& test_case = GetParam();
+ DVLOG(1) << "Test case: " << test_case;
+
+ base::FilePath path;
+ ASSERT_NO_FATAL_FAILURE(path = GetFilePath(test_case.file_name));
+
+ ASSERT_EQ(test_case.expected_results,
+ DiskImageTypeSnifferMac::IsAppleDiskImage(path));
+}
+
+const ArchiveTestCase cases[] = {
+ {"dmg_UDBZ_GPTSPUD.dmg", true},
+ {"dmg_UDBZ_NONE.dmg", true},
+ {"dmg_UDBZ_SPUD.dmg", true},
+ {"dmg_UDCO_GPTSPUD.dmg", true},
+ {"dmg_UDCO_NONE.dmg", true},
+ {"dmg_UDCO_SPUD.dmg", true},
+ {"dmg_UDRO_GPTSPUD.dmg", true},
+ {"dmg_UDRO_NONE.dmg", true},
+ {"dmg_UDRO_SPUD.dmg", true},
+ // UDRW not supported.
+ {"dmg_UDRW_GPTSPUD.dmg", false},
+ // UDRW not supported.
+ {"dmg_UDRW_NONE.dmg", false},
+ // UDRW not supported.
+ {"dmg_UDRW_SPUD.dmg", false},
+ // Sparse images not supported.
+ {"dmg_UDSP_GPTSPUD.sparseimage", false},
+ // UDRW not supported.
+ {"dmg_UDSP_NONE.sparseimage", false},
+ // Sparse images not supported.
+ {"dmg_UDSP_SPUD.sparseimage", false},
+ // CD/DVD format not supported.
+ {"dmg_UDTO_GPTSPUD.cdr", false},
+ // CD/DVD format not supported.
+ {"dmg_UDTO_NONE.cdr", false},
+ // CD/DVD format not supported.
+ {"dmg_UDTO_SPUD.cdr", false},
+ {"dmg_UDZO_GPTSPUD.dmg", true},
+ {"dmg_UDZO_SPUD.dmg", true},
+ {"dmg_UFBI_GPTSPUD.dmg", true},
+ {"dmg_UFBI_SPUD.dmg", true},
+ {"mach_o_in_dmg.dmg", true},
+ // Absence of 'koly' signature will cause parsing to fail - even if file has
+ // .dmg extension.
+ {"mach_o_in_dmg_no_koly_signature.dmg", false},
+ // Type sniffer should realize DMG type even without extension.
+ {"mach_o_in_dmg.txt", true}
+
+};
+
+INSTANTIATE_TEST_SUITE_P(DiskImageTypeSnifferMacTestInstantiation,
+ DiskImageTypeSnifferMacTest,
+ testing::ValuesIn(cases));
+
+TEST(DiskImageTypeSnifferMacTest, IsAppleDiskImageTrailerIsCorrect) {
+ uint8_t good_header[4] = {'k', 'o', 'l', 'y'};
+ EXPECT_TRUE(DiskImageTypeSnifferMac::IsAppleDiskImageTrailer(good_header));
+
+ uint8_t bad_header[6] = {'f', 'o', 'o', 'b', 'a', 'r'};
+ EXPECT_FALSE(DiskImageTypeSnifferMac::IsAppleDiskImageTrailer(bad_header));
+}
+
+} // namespace
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/download_file_types.proto b/chromium/chrome/common/safe_browsing/download_file_types.proto
new file mode 100644
index 00000000000..f9e2f3bb561
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/download_file_types.proto
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package safe_browsing;
+
+// See //chrome/browser/resources/safe_browsing/README.md for guidelines
+// on how to set fields in this file.
+
+// Next id: 5
+message DownloadFileType {
+ optional string extension = 1; // required, except in default_file_type.
+ optional int64 uma_value = 2; // required
+ optional bool is_archive = 3 [default = false];
+
+ enum PingSetting {
+ SAMPLED_PING = 0;
+ NO_PING = 1;
+ FULL_PING = 2;
+ }
+ optional PingSetting ping_setting = 4; // required
+
+ enum DangerLevel {
+ NOT_DANGEROUS = 0;
+ ALLOW_ON_USER_GESTURE = 1;
+ DANGEROUS = 2;
+ }
+
+ enum AutoOpenHint {
+ DISALLOW_AUTO_OPEN = 0;
+ ALLOW_AUTO_OPEN = 1;
+ }
+
+ enum PlatformType {
+ PLATFORM_ANY = 0;
+ PLATFORM_ANDROID = 1;
+ PLATFORM_CHROME_OS = 2;
+ PLATFORM_LINUX = 3;
+ PLATFORM_MAC = 4;
+ PLATFORM_WINDOWS = 5;
+ }
+
+ // Next id: 5
+ message PlatformSettings {
+ optional PlatformType platform = 1 [default = PLATFORM_ANY];
+ optional DangerLevel danger_level = 2; // required
+ optional AutoOpenHint auto_open_hint = 3; // required
+ optional uint64 max_file_size_to_analyze = 4
+ [default = 18446744073709551615]; // (2^64)-1]
+ };
+
+ // Protos parsed by Chrome should have exactly one entry here.
+ repeated PlatformSettings platform_settings = 5;
+
+ // The type of file content inspection we should do, if any.
+ enum InspectionType {
+ NONE = 0;
+ ZIP = 1;
+ RAR = 2;
+ DMG = 3;
+ }
+ optional InspectionType inspection_type = 6;
+};
+
+// Next id: 6
+message DownloadFileTypeConfig {
+ // All required
+ optional uint32 version_id = 1;
+ optional float sampled_ping_probability = 2;
+ repeated DownloadFileType file_types = 3;
+ optional DownloadFileType default_file_type = 4;
+
+ // Limits on repeated fields in the ClientDownloadRequest (i.e. the
+ // download ping). Limits are per-ping.
+ optional uint64 max_archived_binaries_to_report = 5;
+}
diff --git a/chromium/chrome/common/safe_browsing/download_type_util.cc b/chromium/chrome/common/safe_browsing/download_type_util.cc
new file mode 100644
index 00000000000..a7967401240
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/download_type_util.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/download_type_util.h"
+
+#include <algorithm>
+
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "chrome/common/safe_browsing/file_type_policies.h"
+#include "components/safe_browsing/features.h"
+
+namespace safe_browsing {
+namespace download_type_util {
+
+ClientDownloadRequest::DownloadType GetDownloadType(
+ const base::FilePath& file) {
+ // TODO(nparker): Put all of this logic into the FileTypePolicies
+ // protobuf.
+ if (file.MatchesExtension(FILE_PATH_LITERAL(".apk")))
+ return ClientDownloadRequest::ANDROID_APK;
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".crx")))
+ return ClientDownloadRequest::CHROME_EXTENSION;
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".zip")))
+ // DownloadProtectionService doesn't send a ClientDownloadRequest for ZIP
+ // files unless they contain either executables or archives. The resulting
+ // DownloadType is either ZIPPED_EXECUTABLE or ZIPPED_ARCHIVE respectively.
+ // This function will return ZIPPED_EXECUTABLE for ZIP files as a
+ // placeholder. The correct DownloadType will be determined based on the
+ // result of analyzing the ZIP file.
+ return ClientDownloadRequest::ZIPPED_EXECUTABLE;
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".rar")))
+ // See the comment for .zip files.
+ return ClientDownloadRequest::RAR_COMPRESSED_EXECUTABLE;
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".dmg")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".img")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".iso")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".pkg")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".mpkg")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".smi")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".app")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".cdr")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dmgpart")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dvdr")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dart")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dc42")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".diskcopy42")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".imgpart")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".ndif")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".udif")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".toast")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".sparsebundle")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".sparseimage")))
+ return ClientDownloadRequest::MAC_EXECUTABLE;
+ else if (FileTypePolicies::GetInstance()->IsArchiveFile(file))
+ return ClientDownloadRequest::ARCHIVE;
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".pdf")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".doc")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".docx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".docm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".docb")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dot")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dotm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".dotx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xls")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlsb")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlt")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlsx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xldm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xltx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xltm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlsb")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xla")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlam")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xll")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xlw")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".ppt")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".pot")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".pps")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".pptx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".pptm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".potx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".potm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".ppam")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".ppsx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".ppsm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".sldx")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".xldm")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".rtf")))
+ return ClientDownloadRequest::DOCUMENT;
+
+ return ClientDownloadRequest::WIN_EXECUTABLE;
+}
+
+} // namespace download_type_util
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/download_type_util.h b/chromium/chrome/common/safe_browsing/download_type_util.h
new file mode 100644
index 00000000000..87f17e4e479
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/download_type_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_DOWNLOAD_TYPE_UTIL_H_
+#define CHROME_COMMON_SAFE_BROWSING_DOWNLOAD_TYPE_UTIL_H_
+
+#include "base/files/file_path.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+
+namespace safe_browsing {
+namespace download_type_util {
+
+// Returns the DownloadType of the file at |path|. This function is only valid
+// for paths that satisfy IsSupportedBinaryFile() above.
+ClientDownloadRequest::DownloadType GetDownloadType(const base::FilePath& file);
+
+} // namespace download_type_util
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_DOWNLOAD_TYPE_UTIL_H_
diff --git a/chromium/chrome/common/safe_browsing/download_type_util_unittest.cc b/chromium/chrome/common/safe_browsing/download_type_util_unittest.cc
new file mode 100644
index 00000000000..9e507baca62
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/download_type_util_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/download_type_util.h"
+
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "components/safe_browsing/features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+namespace download_type_util {
+
+TEST(DownloadProtectionUtilTest, KnownValues) {
+ EXPECT_EQ(ClientDownloadRequest::WIN_EXECUTABLE,
+ GetDownloadType(base::FilePath(FILE_PATH_LITERAL("foo.exe"))));
+ EXPECT_EQ(ClientDownloadRequest::CHROME_EXTENSION,
+ GetDownloadType(base::FilePath(FILE_PATH_LITERAL("foo.crx"))));
+ EXPECT_EQ(ClientDownloadRequest::ZIPPED_EXECUTABLE,
+ GetDownloadType(base::FilePath(FILE_PATH_LITERAL("foo.zip"))));
+ EXPECT_EQ(ClientDownloadRequest::RAR_COMPRESSED_EXECUTABLE,
+ GetDownloadType(base::FilePath(FILE_PATH_LITERAL("foo.rar"))));
+ EXPECT_EQ(ClientDownloadRequest::MAC_EXECUTABLE,
+ GetDownloadType(base::FilePath(FILE_PATH_LITERAL("foo.pkg"))));
+ EXPECT_EQ(ClientDownloadRequest::ANDROID_APK,
+ GetDownloadType(base::FilePath(FILE_PATH_LITERAL("foo.apk"))));
+}
+
+} // namespace download_type_util
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/file_type_policies.cc b/chromium/chrome/common/safe_browsing/file_type_policies.cc
new file mode 100644
index 00000000000..b6f7939ff2d
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/file_type_policies.cc
@@ -0,0 +1,273 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/file_type_policies.h"
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/string_util.h"
+#include "chrome/grit/browser_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace safe_browsing {
+
+using base::AutoLock;
+
+// Our Singleton needs to populate itself when first constructed.
+// This is left out of the constructor to make testing simpler.
+struct FileTypePoliciesSingletonTrait
+ : public base::DefaultSingletonTraits<FileTypePolicies> {
+ static FileTypePolicies* New() {
+ FileTypePolicies* instance = new FileTypePolicies();
+ instance->PopulateFromResourceBundle();
+ return instance;
+ }
+};
+
+// --- FileTypePolicies methods ---
+
+// static
+FileTypePolicies* FileTypePolicies::GetInstance() {
+ return base::Singleton<FileTypePolicies,
+ FileTypePoliciesSingletonTrait>::get();
+}
+
+FileTypePolicies::FileTypePolicies() {
+ // Setup a file-type policy to use if the ResourceBundle is unreadable.
+ // This should normally never be used.
+ last_resort_default_.set_uma_value(-1l);
+ last_resort_default_.set_ping_setting(DownloadFileType::NO_PING);
+ auto* settings = last_resort_default_.add_platform_settings();
+ settings->set_danger_level(DownloadFileType::ALLOW_ON_USER_GESTURE);
+ settings->set_auto_open_hint(DownloadFileType::DISALLOW_AUTO_OPEN);
+}
+
+FileTypePolicies::~FileTypePolicies() {
+ AutoLock lock(lock_); // DCHECK fail if the lock is held.
+}
+
+void FileTypePolicies::ReadResourceBundle(std::string* binary_pb) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ bundle.GetRawDataResource(IDR_DOWNLOAD_FILE_TYPES_PB).CopyToString(binary_pb);
+}
+
+void FileTypePolicies::RecordUpdateMetrics(UpdateResult result,
+ const std::string& src_name) {
+ lock_.AssertAcquired();
+ // src_name should be "ResourceBundle" or "DynamicUpdate".
+ base::UmaHistogramSparse("SafeBrowsing.FileTypeUpdate." + src_name + "Result",
+ static_cast<unsigned int>(result));
+
+ if (result == UpdateResult::SUCCESS) {
+ base::UmaHistogramSparse(
+ "SafeBrowsing.FileTypeUpdate." + src_name + "Version",
+ config_->version_id());
+ base::UmaHistogramSparse(
+ "SafeBrowsing.FileTypeUpdate." + src_name + "TypeCount",
+ config_->file_types().size());
+ }
+}
+
+void FileTypePolicies::PopulateFromResourceBundle() {
+ AutoLock lock(lock_);
+ std::string binary_pb;
+ ReadResourceBundle(&binary_pb);
+ UpdateResult result = PopulateFromBinaryPb(binary_pb);
+ RecordUpdateMetrics(result, "ResourceBundle");
+}
+
+void FileTypePolicies::PopulateFromDynamicUpdate(const std::string& binary_pb) {
+ AutoLock lock(lock_);
+ UpdateResult result = PopulateFromBinaryPb(binary_pb);
+ RecordUpdateMetrics(result, "DynamicUpdate");
+}
+
+FileTypePolicies::UpdateResult FileTypePolicies::PopulateFromBinaryPb(
+ const std::string& binary_pb) {
+ lock_.AssertAcquired();
+
+ // Parse the proto and do some validation on it.
+ if (binary_pb.empty())
+ return UpdateResult::FAILED_EMPTY;
+
+ std::unique_ptr<DownloadFileTypeConfig> new_config(
+ new DownloadFileTypeConfig);
+ if (!new_config->ParseFromString(binary_pb))
+ return UpdateResult::FAILED_PROTO_PARSE;
+
+ // Need at least a default setting.
+ if (new_config->default_file_type().platform_settings().size() == 0)
+ return UpdateResult::FAILED_DEFAULT_SETTING_SET;
+
+ // Every file type should have exactly one setting, pre-filtered for this
+ // platform.
+ for (const auto& file_type : new_config->file_types()) {
+ if (file_type.platform_settings().size() != 1)
+ return UpdateResult::FAILED_WRONG_SETTINGS_COUNT;
+ }
+
+ // Compare against existing config, if we have one.
+ if (config_) {
+ // If versions are equal, we skip the update but it's not really
+ // a failure.
+ if (new_config->version_id() == config_->version_id())
+ return UpdateResult::SKIPPED_VERSION_CHECK_EQUAL;
+
+ // Check that version number increases
+ if (new_config->version_id() <= config_->version_id())
+ return UpdateResult::FAILED_VERSION_CHECK;
+
+ // Check that we haven't dropped more than 1/2 the list.
+ if (new_config->file_types().size() * 2 < config_->file_types().size())
+ return UpdateResult::FAILED_DELTA_CHECK;
+ }
+
+ // Looks good. Update our internal list.
+ SwapConfigLocked(new_config);
+
+ return UpdateResult::SUCCESS;
+}
+
+void FileTypePolicies::SwapConfig(
+ std::unique_ptr<DownloadFileTypeConfig>& new_config) {
+ AutoLock lock(lock_);
+ SwapConfigLocked(new_config);
+}
+
+void FileTypePolicies::SwapConfigLocked(
+ std::unique_ptr<DownloadFileTypeConfig>& new_config) {
+ lock_.AssertAcquired();
+ config_.swap(new_config);
+
+ // Build an index for faster lookup.
+ file_type_by_ext_.clear();
+ for (const DownloadFileType& file_type : config_->file_types()) {
+ // If there are dups, first one wins.
+ file_type_by_ext_.insert(std::make_pair(file_type.extension(), &file_type));
+ }
+}
+
+// static
+base::FilePath::StringType FileTypePolicies::GetFileExtension(
+ const base::FilePath& file) {
+ // Remove trailing space and period characters from the extension.
+ base::FilePath::StringType file_basename = file.BaseName().value();
+ base::FilePath::StringPieceType trimmed_filename = base::TrimString(
+ file_basename, FILE_PATH_LITERAL(". "), base::TRIM_TRAILING);
+ return base::FilePath(trimmed_filename).FinalExtension();
+}
+
+// static
+std::string FileTypePolicies::CanonicalizedExtension(
+ const base::FilePath& file) {
+ // The policy list is all ASCII, so a non-ASCII extension won't be in it.
+ const base::FilePath::StringType ext = GetFileExtension(file);
+ std::string ascii_ext =
+ base::ToLowerASCII(base::FilePath(ext).MaybeAsASCII());
+ if (ascii_ext[0] == '.')
+ ascii_ext.erase(0, 1);
+ return ascii_ext;
+}
+
+//
+// Accessors
+//
+
+float FileTypePolicies::SampledPingProbability() const {
+ AutoLock lock(lock_);
+ return config_ ? config_->sampled_ping_probability() : 0.0;
+}
+
+const DownloadFileType& FileTypePolicies::PolicyForExtension(
+ const std::string& ascii_ext) const {
+ lock_.AssertAcquired();
+ // This could happen if the ResourceBundle is corrupted.
+ if (!config_) {
+ DCHECK(false);
+ return last_resort_default_;
+ }
+ auto itr = file_type_by_ext_.find(ascii_ext);
+ if (itr != file_type_by_ext_.end())
+ return *itr->second;
+ else
+ return config_->default_file_type();
+}
+
+DownloadFileType FileTypePolicies::PolicyForFile(
+ const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ AutoLock lock(lock_);
+ return PolicyForExtension(ext);
+}
+
+DownloadFileType::PlatformSettings FileTypePolicies::SettingsForFile(
+ const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ AutoLock lock(lock_);
+ DCHECK_EQ(1, PolicyForExtension(ext).platform_settings().size());
+ return PolicyForExtension(ext).platform_settings(0);
+}
+
+int64_t FileTypePolicies::UmaValueForFile(const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ AutoLock lock(lock_);
+ return PolicyForExtension(ext).uma_value();
+}
+
+bool FileTypePolicies::IsArchiveFile(const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ AutoLock lock(lock_);
+ return PolicyForExtension(ext).is_archive();
+}
+
+// TODO(nparker): Add unit tests for these accessors.
+
+bool FileTypePolicies::IsAllowedToOpenAutomatically(
+ const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ if (ext.empty())
+ return false;
+ AutoLock lock(lock_);
+ return PolicyForExtension(ext).platform_settings(0).auto_open_hint() ==
+ DownloadFileType::ALLOW_AUTO_OPEN;
+}
+
+DownloadFileType::PingSetting FileTypePolicies::PingSettingForFile(
+ const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ AutoLock lock(lock_);
+ return PolicyForExtension(ext).ping_setting();
+}
+
+bool FileTypePolicies::IsCheckedBinaryFile(const base::FilePath& file) const {
+ return PingSettingForFile(file) == DownloadFileType::FULL_PING;
+}
+
+DownloadFileType::DangerLevel FileTypePolicies::GetFileDangerLevel(
+ const base::FilePath& file) const {
+ const std::string ext = CanonicalizedExtension(file);
+ AutoLock lock(lock_);
+ return PolicyForExtension(ext).platform_settings(0).danger_level();
+}
+
+uint64_t FileTypePolicies::GetMaxFileSizeToAnalyze(
+ const std::string& ascii_ext) const {
+ AutoLock lock(lock_);
+ return PolicyForExtension(ascii_ext)
+ .platform_settings(0)
+ .max_file_size_to_analyze();
+}
+
+uint64_t FileTypePolicies::GetMaxArchivedBinariesToReport() const {
+ AutoLock lock(lock_);
+ if (!config_ || !config_->has_max_archived_binaries_to_report()) {
+ // The resource bundle may be corrupted.
+ DCHECK(false);
+ return 10; // reasonable default
+ }
+ return config_->max_archived_binaries_to_report();
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/file_type_policies.h b/chromium/chrome/common/safe_browsing/file_type_policies.h
new file mode 100644
index 00000000000..41b885071dd
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/file_type_policies.h
@@ -0,0 +1,156 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_FILE_TYPE_POLICIES_H_
+#define CHROME_COMMON_SAFE_BROWSING_FILE_TYPE_POLICIES_H_
+
+#include <map>
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/synchronization/lock.h"
+#include "chrome/common/safe_browsing/download_file_types.pb.h"
+
+namespace safe_browsing {
+
+struct FileTypePoliciesSingletonTrait;
+class FileTypePoliciesTestOverlay;
+
+// This holds a list of file types (aka file extensions) that we know about,
+// with policies related to how Safe Browsing and the download UI should treat
+// them.
+//
+// The data to populate it is read from a ResourceBundle and then also
+// fetched periodically from Google to get the most up-to-date policies.
+//
+// This is thread safe. We assume it is updated at most every few hours.
+
+class FileTypePolicies {
+ public:
+ virtual ~FileTypePolicies();
+
+ static FileTypePolicies* GetInstance(); // Singleton
+
+ // Update the internal list from a binary proto fetched from the network.
+ // Same integrity checks apply. This can be called multiple times with new
+ // protos.
+ void PopulateFromDynamicUpdate(const std::string& binary_pb);
+
+ //
+ // Static Utils
+ //
+
+ // Returns the final extension with the leading dot, after stripping
+ // trailing dots and spaces. It is difference from FilePath::Extension()
+ // and FilePath::FinalExtension().
+ // TODO(nparker): Consolidate. Maybe add this code to FinalExtension().
+ static base::FilePath::StringType GetFileExtension(
+ const base::FilePath& file);
+
+ //
+ // Accessors
+ //
+ bool IsArchiveFile(const base::FilePath& file) const;
+
+ // SBClientDownloadExtensions UMA histogram bucket for this file's type.
+ int64_t UmaValueForFile(const base::FilePath& file) const;
+
+ // True if download protection should send a ping to check
+ // this type of file.
+ bool IsCheckedBinaryFile(const base::FilePath& file) const;
+
+ // True if the user can select this file type to be opened automatically.
+ bool IsAllowedToOpenAutomatically(const base::FilePath& file) const;
+
+ // Return the danger level of this file type.
+ DownloadFileType::DangerLevel GetFileDangerLevel(
+ const base::FilePath& file) const;
+
+ // Return the type of ping we should send for this file
+ DownloadFileType::PingSetting PingSettingForFile(
+ const base::FilePath& file) const;
+
+ float SampledPingProbability() const;
+
+ DownloadFileType PolicyForFile(const base::FilePath& file) const;
+ DownloadFileType::PlatformSettings SettingsForFile(
+ const base::FilePath& file) const;
+
+ // Return max size for which unpacking and/or binary feature extration is
+ // supported for the given file extension.
+ uint64_t GetMaxFileSizeToAnalyze(const std::string& ascii_ext) const;
+
+ // Return max number of archived_binaries we should add to a download ping.
+ uint64_t GetMaxArchivedBinariesToReport() const;
+
+ protected:
+ // Creator must call one of Populate* before calling other methods.
+ FileTypePolicies();
+
+ // Used in metrics, do not reorder.
+ enum class UpdateResult {
+ SUCCESS = 1,
+ FAILED_EMPTY = 2,
+ FAILED_PROTO_PARSE = 3,
+ FAILED_DELTA_CHECK = 4,
+ FAILED_VERSION_CHECK = 5,
+ FAILED_DEFAULT_SETTING_SET = 6,
+ FAILED_WRONG_SETTINGS_COUNT = 7,
+ SKIPPED_VERSION_CHECK_EQUAL = 8,
+ };
+
+ // Read data from an serialized protobuf and update the internal list
+ // only if it passes integrity checks.
+ virtual UpdateResult PopulateFromBinaryPb(const std::string& binary_pb);
+
+ // Fetch the blob from the main resource bundle.
+ virtual void ReadResourceBundle(std::string* binary_pb);
+
+ // Record the result of an update attempt.
+ virtual void RecordUpdateMetrics(UpdateResult result,
+ const std::string& src_name);
+
+ // Return the ASCII lowercase extension w/o leading dot, or empty.
+ static std::string CanonicalizedExtension(const base::FilePath& file);
+
+ // Look up the policy for a given ASCII ext.
+ virtual const DownloadFileType& PolicyForExtension(
+ const std::string& ext) const;
+
+ private:
+ // Swap in a different config. This will rebuild file_type_by_ext_ index.
+ void SwapConfig(std::unique_ptr<DownloadFileTypeConfig>& new_config);
+ void SwapConfigLocked(std::unique_ptr<DownloadFileTypeConfig>& new_config);
+
+ // Read data from the main ResourceBundle. This updates the internal list
+ // only if the data passes integrity checks. This is normally called once
+ // after construction.
+ void PopulateFromResourceBundle();
+
+ // The latest config we've committed. Starts out null.
+ // Protected by lock_.
+ std::unique_ptr<DownloadFileTypeConfig> config_;
+
+ // This references entries in config_.
+ // Protected by lock_.
+ std::map<std::string, const DownloadFileType*> file_type_by_ext_;
+
+ // Type used if we can't load from disk.
+ // Written only in the constructor.
+ DownloadFileType last_resort_default_;
+
+ mutable base::Lock lock_;
+
+ FRIEND_TEST_ALL_PREFIXES(FileTypePoliciesTest, UnpackResourceBundle);
+ FRIEND_TEST_ALL_PREFIXES(FileTypePoliciesTest, BadProto);
+ FRIEND_TEST_ALL_PREFIXES(FileTypePoliciesTest, BadUpdateFromExisting);
+
+ friend struct FileTypePoliciesSingletonTrait;
+ friend class FileTypePoliciesTestOverlay;
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_FILE_TYPE_POLICIES_H_
diff --git a/chromium/chrome/common/safe_browsing/file_type_policies_test_util.cc b/chromium/chrome/common/safe_browsing/file_type_policies_test_util.cc
new file mode 100644
index 00000000000..d0520b9d176
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/file_type_policies_test_util.cc
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/file_type_policies_test_util.h"
+
+namespace safe_browsing {
+
+FileTypePoliciesTestOverlay::FileTypePoliciesTestOverlay()
+ : orig_config_(new DownloadFileTypeConfig()) {
+ // Make a copy of the global config so we can put it back later.
+ // Swap out, copy, swap back in.
+ SwapConfig(orig_config_);
+ std::unique_ptr<DownloadFileTypeConfig> copy_config = DuplicateConfig();
+ SwapConfig(copy_config);
+}
+
+FileTypePoliciesTestOverlay::~FileTypePoliciesTestOverlay() {
+ SwapConfig(orig_config_);
+}
+
+void FileTypePoliciesTestOverlay::SwapConfig(
+ std::unique_ptr<DownloadFileTypeConfig>& new_config) const {
+ FileTypePolicies::GetInstance()->SwapConfig(new_config);
+}
+
+std::unique_ptr<DownloadFileTypeConfig>
+FileTypePoliciesTestOverlay::DuplicateConfig() const {
+ std::unique_ptr<DownloadFileTypeConfig> new_config(
+ new DownloadFileTypeConfig());
+ // Deep copy
+ new_config->CopyFrom(*orig_config_);
+ return new_config;
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/file_type_policies_test_util.h b/chromium/chrome/common/safe_browsing/file_type_policies_test_util.h
new file mode 100644
index 00000000000..7d6fd54ceb1
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/file_type_policies_test_util.h
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_FILE_TYPE_POLICIES_TEST_UTIL_H_
+#define CHROME_COMMON_SAFE_BROWSING_FILE_TYPE_POLICIES_TEST_UTIL_H_
+
+#include "chrome/common/safe_browsing/file_type_policies.h"
+
+namespace safe_browsing {
+
+// This is a test fixture for modifying the proto with FileTypePolicies.
+// While an object of this class is in scope, it will cause callers
+// of FileTypePolicies::GetInstance() to see the modified list.
+// When it goes out of scope, future callers will get the original list.
+//
+// Example:
+// FileTypePoliciesTestOverlay overlay_;
+// std::unique_ptr<DownloadFileTypesConfig> cfg =
+// overlay_.DuplicateConfig();
+// cfg.set_sampled_ping_probability(1.0);
+// overlay_.SwapConfig(cfg);
+// ...
+class FileTypePoliciesTestOverlay {
+ public:
+ FileTypePoliciesTestOverlay();
+ ~FileTypePoliciesTestOverlay();
+
+ // Swaps the contents bewtween the existing config and |new_config|.
+ void SwapConfig(std::unique_ptr<DownloadFileTypeConfig>& new_config) const;
+
+ // Return a new copy of the original config.
+ std::unique_ptr<DownloadFileTypeConfig> DuplicateConfig() const;
+
+ private:
+ std::unique_ptr<DownloadFileTypeConfig> orig_config_;
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_FILE_TYPE_POLICIES_TEST_UTIL_H_
diff --git a/chromium/chrome/common/safe_browsing/file_type_policies_unittest.cc b/chromium/chrome/common/safe_browsing/file_type_policies_unittest.cc
new file mode 100644
index 00000000000..f682558dfe0
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/file_type_policies_unittest.cc
@@ -0,0 +1,219 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/file_type_policies.h"
+
+#include <string.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::NiceMock;
+
+namespace safe_browsing {
+
+class MockFileTypePolicies : public FileTypePolicies {
+ public:
+ MockFileTypePolicies() {}
+ ~MockFileTypePolicies() override {}
+
+ MOCK_METHOD2(RecordUpdateMetrics, void(UpdateResult, const std::string&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockFileTypePolicies);
+};
+
+class FileTypePoliciesTest : public testing::Test {
+ protected:
+ FileTypePoliciesTest() {}
+ ~FileTypePoliciesTest() override {}
+
+ protected:
+ NiceMock<MockFileTypePolicies> policies_;
+};
+
+TEST_F(FileTypePoliciesTest, UnpackResourceBundle) {
+ EXPECT_CALL(policies_,
+ RecordUpdateMetrics(FileTypePolicies::UpdateResult::SUCCESS,
+ "ResourceBundle"));
+ policies_.PopulateFromResourceBundle();
+
+ // Look up a few well known types to ensure they're present.
+ // Some types vary by OS, and we check one per OS to validate
+ // that gen_file_type_proto.py does its job.
+ //
+ // NOTE: If the settings for these change in download_file_types.asciipb,
+ // then you'll need to change them here as well.
+
+ // Lookup .exe that varies on OS_WIN.
+ base::FilePath exe_file(FILE_PATH_LITERAL("a/foo.exe"));
+ DownloadFileType file_type = policies_.PolicyForFile(exe_file);
+ EXPECT_EQ("exe", file_type.extension());
+ EXPECT_EQ(0l, file_type.uma_value());
+ EXPECT_FALSE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+#if defined(OS_WIN)
+ EXPECT_EQ(DownloadFileType::ALLOW_ON_USER_GESTURE,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::DISALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#else
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::ALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#endif
+
+ // Lookup .class that varies on OS_CHROMEOS, and also has a
+ // default setting set.
+ base::FilePath class_file(FILE_PATH_LITERAL("foo.class"));
+ file_type = policies_.PolicyForFile(class_file);
+ EXPECT_EQ("class", file_type.extension());
+ EXPECT_EQ(13l, file_type.uma_value());
+ EXPECT_FALSE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+#if defined(OS_CHROMEOS)
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::ALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#else
+ EXPECT_EQ(DownloadFileType::DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::DISALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#endif
+
+ // Lookup .dmg that varies on OS_MACOS
+ base::FilePath dmg_file(FILE_PATH_LITERAL("foo.dmg"));
+ file_type = policies_.PolicyForFile(dmg_file);
+ EXPECT_EQ("dmg", file_type.extension());
+ EXPECT_EQ(21, file_type.uma_value());
+ EXPECT_FALSE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+#if defined(OS_MACOSX)
+ EXPECT_EQ(DownloadFileType::ALLOW_ON_USER_GESTURE,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::DISALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#else
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::ALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#endif
+
+ // Lookup .dex that varies on OS_ANDROID and OS_CHROMEOS
+ base::FilePath dex_file(FILE_PATH_LITERAL("foo.dex"));
+ file_type = policies_.PolicyForFile(dex_file);
+ EXPECT_EQ("dex", file_type.extension());
+ EXPECT_EQ(143, file_type.uma_value());
+ EXPECT_FALSE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+ EXPECT_EQ(DownloadFileType::ALLOW_ON_USER_GESTURE,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::DISALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#else
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::ALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#endif
+
+ // Lookup .rpm that varies on OS_LINUX
+ base::FilePath rpm_file(FILE_PATH_LITERAL("foo.rpm"));
+ file_type = policies_.PolicyForFile(rpm_file);
+ EXPECT_EQ("rpm", file_type.extension());
+ EXPECT_EQ(142, file_type.uma_value());
+ EXPECT_FALSE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ EXPECT_EQ(DownloadFileType::ALLOW_ON_USER_GESTURE,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::DISALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#else
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::ALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+#endif
+
+ // Look .zip, an archive. The same on all platforms.
+ base::FilePath zip_file(FILE_PATH_LITERAL("b/bar.txt.zip"));
+ file_type = policies_.PolicyForFile(zip_file);
+ EXPECT_EQ("zip", file_type.extension());
+ EXPECT_EQ(7l, file_type.uma_value());
+ EXPECT_TRUE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+
+ // Check other accessors
+ EXPECT_EQ(7l, policies_.UmaValueForFile(zip_file));
+ EXPECT_TRUE(policies_.IsArchiveFile(zip_file));
+ EXPECT_FALSE(policies_.IsArchiveFile(exe_file));
+
+ // Verify settings on the default type.
+ file_type = policies_.PolicyForFile(
+ base::FilePath(FILE_PATH_LITERAL("a/foo.fooobar")));
+ EXPECT_EQ("", file_type.extension());
+ EXPECT_EQ(18l, file_type.uma_value());
+ EXPECT_FALSE(file_type.is_archive());
+ EXPECT_EQ(DownloadFileType::FULL_PING, file_type.ping_setting());
+ EXPECT_EQ(DownloadFileType::NOT_DANGEROUS,
+ file_type.platform_settings(0).danger_level());
+ EXPECT_EQ(DownloadFileType::ALLOW_AUTO_OPEN,
+ file_type.platform_settings(0).auto_open_hint());
+}
+
+TEST_F(FileTypePoliciesTest, BadProto) {
+ base::AutoLock lock(policies_.lock_);
+ EXPECT_EQ(FileTypePolicies::UpdateResult::FAILED_EMPTY,
+ policies_.PopulateFromBinaryPb(std::string()));
+
+ EXPECT_EQ(FileTypePolicies::UpdateResult::FAILED_PROTO_PARSE,
+ policies_.PopulateFromBinaryPb("foobar"));
+
+ DownloadFileTypeConfig cfg;
+ cfg.set_sampled_ping_probability(0.1f);
+ EXPECT_EQ(FileTypePolicies::UpdateResult::FAILED_DEFAULT_SETTING_SET,
+ policies_.PopulateFromBinaryPb(cfg.SerializeAsString()));
+
+ cfg.mutable_default_file_type()->add_platform_settings();
+ // This is missing a platform_setting.
+ auto* file_type = cfg.add_file_types();
+ EXPECT_EQ(FileTypePolicies::UpdateResult::FAILED_WRONG_SETTINGS_COUNT,
+ policies_.PopulateFromBinaryPb(cfg.SerializeAsString()));
+
+ file_type->add_platform_settings();
+ EXPECT_EQ(FileTypePolicies::UpdateResult::SUCCESS,
+ policies_.PopulateFromBinaryPb(cfg.SerializeAsString()));
+}
+
+TEST_F(FileTypePoliciesTest, BadUpdateFromExisting) {
+ base::AutoLock lock(policies_.lock_);
+ // Make a minimum viable config
+ DownloadFileTypeConfig cfg;
+ cfg.mutable_default_file_type()->add_platform_settings();
+ cfg.add_file_types()->add_platform_settings();
+ cfg.set_version_id(2);
+ EXPECT_EQ(FileTypePolicies::UpdateResult::SUCCESS,
+ policies_.PopulateFromBinaryPb(cfg.SerializeAsString()));
+
+ // Can't update to the same version
+ EXPECT_EQ(FileTypePolicies::UpdateResult::SKIPPED_VERSION_CHECK_EQUAL,
+ policies_.PopulateFromBinaryPb(cfg.SerializeAsString()));
+
+ // Can't go backward
+ cfg.set_version_id(1);
+ EXPECT_EQ(FileTypePolicies::UpdateResult::FAILED_VERSION_CHECK,
+ policies_.PopulateFromBinaryPb(cfg.SerializeAsString()));
+}
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/ipc_protobuf_message_macros.h b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_macros.h
new file mode 100644
index 00000000000..db17241ad7f
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_macros.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Protobuf Messages over IPC
+//
+// Protobuf messages are registered with IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN() and
+// friends in much the same way as other externally-defined structs (see
+// ipc/ipc_message_macros.h). These macros also cause only registration of the
+// protobuf message type IPC with message generation. Within matching calls to
+// _BEGIN() and _END(), one may use:
+// - IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER() to register an
+// optional field of fundamental type (any scalar message field type save
+// "string" and "bytes").
+// - IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER() to register an
+// optional field of complex type (scalar message field type "string" or
+// "bytes", or another message type).
+// - IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER() to register a
+// repeated field of complex type (scalar message field type "string" or
+// "bytes", or another message type).
+//
+// Enum types in protobuf messages are registered with
+// IPC_ENUM_TRAITS_VALIDATE() as with any other enum. In this case, the
+// validation expression should be the _IsValid() function provided by the
+// generated protobuf code. For example:
+//
+// IPC_ENUM_TRAITS_VALIDATE(MyEnumType, MyEnumType_IsValid(value))
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_MACROS_H_
+#define CHROME_COMMON_SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_MACROS_H_
+
+#include <string>
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(message_name) \
+ namespace IPC { \
+ template <> \
+ struct IPC_MESSAGE_EXPORT ParamTraits<message_name> { \
+ typedef message_name param_type; \
+ static void Write(base::Pickle* m, const param_type& p); \
+ static bool Read(const base::Pickle* m, \
+ base::PickleIterator* iter, \
+ param_type* p); \
+ static void Log(const param_type& p, std::string* l); \
+ \
+ private: \
+ template <class P> \
+ static bool ReadParamF(const base::Pickle* m, \
+ base::PickleIterator* iter, \
+ param_type* p, \
+ void (param_type::*setter_function)(P)); \
+ }; \
+ } // namespace IPC
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER(name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_END()
+
+#endif // CHROME_COMMON_SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_MACROS_H_
diff --git a/chromium/chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h
new file mode 100644
index 00000000000..ffb7fe0f433
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// No include guard, may be included multiple times.
+
+// NULL out all the macros that need NULLing, so that multiple includes of
+// *_generator.h files will not generate noise.
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_END
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(message_name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER(name)
+#define IPC_PROTOBUF_MESSAGE_TRAITS_END()
diff --git a/chromium/chrome/common/safe_browsing/ipc_protobuf_message_test.proto b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_test.proto
new file mode 100644
index 00000000000..8148461a68b
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_test.proto
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+message SubMessage {
+ optional int32 foo = 1;
+}
+
+message TestMessage {
+ optional int32 fund_int = 1;
+ optional string op_comp_string = 2;
+ optional bytes op_comp_bytes = 3;
+ optional SubMessage op_comp_sub = 4;
+ repeated SubMessage rep_comp_sub = 5;
+}
diff --git a/chromium/chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h
new file mode 100644
index 00000000000..64283ffc115
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
+#define SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
+
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_message_protobuf_utils.h"
+#include "chrome/common/safe_browsing/ipc_protobuf_message_macros.h"
+
+IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(SubMessage)
+ IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(foo)
+IPC_PROTOBUF_MESSAGE_TRAITS_END()
+
+IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(TestMessage)
+ IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(fund_int)
+ IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(op_comp_string)
+ IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(op_comp_bytes)
+ IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(op_comp_sub)
+ IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER(rep_comp_sub)
+IPC_PROTOBUF_MESSAGE_TRAITS_END()
+
+#endif // SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
diff --git a/chromium/chrome/common/safe_browsing/ipc_protobuf_message_unittest.cc b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_unittest.cc
new file mode 100644
index 00000000000..b66377511fc
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/ipc_protobuf_message_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/ipc_protobuf_message_test.pb.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define IPC_MESSAGE_IMPL
+#undef SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
+#include "chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h"
+
+// Generate ipc protobuf traits write methods.
+#include "chrome/common/safe_browsing/protobuf_message_write_macros.h"
+namespace IPC {
+#undef SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
+#include "chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h"
+} // namespace IPC
+
+// Generate ipc protobuf traits read methods.
+#include "chrome/common/safe_browsing/protobuf_message_read_macros.h"
+namespace IPC {
+#undef SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
+#include "chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h"
+} // namespace IPC
+
+// Generate ipc protobuf traits log methods.
+#include "chrome/common/safe_browsing/protobuf_message_log_macros.h"
+namespace IPC {
+#undef SAFE_BROWSING_IPC_PROTOBUF_MESSAGE_TEST_MESSAGES_H_
+#include "chrome/common/safe_browsing/ipc_protobuf_message_test_messages.h"
+} // namespace IPC
+
+class IPCProtobufMessageTest : public ::testing::TestWithParam<bool> {
+ protected:
+ IPCProtobufMessageTest() : field_is_present_(GetParam()) {}
+
+ bool field_is_present_;
+};
+
+// Tests writing and reading a message with an optional fundamental field.
+TEST_P(IPCProtobufMessageTest, FundamentalField) {
+ TestMessage input;
+
+ if (field_is_present_)
+ input.set_fund_int(42);
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ TestMessage output;
+ base::PickleIterator iter(msg);
+ ASSERT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ if (field_is_present_) {
+ ASSERT_TRUE(output.has_fund_int());
+ EXPECT_EQ(input.fund_int(), output.fund_int());
+ } else {
+ ASSERT_FALSE(output.has_fund_int());
+ }
+}
+
+// Tests writing and reading a message with an optional string field.
+TEST_P(IPCProtobufMessageTest, StringField) {
+ TestMessage input;
+
+ if (field_is_present_)
+ input.set_op_comp_string("some string");
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ TestMessage output;
+ base::PickleIterator iter(msg);
+ ASSERT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ if (field_is_present_) {
+ ASSERT_TRUE(output.has_op_comp_string());
+ EXPECT_EQ(input.op_comp_string(), output.op_comp_string());
+ } else {
+ ASSERT_FALSE(output.has_op_comp_string());
+ }
+}
+
+// Tests writing and reading a message with an optional bytes field.
+TEST_P(IPCProtobufMessageTest, BytesField) {
+ TestMessage input;
+
+ if (field_is_present_)
+ input.set_op_comp_bytes("some string");
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ TestMessage output;
+ base::PickleIterator iter(msg);
+ ASSERT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ if (field_is_present_) {
+ ASSERT_TRUE(output.has_op_comp_bytes());
+ EXPECT_EQ(input.op_comp_bytes(), output.op_comp_bytes());
+ } else {
+ ASSERT_FALSE(output.has_op_comp_bytes());
+ }
+}
+
+// Tests writing and reading a message with an optional submessage field.
+TEST_P(IPCProtobufMessageTest, OptionalSubmessage) {
+ TestMessage input;
+
+ if (field_is_present_)
+ input.mutable_op_comp_sub()->set_foo(47);
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ TestMessage output;
+ base::PickleIterator iter(msg);
+ ASSERT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ if (field_is_present_) {
+ ASSERT_TRUE(output.has_op_comp_sub());
+ ASSERT_TRUE(output.op_comp_sub().has_foo());
+ EXPECT_EQ(input.op_comp_sub().foo(), output.op_comp_sub().foo());
+ } else {
+ ASSERT_FALSE(output.has_op_comp_sub());
+ }
+}
+
+// Tests writing and reading a message with a repeated submessage field.
+TEST_P(IPCProtobufMessageTest, RepeatedSubmessage) {
+ TestMessage input;
+
+ if (field_is_present_) {
+ input.add_rep_comp_sub()->set_foo(0);
+ input.add_rep_comp_sub()->set_foo(1);
+ input.add_rep_comp_sub()->set_foo(2);
+ }
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ TestMessage output;
+ base::PickleIterator iter(msg);
+ ASSERT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ if (field_is_present_) {
+ ASSERT_EQ(3, output.rep_comp_sub_size());
+ ASSERT_TRUE(output.rep_comp_sub(0).has_foo());
+ EXPECT_EQ(input.rep_comp_sub(0).foo(), output.rep_comp_sub(0).foo());
+ ASSERT_TRUE(output.rep_comp_sub(1).has_foo());
+ EXPECT_EQ(input.rep_comp_sub(1).foo(), output.rep_comp_sub(1).foo());
+ ASSERT_TRUE(output.rep_comp_sub(2).has_foo());
+ EXPECT_EQ(input.rep_comp_sub(2).foo(), output.rep_comp_sub(2).foo());
+ } else {
+ ASSERT_EQ(0, output.rep_comp_sub_size());
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(IPCProtobufMessage,
+ IPCProtobufMessageTest,
+ ::testing::Bool());
diff --git a/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.cc b/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.cc
new file mode 100644
index 00000000000..655fc56c6d8
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.cc
@@ -0,0 +1,257 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
+
+#include <libkern/OSByteOrder.h>
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+
+#include "base/logging.h"
+#include "base/numerics/safe_math.h"
+
+namespace safe_browsing {
+
+// ByteSlice is a bounds-checking view of an arbitrary byte array.
+class ByteSlice {
+ public:
+ // Creates an invalid byte slice.
+ ByteSlice() : ByteSlice(nullptr, 0) {}
+
+ // Creates a slice for a given data array.
+ explicit ByteSlice(const uint8_t* data, size_t size)
+ : data_(data), size_(size) {}
+ ~ByteSlice() {}
+
+ bool IsValid() {
+ return data_ != nullptr;
+ }
+
+ // Creates a sub-slice from the current slice.
+ ByteSlice Slice(size_t at, size_t size) {
+ if (!RangeCheck(at, size))
+ return ByteSlice();
+ return ByteSlice(data_ + at, size);
+ }
+
+ // Casts an offset to a specific type.
+ template <typename T>
+ const T* GetPointerAt(size_t at) {
+ if (!RangeCheck(at, sizeof(T)))
+ return nullptr;
+ return reinterpret_cast<const T*>(data_ + at);
+ }
+
+ // Copies data from an offset to a buffer.
+ bool CopyDataAt(size_t at, size_t size, uint8_t* out_data) {
+ if (!RangeCheck(at, size))
+ return false;
+ memcpy(out_data, data_ + at, size);
+ return true;
+ }
+
+ bool RangeCheck(size_t offset, size_t size) {
+ if (offset >= size_)
+ return false;
+ base::CheckedNumeric<size_t> range(offset);
+ range += size;
+ if (!range.IsValid())
+ return false;
+ return range.ValueOrDie() <= size_;
+ }
+
+ const uint8_t* data() const { return data_; }
+ size_t size() const { return size_; }
+
+ private:
+ const uint8_t* data_;
+ size_t size_;
+
+ // Copy and assign allowed.
+};
+
+MachOImageReader::LoadCommand::LoadCommand() {}
+
+MachOImageReader::LoadCommand::LoadCommand(const LoadCommand& other) = default;
+
+MachOImageReader::LoadCommand::~LoadCommand() {}
+
+// static
+bool MachOImageReader::IsMachOMagicValue(uint32_t magic) {
+ return magic == FAT_MAGIC || magic == FAT_CIGAM ||
+ magic == MH_MAGIC || magic == MH_CIGAM ||
+ magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
+}
+
+MachOImageReader::MachOImageReader()
+ : data_(),
+ is_fat_(false),
+ is_64_bit_(false),
+ commands_() {
+}
+
+MachOImageReader::~MachOImageReader() {}
+
+bool MachOImageReader::Initialize(const uint8_t* image, size_t image_size) {
+ if (!image)
+ return false;
+
+ data_.reset(new ByteSlice(image, image_size));
+
+ const uint32_t* magic = data_->GetPointerAt<uint32_t>(0);
+ if (!magic)
+ return false;
+
+ // Check if this is a fat file. Note that the fat_header and fat_arch
+ // structs are always in big endian.
+ is_fat_ = *magic == FAT_MAGIC || *magic == FAT_CIGAM;
+ if (is_fat_) {
+ const fat_header* header = data_->GetPointerAt<fat_header>(0);
+ if (!header)
+ return false;
+
+ bool do_swap = header->magic == FAT_CIGAM;
+ uint32_t nfat_arch = do_swap ? OSSwapInt32(header->nfat_arch)
+ : header->nfat_arch;
+
+ size_t offset = sizeof(*header);
+ for (uint32_t i = 0; i < nfat_arch; ++i) {
+ const fat_arch* arch = data_->GetPointerAt<fat_arch>(offset);
+ if (!arch)
+ return false;
+
+ uint32_t arch_offset = do_swap ? OSSwapInt32(arch->offset) : arch->offset;
+ uint32_t arch_size = do_swap ? OSSwapInt32(arch->size) : arch->size;
+
+ // Cannot refer back to headers of previous arches to cause
+ // recursive processing.
+ if (arch_offset < offset)
+ return false;
+
+ ByteSlice slice = data_->Slice(arch_offset, arch_size);
+ if (!slice.IsValid())
+ return false;
+
+ fat_images_.push_back(std::make_unique<MachOImageReader>());
+ if (!fat_images_.back()->Initialize(slice.data(), slice.size()))
+ return false;
+
+ offset += sizeof(*arch);
+ }
+
+ return true;
+ }
+
+ bool do_swap = *magic == MH_CIGAM || *magic == MH_CIGAM_64;
+
+ // Make sure this is a Mach-O file.
+ is_64_bit_ = *magic == MH_MAGIC_64 || *magic == MH_CIGAM_64;
+ if (!(is_64_bit_ || *magic == MH_MAGIC || do_swap))
+ return false;
+
+ // Read the full Mach-O image header.
+ if (is_64_bit_) {
+ if (!GetMachHeader64())
+ return false;
+ } else {
+ if (!GetMachHeader())
+ return false;
+ }
+
+ // Collect all the load commands for the binary.
+ const size_t load_command_size = sizeof(load_command);
+ size_t offset = is_64_bit_ ? sizeof(mach_header_64) : sizeof(mach_header);
+ const uint32_t num_commands = do_swap ? OSSwapInt32(GetMachHeader()->ncmds)
+ : GetMachHeader()->ncmds;
+ commands_.resize(num_commands);
+ for (uint32_t i = 0; i < num_commands; ++i) {
+ LoadCommand* command = &commands_[i];
+
+ command->data.resize(load_command_size);
+ if (!data_->CopyDataAt(offset, load_command_size, &command->data[0])) {
+ return false;
+ }
+
+ uint32_t cmdsize = do_swap ? OSSwapInt32(command->cmdsize())
+ : command->cmdsize();
+ // If the load_command's reported size is smaller than the size of the base
+ // struct, do not try to copy additional data (or resize to be smaller
+ // than the base struct). This may not be valid Mach-O.
+ if (cmdsize < load_command_size) {
+ offset += load_command_size;
+ continue;
+ }
+
+ command->data.resize(cmdsize);
+ if (!data_->CopyDataAt(offset, cmdsize, &command->data[0])) {
+ return false;
+ }
+
+ offset += cmdsize;
+ }
+
+ return true;
+}
+
+bool MachOImageReader::IsFat() {
+ return is_fat_;
+}
+
+std::vector<MachOImageReader*> MachOImageReader::GetFatImages() {
+ DCHECK(is_fat_);
+ std::vector<MachOImageReader*> images;
+ for (const auto& image : fat_images_)
+ images.push_back(image.get());
+ return images;
+}
+
+bool MachOImageReader::Is64Bit() {
+ DCHECK(!is_fat_);
+ return is_64_bit_;
+}
+
+const mach_header* MachOImageReader::GetMachHeader() {
+ DCHECK(!is_fat_);
+ return data_->GetPointerAt<mach_header>(0);
+}
+
+const mach_header_64* MachOImageReader::GetMachHeader64() {
+ DCHECK(is_64_bit_);
+ DCHECK(!is_fat_);
+ return data_->GetPointerAt<mach_header_64>(0);
+}
+
+uint32_t MachOImageReader::GetFileType() {
+ DCHECK(!is_fat_);
+ return GetMachHeader()->filetype;
+}
+
+const std::vector<MachOImageReader::LoadCommand>&
+MachOImageReader::GetLoadCommands() {
+ DCHECK(!is_fat_);
+ return commands_;
+}
+
+bool MachOImageReader::GetCodeSignatureInfo(std::vector<uint8_t>* info) {
+ DCHECK(!is_fat_);
+ DCHECK(info->empty());
+
+ // Find the LC_CODE_SIGNATURE command and cast it to its linkedit format.
+ const linkedit_data_command* lc_code_signature = nullptr;
+ for (const auto& command : commands_) {
+ if (command.cmd() == LC_CODE_SIGNATURE) {
+ lc_code_signature = command.as_command<linkedit_data_command>();
+ break;
+ }
+ }
+ if (lc_code_signature == nullptr)
+ return false;
+
+ info->resize(lc_code_signature->datasize);
+ return data_->CopyDataAt(lc_code_signature->dataoff,
+ lc_code_signature->datasize,
+ &(*info)[0]);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.h b/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.h
new file mode 100644
index 00000000000..5f93e6285fb
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.h
@@ -0,0 +1,107 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_MACH_O_IMAGE_READER_MAC_H_
+#define CHROME_COMMON_SAFE_BROWSING_MACH_O_IMAGE_READER_MAC_H_
+
+#include <mach-o/loader.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace safe_browsing {
+
+class ByteSlice;
+
+// MachOImageReader is used to extract information about a Mach-O binary image.
+// This class supports fat and thin images. Initialize() must be called before
+// any other methods; if it returns false, it is illegal to call any other
+// methods on this class.
+class MachOImageReader {
+ public:
+ // Represents a Mach-O load command, including all of its data.
+ struct LoadCommand {
+ LoadCommand();
+ LoadCommand(const LoadCommand& other);
+ ~LoadCommand();
+
+ uint32_t cmd() const {
+ return as_command<load_command>()->cmd;
+ }
+
+ uint32_t cmdsize() const {
+ return as_command<load_command>()->cmdsize;
+ }
+
+ template <typename T>
+ const T* as_command() const {
+ if (data.size() < sizeof(T))
+ return nullptr;
+ return reinterpret_cast<const T*>(&data[0]);
+ }
+
+ std::vector<uint8_t> data;
+ };
+
+ // Returns true if |magic| is any Mach-O magic number. This can be used on the
+ // first four bytes of a file (either in little- or big-endian) to quickly
+ // determine whether or not the file is potentially a Mach-O file. An instance
+ // of this class must be used for a true validity check.
+ static bool IsMachOMagicValue(uint32_t magic);
+
+ MachOImageReader();
+ ~MachOImageReader();
+
+ // Initializes the instance and verifies that the data is a valid Mach-O
+ // image. This does not take ownership of the bytes, so the data must
+ // remain valid for the lifetime of this object. Returns true if the
+ // instance is initialized and valid, false if the file could not be parsed
+ // as a Mach-O image.
+ bool Initialize(const uint8_t* image, size_t image_size);
+
+ // Returns whether this is a fat Mach-O image. If this returns true, it is
+ // only valid to call GetFatImages() and none of the other methods.
+ bool IsFat();
+
+ // It is only valid to call this method if IsFat() returns true. This
+ // returns an image reader for each architecture in the fat file.
+ std::vector<MachOImageReader*> GetFatImages();
+
+ // Returns whether the image is a 64-bit image.
+ bool Is64Bit();
+
+ // Retrieves the mach_header structure for the appropriate architecture.
+ const mach_header* GetMachHeader();
+ const mach_header_64* GetMachHeader64();
+
+ // Returns the Mach-O filetype field from the header.
+ uint32_t GetFileType();
+
+ // Returns an array of all the load commands in the image.
+ const std::vector<MachOImageReader::LoadCommand>& GetLoadCommands();
+
+ // If the image has a LC_CODE_SIGNATURE command, this retreives the code
+ // signature blob in the __LINKEDIT segment.
+ bool GetCodeSignatureInfo(std::vector<uint8_t>* info);
+
+ private:
+ std::unique_ptr<ByteSlice> data_;
+
+ bool is_fat_;
+ std::vector<std::unique_ptr<MachOImageReader>> fat_images_;
+
+ bool is_64_bit_;
+ std::vector<LoadCommand> commands_;
+
+ DISALLOW_COPY_AND_ASSIGN(MachOImageReader);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_MACH_O_IMAGE_READER_MAC_H_
diff --git a/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac_unittest.cc b/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac_unittest.cc
new file mode 100644
index 00000000000..5d6d270dbfa
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/mach_o_image_reader_mac_unittest.cc
@@ -0,0 +1,515 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
+
+#include <arpa/inet.h>
+#include <libkern/OSByteOrder.h>
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <uuid/uuid.h>
+
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+namespace {
+
+// Definitions from
+// <http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/codesign.h>.
+
+enum {
+ CSMAGIC_CODEDIRECTORY = 0xfade0c02,
+ CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0,
+
+ CSSLOT_CODEDIRECTORY = 0,
+};
+
+struct CodeSigningBlob {
+ uint32_t type;
+ uint32_t offset;
+};
+
+struct CodeSigningSuperBlob {
+ uint32_t magic;
+ uint32_t length;
+ uint32_t count;
+ CodeSigningBlob index[];
+};
+
+struct CodeSigningDirectory {
+ uint32_t magic;
+ uint32_t length;
+ uint32_t version;
+ uint32_t flags;
+ uint32_t hashOffset;
+ uint32_t identOffset;
+ uint32_t nSpecialSlots;
+ uint32_t nCodeSlots;
+ uint32_t codeLimit;
+ uint8_t hashSize;
+ uint8_t hashType;
+ uint8_t spare1;
+ uint8_t pageSize;
+ uint32_t spare2;
+ // Version 0x20100.
+ uint32_t scatterOffset;
+ // Version 0x20200.
+ uint32_t teamOffset;
+};
+
+class MachOImageReaderTest : public testing::Test {
+ protected:
+ void OpenTestFile(const char* file_name, base::MemoryMappedFile* file) {
+ base::FilePath test_data;
+ ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
+
+ base::FilePath path = test_data.AppendASCII("safe_browsing")
+ .AppendASCII("mach_o")
+ .AppendASCII(file_name);
+
+ ASSERT_TRUE(file->Initialize(path));
+ }
+
+ // Returns the identity of the signed code data.
+ void GetSigningIdentity(const std::vector<uint8_t>& signature,
+ std::string* identity) {
+ auto* super_blob =
+ reinterpret_cast<const CodeSigningSuperBlob*>(&signature[0]);
+ EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE, ntohl(super_blob->magic));
+ ASSERT_EQ(CSSLOT_CODEDIRECTORY, ntohl(super_blob->index[0].type));
+ size_t dir_offset = ntohl(super_blob->index[0].offset);
+ auto* directory =
+ reinterpret_cast<const CodeSigningDirectory*>(&signature[dir_offset]);
+ ASSERT_EQ(CSMAGIC_CODEDIRECTORY, ntohl(directory->magic));
+ size_t ident_offset = ntohl(directory->identOffset) + dir_offset;
+ *identity =
+ std::string(reinterpret_cast<const char*>(&signature[ident_offset]));
+ }
+
+ // Returns the hash of the code data itself. Note that this is not the
+ // CDHash, but is instead the hash in the CodeDirectory blob, which is
+ // over the contents of the signed data. This is visible as hash #0
+ // when using `codesign -d -vvvvvv`.
+ void GetCodeSignatureHash(const std::vector<uint8_t>& signature,
+ std::vector<uint8_t>* hash) {
+ auto* super_blob =
+ reinterpret_cast<const CodeSigningSuperBlob*>(&signature[0]);
+ EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE, ntohl(super_blob->magic));
+ ASSERT_EQ(CSSLOT_CODEDIRECTORY, ntohl(super_blob->index[0].type));
+ size_t dir_offset = ntohl(super_blob->index[0].offset);
+ auto* directory =
+ reinterpret_cast<const CodeSigningDirectory*>(&signature[dir_offset]);
+ ASSERT_EQ(CSMAGIC_CODEDIRECTORY, ntohl(directory->magic));
+ size_t hash_offset = ntohl(directory->hashOffset) + dir_offset;
+ std::vector<uint8_t> actual_hash(&signature[hash_offset],
+ &signature[hash_offset + directory->hashSize]);
+ EXPECT_EQ(20u, actual_hash.size());
+ *hash = actual_hash;
+ }
+
+ void ExpectCodeSignatureHash(const std::vector<uint8_t>& signature,
+ const char* expected) {
+ std::vector<uint8_t> actual_hash;
+ GetCodeSignatureHash(signature, &actual_hash);
+
+ std::vector<uint8_t> expected_hash;
+ ASSERT_TRUE(base::HexStringToBytes(expected, &expected_hash));
+ EXPECT_EQ(expected_hash, actual_hash);
+ }
+};
+
+TEST_F(MachOImageReaderTest, Executable32) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable32", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_FALSE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), reader.GetFileType());
+ EXPECT_EQ(15u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+
+ // Test an arbitrary load command.
+ auto commands = reader.GetLoadCommands();
+ ASSERT_EQ(15u, commands.size());
+ auto command = commands[11];
+ ASSERT_EQ(static_cast<uint32_t>(LC_LOAD_DYLIB), command.cmd());
+ auto* actual = command.as_command<dylib_command>();
+ EXPECT_EQ(2u, actual->dylib.timestamp);
+ EXPECT_EQ(0x4ad0101u, actual->dylib.current_version);
+ EXPECT_EQ(0x10000u, actual->dylib.compatibility_version);
+}
+
+TEST_F(MachOImageReaderTest, Executable64) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable64", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_TRUE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_TRUE(reader.GetMachHeader64());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), reader.GetFileType());
+ EXPECT_EQ(15u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+}
+
+TEST_F(MachOImageReaderTest, ExecutableFat) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("executablefat", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_TRUE(reader.IsFat());
+ auto images = reader.GetFatImages();
+ ASSERT_EQ(2u, images.size());
+
+ // Note: this image is crafted to have 32-bit first.
+ {
+ EXPECT_FALSE(images[0]->IsFat());
+ EXPECT_FALSE(images[0]->Is64Bit());
+ EXPECT_TRUE(images[0]->GetMachHeader());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), images[0]->GetFileType());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(images[0]->GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+ }
+
+ {
+ EXPECT_FALSE(images[1]->IsFat());
+ EXPECT_TRUE(images[1]->Is64Bit());
+ EXPECT_TRUE(images[1]->GetMachHeader());
+ EXPECT_TRUE(images[1]->GetMachHeader64());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), images[1]->GetFileType());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(images[1]->GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+
+ // Test an arbitrary load command.
+ auto commands = images[1]->GetLoadCommands();
+ ASSERT_EQ(15u, commands.size());
+ auto command = commands[1];
+ ASSERT_EQ(static_cast<uint32_t>(LC_SEGMENT_64), command.cmd());
+ auto* actual = command.as_command<segment_command_64>();
+ EXPECT_EQ("__TEXT", std::string(actual->segname));
+ EXPECT_EQ(0u, actual->fileoff);
+ EXPECT_EQ(4096u, actual->filesize);
+ EXPECT_EQ(0x7, actual->maxprot);
+ EXPECT_EQ(0x5, actual->initprot);
+ EXPECT_EQ(3u, actual->nsects);
+ EXPECT_EQ(0u, actual->flags);
+ }
+}
+
+TEST_F(MachOImageReaderTest, ExecutablePPC) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("executableppc", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_FALSE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_EQ(OSSwapInt32(MH_EXECUTE), reader.GetFileType());
+ EXPECT_EQ(10u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+}
+
+TEST_F(MachOImageReaderTest, Dylib32) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib32.dylib", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_FALSE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB), reader.GetFileType());
+ EXPECT_EQ(13u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+}
+
+TEST_F(MachOImageReaderTest, Dylib64) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib64.dylib", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_TRUE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_TRUE(reader.GetMachHeader64());
+ EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB), reader.GetFileType());
+ EXPECT_EQ(13u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+
+ // Test an arbitrary load command.
+ auto commands = reader.GetLoadCommands();
+ ASSERT_EQ(13u, commands.size());
+ auto command = commands[6];
+ ASSERT_EQ(static_cast<uint32_t>(LC_UUID), command.cmd());
+ uuid_t expected = {0xB6, 0xB5, 0x12, 0xD7,
+ 0x64, 0xE9,
+ 0x3F, 0x7A,
+ 0xAB, 0x4A,
+ 0x87, 0x46, 0x36, 0x76, 0x87, 0x47};
+ EXPECT_EQ(0, uuid_compare(expected,
+ command.as_command<uuid_command>()->uuid));
+}
+
+TEST_F(MachOImageReaderTest, DylibFat) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("libfat.dylib", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_TRUE(reader.IsFat());
+ auto images = reader.GetFatImages();
+ ASSERT_EQ(2u, images.size());
+
+ // Note: this image is crafted to have 64-bit first.
+ {
+ EXPECT_FALSE(images[0]->IsFat());
+ EXPECT_TRUE(images[0]->Is64Bit());
+ EXPECT_TRUE(images[0]->GetMachHeader());
+ EXPECT_TRUE(images[0]->GetMachHeader64());
+ EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB), images[0]->GetFileType());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(images[0]->GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+ }
+
+ {
+ EXPECT_FALSE(images[1]->IsFat());
+ EXPECT_FALSE(images[1]->Is64Bit());
+ EXPECT_TRUE(images[1]->GetMachHeader());
+ EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB), images[1]->GetFileType());
+
+ std::vector<uint8_t> signature;
+ EXPECT_FALSE(images[1]->GetCodeSignatureInfo(&signature));
+ EXPECT_TRUE(signature.empty());
+ }
+}
+
+TEST_F(MachOImageReaderTest, SignedExecutable32) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutable32", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_FALSE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), reader.GetFileType());
+ EXPECT_EQ(16u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_TRUE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_EQ(9344u, signature.size());
+
+ std::string identity;
+ GetSigningIdentity(signature, &identity);
+ EXPECT_EQ("signedexecutable32", identity);
+
+ ExpectCodeSignatureHash(signature,
+ "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5");
+}
+
+TEST_F(MachOImageReaderTest, SignedExecutableFat) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutablefat", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_TRUE(reader.IsFat());
+ auto images = reader.GetFatImages();
+ ASSERT_EQ(2u, images.size());
+
+ // Note: this image is crafted to have 32-bit first.
+ {
+ EXPECT_FALSE(images[0]->IsFat());
+ EXPECT_FALSE(images[0]->Is64Bit());
+ EXPECT_TRUE(images[0]->GetMachHeader());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), images[0]->GetFileType());
+
+ std::vector<uint8_t> signature;
+ EXPECT_TRUE(images[0]->GetCodeSignatureInfo(&signature));
+ EXPECT_EQ(9344u, signature.size());
+
+ std::string identity;
+ GetSigningIdentity(signature, &identity);
+ EXPECT_EQ("signedexecutablefat", identity);
+
+ ExpectCodeSignatureHash(signature,
+ "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5");
+ }
+
+ {
+ EXPECT_FALSE(images[1]->IsFat());
+ EXPECT_TRUE(images[1]->Is64Bit());
+ EXPECT_TRUE(images[1]->GetMachHeader());
+ EXPECT_TRUE(images[1]->GetMachHeader64());
+ EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), images[1]->GetFileType());
+
+ std::vector<uint8_t> signature;
+ EXPECT_TRUE(images[1]->GetCodeSignatureInfo(&signature));
+ EXPECT_EQ(9344u, signature.size());
+
+ std::string identity;
+ GetSigningIdentity(signature, &identity);
+ EXPECT_EQ("signedexecutablefat", identity);
+
+ ExpectCodeSignatureHash(signature,
+ "750a57326ba85857371094900475defd837f5e14");
+ }
+}
+
+TEST_F(MachOImageReaderTest, SignedDylib64) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("libsigned64.dylib", &file));
+ MachOImageReader reader;
+ ASSERT_TRUE(reader.Initialize(file.data(), file.length()));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_TRUE(reader.Is64Bit());
+ EXPECT_TRUE(reader.GetMachHeader());
+ EXPECT_TRUE(reader.GetMachHeader64());
+ EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB), reader.GetFileType());
+ EXPECT_EQ(14u, reader.GetLoadCommands().size());
+
+ std::vector<uint8_t> signature;
+ EXPECT_TRUE(reader.GetCodeSignatureInfo(&signature));
+ EXPECT_EQ(9328u, signature.size());
+
+ std::string identity;
+ GetSigningIdentity(signature, &identity);
+ EXPECT_EQ("libsigned64", identity);
+
+ ExpectCodeSignatureHash(signature,
+ "8b1c79b60bb53a7f17b5618d5feb10dc8b88d806");
+}
+
+TEST_F(MachOImageReaderTest, NotMachO) {
+ base::MemoryMappedFile file;
+ ASSERT_NO_FATAL_FAILURE(OpenTestFile("src.c", &file));
+ MachOImageReader reader;
+ EXPECT_FALSE(reader.Initialize(file.data(), file.length()));
+}
+
+TEST_F(MachOImageReaderTest, IsMachOMagicValue) {
+ static const uint32_t kMagics[] = { MH_MAGIC, MH_MAGIC, FAT_MAGIC };
+ for (uint32_t magic : kMagics) {
+ SCOPED_TRACE(base::StringPrintf("0x%x", magic));
+ EXPECT_TRUE(MachOImageReader::IsMachOMagicValue(magic));
+ EXPECT_TRUE(MachOImageReader::IsMachOMagicValue(OSSwapInt32(magic)));
+ }
+}
+
+// https://crbug.com/524044
+TEST_F(MachOImageReaderTest, CmdsizeSmallerThanLoadCommand) {
+#pragma pack(push, 1)
+ struct TestImage {
+ mach_header_64 header;
+ segment_command_64 page_zero;
+ load_command small_sized;
+ segment_command_64 fake_code;
+ };
+#pragma pack(pop)
+
+ TestImage test_image = {};
+
+ test_image.header.magic = MH_MAGIC_64;
+ test_image.header.cputype = CPU_TYPE_X86_64;
+ test_image.header.filetype = MH_EXECUTE;
+ test_image.header.ncmds = 3;
+ test_image.header.sizeofcmds = sizeof(test_image) - sizeof(test_image.header);
+
+ test_image.page_zero.cmd = LC_SEGMENT;
+ test_image.page_zero.cmdsize = sizeof(test_image.page_zero);
+ strcpy(test_image.page_zero.segname, SEG_PAGEZERO);
+ test_image.page_zero.vmsize = PAGE_SIZE;
+
+ test_image.small_sized.cmd = LC_SYMSEG;
+ test_image.small_sized.cmdsize = sizeof(test_image.small_sized) - 3;
+
+ test_image.fake_code.cmd = LC_SEGMENT;
+ test_image.fake_code.cmdsize = sizeof(test_image.fake_code);
+ strcpy(test_image.fake_code.segname, SEG_TEXT);
+
+ MachOImageReader reader;
+ EXPECT_TRUE(reader.Initialize(reinterpret_cast<const uint8_t*>(&test_image),
+ sizeof(test_image)));
+
+ EXPECT_FALSE(reader.IsFat());
+ EXPECT_TRUE(reader.Is64Bit());
+
+ const auto& load_commands = reader.GetLoadCommands();
+ EXPECT_EQ(3u, load_commands.size());
+
+ EXPECT_EQ(static_cast<uint32_t>(LC_SEGMENT), load_commands[0].cmd());
+ EXPECT_EQ(static_cast<uint32_t>(LC_SYMSEG), load_commands[1].cmd());
+ EXPECT_EQ(sizeof(load_command) - 3, load_commands[1].cmdsize());
+ EXPECT_EQ(static_cast<uint32_t>(LC_SEGMENT), load_commands[2].cmd());
+}
+
+// https://crbug.com/591194
+TEST_F(MachOImageReaderTest, RecurseFatHeader) {
+#pragma pack(push, 1)
+ struct TestImage {
+ fat_header header;
+ fat_arch arch1;
+ fat_arch arch2;
+ mach_header_64 macho64;
+ mach_header macho;
+ };
+#pragma pack(pop)
+
+ TestImage test_image = {};
+ test_image.header.magic = FAT_MAGIC;
+ test_image.header.nfat_arch = 2;
+ test_image.arch1.offset = offsetof(TestImage, macho64);
+ test_image.arch1.size = sizeof(mach_header_64);
+ test_image.arch2.offset = 0; // Cannot point back at the fat_header.
+ test_image.arch2.size = sizeof(test_image);
+
+ test_image.macho64.magic = MH_MAGIC_64;
+ test_image.macho.magic = MH_MAGIC;
+
+ MachOImageReader reader;
+ EXPECT_FALSE(reader.Initialize(reinterpret_cast<const uint8_t*>(&test_image),
+ sizeof(test_image)));
+}
+
+} // namespace
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.cc b/chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.cc
new file mode 100644
index 00000000000..0f5baa80c6d
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.cc
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/mock_binary_feature_extractor.h"
+
+namespace safe_browsing {
+
+MockBinaryFeatureExtractor::MockBinaryFeatureExtractor() {}
+
+MockBinaryFeatureExtractor::~MockBinaryFeatureExtractor() {}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.h b/chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.h
new file mode 100644
index 00000000000..a3223f149e3
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/mock_binary_feature_extractor.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_MOCK_BINARY_FEATURE_EXTRACTOR_H_
+#define CHROME_COMMON_SAFE_BROWSING_MOCK_BINARY_FEATURE_EXTRACTOR_H_
+
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace safe_browsing {
+
+class MockBinaryFeatureExtractor : public BinaryFeatureExtractor {
+ public:
+ MockBinaryFeatureExtractor();
+ MOCK_METHOD2(CheckSignature,
+ void(const base::FilePath&,
+ ClientDownloadRequest_SignatureInfo*));
+ MOCK_METHOD4(ExtractImageFeatures,
+ bool(const base::FilePath&,
+ ExtractHeadersOption,
+ ClientDownloadRequest_ImageHeaders*,
+ google::protobuf::RepeatedPtrField<std::string>*));
+
+ protected:
+ ~MockBinaryFeatureExtractor() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBinaryFeatureExtractor);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_MOCK_BINARY_FEATURE_EXTRACTOR_H_
diff --git a/chromium/chrome/common/safe_browsing/pe_image_reader_win.cc b/chromium/chrome/common/safe_browsing/pe_image_reader_win.cc
new file mode 100644
index 00000000000..db678abc008
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/pe_image_reader_win.cc
@@ -0,0 +1,390 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/pe_image_reader_win.h"
+
+#include <wintrust.h>
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/numerics/safe_math.h"
+
+namespace safe_browsing {
+
+// A class template of traits pertaining to IMAGE_OPTIONAL_HEADER{32,64}.
+template<class HEADER_TYPE>
+struct OptionalHeaderTraits {
+};
+
+template<>
+struct OptionalHeaderTraits<IMAGE_OPTIONAL_HEADER32> {
+ static const PeImageReader::WordSize word_size = PeImageReader::WORD_SIZE_32;
+};
+
+template<>
+struct OptionalHeaderTraits<IMAGE_OPTIONAL_HEADER64> {
+ static const PeImageReader::WordSize word_size = PeImageReader::WORD_SIZE_64;
+};
+
+// A template for type-specific optional header implementations. This, in
+// conjunction with the OptionalHeader interface, effectively erases the
+// underlying structure type from the point of view of the PeImageReader.
+template<class OPTIONAL_HEADER_TYPE>
+class PeImageReader::OptionalHeaderImpl : public PeImageReader::OptionalHeader {
+ public:
+ typedef OptionalHeaderTraits<OPTIONAL_HEADER_TYPE> TraitsType;
+
+ explicit OptionalHeaderImpl(const uint8_t* optional_header_start)
+ : optional_header_(reinterpret_cast<const OPTIONAL_HEADER_TYPE*>(
+ optional_header_start)) {}
+
+ WordSize GetWordSize() override {
+ return TraitsType::word_size;
+ }
+
+ size_t GetDataDirectoryOffset() override {
+ return offsetof(OPTIONAL_HEADER_TYPE, DataDirectory);
+ }
+
+ DWORD GetDataDirectorySize() override {
+ return optional_header_->NumberOfRvaAndSizes;
+ }
+
+ const IMAGE_DATA_DIRECTORY* GetDataDirectoryEntries() override {
+ return &optional_header_->DataDirectory[0];
+ }
+
+ DWORD GetSizeOfImage() override { return optional_header_->SizeOfImage; }
+
+ private:
+ const OPTIONAL_HEADER_TYPE* optional_header_;
+ DISALLOW_COPY_AND_ASSIGN(OptionalHeaderImpl);
+};
+
+PeImageReader::PeImageReader()
+ : image_data_(),
+ image_size_(),
+ validation_state_() {}
+
+PeImageReader::~PeImageReader() {
+ Clear();
+}
+
+bool PeImageReader::Initialize(const uint8_t* image_data, size_t image_size) {
+ image_data_ = image_data;
+ image_size_ = image_size;
+
+ if (!ValidateDosHeader() ||
+ !ValidatePeSignature() ||
+ !ValidateCoffFileHeader() ||
+ !ValidateOptionalHeader() ||
+ !ValidateSectionHeaders()) {
+ Clear();
+ return false;
+ }
+
+ return true;
+}
+
+PeImageReader::WordSize PeImageReader::GetWordSize() {
+ return optional_header_->GetWordSize();
+}
+
+const IMAGE_DOS_HEADER* PeImageReader::GetDosHeader() {
+ DCHECK_NE((validation_state_ & VALID_DOS_HEADER), 0U);
+ return reinterpret_cast<const IMAGE_DOS_HEADER*>(image_data_);
+}
+
+const IMAGE_FILE_HEADER* PeImageReader::GetCoffFileHeader() {
+ DCHECK_NE((validation_state_ & VALID_COFF_FILE_HEADER), 0U);
+ return reinterpret_cast<const IMAGE_FILE_HEADER*>(
+ image_data_ + GetDosHeader()->e_lfanew + sizeof(DWORD));
+}
+
+const uint8_t* PeImageReader::GetOptionalHeaderData(
+ size_t* optional_header_size) {
+ *optional_header_size = GetOptionalHeaderSize();
+ return GetOptionalHeaderStart();
+}
+
+size_t PeImageReader::GetNumberOfSections() {
+ return GetCoffFileHeader()->NumberOfSections;
+}
+
+const IMAGE_SECTION_HEADER* PeImageReader::GetSectionHeaderAt(size_t index) {
+ DCHECK_NE((validation_state_ & VALID_SECTION_HEADERS), 0U);
+ DCHECK_LT(index, GetNumberOfSections());
+ return reinterpret_cast<const IMAGE_SECTION_HEADER*>(
+ GetOptionalHeaderStart() +
+ GetOptionalHeaderSize() +
+ (sizeof(IMAGE_SECTION_HEADER) * index));
+}
+
+const uint8_t* PeImageReader::GetExportSection(size_t* section_size) {
+ size_t data_size = 0;
+ const uint8_t* data = GetImageData(IMAGE_DIRECTORY_ENTRY_EXPORT, &data_size);
+
+ // The export section data must be big enough for the export directory.
+ if (!data || data_size < sizeof(IMAGE_EXPORT_DIRECTORY))
+ return NULL;
+
+ *section_size = data_size;
+ return data;
+}
+
+size_t PeImageReader::GetNumberOfDebugEntries() {
+ size_t data_size = 0;
+ const uint8_t* data = GetImageData(IMAGE_DIRECTORY_ENTRY_DEBUG, &data_size);
+ return data ? (data_size / sizeof(IMAGE_DEBUG_DIRECTORY)) : 0;
+}
+
+const IMAGE_DEBUG_DIRECTORY* PeImageReader::GetDebugEntry(
+ size_t index,
+ const uint8_t** raw_data,
+ size_t* raw_data_size) {
+ DCHECK_LT(index, GetNumberOfDebugEntries());
+
+ // Get the debug directory.
+ size_t debug_directory_size = 0;
+ const IMAGE_DEBUG_DIRECTORY* entries =
+ reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(
+ GetImageData(IMAGE_DIRECTORY_ENTRY_DEBUG, &debug_directory_size));
+ if (!entries)
+ return NULL;
+
+ const IMAGE_DEBUG_DIRECTORY& entry = entries[index];
+ const uint8_t* debug_data = NULL;
+ if (GetStructureAt(entry.PointerToRawData, entry.SizeOfData, &debug_data)) {
+ *raw_data = debug_data;
+ *raw_data_size = entry.SizeOfData;
+ }
+ return &entry;
+}
+
+bool PeImageReader::EnumCertificates(EnumCertificatesCallback callback,
+ void* context) {
+ size_t data_size = 0;
+ const uint8_t* data = GetImageData(IMAGE_DIRECTORY_ENTRY_SECURITY,
+ &data_size);
+ if (!data)
+ return false; // Certificate table is out of bounds.
+ const size_t kWinCertificateSize = offsetof(WIN_CERTIFICATE, bCertificate);
+ while (data_size) {
+ const WIN_CERTIFICATE* win_certificate =
+ reinterpret_cast<const WIN_CERTIFICATE*>(data);
+ if (kWinCertificateSize > data_size ||
+ kWinCertificateSize > win_certificate->dwLength ||
+ win_certificate->dwLength > data_size) {
+ return false;
+ }
+ if (!(*callback)(win_certificate->wRevision,
+ win_certificate->wCertificateType,
+ &win_certificate->bCertificate[0],
+ win_certificate->dwLength - kWinCertificateSize,
+ context)) {
+ return false;
+ }
+ size_t padded_length = (win_certificate->dwLength + 7) & ~0x7;
+ // Don't overflow when recalculating data_size, since padded_length can be
+ // attacker controlled.
+ if (!base::CheckSub(data_size, padded_length).AssignIfValid(&data_size))
+ return false;
+ data += padded_length;
+ }
+ return true;
+}
+
+DWORD PeImageReader::GetSizeOfImage() {
+ return optional_header_->GetSizeOfImage();
+}
+
+void PeImageReader::Clear() {
+ image_data_ = NULL;
+ image_size_ = 0;
+ validation_state_ = 0;
+ optional_header_.reset();
+}
+
+bool PeImageReader::ValidateDosHeader() {
+ const IMAGE_DOS_HEADER* dos_header = NULL;
+ if (!GetStructureAt(0, &dos_header) ||
+ dos_header->e_magic != IMAGE_DOS_SIGNATURE ||
+ dos_header->e_lfanew < 0) {
+ return false;
+ }
+
+ validation_state_ |= VALID_DOS_HEADER;
+ return true;
+}
+
+bool PeImageReader::ValidatePeSignature() {
+ const DWORD* signature = NULL;
+ if (!GetStructureAt(GetDosHeader()->e_lfanew, &signature) ||
+ *signature != IMAGE_NT_SIGNATURE) {
+ return false;
+ }
+
+ validation_state_ |= VALID_PE_SIGNATURE;
+ return true;
+}
+
+bool PeImageReader::ValidateCoffFileHeader() {
+ DCHECK_NE((validation_state_ & VALID_PE_SIGNATURE), 0U);
+ const IMAGE_FILE_HEADER* file_header = NULL;
+ if (!GetStructureAt(GetDosHeader()->e_lfanew +
+ offsetof(IMAGE_NT_HEADERS32, FileHeader),
+ &file_header)) {
+ return false;
+ }
+
+ validation_state_ |= VALID_COFF_FILE_HEADER;
+ return true;
+}
+
+bool PeImageReader::ValidateOptionalHeader() {
+ const IMAGE_FILE_HEADER* file_header = GetCoffFileHeader();
+ const size_t optional_header_offset =
+ GetDosHeader()->e_lfanew + offsetof(IMAGE_NT_HEADERS32, OptionalHeader);
+ const size_t optional_header_size = file_header->SizeOfOptionalHeader;
+ const WORD* optional_header_magic = NULL;
+
+ if (optional_header_size < sizeof(*optional_header_magic) ||
+ !GetStructureAt(optional_header_offset, &optional_header_magic)) {
+ return false;
+ }
+
+ std::unique_ptr<OptionalHeader> optional_header;
+ if (*optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
+ optional_header.reset(new OptionalHeaderImpl<IMAGE_OPTIONAL_HEADER32>(
+ image_data_ + optional_header_offset));
+ } else if (*optional_header_magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
+ optional_header.reset(new OptionalHeaderImpl<IMAGE_OPTIONAL_HEADER64>(
+ image_data_ + optional_header_offset));
+ } else {
+ return false;
+ }
+
+ // Does all of the claimed optional header fit in the image?
+ if (optional_header_size > image_size_ - optional_header_offset)
+ return false;
+
+ // Is the claimed optional header big enough for everything but the dir?
+ if (optional_header->GetDataDirectoryOffset() > optional_header_size)
+ return false;
+
+ // Is there enough room for all of the claimed directory entries?
+ if (optional_header->GetDataDirectorySize() >
+ ((optional_header_size - optional_header->GetDataDirectoryOffset()) /
+ sizeof(IMAGE_DATA_DIRECTORY))) {
+ return false;
+ }
+
+ optional_header_.swap(optional_header);
+ validation_state_ |= VALID_OPTIONAL_HEADER;
+ return true;
+}
+
+bool PeImageReader::ValidateSectionHeaders() {
+ const uint8_t* first_section_header =
+ GetOptionalHeaderStart() + GetOptionalHeaderSize();
+ const size_t number_of_sections = GetNumberOfSections();
+
+ // Do all section headers fit in the image?
+ if (!GetStructureAt(first_section_header - image_data_,
+ number_of_sections * sizeof(IMAGE_SECTION_HEADER),
+ &first_section_header)) {
+ return false;
+ }
+
+ validation_state_ |= VALID_SECTION_HEADERS;
+ return true;
+}
+
+const uint8_t* PeImageReader::GetOptionalHeaderStart() {
+ DCHECK_NE((validation_state_ & VALID_OPTIONAL_HEADER), 0U);
+ return (image_data_ +
+ GetDosHeader()->e_lfanew +
+ offsetof(IMAGE_NT_HEADERS32, OptionalHeader));
+}
+
+size_t PeImageReader::GetOptionalHeaderSize() {
+ return GetCoffFileHeader()->SizeOfOptionalHeader;
+}
+
+const IMAGE_DATA_DIRECTORY* PeImageReader::GetDataDirectoryEntryAt(
+ size_t index) {
+ DCHECK_NE((validation_state_ & VALID_OPTIONAL_HEADER), 0U);
+ if (index >= optional_header_->GetDataDirectorySize())
+ return NULL;
+ return &optional_header_->GetDataDirectoryEntries()[index];
+}
+
+const IMAGE_SECTION_HEADER* PeImageReader::FindSectionFromRva(
+ uint32_t relative_address) {
+ const size_t number_of_sections = GetNumberOfSections();
+ for (size_t i = 0; i < number_of_sections; ++i) {
+ const IMAGE_SECTION_HEADER* section_header = GetSectionHeaderAt(i);
+ // Is the raw data present in the image? If no, optimistically keep looking.
+ const uint8_t* section_data = NULL;
+ if (!GetStructureAt(section_header->PointerToRawData,
+ section_header->SizeOfRawData,
+ &section_data)) {
+ continue;
+ }
+ // Does the RVA lie on or after this section's start when mapped? If no,
+ // bail.
+ if (section_header->VirtualAddress > relative_address)
+ break;
+ // Does the RVA lie within the section when mapped? If no, keep looking.
+ size_t address_offset = relative_address - section_header->VirtualAddress;
+ if (address_offset > section_header->Misc.VirtualSize)
+ continue;
+ // We have a winner.
+ return section_header;
+ }
+ return NULL;
+}
+
+const uint8_t* PeImageReader::GetImageData(size_t index, size_t* data_length) {
+ // Get the requested directory entry.
+ const IMAGE_DATA_DIRECTORY* entry = GetDataDirectoryEntryAt(index);
+ if (!entry)
+ return NULL;
+
+ // The entry for the certificate table is special in that its address is a
+ // file pointer rather than an RVA.
+ if (index == IMAGE_DIRECTORY_ENTRY_SECURITY) {
+ // Does the data fit within the file.
+ if (entry->VirtualAddress > image_size_ ||
+ image_size_ - entry->VirtualAddress < entry->Size) {
+ return nullptr;
+ }
+ *data_length = entry->Size;
+ return image_data_ + entry->VirtualAddress;
+ }
+
+ // Find the section containing the data.
+ const IMAGE_SECTION_HEADER* header =
+ FindSectionFromRva(entry->VirtualAddress);
+ if (!header)
+ return NULL;
+
+ // Does the data fit within the section when mapped?
+ size_t data_offset = entry->VirtualAddress - header->VirtualAddress;
+ if (entry->Size > (header->Misc.VirtualSize - data_offset))
+ return NULL;
+
+ // Is the data entirely present on disk (if not it's zeroed out when loaded)?
+ if (data_offset >= header->SizeOfRawData ||
+ header->SizeOfRawData - data_offset < entry->Size) {
+ return NULL;
+ }
+
+ *data_length = entry->Size;
+ return image_data_ + header->PointerToRawData + data_offset;
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/pe_image_reader_win.h b/chromium/chrome/common/safe_browsing/pe_image_reader_win.h
new file mode 100644
index 00000000000..30c2d255cd8
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/pe_image_reader_win.h
@@ -0,0 +1,164 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_PE_IMAGE_READER_WIN_H_
+#define CHROME_COMMON_SAFE_BROWSING_PE_IMAGE_READER_WIN_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <windows.h>
+
+#include <memory>
+
+#include "base/macros.h"
+
+namespace safe_browsing {
+
+// Parses headers and various data from a PE image. This parser is safe for use
+// on untrusted data.
+class PeImageReader {
+ public:
+ enum WordSize {
+ WORD_SIZE_32,
+ WORD_SIZE_64,
+ };
+
+ // A callback invoked by EnumCertificates once for each attribute certificate
+ // entry in the image's attribute certificate table. |revision| and
+ // |certificate_type| identify the contents of |certificate_data| (which is of
+ // |certificate_data_size| bytes). |context| is the value provided by the
+ // caller to EnumCertificates(). Implementations must return true to continue
+ // the enumeration, or false to abort.
+ typedef bool (*EnumCertificatesCallback)(uint16_t revision,
+ uint16_t certificate_type,
+ const uint8_t* certificate_data,
+ size_t certificate_data_size,
+ void* context);
+
+ PeImageReader();
+ ~PeImageReader();
+
+ // Returns false if the given data does not appear to be a valid PE image.
+ bool Initialize(const uint8_t* image_data, size_t image_size);
+
+ // Returns the machine word size for the image.
+ WordSize GetWordSize();
+
+ const IMAGE_DOS_HEADER* GetDosHeader();
+ const IMAGE_FILE_HEADER* GetCoffFileHeader();
+
+ // Returns a pointer to the optional header and its size.
+ const uint8_t* GetOptionalHeaderData(size_t* optional_data_size);
+ size_t GetNumberOfSections();
+ const IMAGE_SECTION_HEADER* GetSectionHeaderAt(size_t index);
+
+ // Returns a pointer to the image's export data (.edata) section and its size,
+ // or NULL if the section is not present.
+ const uint8_t* GetExportSection(size_t* section_size);
+
+ size_t GetNumberOfDebugEntries();
+ const IMAGE_DEBUG_DIRECTORY* GetDebugEntry(size_t index,
+ const uint8_t** raw_data,
+ size_t* raw_data_size);
+
+ // Invokes |callback| once per attribute certificate entry. |context| is a
+ // caller-specific value that is passed to |callback|. Returns true if all
+ // certificate entries are visited (even if there are no such entries) and
+ // |callback| returns true for each. Conversely, returns |false| if |callback|
+ // returns false or if the image is malformed in any way.
+ bool EnumCertificates(EnumCertificatesCallback callback,
+ void* context);
+
+ // Returns the size of the image file.
+ DWORD GetSizeOfImage();
+
+ private:
+ // Bits indicating what portions of the image have been validated.
+ enum ValidationStages {
+ VALID_DOS_HEADER = 1 << 0,
+ VALID_PE_SIGNATURE = 1 << 1,
+ VALID_COFF_FILE_HEADER = 1 << 2,
+ VALID_OPTIONAL_HEADER = 1 << 3,
+ VALID_SECTION_HEADERS = 1 << 4,
+ };
+
+ // An interface to an image's optional header.
+ class OptionalHeader {
+ public:
+ virtual ~OptionalHeader() {}
+
+ virtual WordSize GetWordSize() = 0;
+
+ // Returns the offset of the DataDirectory member relative to the start of
+ // the optional header.
+ virtual size_t GetDataDirectoryOffset() = 0;
+
+ // Returns the number of entries in the data directory.
+ virtual DWORD GetDataDirectorySize() = 0;
+
+ // Returns a pointer to the first data directory entry.
+ virtual const IMAGE_DATA_DIRECTORY* GetDataDirectoryEntries() = 0;
+
+ // Returns the size of the image file.
+ virtual DWORD GetSizeOfImage() = 0;
+ };
+
+ template<class OPTIONAL_HEADER_TYPE>
+ class OptionalHeaderImpl;
+
+ void Clear();
+ bool ValidateDosHeader();
+ bool ValidatePeSignature();
+ bool ValidateCoffFileHeader();
+ bool ValidateOptionalHeader();
+ bool ValidateSectionHeaders();
+
+ // Return a pointer to the first byte of the image's optional header.
+ const uint8_t* GetOptionalHeaderStart();
+ size_t GetOptionalHeaderSize();
+
+ // Returns the desired directory entry, or NULL if |index| is out of bounds.
+ const IMAGE_DATA_DIRECTORY* GetDataDirectoryEntryAt(size_t index);
+
+ // Returns the header for the section that contains the given address, or NULL
+ // if the address is out of bounds or the image does not contain the section.
+ const IMAGE_SECTION_HEADER* FindSectionFromRva(uint32_t relative_address);
+
+ // Returns a pointer to the |data_length| bytes referenced by the |index|'th
+ // data directory entry.
+ const uint8_t* GetImageData(size_t index, size_t* data_length);
+
+ // Populates |structure| with a pointer to a desired structure of type T at
+ // the given offset if the image is sufficiently large to contain it. Returns
+ // false if the structure does not fully fit within the image at the given
+ // offset.
+ template<typename T> bool GetStructureAt(size_t offset, const T** structure) {
+ return GetStructureAt(offset, sizeof(**structure), structure);
+ }
+
+ // Populates |structure| with a pointer to a desired structure of type T at
+ // the given offset if the image is sufficiently large to contain
+ // |structure_size| bytes. Returns false if the structure does not fully fit
+ // within the image at the given offset.
+ template<typename T> bool GetStructureAt(size_t offset,
+ size_t structure_size,
+ const T** structure) {
+ if (offset > image_size_)
+ return false;
+ if (structure_size > image_size_ - offset)
+ return false;
+ *structure = reinterpret_cast<const T*>(image_data_ + offset);
+ return true;
+ }
+
+ const uint8_t* image_data_;
+ size_t image_size_;
+ uint32_t validation_state_;
+ std::unique_ptr<OptionalHeader> optional_header_;
+ DISALLOW_COPY_AND_ASSIGN(PeImageReader);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_PE_IMAGE_READER_WIN_H_
diff --git a/chromium/chrome/common/safe_browsing/pe_image_reader_win_unittest.cc b/chromium/chrome/common/safe_browsing/pe_image_reader_win_unittest.cc
new file mode 100644
index 00000000000..f31c417e95b
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/pe_image_reader_win_unittest.cc
@@ -0,0 +1,294 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <windows.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <wintrust.h>
+
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/safe_browsing/pe_image_reader_win.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Gt;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+struct TestData {
+ const char* filename;
+ safe_browsing::PeImageReader::WordSize word_size;
+ WORD machine_identifier;
+ WORD optional_header_size;
+ size_t number_of_sections;
+ size_t number_of_debug_entries;
+};
+
+// A test fixture parameterized on test data containing the name of a PE image
+// to parse and the expected values to be read from it. The file is read from
+// the src/chrome/test/data/safe_browsing directory.
+class PeImageReaderTest : public testing::TestWithParam<const TestData*> {
+ protected:
+ PeImageReaderTest() : expected_data_(GetParam()) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(
+ base::PathService::Get(chrome::DIR_TEST_DATA, &data_file_path_));
+ data_file_path_ = data_file_path_.AppendASCII("safe_browsing");
+ data_file_path_ = data_file_path_.AppendASCII(expected_data_->filename);
+
+ ASSERT_TRUE(data_file_.Initialize(data_file_path_));
+
+ ASSERT_TRUE(image_reader_.Initialize(data_file_.data(),
+ data_file_.length()));
+ }
+
+ const TestData* expected_data_;
+ base::FilePath data_file_path_;
+ base::MemoryMappedFile data_file_;
+ safe_browsing::PeImageReader image_reader_;
+};
+
+TEST_P(PeImageReaderTest, GetWordSize) {
+ EXPECT_EQ(expected_data_->word_size, image_reader_.GetWordSize());
+}
+
+TEST_P(PeImageReaderTest, GetDosHeader) {
+ const IMAGE_DOS_HEADER* dos_header = image_reader_.GetDosHeader();
+ ASSERT_NE(reinterpret_cast<const IMAGE_DOS_HEADER*>(NULL), dos_header);
+ EXPECT_EQ(IMAGE_DOS_SIGNATURE, dos_header->e_magic);
+}
+
+TEST_P(PeImageReaderTest, GetCoffFileHeader) {
+ const IMAGE_FILE_HEADER* file_header = image_reader_.GetCoffFileHeader();
+ ASSERT_NE(reinterpret_cast<const IMAGE_FILE_HEADER*>(NULL), file_header);
+ EXPECT_EQ(expected_data_->machine_identifier, file_header->Machine);
+ EXPECT_EQ(expected_data_->optional_header_size,
+ file_header->SizeOfOptionalHeader);
+}
+
+TEST_P(PeImageReaderTest, GetOptionalHeaderData) {
+ size_t optional_header_size = 0;
+ const uint8_t* optional_header_data =
+ image_reader_.GetOptionalHeaderData(&optional_header_size);
+ ASSERT_NE(reinterpret_cast<const uint8_t*>(NULL), optional_header_data);
+ EXPECT_EQ(expected_data_->optional_header_size, optional_header_size);
+}
+
+TEST_P(PeImageReaderTest, GetNumberOfSections) {
+ EXPECT_EQ(expected_data_->number_of_sections,
+ image_reader_.GetNumberOfSections());
+}
+
+TEST_P(PeImageReaderTest, GetSectionHeaderAt) {
+ size_t number_of_sections = image_reader_.GetNumberOfSections();
+ for (size_t i = 0; i < number_of_sections; ++i) {
+ const IMAGE_SECTION_HEADER* section_header =
+ image_reader_.GetSectionHeaderAt(i);
+ ASSERT_NE(reinterpret_cast<const IMAGE_SECTION_HEADER*>(NULL),
+ section_header);
+ }
+}
+
+TEST_P(PeImageReaderTest, InitializeFailTruncatedFile) {
+ // Compute the size of all headers through the section headers.
+ const IMAGE_SECTION_HEADER* last_section_header =
+ image_reader_.GetSectionHeaderAt(image_reader_.GetNumberOfSections() - 1);
+ const uint8_t* headers_end =
+ reinterpret_cast<const uint8_t*>(last_section_header) +
+ sizeof(*last_section_header);
+ size_t header_size = headers_end - data_file_.data();
+ safe_browsing::PeImageReader short_reader;
+
+ // Initialize should succeed when all headers are present.
+ EXPECT_TRUE(short_reader.Initialize(data_file_.data(), header_size));
+
+ // But fail if anything is missing.
+ for (size_t i = 0; i < header_size; ++i) {
+ EXPECT_FALSE(short_reader.Initialize(data_file_.data(), i));
+ }
+}
+
+TEST_P(PeImageReaderTest, GetExportSection) {
+ size_t section_size = 0;
+ const uint8_t* export_section = image_reader_.GetExportSection(&section_size);
+ ASSERT_NE(reinterpret_cast<const uint8_t*>(NULL), export_section);
+ EXPECT_NE(0U, section_size);
+}
+
+TEST_P(PeImageReaderTest, GetNumberOfDebugEntries) {
+ EXPECT_EQ(expected_data_->number_of_debug_entries,
+ image_reader_.GetNumberOfDebugEntries());
+}
+
+TEST_P(PeImageReaderTest, GetDebugEntry) {
+ size_t number_of_debug_entries = image_reader_.GetNumberOfDebugEntries();
+ for (size_t i = 0; i < number_of_debug_entries; ++i) {
+ const uint8_t* raw_data = NULL;
+ size_t raw_data_size = 0;
+ const IMAGE_DEBUG_DIRECTORY* entry =
+ image_reader_.GetDebugEntry(i, &raw_data, &raw_data_size);
+ EXPECT_NE(reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(NULL), entry);
+ EXPECT_NE(reinterpret_cast<const uint8_t*>(NULL), raw_data);
+ EXPECT_NE(0U, raw_data_size);
+ }
+}
+
+namespace {
+
+const TestData kTestData[] = {
+ {
+ "module_with_exports_x86.dll",
+ safe_browsing::PeImageReader::WORD_SIZE_32,
+ IMAGE_FILE_MACHINE_I386,
+ sizeof(IMAGE_OPTIONAL_HEADER32),
+ 4,
+ 1,
+ }, {
+ "module_with_exports_x64.dll",
+ safe_browsing::PeImageReader::WORD_SIZE_64,
+ IMAGE_FILE_MACHINE_AMD64,
+ sizeof(IMAGE_OPTIONAL_HEADER64),
+ 5,
+ 1,
+ },
+};
+
+} // namespace
+
+INSTANTIATE_TEST_SUITE_P(WordSize32,
+ PeImageReaderTest,
+ testing::Values(&kTestData[0]));
+INSTANTIATE_TEST_SUITE_P(WordSize64,
+ PeImageReaderTest,
+ testing::Values(&kTestData[1]));
+
+// An object exposing a PeImageReader::EnumCertificatesCallback that invokes a
+// virtual OnCertificate() method. This method is suitable for mocking in tests.
+class CertificateReceiver {
+ public:
+ void* AsContext() { return this; }
+ static bool OnCertificateCallback(uint16_t revision,
+ uint16_t certificate_type,
+ const uint8_t* certificate_data,
+ size_t certificate_data_size,
+ void* context) {
+ return reinterpret_cast<CertificateReceiver*>(context)->OnCertificate(
+ revision, certificate_type, certificate_data, certificate_data_size);
+ }
+
+ protected:
+ CertificateReceiver() {}
+ virtual ~CertificateReceiver() {}
+ virtual bool OnCertificate(uint16_t revision,
+ uint16_t certificate_type,
+ const uint8_t* certificate_data,
+ size_t certificate_data_size) = 0;
+};
+
+class MockCertificateReceiver : public CertificateReceiver {
+ public:
+ MockCertificateReceiver() {}
+ MOCK_METHOD4(OnCertificate, bool(uint16_t, uint16_t, const uint8_t*, size_t));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCertificateReceiver);
+};
+
+struct CertificateTestData {
+ const char* filename;
+ int num_signers;
+};
+
+// A test fixture parameterized on test data containing the name of a PE image
+// to parse and the expected values to be read from it. The file is read from
+// the src/chrome/test/data/safe_browsing/download_protection directory.
+class PeImageReaderCertificateTest
+ : public testing::TestWithParam<const CertificateTestData*> {
+ protected:
+ PeImageReaderCertificateTest() : expected_data_(GetParam()) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(
+ base::PathService::Get(chrome::DIR_TEST_DATA, &data_file_path_));
+ data_file_path_ = data_file_path_.AppendASCII("safe_browsing");
+ data_file_path_ = data_file_path_.AppendASCII("download_protection");
+ data_file_path_ = data_file_path_.AppendASCII(expected_data_->filename);
+ ASSERT_TRUE(data_file_.Initialize(data_file_path_));
+ ASSERT_TRUE(image_reader_.Initialize(data_file_.data(),
+ data_file_.length()));
+ }
+
+ const CertificateTestData* expected_data_;
+ base::FilePath data_file_path_;
+ base::MemoryMappedFile data_file_;
+ safe_browsing::PeImageReader image_reader_;
+};
+
+TEST_P(PeImageReaderCertificateTest, EnumCertificates) {
+ StrictMock<MockCertificateReceiver> receiver;
+ if (expected_data_->num_signers) {
+ EXPECT_CALL(receiver, OnCertificate(WIN_CERT_REVISION_2_0,
+ WIN_CERT_TYPE_PKCS_SIGNED_DATA,
+ NotNull(),
+ Gt(0U)))
+ .Times(expected_data_->num_signers)
+ .WillRepeatedly(Return(true));
+ }
+ EXPECT_TRUE(image_reader_.EnumCertificates(
+ &CertificateReceiver::OnCertificateCallback, receiver.AsContext()));
+}
+
+TEST_P(PeImageReaderCertificateTest, AbortEnum) {
+ StrictMock<MockCertificateReceiver> receiver;
+ if (expected_data_->num_signers) {
+ // Return false for the first cert, thereby stopping the enumeration.
+ EXPECT_CALL(receiver, OnCertificate(_, _, _, _)).WillOnce(Return(false));
+ EXPECT_FALSE(image_reader_.EnumCertificates(
+ &CertificateReceiver::OnCertificateCallback, receiver.AsContext()));
+ } else {
+ // An unsigned file always reports true with no invocations of the callback.
+ EXPECT_TRUE(image_reader_.EnumCertificates(
+ &CertificateReceiver::OnCertificateCallback, receiver.AsContext()));
+ }
+}
+
+namespace {
+
+const CertificateTestData kCertificateTestData[] = {
+ {
+ "signed.exe",
+ 1,
+ }, {
+ "unsigned.exe",
+ 0,
+ }, {
+ "disable_outdated_build_detector.exe",
+ 1,
+ }, {
+ "signed_twice.exe",
+ 2,
+ },
+};
+
+} // namespace
+
+INSTANTIATE_TEST_SUITE_P(SignedExe,
+ PeImageReaderCertificateTest,
+ testing::Values(&kCertificateTestData[0]));
+INSTANTIATE_TEST_SUITE_P(UnsignedExe,
+ PeImageReaderCertificateTest,
+ testing::Values(&kCertificateTestData[1]));
+INSTANTIATE_TEST_SUITE_P(DisableOutdatedBuildDetectorExe,
+ PeImageReaderCertificateTest,
+ testing::Values(&kCertificateTestData[2]));
+INSTANTIATE_TEST_SUITE_P(SignedTwiceExe,
+ PeImageReaderCertificateTest,
+ testing::Values(&kCertificateTestData[3]));
diff --git a/chromium/chrome/common/safe_browsing/protobuf_message_log_macros.h b/chromium/chrome/common/safe_browsing/protobuf_message_log_macros.h
new file mode 100644
index 00000000000..330ed1fae5d
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/protobuf_message_log_macros.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_LOG_MACROS_H_
+#define CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_LOG_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h"
+
+// Set up so next include will generate log methods.
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_END
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(message_name) \
+ void ParamTraits<message_name>::Log(const param_type& p, std::string* l) { \
+ bool needs_comma = false; \
+ l->append("(");
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(name) \
+ if (needs_comma) \
+ l->append(", "); \
+ LogParam(p.name(), l); \
+ needs_comma = true;
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(name) \
+ if (needs_comma) \
+ l->append(", "); \
+ LogParam(p.name(), l); \
+ needs_comma = true;
+#define IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER(name) \
+ if (needs_comma) \
+ l->append(", "); \
+ LogParam(p.name(), l); \
+ needs_comma = true;
+#define IPC_PROTOBUF_MESSAGE_TRAITS_END() }
+
+#endif // CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_LOG_MACROS_H_
diff --git a/chromium/chrome/common/safe_browsing/protobuf_message_read_macros.h b/chromium/chrome/common/safe_browsing/protobuf_message_read_macros.h
new file mode 100644
index 00000000000..9c7720dbef4
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/protobuf_message_read_macros.h
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_READ_MACROS_H_
+#define CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_READ_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h"
+
+// Set up so next include will generate read methods.
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_END
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(message_name) \
+ template <class P> \
+ bool ParamTraits<message_name>::ReadParamF( \
+ const base::Pickle* m, base::PickleIterator* iter, param_type* p, \
+ void (param_type::*setter_function)(P)) { \
+ P value; \
+ if (!ReadParam(m, iter, &value)) \
+ return false; \
+ (p->*setter_function)(value); \
+ return true; \
+ } \
+ bool ParamTraits<message_name>::Read( \
+ const base::Pickle* m, base::PickleIterator* iter, param_type* p) {
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(name) \
+ { \
+ bool is_present; \
+ if (!iter->ReadBool(&is_present)) \
+ return false; \
+ if (!is_present) \
+ p->clear_##name(); \
+ else if (!ReadParamF(m, iter, p, &param_type::set_##name)) \
+ return false; \
+ }
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(name) \
+ { \
+ bool is_present; \
+ if (!iter->ReadBool(&is_present)) \
+ return false; \
+ if (!is_present) \
+ p->clear_##name(); \
+ else if (!ReadParam(m, iter, p->mutable_##name())) \
+ return false; \
+ }
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER(name) \
+ if (!ReadParam(m, iter, p->mutable_##name())) \
+ return false;
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_END() \
+ return true; \
+ }
+
+#endif // CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_READ_MACROS_H_
diff --git a/chromium/chrome/common/safe_browsing/protobuf_message_write_macros.h b/chromium/chrome/common/safe_browsing/protobuf_message_write_macros.h
new file mode 100644
index 00000000000..578d23b2b18
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/protobuf_message_write_macros.h
@@ -0,0 +1,33 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_WRITE_MACROS_H_
+#define CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_WRITE_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "chrome/common/safe_browsing/ipc_protobuf_message_null_macros.h"
+
+// Set up so next include will generate write methods.
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER
+#undef IPC_PROTOBUF_MESSAGE_TRAITS_END
+
+#define IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(message_name) \
+ void ParamTraits<message_name>::Write(base::Pickle* m, const param_type& p) {
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER \
+ IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER
+#define IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_FUNDAMENTAL_MEMBER(name) \
+ if (p.has_##name()) { \
+ m->WriteBool(true); \
+ WriteParam(m, p.name()); \
+ } else { \
+ m->WriteBool(false); \
+ }
+#define IPC_PROTOBUF_MESSAGE_TRAITS_REPEATED_COMPLEX_MEMBER(name) \
+ WriteParam(m, p.name());
+#define IPC_PROTOBUF_MESSAGE_TRAITS_END() }
+
+#endif // CHROME_COMMON_SAFE_BROWSING_PROTOBUF_MESSAGE_WRITE_MACROS_H_
diff --git a/chromium/chrome/common/safe_browsing/rar_analyzer.cc b/chromium/chrome/common/safe_browsing/rar_analyzer.cc
new file mode 100644
index 00000000000..62af2be4fb8
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/rar_analyzer.cc
@@ -0,0 +1,74 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/rar_analyzer.h"
+
+#include <memory>
+#include <string>
+
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/i18n/streaming_utf8_validator.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/common/safe_browsing/archive_analyzer_results.h"
+#include "chrome/common/safe_browsing/download_type_util.h"
+#include "chrome/common/safe_browsing/file_type_policies.h"
+#include "components/safe_browsing/features.h"
+#include "third_party/unrar/src/unrar_wrapper.h"
+
+namespace safe_browsing {
+namespace rar_analyzer {
+
+namespace {
+
+// The maximum duration of RAR analysis, in milliseconds.
+const int kRarAnalysisTimeoutMs = 10000;
+
+} // namespace
+
+void AnalyzeRarFile(base::File rar_file,
+ base::File temp_file,
+ ArchiveAnalyzerResults* results) {
+ base::Time start_time = base::Time::Now();
+ results->success = false;
+ results->file_count = 0;
+ results->directory_count = 0;
+
+ // If the file is too big to unpack, return failure. This will still send a
+ // ping as an "invalid" RAR.
+ bool too_big_to_unpack =
+ base::checked_cast<uint64_t>(rar_file.GetLength()) >
+ FileTypePolicies::GetInstance()->GetMaxFileSizeToAnalyze("rar");
+ if (too_big_to_unpack)
+ return;
+
+ third_party_unrar::RarReader reader;
+ if (!reader.Open(std::move(rar_file), temp_file.Duplicate()))
+ return;
+
+ bool timeout = false;
+ while (reader.ExtractNextEntry()) {
+ if (base::Time::Now() - start_time >
+ base::TimeDelta::FromMilliseconds(kRarAnalysisTimeoutMs)) {
+ timeout = true;
+ break;
+ }
+ const third_party_unrar::RarReader::EntryInfo& entry =
+ reader.current_entry();
+ UpdateArchiveAnalyzerResultsWithFile(entry.file_path, &temp_file,
+ entry.file_size, entry.is_encrypted,
+ results);
+ if (entry.is_directory)
+ results->directory_count++;
+ else
+ results->file_count++;
+ }
+
+ results->success = !timeout;
+}
+
+} // namespace rar_analyzer
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/rar_analyzer.h b/chromium/chrome/common/safe_browsing/rar_analyzer.h
new file mode 100644
index 00000000000..0952ccad919
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/rar_analyzer.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains the rar file analysis implementation for download
+// protection, which runs in a sandbox. The reason for running in a sandbox is
+// to isolate the browser and other renderer processes from any vulnerabilities
+// that the attacker-controlled download file may try to exploit.
+//
+// Here's the call flow for inspecting .rar files upon download:
+// 1. File is downloaded.
+// 2. |CheckClientDownloadRequest::AnalyzeFile()| is called to analyze the Safe
+// Browsing reputation of the downloaded file.
+// 3. It calls |CheckClientDownloadRequest::StartExtractRarFeatures()|, which
+// creates an instance of |SandboxedRarAnalyzer|, and calls |Start()|.
+// 4. |SandboxedRarAnalyzer::Start()| leads to a mojo call to
+// |SafeArchiveAnalyzer::AnalyzeRarFile()| in a sandbox.
+// 5. Finally, |SafeArchiveAnalyzer::AnalyzeRarFile()| calls |AnalyzeRarFile()|
+// defined in this file to actually inspect the file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_RAR_ANALYZER_H_
+#define CHROME_COMMON_SAFE_BROWSING_RAR_ANALYZER_H_
+
+#include "base/files/file.h"
+
+namespace safe_browsing {
+
+struct ArchiveAnalyzerResults;
+
+namespace rar_analyzer {
+
+// |rar_file| is a platform-agnostic handle to the file, and |temp_file| is a
+// handle for a temporary file the sandbox can write to. Since |AnalyzeRarFile|
+// runs inside a sandbox, it isn't allowed to open file handles. So both files
+// are opened in |SandboxedRarAnalyzer|, which runs in the browser process, and
+// the handles are passed here. The function populates the various fields in
+// |results| based on the results of parsing the rar file. If the parsing fails
+// for any reason, including crashing the sandbox process, the browser process
+// considers the file safe.
+void AnalyzeRarFile(base::File rar_file,
+ base::File temp_file,
+ ArchiveAnalyzerResults* results);
+
+} // namespace rar_analyzer
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_RAR_ANALYZER_H_
diff --git a/chromium/chrome/common/safe_browsing/zip_analyzer.cc b/chromium/chrome/common/safe_browsing/zip_analyzer.cc
new file mode 100644
index 00000000000..ac14ff66f79
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/zip_analyzer.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/safe_browsing/zip_analyzer.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/numerics/checked_math.h"
+#include "base/numerics/ranges.h"
+#include "base/rand_util.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/common/safe_browsing/archive_analyzer_results.h"
+#include "chrome/common/safe_browsing/file_type_policies.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "third_party/zlib/google/zip_reader.h"
+
+namespace safe_browsing {
+namespace zip_analyzer {
+
+namespace {
+
+// The maximum duration of ZIP analysis, in milliseconds.
+const int kZipAnalysisTimeoutMs = 10000;
+
+} // namespace
+
+void AnalyzeZipFile(base::File zip_file,
+ base::File temp_file,
+ ArchiveAnalyzerResults* results) {
+ base::Time start_time = base::Time::Now();
+ zip::ZipReader reader;
+ if (!reader.OpenFromPlatformFile(zip_file.GetPlatformFile())) {
+ DVLOG(1) << "Failed to open zip file";
+ return;
+ }
+
+ bool too_big_to_unpack =
+ base::checked_cast<uint64_t>(zip_file.GetLength()) >
+ FileTypePolicies::GetInstance()->GetMaxFileSizeToAnalyze("zip");
+ if (too_big_to_unpack) {
+ results->success = false;
+ return;
+ }
+
+ bool timeout = false;
+ bool advanced = true;
+ results->file_count = 0;
+ results->directory_count = 0;
+ base::CheckedNumeric<uint64_t> total_uncompressed_size = 0u;
+ for (; reader.HasMore(); advanced = reader.AdvanceToNextEntry()) {
+ if (!advanced) {
+ DVLOG(1) << "Could not advance to next entry, aborting zip scan.";
+ return;
+ }
+ if (!reader.OpenCurrentEntryInZip()) {
+ DVLOG(1) << "Failed to open current entry in zip file";
+ continue;
+ }
+ if (base::Time::Now() - start_time >
+ base::TimeDelta::FromMilliseconds(kZipAnalysisTimeoutMs)) {
+ timeout = true;
+ break;
+ }
+
+ // Clear the |temp_file| between extractions.
+ temp_file.Seek(base::File::Whence::FROM_BEGIN, 0);
+ temp_file.SetLength(0);
+ zip::FileWriterDelegate writer(&temp_file);
+ reader.ExtractCurrentEntry(&writer, std::numeric_limits<uint64_t>::max());
+ UpdateArchiveAnalyzerResultsWithFile(
+ reader.current_entry_info()->file_path(), &temp_file,
+ writer.file_length(), reader.current_entry_info()->is_encrypted(),
+ results);
+
+ UMA_HISTOGRAM_MEMORY_LARGE_MB("SBClientDownload.ZipEntrySize",
+ writer.file_length());
+ total_uncompressed_size += writer.file_length();
+
+ if (reader.current_entry_info()->is_directory())
+ results->directory_count++;
+ else
+ results->file_count++;
+ }
+
+ // We represent the size as a percent, so multiply by 100, then check for
+ // overflow.
+ total_uncompressed_size *= 100;
+ UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipArchiveUncompressedSizeOverflow",
+ !total_uncompressed_size.IsValid());
+ if (total_uncompressed_size.IsValid() && zip_file.GetLength() > 0) {
+ UMA_HISTOGRAM_COUNTS_10000(
+ "SBClientDownload.ZipCompressionRatio",
+ static_cast<uint64_t>(total_uncompressed_size.ValueOrDie()) /
+ zip_file.GetLength());
+ }
+
+ results->success = !timeout;
+}
+
+} // namespace zip_analyzer
+} // namespace safe_browsing
diff --git a/chromium/chrome/common/safe_browsing/zip_analyzer.h b/chromium/chrome/common/safe_browsing/zip_analyzer.h
new file mode 100644
index 00000000000..176ab651b09
--- /dev/null
+++ b/chromium/chrome/common/safe_browsing/zip_analyzer.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains the zip file analysis implementation for download
+// protection, which runs in a sandboxed utility process.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_ZIP_ANALYZER_H_
+#define CHROME_COMMON_SAFE_BROWSING_ZIP_ANALYZER_H_
+
+#include "base/files/file.h"
+
+namespace safe_browsing {
+
+struct ArchiveAnalyzerResults;
+
+namespace zip_analyzer {
+
+void AnalyzeZipFile(base::File zip_file,
+ base::File temp_file,
+ ArchiveAnalyzerResults* results);
+
+} // namespace zip_analyzer
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_ZIP_ANALYZER_H_
diff --git a/chromium/chrome/common/search/OWNERS b/chromium/chrome/common/search/OWNERS
new file mode 100644
index 00000000000..8d760b139d7
--- /dev/null
+++ b/chromium/chrome/common/search/OWNERS
@@ -0,0 +1,2 @@
+file://chrome/browser/search/OWNERS
+# COMPONENT: UI>Browser>NewTabPage
diff --git a/chromium/chrome/common/search/chrome_colors_icon_template.h b/chromium/chrome/common/search/chrome_colors_icon_template.h
new file mode 100644
index 00000000000..fd99be1b377
--- /dev/null
+++ b/chromium/chrome/common/search/chrome_colors_icon_template.h
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SEARCH_CHROME_COLORS_ICON_TEMPLATE_H_
+#define CHROME_COMMON_SEARCH_CHROME_COLORS_ICON_TEMPLATE_H_
+
+// Template for the icon svg.
+// $1 - primary color
+// $2 - secondary color
+const char kChromeColorsIconTemplate[] =
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" "
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"64\" "
+ "height=\"64\"><defs><path d=\"M32 64C14.34 64 0 49.66 0 32S14.34 0 32 "
+ "0s32 14.34 32 32-14.34 32-32 32z\" id=\"a\"/><linearGradient id=\"b\" "
+ "gradientUnits=\"userSpaceOnUse\" x1=\"32\" y1=\"32\" x2=\"32.08\" "
+ "y2=\"32\"><stop offset=\"0\%\" stop-color=\"$2\"/><stop offset=\"100\%\" "
+ "stop-color=\"$1\"/></linearGradient><clipPath id=\"c\"><use "
+ "xlink:href=\"#a\"/></clipPath></defs><use xlink:href=\"#a\" "
+ "fill=\"url(#b)\"/><g clip-path=\"url(#c)\"><use xlink:href=\"#a\" "
+ "fill-opacity=\"0\" stroke=\"$1\" stroke-width=\"2\"/></g></svg>";
+
+#endif // CHROME_COMMON_SEARCH_CHROME_COLORS_ICON_TEMPLATE_H_
diff --git a/chromium/chrome/common/search/generate_colors_info.cc b/chromium/chrome/common/search/generate_colors_info.cc
new file mode 100644
index 00000000000..bba9e5bec5d
--- /dev/null
+++ b/chromium/chrome/common/search/generate_colors_info.cc
@@ -0,0 +1,120 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/search/chrome_colors_icon_template.h"
+#include "chrome/common/search/selected_colors_info.h"
+#include "chrome/common/themes/autogenerated_theme_util.h"
+
+// Template for color info line.
+// $1 - color id
+// $2 - red value of primary color
+// $3 - green value of primary color
+// $4 - blue value of primary color
+// $5 - color label id
+// $6 - icon data
+const char kColorInfoLineTemplate[] =
+ " ColorInfo($1, SkColorSetRGB($2, $3, $4), $5, "
+ "\"data:image/svg+xml;base64,$6\")";
+
+// Template for the generated file content.
+// $1 - lines for updated color info.
+// $2 - number of colors.
+const char kFileContentTemplate[] =
+ "// Generated from generate_colors_info.cc. Do not edit!\n"
+ "\n"
+ "#ifndef CHROME_COMMON_SEARCH_GENERATED_COLORS_INFO_H_\n"
+ "#define CHROME_COMMON_SEARCH_GENERATED_COLORS_INFO_H_\n"
+ "\n"
+ "#include <stdint.h>\n"
+ "\n"
+ "#include \"chrome/common/search/selected_colors_info.h\"\n"
+ "#include \"third_party/skia/include/core/SkColor.h\"\n"
+ "\n"
+ "namespace chrome_colors {\n"
+ "\n"
+ "// List of preselected colors with icon data to show in Chrome Colors"
+ " menu.\n"
+ "constexpr ColorInfo kGeneratedColorsInfo[] = {\n"
+ "$1\n"
+ "};\n"
+ "\n"
+ "const size_t kNumColorsInfo = $2;"
+ "\n"
+ "} // namespace chrome_colors\n"
+ "\n"
+ "#endif // CHROME_COMMON_SEARCH_GENERATED_COLORS_INFO_H_\n";
+
+// Returns hex string representation for the |color| in "#FFFFFF" format.
+std::string SkColorToHexString(SkColor color) {
+ return base::StringPrintf("#%02X%02X%02X", SkColorGetR(color),
+ SkColorGetG(color), SkColorGetB(color));
+}
+
+// Returns icon data for the given |color| as encoded svg.
+// The returned string can be later directly set in JS with the following
+// format: "data:image/svg+xml;base64<ENCODED_SVG>"
+std::string GenerateIconDataForColor(SkColor color) {
+ AutogeneratedThemeColors colors = GetAutogeneratedThemeColors(color);
+
+ std::vector<std::string> subst;
+ subst.push_back(SkColorToHexString(colors.frame_color));
+ subst.push_back(SkColorToHexString(colors.active_tab_color));
+
+ std::string svg_base64;
+ base::Base64Encode(
+ base::ReplaceStringPlaceholders(kChromeColorsIconTemplate, subst, NULL),
+ &svg_base64);
+ return svg_base64;
+}
+
+// Generates color info line in the following format:
+// ColorInfo(ID, SkColorSetRGB(R, G, B), LABEL, ICON_DATA)
+std::string GenerateColorLine(chrome_colors::ColorInfo color_info) {
+ std::vector<std::string> subst;
+ subst.push_back(base::NumberToString(color_info.id));
+ subst.push_back(base::NumberToString(SkColorGetR(color_info.color)));
+ subst.push_back(base::NumberToString(SkColorGetG(color_info.color)));
+ subst.push_back(base::NumberToString(SkColorGetB(color_info.color)));
+ subst.push_back(base::NumberToString(color_info.label_id));
+ subst.push_back(GenerateIconDataForColor(color_info.color));
+ return base::ReplaceStringPlaceholders(kColorInfoLineTemplate, subst, NULL);
+}
+
+// Generates 'generated_colors_info.h' that contains selected colors from
+// |chrome_colors::kSelectedColorsInfo| along with generated icon data.
+void GenerateColorsInfoFile(std::string output_dir) {
+ std::vector<std::string> updated_color_info;
+ int colors_num = 0;
+ for (chrome_colors::ColorInfo color_info :
+ chrome_colors::kSelectedColorsInfo) {
+ updated_color_info.push_back(GenerateColorLine(color_info));
+ colors_num++;
+ }
+
+ std::vector<std::string> subst;
+ subst.push_back(base::JoinString(updated_color_info, ",\n"));
+ subst.push_back(base::NumberToString(colors_num));
+ std::string output =
+ base::ReplaceStringPlaceholders(kFileContentTemplate, subst, NULL);
+
+ base::FilePath output_path = base::FilePath::FromUTF8Unsafe(output_dir);
+ base::FilePath directory = output_path.DirName();
+ if (!base::DirectoryExists(directory))
+ base::CreateDirectory(directory);
+
+ if (base::WriteFile(output_path, output.c_str(),
+ static_cast<uint32_t>(output.size())) <= 0) {
+ LOG(ERROR) << "Failed to write output to " << output_path;
+ }
+}
+
+int main(int argc, char* argv[]) {
+ GenerateColorsInfoFile(argv[1]);
+ return 0;
+}
diff --git a/chromium/chrome/common/search/instant_types.cc b/chromium/chrome/common/search/instant_types.cc
new file mode 100644
index 00000000000..f94c1be9813
--- /dev/null
+++ b/chromium/chrome/common/search/instant_types.cc
@@ -0,0 +1,54 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/search/instant_types.h"
+
+ThemeBackgroundInfo::ThemeBackgroundInfo() = default;
+
+ThemeBackgroundInfo::ThemeBackgroundInfo(const ThemeBackgroundInfo& other) =
+ default;
+
+ThemeBackgroundInfo::~ThemeBackgroundInfo() = default;
+
+bool ThemeBackgroundInfo::operator==(const ThemeBackgroundInfo& rhs) const {
+ return using_default_theme == rhs.using_default_theme &&
+ using_dark_colors == rhs.using_dark_colors &&
+ custom_background_url == rhs.custom_background_url &&
+ custom_background_attribution_line_1 ==
+ rhs.custom_background_attribution_line_1 &&
+ custom_background_attribution_line_2 ==
+ rhs.custom_background_attribution_line_2 &&
+ custom_background_attribution_action_url ==
+ rhs.custom_background_attribution_action_url &&
+ collection_id == rhs.collection_id &&
+ background_color == rhs.background_color &&
+ text_color == rhs.text_color &&
+ text_color_light == rhs.text_color_light && theme_id == rhs.theme_id &&
+ image_horizontal_alignment == rhs.image_horizontal_alignment &&
+ image_vertical_alignment == rhs.image_vertical_alignment &&
+ image_tiling == rhs.image_tiling &&
+ has_attribution == rhs.has_attribution &&
+ logo_alternate == rhs.logo_alternate &&
+ has_theme_image == rhs.has_theme_image &&
+ theme_name == rhs.theme_name && color_id == rhs.color_id &&
+ color_dark == rhs.color_dark && color_light == rhs.color_light &&
+ color_picked == rhs.color_picked && logo_color == rhs.logo_color &&
+ shortcut_color == rhs.shortcut_color;
+}
+
+InstantMostVisitedItem::InstantMostVisitedItem()
+ : title_source(ntp_tiles::TileTitleSource::UNKNOWN),
+ source(ntp_tiles::TileSource::TOP_SITES) {}
+
+InstantMostVisitedItem::InstantMostVisitedItem(
+ const InstantMostVisitedItem& other) = default;
+
+InstantMostVisitedItem::~InstantMostVisitedItem() {}
+
+InstantMostVisitedInfo::InstantMostVisitedInfo() = default;
+
+InstantMostVisitedInfo::InstantMostVisitedInfo(
+ const InstantMostVisitedInfo& other) = default;
+
+InstantMostVisitedInfo::~InstantMostVisitedInfo() {}
diff --git a/chromium/chrome/common/search/instant_types.h b/chromium/chrome/common/search/instant_types.h
new file mode 100644
index 00000000000..99916b21c4d
--- /dev/null
+++ b/chromium/chrome/common/search/instant_types.h
@@ -0,0 +1,188 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SEARCH_INSTANT_TYPES_H_
+#define CHROME_COMMON_SEARCH_INSTANT_TYPES_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "components/ntp_tiles/tile_source.h"
+#include "components/ntp_tiles/tile_title_source.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/color_palette.h"
+#include "url/gurl.h"
+
+// ID used by Instant code to refer to objects (e.g. Autocomplete results, Most
+// Visited items) that the Instant page needs access to.
+typedef int InstantRestrictedID;
+
+// The alignment of the theme background image.
+enum ThemeBackgroundImageAlignment {
+ THEME_BKGRND_IMAGE_ALIGN_CENTER,
+ THEME_BKGRND_IMAGE_ALIGN_LEFT,
+ THEME_BKGRND_IMAGE_ALIGN_TOP,
+ THEME_BKGRND_IMAGE_ALIGN_RIGHT,
+ THEME_BKGRND_IMAGE_ALIGN_BOTTOM,
+
+ THEME_BKGRND_IMAGE_ALIGN_LAST = THEME_BKGRND_IMAGE_ALIGN_BOTTOM,
+};
+
+// The tiling of the theme background image.
+enum ThemeBackgroundImageTiling {
+ THEME_BKGRND_IMAGE_NO_REPEAT,
+ THEME_BKGRND_IMAGE_REPEAT_X,
+ THEME_BKGRND_IMAGE_REPEAT_Y,
+ THEME_BKGRND_IMAGE_REPEAT,
+
+ THEME_BKGRND_IMAGE_LAST = THEME_BKGRND_IMAGE_REPEAT,
+};
+
+// Theme background settings for the NTP.
+struct ThemeBackgroundInfo {
+ ThemeBackgroundInfo();
+ ThemeBackgroundInfo(const ThemeBackgroundInfo& other);
+ ~ThemeBackgroundInfo();
+
+ bool operator==(const ThemeBackgroundInfo& rhs) const;
+
+ // True if the default theme is selected.
+ bool using_default_theme = true;
+
+ // True if the system theme uses a light-on-dark color scheme instead of
+ // dark-on-light.
+ bool using_dark_colors = false;
+
+ // Url of the custom background selected by the user.
+ GURL custom_background_url;
+
+ // First attribution string for custom background.
+ std::string custom_background_attribution_line_1;
+
+ // Second attribution string for custom background.
+ std::string custom_background_attribution_line_2;
+
+ // Url to learn more info about the custom background.
+ GURL custom_background_attribution_action_url;
+
+ // Id of the collection being used for "daily refresh".
+ std::string collection_id;
+
+ // The theme background color. Always valid.
+ SkColor background_color = gfx::kPlaceholderColor;
+
+ // The theme text color.
+ SkColor text_color = gfx::kPlaceholderColor;
+
+ // The theme text color light.
+ SkColor text_color_light = gfx::kPlaceholderColor;
+
+ // The theme id for the theme background image.
+ // Value is only valid if there's a custom theme background image.
+ std::string theme_id;
+
+ // The theme background image horizontal alignment is only valid if |theme_id|
+ // is valid.
+ ThemeBackgroundImageAlignment image_horizontal_alignment =
+ THEME_BKGRND_IMAGE_ALIGN_CENTER;
+
+ // The theme background image vertical alignment is only valid if |theme_id|
+ // is valid.
+ ThemeBackgroundImageAlignment image_vertical_alignment =
+ THEME_BKGRND_IMAGE_ALIGN_CENTER;
+
+ // The theme background image tiling is only valid if |theme_id| is valid.
+ ThemeBackgroundImageTiling image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT;
+
+ // True if theme has attribution logo.
+ // Value is only valid if |theme_id| is valid.
+ bool has_attribution = false;
+
+ // True if theme has an alternate logo.
+ bool logo_alternate = false;
+
+ // True if theme has NTP image.
+ bool has_theme_image = false;
+
+ // The theme name.
+ std::string theme_name;
+
+ // The color id for Chrome Colors. It is -1 if Chrome Colors is not set, 0
+ // when Chrome Colors is set but not from predefined color list, and > 0 if
+ // Chrome Colors is set from predefined color list.
+ int color_id = -1;
+
+ // The dark color for Chrome Colors. Valid only if Chrome Colors is set.
+ SkColor color_dark = gfx::kPlaceholderColor;
+
+ // The light color for Chrome Colors. Valid only if Chrome Colors is set.
+ SkColor color_light = gfx::kPlaceholderColor;
+
+ // The picked custom color for Chrome Colors. Valid only if Chrome Colors is
+ // set.
+ SkColor color_picked = gfx::kPlaceholderColor;
+
+ // Color used for alternative Google logo on NTP.
+ SkColor logo_color = gfx::kPlaceholderColor;
+
+ // Color for NTP shortcut backgrounds.
+ SkColor shortcut_color = gfx::kPlaceholderColor;
+};
+
+struct InstantMostVisitedItem {
+ InstantMostVisitedItem();
+ InstantMostVisitedItem(const InstantMostVisitedItem& other);
+ ~InstantMostVisitedItem();
+
+ // The URL of the Most Visited item.
+ GURL url;
+
+ // The title of the Most Visited page. May be empty, in which case the |url|
+ // is used as the title.
+ base::string16 title;
+
+ // The external URL of the favicon associated with this page.
+ GURL favicon;
+
+ // The source of the item's |title|.
+ ntp_tiles::TileTitleSource title_source;
+
+ // The source of the item, e.g. server-side or client-side.
+ ntp_tiles::TileSource source;
+
+ // The timestamp representing when the tile data (e.g. URL) was generated
+ // originally, regardless of the impression timestamp.
+ base::Time data_generation_time;
+};
+
+struct InstantMostVisitedInfo {
+ InstantMostVisitedInfo();
+ InstantMostVisitedInfo(const InstantMostVisitedInfo& other);
+ ~InstantMostVisitedInfo();
+
+ std::vector<InstantMostVisitedItem> items;
+
+ // True if the source of the |items| is custom links (i.e.
+ // ntp_tiles::TileSource::CUSTOM_LINKS). Required since the source cannot be
+ // checked if |items| is empty.
+ bool items_are_custom_links = false;
+
+ // True if Most Visited functionality is enabled instead of customizable
+ // shortcuts.
+ bool use_most_visited = false;
+
+ // True if the items are visible and not hidden by the user.
+ bool is_visible = true;
+};
+
+// An InstantMostVisitedItem along with its assigned restricted ID.
+typedef std::pair<InstantRestrictedID, InstantMostVisitedItem>
+ InstantMostVisitedItemIDPair;
+
+#endif // CHROME_COMMON_SEARCH_INSTANT_TYPES_H_
diff --git a/chromium/chrome/common/search/mock_embedded_search_client.cc b/chromium/chrome/common/search/mock_embedded_search_client.cc
new file mode 100644
index 00000000000..7e11eb3356c
--- /dev/null
+++ b/chromium/chrome/common/search/mock_embedded_search_client.cc
@@ -0,0 +1,8 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/search/mock_embedded_search_client.h"
+
+MockEmbeddedSearchClient::MockEmbeddedSearchClient() = default;
+MockEmbeddedSearchClient::~MockEmbeddedSearchClient() = default;
diff --git a/chromium/chrome/common/search/mock_embedded_search_client.h b/chromium/chrome/common/search/mock_embedded_search_client.h
new file mode 100644
index 00000000000..f5008ea9677
--- /dev/null
+++ b/chromium/chrome/common/search/mock_embedded_search_client.h
@@ -0,0 +1,24 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SEARCH_MOCK_EMBEDDED_SEARCH_CLIENT_H_
+#define CHROME_COMMON_SEARCH_MOCK_EMBEDDED_SEARCH_CLIENT_H_
+
+#include "chrome/common/search.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class MockEmbeddedSearchClient : public chrome::mojom::EmbeddedSearchClient {
+ public:
+ MockEmbeddedSearchClient();
+ ~MockEmbeddedSearchClient() override;
+
+ MOCK_METHOD1(SetPageSequenceNumber, void(int));
+ MOCK_METHOD2(FocusChanged, void(OmniboxFocusState, OmniboxFocusChangeReason));
+ MOCK_METHOD1(MostVisitedInfoChanged, void(const InstantMostVisitedInfo&));
+ MOCK_METHOD1(SetInputInProgress, void(bool));
+ MOCK_METHOD1(ThemeChanged, void(const ThemeBackgroundInfo&));
+ MOCK_METHOD0(LocalBackgroundSelected, void());
+};
+
+#endif // CHROME_COMMON_SEARCH_MOCK_EMBEDDED_SEARCH_CLIENT_H_
diff --git a/chromium/chrome/common/search/ntp_logging_events.h b/chromium/chrome/common/search/ntp_logging_events.h
new file mode 100644
index 00000000000..c15685d496e
--- /dev/null
+++ b/chromium/chrome/common/search/ntp_logging_events.h
@@ -0,0 +1,190 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SEARCH_NTP_LOGGING_EVENTS_H_
+#define CHROME_COMMON_SEARCH_NTP_LOGGING_EVENTS_H_
+
+// The different types of events that are logged from the NTP. This enum is used
+// to transfer information from the NTP javascript to the renderer and is *not*
+// used as a UMA enum histogram's logged value.
+// Note: Keep in sync with browser/resources/local_ntp/local_ntp.js, voice.js,
+// most_visited_single.js, and custom_backgrounds.js.
+enum NTPLoggingEventType {
+ // Deleted: NTP_SERVER_SIDE_SUGGESTION = 0,
+ // Deleted: NTP_CLIENT_SIDE_SUGGESTION = 1,
+ // Deleted: NTP_TILE = 2,
+ // Deleted: NTP_THUMBNAIL_TILE = 3,
+ // Deleted: NTP_GRAY_TILE = 4,
+ // Deleted: NTP_EXTERNAL_TILE = 5,
+ // Deleted: NTP_THUMBNAIL_ERROR = 6,
+ // Deleted: NTP_GRAY_TILE_FALLBACK = 7,
+ // Deleted: NTP_EXTERNAL_TILE_FALLBACK = 8,
+ // Deleted: NTP_MOUSEOVER = 9
+ // Deleted: NTP_TILE_LOADED = 10,
+ // Deleted: NTP_ALL_TILES_RECEIVED = 12,
+
+ // All NTP tiles have finished loading (successfully or failing). Logged only
+ // by the single-iframe version of the NTP.
+ NTP_ALL_TILES_LOADED = 11,
+
+ // Activated by clicking on the fakebox icon. Logged by Voice Search.
+ NTP_VOICE_ACTION_ACTIVATE_FAKEBOX = 13,
+ // Activated by keyboard shortcut.
+ NTP_VOICE_ACTION_ACTIVATE_KEYBOARD = 14,
+ // Close the voice overlay by a user's explicit action.
+ NTP_VOICE_ACTION_CLOSE_OVERLAY = 15,
+ // Submitted voice query.
+ NTP_VOICE_ACTION_QUERY_SUBMITTED = 16,
+ // Clicked on support link in error message.
+ NTP_VOICE_ACTION_SUPPORT_LINK_CLICKED = 17,
+ // Retried by clicking Try Again link.
+ NTP_VOICE_ACTION_TRY_AGAIN_LINK = 18,
+ // Retried by clicking microphone button.
+ NTP_VOICE_ACTION_TRY_AGAIN_MIC_BUTTON = 19,
+ // Errors received from the Speech Recognition API.
+ NTP_VOICE_ERROR_NO_SPEECH = 20,
+ NTP_VOICE_ERROR_ABORTED = 21,
+ NTP_VOICE_ERROR_AUDIO_CAPTURE = 22,
+ NTP_VOICE_ERROR_NETWORK = 23,
+ NTP_VOICE_ERROR_NOT_ALLOWED = 24,
+ NTP_VOICE_ERROR_SERVICE_NOT_ALLOWED = 25,
+ NTP_VOICE_ERROR_BAD_GRAMMAR = 26,
+ NTP_VOICE_ERROR_LANGUAGE_NOT_SUPPORTED = 27,
+ NTP_VOICE_ERROR_NO_MATCH = 28,
+ NTP_VOICE_ERROR_OTHER = 29,
+
+ // A static Doodle was shown, coming from cache.
+ NTP_STATIC_LOGO_SHOWN_FROM_CACHE = 30,
+ // A static Doodle was shown, coming from the network.
+ NTP_STATIC_LOGO_SHOWN_FRESH = 31,
+ // A call-to-action Doodle image was shown, coming from cache.
+ NTP_CTA_LOGO_SHOWN_FROM_CACHE = 32,
+ // A call-to-action Doodle image was shown, coming from the network.
+ NTP_CTA_LOGO_SHOWN_FRESH = 33,
+
+ // A static Doodle was clicked.
+ NTP_STATIC_LOGO_CLICKED = 34,
+ // A call-to-action Doodle was clicked.
+ NTP_CTA_LOGO_CLICKED = 35,
+ // An animated Doodle was clicked.
+ NTP_ANIMATED_LOGO_CLICKED = 36,
+
+ // The One Google Bar was shown.
+ NTP_ONE_GOOGLE_BAR_SHOWN = 37,
+
+ // The NTP background has been customized with an image.
+ NTP_BACKGROUND_CUSTOMIZED = 38,
+ // Shortcuts have been customized on the NTP.
+ NTP_SHORTCUT_CUSTOMIZED = 39,
+
+ // The 'Chrome backgrounds' menu item was clicked.
+ NTP_CUSTOMIZE_CHROME_BACKGROUNDS_CLICKED = 40,
+ // The 'Upload an image' menu item was clicked.
+ NTP_CUSTOMIZE_LOCAL_IMAGE_CLICKED = 41,
+ // The 'Restore default background' menu item was clicked.
+ NTP_CUSTOMIZE_RESTORE_BACKGROUND_CLICKED = 42,
+ // The attribution link on a customized background image was clicked.
+ NTP_CUSTOMIZE_ATTRIBUTION_CLICKED = 43,
+ // The 'Add shortcut' link was clicked.
+ NTP_CUSTOMIZE_ADD_SHORTCUT_CLICKED = 44,
+ // The 'Edit shortcut' link was clicked.
+ NTP_CUSTOMIZE_EDIT_SHORTCUT_CLICKED = 45,
+ // The 'Restore default shortcuts' menu item was clicked.
+ NTP_CUSTOMIZE_RESTORE_SHORTCUTS_CLICKED = 46,
+
+ // A collection was selected in the 'Chrome backgrounds' dialog.
+ NTP_CUSTOMIZE_CHROME_BACKGROUND_SELECT_COLLECTION = 47,
+ // An image was selected in the 'Chrome backgrounds' dialog.
+ NTP_CUSTOMIZE_CHROME_BACKGROUND_SELECT_IMAGE = 48,
+ // 'Cancel' was clicked in the 'Chrome backgrounds' dialog.
+ NTP_CUSTOMIZE_CHROME_BACKGROUND_CANCEL = 49,
+ // 'Done' was clicked in the 'Chrome backgrounds' dialog.
+ NTP_CUSTOMIZE_CHROME_BACKGROUND_DONE = 50,
+
+ // 'Cancel' was clicked in the 'Upload an image' dialog.
+ NTP_CUSTOMIZE_LOCAL_IMAGE_CANCEL = 51,
+ // 'Done' was clicked in the 'Upload an image' dialog.
+ NTP_CUSTOMIZE_LOCAL_IMAGE_DONE = 52,
+
+ // A custom shortcut was removed.
+ NTP_CUSTOMIZE_SHORTCUT_REMOVE = 53,
+ // 'Cancel' was clicked in the 'Edit shortcut' dialog.
+ NTP_CUSTOMIZE_SHORTCUT_CANCEL = 54,
+ // 'Done' was clicked in the 'Edit shortcut' dialog.
+ NTP_CUSTOMIZE_SHORTCUT_DONE = 55,
+ // A custom shortcut action was undone.
+ NTP_CUSTOMIZE_SHORTCUT_UNDO = 56,
+ // All custom shortcuts were restored.
+ NTP_CUSTOMIZE_SHORTCUT_RESTORE_ALL = 57,
+ // A custom shortcut was added.
+ NTP_CUSTOMIZE_SHORTCUT_ADD = 58,
+ // A custom shortcut was updated.
+ NTP_CUSTOMIZE_SHORTCUT_UPDATE = 59,
+
+ // A middle slot promo was shown.
+ NTP_MIDDLE_SLOT_PROMO_SHOWN = 60,
+ // A promo link was clicked.
+ NTP_MIDDLE_SLOT_PROMO_LINK_CLICKED = 61,
+
+ // The shortcut type displayed (i.e. Most Visited or custom links) was
+ // changed.
+ NTP_CUSTOMIZE_SHORTCUT_TOGGLE_TYPE = 62,
+ // The visibility of shortcuts was changed.
+ NTP_CUSTOMIZE_SHORTCUT_TOGGLE_VISIBILITY = 63,
+
+ // The richer picker was opened.
+ NTP_CUSTOMIZATION_MENU_OPENED = 64,
+ // 'Cancel' was clicked in the richer picker.
+ NTP_CUSTOMIZATION_MENU_CANCEL = 65,
+ // 'Done' was clicked in the richer picker.
+ NTP_CUSTOMIZATION_MENU_DONE = 66,
+
+ // 'Upload from device' was selected in the richer picker.
+ NTP_BACKGROUND_UPLOAD_FROM_DEVICE = 67,
+ // A collection tile was selected in the richer picker.
+ NTP_BACKGROUND_OPEN_COLLECTION = 68,
+ // A image tile was selected in the richer picker.
+ NTP_BACKGROUND_SELECT_IMAGE = 69,
+ // An image was set as the NTP background.
+ NTP_BACKGROUND_IMAGE_SET = 71,
+ // The back arrow was clicked in the richer picker.
+ NTP_BACKGROUND_BACK_CLICK = 72,
+ // The 'No background' tile was selected in the richer picker.
+ NTP_BACKGROUND_DEFAULT_SELECTED = 73,
+ // 'Cancel' was clicked in the image selection dialog.
+ NTP_BACKGROUND_UPLOAD_CANCEL = 75,
+ // 'Done' was clicked in the image selection dialog.
+ NTP_BACKGROUND_UPLOAD_DONE = 76,
+ // The NTP background image was reset in the richer picker.
+ NTP_BACKGROUND_IMAGE_RESET = 77,
+
+ // The 'My shortcuts' (i.e. custom links) option was clicked in the richer
+ // picker.
+ NTP_CUSTOMIZE_SHORTCUT_CUSTOM_LINKS_CLICKED = 78,
+ // The 'Most visited sites' option was clicked in the richer picker.
+ NTP_CUSTOMIZE_SHORTCUT_MOST_VISITED_CLICKED = 79,
+ // The 'Hide shortcuts' toggle was clicked in the richer picker.
+ NTP_CUSTOMIZE_SHORTCUT_VISIBILITY_TOGGLE_CLICKED = 80,
+
+ // The 'refresh daily' toggle was licked in the richer picker.
+ NTP_BACKGROUND_REFRESH_TOGGLE_CLICKED = 81,
+ // Daily refresh was enabled by clicked 'Done' in the richer picker.
+ NTP_BACKGROUND_DAILY_REFRESH_ENABLED = 82,
+
+ NTP_EVENT_TYPE_LAST = NTP_BACKGROUND_DAILY_REFRESH_ENABLED
+};
+
+// The different types of events that are logged for NTP search suggestions,
+// such as number of chips shown and the index of chips that are clicked. This
+// enum is used to transfer information from the NTP javascript to the renderer
+// and is *not* used as a UMA enum histogram's logged value. These events may be
+// logged by javascript served from GWS, see
+// google3/java/com/google/gws/plugins/newtab/suggestions.js.
+enum class NTPSuggestionsLoggingEventType {
+ kShownCount = 0,
+ kIndexClicked = 1,
+ kMaxValue = kIndexClicked,
+};
+
+#endif // CHROME_COMMON_SEARCH_NTP_LOGGING_EVENTS_H_
diff --git a/chromium/chrome/common/search/selected_colors_info.h b/chromium/chrome/common/search/selected_colors_info.h
new file mode 100644
index 00000000000..d1c6aa450c2
--- /dev/null
+++ b/chromium/chrome/common/search/selected_colors_info.h
@@ -0,0 +1,65 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SEARCH_SELECTED_COLORS_INFO_H_
+#define CHROME_COMMON_SEARCH_SELECTED_COLORS_INFO_H_
+
+#include <stdint.h>
+
+#include "chrome/grit/generated_resources.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace chrome_colors {
+
+struct ColorInfo {
+ constexpr ColorInfo(int id, SkColor color, int label_id)
+ : ColorInfo(id, color, label_id, nullptr) {}
+ constexpr ColorInfo(int id,
+ SkColor color,
+ int label_id,
+ const char* icon_data)
+ : id(id), color(color), label_id(label_id), icon_data(icon_data) {}
+ int id;
+ SkColor color;
+ int label_id;
+ const char* icon_data;
+};
+
+// List of preselected colors to show in Chrome Colors menu. This array should
+// always be in sync with ChromeColorsInfo in enums.xml.
+constexpr ColorInfo kSelectedColorsInfo[] = {
+ // 0 - reserved for any color not in this set.
+ ColorInfo(1, SkColorSetRGB(239, 235, 233), IDS_NTP_COLORS_WARM_GREY),
+ ColorInfo(2, SkColorSetRGB(120, 127, 145), IDS_NTP_COLORS_COOL_GREY),
+ ColorInfo(3, SkColorSetRGB(55, 71, 79), IDS_NTP_COLORS_MIDNIGHT_BLUE),
+ ColorInfo(4, SkColorSetRGB(0, 0, 0), IDS_NTP_COLORS_BLACK),
+ ColorInfo(5, SkColorSetRGB(252, 219, 201), IDS_NTP_COLORS_BEIGE_AND_WHITE),
+ ColorInfo(6, SkColorSetRGB(255, 249, 228), IDS_NTP_COLORS_YELLOW_AND_WHITE),
+ ColorInfo(7, SkColorSetRGB(203, 233, 191), IDS_NTP_COLORS_GREEN_AND_WHITE),
+ ColorInfo(8,
+ SkColorSetRGB(221, 244, 249),
+ IDS_NTP_COLORS_LIGHT_TEAL_AND_WHITE),
+ ColorInfo(9,
+ SkColorSetRGB(233, 212, 255),
+ IDS_NTP_COLORS_LIGHT_PURPLE_AND_WHITE),
+ ColorInfo(10, SkColorSetRGB(249, 226, 237), IDS_NTP_COLORS_PINK_AND_WHITE),
+ ColorInfo(11, SkColorSetRGB(227, 171, 154), IDS_NTP_COLORS_BEIGE),
+ ColorInfo(12, SkColorSetRGB(255, 171, 64), IDS_NTP_COLORS_ORANGE),
+ ColorInfo(13, SkColorSetRGB(67, 160, 71), IDS_NTP_COLORS_LIGHT_GREEN),
+ ColorInfo(14, SkColorSetRGB(25, 157, 169), IDS_NTP_COLORS_LIGHT_TEAL),
+ ColorInfo(15, SkColorSetRGB(93, 147, 228), IDS_NTP_COLORS_LIGHT_BLUE),
+ ColorInfo(16, SkColorSetRGB(255, 174, 189), IDS_NTP_COLORS_PINK),
+ ColorInfo(17, SkColorSetRGB(189, 22, 92), IDS_NTP_COLORS_DARK_PINK_AND_RED),
+ ColorInfo(18,
+ SkColorSetRGB(183, 28, 28),
+ IDS_NTP_COLORS_DARK_RED_AND_ORANGE),
+ ColorInfo(19, SkColorSetRGB(46, 125, 50), IDS_NTP_COLORS_DARK_GREEN),
+ ColorInfo(20, SkColorSetRGB(0, 110, 120), IDS_NTP_COLORS_DARK_TEAL),
+ ColorInfo(21, SkColorSetRGB(21, 101, 192), IDS_NTP_COLORS_DARK_BLUE),
+ ColorInfo(22, SkColorSetRGB(91, 54, 137), IDS_NTP_COLORS_DARK_PURPLE),
+};
+
+} // namespace chrome_colors
+
+#endif // CHROME_COMMON_SEARCH_SELECTED_COLORS_INFO_H_
diff --git a/chromium/chrome/common/secure_origin_whitelist.cc b/chromium/chrome/common/secure_origin_whitelist.cc
new file mode 100644
index 00000000000..e6a2a3ba1e0
--- /dev/null
+++ b/chromium/chrome/common/secure_origin_whitelist.cc
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/secure_origin_whitelist.h"
+
+#include <set>
+#include <string>
+
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "extensions/common/constants.h"
+
+namespace secure_origin_whitelist {
+
+std::set<std::string> GetSchemesBypassingSecureContextCheck() {
+ std::set<std::string> schemes;
+ schemes.insert(extensions::kExtensionScheme);
+ return schemes;
+}
+
+void RegisterPrefs(PrefRegistrySimple* local_state) {
+ local_state->RegisterStringPref(prefs::kUnsafelyTreatInsecureOriginAsSecure,
+ /* default_value */ "");
+}
+
+} // namespace secure_origin_whitelist
diff --git a/chromium/chrome/common/secure_origin_whitelist.h b/chromium/chrome/common/secure_origin_whitelist.h
new file mode 100644
index 00000000000..e55ca47d2fd
--- /dev/null
+++ b/chromium/chrome/common/secure_origin_whitelist.h
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SECURE_ORIGIN_WHITELIST_H_
+#define CHROME_COMMON_SECURE_ORIGIN_WHITELIST_H_
+
+#include <set>
+#include <string>
+
+class PrefRegistrySimple;
+
+namespace secure_origin_whitelist {
+
+// Returns a whitelist of schemes that should bypass the Is Privileged Context
+// check. See http://www.w3.org/TR/powerful-features/#settings-privileged.
+std::set<std::string> GetSchemesBypassingSecureContextCheck();
+
+// Register preferences for Secure Origin Whitelists.
+void RegisterPrefs(PrefRegistrySimple* local_state);
+
+} // namespace secure_origin_whitelist
+
+#endif // CHROME_COMMON_SECURE_ORIGIN_WHITELIST_H_
diff --git a/chromium/chrome/common/service_process_util.cc b/chromium/chrome/common/service_process_util.cc
new file mode 100644
index 00000000000..2ebaab1c8eb
--- /dev/null
+++ b/chromium/chrome/common/service_process_util.cc
@@ -0,0 +1,276 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/service_process_util.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/hash/sha1.h"
+#include "base/logging.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/singleton.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/cloud_devices/common/cloud_devices_switches.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "google_apis/gaia/gaia_switches.h"
+#include "services/network/public/cpp/network_switches.h"
+#include "ui/base/ui_base_switches.h"
+
+#if !defined(OS_MACOSX)
+
+namespace {
+
+// This should be more than enough to hold a version string assuming each part
+// of the version string is an int64_t.
+const uint32_t kMaxVersionStringLength = 256;
+
+// The structure that gets written to shared memory.
+struct ServiceProcessSharedData {
+ char service_process_version[kMaxVersionStringLength];
+ base::ProcessId service_process_pid;
+};
+
+} // namespace
+
+// Return a name that is scoped to this instance of the service process. We
+// use the user-data-dir and the version as a scoping prefix.
+std::string GetServiceProcessScopedVersionedName(
+ const std::string& append_str) {
+ std::string versioned_str = version_info::GetVersionNumber();
+ versioned_str.append(append_str);
+ return GetServiceProcessScopedName(versioned_str);
+}
+
+// Reads the named shared memory to get the shared data. Returns false if no
+// matching shared memory was found.
+// static
+bool ServiceProcessState::GetServiceProcessData(std::string* version,
+ base::ProcessId* pid) {
+ base::ReadOnlySharedMemoryMapping service_process_data_mapping =
+ OpenServiceProcessDataMapping(sizeof(ServiceProcessSharedData));
+ if (!service_process_data_mapping.IsValid())
+ return false;
+
+ const ServiceProcessSharedData* service_data =
+ service_process_data_mapping.GetMemoryAs<ServiceProcessSharedData>();
+ // Make sure the version in shared memory is null-terminated. If it is not,
+ // treat it as invalid.
+ if (version && memchr(service_data->service_process_version, '\0',
+ sizeof(service_data->service_process_version)))
+ *version = service_data->service_process_version;
+ if (pid)
+ *pid = service_data->service_process_pid;
+ return true;
+}
+#endif // !OS_MACOSX
+
+// Return a name that is scoped to this instance of the service process. We
+// use the hash of the user-data-dir as a scoping prefix. We can't use
+// the user-data-dir itself as we have limits on the size of the lock names.
+std::string GetServiceProcessScopedName(const std::string& append_str) {
+ base::FilePath user_data_dir;
+ base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+#if defined(OS_WIN)
+ std::string user_data_dir_path = base::WideToUTF8(user_data_dir.value());
+#elif defined(OS_POSIX)
+ std::string user_data_dir_path = user_data_dir.value();
+#endif // defined(OS_WIN)
+ std::string hash = base::SHA1HashString(user_data_dir_path);
+ std::string hex_hash = base::HexEncode(hash.c_str(), hash.length());
+ return hex_hash + "." + append_str;
+}
+
+std::unique_ptr<base::CommandLine> CreateServiceProcessCommandLine() {
+ base::FilePath exe_path;
+ base::PathService::Get(content::CHILD_PROCESS_EXE, &exe_path);
+ DCHECK(!exe_path.empty()) << "Unable to get service process binary name.";
+ std::unique_ptr<base::CommandLine> command_line(
+ new base::CommandLine(exe_path));
+ command_line->AppendSwitchASCII(switches::kProcessType,
+ switches::kCloudPrintServiceProcess);
+
+#if defined(OS_WIN)
+ command_line->AppendArg(switches::kPrefetchArgumentOther);
+#endif // defined(OS_WIN)
+
+ static const char* const kSwitchesToCopy[] = {
+ network::switches::kIgnoreUrlFetcherCertRequests,
+ switches::kCloudPrintSetupProxy,
+ switches::kCloudPrintURL,
+ switches::kCloudPrintXmppEndpoint,
+#if defined(OS_WIN)
+ switches::kEnableCloudPrintXps,
+#endif
+ switches::kEnableLogging,
+ switches::kLang,
+ switches::kLoggingLevel,
+ switches::kLsoUrl,
+ switches::kNoServiceAutorun,
+ switches::kUserDataDir,
+ switches::kV,
+ switches::kVModule,
+ switches::kWaitForDebugger,
+ };
+
+ command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
+ kSwitchesToCopy, base::size(kSwitchesToCopy));
+ return command_line;
+}
+
+ServiceProcessState::ServiceProcessState() : state_(NULL) {
+ autorun_command_line_ = CreateServiceProcessCommandLine();
+ CreateState();
+}
+
+ServiceProcessState::~ServiceProcessState() {
+#if !defined(OS_MACOSX)
+ if (service_process_data_region_.IsValid()) {
+ service_process_data_region_ = {};
+ DeleteServiceProcessDataRegion();
+ }
+#endif // !OS_MACOSX
+ TearDownState();
+}
+
+void ServiceProcessState::SignalStopped() {
+ TearDownState();
+#if !defined(OS_MACOSX)
+ service_process_data_region_ = {};
+#endif // !OS_MACOSX
+}
+
+#if !defined(OS_MACOSX)
+bool ServiceProcessState::Initialize() {
+ if (!TakeSingletonLock()) {
+ return false;
+ }
+ // Now that we have the singleton, take care of killing an older version, if
+ // it exists.
+ if (!HandleOtherVersion())
+ return false;
+
+ // Write the version we are using to shared memory. This can be used by a
+ // newer service to signal us to exit.
+ return CreateSharedData();
+}
+
+// static
+std::string ServiceProcessState::GetServiceProcessSharedMemName() {
+ return GetServiceProcessScopedName("_service_shmem");
+}
+
+bool ServiceProcessState::HandleOtherVersion() {
+ std::string running_version;
+ base::ProcessId process_id = 0;
+ ServiceProcessRunningState state =
+ GetServiceProcessRunningState(&running_version, &process_id);
+ switch (state) {
+ case SERVICE_SAME_VERSION_RUNNING:
+ case SERVICE_NEWER_VERSION_RUNNING:
+ return false;
+ case SERVICE_OLDER_VERSION_RUNNING:
+ // If an older version is running, kill it.
+ ForceServiceProcessShutdown(running_version, process_id);
+ break;
+ case SERVICE_NOT_RUNNING:
+ break;
+ }
+ return true;
+}
+
+bool ServiceProcessState::CreateSharedData() {
+ if (version_info::GetVersionNumber().length() >= kMaxVersionStringLength) {
+ NOTREACHED() << "Version string length is << "
+ << version_info::GetVersionNumber().length()
+ << " which is longer than" << kMaxVersionStringLength;
+ return false;
+ }
+
+ uint32_t alloc_size = sizeof(ServiceProcessSharedData);
+ service_process_data_region_ = CreateServiceProcessDataRegion(alloc_size);
+ if (!service_process_data_region_.IsValid())
+ return false;
+ base::WritableSharedMemoryMapping mapping =
+ service_process_data_region_.Map();
+ if (!mapping.IsValid())
+ return false;
+ memset(mapping.memory(), 0, alloc_size);
+ ServiceProcessSharedData* shared_data =
+ mapping.GetMemoryAs<ServiceProcessSharedData>();
+ DCHECK(shared_data);
+ memcpy(shared_data->service_process_version,
+ version_info::GetVersionNumber().c_str(),
+ version_info::GetVersionNumber().length());
+ shared_data->service_process_pid = base::GetCurrentProcId();
+ return true;
+}
+
+// static
+ServiceProcessState::ServiceProcessRunningState
+ServiceProcessState::GetServiceProcessRunningState(
+ std::string* service_version_out,
+ base::ProcessId* pid_out) {
+ std::string version;
+ if (!ServiceProcessState::GetServiceProcessData(&version, pid_out))
+ return SERVICE_NOT_RUNNING;
+
+#if defined(OS_POSIX)
+ // We only need to check for service running on POSIX because Windows cleans
+ // up shared memory files when an app crashes, so there isn't a chance of
+ // us reading bogus data from shared memory for an app that has died.
+ if (!CheckServiceProcessReady()) {
+ return SERVICE_NOT_RUNNING;
+ }
+#endif // defined(OS_POSIX)
+
+ // At this time we have a version string. Set the out param if it exists.
+ if (service_version_out)
+ *service_version_out = version;
+
+ base::Version service_version(version);
+ // If the version string is invalid, treat it like an older version.
+ if (!service_version.IsValid())
+ return SERVICE_OLDER_VERSION_RUNNING;
+
+ // Get the version of the currently *running* instance of Chrome.
+ const base::Version& running_version = version_info::GetVersion();
+ if (!running_version.IsValid()) {
+ NOTREACHED() << "Failed to parse version info";
+ // Our own version is invalid. This is an error case. Pretend that we
+ // are out of date.
+ return SERVICE_NEWER_VERSION_RUNNING;
+ }
+
+ int comp = running_version.CompareTo(service_version);
+ if (comp == 0)
+ return SERVICE_SAME_VERSION_RUNNING;
+ return comp > 0 ? SERVICE_OLDER_VERSION_RUNNING
+ : SERVICE_NEWER_VERSION_RUNNING;
+}
+
+mojo::NamedPlatformChannel::ServerName
+ServiceProcessState::GetServiceProcessServerName() {
+ return ::GetServiceProcessServerName();
+}
+
+#endif // !OS_MACOSX
diff --git a/chromium/chrome/common/service_process_util.h b/chromium/chrome/common/service_process_util.h
new file mode 100644
index 00000000000..cb9e903c0e9
--- /dev/null
+++ b/chromium/chrome/common/service_process_util.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SERVICE_PROCESS_UTIL_H_
+#define CHROME_COMMON_SERVICE_PROCESS_UTIL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/process/process.h"
+#include "base/single_thread_task_runner.h"
+#include "build/build_config.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+
+class MultiProcessLock;
+
+#if defined(OS_MACOSX)
+#ifdef __OBJC__
+@class NSString;
+#else
+class NSString;
+#endif
+#endif
+
+namespace base {
+class CommandLine;
+}
+
+// Return the IPC channel to connect to the service process.
+mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName();
+
+// Return a name that is scoped to this instance of the service process. We
+// use the user-data-dir as a scoping prefix.
+std::string GetServiceProcessScopedName(const std::string& append_str);
+
+#if !defined(OS_MACOSX)
+// Return a name that is scoped to this instance of the service process. We
+// use the user-data-dir and the version as a scoping prefix.
+std::string GetServiceProcessScopedVersionedName(const std::string& append_str);
+#endif // !OS_MACOSX
+
+#if defined(OS_POSIX)
+// Attempts to take a lock named |name|. If |waiting| is true then this will
+// make multiple attempts to acquire the lock.
+// Caller is responsible for ownership of the MultiProcessLock.
+MultiProcessLock* TakeNamedLock(const std::string& name, bool waiting);
+#endif
+
+// The following method is used in a process that acts as a client to the
+// service process (typically the browser process). It method checks that if the
+// service process is ready to receive IPC commands.
+bool CheckServiceProcessReady();
+
+// --------------------------------------------------------------------------
+
+// Forces a service process matching the specified version to shut down.
+bool ForceServiceProcessShutdown(const std::string& version,
+ base::ProcessId process_id);
+
+// Creates command-line to run the service process.
+std::unique_ptr<base::CommandLine> CreateServiceProcessCommandLine();
+
+// This is a class that is used by the service process to signal events and
+// share data with external clients. This class lives in this file because the
+// internal data structures and mechanisms used by the utility methods above
+// and this class are shared.
+class ServiceProcessState {
+ public:
+ ServiceProcessState();
+ ~ServiceProcessState();
+
+ // Tries to become the sole service process for the current user data dir.
+ // Returns false if another service process is already running.
+ bool Initialize();
+
+ // Signal that the service process is ready.
+ // This method is called when the service process is running and initialized.
+ // |terminate_task| is invoked when we get a terminate request from another
+ // process (in the same thread that called SignalReady). It can be NULL.
+ // |task_runner| must be of type IO and is the loop that POSIX uses
+ // to monitor the service process.
+ bool SignalReady(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ const base::Closure& terminate_task);
+
+ // Signal that the service process is stopped.
+ void SignalStopped();
+
+ // Register the service process to run on startup.
+ bool AddToAutoRun();
+
+ // Unregister the service process to run on startup.
+ bool RemoveFromAutoRun();
+
+ // Return the channel handle used for communicating with the service.
+#if defined(OS_MACOSX)
+ mojo::PlatformChannelServerEndpoint GetServiceProcessServerEndpoint();
+#else
+ mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName();
+#endif
+
+ private:
+#if !defined(OS_MACOSX)
+ enum ServiceProcessRunningState {
+ SERVICE_NOT_RUNNING,
+ SERVICE_OLDER_VERSION_RUNNING,
+ SERVICE_SAME_VERSION_RUNNING,
+ SERVICE_NEWER_VERSION_RUNNING,
+ };
+
+ // Create the shared memory data for the service process.
+ bool CreateSharedData();
+
+ // If an older version of the service process running, it should be shutdown.
+ // Returns false if this process needs to exit.
+ bool HandleOtherVersion();
+
+ // Acquires a singleton lock for the service process. A return value of false
+ // means that a service process instance is already running.
+ bool TakeSingletonLock();
+
+ // Return a name used to name a shared memory file that will be used to locate
+ // the currently running service process.
+ static std::string GetServiceProcessSharedMemName();
+
+ // Create a writable service process data shared memory region of the
+ // specified size. Returns an invalid region on error. If the backing file for
+ // the shared memory region already exists but is smaller than |size|, this
+ // function may return a valid region which will fail to be mapped.
+ static base::WritableSharedMemoryRegion CreateServiceProcessDataRegion(
+ size_t size);
+
+ // Open an existing service process data shared memory region of the specified
+ // size. Returns an invalid region on error. Note that if the size of the
+ // existing region is smaller than |size|, this function may return a valid
+ // region which will fail to be mapped. Also note that since the underlying
+ // file is writable, the region cannot be read-only.
+ static base::ReadOnlySharedMemoryMapping OpenServiceProcessDataMapping(
+ size_t size);
+
+ // Deletes a service process data shared memory backing file. Returns false if
+ // the file was not able to be deleted.
+ static bool DeleteServiceProcessDataRegion();
+
+ static ServiceProcessRunningState GetServiceProcessRunningState(
+ std::string* service_version_out,
+ base::ProcessId* pid_out);
+#endif // !OS_MACOSX
+
+ // Returns the process id and version of the currently running service
+ // process. Note: DO NOT use this check whether the service process is ready
+ // because a true return value only means that some process shared data was
+ // available, and not that the process is ready to receive IPC commands, or
+ // even running.
+ static bool GetServiceProcessData(std::string* version, base::ProcessId* pid);
+
+ // Creates the platform specific state.
+ void CreateState();
+
+ // Tear down the platform specific state.
+ void TearDownState();
+
+ // An opaque object that maintains state. The actual definition of this is
+ // platform dependent.
+ struct StateData;
+ StateData* state_;
+
+#if !defined(OS_MACOSX)
+ // The shared memory mapping backing the shared state on non-macos
+ // platforms. This is actually referring to named shared memory, and on some
+ // platforms (eg, Windows) determines the lifetime of when consumers are able
+ // to open the named shared region. This means this region must stay alive
+ // for the named region to be visible.
+ base::WritableSharedMemoryRegion service_process_data_region_;
+#endif
+
+ std::unique_ptr<base::CommandLine> autorun_command_line_;
+
+#if defined(OS_MACOSX)
+ friend bool CheckServiceProcessReady();
+#endif
+
+ FRIEND_TEST_ALL_PREFIXES(ServiceProcessStateTest, SharedMem);
+ FRIEND_TEST_ALL_PREFIXES(ServiceProcessStateTest, ForceShutdown);
+
+ friend class ServiceProcessControlBrowserTest;
+ FRIEND_TEST_ALL_PREFIXES(ServiceProcessControlBrowserTest, ForceShutdown);
+ FRIEND_TEST_ALL_PREFIXES(ServiceProcessControlBrowserTest, CheckPid);
+};
+
+#endif // CHROME_COMMON_SERVICE_PROCESS_UTIL_H_
diff --git a/chromium/chrome/common/service_process_util_linux.cc b/chromium/chrome/common/service_process_util_linux.cc
new file mode 100644
index 00000000000..cb75865b81b
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_linux.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <signal.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/threading/platform_thread.h"
+#include "build/branding_buildflags.h"
+#include "chrome/common/auto_start_linux.h"
+#include "chrome/common/multi_process_lock.h"
+#include "chrome/common/service_process_util_posix.h"
+
+namespace {
+
+MultiProcessLock* TakeServiceInitializingLock(bool waiting) {
+ std::string lock_name =
+ GetServiceProcessScopedName("_service_initializing");
+ return TakeNamedLock(lock_name, waiting);
+}
+
+std::string GetBaseDesktopName() {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ return "google-chrome-service.desktop";
+#else // BUILDFLAG(CHROMIUM_BRANDING)
+ return "chromium-service.desktop";
+#endif
+}
+} // namespace
+
+MultiProcessLock* TakeServiceRunningLock(bool waiting) {
+ std::string lock_name =
+ GetServiceProcessScopedName("_service_running");
+ return TakeNamedLock(lock_name, waiting);
+}
+
+bool ForceServiceProcessShutdown(const std::string& version,
+ base::ProcessId process_id) {
+ if (kill(process_id, SIGTERM) < 0) {
+ DPLOG(ERROR) << "kill";
+ return false;
+ }
+ return true;
+}
+
+// Gets the name of the service process IPC channel.
+// Returns an absolute path as required.
+mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName() {
+ base::FilePath temp_dir;
+ base::PathService::Get(base::DIR_TEMP, &temp_dir);
+ std::string pipe_name = GetServiceProcessScopedVersionedName("_service_ipc");
+ return temp_dir.Append(pipe_name).value();
+}
+
+bool CheckServiceProcessReady() {
+ std::unique_ptr<MultiProcessLock> running_lock(TakeServiceRunningLock(false));
+ return running_lock.get() == NULL;
+}
+
+bool ServiceProcessState::TakeSingletonLock() {
+ state_->initializing_lock.reset(TakeServiceInitializingLock(true));
+ return state_->initializing_lock.get();
+}
+
+bool ServiceProcessState::AddToAutoRun() {
+ DCHECK(autorun_command_line_.get());
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ std::string app_name = "Google Chrome Service";
+#else // BUILDFLAG(CHROMIUM_BRANDING)
+ std::string app_name = "Chromium Service";
+#endif
+ return AutoStart::AddApplication(
+ GetServiceProcessScopedName(GetBaseDesktopName()),
+ app_name,
+ autorun_command_line_->GetCommandLineString(),
+ false);
+}
+
+bool ServiceProcessState::RemoveFromAutoRun() {
+ return AutoStart::Remove(
+ GetServiceProcessScopedName(GetBaseDesktopName()));
+}
diff --git a/chromium/chrome/common/service_process_util_mac.mm b/chromium/chrome/common/service_process_util_mac.mm
new file mode 100644
index 00000000000..edef9967a03
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_mac.mm
@@ -0,0 +1,405 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Foundation/Foundation.h>
+#include <launch.h>
+#include <sys/un.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/version.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/service_process_util_posix.h"
+#include "components/version_info/version_info.h"
+
+using ::base::FilePathWatcher;
+
+namespace {
+
+#define kServiceProcessSessionType "Aqua"
+
+CFStringRef CopyServiceProcessLaunchDName() {
+ @autoreleasepool {
+ NSBundle* bundle = base::mac::FrameworkBundle();
+ return CFStringCreateCopy(kCFAllocatorDefault,
+ base::mac::NSToCFCast([bundle bundleIdentifier]));
+ }
+}
+
+NSString* GetServiceProcessLaunchDLabel() {
+ base::scoped_nsobject<NSString> name(
+ base::mac::CFToNSCast(CopyServiceProcessLaunchDName()));
+ NSString* label = [name stringByAppendingString:@".service_process"];
+ base::FilePath user_data_dir;
+ base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+ std::string user_data_dir_path = user_data_dir.value();
+ NSString* ns_path = base::SysUTF8ToNSString(user_data_dir_path);
+ ns_path = [ns_path stringByReplacingOccurrencesOfString:@" "
+ withString:@"_"];
+ label = [label stringByAppendingString:ns_path];
+ return label;
+}
+
+bool RemoveFromLaunchd() {
+ // We're killing a file.
+ base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+ base::BlockingType::MAY_BLOCK);
+ base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
+ return Launchd::GetInstance()->DeletePlist(Launchd::User,
+ Launchd::Agent,
+ name);
+}
+
+class ExecFilePathWatcherCallback {
+ public:
+ ExecFilePathWatcherCallback() {}
+ ~ExecFilePathWatcherCallback() {}
+
+ bool Init(const base::FilePath& path);
+ void NotifyPathChanged(const base::FilePath& path, bool error);
+
+ private:
+ base::scoped_nsobject<NSURL> executable_fsref_;
+};
+
+NSString* GetServiceProcessMachName() {
+ base::scoped_nsobject<NSString> name(
+ base::mac::CFToNSCast(CopyServiceProcessLaunchDName()));
+ return [name stringByAppendingFormat:@".service_process.%lu",
+ [GetServiceProcessLaunchDLabel() hash]];
+}
+
+} // namespace
+
+mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName() {
+ return base::SysNSStringToUTF8(GetServiceProcessMachName());
+}
+
+bool ForceServiceProcessShutdown(const std::string& /* version */,
+ base::ProcessId /* process_id */) {
+ const std::string& label =
+ base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
+ bool ret = Launchd::GetInstance()->RemoveJob(label);
+ if (!ret) {
+ DLOG(ERROR) << "ForceServiceProcessShutdown: " << label;
+ }
+ return ret;
+}
+
+bool ServiceProcessState::GetServiceProcessData(std::string* version,
+ base::ProcessId* pid) {
+ @autoreleasepool {
+ std::string label =
+ base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
+ mac::services::JobInfo info;
+ if (!Launchd::GetInstance()->GetJobInfo(label, &info))
+ return false;
+ // Anything past here will return true in that there does appear
+ // to be a service process of some sort registered with launchd.
+ if (version) {
+ *version = "0";
+ NSString* exe_path = base::SysUTF8ToNSString(info.program);
+ if (exe_path) {
+ NSString* bundle_path = [[[exe_path stringByDeletingLastPathComponent]
+ stringByDeletingLastPathComponent]
+ stringByDeletingLastPathComponent];
+ NSBundle* bundle = [NSBundle bundleWithPath:bundle_path];
+ if (bundle) {
+ NSString* ns_version =
+ [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+ if (ns_version) {
+ *version = base::SysNSStringToUTF8(ns_version);
+ } else {
+ DLOG(ERROR) << "Unable to get version at: "
+ << reinterpret_cast<CFStringRef>(bundle_path);
+ }
+ } else {
+ // The bundle has been deleted out from underneath the registered
+ // job.
+ DLOG(ERROR) << "Unable to get bundle at: "
+ << reinterpret_cast<CFStringRef>(bundle_path);
+ }
+ } else {
+ DLOG(ERROR) << "Unable to get executable path for service process";
+ }
+ }
+ if (pid) {
+ *pid = info.pid ? *info.pid : -1;
+ }
+ return true;
+ }
+}
+
+bool ServiceProcessState::Initialize() {
+ mac::services::JobInfo info;
+ // The Mach service will be checked when GetServiceProcessServerEndpoint()
+ // is called.
+ bool ok = Launchd::GetInstance()->GetJobInfo(
+ base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel()), &info);
+ if (!ok) {
+ DLOG(ERROR) << "Failed to look up job info.";
+ return false;
+ }
+ state_->job_info.program = info.program;
+ return true;
+}
+
+mojo::PlatformChannelServerEndpoint
+ServiceProcessState::GetServiceProcessServerEndpoint() {
+ mojo::NamedPlatformChannel::Options options;
+ options.server_name = base::SysNSStringToUTF8(GetServiceProcessMachName());
+ return mojo::NamedPlatformChannel(options).TakeServerEndpoint();
+}
+
+bool CheckServiceProcessReady() {
+ std::string version;
+ pid_t pid;
+ if (!ServiceProcessState::GetServiceProcessData(&version, &pid)) {
+ return false;
+ }
+ base::Version service_version(version);
+ bool ready = true;
+ if (!service_version.IsValid()) {
+ ready = false;
+ } else {
+ const base::Version& running_version = version_info::GetVersion();
+ if (!running_version.IsValid()) {
+ // Our own version is invalid. This is an error case. Pretend that we
+ // are out of date.
+ NOTREACHED();
+ ready = true;
+ } else {
+ ready = running_version.CompareTo(service_version) <= 0;
+ }
+ }
+ if (!ready) {
+ ForceServiceProcessShutdown(version, pid);
+ }
+ return ready;
+}
+
+mac::services::JobOptions GetServiceProcessJobOptions(
+ base::CommandLine* cmd_line,
+ bool for_auto_launch) {
+ mac::services::JobOptions options;
+
+ options.label = base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
+ options.executable_path = cmd_line->GetProgram().value();
+ options.arguments = cmd_line->argv();
+ options.mach_service_name =
+ base::SysNSStringToUTF8(GetServiceProcessMachName());
+ options.run_at_load = for_auto_launch;
+ options.auto_launch = for_auto_launch;
+
+ return options;
+}
+
+CFDictionaryRef CreateServiceProcessLaunchdPlist(base::CommandLine* cmd_line,
+ bool for_auto_launch) {
+ @autoreleasepool {
+ NSString* program = base::SysUTF8ToNSString(cmd_line->GetProgram().value());
+
+ std::vector<std::string> args = cmd_line->argv();
+ NSMutableArray* ns_args = [NSMutableArray arrayWithCapacity:args.size()];
+
+ for (std::vector<std::string>::iterator iter = args.begin();
+ iter < args.end(); ++iter) {
+ [ns_args addObject:base::SysUTF8ToNSString(*iter)];
+ }
+
+ // See the man page for launchd.plist.
+ NSMutableDictionary* launchd_plist = [@{
+ @LAUNCH_JOBKEY_LABEL : GetServiceProcessLaunchDLabel(),
+ @LAUNCH_JOBKEY_PROGRAM : program,
+ @LAUNCH_JOBKEY_PROGRAMARGUMENTS : ns_args,
+ @LAUNCH_JOBKEY_MACHSERVICES : GetServiceProcessMachName(),
+ } mutableCopy];
+
+ if (for_auto_launch) {
+ // We want the service process to be able to exit if there are no services
+ // enabled. With a value of NO in the SuccessfulExit key, launchd will
+ // relaunch the service automatically in any other case than exiting
+ // cleanly with a 0 return code.
+ NSDictionary* keep_alive =
+ @{@LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT : @NO};
+ NSDictionary* auto_launchd_plist = @{
+ @LAUNCH_JOBKEY_RUNATLOAD : @YES,
+ @LAUNCH_JOBKEY_KEEPALIVE : keep_alive,
+ @LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE : @kServiceProcessSessionType
+ };
+ [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
+ }
+ return reinterpret_cast<CFDictionaryRef>(launchd_plist);
+ }
+}
+
+// Writes the launchd property list into the user's LaunchAgents directory,
+// creating that directory if needed. This will cause the service process to be
+// auto launched on the next user login.
+bool ServiceProcessState::AddToAutoRun() {
+ // We're creating directories and writing a file.
+ base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+ base::BlockingType::MAY_BLOCK);
+ DCHECK(autorun_command_line_.get());
+ base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
+ base::ScopedCFTypeRef<CFDictionaryRef> plist(
+ CreateServiceProcessLaunchdPlist(autorun_command_line_.get(), true));
+ return Launchd::GetInstance()->WritePlistToFile(Launchd::User,
+ Launchd::Agent,
+ name,
+ plist);
+}
+
+bool ServiceProcessState::RemoveFromAutoRun() {
+ return RemoveFromLaunchd();
+}
+
+bool ServiceProcessState::StateData::WatchExecutable() {
+ @autoreleasepool {
+ base::FilePath executable_path = base::FilePath(job_info.program);
+ std::unique_ptr<ExecFilePathWatcherCallback> callback(
+ new ExecFilePathWatcherCallback);
+ if (!callback->Init(executable_path)) {
+ DLOG(ERROR) << "executable_watcher.Init " << executable_path.value();
+ return false;
+ }
+ if (!executable_watcher.Watch(
+ executable_path, false,
+ base::Bind(&ExecFilePathWatcherCallback::NotifyPathChanged,
+ base::Owned(callback.release())))) {
+ DLOG(ERROR) << "executable_watcher.watch " << executable_path.value();
+ return false;
+ }
+ return true;
+ }
+}
+
+bool ExecFilePathWatcherCallback::Init(const base::FilePath& path) {
+ NSString* path_string = base::mac::FilePathToNSString(path);
+ NSURL* path_url = [NSURL fileURLWithPath:path_string isDirectory:NO];
+ executable_fsref_.reset([[path_url fileReferenceURL] retain]);
+ return executable_fsref_.get() != nil;
+}
+
+void ExecFilePathWatcherCallback::NotifyPathChanged(const base::FilePath& path,
+ bool error) {
+ if (error) {
+ NOTREACHED(); // TODO(darin): Do something smarter?
+ return;
+ }
+
+ @autoreleasepool {
+ bool needs_shutdown = false;
+ bool needs_restart = false;
+ bool good_bundle = false;
+
+ // Go from bundle/Contents/MacOS/executable to bundle.
+ NSURL* bundle_url = [[[executable_fsref_ URLByDeletingLastPathComponent]
+ URLByDeletingLastPathComponent] URLByDeletingLastPathComponent];
+ if (bundle_url) {
+ base::ScopedCFTypeRef<CFBundleRef> bundle(CFBundleCreate(
+ kCFAllocatorDefault, base::mac::NSToCFCast(bundle_url)));
+ good_bundle = CFBundleGetIdentifier(bundle) != NULL;
+ }
+
+ if (!good_bundle) {
+ needs_shutdown = true;
+ } else {
+ bool in_trash = false;
+ NSFileManager* file_manager = [NSFileManager defaultManager];
+ NSURLRelationship relationship;
+ if ([file_manager getRelationship:&relationship
+ ofDirectory:NSTrashDirectory
+ inDomain:0
+ toItemAtURL:executable_fsref_
+ error:nil]) {
+ in_trash = relationship == NSURLRelationshipContains;
+ }
+ if (in_trash) {
+ needs_shutdown = true;
+ } else {
+ bool was_moved = true;
+ NSString* path_string = base::mac::FilePathToNSString(path);
+ NSURL* path_url = [NSURL fileURLWithPath:path_string isDirectory:NO];
+ NSURL* path_ref = [path_url fileReferenceURL];
+ if (path_ref != nil) {
+ if ([path_ref isEqual:executable_fsref_]) {
+ was_moved = false;
+ }
+ }
+ if (was_moved) {
+ needs_restart = true;
+ }
+ }
+ }
+ if (needs_shutdown || needs_restart) {
+ // First deal with the plist.
+ base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
+ if (needs_restart) {
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
+ Launchd::GetInstance()->CreatePlistFromFile(Launchd::User,
+ Launchd::Agent, name));
+ if (plist.get()) {
+ NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
+ NSURL* new_path = [executable_fsref_ filePathURL];
+ DCHECK([new_path isFileURL]);
+ NSString* ns_new_path = [new_path path];
+ ns_plist[@LAUNCH_JOBKEY_PROGRAM] = ns_new_path;
+ base::scoped_nsobject<NSMutableArray> args(
+ [ns_plist[@LAUNCH_JOBKEY_PROGRAMARGUMENTS] mutableCopy]);
+ args[0] = ns_new_path;
+ ns_plist[@LAUNCH_JOBKEY_PROGRAMARGUMENTS] = args;
+ if (!Launchd::GetInstance()->WritePlistToFile(
+ Launchd::User, Launchd::Agent, name, plist)) {
+ DLOG(ERROR) << "Unable to rewrite plist.";
+ needs_shutdown = true;
+ }
+ } else {
+ DLOG(ERROR) << "Unable to read plist.";
+ needs_shutdown = true;
+ }
+ }
+ if (needs_shutdown) {
+ if (!RemoveFromLaunchd()) {
+ DLOG(ERROR) << "Unable to RemoveFromLaunchd.";
+ }
+ }
+
+ // Then deal with the process.
+ CFStringRef session_type = CFSTR(kServiceProcessSessionType);
+ if (needs_restart) {
+ if (!Launchd::GetInstance()->RestartJob(Launchd::User, Launchd::Agent,
+ name, session_type)) {
+ DLOG(ERROR) << "RestartLaunchdJob";
+ needs_shutdown = true;
+ }
+ }
+ if (needs_shutdown) {
+ const std::string& label =
+ base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
+ if (!Launchd::GetInstance()->RemoveJob(label)) {
+ DLOG(ERROR) << "RemoveJob " << label;
+ // Exiting with zero, so launchd doesn't restart the process.
+ exit(0);
+ }
+ }
+ }
+ }
+}
diff --git a/chromium/chrome/common/service_process_util_mac_unittest.mm b/chromium/chrome/common/service_process_util_mac_unittest.mm
new file mode 100644
index 00000000000..06c8e07cc7e
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_mac_unittest.mm
@@ -0,0 +1,191 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/service_process_util.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/process/launch.h"
+#include "base/run_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/mac/mock_launchd.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class ServiceProcessStateFileManipulationTest : public ::testing::Test {
+ public:
+ void TrashFunc(const base::FilePath& src) {
+ NSURL* url = [NSURL fileURLWithPath:base::SysUTF8ToNSString(src.value())];
+ ASSERT_TRUE(url);
+ NSURL* resultingItemURL = nil;
+ BOOL success =
+ [[NSFileManager defaultManager] trashItemAtURL:url
+ resultingItemURL:&resultingItemURL
+ error:nil];
+ ASSERT_TRUE(success);
+ trashed_url_.reset([resultingItemURL retain]);
+ }
+
+ protected:
+ ServiceProcessStateFileManipulationTest()
+ : io_thread_("ServiceProcessStateFileManipulationTest_IO") {}
+
+ void SetUp() override {
+ base::Thread::Options options;
+ options.message_pump_type = base::MessagePumpType::IO;
+ ASSERT_TRUE(io_thread_.StartWithOptions(options));
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(MockLaunchd::MakeABundle(GetTempDirPath(), "Test",
+ &bundle_path_, &executable_path_));
+ mock_launchd_.reset(new MockLaunchd(
+ executable_path_, task_environment_.GetMainThreadTaskRunner(),
+ run_loop_.QuitClosure(), true));
+ scoped_launchd_instance_.reset(
+ new Launchd::ScopedInstance(mock_launchd_.get()));
+ ASSERT_TRUE(service_process_state_.Initialize());
+ ASSERT_TRUE(service_process_state_.SignalReady(
+ io_thread_.task_runner().get(), base::Closure()));
+ task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE, run_loop_.QuitWhenIdleClosure(),
+ TestTimeouts::action_max_timeout());
+ }
+
+ const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); }
+ const base::FilePath& executable_path() const { return executable_path_; }
+ const base::FilePath& bundle_path() const { return bundle_path_; }
+ const base::FilePath& GetTempDirPath() const { return temp_dir_.GetPath(); }
+
+ base::SingleThreadTaskRunner* GetIOTaskRunner() {
+ return io_thread_.task_runner().get();
+ }
+ void Run() { run_loop_.Run(); }
+
+ base::scoped_nsobject<NSURL> trashed_url_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ base::test::SingleThreadTaskEnvironment task_environment_{
+ base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
+ base::RunLoop run_loop_;
+ base::Thread io_thread_;
+ base::FilePath executable_path_, bundle_path_;
+ std::unique_ptr<MockLaunchd> mock_launchd_;
+ std::unique_ptr<Launchd::ScopedInstance> scoped_launchd_instance_;
+ ServiceProcessState service_process_state_;
+};
+
+void DeleteFunc(const base::FilePath& file) {
+ EXPECT_TRUE(base::DeleteFile(file, true));
+}
+
+void MoveFunc(const base::FilePath& from, const base::FilePath& to) {
+ EXPECT_TRUE(base::Move(from, to));
+}
+
+void ChangeAttr(const base::FilePath& from, int mode) {
+ EXPECT_EQ(chmod(from.value().c_str(), mode), 0);
+}
+
+class ScopedAttributesRestorer {
+ public:
+ ScopedAttributesRestorer(const base::FilePath& path, int mode)
+ : path_(path), mode_(mode) {}
+ ~ScopedAttributesRestorer() { ChangeAttr(path_, mode_); }
+
+ private:
+ base::FilePath path_;
+ int mode_;
+};
+
+TEST_F(ServiceProcessStateFileManipulationTest, VerifyLaunchD) {
+ // There have been problems where launchd has gotten into a bad state, usually
+ // because something had deleted all the files in /tmp. launchd depends on
+ // a Unix Domain Socket that it creates at /tmp/launchd*/sock.
+ // The symptom of this problem is that the service process connect fails
+ // on Mac and "launch_msg(): Socket is not connected" appears.
+ // This test is designed to make sure that launchd is working.
+ // http://crbug/75518
+ // Note: This particular problem no longer affects launchd in 10.10+, since
+ // there is no user owned launchd process and sockets are no longer made at
+ // /tmp/launchd*/sock. This test is still useful as a sanity check to make
+ // sure that launchd appears to be working.
+
+ base::CommandLine cl(base::FilePath("/bin/launchctl"));
+ cl.AppendArg("limit");
+
+ std::string output;
+ int exit_code = -1;
+ ASSERT_TRUE(base::GetAppOutputWithExitCode(cl, &output, &exit_code) &&
+ exit_code == 0)
+ << " exit_code:" << exit_code << " " << output;
+}
+
+// Flaky: https://crbug.com/903823
+TEST_F(ServiceProcessStateFileManipulationTest, DISABLED_DeleteFile) {
+ GetIOTaskRunner()->PostTask(FROM_HERE,
+ base::BindOnce(&DeleteFunc, executable_path()));
+ Run();
+ ASSERT_TRUE(mock_launchd()->remove_called());
+ ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) {
+ GetIOTaskRunner()->PostTask(FROM_HERE,
+ base::BindOnce(&DeleteFunc, bundle_path()));
+ Run();
+ ASSERT_TRUE(mock_launchd()->remove_called());
+ ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) {
+ base::FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle");
+ GetIOTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&MoveFunc, bundle_path(), new_loc));
+ Run();
+ ASSERT_TRUE(mock_launchd()->restart_called());
+ ASSERT_TRUE(mock_launchd()->write_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) {
+ base::FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile");
+ GetIOTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&MoveFunc, executable_path(), new_loc));
+ Run();
+ ASSERT_TRUE(mock_launchd()->remove_called());
+ ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, TrashBundle) {
+ GetIOTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ServiceProcessStateFileManipulationTest::TrashFunc,
+ base::Unretained(this), bundle_path()));
+ Run();
+ ASSERT_TRUE(mock_launchd()->remove_called());
+ ASSERT_TRUE(mock_launchd()->delete_called());
+ std::string path(base::SysNSStringToUTF8([trashed_url_ path]));
+ base::FilePath file_path(path);
+ ASSERT_TRUE(base::DeleteFile(file_path, true));
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) {
+ ScopedAttributesRestorer restorer(bundle_path(), 0777);
+ GetIOTaskRunner()->PostTask(FROM_HERE,
+ base::BindOnce(&ChangeAttr, bundle_path(), 0222));
+ Run();
+ ASSERT_TRUE(mock_launchd()->remove_called());
+ ASSERT_TRUE(mock_launchd()->delete_called());
+}
diff --git a/chromium/chrome/common/service_process_util_posix.cc b/chromium/chrome/common/service_process_util_posix.cc
new file mode 100644
index 00000000000..698cf49c19c
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_posix.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/service_process_util_posix.h"
+
+#include <fcntl.h>
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/synchronization/waitable_event.h"
+#include "build/branding_buildflags.h"
+#include "chrome/common/multi_process_lock.h"
+
+#if defined(OS_ANDROID)
+#error "Should not be built on android"
+#endif
+
+namespace {
+int g_signal_socket = -1;
+
+#if !defined(OS_MACOSX)
+
+bool FilePathForMemoryName(const std::string& mem_name, base::FilePath* path) {
+ // mem_name will be used for a filename; make sure it doesn't
+ // contain anything which will confuse us.
+ DCHECK_EQ(std::string::npos, mem_name.find('/'));
+ DCHECK_EQ(std::string::npos, mem_name.find('\0'));
+
+ base::FilePath temp_dir;
+ if (!GetShmemTempDir(false, &temp_dir))
+ return false;
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ static const char kShmem[] = "com.google.Chrome.shmem.";
+#else
+ static const char kShmem[] = "org.chromium.Chromium.shmem.";
+#endif
+ *path = temp_dir.AppendASCII(kShmem + mem_name);
+ return true;
+}
+
+#endif // !defined(OS_MACOSX)
+
+} // namespace
+
+#if !defined(OS_MACOSX)
+
+// static
+base::WritableSharedMemoryRegion
+ServiceProcessState::CreateServiceProcessDataRegion(size_t size) {
+ base::FilePath path;
+
+ if (!FilePathForMemoryName(GetServiceProcessSharedMemName(), &path))
+ return {};
+
+ // Make sure that the file is opened without any permission
+ // to other users on the system.
+ const mode_t kOwnerOnly = S_IRUSR | S_IWUSR;
+
+ bool fix_size = true;
+
+ // First, try to create the file.
+ base::ScopedFD fd(HANDLE_EINTR(
+ open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly)));
+ if (!fd.is_valid()) {
+ // If this doesn't work, try and open an existing file in append mode.
+ // Opening an existing file in a world writable directory has two main
+ // security implications:
+ // - Attackers could plant a file under their control, so ownership of
+ // the file is checked below.
+ // - Attackers could plant a symbolic link so that an unexpected file
+ // is opened, so O_NOFOLLOW is passed to open().
+#if !defined(OS_AIX)
+ fd.reset(HANDLE_EINTR(
+ open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW)));
+#else
+ // AIX has no 64-bit support for open flags such as -
+ // O_CLOEXEC, O_NOFOLLOW and O_TTY_INIT.
+ fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDWR | O_APPEND)));
+#endif
+ // Check that the current user owns the file.
+ // If uid != euid, then a more complex permission model is used and this
+ // API is not appropriate.
+ const uid_t real_uid = getuid();
+ const uid_t effective_uid = geteuid();
+ struct stat sb;
+ if (fd.is_valid() && (fstat(fd.get(), &sb) != 0 || sb.st_uid != real_uid ||
+ sb.st_uid != effective_uid)) {
+ DLOG(ERROR) << "Invalid owner when opening existing shared memory file.";
+ return {};
+ }
+
+ // An existing file was opened, so its size should not be fixed.
+ fix_size = false;
+ }
+
+ if (fd.is_valid() && fix_size) {
+ // Get current size.
+ struct stat stat;
+ if (fstat(fd.get(), &stat) != 0)
+ return {};
+ const size_t current_size = stat.st_size;
+ if (current_size != size) {
+ if (HANDLE_EINTR(ftruncate(fd.get(), size)) != 0)
+ return {};
+ }
+ }
+
+ // Everything has worked out so far, so open a read-only handle to the region
+ // in order to be able to create a writable region (which needs a read-only
+ // handle in order to convert to a read-only region.
+ base::ScopedFD read_only_fd(
+ HANDLE_EINTR(open(path.value().c_str(), O_RDONLY, kOwnerOnly)));
+ if (!read_only_fd.is_valid()) {
+ DPLOG(ERROR) << "Could not reopen shared memory region as read-only";
+ return {};
+ }
+
+ base::WritableSharedMemoryRegion writable_region =
+ base::WritableSharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ base::subtle::ScopedFDPair(std::move(fd),
+ std::move(read_only_fd)),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable, size,
+ base::UnguessableToken::Create()));
+ if (!writable_region.IsValid()) {
+ DLOG(ERROR) << "Could not deserialize named region";
+ return {};
+ }
+ return writable_region;
+}
+
+// static
+base::ReadOnlySharedMemoryMapping
+ServiceProcessState::OpenServiceProcessDataMapping(size_t size) {
+ base::FilePath path;
+ if (!FilePathForMemoryName(GetServiceProcessSharedMemName(), &path))
+ return {};
+
+ base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
+ if (!fd.is_valid()) {
+ DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
+ return {};
+ }
+ return base::ReadOnlySharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ base::subtle::ScopedFDPair(std::move(fd), base::ScopedFD()),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
+ size, base::UnguessableToken::Create()))
+ .Map();
+}
+
+// static
+bool ServiceProcessState::DeleteServiceProcessDataRegion() {
+ base::FilePath path;
+ if (!FilePathForMemoryName(GetServiceProcessSharedMemName(), &path))
+ return false;
+
+ if (PathExists(path))
+ return DeleteFile(path, false);
+
+ // Doesn't exist, so success.
+ return true;
+}
+
+#endif // !defined(OS_MACOSX)
+
+// Attempts to take a lock named |name|. If |waiting| is true then this will
+// make multiple attempts to acquire the lock.
+// Caller is responsible for ownership of the MultiProcessLock.
+MultiProcessLock* TakeNamedLock(const std::string& name, bool waiting) {
+ std::unique_ptr<MultiProcessLock> lock = MultiProcessLock::Create(name);
+ if (lock == NULL) return NULL;
+ bool got_lock = false;
+ for (int i = 0; i < 10; ++i) {
+ if (lock->TryLock()) {
+ got_lock = true;
+ break;
+ }
+ if (!waiting) break;
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100 * i));
+ }
+ if (!got_lock) {
+ lock.reset();
+ }
+ return lock.release();
+}
+
+ServiceProcessTerminateMonitor::ServiceProcessTerminateMonitor(
+ const base::Closure& terminate_task)
+ : terminate_task_(terminate_task) {
+}
+
+ServiceProcessTerminateMonitor::~ServiceProcessTerminateMonitor() {
+}
+
+void ServiceProcessTerminateMonitor::OnFileCanReadWithoutBlocking(int fd) {
+ if (!terminate_task_.is_null()) {
+ int buffer;
+ int length = read(fd, &buffer, sizeof(buffer));
+ if ((length == sizeof(buffer)) && (buffer == kTerminateMessage)) {
+ terminate_task_.Run();
+ terminate_task_.Reset();
+ } else if (length > 0) {
+ DLOG(ERROR) << "Unexpected read: " << buffer;
+ } else if (length == 0) {
+ DLOG(ERROR) << "Unexpected fd close";
+ } else if (length < 0) {
+ DPLOG(ERROR) << "read";
+ }
+ }
+}
+
+void ServiceProcessTerminateMonitor::OnFileCanWriteWithoutBlocking(int fd) {
+ NOTIMPLEMENTED();
+}
+
+// "Forced" Shutdowns on POSIX are done via signals. The magic signal for
+// a shutdown is SIGTERM. "write" is a signal safe function. PLOG(ERROR) is
+// not, but we don't ever expect it to be called.
+static void SigTermHandler(int sig, siginfo_t* info, void* uap) {
+ // TODO(dmaclach): add security here to make sure that we are being shut
+ // down by an appropriate process.
+ int message = ServiceProcessTerminateMonitor::kTerminateMessage;
+ if (write(g_signal_socket, &message, sizeof(message)) < 0) {
+ DPLOG(ERROR) << "write";
+ }
+}
+
+ServiceProcessState::StateData::StateData()
+ : watcher(FROM_HERE), set_action(false) {
+ memset(sockets, -1, sizeof(sockets));
+ memset(&old_action, 0, sizeof(old_action));
+}
+
+void ServiceProcessState::StateData::SignalReady(base::WaitableEvent* signal,
+ bool* success) {
+ DCHECK(task_runner->BelongsToCurrentThread());
+ DCHECK_EQ(g_signal_socket, -1);
+ DCHECK(!signal->IsSignaled());
+ *success = base::MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ sockets[0], true, base::MessagePumpForIO::WATCH_READ, &watcher,
+ terminate_monitor.get());
+ if (!*success) {
+ DLOG(ERROR) << "WatchFileDescriptor";
+ signal->Signal();
+ return;
+ }
+ g_signal_socket = sockets[1];
+
+ // Set up signal handler for SIGTERM.
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ action.sa_sigaction = SigTermHandler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = SA_SIGINFO;
+ *success = sigaction(SIGTERM, &action, &old_action) == 0;
+ if (!*success) {
+ DPLOG(ERROR) << "sigaction";
+ signal->Signal();
+ return;
+ }
+
+ // If the old_action is not default, somebody else has installed a
+ // a competing handler. Our handler is going to override it so it
+ // won't be called. If this occurs it needs to be fixed.
+ DCHECK_EQ(old_action.sa_handler, SIG_DFL);
+ set_action = true;
+
+#if defined(OS_MACOSX)
+ *success = WatchExecutable();
+ if (!*success) {
+ DLOG(ERROR) << "WatchExecutable";
+ signal->Signal();
+ return;
+ }
+#elif defined(OS_POSIX)
+ initializing_lock.reset();
+#endif // OS_POSIX
+ signal->Signal();
+}
+
+ServiceProcessState::StateData::~StateData() {
+ // StateData is destroyed on the thread that called SignalReady() (if any) to
+ // satisfy the requirement that base::FilePathWatcher is destroyed in sequence
+ // with base::FilePathWatcher::Watch().
+ DCHECK(!task_runner || task_runner->BelongsToCurrentThread());
+
+ // Cancel any pending file-descriptor watch before closing the descriptor.
+ watcher.StopWatchingFileDescriptor();
+
+ if (sockets[0] != -1) {
+ if (IGNORE_EINTR(close(sockets[0]))) {
+ DPLOG(ERROR) << "close";
+ }
+ }
+ if (sockets[1] != -1) {
+ if (IGNORE_EINTR(close(sockets[1]))) {
+ DPLOG(ERROR) << "close";
+ }
+ }
+ if (set_action) {
+ if (sigaction(SIGTERM, &old_action, NULL) < 0) {
+ DPLOG(ERROR) << "sigaction";
+ }
+ }
+ g_signal_socket = -1;
+}
+
+void ServiceProcessState::CreateState() {
+ DCHECK(!state_);
+ state_ = new StateData();
+}
+
+bool ServiceProcessState::SignalReady(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ const base::Closure& terminate_task) {
+ DCHECK(task_runner);
+ DCHECK(state_);
+
+#if !defined(OS_MACOSX)
+ state_->running_lock.reset(TakeServiceRunningLock(true));
+ if (state_->running_lock.get() == NULL) {
+ return false;
+ }
+#endif
+ state_->terminate_monitor.reset(
+ new ServiceProcessTerminateMonitor(terminate_task));
+ if (pipe(state_->sockets) < 0) {
+ DPLOG(ERROR) << "pipe";
+ return false;
+ }
+ base::WaitableEvent signal_ready(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ bool success = false;
+
+ state_->task_runner = std::move(task_runner);
+ state_->task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ServiceProcessState::StateData::SignalReady,
+ base::Unretained(state_), &signal_ready, &success));
+ signal_ready.Wait();
+ return success;
+}
+
+void ServiceProcessState::TearDownState() {
+ if (state_ && state_->task_runner)
+ state_->task_runner->DeleteSoon(FROM_HERE, state_);
+ else
+ delete state_;
+ state_ = nullptr;
+}
diff --git a/chromium/chrome/common/service_process_util_posix.h b/chromium/chrome/common/service_process_util_posix.h
new file mode 100644
index 00000000000..bcba5bfe7a0
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_posix.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SERVICE_PROCESS_UTIL_POSIX_H_
+#define CHROME_COMMON_SERVICE_PROCESS_UTIL_POSIX_H_
+
+#include "chrome/common/service_process_util.h"
+
+#include <signal.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/single_thread_task_runner.h"
+#include "build/build_config.h"
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+#include "chrome/common/multi_process_lock.h"
+MultiProcessLock* TakeServiceRunningLock(bool waiting);
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/files/file_path_watcher.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "chrome/common/mac/service_management.h"
+
+namespace base {
+class CommandLine;
+}
+
+mac::services::JobOptions GetServiceProcessJobOptions(
+ base::CommandLine* cmd_line,
+ bool for_auto_launch);
+#endif // OS_MACOSX
+
+namespace base {
+class WaitableEvent;
+}
+
+// Watches for |kTerminateMessage| to be written to the file descriptor it is
+// watching. When it reads |kTerminateMessage|, it performs |terminate_task_|.
+// Used here to monitor the socket listening to g_signal_socket.
+class ServiceProcessTerminateMonitor
+ : public base::MessagePumpForIO::FdWatcher {
+ public:
+
+ enum {
+ kTerminateMessage = 0xdecea5e
+ };
+
+ explicit ServiceProcessTerminateMonitor(const base::Closure& terminate_task);
+ ~ServiceProcessTerminateMonitor() override;
+
+ // MessagePumpForIO::FdWatcher overrides
+ void OnFileCanReadWithoutBlocking(int fd) override;
+ void OnFileCanWriteWithoutBlocking(int fd) override;
+
+ private:
+ base::Closure terminate_task_;
+};
+
+struct ServiceProcessState::StateData {
+ StateData();
+ ~StateData();
+
+ // WatchFileDescriptor needs to be set up by the thread that is going
+ // to be monitoring it.
+ void SignalReady(base::WaitableEvent* signal, bool* success);
+
+#if defined(OS_MACOSX)
+ bool WatchExecutable();
+
+ mac::services::JobCheckinInfo job_info;
+ base::FilePathWatcher executable_watcher;
+#else
+ std::unique_ptr<MultiProcessLock> initializing_lock;
+ std::unique_ptr<MultiProcessLock> running_lock;
+#endif
+ std::unique_ptr<ServiceProcessTerminateMonitor> terminate_monitor;
+ base::MessagePumpForIO::FdWatchController watcher;
+ int sockets[2];
+ struct sigaction old_action;
+ bool set_action;
+
+ // The SingleThreadTaskRunner on which SignalReady and the destructor are
+ // invoked.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner;
+};
+
+#endif // CHROME_COMMON_SERVICE_PROCESS_UTIL_POSIX_H_
diff --git a/chromium/chrome/common/service_process_util_unittest.cc b/chromium/chrome/common/service_process_util_unittest.cc
new file mode 100644
index 00000000000..99ccc2c2476
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/service_process_util.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_split.h"
+#include "base/test/task_environment.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+
+#if !defined(OS_MACOSX)
+#include "base/at_exit.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_switches.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_WIN)
+#include "base/win/win_util.h"
+#endif
+
+#if defined(OS_POSIX)
+#include "chrome/common/auto_start_linux.h"
+#endif
+
+#if defined(USE_AURA)
+// This test fails http://crbug.com/84854, and is very flaky on CrOS and
+// somewhat flaky on other Linux.
+#define MAYBE_ForceShutdown DISABLED_ForceShutdown
+#else
+#if defined(OS_LINUX) || defined(OS_WIN)
+#define MAYBE_ForceShutdown DISABLED_ForceShutdown
+#else
+#define MAYBE_ForceShutdown ForceShutdown
+#endif
+#endif
+
+namespace {
+
+bool g_good_shutdown = false;
+
+void ShutdownTask(base::RunLoop* loop) {
+ // Quit the main message loop.
+ ASSERT_FALSE(g_good_shutdown);
+ g_good_shutdown = true;
+ loop->QuitWhenIdle();
+}
+
+} // namespace
+
+TEST(ServiceProcessUtilTest, ScopedVersionedName) {
+ std::string test_str = "test";
+ std::string scoped_name = GetServiceProcessScopedVersionedName(test_str);
+ EXPECT_TRUE(base::EndsWith(scoped_name, test_str,
+ base::CompareCase::SENSITIVE));
+ EXPECT_NE(std::string::npos,
+ scoped_name.find(version_info::GetVersionNumber()));
+}
+
+class ServiceProcessStateTest : public base::MultiProcessTest {
+ public:
+ ServiceProcessStateTest();
+ ~ServiceProcessStateTest() override;
+ void SetUp() override;
+ base::SingleThreadTaskRunner* IOTaskRunner() {
+ return io_thread_.task_runner().get();
+ }
+ void LaunchAndWait(const std::string& name);
+
+ private:
+ // This is used to release the ServiceProcessState singleton after each test.
+ base::ShadowingAtExitManager at_exit_manager_;
+ base::Thread io_thread_;
+};
+
+ServiceProcessStateTest::ServiceProcessStateTest()
+ : io_thread_("ServiceProcessStateTestThread") {
+}
+
+ServiceProcessStateTest::~ServiceProcessStateTest() {
+}
+
+void ServiceProcessStateTest::SetUp() {
+ base::Thread::Options options(base::MessagePumpType::IO, 0);
+ ASSERT_TRUE(io_thread_.StartWithOptions(options));
+}
+
+void ServiceProcessStateTest::LaunchAndWait(const std::string& name) {
+ base::Process process = SpawnChild(name);
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = 0;
+ ASSERT_TRUE(process.WaitForExit(&exit_code));
+ ASSERT_EQ(exit_code, 0);
+}
+
+TEST_F(ServiceProcessStateTest, Singleton) {
+ ServiceProcessState state;
+ ASSERT_TRUE(state.Initialize());
+ LaunchAndWait("ServiceProcessStateTestSingleton");
+}
+
+// http://crbug.com/396390
+TEST_F(ServiceProcessStateTest, DISABLED_ReadyState) {
+ ASSERT_FALSE(CheckServiceProcessReady());
+ ServiceProcessState state;
+ ASSERT_TRUE(state.Initialize());
+ ASSERT_TRUE(state.SignalReady(IOTaskRunner(), base::Closure()));
+ LaunchAndWait("ServiceProcessStateTestReadyTrue");
+ state.SignalStopped();
+ LaunchAndWait("ServiceProcessStateTestReadyFalse");
+}
+
+TEST_F(ServiceProcessStateTest, AutoRun) {
+ ServiceProcessState state;
+ ASSERT_TRUE(state.AddToAutoRun());
+ std::unique_ptr<base::CommandLine> autorun_command_line;
+#if defined(OS_WIN)
+ std::string value_name = GetServiceProcessScopedName("_service_run");
+ base::string16 value;
+ EXPECT_TRUE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
+ base::UTF8ToWide(value_name),
+ &value));
+ autorun_command_line.reset(
+ new base::CommandLine(base::CommandLine::FromString(value)));
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ std::string base_desktop_name = "google-chrome-service.desktop";
+#else // BUILDFLAG(CHROMIUM_BRANDING)
+ std::string base_desktop_name = "chromium-service.desktop";
+#endif
+ std::string exec_value;
+ EXPECT_TRUE(AutoStart::GetAutostartFileValue(
+ GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
+
+ // Make sure |exec_value| doesn't contain strings a shell would
+ // treat specially.
+ ASSERT_EQ(std::string::npos, exec_value.find('#'));
+ ASSERT_EQ(std::string::npos, exec_value.find('\n'));
+ ASSERT_EQ(std::string::npos, exec_value.find('"'));
+ ASSERT_EQ(std::string::npos, exec_value.find('\''));
+
+ base::CommandLine::StringVector argv = base::SplitString(
+ exec_value, base::CommandLine::StringType(1, ' '),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ ASSERT_GE(argv.size(), 2U)
+ << "Expected at least one command-line option in: " << exec_value;
+ autorun_command_line.reset(new base::CommandLine(argv));
+#endif // defined(OS_WIN)
+ if (autorun_command_line.get()) {
+ EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType),
+ std::string(switches::kCloudPrintServiceProcess));
+ }
+ ASSERT_TRUE(state.RemoveFromAutoRun());
+#if defined(OS_WIN)
+ EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
+ base::UTF8ToWide(value_name),
+ &value));
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
+ EXPECT_FALSE(AutoStart::GetAutostartFileValue(
+ GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
+#endif // defined(OS_WIN)
+}
+
+TEST_F(ServiceProcessStateTest, SharedMem) {
+ std::string version;
+ base::ProcessId pid;
+#if defined(OS_POSIX)
+ // On Posix, named shared memory uses a file on disk. This file could be lying
+ // around from previous crashes which could cause GetServiceProcessPid to lie,
+ // so we aggressively delete it before testing. On Windows, we use a named
+ // event so we don't have this issue.
+ ServiceProcessState::DeleteServiceProcessDataRegion();
+#endif // defined(OS_POSIX)
+ ASSERT_FALSE(ServiceProcessState::GetServiceProcessData(&version, &pid));
+ ServiceProcessState state;
+ ASSERT_TRUE(state.Initialize());
+ ASSERT_TRUE(ServiceProcessState::GetServiceProcessData(&version, &pid));
+ ASSERT_EQ(base::GetCurrentProcId(), pid);
+}
+
+TEST_F(ServiceProcessStateTest, MAYBE_ForceShutdown) {
+ base::Process process = SpawnChild("ServiceProcessStateTestShutdown");
+ ASSERT_TRUE(process.IsValid());
+ for (int i = 0; !CheckServiceProcessReady() && i < 10; ++i) {
+ base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ }
+ ASSERT_TRUE(CheckServiceProcessReady());
+ std::string version;
+ base::ProcessId pid;
+ ASSERT_TRUE(ServiceProcessState::GetServiceProcessData(&version, &pid));
+ ASSERT_TRUE(ForceServiceProcessShutdown(version, pid));
+ int exit_code = 0;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ ASSERT_EQ(exit_code, 0);
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) {
+ ServiceProcessState state;
+ EXPECT_FALSE(state.Initialize());
+ return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) {
+ EXPECT_TRUE(CheckServiceProcessReady());
+ return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) {
+ EXPECT_FALSE(CheckServiceProcessReady());
+ return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) {
+ base::PlatformThread::SetName("ServiceProcessStateTestShutdownMainThread");
+ base::test::SingleThreadTaskEnvironment task_environment;
+ base::RunLoop run_loop;
+ base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread");
+ base::Thread::Options options(base::MessagePumpType::IO, 0);
+ EXPECT_TRUE(io_thread_.StartWithOptions(options));
+ ServiceProcessState state;
+ EXPECT_TRUE(state.Initialize());
+ EXPECT_TRUE(state.SignalReady(io_thread_.task_runner().get(),
+ base::Bind(&ShutdownTask, &run_loop)));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitWhenIdleClosure(),
+ TestTimeouts::action_max_timeout());
+ EXPECT_FALSE(g_good_shutdown);
+ run_loop.Run();
+ EXPECT_TRUE(g_good_shutdown);
+ return 0;
+}
+
+#endif // !OS_MACOSX
diff --git a/chromium/chrome/common/service_process_util_win.cc b/chromium/chrome/common/service_process_util_win.cc
new file mode 100644
index 00000000000..47302608826
--- /dev/null
+++ b/chromium/chrome/common/service_process_util_win.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/service_process_util.h"
+
+#include <windows.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/unguessable_token.h"
+#include "base/win/object_watcher.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace {
+
+const char kTerminateEventSuffix[] = "_service_terminate_evt";
+
+base::string16 GetServiceProcessReadyEventName() {
+ return base::UTF8ToWide(
+ GetServiceProcessScopedVersionedName("_service_ready"));
+}
+
+base::string16 GetServiceProcessTerminateEventName() {
+ return base::UTF8ToWide(
+ GetServiceProcessScopedVersionedName(kTerminateEventSuffix));
+}
+
+std::string GetServiceProcessAutoRunKey() {
+ return GetServiceProcessScopedName("_service_run");
+}
+
+// Returns the name of the autotun reg value that we used to use for older
+// versions of Chrome.
+std::string GetObsoleteServiceProcessAutoRunKey() {
+ base::FilePath user_data_dir;
+ base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+ std::string scoped_name = base::WideToUTF8(user_data_dir.value());
+ std::replace(scoped_name.begin(), scoped_name.end(), '\\', '!');
+ std::replace(scoped_name.begin(), scoped_name.end(), '/', '!');
+ scoped_name.append("_service_run");
+ return scoped_name;
+}
+
+class ServiceProcessTerminateMonitor
+ : public base::win::ObjectWatcher::Delegate {
+ public:
+ explicit ServiceProcessTerminateMonitor(const base::Closure& terminate_task)
+ : terminate_task_(terminate_task) {
+ }
+ void Start() {
+ base::string16 event_name = GetServiceProcessTerminateEventName();
+ DCHECK(event_name.length() <= MAX_PATH);
+ terminate_event_.Set(CreateEvent(NULL, TRUE, FALSE, event_name.c_str()));
+ watcher_.StartWatchingOnce(terminate_event_.Get(), this);
+ }
+
+ // base::ObjectWatcher::Delegate implementation.
+ void OnObjectSignaled(HANDLE object) override {
+ if (!terminate_task_.is_null()) {
+ terminate_task_.Run();
+ terminate_task_.Reset();
+ }
+ }
+
+ private:
+ base::win::ScopedHandle terminate_event_;
+ base::win::ObjectWatcher watcher_;
+ base::Closure terminate_task_;
+};
+
+} // namespace
+
+// Gets the name of the service process IPC channel.
+mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName() {
+ return mojo::NamedPlatformChannel::ServerNameFromUTF8(
+ GetServiceProcessScopedVersionedName("_service_ipc"));
+}
+
+bool ForceServiceProcessShutdown(const std::string& version,
+ base::ProcessId process_id) {
+ base::win::ScopedHandle terminate_event;
+ std::string versioned_name = version;
+ versioned_name.append(kTerminateEventSuffix);
+ base::string16 event_name =
+ base::UTF8ToWide(GetServiceProcessScopedName(versioned_name));
+ terminate_event.Set(OpenEvent(EVENT_MODIFY_STATE, FALSE, event_name.c_str()));
+ if (!terminate_event.IsValid())
+ return false;
+ SetEvent(terminate_event.Get());
+ return true;
+}
+
+// static
+base::WritableSharedMemoryRegion
+ServiceProcessState::CreateServiceProcessDataRegion(size_t size) {
+ // Check maximum accounting for overflow.
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ base::string16 name = base::ASCIIToUTF16(GetServiceProcessSharedMemName());
+
+ SECURITY_ATTRIBUTES sa = {sizeof(sa), nullptr, FALSE};
+ HANDLE raw_handle =
+ CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0,
+ static_cast<DWORD>(size), base::as_wcstr(name));
+ if (!raw_handle) {
+ auto error = GetLastError();
+ DLOG(ERROR) << "Cannot create named mapping " << name << ": " << error;
+ return {};
+ }
+ base::win::ScopedHandle handle(raw_handle);
+
+ base::WritableSharedMemoryRegion writable_region =
+ base::WritableSharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable, size,
+ base::UnguessableToken::Create()));
+ if (!writable_region.IsValid()) {
+ DLOG(ERROR) << "Cannot deserialize file mapping";
+ return {};
+ }
+ return writable_region;
+}
+
+// static
+base::ReadOnlySharedMemoryMapping
+ServiceProcessState::OpenServiceProcessDataMapping(size_t size) {
+ DWORD access = FILE_MAP_READ | SECTION_QUERY;
+ base::string16 name = base::ASCIIToUTF16(GetServiceProcessSharedMemName());
+ HANDLE raw_handle = OpenFileMapping(access, false, base::as_wcstr(name));
+ if (!raw_handle) {
+ auto err = GetLastError();
+ DLOG(ERROR) << "OpenFileMapping failed for " << name << " / "
+ << GetServiceProcessSharedMemName() << " / " << err;
+ return {};
+ }
+
+ // The region is writable for this user, so the handle is converted to a
+ // WritableSharedMemoryMapping which is then downgraded to read-only for the
+ // mapping.
+ base::WritableSharedMemoryRegion writable_region =
+ base::WritableSharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ base::win::ScopedHandle(raw_handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable, size,
+ base::UnguessableToken::Create()));
+ if (!writable_region.IsValid()) {
+ DLOG(ERROR) << "Unable to deserialize raw file mapping handle to "
+ << "WritableSharedMemoryRegion";
+ return {};
+ }
+ base::ReadOnlySharedMemoryRegion readonly_region =
+ base::WritableSharedMemoryRegion::ConvertToReadOnly(
+ std::move(writable_region));
+ if (!readonly_region.IsValid()) {
+ DLOG(ERROR) << "Unable to convert to read-only region";
+ return {};
+ }
+ base::ReadOnlySharedMemoryMapping mapping = readonly_region.Map();
+ if (!mapping.IsValid()) {
+ DLOG(ERROR) << "Unable to map region";
+ return {};
+ }
+ // The region will be closed on return, leaving on the mapping.
+ return mapping;
+}
+
+// static
+bool ServiceProcessState::DeleteServiceProcessDataRegion() {
+ // intentionally empty -- there is nothing for us to do on Windows.
+ return true;
+}
+
+bool CheckServiceProcessReady() {
+ base::string16 event_name = GetServiceProcessReadyEventName();
+ base::win::ScopedHandle event(
+ OpenEvent(SYNCHRONIZE | READ_CONTROL, false, event_name.c_str()));
+ if (!event.IsValid())
+ return false;
+ // Check if the event is signaled.
+ return WaitForSingleObject(event.Get(), 0) == WAIT_OBJECT_0;
+}
+
+struct ServiceProcessState::StateData {
+ // An event that is signaled when a service process is ready.
+ base::win::ScopedHandle ready_event;
+ std::unique_ptr<ServiceProcessTerminateMonitor> terminate_monitor;
+};
+
+void ServiceProcessState::CreateState() {
+ DCHECK(!state_);
+ state_ = new StateData;
+}
+
+bool ServiceProcessState::TakeSingletonLock() {
+ DCHECK(state_);
+ base::string16 event_name = GetServiceProcessReadyEventName();
+ DCHECK(event_name.length() <= MAX_PATH);
+ base::win::ScopedHandle service_process_ready_event;
+ service_process_ready_event.Set(
+ CreateEvent(NULL, TRUE, FALSE, event_name.c_str()));
+ DWORD error = GetLastError();
+ if ((error == ERROR_ALREADY_EXISTS) || (error == ERROR_ACCESS_DENIED))
+ return false;
+ DCHECK(service_process_ready_event.IsValid());
+ state_->ready_event.Set(service_process_ready_event.Take());
+ return true;
+}
+
+bool ServiceProcessState::SignalReady(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ const base::Closure& terminate_task) {
+ DCHECK(state_);
+ DCHECK(state_->ready_event.IsValid());
+ if (!SetEvent(state_->ready_event.Get())) {
+ return false;
+ }
+ if (!terminate_task.is_null()) {
+ state_->terminate_monitor.reset(
+ new ServiceProcessTerminateMonitor(terminate_task));
+ state_->terminate_monitor->Start();
+ }
+ return true;
+}
+
+bool ServiceProcessState::AddToAutoRun() {
+ DCHECK(autorun_command_line_.get());
+ // Remove the old autorun value first because we changed the naming scheme
+ // for the autorun value name.
+ base::win::RemoveCommandFromAutoRun(
+ HKEY_CURRENT_USER,
+ base::UTF8ToWide(GetObsoleteServiceProcessAutoRunKey()));
+ return base::win::AddCommandToAutoRun(
+ HKEY_CURRENT_USER,
+ base::UTF8ToWide(GetServiceProcessAutoRunKey()),
+ autorun_command_line_->GetCommandLineString());
+}
+
+bool ServiceProcessState::RemoveFromAutoRun() {
+ // Remove the old autorun value first because we changed the naming scheme
+ // for the autorun value name.
+ base::win::RemoveCommandFromAutoRun(
+ HKEY_CURRENT_USER,
+ base::UTF8ToWide(GetObsoleteServiceProcessAutoRunKey()));
+ return base::win::RemoveCommandFromAutoRun(
+ HKEY_CURRENT_USER, base::UTF8ToWide(GetServiceProcessAutoRunKey()));
+}
+
+void ServiceProcessState::TearDownState() {
+ delete state_;
+ state_ = NULL;
+}
diff --git a/chromium/chrome/common/ssl_insecure_content.cc b/chromium/chrome/common/ssl_insecure_content.cc
new file mode 100644
index 00000000000..d8c91c930e2
--- /dev/null
+++ b/chromium/chrome/common/ssl_insecure_content.cc
@@ -0,0 +1,45 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/ssl_insecure_content.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Constants for UMA statistic collection.
+static const char kDotJS[] = ".js";
+static const char kDotCSS[] = ".css";
+static const char kDotSWF[] = ".swf";
+static const char kDotHTML[] = ".html";
+
+} // namespace
+
+void ReportInsecureContent(SslInsecureContentType signal) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SSL.InsecureContent", static_cast<int>(signal),
+ static_cast<int>(SslInsecureContentType::NUM_EVENTS));
+}
+
+void FilteredReportInsecureContentDisplayed(const GURL& resource_gurl) {
+ if (base::EndsWith(resource_gurl.path(), kDotHTML,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ ReportInsecureContent(SslInsecureContentType::DISPLAY_HTML);
+ }
+}
+
+void FilteredReportInsecureContentRan(const GURL& resource_gurl) {
+ if (base::EndsWith(resource_gurl.path(), kDotJS,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ ReportInsecureContent(SslInsecureContentType::RUN_JS);
+ } else if (base::EndsWith(resource_gurl.path(), kDotCSS,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ ReportInsecureContent(SslInsecureContentType::RUN_CSS);
+ } else if (base::EndsWith(resource_gurl.path(), kDotSWF,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ ReportInsecureContent(SslInsecureContentType::RUN_SWF);
+ }
+}
diff --git a/chromium/chrome/common/ssl_insecure_content.h b/chromium/chrome/common/ssl_insecure_content.h
new file mode 100644
index 00000000000..45ce1597d03
--- /dev/null
+++ b/chromium/chrome/common/ssl_insecure_content.h
@@ -0,0 +1,61 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SSL_INSECURE_CONTENT_H_
+#define CHROME_COMMON_SSL_INSECURE_CONTENT_H_
+
+class GURL;
+
+// Insecure content types used in the SSL.InsecureContent histogram.
+// This enum is histogrammed, so do not add, reorder, or remove values.
+enum class SslInsecureContentType {
+ DISPLAY = 0,
+ DISPLAY_HOST_GOOGLE, // deprecated
+ DISPLAY_HOST_WWW_GOOGLE, // deprecated
+ DISPLAY_HTML,
+ RUN,
+ RUN_HOST_GOOGLE, // deprecated
+ RUN_HOST_WWW_GOOGLE, // deprecated
+ RUN_TARGET_YOUTUBE, // deprecated
+ RUN_JS,
+ RUN_CSS,
+ RUN_SWF,
+ DISPLAY_HOST_YOUTUBE, // deprecated
+ RUN_HOST_YOUTUBE, // deprecated
+ RUN_HOST_GOOGLEUSERCONTENT, // deprecated
+ DISPLAY_HOST_MAIL_GOOGLE, // deprecated
+ RUN_HOST_MAIL_GOOGLE, // deprecated
+ DISPLAY_HOST_PLUS_GOOGLE, // deprecated
+ RUN_HOST_PLUS_GOOGLE, // deprecated
+ DISPLAY_HOST_DOCS_GOOGLE, // deprecated
+ RUN_HOST_DOCS_GOOGLE, // deprecated
+ DISPLAY_HOST_SITES_GOOGLE, // deprecated
+ RUN_HOST_SITES_GOOGLE, // deprecated
+ DISPLAY_HOST_PICASAWEB_GOOGLE, // deprecated
+ RUN_HOST_PICASAWEB_GOOGLE, // deprecated
+ DISPLAY_HOST_GOOGLE_READER, // deprecated
+ RUN_HOST_GOOGLE_READER, // deprecated
+ DISPLAY_HOST_CODE_GOOGLE, // deprecated
+ RUN_HOST_CODE_GOOGLE, // deprecated
+ DISPLAY_HOST_GROUPS_GOOGLE, // deprecated
+ RUN_HOST_GROUPS_GOOGLE, // deprecated
+ DISPLAY_HOST_MAPS_GOOGLE, // deprecated
+ RUN_HOST_MAPS_GOOGLE, // deprecated
+ DISPLAY_HOST_GOOGLE_SUPPORT, // deprecated
+ RUN_HOST_GOOGLE_SUPPORT, // deprecated
+ DISPLAY_HOST_GOOGLE_INTL, // deprecated
+ RUN_HOST_GOOGLE_INTL, // deprecated
+ NUM_EVENTS
+};
+
+// Reports insecure content to the SSL.InsecureContent histogram using the
+// provided |signal|.
+void ReportInsecureContent(SslInsecureContentType signal);
+
+// Reports insecure content displayed or ran if |resource_URL| matches specific
+// file types.
+void FilteredReportInsecureContentDisplayed(const GURL& resource_gurl);
+void FilteredReportInsecureContentRan(const GURL& resource_gurl);
+
+#endif // CHROME_COMMON_SSL_INSECURE_CONTENT_H_
diff --git a/chromium/chrome/common/stack_sampling_configuration.cc b/chromium/chrome/common/stack_sampling_configuration.cc
new file mode 100644
index 00000000000..2d8c22e6b0d
--- /dev/null
+++ b/chromium/chrome/common/stack_sampling_configuration.cc
@@ -0,0 +1,219 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/stack_sampling_configuration.h"
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/rand_util.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/buildflags/buildflags.h"
+
+#if defined(OS_WIN)
+#include "base/win/static_constants.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/switches.h"
+#endif
+
+namespace {
+
+base::LazyInstance<StackSamplingConfiguration>::Leaky g_configuration =
+ LAZY_INSTANCE_INITIALIZER;
+
+// The profiler is currently only implemented for Windows x64 and Mac x64.
+bool IsProfilerSupported() {
+#if (defined(OS_WIN) && defined(ARCH_CPU_X86_64)) || defined(OS_MACOSX)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // Only run on canary and dev.
+ const version_info::Channel channel = chrome::GetChannel();
+ return channel == version_info::Channel::CANARY ||
+ channel == version_info::Channel::DEV;
+#else
+ return true;
+#endif
+#else
+ return false;
+#endif
+}
+
+// Returns true if the current execution is taking place in the browser process.
+bool IsBrowserProcess() {
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+ std::string process_type =
+ command_line->GetSwitchValueASCII(switches::kProcessType);
+ return process_type.empty();
+}
+
+// True if the command line corresponds to an extension renderer process.
+bool IsExtensionRenderer(const base::CommandLine& command_line) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return command_line.HasSwitch(extensions::switches::kExtensionProcess);
+#else
+ return false;
+#endif
+}
+
+bool ShouldEnableProfilerForNextRendererProcess() {
+ // Enable for every N-th renderer process, where N = 5.
+ return base::RandInt(0, 4) == 0;
+}
+
+} // namespace
+
+StackSamplingConfiguration::StackSamplingConfiguration()
+ : configuration_(GenerateConfiguration()) {
+}
+
+base::StackSamplingProfiler::SamplingParams
+StackSamplingConfiguration::GetSamplingParamsForCurrentProcess() const {
+ base::StackSamplingProfiler::SamplingParams params;
+ params.initial_delay = base::TimeDelta::FromMilliseconds(0);
+ params.sampling_interval = base::TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 0;
+
+ if (IsProfilerEnabledForCurrentProcess()) {
+ const base::TimeDelta duration = base::TimeDelta::FromSeconds(30);
+ params.sampling_interval = base::TimeDelta::FromMilliseconds(100);
+ params.samples_per_profile = duration / params.sampling_interval;
+ }
+
+ return params;
+}
+
+bool StackSamplingConfiguration::IsProfilerEnabledForCurrentProcess() const {
+ if (IsBrowserProcess()) {
+ return configuration_ == PROFILE_ENABLED ||
+ configuration_ == PROFILE_CONTROL;
+ }
+
+ DCHECK_EQ(PROFILE_FROM_COMMAND_LINE, configuration_);
+ // This is a child process. The |kStartStackProfiler| switch passed by the
+ // browser process determines whether the profiler is enabled for the process.
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+ return command_line->HasSwitch(switches::kStartStackProfiler);
+}
+
+bool StackSamplingConfiguration::GetSyntheticFieldTrial(
+ std::string* trial_name,
+ std::string* group_name) const {
+ DCHECK(IsBrowserProcess());
+
+ if (!IsProfilerSupported())
+ return false;
+
+ *trial_name = "SyntheticStackProfilingConfiguration";
+ *group_name = std::string();
+ switch (configuration_) {
+ case PROFILE_DISABLED:
+ *group_name = "Disabled";
+ break;
+
+ case PROFILE_CONTROL:
+ *group_name = "Control";
+ break;
+
+ case PROFILE_ENABLED:
+ *group_name = "Enabled";
+ break;
+
+ case PROFILE_FROM_COMMAND_LINE:
+ NOTREACHED();
+ break;
+ }
+
+ return !group_name->empty();
+}
+
+void StackSamplingConfiguration::AppendCommandLineSwitchForChildProcess(
+ const std::string& process_type,
+ base::CommandLine* command_line) const {
+ DCHECK(IsBrowserProcess());
+
+ bool enable =
+ configuration_ == PROFILE_ENABLED || configuration_ == PROFILE_CONTROL;
+ if (!enable)
+ return;
+ if (process_type == switches::kGpuProcess ||
+ (process_type == switches::kRendererProcess &&
+ // Do not start the profiler for extension processes since profiling the
+ // compositor thread in them is not useful.
+ !IsExtensionRenderer(*command_line) &&
+ ShouldEnableProfilerForNextRendererProcess())) {
+ command_line->AppendSwitch(switches::kStartStackProfiler);
+ }
+}
+
+// static
+StackSamplingConfiguration* StackSamplingConfiguration::Get() {
+ return g_configuration.Pointer();
+}
+
+// static
+StackSamplingConfiguration::ProfileConfiguration
+StackSamplingConfiguration::ChooseConfiguration(
+ const std::vector<Variation>& variations) {
+ int total_weight = 0;
+ for (const Variation& variation : variations)
+ total_weight += variation.weight;
+ DCHECK_EQ(100, total_weight);
+
+ int chosen = base::RandInt(0, total_weight - 1); // Max is inclusive.
+ int cumulative_weight = 0;
+ for (const auto& variation : variations) {
+ if (chosen >= cumulative_weight &&
+ chosen < cumulative_weight + variation.weight) {
+ return variation.config;
+ }
+ cumulative_weight += variation.weight;
+ }
+ NOTREACHED();
+ return PROFILE_DISABLED;
+}
+
+// static
+StackSamplingConfiguration::ProfileConfiguration
+StackSamplingConfiguration::GenerateConfiguration() {
+ if (!IsBrowserProcess())
+ return PROFILE_FROM_COMMAND_LINE;
+
+ if (!IsProfilerSupported())
+ return PROFILE_DISABLED;
+
+#if defined(OS_WIN)
+ // Do not start the profiler when Application Verifier is in use; running them
+ // simultaneously can cause crashes and has no known use case.
+ if (GetModuleHandleA(base::win::kApplicationVerifierDllName))
+ return PROFILE_DISABLED;
+#endif
+
+ switch (chrome::GetChannel()) {
+ // Enable the profiler unconditionally for development/waterfall builds.
+ case version_info::Channel::UNKNOWN:
+ return PROFILE_ENABLED;
+
+#if (defined(OS_WIN) && defined(ARCH_CPU_X86_64)) || defined(OS_MACOSX)
+ case version_info::Channel::CANARY:
+ case version_info::Channel::DEV:
+ return ChooseConfiguration({{PROFILE_ENABLED, 80},
+ {PROFILE_CONTROL, 10},
+ {PROFILE_DISABLED, 10}});
+#endif
+
+ default:
+ return PROFILE_DISABLED;
+ }
+}
diff --git a/chromium/chrome/common/stack_sampling_configuration.h b/chromium/chrome/common/stack_sampling_configuration.h
new file mode 100644
index 00000000000..8a51ae921db
--- /dev/null
+++ b/chromium/chrome/common/stack_sampling_configuration.h
@@ -0,0 +1,88 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_STACK_SAMPLING_CONFIGURATION_H_
+#define CHROME_COMMON_STACK_SAMPLING_CONFIGURATION_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/profiler/stack_sampling_profiler.h"
+
+namespace base {
+class CommandLine;
+} // namespace base
+
+// StackSamplingConfiguration chooses a configuration for the enable state of
+// the stack sampling profiler across all processes. This configuration is
+// determined once at browser process startup. Configurations for child
+// processes are communicated via command line arguments.
+class StackSamplingConfiguration {
+ public:
+ StackSamplingConfiguration();
+
+ // Get the stack sampling params to use for this process.
+ base::StackSamplingProfiler::SamplingParams
+ GetSamplingParamsForCurrentProcess() const;
+
+ // Returns true if the profiler should be started for the current process.
+ bool IsProfilerEnabledForCurrentProcess() const;
+
+ // Get the synthetic field trial configuration. Returns true if a synthetic
+ // field trial should be registered. This should only be called from the
+ // browser process. When run at startup, the profiler must use a synthetic
+ // field trial since it runs before the metrics field trials are initialized.
+ bool GetSyntheticFieldTrial(std::string* trial_name,
+ std::string* group_name) const;
+
+ // Add a command line switch that instructs the child process to run the
+ // profiler. This should only be called from the browser process.
+ void AppendCommandLineSwitchForChildProcess(
+ const std::string& process_type,
+ base::CommandLine* command_line) const;
+
+ // Returns the StackSamplingConfiguration for the process.
+ static StackSamplingConfiguration* Get();
+
+ private:
+ // Configuration to use for this Chrome instance.
+ enum ProfileConfiguration {
+ // Chrome-wide configurations set in the browser process.
+ PROFILE_DISABLED,
+ PROFILE_CONTROL,
+ PROFILE_ENABLED,
+
+ // Configuration set in the child processes, which receive their enable
+ // state on the command line from the browser process.
+ PROFILE_FROM_COMMAND_LINE
+ };
+
+ // Configuration variations, along with weights to use when randomly choosing
+ // one of a set of variations.
+ struct Variation {
+ ProfileConfiguration config;
+ int weight;
+ };
+
+ // Randomly chooses a configuration from the weighted variations. Weights are
+ // expected to sum to 100 as a sanity check.
+ static ProfileConfiguration ChooseConfiguration(
+ const std::vector<Variation>& variations);
+
+ // Generates sampling profiler configurations for all processes.
+ static ProfileConfiguration GenerateConfiguration();
+
+ // NOTE: all state in this class must be const and initialized at construction
+ // time to ensure thread-safe access post-construction.
+
+ // In the browser process this represents the configuration to use across all
+ // Chrome processes. In the child processes it is always
+ // PROFILE_FROM_COMMAND_LINE.
+ const ProfileConfiguration configuration_;
+
+ DISALLOW_COPY_AND_ASSIGN(StackSamplingConfiguration);
+};
+
+#endif // CHROME_COMMON_STACK_SAMPLING_CONFIGURATION_H_
diff --git a/chromium/chrome/common/themes/OWNERS b/chromium/chrome/common/themes/OWNERS
new file mode 100644
index 00000000000..81117d8a81e
--- /dev/null
+++ b/chromium/chrome/common/themes/OWNERS
@@ -0,0 +1,3 @@
+gayane@chromium.org
+
+file://chrome/browser/themes/OWNERS \ No newline at end of file
diff --git a/chromium/chrome/common/themes/autogenerated_theme_util.cc b/chromium/chrome/common/themes/autogenerated_theme_util.cc
new file mode 100644
index 00000000000..6a6b8d74fd7
--- /dev/null
+++ b/chromium/chrome/common/themes/autogenerated_theme_util.cc
@@ -0,0 +1,136 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/themes/autogenerated_theme_util.h"
+
+#include "ui/gfx/color_utils.h"
+
+// Decreases the lightness of the given color.
+SkColor DarkenColor(SkColor color, float change) {
+ color_utils::HSL hsl;
+ SkColorToHSL(color, &hsl);
+ hsl.l -= change;
+ if (hsl.l >= 0.0f)
+ return HSLToSkColor(hsl, 255);
+ return color;
+}
+
+// Increases the lightness of |source| until it reaches |contrast_ratio| with
+// |base| or reaches |white_contrast| with white. This avoids decreasing
+// saturation, as the alternative contrast-guaranteeing functions in color_utils
+// would do.
+SkColor LightenUntilContrast(SkColor source,
+ SkColor base,
+ float contrast_ratio,
+ float white_contrast) {
+ const float kBaseLuminance = color_utils::GetRelativeLuminance(base);
+ constexpr float kWhiteLuminance = 1.0f;
+
+ color_utils::HSL hsl;
+ SkColorToHSL(source, &hsl);
+ float min_l = hsl.l;
+ float max_l = 1.0f;
+
+ // Need only precision of 2 digits.
+ while (max_l - min_l > 0.01) {
+ hsl.l = min_l + (max_l - min_l) / 2;
+ float luminance = color_utils::GetRelativeLuminance(HSLToSkColor(hsl, 255));
+ if (color_utils::GetContrastRatio(kBaseLuminance, luminance) >=
+ contrast_ratio ||
+ (color_utils::GetContrastRatio(kWhiteLuminance, luminance) <
+ white_contrast)) {
+ max_l = hsl.l;
+ } else {
+ min_l = hsl.l;
+ }
+ }
+
+ hsl.l = max_l;
+ return HSLToSkColor(hsl, 255);
+}
+
+AutogeneratedThemeColors GetAutogeneratedThemeColors(SkColor color) {
+ SkColor frame_color = color;
+ SkColor frame_text_color;
+ SkColor active_tab_color = color;
+ SkColor active_tab_text_color;
+
+ constexpr float kDarkenStep = 0.03f;
+ constexpr float kMinWhiteContrast = 1.3f;
+ constexpr float kNoWhiteContrast = 0.0f;
+
+ // Used to determine what we consider a very dark color.
+ constexpr float kMaxLuminosityForDark = 0.05f;
+
+ // Increasingly darken frame color and calculate the rest until colors with
+ // sufficient contrast are found.
+ while (true) {
+ // Calculate frame color to have sufficient contrast with white or dark grey
+ // text.
+ frame_text_color = color_utils::GetColorWithMaxContrast(frame_color);
+ SkColor blend_target =
+ color_utils::GetColorWithMaxContrast(frame_text_color);
+ frame_color = color_utils::BlendForMinContrast(
+ frame_color, frame_text_color, blend_target,
+ kAutogeneratedThemeTextPreferredContrast)
+ .color;
+
+ // Generate active tab color so that it has enough contrast with the
+ // |frame_color| to avoid the isolation line in the tab strip.
+ active_tab_color = LightenUntilContrast(
+ frame_color, frame_color, kAutogeneratedThemeActiveTabMinContrast,
+ kNoWhiteContrast);
+
+ // We want more contrast between frame and active tab for dark colors.
+ color_utils::HSL hsl;
+ SkColorToHSL(frame_color, &hsl);
+ float preferred_contrast =
+ hsl.l <= kMaxLuminosityForDark
+ ? kAutogeneratedThemeActiveTabPreferredContrastForDark
+ : kAutogeneratedThemeActiveTabPreferredContrast;
+
+ // Try lightening the color to get more contrast with frame without getting
+ // too close to white.
+ active_tab_color = LightenUntilContrast(
+ active_tab_color, frame_color, preferred_contrast, kMinWhiteContrast);
+
+ // If we didn't succeed in generating active tab color with minimum
+ // contrast with frame, then darken the frame color and try again.
+ if (color_utils::GetContrastRatio(frame_color, active_tab_color) <
+ kAutogeneratedThemeActiveTabMinContrast) {
+ frame_color = DarkenColor(frame_color, kDarkenStep);
+ continue;
+ }
+
+ // Select active tab text color, if possible.
+ active_tab_text_color =
+ color_utils::GetColorWithMaxContrast(active_tab_color);
+
+ if (!color_utils::IsDark(active_tab_color)) {
+ // If active tab is light color then continue lightening it until enough
+ // contrast with dark text is reached.
+ active_tab_text_color =
+ color_utils::GetColorWithMaxContrast(active_tab_color);
+ active_tab_color = LightenUntilContrast(
+ active_tab_color, active_tab_text_color,
+ kAutogeneratedThemeTextPreferredContrast, kNoWhiteContrast);
+ break;
+ }
+
+ // If the active tab color is dark and has enough contrast with white text.
+ // Then we are all set.
+ if (color_utils::GetContrastRatio(active_tab_color, SK_ColorWHITE) >=
+ kAutogeneratedThemeTextPreferredContrast)
+ break;
+
+ // If the active tab color is a dark color but the contrast with white is
+ // not enough then we should darken the active tab color to reach the
+ // contrast with white. But to keep the contrast with the frame we should
+ // also darken the frame color. Therefore, just darken the frame color and
+ // try again.
+ frame_color = DarkenColor(frame_color, kDarkenStep);
+ }
+ return {frame_color, frame_text_color, active_tab_color,
+ active_tab_text_color};
+}
diff --git a/chromium/chrome/common/themes/autogenerated_theme_util.h b/chromium/chrome/common/themes/autogenerated_theme_util.h
new file mode 100644
index 00000000000..ba84c4028ca
--- /dev/null
+++ b/chromium/chrome/common/themes/autogenerated_theme_util.h
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_THEMES_AUTOGENERATED_THEME_UTIL_H_
+#define CHROME_COMMON_THEMES_AUTOGENERATED_THEME_UTIL_H_
+
+#include "third_party/skia/include/core/SkColor.h"
+
+// Constants for autogenerated themes.
+// Minimum contrast for active tab and frame color to avoid isolation line in
+// the tab strip.
+constexpr float kAutogeneratedThemeActiveTabMinContrast = 1.3f;
+constexpr float kAutogeneratedThemeActiveTabPreferredContrast = 1.6f;
+constexpr float kAutogeneratedThemeActiveTabPreferredContrastForDark = 1.7f;
+
+// Contrast between foreground and background.
+constexpr float kAutogeneratedThemeTextPreferredContrast = 7.0f;
+
+struct AutogeneratedThemeColors {
+ SkColor frame_color;
+ SkColor frame_text_color;
+ SkColor active_tab_color;
+ SkColor active_tab_text_color;
+};
+
+// Generates theme colors for the given |color|.
+AutogeneratedThemeColors GetAutogeneratedThemeColors(SkColor color);
+
+#endif // CHROME_COMMON_THEMES_AUTOGENERATED_THEME_UTIL_H_
diff --git a/chromium/chrome/common/thread_profiler.cc b/chromium/chrome/common/thread_profiler.cc
new file mode 100644
index 00000000000..4197d329d86
--- /dev/null
+++ b/chromium/chrome/common/thread_profiler.cc
@@ -0,0 +1,312 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/thread_profiler.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/message_loop/work_id_provider.h"
+#include "base/no_destructor.h"
+#include "base/profiler/sample_metadata.h"
+#include "base/rand_util.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequence_local_storage_slot.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/common/stack_sampling_configuration.h"
+#include "components/metrics/call_stack_profile_builder.h"
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/service_names.mojom.h"
+#include "services/service_manager/embedder/switches.h"
+
+using CallStackProfileBuilder = metrics::CallStackProfileBuilder;
+using CallStackProfileParams = metrics::CallStackProfileParams;
+using StackSamplingProfiler = base::StackSamplingProfiler;
+
+namespace {
+
+// Pointer to the main thread instance, if any. Stored as a global because it's
+// created very early in chrome/app - and is thus otherwise inaccessible from
+// chrome_dll, by the time we need to register the main thread task runner.
+ThreadProfiler* g_main_thread_instance = nullptr;
+
+// Run continuous profiling 2% of the time.
+constexpr const double kFractionOfExecutionTimeToSample = 0.02;
+
+constexpr struct StackSamplingProfiler::SamplingParams kSamplingParams = {
+ /* initial_delay= */ base::TimeDelta::FromMilliseconds(0),
+ /* samples_per_profile= */ 300,
+ /* sampling_interval= */ base::TimeDelta::FromMilliseconds(100),
+ /* keep_consistent_sampling_interval= */ true};
+
+CallStackProfileParams::Process GetProcess() {
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+ std::string process_type =
+ command_line->GetSwitchValueASCII(switches::kProcessType);
+ if (process_type.empty())
+ return CallStackProfileParams::BROWSER_PROCESS;
+ if (process_type == switches::kRendererProcess)
+ return CallStackProfileParams::RENDERER_PROCESS;
+ if (process_type == switches::kGpuProcess)
+ return CallStackProfileParams::GPU_PROCESS;
+ if (process_type == switches::kUtilityProcess)
+ return CallStackProfileParams::UTILITY_PROCESS;
+ if (process_type == service_manager::switches::kZygoteProcess)
+ return CallStackProfileParams::ZYGOTE_PROCESS;
+ if (process_type == switches::kPpapiPluginProcess)
+ return CallStackProfileParams::PPAPI_PLUGIN_PROCESS;
+ if (process_type == switches::kPpapiBrokerProcess)
+ return CallStackProfileParams::PPAPI_BROKER_PROCESS;
+ return CallStackProfileParams::UNKNOWN_PROCESS;
+}
+
+} // namespace
+
+// The scheduler works by splitting execution time into repeated periods such
+// that the time to take one collection represents
+// |fraction_of_execution_time_to_sample| of the period, and the time not spent
+// sampling represents 1 - |fraction_of_execution_time_to_sample| of the period.
+// The collection start time is chosen randomly within each period such that the
+// entire collection is contained within the period.
+//
+// The kFractionOfExecutionTimeToSample and SamplingParams settings at the top
+// of the file specify fraction = 0.02 and sampling period = 1 sample / .1s
+// sampling interval * 300 samples = 30s. The period length works out to
+// 30s/0.02 = 1500s = 25m. So every 25 minutes a random 30 second continuous
+// interval will be picked to sample.
+PeriodicSamplingScheduler::PeriodicSamplingScheduler(
+ base::TimeDelta sampling_duration,
+ double fraction_of_execution_time_to_sample,
+ base::TimeTicks start_time)
+ : period_duration_(
+ base::TimeDelta::FromSecondsD(sampling_duration.InSecondsF() /
+ fraction_of_execution_time_to_sample)),
+ sampling_duration_(sampling_duration),
+ period_start_time_(start_time) {
+ DCHECK(sampling_duration_ <= period_duration_);
+}
+
+PeriodicSamplingScheduler::~PeriodicSamplingScheduler() = default;
+
+base::TimeDelta PeriodicSamplingScheduler::GetTimeToNextCollection() {
+ const base::TimeTicks now = Now();
+ // Avoid scheduling in the past in the presence of discontinuous jumps in
+ // the current TimeTicks.
+ period_start_time_ = std::max(period_start_time_, now);
+
+ double sampling_offset_seconds =
+ (period_duration_ - sampling_duration_).InSecondsF() * RandDouble();
+ base::TimeTicks next_collection_time =
+ period_start_time_ +
+ base::TimeDelta::FromSecondsD(sampling_offset_seconds);
+ period_start_time_ += period_duration_;
+ return next_collection_time - now;
+}
+
+double PeriodicSamplingScheduler::RandDouble() const {
+ return base::RandDouble();
+}
+
+base::TimeTicks PeriodicSamplingScheduler::Now() const {
+ return base::TimeTicks::Now();
+}
+
+// Records the current unique id for the work item being executed in the target
+// thread's message loop.
+class ThreadProfiler::WorkIdRecorder : public metrics::WorkIdRecorder {
+ public:
+ explicit WorkIdRecorder(base::WorkIdProvider* work_id_provider)
+ : work_id_provider_(work_id_provider) {}
+
+ // Invoked on the profiler thread while the target thread is suspended.
+ unsigned int RecordWorkId() const override {
+ return work_id_provider_->GetWorkId();
+ }
+
+ WorkIdRecorder(const WorkIdRecorder&) = delete;
+ WorkIdRecorder& operator=(const WorkIdRecorder&) = delete;
+
+ private:
+ base::WorkIdProvider* const work_id_provider_;
+};
+
+ThreadProfiler::~ThreadProfiler() {
+ if (g_main_thread_instance == this)
+ g_main_thread_instance = nullptr;
+}
+
+// static
+std::unique_ptr<ThreadProfiler> ThreadProfiler::CreateAndStartOnMainThread() {
+ // If running in single process mode, there may be multiple "main thread"
+ // profilers created. In this case, we assume the first created one is the
+ // browser one.
+ auto* command_line = base::CommandLine::ForCurrentProcess();
+ bool is_single_process = command_line->HasSwitch(switches::kSingleProcess) ||
+ command_line->HasSwitch(switches::kInProcessGPU);
+ DCHECK(!g_main_thread_instance || is_single_process);
+ auto instance = std::unique_ptr<ThreadProfiler>(
+ new ThreadProfiler(CallStackProfileParams::MAIN_THREAD));
+ if (!g_main_thread_instance)
+ g_main_thread_instance = instance.get();
+ return instance;
+}
+
+// static
+void ThreadProfiler::SetMainThreadTaskRunner(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ DCHECK(g_main_thread_instance);
+ g_main_thread_instance->SetMainThreadTaskRunnerImpl(task_runner);
+}
+
+void ThreadProfiler::SetAuxUnwinderFactory(
+ const base::RepeatingCallback<std::unique_ptr<base::Unwinder>()>& factory) {
+ if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
+ return;
+
+ aux_unwinder_factory_ = factory;
+ startup_profiler_->AddAuxUnwinder(aux_unwinder_factory_.Run());
+ if (periodic_profiler_)
+ periodic_profiler_->AddAuxUnwinder(aux_unwinder_factory_.Run());
+}
+
+// static
+void ThreadProfiler::StartOnChildThread(CallStackProfileParams::Thread thread) {
+ // The profiler object is stored in a SequenceLocalStorageSlot on child
+ // threads to give it the same lifetime as the threads.
+ static base::NoDestructor<
+ base::SequenceLocalStorageSlot<std::unique_ptr<ThreadProfiler>>>
+ child_thread_profiler_sequence_local_storage;
+
+ if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
+ return;
+
+ child_thread_profiler_sequence_local_storage->emplace(
+ new ThreadProfiler(thread, base::ThreadTaskRunnerHandle::Get()));
+}
+
+// static
+void ThreadProfiler::SetBrowserProcessReceiverCallback(
+ const base::RepeatingCallback<void(base::TimeTicks,
+ metrics::SampledProfile)>& callback) {
+ CallStackProfileBuilder::SetBrowserProcessReceiverCallback(callback);
+}
+
+// static
+void ThreadProfiler::SetCollectorForChildProcess(
+ mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> collector) {
+ if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
+ return;
+
+ DCHECK_NE(CallStackProfileParams::BROWSER_PROCESS, GetProcess());
+ CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
+ std::move(collector));
+}
+
+// ThreadProfiler implementation synopsis:
+//
+// On creation, the profiler creates and starts the startup
+// StackSamplingProfiler, and configures the PeriodicSamplingScheduler such that
+// it starts scheduling from the time the startup profiling will be complete.
+// When a message loop is available (either in the constructor, or via
+// SetMainThreadTaskRunner) a task is posted to start the first periodic
+// collection at the initial scheduled collection time.
+//
+// When the periodic collection task executes, it creates and starts a new
+// periodic profiler and configures it to call OnPeriodicCollectionCompleted as
+// its completion callback. OnPeriodicCollectionCompleted is called on the
+// profiler thread and schedules a task on the original thread to schedule
+// another periodic collection. When the task runs, it posts a new task to start
+// another periodic collection at the next scheduled collection time.
+//
+// The process in previous paragraph continues until the ThreadProfiler is
+// destroyed prior to thread exit.
+ThreadProfiler::ThreadProfiler(
+ CallStackProfileParams::Thread thread,
+ scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner)
+ : thread_(thread),
+ owning_thread_task_runner_(owning_thread_task_runner),
+ work_id_recorder_(std::make_unique<WorkIdRecorder>(
+ base::WorkIdProvider::GetForCurrentThread())) {
+ if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
+ return;
+
+ startup_profiler_ = std::make_unique<StackSamplingProfiler>(
+ base::PlatformThread::CurrentId(), kSamplingParams,
+ std::make_unique<CallStackProfileBuilder>(
+ CallStackProfileParams(GetProcess(), thread,
+ CallStackProfileParams::PROCESS_STARTUP),
+ work_id_recorder_.get()));
+
+ startup_profiler_->Start();
+
+ // Estimated time at which the startup profiling will be completed. It's OK if
+ // this doesn't exactly coincide with the end of the startup profiling, since
+ // there's no harm in having a brief overlap of startup and periodic
+ // profiling.
+ base::TimeTicks startup_profiling_completion_time =
+ base::TimeTicks::Now() +
+ kSamplingParams.samples_per_profile * kSamplingParams.sampling_interval;
+
+ periodic_sampling_scheduler_ = std::make_unique<PeriodicSamplingScheduler>(
+ kSamplingParams.samples_per_profile * kSamplingParams.sampling_interval,
+ kFractionOfExecutionTimeToSample, startup_profiling_completion_time);
+
+ if (owning_thread_task_runner_)
+ ScheduleNextPeriodicCollection();
+}
+
+// static
+void ThreadProfiler::OnPeriodicCollectionCompleted(
+ scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner,
+ base::WeakPtr<ThreadProfiler> thread_profiler) {
+ owning_thread_task_runner->PostTask(
+ FROM_HERE, base::BindOnce(&ThreadProfiler::ScheduleNextPeriodicCollection,
+ thread_profiler));
+}
+
+void ThreadProfiler::SetMainThreadTaskRunnerImpl(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
+ return;
+
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ // This should only be called if the task runner wasn't provided in the
+ // constructor.
+ DCHECK(!owning_thread_task_runner_);
+ owning_thread_task_runner_ = task_runner;
+ ScheduleNextPeriodicCollection();
+}
+
+void ThreadProfiler::ScheduleNextPeriodicCollection() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ owning_thread_task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&ThreadProfiler::StartPeriodicSamplingCollection,
+ weak_factory_.GetWeakPtr()),
+ periodic_sampling_scheduler_->GetTimeToNextCollection());
+}
+
+void ThreadProfiler::StartPeriodicSamplingCollection() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ // NB: Destroys the previous profiler as side effect.
+ periodic_profiler_ = std::make_unique<StackSamplingProfiler>(
+ base::PlatformThread::CurrentId(), kSamplingParams,
+ std::make_unique<CallStackProfileBuilder>(
+ CallStackProfileParams(GetProcess(), thread_,
+ CallStackProfileParams::PERIODIC_COLLECTION),
+ work_id_recorder_.get(),
+ base::BindOnce(&ThreadProfiler::OnPeriodicCollectionCompleted,
+ owning_thread_task_runner_,
+ weak_factory_.GetWeakPtr())));
+ if (aux_unwinder_factory_)
+ periodic_profiler_->AddAuxUnwinder(aux_unwinder_factory_.Run());
+
+ periodic_profiler_->Start();
+}
diff --git a/chromium/chrome/common/thread_profiler.h b/chromium/chrome/common/thread_profiler.h
new file mode 100644
index 00000000000..21e3db7dc46
--- /dev/null
+++ b/chromium/chrome/common/thread_profiler.h
@@ -0,0 +1,155 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_THREAD_PROFILER_H_
+#define CHROME_COMMON_THREAD_PROFILER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/profiler/unwinder.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/metrics/call_stack_profile_params.h"
+#include "components/metrics/public/mojom/call_stack_profile_collector.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+// PeriodicSamplingScheduler repeatedly schedules periodic sampling of the
+// thread through calls to GetTimeToNextCollection(). This class is exposed
+// to allow testing.
+class PeriodicSamplingScheduler {
+ public:
+ PeriodicSamplingScheduler(base::TimeDelta sampling_duration,
+ double fraction_of_execution_time_to_sample,
+ base::TimeTicks start_time);
+ virtual ~PeriodicSamplingScheduler();
+
+ // Returns the amount of time between now and the next collection.
+ base::TimeDelta GetTimeToNextCollection();
+
+ protected:
+ // Virtual to provide seams for test use.
+ virtual double RandDouble() const;
+ virtual base::TimeTicks Now() const;
+
+ private:
+ const base::TimeDelta period_duration_;
+ const base::TimeDelta sampling_duration_;
+ base::TimeTicks period_start_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeriodicSamplingScheduler);
+};
+
+// ThreadProfiler performs startup and periodic profiling of Chrome
+// threads.
+class ThreadProfiler {
+ public:
+ ~ThreadProfiler();
+
+ // Creates a profiler for a main thread and immediately starts it. This
+ // function should only be used when profiling the main thread of a
+ // process. The returned profiler must be destroyed prior to thread exit to
+ // stop the profiling.
+ //
+ // SetMainThreadTaskRunner() should be called after the message loop has been
+ // started on the thread. It is the caller's responsibility to ensure that
+ // the instance returned by this function is still alive when the static API
+ // SetMainThreadTaskRunner() is used. The latter is static to support Chrome's
+ // set up where the ThreadProfiler is created in chrome/app which cannot be
+ // easily accessed from chrome_browser_main.cc which sets the task runner.
+ static std::unique_ptr<ThreadProfiler> CreateAndStartOnMainThread();
+
+ // Sets the task runner when profiling on the main thread. This occurs in a
+ // separate call from CreateAndStartOnMainThread so that startup profiling can
+ // occur prior to message loop start. The task runner is associated with the
+ // instance returned by CreateAndStartOnMainThread(), which must be alive when
+ // this is called.
+ static void SetMainThreadTaskRunner(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Sets a callback to create auxiliary unwinders, for handling additional,
+ // non-native-code unwind scenarios. Currently used to support
+ // unwinding V8 JavaScript frames.
+ void SetAuxUnwinderFactory(
+ const base::RepeatingCallback<std::unique_ptr<base::Unwinder>()>&
+ factory);
+
+ // Creates a profiler for a child thread and immediately starts it. This
+ // should be called from a task posted on the child thread immediately after
+ // thread start. The thread will be profiled until exit.
+ static void StartOnChildThread(
+ metrics::CallStackProfileParams::Thread thread);
+
+ // Sets the callback to use for reporting browser process profiles. This
+ // indirection is required to avoid a dependency on unnecessary metrics code
+ // in child processes.
+ static void SetBrowserProcessReceiverCallback(
+ const base::RepeatingCallback<void(base::TimeTicks,
+ metrics::SampledProfile)>& callback);
+
+ // This function must be called within child processes to supply the Service
+ // Manager's connector, to bind the interface through which a profile is sent
+ // back to the browser process.
+ //
+ // Note that the metrics::CallStackProfileCollector interface also must be
+ // exposed to the child process, and metrics::mojom::CallStackProfileCollector
+ // declared in chrome_content_browser_manifest_overlay.json, for the binding
+ // to succeed.
+ static void SetCollectorForChildProcess(
+ mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> collector);
+
+ private:
+ class WorkIdRecorder;
+
+ // Creates the profiler. The task runner will be supplied for child threads
+ // but not for main threads.
+ ThreadProfiler(
+ metrics::CallStackProfileParams::Thread thread,
+ scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner =
+ scoped_refptr<base::SingleThreadTaskRunner>());
+
+ // Posts a task on |owning_thread_task_runner| to start the next periodic
+ // sampling collection on the completion of the previous collection.
+ static void OnPeriodicCollectionCompleted(
+ scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner,
+ base::WeakPtr<ThreadProfiler> thread_profiler);
+
+ // Sets the task runner when profiling on the main thread. This occurs in a
+ // separate call from CreateAndStartOnMainThread so that startup profiling can
+ // occur prior to message loop start.
+ void SetMainThreadTaskRunnerImpl(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Posts a delayed task to start the next periodic sampling collection.
+ void ScheduleNextPeriodicCollection();
+
+ // Creates a new periodic profiler and initiates a collection with it.
+ void StartPeriodicSamplingCollection();
+
+ metrics::CallStackProfileParams::Thread thread_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner_;
+
+ std::unique_ptr<WorkIdRecorder> work_id_recorder_;
+
+ base::RepeatingCallback<std::unique_ptr<base::Unwinder>()>
+ aux_unwinder_factory_;
+
+ std::unique_ptr<base::StackSamplingProfiler> startup_profiler_;
+
+ std::unique_ptr<base::StackSamplingProfiler> periodic_profiler_;
+ std::unique_ptr<PeriodicSamplingScheduler> periodic_sampling_scheduler_;
+
+ THREAD_CHECKER(thread_checker_);
+ base::WeakPtrFactory<ThreadProfiler> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadProfiler);
+};
+
+#endif // CHROME_COMMON_THREAD_PROFILER_H_
diff --git a/chromium/chrome/common/thread_profiler_unittest.cc b/chromium/chrome/common/thread_profiler_unittest.cc
new file mode 100644
index 00000000000..cf8018139a6
--- /dev/null
+++ b/chromium/chrome/common/thread_profiler_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/thread_profiler.h"
+
+#include "base/macros.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestScheduler : public PeriodicSamplingScheduler {
+ public:
+ TestScheduler(base::TimeDelta sampling_duration,
+ double fraction_of_execution_time_to_sample)
+ : PeriodicSamplingScheduler(sampling_duration,
+ fraction_of_execution_time_to_sample,
+ kStartTime),
+ rand_double_value_(0.0) {
+ tick_clock_.SetNowTicks(kStartTime);
+ }
+
+ double RandDouble() const override { return rand_double_value_; }
+ base::TimeTicks Now() const override { return tick_clock_.NowTicks(); }
+
+ void SetRandDouble(double value) { rand_double_value_ = value; }
+ base::SimpleTestTickClock& tick_clock() { return tick_clock_; }
+
+ private:
+ static constexpr base::TimeTicks kStartTime = base::TimeTicks();
+ base::SimpleTestTickClock tick_clock_;
+ double rand_double_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestScheduler);
+};
+
+constexpr base::TimeTicks TestScheduler::kStartTime;
+
+} // namespace
+
+TEST(ThreadProfilerTest, PeriodicSamplingScheduler) {
+ const base::TimeDelta sampling_duration = base::TimeDelta::FromSeconds(30);
+ const double fraction_of_execution_time_to_sample = 0.01;
+
+ const base::TimeDelta expected_period =
+ sampling_duration / fraction_of_execution_time_to_sample;
+
+ TestScheduler scheduler(sampling_duration,
+ fraction_of_execution_time_to_sample);
+
+ // The first collection should be exactly at the start time, since the random
+ // value is 0.0.
+ scheduler.SetRandDouble(0.0);
+ EXPECT_EQ(base::TimeDelta::FromSeconds(0),
+ scheduler.GetTimeToNextCollection());
+
+ // With a random value of 1.0 the second collection should be at the end of
+ // the second period.
+ scheduler.SetRandDouble(1.0);
+ EXPECT_EQ(2 * expected_period - sampling_duration,
+ scheduler.GetTimeToNextCollection());
+
+ // With a random value of 0.25 the second collection should be a quarter into
+ // the third period exclusive of the sampling duration.
+ scheduler.SetRandDouble(0.25);
+ EXPECT_EQ(2 * expected_period + 0.25 * (expected_period - sampling_duration),
+ scheduler.GetTimeToNextCollection());
+}
+
+TEST(ThreadProfilerTest, PeriodicSamplingSchedulerWithJumpInTimeTicks) {
+ const base::TimeDelta sampling_duration = base::TimeDelta::FromSeconds(30);
+ const double fraction_of_execution_time_to_sample = 0.01;
+
+ const base::TimeDelta expected_period =
+ sampling_duration / fraction_of_execution_time_to_sample;
+
+ TestScheduler scheduler(sampling_duration,
+ fraction_of_execution_time_to_sample);
+
+ // The first collection should be exactly at the start time, since the random
+ // value is 0.0.
+ scheduler.SetRandDouble(0.0);
+ EXPECT_EQ(base::TimeDelta::FromSeconds(0),
+ scheduler.GetTimeToNextCollection());
+
+ // Simulate a non-continuous jump in the current TimeTicks such that the next
+ // period would start before the current time. In this case the
+ // period start should be reset to the current time, and the next collection
+ // chosen within that period.
+ scheduler.tick_clock().Advance(expected_period +
+ base::TimeDelta::FromSeconds(1));
+ scheduler.SetRandDouble(0.5);
+ EXPECT_EQ(0.5 * (expected_period - sampling_duration),
+ scheduler.GetTimeToNextCollection());
+}
diff --git a/chromium/chrome/common/time_format_browsertest.cc b/chromium/chrome/common/time_format_browsertest.cc
new file mode 100644
index 00000000000..0a110aacafc
--- /dev/null
+++ b/chromium/chrome/common/time_format_browsertest.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This whole test runs as a separate browser_test because it depends on a
+// static initialization inside third_party/icu (gDecimal in digitlst.cpp).
+//
+// That initialization depends on the current locale, and on certain locales
+// will lead to wrong behavior. To make sure that the locale is set before
+// icu is used, and that the "wrong" static value doesn't affect other tests,
+// this test is executed on its own process.
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_locale.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "ui/base/l10n/time_format.h"
+
+using base::TimeDelta;
+
+class TimeFormatBrowserTest : public InProcessBrowserTest {
+ public:
+ TimeFormatBrowserTest() : scoped_locale_("fr_FR.utf-8") {
+ }
+
+ private:
+ base::ScopedLocale scoped_locale_;
+};
+
+IN_PROC_BROWSER_TEST_F(TimeFormatBrowserTest, DecimalPointNotDot) {
+ // Some locales use a comma ',' instead of a dot '.' as the separator for
+ // decimal digits. The icu library wasn't handling this, leading to "1"
+ // being internally converted to "+1,0e00" and ultimately leading to "NaN".
+ // This showed up on the browser on estimated download time, for example.
+ // http://crbug.com/60476
+
+ base::string16 one_min =
+ ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION,
+ ui::TimeFormat::LENGTH_SHORT,
+ TimeDelta::FromMinutes(1));
+ EXPECT_EQ(base::ASCIIToUTF16("1 min"), one_min);
+}
diff --git a/chromium/chrome/common/web_application_info.cc b/chromium/chrome/common/web_application_info.cc
new file mode 100644
index 00000000000..340eeb360b2
--- /dev/null
+++ b/chromium/chrome/common/web_application_info.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/web_application_info.h"
+
+WebApplicationIconInfo::WebApplicationIconInfo() : width(0), height(0) {}
+
+WebApplicationIconInfo::~WebApplicationIconInfo() = default;
+
+WebApplicationInfo::WebApplicationInfo()
+ : mobile_capable(MOBILE_CAPABLE_UNSPECIFIED),
+ generated_icon_color(SK_ColorTRANSPARENT),
+ open_as_window(false) {}
+
+WebApplicationInfo::WebApplicationInfo(const WebApplicationInfo& other) =
+ default;
+
+WebApplicationInfo::~WebApplicationInfo() = default;
diff --git a/chromium/chrome/common/web_application_info.h b/chromium/chrome/common/web_application_info.h
new file mode 100644
index 00000000000..1d75589011f
--- /dev/null
+++ b/chromium/chrome/common/web_application_info.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_WEB_APPLICATION_INFO_H_
+#define CHROME_COMMON_WEB_APPLICATION_INFO_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+
+struct WebApplicationIconInfo {
+ WebApplicationIconInfo();
+ ~WebApplicationIconInfo();
+
+ GURL url;
+ int width;
+ int height;
+ SkBitmap data;
+};
+
+// Structure used when installing a web page as an app.
+struct WebApplicationInfo {
+ enum MobileCapable {
+ MOBILE_CAPABLE_UNSPECIFIED,
+ MOBILE_CAPABLE,
+ MOBILE_CAPABLE_APPLE
+ };
+
+ WebApplicationInfo();
+ WebApplicationInfo(const WebApplicationInfo& other);
+ ~WebApplicationInfo();
+
+ // Title of the application.
+ base::string16 title;
+
+ // Description of the application.
+ base::string16 description;
+
+ // The launch URL for the app.
+ GURL app_url;
+
+ // Scope for the app. Dictates what URLs will be opened in the app.
+ GURL scope;
+
+ // Set of available icons.
+ std::vector<WebApplicationIconInfo> icons;
+
+ // Whether the page is marked as mobile-capable, including apple specific meta
+ // tag.
+ MobileCapable mobile_capable;
+
+ // The color to use if an icon needs to be generated for the web app.
+ SkColor generated_icon_color;
+
+ // The color to use for the web app frame.
+ base::Optional<SkColor> theme_color;
+
+ // Whether the app should be opened in a window. If false, the app will be
+ // opened in a tab.
+ bool open_as_window;
+
+ // The extensions and mime types the app can handle.
+ base::Optional<blink::Manifest::FileHandler> file_handler;
+};
+
+#endif // CHROME_COMMON_WEB_APPLICATION_INFO_H_
diff --git a/chromium/chrome/common/web_application_info_provider_param_traits.h b/chromium/chrome/common/web_application_info_provider_param_traits.h
new file mode 100644
index 00000000000..f23ff915a9e
--- /dev/null
+++ b/chromium/chrome/common/web_application_info_provider_param_traits.h
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// NOLINT(build/header_guard)
+// no-include-guard-because-multiply-included
+
+#include "chrome/common/web_application_info.h"
+#include "ipc/ipc_message_macros.h"
+
+IPC_ENUM_TRAITS_MAX_VALUE(WebApplicationInfo::MobileCapable,
+ WebApplicationInfo::MOBILE_CAPABLE_APPLE)
+
+IPC_STRUCT_TRAITS_BEGIN(WebApplicationIconInfo)
+ IPC_STRUCT_TRAITS_MEMBER(url)
+ IPC_STRUCT_TRAITS_MEMBER(width)
+ IPC_STRUCT_TRAITS_MEMBER(height)
+ IPC_STRUCT_TRAITS_MEMBER(data)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(WebApplicationInfo)
+ IPC_STRUCT_TRAITS_MEMBER(title)
+ IPC_STRUCT_TRAITS_MEMBER(description)
+ IPC_STRUCT_TRAITS_MEMBER(app_url)
+ IPC_STRUCT_TRAITS_MEMBER(icons)
+ IPC_STRUCT_TRAITS_MEMBER(mobile_capable)
+IPC_STRUCT_TRAITS_END()
diff --git a/chromium/chrome/common/win/OWNERS b/chromium/chrome/common/win/OWNERS
new file mode 100644
index 00000000000..5bbda7a59c4
--- /dev/null
+++ b/chromium/chrome/common/win/OWNERS
@@ -0,0 +1 @@
+# COMPONENT: Platform>Apps>API>Windows
diff --git a/chromium/chrome/common/win/eventlog_messages.mc b/chromium/chrome/common/win/eventlog_messages.mc
new file mode 100644
index 00000000000..bfc16319bb1
--- /dev/null
+++ b/chromium/chrome/common/win/eventlog_messages.mc
@@ -0,0 +1,32 @@
+;// Copyright 2016 The Chromium Authors. All rights reserved.
+;// Use of this source code is governed by a BSD-style license that can be
+;// found in the LICENSE file.
+;//
+;// Defines the names and types of messages that are logged with the SYSLOG
+;// macro.
+SeverityNames=(Informational=0x0:STATUS_SEVERITY_INFORMATIONAL
+ Warning=0x1:STATUS_SEVERITY_WARNING
+ Error=0x2:STATUS_SEVERITY_ERROR
+ Fatal=0x3:STATUS_SEVERITY_FATAL
+ )
+FacilityNames=(Browser=0x0:FACILITY_SYSTEM)
+LanguageNames=(English=0x409:MSG00409)
+
+;// TODO(pastarmovj): Subdivide into more categories if needed.
+MessageIdTypedef=WORD
+
+MessageId=0x1
+SymbolicName=BROWSER_CATEGORY
+Language=English
+Browser Events
+.
+
+MessageIdTypedef=DWORD
+
+MessageId=0x100
+Severity=Error
+Facility=Browser
+SymbolicName=MSG_LOG_MESSAGE
+Language=English
+%1!S!
+.
diff --git a/chromium/chrome/common/win/eventlog_provider.cc b/chromium/chrome/common/win/eventlog_provider.cc
new file mode 100644
index 00000000000..d4116c26584
--- /dev/null
+++ b/chromium/chrome/common/win/eventlog_provider.cc
@@ -0,0 +1,9 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Force the generation of a .lib file for the .dll since Ninja expects shared
+// libraries to generate a .dll and a .lib file.
+__declspec(dllexport) bool fn() {
+ return true;
+}
diff --git a/chromium/chrome/common/win/eventlog_provider.ver b/chromium/chrome/common/win/eventlog_provider.ver
new file mode 100644
index 00000000000..2d6c97c0125
--- /dev/null
+++ b/chromium/chrome/common/win/eventlog_provider.ver
@@ -0,0 +1,2 @@
+INTERNAL_NAME=eventlog_provider_dll
+ORIGINAL_FILENAME=eventlog_provider.dll
diff --git a/chromium/chrome/installer/linux/debian/deb_version.py b/chromium/chrome/installer/linux/debian/deb_version.py
deleted file mode 100755
index 12470695c5e..00000000000
--- a/chromium/chrome/installer/linux/debian/deb_version.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-def compare_int(left, right):
- if left == right:
- return 0
- return -1 if left < right else 1
-
-def compare_char(left, right):
- # 'man deb-version' specifies that characters are compared using
- # their ASCII values, except alphabetic characters come before
- # special characters and ~ comes before everything else.
- table = '~$ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:'
- left = table.find(left)
- right = table.find(right)
- assert left >= 0 and right >= 0
- return compare_int(left, right)
-
-def compare_part(left, right):
- if isinstance(left, int) and isinstance(right, int):
- return compare_int(left, right)
- assert isinstance(left, (str, unicode)) and isinstance(right, (str, unicode))
- # 'man deb-version' specifies that '~' should be matched before the
- # empty string. Add a '$' to the end of the strings to make this
- # comparison easier.
- left += '$'
- right += '$'
- i = 0
- while i < len(left) and i < len(right):
- comp = compare_char(left[i], right[i])
- if comp != 0:
- return comp
- i += 1
- return compare_int(len(left), len(right))
-
-def get_parts_from_component(component):
- # 'man deb-version' specifies that components should be compared
- # part-by-part, where parts are either strings or numbers. Strings
- # are compared lexicographically while numbers are compared using
- # magnitude. Components must start with a string part and end with
- # a number part. The empty string will be prepended and 0 will be
- # appended to satisfy this requirement. For example, the component
- # '1.2.3' will be expanded to ['', 1, '.', 2, '.', 3], and the empty
- # component will be expanded to ['', 0].
- part_is_string = True
- parts = []
- part = ''
- for c in component:
- char_is_string = not c.isdigit()
- if char_is_string != part_is_string:
- parts.append(part if part_is_string else int(part))
- part = ''
- part_is_string = char_is_string
- part += c
- if part_is_string:
- parts.append(part)
- part = ''
- parts.append(0 if part == '' else int(part))
- return parts
-
-def compare_component(left, right):
- i = 0
- left = get_parts_from_component(left)
- right = get_parts_from_component(right)
- while i < len(left) and i < len(right):
- comp = compare_part(left[i], right[i])
- if comp != 0:
- return comp
- i += 1
- return compare_int(len(left), len(right))
-
-class DebVersion:
- def __init__(self, version_string):
- self.version_string = version_string
- self.epoch = 0
- self.upstream_version = ''
- self.debian_revision = None
-
- colon = version_string.find(':')
- if colon >= 0:
- self.epoch = int(version_string[:colon])
- hyphen = version_string.rfind('-')
- if hyphen >= 0:
- self.debian_revision = version_string[hyphen + 1:]
- upstream_version_start = colon + 1
- upstream_version_end = hyphen if hyphen >= 0 else len(version_string)
- self.upstream_version = version_string[upstream_version_start:
- upstream_version_end]
-
- def __str__(self):
- return self.version_string
-
- # Comparison algorithm is specified in 'man deb-version'.
- def __cmp__(self, other):
- assert(isinstance(other, DebVersion))
-
- # Epoch comparison.
- if self.epoch != other.epoch:
- return 1 if self.epoch > other.epoch else -1
-
- # Upstream version comparison.
- upstream_version_cmp = compare_component(self.upstream_version,
- other.upstream_version)
- if upstream_version_cmp != 0:
- return upstream_version_cmp
-
- # Debian revision comparison.
- if self.debian_revision == None and other.debian_revision == None:
- return 0
- if self.debian_revision == None:
- return -1
- if other.debian_revision == None:
- return 1
- return compare_component(self.debian_revision, other.debian_revision)
diff --git a/chromium/chrome/installer/linux/debian/dist_package_versions.json b/chromium/chrome/installer/linux/debian/dist_package_versions.json
deleted file mode 100644
index eec53b2be8d..00000000000
--- a/chromium/chrome/installer/linux/debian/dist_package_versions.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
- "Debian 10 (Buster)": {
- "libappindicator3-1": "0.4.92-6",
- "libasound2": "1.1.6-1",
- "libatk-bridge2.0-0": "2.26.2-1",
- "libatk1.0-0": "2.28.1-1",
- "libatspi2.0-0": "2.28.0-3",
- "libc6": "2.27-5",
- "libcairo2": "1.15.12-1",
- "libcups2": "2.2.8-5",
- "libdbus-1-3": "1.12.10-1",
- "libexpat1": "2.2.6-1",
- "libgcc1": "1:8.2.0-4",
- "libgdk-pixbuf2.0-0": "2.36.12-2",
- "libglib2.0-0": "2.56.1-2",
- "libgtk-3-0": "3.22.30-2",
- "libnspr4": "2:4.19-3",
- "libnss3": "2:3.38-1",
- "libpango-1.0-0": "1.42.4-1",
- "libpangocairo-1.0-0": "1.42.4-1",
- "libstdc++6": "8.2.0-4",
- "libuuid1": "2.32.1-0.1",
- "libx11-6": "2:1.6.6-1",
- "libx11-xcb1": "2:1.6.6-1",
- "libxcb1": "1.13-3",
- "libxcomposite1": "1:0.4.4-2",
- "libxcursor1": "1:1.1.15-1",
- "libxdamage1": "1:1.1.4-3",
- "libxext6": "2:1.3.3-1+b2",
- "libxfixes3": "1:5.0.3-1",
- "libxi6": "2:1.7.9-1",
- "libxrandr2": "2:1.5.1-1",
- "libxrender1": "1:0.9.10-1",
- "libxss1": "1:1.2.2-1+b2",
- "libxtst6": "2:1.2.3-1"
- },
- "Debian 8 (Jessie)": {
- "libappindicator3-1": "0.4.92-3.1",
- "libasound2": "1.0.28-1",
- "libatk-bridge2.0-0": "2.14.0-2",
- "libatk1.0-0": "2.14.0-1",
- "libatspi2.0-0": "2.14.0-1",
- "libc6": "2.19-18+deb8u10",
- "libcairo2": "1.14.0-2.1+deb8u2",
- "libcups2": "1.7.5-11+deb8u4",
- "libdbus-1-3": "1.8.22-0+deb8u1",
- "libexpat1": "2.1.0-6+deb8u4",
- "libgcc1": "1:4.9.2-10+deb8u1",
- "libgdk-pixbuf2.0-0": "2.31.1-2+deb8u7",
- "libglib2.0-0": "2.42.1-1+b1",
- "libgtk-3-0": "3.14.5-1+deb8u1",
- "libnspr4": "2:4.12-1+debu8u1",
- "libnss3": "2:3.26-1+debu8u3",
- "libpango-1.0-0": "1.36.8-3",
- "libpangocairo-1.0-0": "1.36.8-3",
- "libstdc++6": "4.9.2-10+deb8u1",
- "libuuid1": "2.25.2-6",
- "libx11-6": "2:1.6.2-3+deb8u2",
- "libx11-xcb1": "2:1.6.2-3+deb8u2",
- "libxcb1": "1.10-3+b1",
- "libxcomposite1": "1:0.4.4-1",
- "libxcursor1": "1:1.1.14-1+deb8u2",
- "libxdamage1": "1:1.1.4-2+b1",
- "libxext6": "2:1.3.3-1",
- "libxfixes3": "1:5.0.1-2+deb8u1",
- "libxi6": "2:1.7.4-1+deb8u1",
- "libxrandr2": "2:1.4.2-1+deb8u1",
- "libxrender1": "1:0.9.8-1+b1",
- "libxss1": "1:1.2.2-1",
- "libxtst6": "2:1.2.2-1+deb8u1"
- },
- "Debian 9 (Stretch)": {
- "libappindicator3-1": "0.4.92-4",
- "libasound2": "1.1.3-5",
- "libatk-bridge2.0-0": "2.22.0-2",
- "libatk1.0-0": "2.22.0-1",
- "libatspi2.0-0": "2.22.0-6+deb9u1",
- "libc6": "2.24-11+deb9u1",
- "libcairo2": "1.14.8-1",
- "libcups2": "2.2.1-8+deb9u2",
- "libdbus-1-3": "1.10.26-0+deb9u1",
- "libexpat1": "2.2.0-2+deb9u1",
- "libgcc1": "1:6.3.0-18+deb9u1",
- "libgdk-pixbuf2.0-0": "2.36.5-2+deb9u2",
- "libglib2.0-0": "2.50.3-2",
- "libgtk-3-0": "3.22.11-1",
- "libnspr4": "2:4.12-6",
- "libnss3": "2:3.26.2-1.1+deb9u1",
- "libpango-1.0-0": "1.40.5-1",
- "libpangocairo-1.0-0": "1.40.5-1",
- "libstdc++6": "6.3.0-18+deb9u1",
- "libuuid1": "2.29.2-1+deb9u1",
- "libx11-6": "2:1.6.4-3",
- "libx11-xcb1": "2:1.6.4-3",
- "libxcb1": "1.12-1",
- "libxcomposite1": "1:0.4.4-2",
- "libxcursor1": "1:1.1.14-1+deb9u1",
- "libxdamage1": "1:1.1.4-2+b3",
- "libxext6": "2:1.3.3-1+b2",
- "libxfixes3": "1:5.0.3-1",
- "libxi6": "2:1.7.9-1",
- "libxrandr2": "2:1.5.1-1",
- "libxrender1": "1:0.9.10-1",
- "libxss1": "1:1.2.2-1",
- "libxtst6": "2:1.2.3-1"
- },
- "Ubuntu 14.04 (Trusty)": {
- "libappindicator3-1": "12.10.1+13.10.20130920-0ubuntu4.1",
- "libasound2": "1.0.27.2-3ubuntu7",
- "libatk-bridge2.0-0": "2.10.2-2ubuntu1",
- "libatk1.0-0": "2.10.0-2ubuntu2",
- "libatspi2.0-0": "2.10.2.is.2.10.1-0ubuntu1",
- "libc6": "2.19-0ubuntu6.14",
- "libcairo2": "1.13.0~20140204-0ubuntu1.1",
- "libcups2": "1.7.2-0ubuntu1.10",
- "libdbus-1-3": "1.6.18-0ubuntu4.4",
- "libexpat1": "2.1.0-4ubuntu1.4",
- "libgcc1": "1:4.9.3-0ubuntu4",
- "libgdk-pixbuf2.0-0": "2.30.7-0ubuntu1.8",
- "libglib2.0-0": "2.40.2-0ubuntu1",
- "libgtk-3-0": "3.10.8-0ubuntu1.4",
- "libnspr4": "2:4.13.1-0ubuntu0.14.04.1",
- "libnss3": "2:3.28.4-0ubuntu0.14.04.3",
- "libpango-1.0-0": "1.36.3-1ubuntu1.1",
- "libpangocairo-1.0-0": "1.36.3-1ubuntu1.1",
- "libstdc++6": "4.8.4-2ubuntu1~14.04.4",
- "libuuid1": "2.20.1-5.1ubuntu20.9",
- "libx11-6": "2:1.6.2-1ubuntu2.1",
- "libx11-xcb1": "2:1.6.2-1ubuntu2.1",
- "libxcb1": "1.10-2ubuntu1",
- "libxcomposite1": "1:0.4.4-1",
- "libxcursor1": "1:1.1.14-1ubuntu0.14.04.2",
- "libxdamage1": "1:1.1.4-1ubuntu1",
- "libxext6": "2:1.3.2-1ubuntu0.0.14.04.1",
- "libxfixes3": "1:5.0.1-1ubuntu1.1",
- "libxi6": "2:1.7.1.901-1ubuntu1.1",
- "libxrandr2": "2:1.5.0-1~trusty1",
- "libxrender1": "1:0.9.8-1build0.14.04.1",
- "libxss1": "1:1.2.2-1",
- "libxtst6": "2:1.2.2-1"
- },
- "Ubuntu 16.04 (Xenial)": {
- "libappindicator3-1": "12.10.1+16.04.20170215-0ubuntu1",
- "libasound2": "1.1.0-0ubuntu1",
- "libatk-bridge2.0-0": "2.18.1-2ubuntu1",
- "libatk1.0-0": "2.18.0-1",
- "libatspi2.0-0": "2.18.3-4ubuntu1",
- "libc6": "2.23-0ubuntu10",
- "libcairo2": "1.14.6-1",
- "libcups2": "2.1.3-4ubuntu0.5",
- "libdbus-1-3": "1.10.6-1ubuntu3.1",
- "libexpat1": "2.1.0-7ubuntu0.16.04.3",
- "libgcc1": "1:6.0.1-0ubuntu1",
- "libgdk-pixbuf2.0-0": "2.32.2-1ubuntu1.4",
- "libglib2.0-0": "2.48.2-0ubuntu4",
- "libgtk-3-0": "3.18.9-1ubuntu3.3",
- "libnspr4": "2:4.13.1-0ubuntu0.16.04.1",
- "libnss3": "2:3.28.4-0ubuntu0.16.04.3",
- "libpango-1.0-0": "1.38.1-1",
- "libpangocairo-1.0-0": "1.38.1-1",
- "libstdc++6": "5.4.0-6ubuntu1~16.04.10",
- "libuuid1": "2.27.1-6ubuntu3.6",
- "libx11-6": "2:1.6.3-1ubuntu2.1",
- "libx11-xcb1": "2:1.6.3-1ubuntu2.1",
- "libxcb1": "1.11.1-1ubuntu1",
- "libxcomposite1": "1:0.4.4-1",
- "libxcursor1": "1:1.1.14-1ubuntu0.16.04.2",
- "libxdamage1": "1:1.1.4-2",
- "libxext6": "2:1.3.3-1",
- "libxfixes3": "1:5.0.1-2",
- "libxi6": "2:1.7.6-1",
- "libxrandr2": "2:1.5.0-1",
- "libxrender1": "1:0.9.9-0ubuntu1",
- "libxss1": "1:1.2.2-1",
- "libxtst6": "2:1.2.2-1"
- },
- "Ubuntu 17.10 (Artful)": {
- "libappindicator3-1": "12.10.1+17.04.20170215-0ubuntu2",
- "libasound2": "1.1.3-5",
- "libatk-bridge2.0-0": "2.26.0-1ubuntu1",
- "libatk1.0-0": "2.26.0-2ubuntu1",
- "libatspi2.0-0": "2.26.0-2",
- "libc6": "2.26-0ubuntu2.1",
- "libcairo2": "1.14.10-1ubuntu1",
- "libcups2": "2.2.4-7ubuntu3.1",
- "libdbus-1-3": "1.10.22-1ubuntu1",
- "libexpat1": "2.2.3-1",
- "libgcc1": "1:7.2.0-8ubuntu3.2",
- "libgdk-pixbuf2.0-0": "2.36.11-1ubuntu0.1",
- "libglib2.0-0": "2.54.1-1ubuntu1",
- "libgtk-3-0": "3.22.25-0ubuntu0.1",
- "libnspr4": "2:4.16-1ubuntu2",
- "libnss3": "2:3.32-1ubuntu3",
- "libpango-1.0-0": "1.40.12-1",
- "libpangocairo-1.0-0": "1.40.12-1",
- "libstdc++6": "7.2.0-8ubuntu3.2",
- "libuuid1": "2.30.1-0ubuntu4.2",
- "libx11-6": "2:1.6.4-3",
- "libx11-xcb1": "2:1.6.4-3",
- "libxcb1": "1.12-1ubuntu1",
- "libxcomposite1": "1:0.4.4-2",
- "libxcursor1": "1:1.1.14-3ubuntu0.1",
- "libxdamage1": "1:1.1.4-3",
- "libxext6": "2:1.3.3-1",
- "libxfixes3": "1:5.0.3-1",
- "libxi6": "2:1.7.9-1",
- "libxrandr2": "2:1.5.1-1",
- "libxrender1": "1:0.9.10-1",
- "libxss1": "1:1.2.2-1",
- "libxtst6": "2:1.2.3-1"
- }
-}
diff --git a/chromium/chrome/installer/linux/rpm/dist_package_provides.json b/chromium/chrome/installer/linux/rpm/dist_package_provides.json
deleted file mode 100644
index 73d516ef062..00000000000
--- a/chromium/chrome/installer/linux/rpm/dist_package_provides.json
+++ /dev/null
@@ -1,1695 +0,0 @@
-{
- "Fedora 25": [
- "ld-linux-x86-64.so.2()(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)",
- "libX11-xcb.so.1()(64bit)",
- "libX11.so.6()(64bit)",
- "libXcomposite.so.1()(64bit)",
- "libXcursor.so.1()(64bit)",
- "libXdamage.so.1()(64bit)",
- "libXext.so.6()(64bit)",
- "libXfixes.so.3()(64bit)",
- "libXi.so.6()(64bit)",
- "libXrandr.so.2()(64bit)",
- "libXrender.so.1()(64bit)",
- "libXss.so.1()(64bit)",
- "libXtst.so.6()(64bit)",
- "libappindicator3.so.1()(64bit)",
- "libasound.so.2()(64bit)",
- "libasound.so.2(ALSA_0.9)(64bit)",
- "libasound.so.2(ALSA_0.9.0)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc4)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc8)(64bit)",
- "libasound.so.2(ALSA_0.9.3)(64bit)",
- "libasound.so.2(ALSA_0.9.5)(64bit)",
- "libasound.so.2(ALSA_0.9.7)(64bit)",
- "libatk-1.0.so.0()(64bit)",
- "libatk-bridge-2.0.so.0()(64bit)",
- "libatspi.so.0()(64bit)",
- "libc.so.6()(64bit)",
- "libc.so.6(GLIBC_2.10)(64bit)",
- "libc.so.6(GLIBC_2.11)(64bit)",
- "libc.so.6(GLIBC_2.12)(64bit)",
- "libc.so.6(GLIBC_2.13)(64bit)",
- "libc.so.6(GLIBC_2.14)(64bit)",
- "libc.so.6(GLIBC_2.15)(64bit)",
- "libc.so.6(GLIBC_2.16)(64bit)",
- "libc.so.6(GLIBC_2.17)(64bit)",
- "libc.so.6(GLIBC_2.18)(64bit)",
- "libc.so.6(GLIBC_2.2.5)(64bit)",
- "libc.so.6(GLIBC_2.2.6)(64bit)",
- "libc.so.6(GLIBC_2.22)(64bit)",
- "libc.so.6(GLIBC_2.23)(64bit)",
- "libc.so.6(GLIBC_2.24)(64bit)",
- "libc.so.6(GLIBC_2.3)(64bit)",
- "libc.so.6(GLIBC_2.3.2)(64bit)",
- "libc.so.6(GLIBC_2.3.3)(64bit)",
- "libc.so.6(GLIBC_2.3.4)(64bit)",
- "libc.so.6(GLIBC_2.4)(64bit)",
- "libc.so.6(GLIBC_2.5)(64bit)",
- "libc.so.6(GLIBC_2.6)(64bit)",
- "libc.so.6(GLIBC_2.7)(64bit)",
- "libc.so.6(GLIBC_2.8)(64bit)",
- "libc.so.6(GLIBC_2.9)(64bit)",
- "libcairo.so()(64bit)",
- "libcairo.so.2()(64bit)",
- "libcups.so.2()(64bit)",
- "libdbus-1.so.3()(64bit)",
- "libdbus-1.so.3(LIBDBUS_1_3)(64bit)",
- "libdbus-1.so.3(LIBDBUS_PRIVATE_1.11.18)(64bit)",
- "libdl.so.2()(64bit)",
- "libdl.so.2(GLIBC_2.2.5)(64bit)",
- "libdl.so.2(GLIBC_2.3.3)(64bit)",
- "libdl.so.2(GLIBC_2.3.4)(64bit)",
- "libexpat.so.0()(64bit)",
- "libexpat.so.1()(64bit)",
- "libgcc_s.so.1()(64bit)",
- "libgcc_s.so.1(GCC_3.0)(64bit)",
- "libgcc_s.so.1(GCC_3.3)(64bit)",
- "libgcc_s.so.1(GCC_3.3.1)(64bit)",
- "libgcc_s.so.1(GCC_3.4)(64bit)",
- "libgcc_s.so.1(GCC_3.4.2)(64bit)",
- "libgcc_s.so.1(GCC_3.4.4)(64bit)",
- "libgcc_s.so.1(GCC_4.0.0)(64bit)",
- "libgcc_s.so.1(GCC_4.2.0)(64bit)",
- "libgcc_s.so.1(GCC_4.3.0)(64bit)",
- "libgcc_s.so.1(GCC_4.7.0)(64bit)",
- "libgcc_s.so.1(GCC_4.8.0)(64bit)",
- "libgdk-3.so.0()(64bit)",
- "libgdk_pixbuf-2.0.so.0()(64bit)",
- "libgio-2.0.so.0()(64bit)",
- "libglib-2.0.so.0()(64bit)",
- "libgmodule-2.0.so.0()(64bit)",
- "libgobject-2.0.so.0()(64bit)",
- "libgtk-3.so.0()(64bit)",
- "libm.so.6()(64bit)",
- "libm.so.6(GLIBC_2.15)(64bit)",
- "libm.so.6(GLIBC_2.18)(64bit)",
- "libm.so.6(GLIBC_2.2.5)(64bit)",
- "libm.so.6(GLIBC_2.23)(64bit)",
- "libm.so.6(GLIBC_2.24)(64bit)",
- "libm.so.6(GLIBC_2.4)(64bit)",
- "libnspr4.so()(64bit)",
- "libnss3.so()(64bit)",
- "libnss3.so(NSS_3.10)(64bit)",
- "libnss3.so(NSS_3.10.2)(64bit)",
- "libnss3.so(NSS_3.11)(64bit)",
- "libnss3.so(NSS_3.11.1)(64bit)",
- "libnss3.so(NSS_3.11.2)(64bit)",
- "libnss3.so(NSS_3.11.7)(64bit)",
- "libnss3.so(NSS_3.11.9)(64bit)",
- "libnss3.so(NSS_3.12)(64bit)",
- "libnss3.so(NSS_3.12.1)(64bit)",
- "libnss3.so(NSS_3.12.10)(64bit)",
- "libnss3.so(NSS_3.12.3)(64bit)",
- "libnss3.so(NSS_3.12.4)(64bit)",
- "libnss3.so(NSS_3.12.5)(64bit)",
- "libnss3.so(NSS_3.12.6)(64bit)",
- "libnss3.so(NSS_3.12.7)(64bit)",
- "libnss3.so(NSS_3.12.9)(64bit)",
- "libnss3.so(NSS_3.13)(64bit)",
- "libnss3.so(NSS_3.13.2)(64bit)",
- "libnss3.so(NSS_3.14)(64bit)",
- "libnss3.so(NSS_3.14.1)(64bit)",
- "libnss3.so(NSS_3.14.3)(64bit)",
- "libnss3.so(NSS_3.15)(64bit)",
- "libnss3.so(NSS_3.15.4)(64bit)",
- "libnss3.so(NSS_3.16.1)(64bit)",
- "libnss3.so(NSS_3.16.2)(64bit)",
- "libnss3.so(NSS_3.18)(64bit)",
- "libnss3.so(NSS_3.19)(64bit)",
- "libnss3.so(NSS_3.19.1)(64bit)",
- "libnss3.so(NSS_3.2)(64bit)",
- "libnss3.so(NSS_3.2.1)(64bit)",
- "libnss3.so(NSS_3.21)(64bit)",
- "libnss3.so(NSS_3.22)(64bit)",
- "libnss3.so(NSS_3.3)(64bit)",
- "libnss3.so(NSS_3.3.1)(64bit)",
- "libnss3.so(NSS_3.30)(64bit)",
- "libnss3.so(NSS_3.31)(64bit)",
- "libnss3.so(NSS_3.33)(64bit)",
- "libnss3.so(NSS_3.34)(64bit)",
- "libnss3.so(NSS_3.4)(64bit)",
- "libnss3.so(NSS_3.5)(64bit)",
- "libnss3.so(NSS_3.6)(64bit)",
- "libnss3.so(NSS_3.7)(64bit)",
- "libnss3.so(NSS_3.7.1)(64bit)",
- "libnss3.so(NSS_3.8)(64bit)",
- "libnss3.so(NSS_3.9)(64bit)",
- "libnss3.so(NSS_3.9.2)(64bit)",
- "libnss3.so(NSS_3.9.3)(64bit)",
- "libnssutil3.so()(64bit)",
- "libnssutil3.so(NSSUTIL_3.12)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.3)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.5)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.7)(64bit)",
- "libnssutil3.so(NSSUTIL_3.13)(64bit)",
- "libnssutil3.so(NSSUTIL_3.14)(64bit)",
- "libnssutil3.so(NSSUTIL_3.15)(64bit)",
- "libnssutil3.so(NSSUTIL_3.17.1)(64bit)",
- "libnssutil3.so(NSSUTIL_3.21)(64bit)",
- "libnssutil3.so(NSSUTIL_3.24)(64bit)",
- "libnssutil3.so(NSSUTIL_3.25)(64bit)",
- "libnssutil3.so(NSSUTIL_3.31)(64bit)",
- "libnssutil3.so(NSSUTIL_3.33)(64bit)",
- "libpango-1.0.so.0()(64bit)",
- "libpangocairo-1.0.so.0()(64bit)",
- "libpthread.so.0()(64bit)",
- "libpthread.so.0(GLIBC_2.11)(64bit)",
- "libpthread.so.0(GLIBC_2.12)(64bit)",
- "libpthread.so.0(GLIBC_2.18)(64bit)",
- "libpthread.so.0(GLIBC_2.2.5)(64bit)",
- "libpthread.so.0(GLIBC_2.2.6)(64bit)",
- "libpthread.so.0(GLIBC_2.3.2)(64bit)",
- "libpthread.so.0(GLIBC_2.3.3)(64bit)",
- "libpthread.so.0(GLIBC_2.3.4)(64bit)",
- "libpthread.so.0(GLIBC_2.4)(64bit)",
- "librt.so.1()(64bit)",
- "librt.so.1(GLIBC_2.2.5)(64bit)",
- "librt.so.1(GLIBC_2.3.3)(64bit)",
- "librt.so.1(GLIBC_2.3.4)(64bit)",
- "librt.so.1(GLIBC_2.4)(64bit)",
- "librt.so.1(GLIBC_2.7)(64bit)",
- "libsmime3.so()(64bit)",
- "libsmime3.so(NSS_3.10)(64bit)",
- "libsmime3.so(NSS_3.12.10)(64bit)",
- "libsmime3.so(NSS_3.12.2)(64bit)",
- "libsmime3.so(NSS_3.13)(64bit)",
- "libsmime3.so(NSS_3.15)(64bit)",
- "libsmime3.so(NSS_3.16)(64bit)",
- "libsmime3.so(NSS_3.18)(64bit)",
- "libsmime3.so(NSS_3.2)(64bit)",
- "libsmime3.so(NSS_3.2.1)(64bit)",
- "libsmime3.so(NSS_3.3)(64bit)",
- "libsmime3.so(NSS_3.4)(64bit)",
- "libsmime3.so(NSS_3.4.1)(64bit)",
- "libsmime3.so(NSS_3.6)(64bit)",
- "libsmime3.so(NSS_3.7)(64bit)",
- "libsmime3.so(NSS_3.7.2)(64bit)",
- "libsmime3.so(NSS_3.8)(64bit)",
- "libsmime3.so(NSS_3.9)(64bit)",
- "libsmime3.so(NSS_3.9.3)(64bit)",
- "libstdc++.so.5()(64bit)",
- "libstdc++.so.5(CXXABI_1.2)(64bit)",
- "libstdc++.so.5(CXXABI_1.2.1)(64bit)",
- "libstdc++.so.5(CXXABI_1.2.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.1)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.3)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.4)(64bit)",
- "libstdc++.so.6()(64bit)",
- "libstdc++.so.6(CXXABI_1.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.1)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.10)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.2)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.4)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.5)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.6)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.7)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.8)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.9)(64bit)",
- "libstdc++.so.6(CXXABI_FLOAT128)(64bit)",
- "libstdc++.so.6(CXXABI_TM_1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.12)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.13)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.16)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.17)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.18)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.19)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.2)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.20)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.21)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.22)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.3)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.5)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.6)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.7)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.8)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
- "libuuid.so.1()(64bit)",
- "libuuid.so.1(UUIDD_PRIVATE)(64bit)",
- "libuuid.so.1(UUID_1.0)(64bit)",
- "libuuid.so.1(UUID_2.20)(64bit)",
- "libxcb.so.1()(64bit)",
- "rtld(GNU_HASH)"
- ],
- "Fedora 26": [
- "ld-linux-x86-64.so.2()(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)",
- "libX11-xcb.so.1()(64bit)",
- "libX11.so.6()(64bit)",
- "libXcomposite.so.1()(64bit)",
- "libXcursor.so.1()(64bit)",
- "libXdamage.so.1()(64bit)",
- "libXext.so.6()(64bit)",
- "libXfixes.so.3()(64bit)",
- "libXi.so.6()(64bit)",
- "libXrandr.so.2()(64bit)",
- "libXrender.so.1()(64bit)",
- "libXss.so.1()(64bit)",
- "libXtst.so.6()(64bit)",
- "libappindicator3.so.1()(64bit)",
- "libasound.so.2()(64bit)",
- "libasound.so.2(ALSA_0.9)(64bit)",
- "libasound.so.2(ALSA_0.9.0)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc4)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc8)(64bit)",
- "libasound.so.2(ALSA_0.9.3)(64bit)",
- "libasound.so.2(ALSA_0.9.5)(64bit)",
- "libasound.so.2(ALSA_0.9.7)(64bit)",
- "libatk-1.0.so.0()(64bit)",
- "libatk-bridge-2.0.so.0()(64bit)",
- "libatspi.so.0()(64bit)",
- "libc.so.6()(64bit)",
- "libc.so.6(GLIBC_2.10)(64bit)",
- "libc.so.6(GLIBC_2.11)(64bit)",
- "libc.so.6(GLIBC_2.12)(64bit)",
- "libc.so.6(GLIBC_2.13)(64bit)",
- "libc.so.6(GLIBC_2.14)(64bit)",
- "libc.so.6(GLIBC_2.15)(64bit)",
- "libc.so.6(GLIBC_2.16)(64bit)",
- "libc.so.6(GLIBC_2.17)(64bit)",
- "libc.so.6(GLIBC_2.18)(64bit)",
- "libc.so.6(GLIBC_2.2.5)(64bit)",
- "libc.so.6(GLIBC_2.2.6)(64bit)",
- "libc.so.6(GLIBC_2.22)(64bit)",
- "libc.so.6(GLIBC_2.23)(64bit)",
- "libc.so.6(GLIBC_2.24)(64bit)",
- "libc.so.6(GLIBC_2.25)(64bit)",
- "libc.so.6(GLIBC_2.3)(64bit)",
- "libc.so.6(GLIBC_2.3.2)(64bit)",
- "libc.so.6(GLIBC_2.3.3)(64bit)",
- "libc.so.6(GLIBC_2.3.4)(64bit)",
- "libc.so.6(GLIBC_2.4)(64bit)",
- "libc.so.6(GLIBC_2.5)(64bit)",
- "libc.so.6(GLIBC_2.6)(64bit)",
- "libc.so.6(GLIBC_2.7)(64bit)",
- "libc.so.6(GLIBC_2.8)(64bit)",
- "libc.so.6(GLIBC_2.9)(64bit)",
- "libcairo.so()(64bit)",
- "libcairo.so.2()(64bit)",
- "libcups.so.2()(64bit)",
- "libdbus-1.so.3()(64bit)",
- "libdbus-1.so.3(LIBDBUS_1_3)(64bit)",
- "libdbus-1.so.3(LIBDBUS_PRIVATE_1.11.18)(64bit)",
- "libdl.so.2()(64bit)",
- "libdl.so.2(GLIBC_2.2.5)(64bit)",
- "libdl.so.2(GLIBC_2.3.3)(64bit)",
- "libdl.so.2(GLIBC_2.3.4)(64bit)",
- "libexpat.so.0()(64bit)",
- "libexpat.so.1()(64bit)",
- "libgcc_s.so.1()(64bit)",
- "libgcc_s.so.1(GCC_3.0)(64bit)",
- "libgcc_s.so.1(GCC_3.3)(64bit)",
- "libgcc_s.so.1(GCC_3.3.1)(64bit)",
- "libgcc_s.so.1(GCC_3.4)(64bit)",
- "libgcc_s.so.1(GCC_3.4.2)(64bit)",
- "libgcc_s.so.1(GCC_3.4.4)(64bit)",
- "libgcc_s.so.1(GCC_4.0.0)(64bit)",
- "libgcc_s.so.1(GCC_4.2.0)(64bit)",
- "libgcc_s.so.1(GCC_4.3.0)(64bit)",
- "libgcc_s.so.1(GCC_4.7.0)(64bit)",
- "libgcc_s.so.1(GCC_4.8.0)(64bit)",
- "libgcc_s.so.1(GCC_7.0.0)(64bit)",
- "libgdk-3.so.0()(64bit)",
- "libgdk_pixbuf-2.0.so.0()(64bit)",
- "libgio-2.0.so.0()(64bit)",
- "libglib-2.0.so.0()(64bit)",
- "libgmodule-2.0.so.0()(64bit)",
- "libgobject-2.0.so.0()(64bit)",
- "libgtk-3.so.0()(64bit)",
- "libm.so.6()(64bit)",
- "libm.so.6(GLIBC_2.15)(64bit)",
- "libm.so.6(GLIBC_2.18)(64bit)",
- "libm.so.6(GLIBC_2.2.5)(64bit)",
- "libm.so.6(GLIBC_2.23)(64bit)",
- "libm.so.6(GLIBC_2.24)(64bit)",
- "libm.so.6(GLIBC_2.25)(64bit)",
- "libm.so.6(GLIBC_2.4)(64bit)",
- "libnspr4.so()(64bit)",
- "libnss3.so()(64bit)",
- "libnss3.so(NSS_3.10)(64bit)",
- "libnss3.so(NSS_3.10.2)(64bit)",
- "libnss3.so(NSS_3.11)(64bit)",
- "libnss3.so(NSS_3.11.1)(64bit)",
- "libnss3.so(NSS_3.11.2)(64bit)",
- "libnss3.so(NSS_3.11.7)(64bit)",
- "libnss3.so(NSS_3.11.9)(64bit)",
- "libnss3.so(NSS_3.12)(64bit)",
- "libnss3.so(NSS_3.12.1)(64bit)",
- "libnss3.so(NSS_3.12.10)(64bit)",
- "libnss3.so(NSS_3.12.3)(64bit)",
- "libnss3.so(NSS_3.12.4)(64bit)",
- "libnss3.so(NSS_3.12.5)(64bit)",
- "libnss3.so(NSS_3.12.6)(64bit)",
- "libnss3.so(NSS_3.12.7)(64bit)",
- "libnss3.so(NSS_3.12.9)(64bit)",
- "libnss3.so(NSS_3.13)(64bit)",
- "libnss3.so(NSS_3.13.2)(64bit)",
- "libnss3.so(NSS_3.14)(64bit)",
- "libnss3.so(NSS_3.14.1)(64bit)",
- "libnss3.so(NSS_3.14.3)(64bit)",
- "libnss3.so(NSS_3.15)(64bit)",
- "libnss3.so(NSS_3.15.4)(64bit)",
- "libnss3.so(NSS_3.16.1)(64bit)",
- "libnss3.so(NSS_3.16.2)(64bit)",
- "libnss3.so(NSS_3.18)(64bit)",
- "libnss3.so(NSS_3.19)(64bit)",
- "libnss3.so(NSS_3.19.1)(64bit)",
- "libnss3.so(NSS_3.2)(64bit)",
- "libnss3.so(NSS_3.2.1)(64bit)",
- "libnss3.so(NSS_3.21)(64bit)",
- "libnss3.so(NSS_3.22)(64bit)",
- "libnss3.so(NSS_3.3)(64bit)",
- "libnss3.so(NSS_3.3.1)(64bit)",
- "libnss3.so(NSS_3.30)(64bit)",
- "libnss3.so(NSS_3.31)(64bit)",
- "libnss3.so(NSS_3.33)(64bit)",
- "libnss3.so(NSS_3.34)(64bit)",
- "libnss3.so(NSS_3.4)(64bit)",
- "libnss3.so(NSS_3.5)(64bit)",
- "libnss3.so(NSS_3.6)(64bit)",
- "libnss3.so(NSS_3.7)(64bit)",
- "libnss3.so(NSS_3.7.1)(64bit)",
- "libnss3.so(NSS_3.8)(64bit)",
- "libnss3.so(NSS_3.9)(64bit)",
- "libnss3.so(NSS_3.9.2)(64bit)",
- "libnss3.so(NSS_3.9.3)(64bit)",
- "libnssutil3.so()(64bit)",
- "libnssutil3.so(NSSUTIL_3.12)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.3)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.5)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.7)(64bit)",
- "libnssutil3.so(NSSUTIL_3.13)(64bit)",
- "libnssutil3.so(NSSUTIL_3.14)(64bit)",
- "libnssutil3.so(NSSUTIL_3.15)(64bit)",
- "libnssutil3.so(NSSUTIL_3.17.1)(64bit)",
- "libnssutil3.so(NSSUTIL_3.21)(64bit)",
- "libnssutil3.so(NSSUTIL_3.24)(64bit)",
- "libnssutil3.so(NSSUTIL_3.25)(64bit)",
- "libnssutil3.so(NSSUTIL_3.31)(64bit)",
- "libnssutil3.so(NSSUTIL_3.33)(64bit)",
- "libpango-1.0.so.0()(64bit)",
- "libpangocairo-1.0.so.0()(64bit)",
- "libpthread.so.0()(64bit)",
- "libpthread.so.0(GLIBC_2.11)(64bit)",
- "libpthread.so.0(GLIBC_2.12)(64bit)",
- "libpthread.so.0(GLIBC_2.18)(64bit)",
- "libpthread.so.0(GLIBC_2.2.5)(64bit)",
- "libpthread.so.0(GLIBC_2.2.6)(64bit)",
- "libpthread.so.0(GLIBC_2.3.2)(64bit)",
- "libpthread.so.0(GLIBC_2.3.3)(64bit)",
- "libpthread.so.0(GLIBC_2.3.4)(64bit)",
- "libpthread.so.0(GLIBC_2.4)(64bit)",
- "librt.so.1()(64bit)",
- "librt.so.1(GLIBC_2.2.5)(64bit)",
- "librt.so.1(GLIBC_2.3.3)(64bit)",
- "librt.so.1(GLIBC_2.3.4)(64bit)",
- "librt.so.1(GLIBC_2.4)(64bit)",
- "librt.so.1(GLIBC_2.7)(64bit)",
- "libsmime3.so()(64bit)",
- "libsmime3.so(NSS_3.10)(64bit)",
- "libsmime3.so(NSS_3.12.10)(64bit)",
- "libsmime3.so(NSS_3.12.2)(64bit)",
- "libsmime3.so(NSS_3.13)(64bit)",
- "libsmime3.so(NSS_3.15)(64bit)",
- "libsmime3.so(NSS_3.16)(64bit)",
- "libsmime3.so(NSS_3.18)(64bit)",
- "libsmime3.so(NSS_3.2)(64bit)",
- "libsmime3.so(NSS_3.2.1)(64bit)",
- "libsmime3.so(NSS_3.3)(64bit)",
- "libsmime3.so(NSS_3.4)(64bit)",
- "libsmime3.so(NSS_3.4.1)(64bit)",
- "libsmime3.so(NSS_3.6)(64bit)",
- "libsmime3.so(NSS_3.7)(64bit)",
- "libsmime3.so(NSS_3.7.2)(64bit)",
- "libsmime3.so(NSS_3.8)(64bit)",
- "libsmime3.so(NSS_3.9)(64bit)",
- "libsmime3.so(NSS_3.9.3)(64bit)",
- "libstdc++.so.5()(64bit)",
- "libstdc++.so.5(CXXABI_1.2)(64bit)",
- "libstdc++.so.5(CXXABI_1.2.1)(64bit)",
- "libstdc++.so.5(CXXABI_1.2.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.1)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.3)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.4)(64bit)",
- "libstdc++.so.6()(64bit)",
- "libstdc++.so.6(CXXABI_1.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.1)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.10)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.11)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.2)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.4)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.5)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.6)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.7)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.8)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.9)(64bit)",
- "libstdc++.so.6(CXXABI_FLOAT128)(64bit)",
- "libstdc++.so.6(CXXABI_TM_1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.12)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.13)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.16)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.17)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.18)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.19)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.2)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.20)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.21)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.22)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.23)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.24)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.3)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.5)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.6)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.7)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.8)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
- "libuuid.so.1()(64bit)",
- "libuuid.so.1(UUIDD_PRIVATE)(64bit)",
- "libuuid.so.1(UUID_1.0)(64bit)",
- "libuuid.so.1(UUID_2.20)(64bit)",
- "libxcb.so.1()(64bit)",
- "rtld(GNU_HASH)"
- ],
- "Fedora 27": [
- "ld-linux-x86-64.so.2()(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)",
- "libX11-xcb.so.1()(64bit)",
- "libX11.so.6()(64bit)",
- "libXcomposite.so.1()(64bit)",
- "libXcursor.so.1()(64bit)",
- "libXdamage.so.1()(64bit)",
- "libXext.so.6()(64bit)",
- "libXfixes.so.3()(64bit)",
- "libXi.so.6()(64bit)",
- "libXrandr.so.2()(64bit)",
- "libXrender.so.1()(64bit)",
- "libXss.so.1()(64bit)",
- "libXtst.so.6()(64bit)",
- "libappindicator3.so.1()(64bit)",
- "libasound.so.2()(64bit)",
- "libasound.so.2(ALSA_0.9)(64bit)",
- "libasound.so.2(ALSA_0.9.0)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc4)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc8)(64bit)",
- "libasound.so.2(ALSA_0.9.3)(64bit)",
- "libasound.so.2(ALSA_0.9.5)(64bit)",
- "libasound.so.2(ALSA_0.9.7)(64bit)",
- "libatk-1.0.so.0()(64bit)",
- "libatk-bridge-2.0.so.0()(64bit)",
- "libatspi.so.0()(64bit)",
- "libc.so.6()(64bit)",
- "libc.so.6(GLIBC_2.10)(64bit)",
- "libc.so.6(GLIBC_2.11)(64bit)",
- "libc.so.6(GLIBC_2.12)(64bit)",
- "libc.so.6(GLIBC_2.13)(64bit)",
- "libc.so.6(GLIBC_2.14)(64bit)",
- "libc.so.6(GLIBC_2.15)(64bit)",
- "libc.so.6(GLIBC_2.16)(64bit)",
- "libc.so.6(GLIBC_2.17)(64bit)",
- "libc.so.6(GLIBC_2.18)(64bit)",
- "libc.so.6(GLIBC_2.2.5)(64bit)",
- "libc.so.6(GLIBC_2.2.6)(64bit)",
- "libc.so.6(GLIBC_2.22)(64bit)",
- "libc.so.6(GLIBC_2.23)(64bit)",
- "libc.so.6(GLIBC_2.24)(64bit)",
- "libc.so.6(GLIBC_2.25)(64bit)",
- "libc.so.6(GLIBC_2.26)(64bit)",
- "libc.so.6(GLIBC_2.3)(64bit)",
- "libc.so.6(GLIBC_2.3.2)(64bit)",
- "libc.so.6(GLIBC_2.3.3)(64bit)",
- "libc.so.6(GLIBC_2.3.4)(64bit)",
- "libc.so.6(GLIBC_2.4)(64bit)",
- "libc.so.6(GLIBC_2.5)(64bit)",
- "libc.so.6(GLIBC_2.6)(64bit)",
- "libc.so.6(GLIBC_2.7)(64bit)",
- "libc.so.6(GLIBC_2.8)(64bit)",
- "libc.so.6(GLIBC_2.9)(64bit)",
- "libcairo.so()(64bit)",
- "libcairo.so.2()(64bit)",
- "libcups.so.2()(64bit)",
- "libdbus-1.so.3()(64bit)",
- "libdbus-1.so.3(LIBDBUS_1_3)(64bit)",
- "libdbus-1.so.3(LIBDBUS_PRIVATE_1.12.8)(64bit)",
- "libdl.so.2()(64bit)",
- "libdl.so.2(GLIBC_2.2.5)(64bit)",
- "libdl.so.2(GLIBC_2.3.3)(64bit)",
- "libdl.so.2(GLIBC_2.3.4)(64bit)",
- "libexpat.so.1()(64bit)",
- "libgcc_s.so.1()(64bit)",
- "libgcc_s.so.1(GCC_3.0)(64bit)",
- "libgcc_s.so.1(GCC_3.3)(64bit)",
- "libgcc_s.so.1(GCC_3.3.1)(64bit)",
- "libgcc_s.so.1(GCC_3.4)(64bit)",
- "libgcc_s.so.1(GCC_3.4.2)(64bit)",
- "libgcc_s.so.1(GCC_3.4.4)(64bit)",
- "libgcc_s.so.1(GCC_4.0.0)(64bit)",
- "libgcc_s.so.1(GCC_4.2.0)(64bit)",
- "libgcc_s.so.1(GCC_4.3.0)(64bit)",
- "libgcc_s.so.1(GCC_4.7.0)(64bit)",
- "libgcc_s.so.1(GCC_4.8.0)(64bit)",
- "libgcc_s.so.1(GCC_7.0.0)(64bit)",
- "libgdk-3.so.0()(64bit)",
- "libgdk_pixbuf-2.0.so.0()(64bit)",
- "libgio-2.0.so.0()(64bit)",
- "libglib-2.0.so.0()(64bit)",
- "libgmodule-2.0.so.0()(64bit)",
- "libgobject-2.0.so.0()(64bit)",
- "libgtk-3.so.0()(64bit)",
- "libm.so.6()(64bit)",
- "libm.so.6(GLIBC_2.15)(64bit)",
- "libm.so.6(GLIBC_2.18)(64bit)",
- "libm.so.6(GLIBC_2.2.5)(64bit)",
- "libm.so.6(GLIBC_2.23)(64bit)",
- "libm.so.6(GLIBC_2.24)(64bit)",
- "libm.so.6(GLIBC_2.25)(64bit)",
- "libm.so.6(GLIBC_2.26)(64bit)",
- "libm.so.6(GLIBC_2.4)(64bit)",
- "libnspr4.so()(64bit)",
- "libnss3.so()(64bit)",
- "libnss3.so(NSS_3.10)(64bit)",
- "libnss3.so(NSS_3.10.2)(64bit)",
- "libnss3.so(NSS_3.11)(64bit)",
- "libnss3.so(NSS_3.11.1)(64bit)",
- "libnss3.so(NSS_3.11.2)(64bit)",
- "libnss3.so(NSS_3.11.7)(64bit)",
- "libnss3.so(NSS_3.11.9)(64bit)",
- "libnss3.so(NSS_3.12)(64bit)",
- "libnss3.so(NSS_3.12.1)(64bit)",
- "libnss3.so(NSS_3.12.10)(64bit)",
- "libnss3.so(NSS_3.12.3)(64bit)",
- "libnss3.so(NSS_3.12.4)(64bit)",
- "libnss3.so(NSS_3.12.5)(64bit)",
- "libnss3.so(NSS_3.12.6)(64bit)",
- "libnss3.so(NSS_3.12.7)(64bit)",
- "libnss3.so(NSS_3.12.9)(64bit)",
- "libnss3.so(NSS_3.13)(64bit)",
- "libnss3.so(NSS_3.13.2)(64bit)",
- "libnss3.so(NSS_3.14)(64bit)",
- "libnss3.so(NSS_3.14.1)(64bit)",
- "libnss3.so(NSS_3.14.3)(64bit)",
- "libnss3.so(NSS_3.15)(64bit)",
- "libnss3.so(NSS_3.15.4)(64bit)",
- "libnss3.so(NSS_3.16.1)(64bit)",
- "libnss3.so(NSS_3.16.2)(64bit)",
- "libnss3.so(NSS_3.18)(64bit)",
- "libnss3.so(NSS_3.19)(64bit)",
- "libnss3.so(NSS_3.19.1)(64bit)",
- "libnss3.so(NSS_3.2)(64bit)",
- "libnss3.so(NSS_3.2.1)(64bit)",
- "libnss3.so(NSS_3.21)(64bit)",
- "libnss3.so(NSS_3.22)(64bit)",
- "libnss3.so(NSS_3.3)(64bit)",
- "libnss3.so(NSS_3.3.1)(64bit)",
- "libnss3.so(NSS_3.30)(64bit)",
- "libnss3.so(NSS_3.31)(64bit)",
- "libnss3.so(NSS_3.33)(64bit)",
- "libnss3.so(NSS_3.34)(64bit)",
- "libnss3.so(NSS_3.4)(64bit)",
- "libnss3.so(NSS_3.5)(64bit)",
- "libnss3.so(NSS_3.6)(64bit)",
- "libnss3.so(NSS_3.7)(64bit)",
- "libnss3.so(NSS_3.7.1)(64bit)",
- "libnss3.so(NSS_3.8)(64bit)",
- "libnss3.so(NSS_3.9)(64bit)",
- "libnss3.so(NSS_3.9.2)(64bit)",
- "libnss3.so(NSS_3.9.3)(64bit)",
- "libnssutil3.so()(64bit)",
- "libnssutil3.so(NSSUTIL_3.12)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.3)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.5)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.7)(64bit)",
- "libnssutil3.so(NSSUTIL_3.13)(64bit)",
- "libnssutil3.so(NSSUTIL_3.14)(64bit)",
- "libnssutil3.so(NSSUTIL_3.15)(64bit)",
- "libnssutil3.so(NSSUTIL_3.17.1)(64bit)",
- "libnssutil3.so(NSSUTIL_3.21)(64bit)",
- "libnssutil3.so(NSSUTIL_3.24)(64bit)",
- "libnssutil3.so(NSSUTIL_3.25)(64bit)",
- "libnssutil3.so(NSSUTIL_3.31)(64bit)",
- "libnssutil3.so(NSSUTIL_3.33)(64bit)",
- "libnssutil3.so(NSSUTIL_3.38)(64bit)",
- "libpango-1.0.so.0()(64bit)",
- "libpangocairo-1.0.so.0()(64bit)",
- "libpthread.so.0()(64bit)",
- "libpthread.so.0(GLIBC_2.11)(64bit)",
- "libpthread.so.0(GLIBC_2.12)(64bit)",
- "libpthread.so.0(GLIBC_2.18)(64bit)",
- "libpthread.so.0(GLIBC_2.2.5)(64bit)",
- "libpthread.so.0(GLIBC_2.2.6)(64bit)",
- "libpthread.so.0(GLIBC_2.3.2)(64bit)",
- "libpthread.so.0(GLIBC_2.3.3)(64bit)",
- "libpthread.so.0(GLIBC_2.3.4)(64bit)",
- "libpthread.so.0(GLIBC_2.4)(64bit)",
- "librt.so.1()(64bit)",
- "librt.so.1(GLIBC_2.2.5)(64bit)",
- "librt.so.1(GLIBC_2.3.3)(64bit)",
- "librt.so.1(GLIBC_2.3.4)(64bit)",
- "librt.so.1(GLIBC_2.4)(64bit)",
- "librt.so.1(GLIBC_2.7)(64bit)",
- "libsmime3.so()(64bit)",
- "libsmime3.so(NSS_3.10)(64bit)",
- "libsmime3.so(NSS_3.12.10)(64bit)",
- "libsmime3.so(NSS_3.12.2)(64bit)",
- "libsmime3.so(NSS_3.13)(64bit)",
- "libsmime3.so(NSS_3.15)(64bit)",
- "libsmime3.so(NSS_3.16)(64bit)",
- "libsmime3.so(NSS_3.18)(64bit)",
- "libsmime3.so(NSS_3.2)(64bit)",
- "libsmime3.so(NSS_3.2.1)(64bit)",
- "libsmime3.so(NSS_3.3)(64bit)",
- "libsmime3.so(NSS_3.4)(64bit)",
- "libsmime3.so(NSS_3.4.1)(64bit)",
- "libsmime3.so(NSS_3.6)(64bit)",
- "libsmime3.so(NSS_3.7)(64bit)",
- "libsmime3.so(NSS_3.7.2)(64bit)",
- "libsmime3.so(NSS_3.8)(64bit)",
- "libsmime3.so(NSS_3.9)(64bit)",
- "libsmime3.so(NSS_3.9.3)(64bit)",
- "libstdc++.so.5()(64bit)",
- "libstdc++.so.5(CXXABI_1.2)(64bit)",
- "libstdc++.so.5(CXXABI_1.2.1)(64bit)",
- "libstdc++.so.5(CXXABI_1.2.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.1)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.2)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.3)(64bit)",
- "libstdc++.so.5(GLIBCPP_3.2.4)(64bit)",
- "libstdc++.so.6()(64bit)",
- "libstdc++.so.6(CXXABI_1.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.1)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.10)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.11)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.2)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.4)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.5)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.6)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.7)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.8)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.9)(64bit)",
- "libstdc++.so.6(CXXABI_FLOAT128)(64bit)",
- "libstdc++.so.6(CXXABI_TM_1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.12)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.13)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.16)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.17)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.18)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.19)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.2)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.20)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.21)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.22)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.23)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.24)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.3)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.5)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.6)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.7)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.8)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
- "libuuid.so.1()(64bit)",
- "libuuid.so.1(UUIDD_PRIVATE)(64bit)",
- "libuuid.so.1(UUID_1.0)(64bit)",
- "libuuid.so.1(UUID_2.20)(64bit)",
- "libxcb.so.1()(64bit)",
- "rtld(GNU_HASH)"
- ],
- "openSUSE Leap 42.2": [
- "ld-linux-x86-64.so.2()(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)",
- "libX11-xcb.so.1",
- "libX11-xcb.so.1()(64bit)",
- "libX11.so.6",
- "libX11.so.6()(64bit)",
- "libXcomposite.so.1",
- "libXcomposite.so.1()(64bit)",
- "libXcursor.so.1",
- "libXcursor.so.1()(64bit)",
- "libXdamage.so.1",
- "libXdamage.so.1()(64bit)",
- "libXext.so.6",
- "libXext.so.6()(64bit)",
- "libXfixes.so.3",
- "libXfixes.so.3()(64bit)",
- "libXi.so.6",
- "libXi.so.6()(64bit)",
- "libXrandr.so.2",
- "libXrandr.so.2()(64bit)",
- "libXrender.so.1",
- "libXrender.so.1()(64bit)",
- "libXss.so.1",
- "libXss.so.1()(64bit)",
- "libXtst.so.6",
- "libXtst.so.6()(64bit)",
- "libappindicator3.so.1()(64bit)",
- "libasound.so.2",
- "libasound.so.2()(64bit)",
- "libasound.so.2(ALSA_0.9)",
- "libasound.so.2(ALSA_0.9)(64bit)",
- "libasound.so.2(ALSA_0.9.0)",
- "libasound.so.2(ALSA_0.9.0)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc4)",
- "libasound.so.2(ALSA_0.9.0rc4)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc8)",
- "libasound.so.2(ALSA_0.9.0rc8)(64bit)",
- "libasound.so.2(ALSA_0.9.3)",
- "libasound.so.2(ALSA_0.9.3)(64bit)",
- "libasound.so.2(ALSA_0.9.5)",
- "libasound.so.2(ALSA_0.9.5)(64bit)",
- "libasound.so.2(ALSA_0.9.7)",
- "libasound.so.2(ALSA_0.9.7)(64bit)",
- "libatk-1.0.so.0",
- "libatk-1.0.so.0()(64bit)",
- "libatk-bridge-2.0.so.0",
- "libatk-bridge-2.0.so.0()(64bit)",
- "libatspi.so.0",
- "libatspi.so.0()(64bit)",
- "libc.so.6",
- "libc.so.6()(64bit)",
- "libc.so.6(GCC_3.0)",
- "libc.so.6(GLIBC_2.0)",
- "libc.so.6(GLIBC_2.1)",
- "libc.so.6(GLIBC_2.1.1)",
- "libc.so.6(GLIBC_2.1.2)",
- "libc.so.6(GLIBC_2.1.3)",
- "libc.so.6(GLIBC_2.10)",
- "libc.so.6(GLIBC_2.10)(64bit)",
- "libc.so.6(GLIBC_2.11)",
- "libc.so.6(GLIBC_2.11)(64bit)",
- "libc.so.6(GLIBC_2.12)",
- "libc.so.6(GLIBC_2.12)(64bit)",
- "libc.so.6(GLIBC_2.13)",
- "libc.so.6(GLIBC_2.13)(64bit)",
- "libc.so.6(GLIBC_2.14)",
- "libc.so.6(GLIBC_2.14)(64bit)",
- "libc.so.6(GLIBC_2.15)",
- "libc.so.6(GLIBC_2.15)(64bit)",
- "libc.so.6(GLIBC_2.16)",
- "libc.so.6(GLIBC_2.16)(64bit)",
- "libc.so.6(GLIBC_2.17)",
- "libc.so.6(GLIBC_2.17)(64bit)",
- "libc.so.6(GLIBC_2.18)",
- "libc.so.6(GLIBC_2.18)(64bit)",
- "libc.so.6(GLIBC_2.2)",
- "libc.so.6(GLIBC_2.2.1)",
- "libc.so.6(GLIBC_2.2.2)",
- "libc.so.6(GLIBC_2.2.3)",
- "libc.so.6(GLIBC_2.2.4)",
- "libc.so.6(GLIBC_2.2.5)(64bit)",
- "libc.so.6(GLIBC_2.2.6)",
- "libc.so.6(GLIBC_2.2.6)(64bit)",
- "libc.so.6(GLIBC_2.22)",
- "libc.so.6(GLIBC_2.22)(64bit)",
- "libc.so.6(GLIBC_2.3)",
- "libc.so.6(GLIBC_2.3)(64bit)",
- "libc.so.6(GLIBC_2.3.2)",
- "libc.so.6(GLIBC_2.3.2)(64bit)",
- "libc.so.6(GLIBC_2.3.3)",
- "libc.so.6(GLIBC_2.3.3)(64bit)",
- "libc.so.6(GLIBC_2.3.4)",
- "libc.so.6(GLIBC_2.3.4)(64bit)",
- "libc.so.6(GLIBC_2.4)",
- "libc.so.6(GLIBC_2.4)(64bit)",
- "libc.so.6(GLIBC_2.5)",
- "libc.so.6(GLIBC_2.5)(64bit)",
- "libc.so.6(GLIBC_2.6)",
- "libc.so.6(GLIBC_2.6)(64bit)",
- "libc.so.6(GLIBC_2.7)",
- "libc.so.6(GLIBC_2.7)(64bit)",
- "libc.so.6(GLIBC_2.8)",
- "libc.so.6(GLIBC_2.8)(64bit)",
- "libc.so.6(GLIBC_2.9)",
- "libc.so.6(GLIBC_2.9)(64bit)",
- "libc.so.6(GLIBC_PRIVATE)",
- "libcairo.so()(64bit)",
- "libcairo.so.2",
- "libcairo.so.2()(64bit)",
- "libcups.so.2",
- "libcups.so.2()(64bit)",
- "libdbus-1.so.3",
- "libdbus-1.so.3()(64bit)",
- "libdl.so.2",
- "libdl.so.2()(64bit)",
- "libdl.so.2(GLIBC_2.0)",
- "libdl.so.2(GLIBC_2.1)",
- "libdl.so.2(GLIBC_2.2.5)(64bit)",
- "libdl.so.2(GLIBC_2.3.3)",
- "libdl.so.2(GLIBC_2.3.3)(64bit)",
- "libdl.so.2(GLIBC_2.3.4)",
- "libdl.so.2(GLIBC_2.3.4)(64bit)",
- "libdl.so.2(GLIBC_PRIVATE)",
- "libexpat.so.0",
- "libexpat.so.0()(64bit)",
- "libexpat.so.1",
- "libexpat.so.1()(64bit)",
- "libgcc_s.so.1",
- "libgcc_s.so.1()(64bit)",
- "libgcc_s.so.1(GCC_3.0)",
- "libgcc_s.so.1(GCC_3.0)(64bit)",
- "libgcc_s.so.1(GCC_3.3)",
- "libgcc_s.so.1(GCC_3.3)(64bit)",
- "libgcc_s.so.1(GCC_3.3.1)",
- "libgcc_s.so.1(GCC_3.3.1)(64bit)",
- "libgcc_s.so.1(GCC_3.4)",
- "libgcc_s.so.1(GCC_3.4)(64bit)",
- "libgcc_s.so.1(GCC_3.4.2)",
- "libgcc_s.so.1(GCC_3.4.2)(64bit)",
- "libgcc_s.so.1(GCC_3.4.4)(64bit)",
- "libgcc_s.so.1(GCC_4.0.0)",
- "libgcc_s.so.1(GCC_4.0.0)(64bit)",
- "libgcc_s.so.1(GCC_4.2.0)",
- "libgcc_s.so.1(GCC_4.2.0)(64bit)",
- "libgcc_s.so.1(GCC_4.3.0)",
- "libgcc_s.so.1(GCC_4.3.0)(64bit)",
- "libgcc_s.so.1(GCC_4.4.0)",
- "libgcc_s.so.1(GCC_4.5.0)",
- "libgcc_s.so.1(GCC_4.7.0)",
- "libgcc_s.so.1(GCC_4.7.0)(64bit)",
- "libgcc_s.so.1(GCC_4.8.0)",
- "libgcc_s.so.1(GCC_4.8.0)(64bit)",
- "libgcc_s.so.1(GLIBC_2.0)",
- "libgdk-3.so.0",
- "libgdk-3.so.0()(64bit)",
- "libgdk_pixbuf-2.0.so.0",
- "libgdk_pixbuf-2.0.so.0()(64bit)",
- "libgio-2.0.so.0",
- "libgio-2.0.so.0()(64bit)",
- "libglib-2.0.so.0",
- "libglib-2.0.so.0()(64bit)",
- "libgmodule-2.0.so.0",
- "libgmodule-2.0.so.0()(64bit)",
- "libgobject-2.0.so.0",
- "libgobject-2.0.so.0()(64bit)",
- "libgtk-3.so.0",
- "libgtk-3.so.0()(64bit)",
- "libm.so.6",
- "libm.so.6()(64bit)",
- "libm.so.6(GLIBC_2.0)",
- "libm.so.6(GLIBC_2.1)",
- "libm.so.6(GLIBC_2.15)",
- "libm.so.6(GLIBC_2.15)(64bit)",
- "libm.so.6(GLIBC_2.18)",
- "libm.so.6(GLIBC_2.18)(64bit)",
- "libm.so.6(GLIBC_2.2)",
- "libm.so.6(GLIBC_2.2.5)(64bit)",
- "libm.so.6(GLIBC_2.4)",
- "libm.so.6(GLIBC_2.4)(64bit)",
- "libnspr4.so",
- "libnspr4.so()(64bit)",
- "libnss3.so",
- "libnss3.so()(64bit)",
- "libnss3.so(NSS_3.10)",
- "libnss3.so(NSS_3.10)(64bit)",
- "libnss3.so(NSS_3.10.2)",
- "libnss3.so(NSS_3.10.2)(64bit)",
- "libnss3.so(NSS_3.11)",
- "libnss3.so(NSS_3.11)(64bit)",
- "libnss3.so(NSS_3.11.1)",
- "libnss3.so(NSS_3.11.1)(64bit)",
- "libnss3.so(NSS_3.11.2)",
- "libnss3.so(NSS_3.11.2)(64bit)",
- "libnss3.so(NSS_3.11.7)",
- "libnss3.so(NSS_3.11.7)(64bit)",
- "libnss3.so(NSS_3.11.9)",
- "libnss3.so(NSS_3.11.9)(64bit)",
- "libnss3.so(NSS_3.12)",
- "libnss3.so(NSS_3.12)(64bit)",
- "libnss3.so(NSS_3.12.1)",
- "libnss3.so(NSS_3.12.1)(64bit)",
- "libnss3.so(NSS_3.12.10)",
- "libnss3.so(NSS_3.12.10)(64bit)",
- "libnss3.so(NSS_3.12.3)",
- "libnss3.so(NSS_3.12.3)(64bit)",
- "libnss3.so(NSS_3.12.4)",
- "libnss3.so(NSS_3.12.4)(64bit)",
- "libnss3.so(NSS_3.12.5)",
- "libnss3.so(NSS_3.12.5)(64bit)",
- "libnss3.so(NSS_3.12.6)",
- "libnss3.so(NSS_3.12.6)(64bit)",
- "libnss3.so(NSS_3.12.7)",
- "libnss3.so(NSS_3.12.7)(64bit)",
- "libnss3.so(NSS_3.12.9)",
- "libnss3.so(NSS_3.12.9)(64bit)",
- "libnss3.so(NSS_3.13)",
- "libnss3.so(NSS_3.13)(64bit)",
- "libnss3.so(NSS_3.13.2)",
- "libnss3.so(NSS_3.13.2)(64bit)",
- "libnss3.so(NSS_3.14)",
- "libnss3.so(NSS_3.14)(64bit)",
- "libnss3.so(NSS_3.14.1)",
- "libnss3.so(NSS_3.14.1)(64bit)",
- "libnss3.so(NSS_3.14.3)",
- "libnss3.so(NSS_3.14.3)(64bit)",
- "libnss3.so(NSS_3.15)",
- "libnss3.so(NSS_3.15)(64bit)",
- "libnss3.so(NSS_3.15.4)",
- "libnss3.so(NSS_3.15.4)(64bit)",
- "libnss3.so(NSS_3.16.1)",
- "libnss3.so(NSS_3.16.1)(64bit)",
- "libnss3.so(NSS_3.16.2)",
- "libnss3.so(NSS_3.16.2)(64bit)",
- "libnss3.so(NSS_3.18)",
- "libnss3.so(NSS_3.18)(64bit)",
- "libnss3.so(NSS_3.19)",
- "libnss3.so(NSS_3.19)(64bit)",
- "libnss3.so(NSS_3.19.1)",
- "libnss3.so(NSS_3.19.1)(64bit)",
- "libnss3.so(NSS_3.2)",
- "libnss3.so(NSS_3.2)(64bit)",
- "libnss3.so(NSS_3.2.1)",
- "libnss3.so(NSS_3.2.1)(64bit)",
- "libnss3.so(NSS_3.21)",
- "libnss3.so(NSS_3.21)(64bit)",
- "libnss3.so(NSS_3.22)",
- "libnss3.so(NSS_3.22)(64bit)",
- "libnss3.so(NSS_3.3)",
- "libnss3.so(NSS_3.3)(64bit)",
- "libnss3.so(NSS_3.3.1)",
- "libnss3.so(NSS_3.3.1)(64bit)",
- "libnss3.so(NSS_3.4)",
- "libnss3.so(NSS_3.4)(64bit)",
- "libnss3.so(NSS_3.5)",
- "libnss3.so(NSS_3.5)(64bit)",
- "libnss3.so(NSS_3.6)",
- "libnss3.so(NSS_3.6)(64bit)",
- "libnss3.so(NSS_3.7)",
- "libnss3.so(NSS_3.7)(64bit)",
- "libnss3.so(NSS_3.7.1)",
- "libnss3.so(NSS_3.7.1)(64bit)",
- "libnss3.so(NSS_3.8)",
- "libnss3.so(NSS_3.8)(64bit)",
- "libnss3.so(NSS_3.9)",
- "libnss3.so(NSS_3.9)(64bit)",
- "libnss3.so(NSS_3.9.2)",
- "libnss3.so(NSS_3.9.2)(64bit)",
- "libnss3.so(NSS_3.9.3)",
- "libnss3.so(NSS_3.9.3)(64bit)",
- "libnssutil3.so",
- "libnssutil3.so()(64bit)",
- "libnssutil3.so(NSSUTIL_3.12)",
- "libnssutil3.so(NSSUTIL_3.12)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.3)",
- "libnssutil3.so(NSSUTIL_3.12.3)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.5)",
- "libnssutil3.so(NSSUTIL_3.12.5)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.7)",
- "libnssutil3.so(NSSUTIL_3.12.7)(64bit)",
- "libnssutil3.so(NSSUTIL_3.13)",
- "libnssutil3.so(NSSUTIL_3.13)(64bit)",
- "libnssutil3.so(NSSUTIL_3.14)",
- "libnssutil3.so(NSSUTIL_3.14)(64bit)",
- "libnssutil3.so(NSSUTIL_3.15)",
- "libnssutil3.so(NSSUTIL_3.15)(64bit)",
- "libnssutil3.so(NSSUTIL_3.17.1)",
- "libnssutil3.so(NSSUTIL_3.17.1)(64bit)",
- "libnssutil3.so(NSSUTIL_3.21)",
- "libnssutil3.so(NSSUTIL_3.21)(64bit)",
- "libnssutil3.so(NSSUTIL_3.24)",
- "libnssutil3.so(NSSUTIL_3.24)(64bit)",
- "libpango-1.0.so.0",
- "libpango-1.0.so.0()(64bit)",
- "libpangocairo-1.0.so.0",
- "libpangocairo-1.0.so.0()(64bit)",
- "libpthread.so.0",
- "libpthread.so.0()(64bit)",
- "libpthread.so.0(GLIBC_2.0)",
- "libpthread.so.0(GLIBC_2.1)",
- "libpthread.so.0(GLIBC_2.1.1)",
- "libpthread.so.0(GLIBC_2.1.2)",
- "libpthread.so.0(GLIBC_2.11)",
- "libpthread.so.0(GLIBC_2.11)(64bit)",
- "libpthread.so.0(GLIBC_2.12)",
- "libpthread.so.0(GLIBC_2.12)(64bit)",
- "libpthread.so.0(GLIBC_2.18)",
- "libpthread.so.0(GLIBC_2.18)(64bit)",
- "libpthread.so.0(GLIBC_2.2)",
- "libpthread.so.0(GLIBC_2.2.3)",
- "libpthread.so.0(GLIBC_2.2.5)(64bit)",
- "libpthread.so.0(GLIBC_2.2.6)",
- "libpthread.so.0(GLIBC_2.2.6)(64bit)",
- "libpthread.so.0(GLIBC_2.3.2)",
- "libpthread.so.0(GLIBC_2.3.2)(64bit)",
- "libpthread.so.0(GLIBC_2.3.3)",
- "libpthread.so.0(GLIBC_2.3.3)(64bit)",
- "libpthread.so.0(GLIBC_2.3.4)",
- "libpthread.so.0(GLIBC_2.3.4)(64bit)",
- "libpthread.so.0(GLIBC_2.4)",
- "libpthread.so.0(GLIBC_2.4)(64bit)",
- "libpthread.so.0(GLIBC_PRIVATE)",
- "librt.so.1",
- "librt.so.1()(64bit)",
- "librt.so.1(GLIBC_2.1)",
- "librt.so.1(GLIBC_2.2)",
- "librt.so.1(GLIBC_2.2.5)(64bit)",
- "librt.so.1(GLIBC_2.3.3)(64bit)",
- "librt.so.1(GLIBC_2.3.4)",
- "librt.so.1(GLIBC_2.3.4)(64bit)",
- "librt.so.1(GLIBC_2.4)",
- "librt.so.1(GLIBC_2.4)(64bit)",
- "librt.so.1(GLIBC_2.7)",
- "librt.so.1(GLIBC_2.7)(64bit)",
- "libsmime3.so",
- "libsmime3.so()(64bit)",
- "libsmime3.so(NSS_3.10)",
- "libsmime3.so(NSS_3.10)(64bit)",
- "libsmime3.so(NSS_3.12.10)",
- "libsmime3.so(NSS_3.12.10)(64bit)",
- "libsmime3.so(NSS_3.12.2)",
- "libsmime3.so(NSS_3.12.2)(64bit)",
- "libsmime3.so(NSS_3.13)",
- "libsmime3.so(NSS_3.13)(64bit)",
- "libsmime3.so(NSS_3.15)",
- "libsmime3.so(NSS_3.15)(64bit)",
- "libsmime3.so(NSS_3.16)",
- "libsmime3.so(NSS_3.16)(64bit)",
- "libsmime3.so(NSS_3.18)",
- "libsmime3.so(NSS_3.18)(64bit)",
- "libsmime3.so(NSS_3.2)",
- "libsmime3.so(NSS_3.2)(64bit)",
- "libsmime3.so(NSS_3.2.1)",
- "libsmime3.so(NSS_3.2.1)(64bit)",
- "libsmime3.so(NSS_3.3)",
- "libsmime3.so(NSS_3.3)(64bit)",
- "libsmime3.so(NSS_3.4)",
- "libsmime3.so(NSS_3.4)(64bit)",
- "libsmime3.so(NSS_3.4.1)",
- "libsmime3.so(NSS_3.4.1)(64bit)",
- "libsmime3.so(NSS_3.6)",
- "libsmime3.so(NSS_3.6)(64bit)",
- "libsmime3.so(NSS_3.7)",
- "libsmime3.so(NSS_3.7)(64bit)",
- "libsmime3.so(NSS_3.7.2)",
- "libsmime3.so(NSS_3.7.2)(64bit)",
- "libsmime3.so(NSS_3.8)",
- "libsmime3.so(NSS_3.8)(64bit)",
- "libsmime3.so(NSS_3.9)",
- "libsmime3.so(NSS_3.9)(64bit)",
- "libsmime3.so(NSS_3.9.3)",
- "libsmime3.so(NSS_3.9.3)(64bit)",
- "libstdc++.so.2.9",
- "libstdc++.so.6",
- "libstdc++.so.6()(64bit)",
- "libstdc++.so.6(CXXABI_1.3)",
- "libstdc++.so.6(CXXABI_1.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.1)",
- "libstdc++.so.6(CXXABI_1.3.1)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.10)",
- "libstdc++.so.6(CXXABI_1.3.10)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.2)",
- "libstdc++.so.6(CXXABI_1.3.2)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.3)",
- "libstdc++.so.6(CXXABI_1.3.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.4)",
- "libstdc++.so.6(CXXABI_1.3.4)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.5)",
- "libstdc++.so.6(CXXABI_1.3.5)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.6)",
- "libstdc++.so.6(CXXABI_1.3.6)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.7)",
- "libstdc++.so.6(CXXABI_1.3.7)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.8)",
- "libstdc++.so.6(CXXABI_1.3.8)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.9)",
- "libstdc++.so.6(CXXABI_1.3.9)(64bit)",
- "libstdc++.so.6(CXXABI_FLOAT128)",
- "libstdc++.so.6(CXXABI_FLOAT128)(64bit)",
- "libstdc++.so.6(CXXABI_TM_1)",
- "libstdc++.so.6(CXXABI_TM_1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4)",
- "libstdc++.so.6(GLIBCXX_3.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.1)",
- "libstdc++.so.6(GLIBCXX_3.4.1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.10)",
- "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.11)",
- "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.12)",
- "libstdc++.so.6(GLIBCXX_3.4.12)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.13)",
- "libstdc++.so.6(GLIBCXX_3.4.13)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.14)",
- "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.15)",
- "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.16)",
- "libstdc++.so.6(GLIBCXX_3.4.16)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.17)",
- "libstdc++.so.6(GLIBCXX_3.4.17)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.18)",
- "libstdc++.so.6(GLIBCXX_3.4.18)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.19)",
- "libstdc++.so.6(GLIBCXX_3.4.19)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.2)",
- "libstdc++.so.6(GLIBCXX_3.4.2)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.20)",
- "libstdc++.so.6(GLIBCXX_3.4.20)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.21)",
- "libstdc++.so.6(GLIBCXX_3.4.21)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.22)",
- "libstdc++.so.6(GLIBCXX_3.4.22)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.3)",
- "libstdc++.so.6(GLIBCXX_3.4.3)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.4)",
- "libstdc++.so.6(GLIBCXX_3.4.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.5)",
- "libstdc++.so.6(GLIBCXX_3.4.5)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.6)",
- "libstdc++.so.6(GLIBCXX_3.4.6)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.7)",
- "libstdc++.so.6(GLIBCXX_3.4.7)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.8)",
- "libstdc++.so.6(GLIBCXX_3.4.8)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.9)",
- "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
- "libuuid.so.1",
- "libuuid.so.1()(64bit)",
- "libuuid.so.1(UUIDD_PRIVATE)",
- "libuuid.so.1(UUIDD_PRIVATE)(64bit)",
- "libuuid.so.1(UUID_1.0)",
- "libuuid.so.1(UUID_1.0)(64bit)",
- "libuuid.so.1(UUID_2.20)",
- "libuuid.so.1(UUID_2.20)(64bit)",
- "libxcb.so.1",
- "libxcb.so.1()(64bit)",
- "rtld(GNU_HASH)"
- ],
- "openSUSE Leap 42.3": [
- "ld-linux-x86-64.so.2()(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)",
- "ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)",
- "libX11-xcb.so.1",
- "libX11-xcb.so.1()(64bit)",
- "libX11.so.6",
- "libX11.so.6()(64bit)",
- "libXcomposite.so.1",
- "libXcomposite.so.1()(64bit)",
- "libXcursor.so.1",
- "libXcursor.so.1()(64bit)",
- "libXdamage.so.1",
- "libXdamage.so.1()(64bit)",
- "libXext.so.6",
- "libXext.so.6()(64bit)",
- "libXfixes.so.3",
- "libXfixes.so.3()(64bit)",
- "libXi.so.6",
- "libXi.so.6()(64bit)",
- "libXrandr.so.2",
- "libXrandr.so.2()(64bit)",
- "libXrender.so.1",
- "libXrender.so.1()(64bit)",
- "libXss.so.1",
- "libXss.so.1()(64bit)",
- "libXtst.so.6",
- "libXtst.so.6()(64bit)",
- "libappindicator3.so.1()(64bit)",
- "libasound.so.2",
- "libasound.so.2()(64bit)",
- "libasound.so.2(ALSA_0.9)",
- "libasound.so.2(ALSA_0.9)(64bit)",
- "libasound.so.2(ALSA_0.9.0)",
- "libasound.so.2(ALSA_0.9.0)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc4)",
- "libasound.so.2(ALSA_0.9.0rc4)(64bit)",
- "libasound.so.2(ALSA_0.9.0rc8)",
- "libasound.so.2(ALSA_0.9.0rc8)(64bit)",
- "libasound.so.2(ALSA_0.9.3)",
- "libasound.so.2(ALSA_0.9.3)(64bit)",
- "libasound.so.2(ALSA_0.9.5)",
- "libasound.so.2(ALSA_0.9.5)(64bit)",
- "libasound.so.2(ALSA_0.9.7)",
- "libasound.so.2(ALSA_0.9.7)(64bit)",
- "libatk-1.0.so.0",
- "libatk-1.0.so.0()(64bit)",
- "libatk-bridge-2.0.so.0",
- "libatk-bridge-2.0.so.0()(64bit)",
- "libatspi.so.0",
- "libatspi.so.0()(64bit)",
- "libc.so.6",
- "libc.so.6()(64bit)",
- "libc.so.6(GCC_3.0)",
- "libc.so.6(GLIBC_2.0)",
- "libc.so.6(GLIBC_2.1)",
- "libc.so.6(GLIBC_2.1.1)",
- "libc.so.6(GLIBC_2.1.2)",
- "libc.so.6(GLIBC_2.1.3)",
- "libc.so.6(GLIBC_2.10)",
- "libc.so.6(GLIBC_2.10)(64bit)",
- "libc.so.6(GLIBC_2.11)",
- "libc.so.6(GLIBC_2.11)(64bit)",
- "libc.so.6(GLIBC_2.12)",
- "libc.so.6(GLIBC_2.12)(64bit)",
- "libc.so.6(GLIBC_2.13)",
- "libc.so.6(GLIBC_2.13)(64bit)",
- "libc.so.6(GLIBC_2.14)",
- "libc.so.6(GLIBC_2.14)(64bit)",
- "libc.so.6(GLIBC_2.15)",
- "libc.so.6(GLIBC_2.15)(64bit)",
- "libc.so.6(GLIBC_2.16)",
- "libc.so.6(GLIBC_2.16)(64bit)",
- "libc.so.6(GLIBC_2.17)",
- "libc.so.6(GLIBC_2.17)(64bit)",
- "libc.so.6(GLIBC_2.18)",
- "libc.so.6(GLIBC_2.18)(64bit)",
- "libc.so.6(GLIBC_2.2)",
- "libc.so.6(GLIBC_2.2.1)",
- "libc.so.6(GLIBC_2.2.2)",
- "libc.so.6(GLIBC_2.2.3)",
- "libc.so.6(GLIBC_2.2.4)",
- "libc.so.6(GLIBC_2.2.5)(64bit)",
- "libc.so.6(GLIBC_2.2.6)",
- "libc.so.6(GLIBC_2.2.6)(64bit)",
- "libc.so.6(GLIBC_2.22)",
- "libc.so.6(GLIBC_2.22)(64bit)",
- "libc.so.6(GLIBC_2.3)",
- "libc.so.6(GLIBC_2.3)(64bit)",
- "libc.so.6(GLIBC_2.3.2)",
- "libc.so.6(GLIBC_2.3.2)(64bit)",
- "libc.so.6(GLIBC_2.3.3)",
- "libc.so.6(GLIBC_2.3.3)(64bit)",
- "libc.so.6(GLIBC_2.3.4)",
- "libc.so.6(GLIBC_2.3.4)(64bit)",
- "libc.so.6(GLIBC_2.4)",
- "libc.so.6(GLIBC_2.4)(64bit)",
- "libc.so.6(GLIBC_2.5)",
- "libc.so.6(GLIBC_2.5)(64bit)",
- "libc.so.6(GLIBC_2.6)",
- "libc.so.6(GLIBC_2.6)(64bit)",
- "libc.so.6(GLIBC_2.7)",
- "libc.so.6(GLIBC_2.7)(64bit)",
- "libc.so.6(GLIBC_2.8)",
- "libc.so.6(GLIBC_2.8)(64bit)",
- "libc.so.6(GLIBC_2.9)",
- "libc.so.6(GLIBC_2.9)(64bit)",
- "libc.so.6(GLIBC_PRIVATE)",
- "libcairo.so()(64bit)",
- "libcairo.so.2",
- "libcairo.so.2()(64bit)",
- "libcups.so.2",
- "libcups.so.2()(64bit)",
- "libdbus-1.so.3",
- "libdbus-1.so.3()(64bit)",
- "libdl.so.2",
- "libdl.so.2()(64bit)",
- "libdl.so.2(GLIBC_2.0)",
- "libdl.so.2(GLIBC_2.1)",
- "libdl.so.2(GLIBC_2.2.5)(64bit)",
- "libdl.so.2(GLIBC_2.3.3)",
- "libdl.so.2(GLIBC_2.3.3)(64bit)",
- "libdl.so.2(GLIBC_2.3.4)",
- "libdl.so.2(GLIBC_2.3.4)(64bit)",
- "libdl.so.2(GLIBC_PRIVATE)",
- "libexpat.so.0",
- "libexpat.so.0()(64bit)",
- "libexpat.so.1",
- "libexpat.so.1()(64bit)",
- "libgcc_s.so.1",
- "libgcc_s.so.1()(64bit)",
- "libgcc_s.so.1(GCC_3.0)",
- "libgcc_s.so.1(GCC_3.0)(64bit)",
- "libgcc_s.so.1(GCC_3.3)",
- "libgcc_s.so.1(GCC_3.3)(64bit)",
- "libgcc_s.so.1(GCC_3.3.1)",
- "libgcc_s.so.1(GCC_3.3.1)(64bit)",
- "libgcc_s.so.1(GCC_3.4)",
- "libgcc_s.so.1(GCC_3.4)(64bit)",
- "libgcc_s.so.1(GCC_3.4.2)",
- "libgcc_s.so.1(GCC_3.4.2)(64bit)",
- "libgcc_s.so.1(GCC_3.4.4)(64bit)",
- "libgcc_s.so.1(GCC_4.0.0)",
- "libgcc_s.so.1(GCC_4.0.0)(64bit)",
- "libgcc_s.so.1(GCC_4.2.0)",
- "libgcc_s.so.1(GCC_4.2.0)(64bit)",
- "libgcc_s.so.1(GCC_4.3.0)",
- "libgcc_s.so.1(GCC_4.3.0)(64bit)",
- "libgcc_s.so.1(GCC_4.4.0)",
- "libgcc_s.so.1(GCC_4.5.0)",
- "libgcc_s.so.1(GCC_4.7.0)",
- "libgcc_s.so.1(GCC_4.7.0)(64bit)",
- "libgcc_s.so.1(GCC_4.8.0)",
- "libgcc_s.so.1(GCC_4.8.0)(64bit)",
- "libgcc_s.so.1(GCC_7.0.0)",
- "libgcc_s.so.1(GCC_7.0.0)(64bit)",
- "libgcc_s.so.1(GLIBC_2.0)",
- "libgdk-3.so.0",
- "libgdk-3.so.0()(64bit)",
- "libgdk_pixbuf-2.0.so.0",
- "libgdk_pixbuf-2.0.so.0()(64bit)",
- "libgio-2.0.so.0",
- "libgio-2.0.so.0()(64bit)",
- "libglib-2.0.so.0",
- "libglib-2.0.so.0()(64bit)",
- "libgmodule-2.0.so.0",
- "libgmodule-2.0.so.0()(64bit)",
- "libgobject-2.0.so.0",
- "libgobject-2.0.so.0()(64bit)",
- "libgtk-3.so.0",
- "libgtk-3.so.0()(64bit)",
- "libm.so.6",
- "libm.so.6()(64bit)",
- "libm.so.6(GLIBC_2.0)",
- "libm.so.6(GLIBC_2.1)",
- "libm.so.6(GLIBC_2.15)",
- "libm.so.6(GLIBC_2.15)(64bit)",
- "libm.so.6(GLIBC_2.18)",
- "libm.so.6(GLIBC_2.18)(64bit)",
- "libm.so.6(GLIBC_2.2)",
- "libm.so.6(GLIBC_2.2.5)(64bit)",
- "libm.so.6(GLIBC_2.4)",
- "libm.so.6(GLIBC_2.4)(64bit)",
- "libnspr4.so",
- "libnspr4.so()(64bit)",
- "libnss3.so",
- "libnss3.so()(64bit)",
- "libnss3.so(NSS_3.10)",
- "libnss3.so(NSS_3.10)(64bit)",
- "libnss3.so(NSS_3.10.2)",
- "libnss3.so(NSS_3.10.2)(64bit)",
- "libnss3.so(NSS_3.11)",
- "libnss3.so(NSS_3.11)(64bit)",
- "libnss3.so(NSS_3.11.1)",
- "libnss3.so(NSS_3.11.1)(64bit)",
- "libnss3.so(NSS_3.11.2)",
- "libnss3.so(NSS_3.11.2)(64bit)",
- "libnss3.so(NSS_3.11.7)",
- "libnss3.so(NSS_3.11.7)(64bit)",
- "libnss3.so(NSS_3.11.9)",
- "libnss3.so(NSS_3.11.9)(64bit)",
- "libnss3.so(NSS_3.12)",
- "libnss3.so(NSS_3.12)(64bit)",
- "libnss3.so(NSS_3.12.1)",
- "libnss3.so(NSS_3.12.1)(64bit)",
- "libnss3.so(NSS_3.12.10)",
- "libnss3.so(NSS_3.12.10)(64bit)",
- "libnss3.so(NSS_3.12.3)",
- "libnss3.so(NSS_3.12.3)(64bit)",
- "libnss3.so(NSS_3.12.4)",
- "libnss3.so(NSS_3.12.4)(64bit)",
- "libnss3.so(NSS_3.12.5)",
- "libnss3.so(NSS_3.12.5)(64bit)",
- "libnss3.so(NSS_3.12.6)",
- "libnss3.so(NSS_3.12.6)(64bit)",
- "libnss3.so(NSS_3.12.7)",
- "libnss3.so(NSS_3.12.7)(64bit)",
- "libnss3.so(NSS_3.12.9)",
- "libnss3.so(NSS_3.12.9)(64bit)",
- "libnss3.so(NSS_3.13)",
- "libnss3.so(NSS_3.13)(64bit)",
- "libnss3.so(NSS_3.13.2)",
- "libnss3.so(NSS_3.13.2)(64bit)",
- "libnss3.so(NSS_3.14)",
- "libnss3.so(NSS_3.14)(64bit)",
- "libnss3.so(NSS_3.14.1)",
- "libnss3.so(NSS_3.14.1)(64bit)",
- "libnss3.so(NSS_3.14.3)",
- "libnss3.so(NSS_3.14.3)(64bit)",
- "libnss3.so(NSS_3.15)",
- "libnss3.so(NSS_3.15)(64bit)",
- "libnss3.so(NSS_3.15.4)",
- "libnss3.so(NSS_3.15.4)(64bit)",
- "libnss3.so(NSS_3.16.1)",
- "libnss3.so(NSS_3.16.1)(64bit)",
- "libnss3.so(NSS_3.16.2)",
- "libnss3.so(NSS_3.16.2)(64bit)",
- "libnss3.so(NSS_3.18)",
- "libnss3.so(NSS_3.18)(64bit)",
- "libnss3.so(NSS_3.19)",
- "libnss3.so(NSS_3.19)(64bit)",
- "libnss3.so(NSS_3.19.1)",
- "libnss3.so(NSS_3.19.1)(64bit)",
- "libnss3.so(NSS_3.2)",
- "libnss3.so(NSS_3.2)(64bit)",
- "libnss3.so(NSS_3.2.1)",
- "libnss3.so(NSS_3.2.1)(64bit)",
- "libnss3.so(NSS_3.21)",
- "libnss3.so(NSS_3.21)(64bit)",
- "libnss3.so(NSS_3.22)",
- "libnss3.so(NSS_3.22)(64bit)",
- "libnss3.so(NSS_3.3)",
- "libnss3.so(NSS_3.3)(64bit)",
- "libnss3.so(NSS_3.3.1)",
- "libnss3.so(NSS_3.3.1)(64bit)",
- "libnss3.so(NSS_3.30)",
- "libnss3.so(NSS_3.30)(64bit)",
- "libnss3.so(NSS_3.31)",
- "libnss3.so(NSS_3.31)(64bit)",
- "libnss3.so(NSS_3.33)",
- "libnss3.so(NSS_3.33)(64bit)",
- "libnss3.so(NSS_3.34)",
- "libnss3.so(NSS_3.34)(64bit)",
- "libnss3.so(NSS_3.4)",
- "libnss3.so(NSS_3.4)(64bit)",
- "libnss3.so(NSS_3.5)",
- "libnss3.so(NSS_3.5)(64bit)",
- "libnss3.so(NSS_3.6)",
- "libnss3.so(NSS_3.6)(64bit)",
- "libnss3.so(NSS_3.7)",
- "libnss3.so(NSS_3.7)(64bit)",
- "libnss3.so(NSS_3.7.1)",
- "libnss3.so(NSS_3.7.1)(64bit)",
- "libnss3.so(NSS_3.8)",
- "libnss3.so(NSS_3.8)(64bit)",
- "libnss3.so(NSS_3.9)",
- "libnss3.so(NSS_3.9)(64bit)",
- "libnss3.so(NSS_3.9.2)",
- "libnss3.so(NSS_3.9.2)(64bit)",
- "libnss3.so(NSS_3.9.3)",
- "libnss3.so(NSS_3.9.3)(64bit)",
- "libnssutil3.so",
- "libnssutil3.so()(64bit)",
- "libnssutil3.so(NSSUTIL_3.12)",
- "libnssutil3.so(NSSUTIL_3.12)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.3)",
- "libnssutil3.so(NSSUTIL_3.12.3)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.5)",
- "libnssutil3.so(NSSUTIL_3.12.5)(64bit)",
- "libnssutil3.so(NSSUTIL_3.12.7)",
- "libnssutil3.so(NSSUTIL_3.12.7)(64bit)",
- "libnssutil3.so(NSSUTIL_3.13)",
- "libnssutil3.so(NSSUTIL_3.13)(64bit)",
- "libnssutil3.so(NSSUTIL_3.14)",
- "libnssutil3.so(NSSUTIL_3.14)(64bit)",
- "libnssutil3.so(NSSUTIL_3.15)",
- "libnssutil3.so(NSSUTIL_3.15)(64bit)",
- "libnssutil3.so(NSSUTIL_3.17.1)",
- "libnssutil3.so(NSSUTIL_3.17.1)(64bit)",
- "libnssutil3.so(NSSUTIL_3.21)",
- "libnssutil3.so(NSSUTIL_3.21)(64bit)",
- "libnssutil3.so(NSSUTIL_3.24)",
- "libnssutil3.so(NSSUTIL_3.24)(64bit)",
- "libnssutil3.so(NSSUTIL_3.25)",
- "libnssutil3.so(NSSUTIL_3.25)(64bit)",
- "libnssutil3.so(NSSUTIL_3.31)",
- "libnssutil3.so(NSSUTIL_3.31)(64bit)",
- "libnssutil3.so(NSSUTIL_3.33)",
- "libnssutil3.so(NSSUTIL_3.33)(64bit)",
- "libpango-1.0.so.0",
- "libpango-1.0.so.0()(64bit)",
- "libpangocairo-1.0.so.0",
- "libpangocairo-1.0.so.0()(64bit)",
- "libpthread.so.0",
- "libpthread.so.0()(64bit)",
- "libpthread.so.0(GLIBC_2.0)",
- "libpthread.so.0(GLIBC_2.1)",
- "libpthread.so.0(GLIBC_2.1.1)",
- "libpthread.so.0(GLIBC_2.1.2)",
- "libpthread.so.0(GLIBC_2.11)",
- "libpthread.so.0(GLIBC_2.11)(64bit)",
- "libpthread.so.0(GLIBC_2.12)",
- "libpthread.so.0(GLIBC_2.12)(64bit)",
- "libpthread.so.0(GLIBC_2.18)",
- "libpthread.so.0(GLIBC_2.18)(64bit)",
- "libpthread.so.0(GLIBC_2.2)",
- "libpthread.so.0(GLIBC_2.2.3)",
- "libpthread.so.0(GLIBC_2.2.5)(64bit)",
- "libpthread.so.0(GLIBC_2.2.6)",
- "libpthread.so.0(GLIBC_2.2.6)(64bit)",
- "libpthread.so.0(GLIBC_2.3.2)",
- "libpthread.so.0(GLIBC_2.3.2)(64bit)",
- "libpthread.so.0(GLIBC_2.3.3)",
- "libpthread.so.0(GLIBC_2.3.3)(64bit)",
- "libpthread.so.0(GLIBC_2.3.4)",
- "libpthread.so.0(GLIBC_2.3.4)(64bit)",
- "libpthread.so.0(GLIBC_2.4)",
- "libpthread.so.0(GLIBC_2.4)(64bit)",
- "libpthread.so.0(GLIBC_PRIVATE)",
- "librt.so.1",
- "librt.so.1()(64bit)",
- "librt.so.1(GLIBC_2.1)",
- "librt.so.1(GLIBC_2.2)",
- "librt.so.1(GLIBC_2.2.5)(64bit)",
- "librt.so.1(GLIBC_2.3.3)(64bit)",
- "librt.so.1(GLIBC_2.3.4)",
- "librt.so.1(GLIBC_2.3.4)(64bit)",
- "librt.so.1(GLIBC_2.4)",
- "librt.so.1(GLIBC_2.4)(64bit)",
- "librt.so.1(GLIBC_2.7)",
- "librt.so.1(GLIBC_2.7)(64bit)",
- "libsmime3.so",
- "libsmime3.so()(64bit)",
- "libsmime3.so(NSS_3.10)",
- "libsmime3.so(NSS_3.10)(64bit)",
- "libsmime3.so(NSS_3.12.10)",
- "libsmime3.so(NSS_3.12.10)(64bit)",
- "libsmime3.so(NSS_3.12.2)",
- "libsmime3.so(NSS_3.12.2)(64bit)",
- "libsmime3.so(NSS_3.13)",
- "libsmime3.so(NSS_3.13)(64bit)",
- "libsmime3.so(NSS_3.15)",
- "libsmime3.so(NSS_3.15)(64bit)",
- "libsmime3.so(NSS_3.16)",
- "libsmime3.so(NSS_3.16)(64bit)",
- "libsmime3.so(NSS_3.18)",
- "libsmime3.so(NSS_3.18)(64bit)",
- "libsmime3.so(NSS_3.2)",
- "libsmime3.so(NSS_3.2)(64bit)",
- "libsmime3.so(NSS_3.2.1)",
- "libsmime3.so(NSS_3.2.1)(64bit)",
- "libsmime3.so(NSS_3.3)",
- "libsmime3.so(NSS_3.3)(64bit)",
- "libsmime3.so(NSS_3.4)",
- "libsmime3.so(NSS_3.4)(64bit)",
- "libsmime3.so(NSS_3.4.1)",
- "libsmime3.so(NSS_3.4.1)(64bit)",
- "libsmime3.so(NSS_3.6)",
- "libsmime3.so(NSS_3.6)(64bit)",
- "libsmime3.so(NSS_3.7)",
- "libsmime3.so(NSS_3.7)(64bit)",
- "libsmime3.so(NSS_3.7.2)",
- "libsmime3.so(NSS_3.7.2)(64bit)",
- "libsmime3.so(NSS_3.8)",
- "libsmime3.so(NSS_3.8)(64bit)",
- "libsmime3.so(NSS_3.9)",
- "libsmime3.so(NSS_3.9)(64bit)",
- "libsmime3.so(NSS_3.9.3)",
- "libsmime3.so(NSS_3.9.3)(64bit)",
- "libstdc++.so.2.9",
- "libstdc++.so.6",
- "libstdc++.so.6()(64bit)",
- "libstdc++.so.6(CXXABI_1.3)",
- "libstdc++.so.6(CXXABI_1.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.1)",
- "libstdc++.so.6(CXXABI_1.3.1)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.10)",
- "libstdc++.so.6(CXXABI_1.3.10)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.11)",
- "libstdc++.so.6(CXXABI_1.3.11)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.2)",
- "libstdc++.so.6(CXXABI_1.3.2)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.3)",
- "libstdc++.so.6(CXXABI_1.3.3)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.4)",
- "libstdc++.so.6(CXXABI_1.3.4)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.5)",
- "libstdc++.so.6(CXXABI_1.3.5)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.6)",
- "libstdc++.so.6(CXXABI_1.3.6)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.7)",
- "libstdc++.so.6(CXXABI_1.3.7)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.8)",
- "libstdc++.so.6(CXXABI_1.3.8)(64bit)",
- "libstdc++.so.6(CXXABI_1.3.9)",
- "libstdc++.so.6(CXXABI_1.3.9)(64bit)",
- "libstdc++.so.6(CXXABI_FLOAT128)",
- "libstdc++.so.6(CXXABI_FLOAT128)(64bit)",
- "libstdc++.so.6(CXXABI_TM_1)",
- "libstdc++.so.6(CXXABI_TM_1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4)",
- "libstdc++.so.6(GLIBCXX_3.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.1)",
- "libstdc++.so.6(GLIBCXX_3.4.1)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.10)",
- "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.11)",
- "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.12)",
- "libstdc++.so.6(GLIBCXX_3.4.12)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.13)",
- "libstdc++.so.6(GLIBCXX_3.4.13)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.14)",
- "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.15)",
- "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.16)",
- "libstdc++.so.6(GLIBCXX_3.4.16)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.17)",
- "libstdc++.so.6(GLIBCXX_3.4.17)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.18)",
- "libstdc++.so.6(GLIBCXX_3.4.18)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.19)",
- "libstdc++.so.6(GLIBCXX_3.4.19)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.2)",
- "libstdc++.so.6(GLIBCXX_3.4.2)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.20)",
- "libstdc++.so.6(GLIBCXX_3.4.20)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.21)",
- "libstdc++.so.6(GLIBCXX_3.4.21)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.22)",
- "libstdc++.so.6(GLIBCXX_3.4.22)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.23)",
- "libstdc++.so.6(GLIBCXX_3.4.23)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.24)",
- "libstdc++.so.6(GLIBCXX_3.4.24)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.3)",
- "libstdc++.so.6(GLIBCXX_3.4.3)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.4)",
- "libstdc++.so.6(GLIBCXX_3.4.4)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.5)",
- "libstdc++.so.6(GLIBCXX_3.4.5)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.6)",
- "libstdc++.so.6(GLIBCXX_3.4.6)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.7)",
- "libstdc++.so.6(GLIBCXX_3.4.7)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.8)",
- "libstdc++.so.6(GLIBCXX_3.4.8)(64bit)",
- "libstdc++.so.6(GLIBCXX_3.4.9)",
- "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
- "libuuid.so.1",
- "libuuid.so.1()(64bit)",
- "libuuid.so.1(UUIDD_PRIVATE)",
- "libuuid.so.1(UUIDD_PRIVATE)(64bit)",
- "libuuid.so.1(UUID_1.0)",
- "libuuid.so.1(UUID_1.0)(64bit)",
- "libuuid.so.1(UUID_2.20)",
- "libuuid.so.1(UUID_2.20)(64bit)",
- "libxcb.so.1",
- "libxcb.so.1()(64bit)",
- "rtld(GNU_HASH)"
- ]
-}
diff --git a/chromium/chrome/renderer/DEPS b/chromium/chrome/renderer/DEPS
new file mode 100644
index 00000000000..ea891766865
--- /dev/null
+++ b/chromium/chrome/renderer/DEPS
@@ -0,0 +1,80 @@
+include_rules = [
+ "+chrome/grit",
+ "+components/autofill/content/common",
+ "+components/autofill/content/renderer",
+ "+components/autofill/core/common",
+ "+components/cdm/renderer",
+ "+components/content_capture/common",
+ "+components/content_capture/renderer",
+ "+components/content_settings/core/common",
+ "+components/contextual_search/content/renderer",
+ "+components/crash/core/common/crash_key.h",
+ "+components/crx_file",
+ "+components/data_reduction_proxy/content/common",
+ "+components/data_reduction_proxy/content/renderer",
+ "+components/data_reduction_proxy/core/common",
+ "+components/dom_distiller/content/common",
+ "+components/dom_distiller/content/renderer",
+ "+components/dom_distiller/core",
+ "+components/grit",
+ "+components/guest_view/renderer",
+ "+components/metrics/child_call_stack_profile_collector.h",
+ "+components/nacl/common",
+ "+components/nacl/renderer",
+ "+components/network_hints/renderer",
+ "+components/network_session_configurator/common",
+ "+components/offline_pages/buildflags",
+ "+components/page_load_metrics/common",
+ "+components/page_load_metrics/renderer",
+ "+components/password_manager/content/renderer",
+ "+components/password_manager/core/common",
+ "+components/pdf/renderer",
+ "+components/plugins/renderer",
+ "+components/printing/common",
+ "+components/printing/renderer",
+ "+components/rappor/public/mojom",
+ "+components/safe_browsing/buildflags.h",
+ "+components/safe_browsing/common",
+ "+components/safe_browsing/features.h",
+ "+components/safe_browsing/renderer",
+ "+components/spellcheck/renderer",
+ "+components/spellcheck/spellcheck_buildflags.h",
+ "+components/startup_metric_utils/common",
+ "+components/strings/grit",
+ "+components/subresource_filter/content/common",
+ "+components/subresource_filter/content/renderer",
+ "+components/subresource_filter/core/common/common_features.h",
+ "+components/translate/content/common",
+ "+components/translate/content/renderer",
+ "+components/translate/core/common",
+ "+components/translate/core/language_detection",
+ "+components/version_info",
+ "+components/visitedlink/renderer",
+ "+components/web_cache/renderer",
+ "+content/public/child",
+ "+content/public/renderer",
+ "+extensions/common",
+ "+extensions/buildflags",
+ "+extensions/renderer",
+ "+gin",
+ "+google_apis",
+ "+media/base",
+ "+ppapi/shared_impl",
+ "+services/network/public/cpp",
+ "+skia",
+ "+storage/common",
+ "+third_party/blink/public/mojom",
+ "+third_party/blink/public/strings/grit/blink_strings.h",
+ "+third_party/metrics_proto",
+ "+third_party/widevine/cdm/buildflags.h",
+ "+third_party/widevine/cdm/widevine_cdm_common.h",
+]
+
+specific_include_rules = {
+ "chrome_content_renderer_client_browsertest\.cc" : [
+ "+chrome/browser/profiles/profile_io_data.h",
+ "+chrome/browser/ui",
+ "+chrome/browser/ui/tabs",
+ "+content/public/browser/browser_task_traits.h",
+ ],
+}
diff --git a/chromium/chrome/renderer/OWNERS b/chromium/chrome/renderer/OWNERS
new file mode 100644
index 00000000000..08914d1b3a4
--- /dev/null
+++ b/chromium/chrome/renderer/OWNERS
@@ -0,0 +1,19 @@
+# This is for the common case of adding or renaming files. If you're doing
+# structural changes, use usual OWNERS rules.
+per-file BUILD.gn=*
+
+# Page Load Histogram files.
+per-file loadtimes_extension_bindings*=file://components/page_load_metrics/OWNERS
+# Instant/Search files.
+per-file instant_restricted_id_cache*=file://chrome/browser/search/OWNERS
+
+# For security review.
+per-file chrome_content_renderer_client_receiver_bindings.cc=set noparent
+per-file chrome_content_renderer_client_receiver_bindings.cc=file://ipc/SECURITY_OWNERS
+
+# Content client.
+per-file chrome_content_renderer_client_browsertest.cc=*
+per-file chrome_content_renderer_client.cc=*
+per-file chrome_content_renderer_client.h=*
+per-file chrome_content_renderer_client_unittest.cc=*
+# COMPONENT: Internals>Services>Network
diff --git a/chromium/chrome/renderer/app_categorizer_unittest.cc b/chromium/chrome/renderer/app_categorizer_unittest.cc
new file mode 100644
index 00000000000..7439061dc56
--- /dev/null
+++ b/chromium/chrome/renderer/app_categorizer_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/app_categorizer.h"
+
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char* kChatAppURLs[] = {
+ "https://hangouts.google.com/hangouts/foo",
+ "https://hAnGoUtS.gOoGlE.com/HaNgOuTs/foo",
+ "https://meet.google.com/hangouts/foo",
+ "https://talkgadget.google.com/hangouts/foo",
+ "https://staging.talkgadget.google.com/hangouts/foo",
+ "https://plus.google.com/hangouts/foo",
+ "https://plus.sandbox.google.com/hangouts/foo"
+};
+
+const char* kChatManifestFSs[] = {
+ "filesystem:https://hangouts.google.com/foo",
+ "filesystem:https://hAnGoUtS.gOoGlE.com/foo",
+ "filesystem:https://meet.google.com/foo",
+ "filesystem:https://talkgadget.google.com/foo",
+ "filesystem:https://staging.talkgadget.google.com/foo",
+ "filesystem:https://plus.google.com/foo",
+ "filesystem:https://plus.sandbox.google.com/foo"
+};
+
+const char* kBadChatAppURLs[] = {
+ "http://talkgadget.google.com/hangouts/foo", // not https
+ "https://talkgadget.evil.com/hangouts/foo" // domain not whitelisted
+};
+
+} // namespace
+
+TEST(AppCategorizerTest, IsHangoutsUrl) {
+ for (size_t i = 0; i < base::size(kChatAppURLs); ++i) {
+ EXPECT_TRUE(AppCategorizer::IsHangoutsUrl(GURL(kChatAppURLs[i])));
+ }
+
+ for (size_t i = 0; i < base::size(kBadChatAppURLs); ++i) {
+ EXPECT_FALSE(AppCategorizer::IsHangoutsUrl(GURL(kBadChatAppURLs[i])));
+ }
+}
+
+TEST(AppCategorizerTest, IsWhitelistedApp) {
+ // Hangouts app
+ {
+ EXPECT_EQ(base::size(kChatAppURLs), base::size(kChatManifestFSs));
+ for (size_t i = 0; i < base::size(kChatAppURLs); ++i) {
+ EXPECT_TRUE(AppCategorizer::IsWhitelistedApp(
+ GURL(kChatManifestFSs[i]), GURL(kChatAppURLs[i])));
+ }
+ for (size_t i = 0; i < base::size(kBadChatAppURLs); ++i) {
+ EXPECT_FALSE(AppCategorizer::IsWhitelistedApp(
+ GURL("filesystem:https://irrelevant.com/"),
+ GURL(kBadChatAppURLs[i])));
+ }
+
+ // Manifest URL not filesystem
+ EXPECT_FALSE(AppCategorizer::IsWhitelistedApp(
+ GURL("https://hangouts.google.com/foo"),
+ GURL("https://hangouts.google.com/hangouts/foo")));
+
+ // Manifest URL not https
+ EXPECT_FALSE(AppCategorizer::IsWhitelistedApp(
+ GURL("filesystem:http://hangouts.google.com/foo"),
+ GURL("https://hangouts.google.com/hangouts/foo")));
+
+ // Manifest URL hostname does not match that of the app URL
+ EXPECT_FALSE(AppCategorizer::IsWhitelistedApp(
+ GURL("filesystem:https://meet.google.com/foo"),
+ GURL("https://hangouts.google.com/hangouts/foo")));
+ }
+}
diff --git a/chromium/chrome/renderer/autofill/OWNERS b/chromium/chrome/renderer/autofill/OWNERS
new file mode 100644
index 00000000000..f9a534e9a52
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/OWNERS
@@ -0,0 +1,6 @@
+file://components/autofill/OWNERS
+
+per-file *password*=kolos@chromium.org
+per-file *password*=vasilii@chromium.org
+
+# COMPONENT: UI>Browser>Autofill
diff --git a/chromium/chrome/renderer/autofill/autofill_renderer_browsertest.cc b/chromium/chrome/renderer/autofill/autofill_renderer_browsertest.cc
new file mode 100644
index 00000000000..f83951ee485
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/autofill_renderer_browsertest.cc
@@ -0,0 +1,402 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <tuple>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_form_element.h"
+#include "third_party/blink/public/web/web_input_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+
+using autofill::features::kAutofillEnforceMinRequiredFieldsForHeuristics;
+using autofill::features::kAutofillEnforceMinRequiredFieldsForQuery;
+using autofill::features::kAutofillEnforceMinRequiredFieldsForUpload;
+using base::ASCIIToUTF16;
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebFormElement;
+using blink::WebFrame;
+using blink::WebLocalFrame;
+using blink::WebInputElement;
+using blink::WebString;
+using blink::WebURLRequest;
+using blink::WebVector;
+
+namespace autofill {
+
+namespace {
+
+class FakeContentAutofillDriver : public mojom::AutofillDriver {
+ public:
+ FakeContentAutofillDriver() : called_field_change_(false) {}
+ ~FakeContentAutofillDriver() override {}
+
+ void BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::AutofillDriver> receiver) {
+ receivers_.Add(this, std::move(receiver));
+ }
+
+ bool called_field_change() const { return called_field_change_; }
+
+ const std::vector<FormData>* forms() const { return forms_.get(); }
+
+ void reset_forms() { return forms_.reset(); }
+
+ private:
+ // mojom::AutofillDriver:
+ void FormsSeen(const std::vector<FormData>& forms,
+ base::TimeTicks timestamp) override {
+ // FormsSeen() could be called multiple times and sometimes even with empty
+ // forms array for main frame, but we're interested in only the first time
+ // call.
+ if (!forms_)
+ forms_.reset(new std::vector<FormData>(forms));
+ }
+
+ void FormSubmitted(const FormData& form,
+ bool known_success,
+ mojom::SubmissionSource source) override {}
+
+ void TextFieldDidChange(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box,
+ base::TimeTicks timestamp) override {
+ called_field_change_ = true;
+ }
+
+ void TextFieldDidScroll(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box) override {}
+
+ void SelectControlDidChange(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box) override {}
+
+ void QueryFormFieldAutofill(int32_t id,
+ const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box,
+ bool autoselect_first_suggestion) override {}
+
+ void HidePopup() override {}
+
+ void FocusNoLongerOnForm() override {}
+
+ void FocusOnFormField(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box) override {}
+
+ void DidFillAutofillFormData(const FormData& form,
+ base::TimeTicks timestamp) override {}
+
+ void DidPreviewAutofillFormData() override {}
+
+ void DidEndTextFieldEditing() override {}
+
+ void SetDataList(const std::vector<base::string16>& values,
+ const std::vector<base::string16>& labels) override {}
+
+ void SelectFieldOptionsDidChange(const autofill::FormData& form) override {}
+
+ // Records whether TextFieldDidChange() get called.
+ bool called_field_change_;
+ // Records data received via FormSeen() call.
+ std::unique_ptr<std::vector<FormData>> forms_;
+
+ mojo::AssociatedReceiverSet<mojom::AutofillDriver> receivers_;
+};
+
+} // namespace
+
+using AutofillQueryParam =
+ std::tuple<int, autofill::FormData, autofill::FormFieldData, gfx::RectF>;
+
+class AutofillRendererTest : public ChromeRenderViewTest {
+ public:
+ AutofillRendererTest() {}
+
+ ~AutofillRendererTest() override {}
+
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+ // We only use the fake driver for main frame
+ // because our test cases only involve the main frame.
+ blink::AssociatedInterfaceProvider* remote_interfaces =
+ view_->GetMainRenderFrame()->GetRemoteAssociatedInterfaces();
+ remote_interfaces->OverrideBinderForTesting(
+ mojom::AutofillDriver::Name_,
+ base::BindRepeating(&AutofillRendererTest::BindAutofillDriver,
+ base::Unretained(this)));
+ }
+
+ void BindAutofillDriver(mojo::ScopedInterfaceEndpointHandle handle) {
+ fake_driver_.BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::AutofillDriver>(
+ std::move(handle)));
+ }
+
+ FakeContentAutofillDriver fake_driver_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutofillRendererTest);
+};
+
+TEST_F(AutofillRendererTest, SendForms) {
+ LoadHTML("<form method='POST'>"
+ " <input type='text' id='firstname'/>"
+ " <input type='text' id='middlename'/>"
+ " <input type='text' id='lastname' autoComplete='off'/>"
+ " <input type='hidden' id='email'/>"
+ " <select id='state'/>"
+ " <option>?</option>"
+ " <option>California</option>"
+ " <option>Texas</option>"
+ " </select>"
+ "</form>");
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ // Verify that "FormsSeen" sends the expected number of fields.
+ ASSERT_TRUE(fake_driver_.forms());
+ std::vector<FormData> forms = *(fake_driver_.forms());
+ ASSERT_EQ(1UL, forms.size());
+ ASSERT_EQ(4UL, forms[0].fields.size());
+
+ FormFieldData expected;
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = base::string16();
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("middlename");
+ expected.name = expected.id_attribute;
+ expected.value = base::string16();
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = base::string16();
+ expected.form_control_type = "text";
+ expected.autocomplete_attribute = "off";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[2]);
+ expected.autocomplete_attribute = std::string(); // reset
+
+ expected.id_attribute = ASCIIToUTF16("state");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("?");
+ expected.form_control_type = "select-one";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[3]);
+
+ fake_driver_.reset_forms();
+
+ // Dynamically create a new form. A new message should be sent for it, but
+ // not for the previous form.
+ ExecuteJavaScriptForTests(
+ "var newForm=document.createElement('form');"
+ "newForm.id='new_testform';"
+ "newForm.action='http://google.com';"
+ "newForm.method='post';"
+ "var newFirstname=document.createElement('input');"
+ "newFirstname.setAttribute('type', 'text');"
+ "newFirstname.setAttribute('id', 'second_firstname');"
+ "newFirstname.value = 'Bob';"
+ "var newLastname=document.createElement('input');"
+ "newLastname.setAttribute('type', 'text');"
+ "newLastname.setAttribute('id', 'second_lastname');"
+ "newLastname.value = 'Hope';"
+ "var newEmail=document.createElement('input');"
+ "newEmail.setAttribute('type', 'text');"
+ "newEmail.setAttribute('id', 'second_email');"
+ "newEmail.value = 'bobhope@example.com';"
+ "newForm.appendChild(newFirstname);"
+ "newForm.appendChild(newLastname);"
+ "newForm.appendChild(newEmail);"
+ "document.body.appendChild(newForm);");
+
+ WaitForAutofillDidAssociateFormControl();
+ ASSERT_TRUE(fake_driver_.forms());
+ forms = *(fake_driver_.forms());
+ ASSERT_EQ(1UL, forms.size());
+ ASSERT_EQ(3UL, forms[0].fields.size());
+
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("second_firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Bob");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("second_lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Hope");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("second_email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("bobhope@example.com");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[2]);
+}
+
+TEST_F(AutofillRendererTest, NoSmallFormsWhenMinimumEnforced) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery,
+ kAutofillEnforceMinRequiredFieldsForUpload},
+ // Disabled.
+ {});
+
+ LoadHTML("<form method='POST'>"
+ " <input type='text' id='firstname'/>"
+ " <input type='text' id='middlename'/>"
+ "</form>");
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ // Verify that "FormsSeen" isn't sent, as there are too few fields.
+ ASSERT_TRUE(fake_driver_.forms());
+ const std::vector<FormData>& forms = *(fake_driver_.forms());
+ ASSERT_EQ(0UL, forms.size());
+}
+
+TEST_F(AutofillRendererTest, SmallFormsFoundWhenMinimumNotEnforced) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {},
+ // Disabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery,
+ kAutofillEnforceMinRequiredFieldsForUpload});
+ LoadHTML(
+ "<form method='POST'>"
+ " <input type='text' id='firstname'/>"
+ " <input type='text' id='middlename'/>"
+ "</form>");
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ // Verify that "FormsSeen" isn't sent, as there are too few fields.
+ ASSERT_TRUE(fake_driver_.forms());
+ const std::vector<FormData>& forms = *(fake_driver_.forms());
+ ASSERT_EQ(1UL, forms.size());
+}
+
+// Regression test for [ http://crbug.com/346010 ].
+TEST_F(AutofillRendererTest, DontCrashWhileAssociatingForms) {
+ LoadHTML("<form id='form'>"
+ "<foo id='foo'>"
+ "<script id='script'>"
+ "document.documentElement.appendChild(foo);"
+ "newDoc = document.implementation.createDocument("
+ " 'http://www.w3.org/1999/xhtml', 'html');"
+ "foo.insertBefore(form, script);"
+ "newDoc.adoptNode(foo);"
+ "</script>");
+
+ // Shouldn't crash.
+}
+
+TEST_F(AutofillRendererTest, DynamicallyAddedUnownedFormElements) {
+ std::string html_data;
+ base::FilePath test_path = ui_test_utils::GetTestFilePath(
+ base::FilePath(FILE_PATH_LITERAL("autofill")),
+ base::FilePath(FILE_PATH_LITERAL("autofill_noform_dynamic.html")));
+ ASSERT_TRUE(base::ReadFileToString(test_path, &html_data));
+ LoadHTML(html_data.c_str());
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ // Verify that "FormsSeen" sends the expected number of fields.
+ ASSERT_TRUE(fake_driver_.forms());
+ std::vector<FormData> forms = *(fake_driver_.forms());
+ ASSERT_EQ(1UL, forms.size());
+ ASSERT_EQ(7UL, forms[0].fields.size());
+
+ fake_driver_.reset_forms();
+
+ ExecuteJavaScriptForTests("AddFields()");
+
+ WaitForAutofillDidAssociateFormControl();
+ ASSERT_TRUE(fake_driver_.forms());
+ forms = *(fake_driver_.forms());
+ ASSERT_EQ(1UL, forms.size());
+ ASSERT_EQ(9UL, forms[0].fields.size());
+
+ FormFieldData expected;
+
+ expected.id_attribute = ASCIIToUTF16("EMAIL_ADDRESS");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[7]);
+
+ expected.id_attribute = ASCIIToUTF16("PHONE_HOME_WHOLE_NUMBER");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[8]);
+}
+
+TEST_F(AutofillRendererTest, IgnoreNonUserGestureTextFieldChanges) {
+ LoadHTML("<form method='post'>"
+ " <input type='text' id='full_name'/>"
+ "</form>");
+
+ blink::WebInputElement full_name = GetMainFrame()
+ ->GetDocument()
+ .GetElementById("full_name")
+ .To<blink::WebInputElement>();
+ while (!full_name.Focused())
+ GetMainFrame()->View()->AdvanceFocus(false);
+
+ ASSERT_FALSE(fake_driver_.called_field_change());
+ full_name.SetValue("Alice", true);
+ GetMainFrame()->AutofillClient()->TextFieldDidChange(full_name);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_FALSE(fake_driver_.called_field_change());
+
+ SimulateUserInputChangeForElement(&full_name, "Alice");
+ ASSERT_TRUE(fake_driver_.called_field_change());
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.cc b/chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.cc
new file mode 100644
index 00000000000..e0af7366ed2
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.cc
@@ -0,0 +1,86 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/autofill/fake_mojo_password_manager_driver.h"
+
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+FakeMojoPasswordManagerDriver::FakeMojoPasswordManagerDriver() = default;
+
+FakeMojoPasswordManagerDriver::~FakeMojoPasswordManagerDriver() = default;
+
+void FakeMojoPasswordManagerDriver::BindReceiver(
+ mojo::PendingAssociatedReceiver<autofill::mojom::PasswordManagerDriver>
+ receiver) {
+ receiver_.Bind(std::move(receiver));
+}
+
+void FakeMojoPasswordManagerDriver::Flush() {
+ receiver_.FlushForTesting();
+}
+
+// mojom::PasswordManagerDriver:
+void FakeMojoPasswordManagerDriver::PasswordFormsParsed(
+ const std::vector<autofill::PasswordForm>& forms) {
+ called_password_forms_parsed_ = true;
+ password_forms_parsed_ = forms;
+}
+
+void FakeMojoPasswordManagerDriver::PasswordFormsRendered(
+ const std::vector<autofill::PasswordForm>& visible_forms,
+ bool did_stop_loading) {
+ called_password_forms_rendered_ = true;
+ password_forms_rendered_ = visible_forms;
+}
+
+void FakeMojoPasswordManagerDriver::PasswordFormSubmitted(
+ const autofill::PasswordForm& password_form) {
+ called_password_form_submitted_ = true;
+ password_form_submitted_ = password_form;
+}
+
+void FakeMojoPasswordManagerDriver::SameDocumentNavigation(
+ autofill::mojom::SubmissionIndicatorEvent submission_indication_event) {
+ called_same_document_navigation_ = true;
+ password_form_maybe_submitted_->form_data.submission_event =
+ submission_indication_event;
+ password_form_maybe_submitted_->submission_event =
+ submission_indication_event;
+}
+
+void FakeMojoPasswordManagerDriver::RecordSavePasswordProgress(
+ const std::string& log) {
+ called_record_save_progress_ = true;
+}
+
+void FakeMojoPasswordManagerDriver::UserModifiedPasswordField() {
+ called_user_modified_password_field_ = true;
+}
+
+void FakeMojoPasswordManagerDriver::UserModifiedNonPasswordField(
+ uint32_t renderer_id,
+ const base::string16& value) {}
+
+void FakeMojoPasswordManagerDriver::CheckSafeBrowsingReputation(
+ const GURL& form_action,
+ const GURL& frame_url) {
+ called_check_safe_browsing_reputation_cnt_++;
+}
+
+void FakeMojoPasswordManagerDriver::ShowManualFallbackForSaving(
+ const autofill::PasswordForm& password_form) {
+ called_show_manual_fallback_for_saving_count_++;
+ password_form_maybe_submitted_ = password_form;
+}
+
+void FakeMojoPasswordManagerDriver::HideManualFallbackForSaving() {
+ called_show_manual_fallback_for_saving_count_ = 0;
+}
+
+void FakeMojoPasswordManagerDriver::FocusedInputChanged(
+ autofill::mojom::FocusedFieldType focused_field_type) {
+ last_focused_field_type_ = focused_field_type;
+}
diff --git a/chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.h b/chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.h
new file mode 100644
index 00000000000..b8279174e4f
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/fake_mojo_password_manager_driver.h
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_AUTOFILL_FAKE_MOJO_PASSWORD_MANAGER_DRIVER_H_
+#define CHROME_RENDERER_AUTOFILL_FAKE_MOJO_PASSWORD_MANAGER_DRIVER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
+#include "components/autofill/core/common/password_form.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class FakeMojoPasswordManagerDriver
+ : public autofill::mojom::PasswordManagerDriver {
+ public:
+ FakeMojoPasswordManagerDriver();
+
+ ~FakeMojoPasswordManagerDriver() override;
+
+ void BindReceiver(
+ mojo::PendingAssociatedReceiver<autofill::mojom::PasswordManagerDriver>
+ receiver);
+
+ // Flushes all pending messages from the associated binding.
+ void Flush();
+
+ // mojom::PasswordManagerDriver:
+ // TODO(crbug.com/948062): Migrate the other methods to GMock as well.
+ MOCK_METHOD0(ShowTouchToFill, void());
+
+ MOCK_METHOD4(ShowPasswordSuggestions,
+ void(base::i18n::TextDirection,
+ const base::string16&,
+ int,
+ const gfx::RectF&));
+
+ bool called_show_not_secure_warning() const {
+ return called_show_not_secure_warning_;
+ }
+
+ bool called_password_form_submitted() const {
+ return called_password_form_submitted_ && password_form_submitted_ &&
+ !password_form_submitted_->only_for_fallback;
+ }
+
+ bool called_password_form_submitted_only_for_fallback() const {
+ return called_password_form_submitted_ && password_form_submitted_ &&
+ password_form_submitted_->only_for_fallback;
+ }
+
+ const base::Optional<autofill::PasswordForm>& password_form_submitted()
+ const {
+ return password_form_submitted_;
+ }
+
+ bool called_same_document_navigation() const {
+ return called_same_document_navigation_;
+ }
+
+ const base::Optional<autofill::PasswordForm>& password_form_maybe_submitted()
+ const {
+ return password_form_maybe_submitted_;
+ }
+
+ bool called_password_forms_parsed() const {
+ return called_password_forms_parsed_;
+ }
+
+ const base::Optional<std::vector<autofill::PasswordForm>>&
+ password_forms_parsed() const {
+ return password_forms_parsed_;
+ }
+
+ bool called_password_forms_rendered() const {
+ return called_password_forms_rendered_;
+ }
+
+ const base::Optional<std::vector<autofill::PasswordForm>>&
+ password_forms_rendered() const {
+ return password_forms_rendered_;
+ }
+
+ void reset_password_forms_calls() {
+ called_password_forms_parsed_ = false;
+ password_forms_parsed_ = base::nullopt;
+ called_password_forms_rendered_ = false;
+ password_forms_rendered_ = base::nullopt;
+ }
+
+ bool called_record_save_progress() const {
+ return called_record_save_progress_;
+ }
+
+ bool called_user_modified_password_field() const {
+ return called_user_modified_password_field_;
+ }
+
+ bool called_save_generation_field() const {
+ return called_save_generation_field_;
+ }
+
+ const base::Optional<base::string16>& save_generation_field() const {
+ return save_generation_field_;
+ }
+
+ void reset_save_generation_field() {
+ called_save_generation_field_ = false;
+ save_generation_field_ = base::nullopt;
+ }
+
+ int called_check_safe_browsing_reputation_cnt() const {
+ return called_check_safe_browsing_reputation_cnt_;
+ }
+
+ int called_show_manual_fallback_for_saving_count() const {
+ return called_show_manual_fallback_for_saving_count_;
+ }
+
+ autofill::mojom::FocusedFieldType last_focused_field_type() const {
+ return last_focused_field_type_;
+ }
+
+ private:
+ // mojom::PasswordManagerDriver:
+ void PasswordFormsParsed(
+ const std::vector<autofill::PasswordForm>& forms) override;
+
+ void PasswordFormsRendered(
+ const std::vector<autofill::PasswordForm>& visible_forms,
+ bool did_stop_loading) override;
+
+ void PasswordFormSubmitted(
+ const autofill::PasswordForm& password_form) override;
+
+ void SameDocumentNavigation(autofill::mojom::SubmissionIndicatorEvent
+ submission_indication_event) override;
+
+ void RecordSavePasswordProgress(const std::string& log) override;
+
+ void UserModifiedPasswordField() override;
+
+ void UserModifiedNonPasswordField(uint32_t renderer_id,
+ const base::string16& value) override;
+
+ void CheckSafeBrowsingReputation(const GURL& form_action,
+ const GURL& frame_url) override;
+
+ void ShowManualFallbackForSaving(
+ const autofill::PasswordForm& password_form) override;
+ void HideManualFallbackForSaving() override;
+ void FocusedInputChanged(
+ autofill::mojom::FocusedFieldType focused_field_type) override;
+ void LogFirstFillingResult(uint32_t form_renderer_id,
+ int32_t result) override {}
+
+ // Records whether ShowNotSecureWarning() gets called.
+ bool called_show_not_secure_warning_ = false;
+ // Records whether PasswordFormSubmitted() gets called.
+ bool called_password_form_submitted_ = false;
+ // Records data received via PasswordFormSubmitted() call.
+ base::Optional<autofill::PasswordForm> password_form_submitted_;
+ // Records data received via ShowManualFallbackForSaving() call.
+ base::Optional<autofill::PasswordForm> password_form_maybe_submitted_;
+ // Records whether SameDocumentNavigation() gets called.
+ bool called_same_document_navigation_ = false;
+ // Records whether PasswordFormsParsed() gets called.
+ bool called_password_forms_parsed_ = false;
+ // Records if the list received via PasswordFormsParsed() call was empty.
+ base::Optional<std::vector<autofill::PasswordForm>> password_forms_parsed_;
+ // Records whether PasswordFormsRendered() gets called.
+ bool called_password_forms_rendered_ = false;
+ // Records data received via PasswordFormsRendered() call.
+ base::Optional<std::vector<autofill::PasswordForm>> password_forms_rendered_;
+ // Records whether RecordSavePasswordProgress() gets called.
+ bool called_record_save_progress_ = false;
+ // Records whether UserModifiedPasswordField() gets called.
+ bool called_user_modified_password_field_ = false;
+ // Records whether SaveGenerationFieldDetectedByClassifier() gets called.
+ bool called_save_generation_field_ = false;
+ // Records data received via SaveGenerationFieldDetectedByClassifier() call.
+ base::Optional<base::string16> save_generation_field_;
+
+ // Records number of times CheckSafeBrowsingReputation() gets called.
+ int called_check_safe_browsing_reputation_cnt_ = 0;
+
+ // Records the number of request to show manual fallback for password saving.
+ // If it is zero, the fallback is not available.
+ int called_show_manual_fallback_for_saving_count_ = 0;
+
+ // Records the last focused field type that FocusedInputChanged() was called
+ // with.
+ autofill::mojom::FocusedFieldType last_focused_field_type_ =
+ autofill::mojom::FocusedFieldType::kUnknown;
+
+ mojo::AssociatedReceiver<autofill::mojom::PasswordManagerDriver> receiver_{
+ this};
+};
+
+#endif // CHROME_RENDERER_AUTOFILL_FAKE_MOJO_PASSWORD_MANAGER_DRIVER_H_
diff --git a/chromium/chrome/renderer/autofill/fake_password_generation_driver.cc b/chromium/chrome/renderer/autofill/fake_password_generation_driver.cc
new file mode 100644
index 00000000000..3fea76acec4
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/fake_password_generation_driver.cc
@@ -0,0 +1,22 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/autofill/fake_password_generation_driver.h"
+
+#include <utility>
+
+FakePasswordGenerationDriver::FakePasswordGenerationDriver() = default;
+
+FakePasswordGenerationDriver::~FakePasswordGenerationDriver() = default;
+
+void FakePasswordGenerationDriver::BindReceiver(
+ mojo::PendingAssociatedReceiver<autofill::mojom::PasswordGenerationDriver>
+ receiver) {
+ receiver_.Bind(std::move(receiver));
+}
+
+void FakePasswordGenerationDriver::Flush() {
+ if (receiver_.is_bound())
+ receiver_.FlushForTesting();
+}
diff --git a/chromium/chrome/renderer/autofill/fake_password_generation_driver.h b/chromium/chrome/renderer/autofill/fake_password_generation_driver.h
new file mode 100644
index 00000000000..597425bdf8b
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/fake_password_generation_driver.h
@@ -0,0 +1,58 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_AUTOFILL_FAKE_PASSWORD_GENERATION_DRIVER_H_
+#define CHROME_RENDERER_AUTOFILL_FAKE_PASSWORD_GENERATION_DRIVER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
+#include "components/autofill/core/common/password_form.h"
+#include "components/autofill/core/common/password_generation_util.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class FakePasswordGenerationDriver
+ : public autofill::mojom::PasswordGenerationDriver {
+ public:
+ FakePasswordGenerationDriver();
+
+ ~FakePasswordGenerationDriver() override;
+
+ void BindReceiver(
+ mojo::PendingAssociatedReceiver<autofill::mojom::PasswordGenerationDriver>
+ receiver);
+
+ void Flush();
+
+ // autofill::mojom::PasswordGenerationDriver:
+ MOCK_METHOD1(GenerationAvailableForForm,
+ void(const autofill::PasswordForm& password_form));
+ MOCK_METHOD1(
+ AutomaticGenerationAvailable,
+ void(const autofill::password_generation::PasswordGenerationUIData&));
+ MOCK_METHOD3(ShowPasswordEditingPopup,
+ void(const gfx::RectF&,
+ const autofill::PasswordForm&,
+ uint32_t));
+ MOCK_METHOD0(PasswordGenerationRejectedByTyping, void());
+ MOCK_METHOD1(PresaveGeneratedPassword,
+ void(const autofill::PasswordForm& password_form));
+ MOCK_METHOD1(PasswordNoLongerGenerated,
+ void(const autofill::PasswordForm& password_form));
+ MOCK_METHOD0(FrameWasScrolled, void());
+ MOCK_METHOD0(GenerationElementLostFocus, void());
+
+ private:
+ mojo::AssociatedReceiver<autofill::mojom::PasswordGenerationDriver> receiver_{
+ this};
+
+ DISALLOW_COPY_AND_ASSIGN(FakePasswordGenerationDriver);
+};
+
+#endif // CHROME_RENDERER_AUTOFILL_FAKE_PASSWORD_GENERATION_DRIVER_H_
diff --git a/chromium/chrome/renderer/autofill/form_autocomplete_browsertest.cc b/chromium/chrome/renderer/autofill/form_autocomplete_browsertest.cc
new file mode 100644
index 00000000000..96bea1d23a6
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/form_autocomplete_browsertest.cc
@@ -0,0 +1,872 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <tuple>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "components/autofill/core/common/form_data.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_form_element.h"
+#include "third_party/blink/public/web/web_input_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebInputElement;
+using blink::WebString;
+
+namespace autofill {
+
+using mojom::SubmissionSource;
+
+namespace {
+
+class FakeContentAutofillDriver : public mojom::AutofillDriver {
+ public:
+ FakeContentAutofillDriver() : did_unfocus_form_(false) {}
+
+ ~FakeContentAutofillDriver() override {}
+
+ void BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::AutofillDriver> receiver) {
+ receivers_.Add(this, std::move(receiver));
+ }
+
+ bool did_unfocus_form() const { return did_unfocus_form_; }
+
+ const FormData* form_submitted() const { return form_submitted_.get(); }
+
+ bool known_success() const { return known_success_; }
+
+ SubmissionSource submission_source() const { return submission_source_; }
+
+ const FormFieldData* select_control_changed() const {
+ return select_control_changed_.get();
+ }
+
+ private:
+ // mojom::AutofillDriver:
+ void FormsSeen(const std::vector<FormData>& forms,
+ base::TimeTicks timestamp) override {}
+
+ void FormSubmitted(const FormData& form,
+ bool known_success,
+ SubmissionSource source) override {
+ form_submitted_.reset(new FormData(form));
+ known_success_ = known_success;
+ submission_source_ = source;
+ }
+
+ void TextFieldDidChange(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box,
+ base::TimeTicks timestamp) override {}
+
+ void TextFieldDidScroll(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box) override {}
+
+ void SelectControlDidChange(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box) override {
+ select_control_changed_ = std::make_unique<FormFieldData>(field);
+ }
+
+ void QueryFormFieldAutofill(int32_t id,
+ const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box,
+ bool autoselect_first_field) override {}
+
+ void HidePopup() override {}
+
+ void FocusNoLongerOnForm() override { did_unfocus_form_ = true; }
+
+ void FocusOnFormField(const FormData& form,
+ const FormFieldData& field,
+ const gfx::RectF& bounding_box) override {}
+
+ void DidFillAutofillFormData(const FormData& form,
+ base::TimeTicks timestamp) override {}
+
+ void DidPreviewAutofillFormData() override {}
+
+ void DidEndTextFieldEditing() override {}
+
+ void SetDataList(const std::vector<base::string16>& values,
+ const std::vector<base::string16>& labels) override {}
+
+ void SelectFieldOptionsDidChange(const autofill::FormData& form) override {}
+
+ // Records whether FocusNoLongerOnForm() get called.
+ bool did_unfocus_form_;
+
+ // Records the form data received via FormSubmitted() call.
+ std::unique_ptr<FormData> form_submitted_;
+
+ bool known_success_;
+
+ SubmissionSource submission_source_;
+
+ std::unique_ptr<FormFieldData> select_control_changed_;
+
+ mojo::AssociatedReceiverSet<mojom::AutofillDriver> receivers_;
+};
+
+// Helper function to verify the form-related messages received from the
+// renderer. The same data is expected in both messages. Depending on
+// |expect_submitted_message|, will verify presence of FormSubmitted message.
+void VerifyReceivedRendererMessages(
+ const FakeContentAutofillDriver& fake_driver,
+ const std::string& fname,
+ const std::string& lname,
+ bool expect_known_success,
+ SubmissionSource expect_submission_source) {
+ ASSERT_TRUE(fake_driver.form_submitted());
+
+ // The tuple also includes a timestamp, which is ignored.
+ const FormData& submitted_form = *(fake_driver.form_submitted());
+ ASSERT_LE(2U, submitted_form.fields.size());
+ EXPECT_EQ(base::ASCIIToUTF16("fname"), submitted_form.fields[0].name);
+ EXPECT_EQ(base::UTF8ToUTF16(fname), submitted_form.fields[0].value);
+ EXPECT_EQ(base::ASCIIToUTF16("lname"), submitted_form.fields[1].name);
+ EXPECT_EQ(expect_known_success, fake_driver.known_success());
+ EXPECT_EQ(expect_submission_source,
+ mojo::ConvertTo<SubmissionSource>(fake_driver.submission_source()));
+}
+
+void VerifyReceivedAddressRendererMessages(
+ const FakeContentAutofillDriver& fake_driver,
+ const std::string& address,
+ bool expect_known_success,
+ SubmissionSource expect_submission_source) {
+ ASSERT_TRUE(fake_driver.form_submitted());
+
+ // The tuple also includes a timestamp, which is ignored.
+ const FormData& submitted_form = *(fake_driver.form_submitted());
+ ASSERT_LE(1U, submitted_form.fields.size());
+ EXPECT_EQ(base::ASCIIToUTF16("address"), submitted_form.fields[0].name);
+ EXPECT_EQ(base::UTF8ToUTF16(address), submitted_form.fields[0].value);
+ EXPECT_EQ(expect_known_success, fake_driver.known_success());
+ EXPECT_EQ(expect_submission_source,
+ mojo::ConvertTo<SubmissionSource>(fake_driver.submission_source()));
+}
+
+// Helper function to verify that NO form-related messages are received from the
+// renderer.
+void VerifyNoSubmitMessagesReceived(
+ const FakeContentAutofillDriver& fake_driver) {
+ // No submission messages sent.
+ EXPECT_EQ(nullptr, fake_driver.form_submitted());
+}
+
+// Simulates receiving a message from the browser to fill a form.
+void SimulateOnFillForm(autofill::AutofillAgent* autofill_agent,
+ blink::WebLocalFrame* main_frame) {
+ WebDocument document = main_frame->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+
+ // This call is necessary to setup the autofill agent appropriate for the
+ // user selection; simulates the menu actually popping up.
+ autofill_agent->FormControlElementClicked(element.To<WebInputElement>(),
+ false);
+
+ FormData data;
+ data.name = base::ASCIIToUTF16("name");
+ data.url = GURL("http://example.com/");
+ data.action = GURL("http://example.com/blade.php");
+ data.is_form_tag = true; // Default value.
+
+ FormFieldData field_data;
+ field_data.name = base::ASCIIToUTF16("fname");
+ field_data.value = base::ASCIIToUTF16("John");
+ field_data.is_autofilled = true;
+ data.fields.push_back(field_data);
+
+ field_data.name = base::ASCIIToUTF16("lname");
+ field_data.value = base::ASCIIToUTF16("Smith");
+ field_data.is_autofilled = true;
+ data.fields.push_back(field_data);
+
+ autofill_agent->FillForm(0, data);
+}
+
+} // end namespace
+
+class FormAutocompleteTest : public ChromeRenderViewTest {
+ public:
+ FormAutocompleteTest() {}
+ ~FormAutocompleteTest() override {}
+
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+ // We only use the fake driver for main frame
+ // because our test cases only involve the main frame.
+ blink::AssociatedInterfaceProvider* remote_interfaces =
+ view_->GetMainRenderFrame()->GetRemoteAssociatedInterfaces();
+ remote_interfaces->OverrideBinderForTesting(
+ mojom::AutofillDriver::Name_,
+ base::BindRepeating(&FormAutocompleteTest::BindAutofillDriver,
+ base::Unretained(this)));
+ }
+
+ void BindAutofillDriver(mojo::ScopedInterfaceEndpointHandle handle) {
+ fake_driver_.BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::AutofillDriver>(
+ std::move(handle)));
+ }
+
+ void SimulateUserInput(const blink::WebString& id, const std::string& value) {
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(id);
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, value);
+ }
+
+ FakeContentAutofillDriver fake_driver_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FormAutocompleteTest);
+};
+
+// Tests that submitting a form generates FormSubmitted message with the form
+// fields.
+TEST_F(FormAutocompleteTest, NormalFormSubmit) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='about:blank'>"
+ "<input name='fname' value='Rick'/>"
+ "<input name='lname' value='Deckard'/></form></html>");
+
+ // Submit the form.
+ ExecuteJavaScriptForTests("document.getElementById('myForm').submit();");
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ false /* expect_known_success */,
+ SubmissionSource::FORM_SUBMISSION);
+}
+
+// Tests that FormSubmitted message is generated even the submit event isn't
+// propagated by Javascript.
+TEST_F(FormAutocompleteTest, SubmitEventPrevented) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm'><input name='fname' value='Rick'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form>"
+ "</html>");
+
+ // Submit the form.
+ ExecuteJavaScriptForTests(
+ "var form = document.forms[0];"
+ "form.onsubmit = function(event) { event.preventDefault(); };"
+ "document.querySelector('input[type=submit]').click();");
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ false /* expect_known_success */,
+ SubmissionSource::FORM_SUBMISSION);
+}
+
+// Tests that completing an Ajax request and having the form disappear will
+// trigger submission from Autofill's point of view.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_NoLongerVisible) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // Simulate user input so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ // Simulate removing the form just before the ajax request completes.
+ ExecuteJavaScriptForTests(
+ "var element = document.getElementById('myForm');"
+ "element.parentNode.removeChild(element);");
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ true /* expect_known_success */,
+ SubmissionSource::XHR_SUCCEEDED);
+}
+
+// Tests that completing an Ajax request and having the form with a specific
+// action disappear will trigger submission from Autofill's point of view, even
+// if there is another form with the same data but different action on the page.
+TEST_F(FormAutocompleteTest,
+ AjaxSucceeded_NoLongerVisible_DifferentActionsSameData) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form>"
+ "<form id='myForm2' action='http://example.com/runner.php'>"
+ "<input name='fname' id='fname2' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // Simulate user input so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ // Simulate removing the form just before the ajax request completes.
+ ExecuteJavaScriptForTests(
+ "var element = document.getElementById('myForm');"
+ "element.parentNode.removeChild(element);");
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ true /* expect_known_success */,
+ SubmissionSource::XHR_SUCCEEDED);
+}
+
+// Tests that completing an Ajax request and having the form with no action
+// specified disappear will trigger submission from Autofill's point of view,
+// even if there is still another form with no action in the page. It will
+// compare field data within the forms.
+// TODO(kolos) Re-enable when the implementation of IsFormVisible is on-par
+// for these platforms.
+#if defined(OS_MACOSX)
+#define MAYBE_NoLongerVisibleBothNoActions DISABLED_NoLongerVisibleBothNoActions
+#else
+#define MAYBE_NoLongerVisibleBothNoActions NoLongerVisibleBothNoActions
+#endif
+TEST_F(FormAutocompleteTest, MAYBE_NoLongerVisibleBothNoActions) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form>"
+ "<form id='myForm2'>"
+ "<input name='fname' id='fname2' value='John'/>"
+ "<input name='lname' value='Doe'/><input type=submit></form></html>");
+
+ // Simulate user input so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ // Simulate removing the form just before the ajax request completes.
+ ExecuteJavaScriptForTests(
+ "var element = document.getElementById('myForm');"
+ "element.parentNode.removeChild(element);");
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ true /* expect_known_success */,
+ SubmissionSource::XHR_SUCCEEDED);
+}
+
+// Tests that completing an Ajax request and having the form with no action
+// specified disappear will trigger submission from Autofill's point of view.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_NoLongerVisible_NoAction) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // Simulate user input so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ // Simulate removing the form just before the ajax request completes.
+ ExecuteJavaScriptForTests("var element = document.getElementById('myForm');"
+ "element.parentNode.removeChild(element);");
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ true /* expect_known_success */,
+ SubmissionSource::XHR_SUCCEEDED);
+}
+
+// Tests that completing an Ajax request but leaving a form visible will not
+// trigger submission from Autofill's point of view.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_StillVisible) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // Simulate user input so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ // No submission messages sent.
+ VerifyNoSubmitMessagesReceived(fake_driver_);
+}
+
+// Tests that completing an Ajax request without any prior form interaction
+// does not trigger form submission from Autofill's point of view.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_NoFormInteractionInvisible) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // No form interaction.
+
+ // Simulate removing the form just before the ajax request completes.
+ ExecuteJavaScriptForTests("var element = document.getElementById('myForm');"
+ "element.parentNode.removeChild(element);");
+
+ // Simulate an Ajax request completing without prior user interaction.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ // No submission messages sent.
+ VerifyNoSubmitMessagesReceived(fake_driver_);
+}
+
+// Tests that completing an Ajax request after having autofilled a form,
+// with the form disappearing, will trigger submission from Autofill's
+// point of view.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_FilledFormIsInvisible) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname'/>"
+ "<input name='lname'/></form></html>");
+
+ // Simulate filling a form using Autofill.
+ SimulateOnFillForm(autofill_agent_, GetMainFrame());
+
+ // Simulate user input since ajax request doesn't fire submission message
+ // if there is no user input.
+ SimulateUserInput(WebString::FromUTF8("fname"), std::string("Rick"));
+
+ // Simulate removing the form just before the ajax request completes.
+ ExecuteJavaScriptForTests("var element = document.getElementById('myForm');"
+ "element.parentNode.removeChild(element);");
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Smith",
+ true /* expect_known_success */,
+ SubmissionSource::XHR_SUCCEEDED);
+}
+
+// Tests that completing an Ajax request after having autofilled a form,
+// without the form disappearing, will not trigger submission from Autofill's
+// point of view.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_FilledFormStillVisible) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Rick'/>"
+ "<input name='lname' value='Deckard'/></form></html>");
+
+ // Simulate filling a form using Autofill.
+ SimulateOnFillForm(autofill_agent_, GetMainFrame());
+
+ // Form still visible.
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ // No submission messages sent.
+ VerifyNoSubmitMessagesReceived(fake_driver_);
+}
+
+// Tests that completing an Ajax request without a form present will still
+// trigger submission, if all the inputs the user has modified disappear.
+TEST_F(FormAutocompleteTest, AjaxSucceeded_FormlessElements) {
+ // Load a "form." Note that kRequiredFieldsForUpload fields are required
+ // for the formless logic to trigger, so we add a throwaway third field.
+ LoadHTML(
+ "<head><title>Checkout</title></head>"
+ "<input type='text' name='fname' id='fname'/>"
+ "<input type='text' name='lname' value='Puckett'/>"
+ "<input type='number' name='number' value='34'/>");
+
+ // Simulate user input.
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Kirby"));
+
+ // Remove element from view.
+ ExecuteJavaScriptForTests(
+ "var element = document.getElementById('fname');"
+ "element.style.display = 'none';");
+
+ // Simulate AJAX request.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Kirby", "Puckett",
+ true /* expect_known_success */,
+ SubmissionSource::XHR_SUCCEEDED);
+}
+
+// Unit test for CollectFormlessElements.
+TEST_F(FormAutocompleteTest, CollectFormlessElements) {
+ LoadHTML(
+ "<html><title>Checkout</title></head>"
+ "<input type='text' name='text_input'/>"
+ "<input type='checkbox' name='check_input'/>"
+ "<input type='number' name='number_input'/>"
+ "<select name='select_input'/>"
+ " <option value='option_1'></option>"
+ " <option value='option_2'></option>"
+ "</select>"
+ "<form><input type='text' name='excluded'/></form>"
+ "</html>");
+
+ FormData result;
+ autofill_agent_->CollectFormlessElements(&result);
+
+ // Asserting size 4 also ensures that 'excluded' field inside <form> is not
+ // collected.
+ ASSERT_EQ(4U, result.fields.size());
+ EXPECT_EQ(base::ASCIIToUTF16("text_input"), result.fields[0].name);
+ EXPECT_EQ(base::ASCIIToUTF16("check_input"), result.fields[1].name);
+ EXPECT_EQ(base::ASCIIToUTF16("number_input"), result.fields[2].name);
+ EXPECT_EQ(base::ASCIIToUTF16("select_input"), result.fields[3].name);
+}
+
+// Unit test for AutofillAgent::AcceptDataListSuggestion.
+TEST_F(FormAutocompleteTest, AcceptDataListSuggestion) {
+ LoadHTML(
+ "<html>"
+ "<input id='empty' type='email' multiple />"
+ "<input id='multi_one' type='email' multiple value='one@example.com'/>"
+ "<input id='multi_two' type='email' multiple"
+ " value='one@example.com,two@example.com'/>"
+ "<input id='multi_trailing' type='email' multiple"
+ " value='one@example.com,two@example.com,'/>"
+ "<input id='not_multi' type='email'"
+ " value='one@example.com,two@example.com,'/>"
+ "<input id='not_email' type='text' multiple"
+ " value='one@example.com,two@example.com,'/>"
+ "</html>");
+ WebDocument document = GetMainFrame()->GetDocument();
+
+ // Each case tests a different field value with the same suggestion.
+ const base::string16 kSuggestion =
+ base::ASCIIToUTF16("suggestion@example.com");
+ struct TestCase {
+ std::string id;
+ std::string expected;
+ } cases[] = {
+ // Empty text field; expect to populate with suggestion.
+ {"empty", "suggestion@example.com"},
+ // Single entry; expect to replace with suggestion.
+ {"multi_one", "suggestion@example.com"},
+ // Two comma-separated entries; expect to replace second with suggestion.
+ {"multi_two", "one@example.com,suggestion@example.com"},
+ // Two comma-separated entries with trailing comma; expect to append
+ // suggestion.
+ {"multi_trailing",
+ "one@example.com,two@example.com,suggestion@example.com"},
+ // Do not apply this logic for a non-multiple or non-email field.
+ {"not_multi", "suggestion@example.com"},
+ {"not_email", "suggestion@example.com"},
+ };
+
+ for (const auto& c : cases) {
+ WebElement element = document.GetElementById(WebString::FromUTF8(c.id));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement* input_element = blink::ToWebInputElement(&element);
+ ASSERT_TRUE(input_element);
+ // Select this element in |autofill_agent_|.
+ autofill_agent_->FormControlElementClicked(element.To<WebInputElement>(),
+ false);
+
+ autofill_agent_->AcceptDataListSuggestion(kSuggestion);
+ EXPECT_EQ(c.expected, input_element->Value().Utf8()) << "Case id: " << c.id;
+ }
+}
+
+// Test that a FocusNoLongerOnForm message is sent if focus goes from an
+// interacted form to an element outside the form.
+TEST_F(FormAutocompleteTest,
+ InteractedFormNoLongerFocused_FocusNoLongerOnForm) {
+ // Load a form.
+ LoadHTML(
+ "<html><input type='text' id='different'/>"
+ "<form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // Simulate user input so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ ASSERT_FALSE(fake_driver_.did_unfocus_form());
+
+ // Change focus to a different node outside the form.
+ WebElement different =
+ document.GetElementById(WebString::FromUTF8("different"));
+ SetFocused(different);
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+
+ EXPECT_TRUE(fake_driver_.did_unfocus_form());
+}
+
+// Test that a FocusNoLongerOnForm message is sent if focus goes from one
+// interacted form to another.
+TEST_F(FormAutocompleteTest, InteractingInDifferentForms_FocusNoLongerOnForm) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='http://example.com/blade.php'>"
+ "<input name='fname' id='fname' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form>"
+ "<form id='myForm2' action='http://example.com/runner.php'>"
+ "<input name='fname' id='fname2' value='Bob'/>"
+ "<input name='lname' value='Deckard'/><input type=submit></form></html>");
+
+ // Simulate user input in the first form so that the form is "remembered".
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("fname"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("Rick"));
+
+ ASSERT_FALSE(fake_driver_.did_unfocus_form());
+
+ // Simulate user input in the second form so that a "no longer focused"
+ // message is sent for the first form.
+ document = GetMainFrame()->GetDocument();
+ element = document.GetElementById(WebString::FromUTF8("fname2"));
+ ASSERT_FALSE(element.IsNull());
+ fname_element = element.To<WebInputElement>();
+ SimulateUserInputChangeForElement(&fname_element, std::string("John"));
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+
+ EXPECT_TRUE(fake_driver_.did_unfocus_form());
+}
+
+// Tests that submitting a form that has autocomplete="off" generates
+// WillSubmitForm and FormSubmitted messages.
+TEST_F(FormAutocompleteTest, AutoCompleteOffFormSubmit) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' autocomplete='off' action='about:blank'>"
+ "<input name='fname' value='Rick'/>"
+ "<input name='lname' value='Deckard'/>"
+ "</form></html>");
+
+ // Submit the form.
+ ExecuteJavaScriptForTests("document.getElementById('myForm').submit();");
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ false /* expect_known_success */,
+ SubmissionSource::FORM_SUBMISSION);
+}
+
+// Tests that fields with autocomplete off are submitted.
+TEST_F(FormAutocompleteTest, AutoCompleteOffInputSubmit) {
+ // Load a form.
+ LoadHTML(
+ "<html><form id='myForm' action='about:blank'>"
+ "<input name='fname' value='Rick'/>"
+ "<input name='lname' value='Deckard' autocomplete='off'/>"
+ "</form></html>");
+
+ // Submit the form.
+ ExecuteJavaScriptForTests("document.getElementById('myForm').submit();");
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ false /* expect_known_success */,
+ SubmissionSource::FORM_SUBMISSION);
+}
+
+// Tests that submitting a form that has been dynamically set as autocomplete
+// off generates WillSubmitForm and FormSubmitted messages.
+// Note: We previously did the opposite, for bug http://crbug.com/36520
+TEST_F(FormAutocompleteTest, DynamicAutoCompleteOffFormSubmit) {
+ LoadHTML(
+ "<html><form id='myForm' action='about:blank'>"
+ "<input name='fname' value='Rick'/>"
+ "<input name='lname' value='Deckard'/></form></html>");
+
+ WebElement element =
+ GetMainFrame()->GetDocument().GetElementById(blink::WebString("myForm"));
+ ASSERT_FALSE(element.IsNull());
+ blink::WebFormElement form = element.To<blink::WebFormElement>();
+ EXPECT_TRUE(form.AutoComplete());
+
+ // Dynamically mark the form as autocomplete off.
+ ExecuteJavaScriptForTests(
+ "document.getElementById('myForm')."
+ "setAttribute('autocomplete', 'off');");
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(form.AutoComplete());
+
+ // Submit the form.
+ ExecuteJavaScriptForTests("document.getElementById('myForm').submit();");
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedRendererMessages(fake_driver_, "Rick", "Deckard",
+ false /* expect_known_success */,
+ SubmissionSource::FORM_SUBMISSION);
+}
+
+TEST_F(FormAutocompleteTest, FormSubmittedByDOMMutationAfterXHR) {
+ LoadHTML(
+ "<html>"
+ "<input type='text' id='address_field' name='address' autocomplete='on'>"
+ "</html>");
+
+ SimulateUserInput(WebString::FromUTF8("address_field"), std::string("City"));
+
+ // Simulate an Ajax request completing.
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)->AjaxSucceeded();
+
+ // Hide elements to simulate successful form submission.
+ std::string hide_elements =
+ "var address = document.getElementById('address_field');"
+ "address.style = 'display:none';";
+
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedAddressRendererMessages(
+ fake_driver_, "City", true /* expect_known_success */,
+ SubmissionSource::DOM_MUTATION_AFTER_XHR);
+}
+
+TEST_F(FormAutocompleteTest, FormSubmittedBySameDocumentNavigation) {
+ LoadHTML(
+ "<html>"
+ "<input type='text' id='address_field' name='address' autocomplete='on'>"
+ "</html>");
+
+ SimulateUserInput(WebString::FromUTF8("address_field"), std::string("City"));
+
+ // Hide elements to simulate successful form submission.
+ std::string hide_elements =
+ "var address = document.getElementById('address_field');"
+ "address.style = 'display:none';";
+
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ // Simulate same document navigation.
+ autofill_agent_->form_tracker_for_testing()->DidCommitProvisionalLoad(
+ true /*is_same_document_navigation*/, ui::PAGE_TRANSITION_LINK);
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedAddressRendererMessages(
+ fake_driver_, "City", true /* expect_known_success */,
+ SubmissionSource::SAME_DOCUMENT_NAVIGATION);
+}
+
+TEST_F(FormAutocompleteTest, FormSubmittedByProbablyFormSubmitted) {
+ LoadHTML(
+ "<html>"
+ "<input type='text' id='address_field' name='address' autocomplete='on'>"
+ "</html>");
+
+ SimulateUserInput(WebString::FromUTF8("address_field"), std::string("City"));
+
+ // Hide elements to simulate successful form submission.
+ std::string hide_elements =
+ "var address = document.getElementById('address_field');"
+ "address.style = 'display:none';";
+
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ // Simulate navigation.
+ autofill_agent_->form_tracker_for_testing()
+ ->FireProbablyFormSubmittedForTesting();
+
+ base::RunLoop().RunUntilIdle();
+
+ VerifyReceivedAddressRendererMessages(
+ fake_driver_, "City", false /* expect_known_success */,
+ SubmissionSource::PROBABLY_FORM_SUBMITTED);
+}
+
+TEST_F(FormAutocompleteTest, SelectControlChanged) {
+ LoadHTML(
+ "<html>"
+ "<form>"
+ "<select id='color'><option value='red'>red</option><option "
+ "value='blue'>blue</option></select>"
+ "</form>"
+ "</html>");
+
+ std::string change_value =
+ "var color = document.getElementById('color');"
+ "color.selectedIndex = 1;";
+
+ ExecuteJavaScriptForTests(change_value.c_str());
+ WebElement element =
+ GetMainFrame()->GetDocument().GetElementById(blink::WebString("color"));
+ static_cast<blink::WebAutofillClient*>(autofill_agent_)
+ ->SelectControlDidChange(
+ *reinterpret_cast<blink::WebFormControlElement*>(&element));
+ base::RunLoop().RunUntilIdle();
+
+ const FormFieldData* field = fake_driver_.select_control_changed();
+ ASSERT_TRUE(field);
+ EXPECT_EQ(base::ASCIIToUTF16("color"), field->name);
+ EXPECT_EQ(base::ASCIIToUTF16("blue"), field->value);
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/form_autofill_browsertest.cc b/chromium/chrome/renderer/autofill/form_autofill_browsertest.cc
new file mode 100644
index 00000000000..1d924559456
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/form_autofill_browsertest.cc
@@ -0,0 +1,5838 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/format_macros.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/autofill/content/renderer/form_autofill_util.h"
+#include "components/autofill/content/renderer/form_cache.h"
+#include "components/autofill/core/common/autofill_data_validation.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/form_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_element_collection.h"
+#include "third_party/blink/public/web/web_form_control_element.h"
+#include "third_party/blink/public/web/web_form_element.h"
+#include "third_party/blink/public/web/web_input_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "third_party/blink/public/web/web_select_element.h"
+
+#if defined(OS_WIN)
+#include "third_party/blink/public/web/win/web_font_rendering.h"
+#endif
+
+using autofill::features::kAutofillEnforceMinRequiredFieldsForHeuristics;
+using autofill::features::kAutofillEnforceMinRequiredFieldsForQuery;
+using autofill::features::kAutofillEnforceMinRequiredFieldsForUpload;
+using base::ASCIIToUTF16;
+using blink::WebAutofillState;
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebFormControlElement;
+using blink::WebFormElement;
+using blink::WebInputElement;
+using blink::WebLocalFrame;
+using blink::WebSelectElement;
+using blink::WebString;
+using blink::WebVector;
+
+namespace autofill {
+namespace form_util {
+
+namespace {
+
+struct AutofillFieldCase {
+ const char* const form_control_type;
+ const char* const id_attribute;
+ const char* const initial_value;
+ const char* const autocomplete_attribute; // The autocomplete attribute of
+ // the element.
+ bool should_be_autofilled; // Whether the filed should be autofilled.
+ const char* const autofill_value; // The value being used to fill the field.
+ const char* const expected_value; // The expected value after Autofill
+ // or Preview.
+};
+
+struct WebElementDescriptor {
+ enum RetrievalMethod {
+ CSS_SELECTOR,
+ ID,
+ NONE,
+ };
+
+ // Information to retrieve element with.
+ std::string descriptor;
+
+ // Which retrieval method to use.
+ RetrievalMethod retrieval_method = NONE;
+};
+
+const char kFormHtml[] =
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='hidden' id='imhidden'/>"
+ " <INPUT type='text' id='notempty' value='Hi'/>"
+ " <INPUT type='text' autocomplete='off' id='noautocomplete'/>"
+ " <INPUT type='text' disabled='disabled' id='notenabled'/>"
+ " <INPUT type='text' readonly id='readonly'/>"
+ " <INPUT type='text' style='visibility: hidden'"
+ " id='invisible'/>"
+ " <INPUT type='text' style='display: none' id='displaynone'/>"
+ " <INPUT type='month' id='month'/>"
+ " <INPUT type='month' id='month-nonempty' value='2011-12'/>"
+ " <SELECT id='select'>"
+ " <OPTION></OPTION>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ " </SELECT>"
+ " <SELECT id='select-nonempty'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ " </SELECT>"
+ " <SELECT id='select-unchanged'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ " </SELECT>"
+ " <SELECT id='select-displaynone' style='display:none'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ " </SELECT>"
+ " <TEXTAREA id='textarea'></TEXTAREA>"
+ " <TEXTAREA id='textarea-nonempty'>Go&#10;away!</TEXTAREA>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>";
+
+// This constant uses a mixed-case title tag to be sure that the title match is
+// not case-sensitive. Other tests in this file use an all-lower title tag.
+const char kUnownedFormHtml[] =
+ "<HEAD><TITLE>Enter Shipping Info</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='hidden' id='imhidden'/>"
+ "<INPUT type='text' id='notempty' value='Hi'/>"
+ "<INPUT type='text' autocomplete='off' id='noautocomplete'/>"
+ "<INPUT type='text' disabled='disabled' id='notenabled'/>"
+ "<INPUT type='text' readonly id='readonly'/>"
+ "<INPUT type='text' style='visibility: hidden'"
+ " id='invisible'/>"
+ "<INPUT type='text' style='display: none' id='displaynone'/>"
+ "<INPUT type='month' id='month'/>"
+ "<INPUT type='month' id='month-nonempty' value='2011-12'/>"
+ "<SELECT id='select'>"
+ " <OPTION></OPTION>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-nonempty'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-unchanged'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-displaynone' style='display:none'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<TEXTAREA id='textarea'></TEXTAREA>"
+ "<TEXTAREA id='textarea-nonempty'>Go&#10;away!</TEXTAREA>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>";
+
+// This constant has no title tag, and should be passed to
+// LoadHTMLWithURLOverride to test the detection of unowned forms by URL.
+const char kUnownedUntitledFormHtml[] =
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='hidden' id='imhidden'/>"
+ "<INPUT type='text' id='notempty' value='Hi'/>"
+ "<INPUT type='text' autocomplete='off' id='noautocomplete'/>"
+ "<INPUT type='text' disabled='disabled' id='notenabled'/>"
+ "<INPUT type='text' readonly id='readonly'/>"
+ "<INPUT type='text' style='visibility: hidden'"
+ " id='invisible'/>"
+ "<INPUT type='text' style='display: none' id='displaynone'/>"
+ "<INPUT type='month' id='month'/>"
+ "<INPUT type='month' id='month-nonempty' value='2011-12'/>"
+ "<SELECT id='select'>"
+ " <OPTION></OPTION>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-nonempty'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-unchanged'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-displaynone' style='display:none'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<TEXTAREA id='textarea'></TEXTAREA>"
+ "<TEXTAREA id='textarea-nonempty'>Go&#10;away!</TEXTAREA>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>";
+
+// This constant does not have a title tag, but should match an unowned form
+// anyway because it is not English.
+const char kUnownedNonEnglishFormHtml[] =
+ "<HTML LANG='fr'>"
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='hidden' id='imhidden'/>"
+ "<INPUT type='text' id='notempty' value='Hi'/>"
+ "<INPUT type='text' autocomplete='off' id='noautocomplete'/>"
+ "<INPUT type='text' disabled='disabled' id='notenabled'/>"
+ "<INPUT type='text' readonly id='readonly'/>"
+ "<INPUT type='text' style='visibility: hidden'"
+ " id='invisible'/>"
+ "<INPUT type='text' style='display: none' id='displaynone'/>"
+ "<INPUT type='month' id='month'/>"
+ "<INPUT type='month' id='month-nonempty' value='2011-12'/>"
+ "<SELECT id='select'>"
+ " <OPTION></OPTION>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-nonempty'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-unchanged'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<SELECT id='select-displaynone' style='display:none'>"
+ " <OPTION value='CA' selected>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<TEXTAREA id='textarea'></TEXTAREA>"
+ "<TEXTAREA id='textarea-nonempty'>Go&#10;away!</TEXTAREA>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>"
+ "</HTML>";
+
+std::string RetrievalMethodToString(
+ const WebElementDescriptor::RetrievalMethod& method) {
+ switch (method) {
+ case WebElementDescriptor::CSS_SELECTOR:
+ return "CSS_SELECTOR";
+ case WebElementDescriptor::ID:
+ return "ID";
+ case WebElementDescriptor::NONE:
+ return "NONE";
+ }
+ NOTREACHED();
+ return "UNKNOWN";
+}
+
+bool ClickElement(const WebDocument& document,
+ const WebElementDescriptor& element_descriptor) {
+ WebString web_descriptor = WebString::FromUTF8(element_descriptor.descriptor);
+ blink::WebElement element;
+
+ switch (element_descriptor.retrieval_method) {
+ case WebElementDescriptor::CSS_SELECTOR: {
+ element = document.QuerySelector(web_descriptor);
+ break;
+ }
+ case WebElementDescriptor::ID:
+ element = document.GetElementById(web_descriptor);
+ break;
+ case WebElementDescriptor::NONE:
+ return true;
+ }
+
+ if (element.IsNull()) {
+ DVLOG(1) << "Could not find "
+ << element_descriptor.descriptor
+ << " by "
+ << RetrievalMethodToString(element_descriptor.retrieval_method)
+ << ".";
+ return false;
+ }
+
+ element.SimulateClick();
+ return true;
+}
+
+} // namespace
+
+class FormAutofillTest : public ChromeRenderViewTest {
+ public:
+ FormAutofillTest() : ChromeRenderViewTest() {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kAutofillRestrictUnownedFieldsToFormlessCheckout);
+ }
+ ~FormAutofillTest() override {}
+
+#if defined(OS_WIN)
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+ // Autofill uses the system font to render suggestion previews. On Windows
+ // an extra step is required to ensure that the system font is configured.
+ blink::WebFontRendering::SetMenuFontMetrics(
+ base::ASCIIToUTF16("Arial").c_str(), 12);
+ }
+#endif
+
+ void ExpectLabels(const char* html,
+ const std::vector<base::string16>& id_attributes,
+ const std::vector<base::string16>& name_attributes,
+ const std::vector<base::string16>& labels,
+ const std::vector<base::string16>& names,
+ const std::vector<base::string16>& values) {
+ ASSERT_EQ(labels.size(), id_attributes.size());
+ ASSERT_EQ(labels.size(), name_attributes.size());
+ ASSERT_EQ(labels.size(), names.size());
+ ASSERT_EQ(labels.size(), values.size());
+
+ std::vector<FormFieldData> fields;
+ for (size_t i = 0; i < labels.size(); ++i) {
+ FormFieldData expected;
+ expected.id_attribute = id_attributes[i];
+ expected.name_attribute = name_attributes[i];
+ expected.label = labels[i];
+ expected.name = names[i];
+ expected.value = values[i];
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ fields.push_back(expected);
+ }
+ ExpectLabelsAndTypes(html, fields);
+ }
+
+ void ExpectLabelsAndTypes(const char* html,
+ const std::vector<FormFieldData>& fields) {
+ LoadHTML(html);
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ const FormData& form = forms[0];
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_EQ(GURL("http://cnn.com"), form.action);
+ ASSERT_EQ(fields.size(), form.fields.size());
+
+ for (size_t i = 0; i < fields.size(); ++i) {
+ SCOPED_TRACE(base::StringPrintf("i: %" PRIuS, i));
+ EXPECT_FORM_FIELD_DATA_EQUALS(fields[i], form.fields[i]);
+ }
+ }
+
+ // Use this validator when the test HTML uses the id attribute instead of
+ // the name attribute to identify the input fields. Otherwise, this is the
+ // same text structure as ExpectJohnSmithLabelsAndNameAttributes().
+ void ExpectJohnSmithLabelsAndIdAttributes(const char* html) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(base::string16());
+ labels.push_back(ASCIIToUTF16("First name:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(base::string16());
+ labels.push_back(ASCIIToUTF16("Last name:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(base::string16());
+ labels.push_back(ASCIIToUTF16("Email:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(html, id_attributes, name_attributes, labels, names, values);
+ }
+
+ // Use this validator when the test HTML uses the name attribute instead of
+ // the id attribute to identify the input fields. Otherwise, this is the same
+ // text structure as ExpectJohnSmithLabelsAndIdAttributes().
+ void ExpectJohnSmithLabelsAndNameAttributes(const char* html) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+ id_attributes.push_back(base::string16());
+ name_attributes.push_back(ASCIIToUTF16("firstname"));
+ labels.push_back(ASCIIToUTF16("First name:"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(base::string16());
+ name_attributes.push_back(ASCIIToUTF16("lastname"));
+ labels.push_back(ASCIIToUTF16("Last name:"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(base::string16());
+ name_attributes.push_back(ASCIIToUTF16("email"));
+ labels.push_back(ASCIIToUTF16("Email:"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+ ExpectLabels(html, id_attributes, name_attributes, labels, names, values);
+ }
+
+ typedef void (*FillFormFunction)(const FormData& form,
+ const WebFormControlElement& element);
+
+ typedef WebString (*GetValueFunction)(WebFormControlElement element);
+
+ // Test FormFillxxx functions.
+ void TestFormFillFunctions(const char* html,
+ bool unowned,
+ const char* url_override,
+ const AutofillFieldCase* field_cases,
+ size_t number_of_field_cases,
+ FillFormFunction fill_form_function,
+ GetValueFunction get_value_function) {
+ if (url_override)
+ LoadHTMLWithUrlOverride(html, url_override);
+ else
+ LoadHTML(html);
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ // Find the form that contains the input element.
+ FormData form_data;
+ FormFieldData field;
+ EXPECT_TRUE(FindFormAndFieldForFormControlElement(input_element, &form_data,
+ &field));
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form_data.name);
+ EXPECT_EQ(GURL("http://abc.com"), form_data.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form_data.fields;
+ ASSERT_EQ(number_of_field_cases, fields.size());
+
+ FormFieldData expected;
+ // Verify field's initial value.
+ for (size_t i = 0; i < number_of_field_cases; ++i) {
+ SCOPED_TRACE(base::StringPrintf("Verify initial value for field %s",
+ field_cases[i].id_attribute));
+ expected.form_control_type = field_cases[i].form_control_type;
+ expected.max_length = expected.form_control_type == "text"
+ ? WebInputElement::DefaultMaxLength()
+ : 0;
+ expected.id_attribute = ASCIIToUTF16(field_cases[i].id_attribute);
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16(field_cases[i].initial_value);
+ if (expected.form_control_type == "text" ||
+ expected.form_control_type == "month") {
+ expected.label = ASCIIToUTF16(field_cases[i].initial_value);
+ } else {
+ expected.label.clear();
+ }
+ expected.autocomplete_attribute = field_cases[i].autocomplete_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[i]);
+ // Fill the form_data for the field.
+ form_data.fields[i].value = ASCIIToUTF16(field_cases[i].autofill_value);
+ // Set the is_autofilled property for the field.
+ form_data.fields[i].is_autofilled = field_cases[i].should_be_autofilled;
+ }
+
+ // Autofill the form using the given fill form function.
+ fill_form_function(form_data, input_element);
+
+ // Validate Autofill or Preview results.
+ for (size_t i = 0; i < number_of_field_cases; ++i) {
+ ValidateFilledField(field_cases[i], get_value_function);
+ }
+ }
+
+ // Validate an Autofilled field.
+ void ValidateFilledField(const AutofillFieldCase& field_case,
+ GetValueFunction get_value_function) {
+ SCOPED_TRACE(base::StringPrintf("Verify autofilled value for field %s",
+ field_case.id_attribute));
+ WebString value;
+ WebFormControlElement element = GetFormControlElementById(
+ WebString::FromASCII(field_case.id_attribute));
+ if ((element.FormControlType() == "select-one") ||
+ (element.FormControlType() == "textarea")) {
+ value = get_value_function(element);
+ } else {
+ ASSERT_TRUE(element.FormControlType() == "text" ||
+ element.FormControlType() == "month");
+ value = get_value_function(element);
+ }
+
+ const WebString expected_value =
+ WebString::FromASCII(field_case.expected_value);
+ if (expected_value.IsEmpty())
+ EXPECT_TRUE(value.IsEmpty());
+ else
+ EXPECT_EQ(expected_value.Utf8(), value.Utf8());
+
+ EXPECT_EQ(field_case.should_be_autofilled, element.IsAutofilled());
+ }
+
+ WebFormControlElement GetFormControlElementById(const WebString& id) {
+ return GetMainFrame()
+ ->GetDocument()
+ .GetElementById(id)
+ .To<WebFormControlElement>();
+ }
+
+ WebInputElement GetInputElementById(const WebString& id) {
+ return GetMainFrame()
+ ->GetDocument()
+ .GetElementById(id)
+ .To<WebInputElement>();
+ }
+
+ void TestFillForm(const char* html, bool unowned, const char* url_override) {
+ static const AutofillFieldCase field_cases[] = {
+ // fields: form_control_type, name, initial_value, autocomplete_attribute,
+ // should_be_autofilled, autofill_value, expected_value
+
+ // Regular empty fields (firstname & lastname) should be autofilled.
+ {"text",
+ "firstname",
+ "",
+ "",
+ true,
+ "filled firstname",
+ "filled firstname"},
+ {"text", "lastname", "", "", true, "filled lastname", "filled lastname"},
+ // hidden fields should not be extracted to form_data.
+ // Non empty fields should not be autofilled.
+ {"text", "notempty", "Hi", "", false, "filled notempty", "Hi"},
+ {"text",
+ "noautocomplete",
+ "",
+ "off",
+ true,
+ "filled noautocomplete",
+ "filled noautocomplete"},
+ // Disabled fields should not be autofilled.
+ {"text", "notenabled", "", "", false, "filled notenabled", ""},
+ // Readonly fields should not be autofilled.
+ {"text", "readonly", "", "", false, "filled readonly", ""},
+ // Fields with "visibility: hidden" should not be autofilled.
+ {"text", "invisible", "", "", false, "filled invisible", ""},
+ // Fields with "display:none" should not be autofilled.
+ {"text", "displaynone", "", "", false, "filled displaynone", ""},
+ // Regular <input type="month"> should be autofilled.
+ {"month", "month", "", "", true, "2017-11", "2017-11"},
+ // Non-empty <input type="month"> should not be autofilled.
+ {"month", "month-nonempty", "2011-12", "", false, "2017-11", "2011-12"},
+ // Regular select fields should be autofilled.
+ {"select-one", "select", "", "", true, "TX", "TX"},
+ // Select fields should be autofilled even if they already have a
+ // non-empty value.
+ {"select-one", "select-nonempty", "CA", "", true, "TX", "TX"},
+ // Select fields should not be autofilled if no new value is passed from
+ // autofill profile. The existing value should not be overriden.
+ {"select-one", "select-unchanged", "CA", "", false, "CA", "CA"},
+ // Select fields that are not focusable should always be filled.
+ {"select-one", "select-displaynone", "CA", "", true, "CA", "CA"},
+ // Regular textarea elements should be autofilled.
+ {"textarea",
+ "textarea",
+ "",
+ "",
+ true,
+ "some multi-\nline value",
+ "some multi-\nline value"},
+ // Non-empty textarea elements should not be autofilled.
+ {"textarea",
+ "textarea-nonempty",
+ "Go\naway!",
+ "",
+ false,
+ "some multi-\nline value",
+ "Go\naway!"},
+ };
+ TestFormFillFunctions(html, unowned, url_override, field_cases,
+ base::size(field_cases), FillForm, &GetValueWrapper);
+ // Verify preview selection.
+ WebInputElement firstname = GetInputElementById("firstname");
+ EXPECT_EQ(16, firstname.SelectionStart());
+ EXPECT_EQ(16, firstname.SelectionEnd());
+ }
+
+ void TestPreviewForm(const char* html, bool unowned,
+ const char* url_override) {
+ static const AutofillFieldCase field_cases[] = {
+ // Normal empty fields should be previewed.
+ {"text",
+ "firstname",
+ "",
+ "",
+ true,
+ "suggested firstname",
+ "suggested firstname"},
+ {"text",
+ "lastname",
+ "",
+ "",
+ true,
+ "suggested lastname",
+ "suggested lastname"},
+ // Hidden fields should not be extracted to form_data.
+ // Non empty fields should not be previewed.
+ {"text", "notempty", "Hi", "", false, "suggested notempty", ""},
+ {"text",
+ "noautocomplete",
+ "",
+ "off",
+ true,
+ "filled noautocomplete",
+ "filled noautocomplete"},
+ // Disabled fields should not be previewed.
+ {"text", "notenabled", "", "", false, "suggested notenabled", ""},
+ // Readonly fields should not be previewed.
+ {"text", "readonly", "", "", false, "suggested readonly", ""},
+ // Fields with "visibility: hidden" should not be previewed.
+ {"text", "invisible", "", "", false, "suggested invisible", ""},
+ // Fields with "display:none" should not previewed.
+ {"text", "displaynone", "", "", false, "suggested displaynone", ""},
+ // Regular <input type="month"> should be previewed.
+ {"month", "month", "", "", true, "2017-11", "2017-11"},
+ // Non-empty <input type="month"> should not be previewed.
+ {"month", "month-nonempty", "2011-12", "", false, "2017-11", ""},
+ // Regular select fields should be previewed.
+ {"select-one", "select", "", "", true, "TX", "TX"},
+ // Select fields should be previewed even if they already have a
+ // non-empty value.
+ {"select-one", "select-nonempty", "CA", "", true, "TX", "TX"},
+ // Select fields should not be previewed if no suggestion is passed from
+ // autofill profile.
+ {"select-one", "select-unchanged", "CA", "", false, "", ""},
+ // Select fields that are not focusable should always be filled.
+ {"select-one", "select-displaynone", "CA", "", true, "CA", "CA"},
+ // Normal textarea elements should be previewed.
+ {"textarea",
+ "textarea",
+ "",
+ "",
+ true,
+ "suggested multi-\nline value",
+ "suggested multi-\nline value"},
+ // Nonempty textarea elements should not be previewed.
+ {"textarea",
+ "textarea-nonempty",
+ "Go\naway!",
+ "",
+ false,
+ "suggested multi-\nline value",
+ ""},
+ };
+ TestFormFillFunctions(html, unowned, url_override, field_cases,
+ base::size(field_cases), &PreviewForm,
+ &GetSuggestedValueWrapper);
+
+ // Verify preview selection.
+ WebInputElement firstname = GetInputElementById("firstname");
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ EXPECT_EQ(0, firstname.SelectionStart());
+ EXPECT_EQ(0, firstname.SelectionEnd());
+ }
+
+ void TestUnmatchedUnownedForm(const char* html, const char* url_override) {
+ if (url_override)
+ LoadHTMLWithUrlOverride(html, url_override);
+ else
+ LoadHTML(html);
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(0U, forms.size());
+ }
+
+ void TestFindFormForInputElement(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ // Find the form and verify it's the correct form.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(4U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("John");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, field);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Smith");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.label = ASCIIToUTF16("john@example.com");
+ expected.autocomplete_attribute = "off";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+ expected.autocomplete_attribute.clear();
+
+ expected.id_attribute = ASCIIToUTF16("phone");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("1.800.555.1234");
+ expected.label = ASCIIToUTF16("1.800.555.1234");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+ }
+
+ void TestFindFormForTextAreaElement(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the textarea element we want to find.
+ WebElement element =
+ web_frame->GetDocument().GetElementById("street-address");
+ WebFormControlElement textarea_element =
+ element.To<WebFormControlElement>();
+
+ // Find the form and verify it's the correct form.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(textarea_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(4U, fields.size());
+
+ FormFieldData expected;
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("John");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Smith");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.label = ASCIIToUTF16("john@example.com");
+ expected.autocomplete_attribute = "off";
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+ expected.autocomplete_attribute.clear();
+
+ expected.id_attribute = ASCIIToUTF16("street-address");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("123 Fantasy Ln.\nApt. 42");
+ expected.label.clear();
+ expected.form_control_type = "textarea";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, field);
+ }
+
+ void TestFillFormMaxLength(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.max_length = 5;
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.max_length = 7;
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.max_length = 9;
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // Fill the form.
+ form.fields[0].value = ASCIIToUTF16("Brother");
+ form.fields[1].value = ASCIIToUTF16("Jonathan");
+ form.fields[2].value = ASCIIToUTF16("brotherj@example.com");
+ form.fields[0].is_autofilled = true;
+ form.fields[1].is_autofilled = true;
+ form.fields[2].is_autofilled = true;
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+ }
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ expected.form_control_type = "text";
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Broth");
+ expected.max_length = 5;
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Jonatha");
+ expected.max_length = 7;
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("brotherj@");
+ expected.max_length = 9;
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+ }
+
+ void TestFillFormNegativeMaxLength(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // Fill the form.
+ form.fields[0].value = ASCIIToUTF16("Brother");
+ form.fields[1].value = ASCIIToUTF16("Jonathan");
+ form.fields[2].value = ASCIIToUTF16("brotherj@example.com");
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+ }
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Brother");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Jonathan");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("brotherj@example.com");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+ }
+
+ void TestFillFormEmptyName(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // Fill the form.
+ form.fields[0].value = ASCIIToUTF16("Wyatt");
+ form.fields[1].value = ASCIIToUTF16("Earp");
+ form.fields[2].value = ASCIIToUTF16("wyatt@example.com");
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+ }
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Wyatt");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Earp");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("wyatt@example.com");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+ }
+
+ void TestFillFormEmptyFormNames(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ const size_t expected_size = unowned ? 1 : 2;
+ ASSERT_EQ(expected_size, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("apple");
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_TRUE(form.name.empty());
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ const size_t unowned_offset = unowned ? 3 : 0;
+ ASSERT_EQ(unowned_offset + 3, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("apple");
+ expected.name = expected.id_attribute;
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[unowned_offset]);
+
+ expected.id_attribute = ASCIIToUTF16("banana");
+ expected.name = expected.id_attribute;
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[unowned_offset + 1]);
+
+ expected.id_attribute = ASCIIToUTF16("cantelope");
+ expected.name = expected.id_attribute;
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[unowned_offset + 2]);
+
+ // Fill the form.
+ form.fields[unowned_offset + 0].value = ASCIIToUTF16("Red");
+ form.fields[unowned_offset + 1].value = ASCIIToUTF16("Yellow");
+ form.fields[unowned_offset + 2].value = ASCIIToUTF16("Also Yellow");
+ form.fields[unowned_offset + 0].is_autofilled = true;
+ form.fields[unowned_offset + 1].is_autofilled = true;
+ form.fields[unowned_offset + 2].is_autofilled = true;
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ if (!unowned) {
+ EXPECT_TRUE(form2.name.empty());
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+ }
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(unowned_offset + 3, fields2.size());
+
+ expected.id_attribute = ASCIIToUTF16("apple");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Red");
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[unowned_offset + 0]);
+
+ expected.id_attribute = ASCIIToUTF16("banana");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Yellow");
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[unowned_offset + 1]);
+
+ expected.id_attribute = ASCIIToUTF16("cantelope");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Also Yellow");
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[unowned_offset + 2]);
+ }
+
+ void TestFillFormNonEmptyField(const char* html,
+ bool unowned,
+ const char* initial_lastname,
+ const char* initial_email,
+ const char* placeholder_firstname,
+ const char* placeholder_lastname,
+ const char* placeholder_email) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ // Simulate typing by modifying the field value.
+ input_element.SetValue(WebString::FromASCII("Wy"));
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Wy");
+ if (placeholder_firstname) {
+ expected.label = ASCIIToUTF16(placeholder_firstname);
+ expected.placeholder = ASCIIToUTF16(placeholder_firstname);
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ if (initial_lastname) {
+ expected.label = ASCIIToUTF16(initial_lastname);
+ expected.value = ASCIIToUTF16(initial_lastname);
+ } else if (placeholder_lastname) {
+ expected.label = ASCIIToUTF16(placeholder_lastname);
+ expected.placeholder = ASCIIToUTF16(placeholder_lastname);
+ expected.value = ASCIIToUTF16(placeholder_lastname);
+ } else {
+ expected.label.clear();
+ expected.value.clear();
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ if (initial_email) {
+ expected.label = ASCIIToUTF16(initial_email);
+ expected.value = ASCIIToUTF16(initial_email);
+ } else if (placeholder_email) {
+ expected.label = ASCIIToUTF16(placeholder_email);
+ expected.placeholder = ASCIIToUTF16(placeholder_email);
+ expected.value = ASCIIToUTF16(placeholder_email);
+ } else {
+ expected.label.clear();
+ expected.value.clear();
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // Preview the form and verify that the cursor position has been updated.
+ form.fields[0].value = ASCIIToUTF16("Wyatt");
+ form.fields[1].value = ASCIIToUTF16("Earp");
+ form.fields[2].value = ASCIIToUTF16("wyatt@example.com");
+ form.fields[0].is_autofilled = true;
+ form.fields[1].is_autofilled = true;
+ form.fields[2].is_autofilled = true;
+ PreviewForm(form, input_element);
+ // The selection should be set after the second character.
+ EXPECT_EQ(2, input_element.SelectionStart());
+ EXPECT_EQ(2, input_element.SelectionEnd());
+
+ // Fill the form.
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+ }
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Wyatt");
+ if (placeholder_firstname) {
+ expected.label = ASCIIToUTF16(placeholder_firstname);
+ expected.placeholder = ASCIIToUTF16(placeholder_firstname);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Earp");
+ if (placeholder_lastname) {
+ expected.label = ASCIIToUTF16(placeholder_lastname);
+ expected.placeholder = ASCIIToUTF16(placeholder_lastname);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("wyatt@example.com");
+ if (placeholder_email) {
+ expected.label = ASCIIToUTF16(placeholder_email);
+ expected.placeholder = ASCIIToUTF16(placeholder_email);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(5, input_element.SelectionStart());
+ EXPECT_EQ(5, input_element.SelectionEnd());
+ }
+
+ void TestFillFormAndModifyValues(const char* html,
+ const char* placeholder_firstname,
+ const char* placeholder_lastname,
+ const char* placeholder_phone,
+ const char* placeholder_creditcard,
+ const char* placeholder_city,
+ const char* placeholder_state) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+ WebFormElement form_element = input_element.Form();
+ std::vector<WebFormControlElement> control_elements =
+ ExtractAutofillableElementsInForm(form_element);
+
+ ASSERT_EQ(6U, control_elements.size());
+ // We now modify the values.
+ // This will be ignored, the string will be sanitized into an empty string.
+ control_elements[0].SetValue(WebString::FromUTF16(
+ base::char16(base::i18n::kLeftToRightMark) + ASCIIToUTF16(" ")));
+
+ // This will be considered as a value entered by the user.
+ control_elements[1].SetValue(WebString::FromUTF16(ASCIIToUTF16("Earp")));
+ control_elements[1].SetUserHasEditedTheFieldForTest();
+
+ // This will be ignored, the string will be sanitized into an empty string.
+ control_elements[2].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("(___)-___-____")));
+
+ // This will be ignored, the string will be sanitized into an empty string.
+ control_elements[3].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("____-____-____-____")));
+
+ // This will be ignored, because it's injected by the website and not the
+ // user.
+ control_elements[4].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("Enter your city..")));
+
+ control_elements[5].SetValue(WebString::FromUTF16(ASCIIToUTF16("AK")));
+ control_elements[5].SetUserHasEditedTheFieldForTest();
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(6U, fields.size());
+
+ // Preview the form and verify that the cursor position has been updated.
+ form.fields[0].value = ASCIIToUTF16("Wyatt");
+ form.fields[1].value = ASCIIToUTF16("Earpagus");
+ form.fields[2].value = ASCIIToUTF16("888-123-4567");
+ form.fields[3].value = ASCIIToUTF16("1111-2222-3333-4444");
+ form.fields[4].value = ASCIIToUTF16("Montreal");
+ form.fields[5].value = ASCIIToUTF16("AA");
+ form.fields[0].is_autofilled = true;
+ form.fields[1].is_autofilled = true;
+ form.fields[2].is_autofilled = true;
+ form.fields[3].is_autofilled = true;
+ form.fields[4].is_autofilled = true;
+ form.fields[5].is_autofilled = true;
+ PreviewForm(form, input_element);
+ // The selection should be set after the fifth character.
+ EXPECT_EQ(5, input_element.SelectionStart());
+ EXPECT_EQ(5, input_element.SelectionEnd());
+
+ // Fill the form.
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(6U, fields2.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Wyatt");
+ if (placeholder_firstname) {
+ expected.label = ASCIIToUTF16(placeholder_firstname);
+ expected.placeholder = ASCIIToUTF16(placeholder_firstname);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ // The last name field is not filled, because there is a value in it.
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Earp");
+ if (placeholder_lastname) {
+ expected.label = ASCIIToUTF16(placeholder_lastname);
+ expected.placeholder = ASCIIToUTF16(placeholder_lastname);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("phone");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("888-123-4567");
+ if (placeholder_phone) {
+ expected.label = ASCIIToUTF16(placeholder_phone);
+ expected.placeholder = ASCIIToUTF16(placeholder_phone);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+
+ expected.id_attribute = ASCIIToUTF16("cc");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("1111-2222-3333-4444");
+ if (placeholder_creditcard) {
+ expected.label = ASCIIToUTF16(placeholder_creditcard);
+ expected.placeholder = ASCIIToUTF16(placeholder_creditcard);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[3]);
+
+ expected.id_attribute = ASCIIToUTF16("city");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Montreal");
+ if (placeholder_city) {
+ expected.label = ASCIIToUTF16(placeholder_city);
+ expected.placeholder = ASCIIToUTF16(placeholder_city);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[4]);
+
+ expected.form_control_type = "select-one";
+ expected.id_attribute = ASCIIToUTF16("state");
+ expected.name_attribute = ASCIIToUTF16("state");
+ expected.name = expected.name_attribute;
+ expected.value = ASCIIToUTF16("AA");
+ if (placeholder_state) {
+ expected.label = ASCIIToUTF16(placeholder_state);
+ expected.placeholder = ASCIIToUTF16(placeholder_state);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[5]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(5, input_element.SelectionStart());
+ EXPECT_EQ(5, input_element.SelectionEnd());
+ }
+
+ void TestFillFormWithPlaceholderValues(const char* html,
+ const char* placeholder_firstname,
+ const char* placeholder_lastname,
+ const char* placeholder_email) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("firstname");
+ WebFormElement form_element = input_element.Form();
+ std::vector<WebFormControlElement> control_elements =
+ ExtractAutofillableElementsInForm(form_element);
+
+ ASSERT_EQ(3U, control_elements.size());
+ // We now modify the values.
+ // These will be ignored, because it's (case insensitively) equal to the
+ // placeholder.
+ control_elements[0].SetValue(
+ WebString::FromUTF16(base::char16(base::i18n::kLeftToRightMark) +
+ ASCIIToUTF16("first name")));
+ control_elements[1].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("LAST NAME")));
+ // This will be considered.
+ control_elements[2].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("john@smith.com")));
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ // Preview the form and verify that the cursor position has been updated.
+ form.fields[0].value = ASCIIToUTF16("Wyatt");
+ form.fields[1].value = ASCIIToUTF16("Earpagus");
+ form.fields[2].value = ASCIIToUTF16("susan@smith.com");
+ form.fields[0].is_autofilled = true;
+ form.fields[1].is_autofilled = true;
+ form.fields[2].is_autofilled = false;
+ PreviewForm(form, input_element);
+ // The selection should be set after the fifth character.
+ EXPECT_EQ(5, input_element.SelectionStart());
+ EXPECT_EQ(5, input_element.SelectionEnd());
+
+ // Fill the form.
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Wyatt");
+ if (placeholder_firstname) {
+ expected.label = ASCIIToUTF16(placeholder_firstname);
+ expected.placeholder = ASCIIToUTF16(placeholder_firstname);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Earpagus");
+ if (placeholder_lastname) {
+ expected.label = ASCIIToUTF16(placeholder_lastname);
+ expected.placeholder = ASCIIToUTF16(placeholder_lastname);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ // The email field is not filled, because there is a value in it.
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@smith.com");
+ if (placeholder_email) {
+ expected.label = ASCIIToUTF16(placeholder_email);
+ expected.placeholder = ASCIIToUTF16(placeholder_email);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(5, input_element.SelectionStart());
+ EXPECT_EQ(5, input_element.SelectionEnd());
+ }
+
+ void TestFillFormAndModifyInitiatingValue(const char* html,
+ const char* placeholder_creditcard,
+ const char* placeholder_expiration,
+ const char* placeholder_name) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("cc");
+ WebFormElement form_element = input_element.Form();
+ std::vector<WebFormControlElement> control_elements =
+ ExtractAutofillableElementsInForm(form_element);
+
+ ASSERT_EQ(3U, control_elements.size());
+ // We now modify the values.
+ // This will be ignored.
+ control_elements[0].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("____-____-____-____")));
+ // This will be ignored.
+ control_elements[1].SetValue(WebString::FromUTF16(ASCIIToUTF16("____/__")));
+ control_elements[2].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("John Smith")));
+ control_elements[2].SetUserHasEditedTheFieldForTest();
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ // Preview the form and verify that the cursor position has been updated.
+ form.fields[0].value = ASCIIToUTF16("1111-2222-3333-4444");
+ form.fields[1].value = ASCIIToUTF16("03/2030");
+ form.fields[2].value = ASCIIToUTF16("Susan Smith");
+ form.fields[0].is_autofilled = true;
+ form.fields[1].is_autofilled = true;
+ form.fields[2].is_autofilled = true;
+ PreviewForm(form, input_element);
+ // The selection should be set after the 19th character.
+ EXPECT_EQ(19, input_element.SelectionStart());
+ EXPECT_EQ(19, input_element.SelectionEnd());
+
+ // Fill the form.
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("cc");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("1111-2222-3333-4444");
+ if (placeholder_creditcard) {
+ expected.label = ASCIIToUTF16(placeholder_creditcard);
+ expected.placeholder = ASCIIToUTF16(placeholder_creditcard);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("expiration_date");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("03/2030");
+ if (placeholder_expiration) {
+ expected.label = ASCIIToUTF16(placeholder_expiration);
+ expected.placeholder = ASCIIToUTF16(placeholder_expiration);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("name");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John Smith");
+ if (placeholder_name) {
+ expected.label = ASCIIToUTF16(placeholder_name);
+ expected.placeholder = ASCIIToUTF16(placeholder_name);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(19, input_element.SelectionStart());
+ EXPECT_EQ(19, input_element.SelectionEnd());
+ }
+
+ void TestFillFormJSModifiesUserInputValue(const char* html,
+ const char* placeholder_creditcard,
+ const char* placeholder_expiration,
+ const char* placeholder_name) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Get the input element we want to find.
+ WebInputElement input_element = GetInputElementById("cc");
+ WebFormElement form_element = input_element.Form();
+ std::vector<WebFormControlElement> control_elements =
+ ExtractAutofillableElementsInForm(form_element);
+
+ ASSERT_EQ(3U, control_elements.size());
+ // We now modify the values.
+ // This will be ignored.
+ control_elements[0].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("____-____-____-____")));
+ // This will be ignored.
+ control_elements[1].SetValue(WebString::FromUTF16(ASCIIToUTF16("____/__")));
+ control_elements[2].SetValue(
+ WebString::FromUTF16(ASCIIToUTF16("john smith")));
+ control_elements[2].SetUserHasEditedTheFieldForTest();
+
+ // Sometimes the JS modifies the value entered by the user.
+ ExecuteJavaScriptForTests(
+ "document.getElementById('name').value = 'John Smith';");
+
+ // Find the form that contains the input element.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ // Preview the form and verify that the cursor position has been updated.
+ form.fields[0].value = ASCIIToUTF16("1111-2222-3333-4444");
+ form.fields[1].value = ASCIIToUTF16("03/2030");
+ form.fields[2].value = ASCIIToUTF16("Susan Smith");
+ form.fields[0].is_autofilled = true;
+ form.fields[1].is_autofilled = true;
+ form.fields[2].is_autofilled = true;
+ PreviewForm(form, input_element);
+ // The selection should be set after the 19th character.
+ EXPECT_EQ(19, input_element.SelectionStart());
+ EXPECT_EQ(19, input_element.SelectionEnd());
+
+ // Fill the form.
+ FillForm(form, input_element);
+
+ // Find the newly-filled form that contains the input element.
+ FormData form2;
+ FormFieldData field2;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(input_element, &form2, &field2));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form2.url);
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name);
+ EXPECT_EQ(GURL("http://abc.com"), form2.action);
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("cc");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("1111-2222-3333-4444");
+ if (placeholder_creditcard) {
+ expected.label = ASCIIToUTF16(placeholder_creditcard);
+ expected.placeholder = ASCIIToUTF16(placeholder_creditcard);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("expiration_date");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("03/2030");
+ if (placeholder_expiration) {
+ expected.label = ASCIIToUTF16(placeholder_expiration);
+ expected.placeholder = ASCIIToUTF16(placeholder_expiration);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("name");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John Smith");
+ if (placeholder_name) {
+ expected.label = ASCIIToUTF16(placeholder_name);
+ expected.placeholder = ASCIIToUTF16(placeholder_name);
+ } else {
+ expected.label.clear();
+ expected.placeholder.clear();
+ }
+ expected.is_autofilled = false;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(19, input_element.SelectionStart());
+ EXPECT_EQ(19, input_element.SelectionEnd());
+ }
+
+ void TestClearSectionWithNode(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the auto-filled attribute.
+ WebInputElement firstname = GetInputElementById("firstname");
+ firstname.SetAutofillState(WebAutofillState::kAutofilled);
+ WebInputElement lastname = GetInputElementById("lastname");
+ lastname.SetAutofillState(WebAutofillState::kAutofilled);
+ WebInputElement month = GetInputElementById("month");
+ month.SetAutofillState(WebAutofillState::kAutofilled);
+ WebFormControlElement textarea = GetFormControlElementById("textarea");
+ textarea.SetAutofillState(WebAutofillState::kAutofilled);
+
+ // Set the value of the disabled text input element.
+ WebInputElement notenabled = GetInputElementById("notenabled");
+ notenabled.SetValue(WebString::FromUTF8("no clear"));
+
+ // Clear the form.
+ EXPECT_TRUE(form_cache.ClearSectionWithElement(firstname));
+
+ // Verify that the auto-filled attribute has been turned off.
+ EXPECT_FALSE(firstname.IsAutofilled());
+
+ // Verify the form is cleared.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(firstname, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_FALSE(form.url.is_empty());
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(9U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("noAC");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("one");
+ expected.label = ASCIIToUTF16("one");
+ expected.autocomplete_attribute = "off";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+ expected.autocomplete_attribute.clear();
+
+ expected.id_attribute = ASCIIToUTF16("notenabled");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("no clear");
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+
+ expected.form_control_type = "month";
+ expected.max_length = 0;
+ expected.id_attribute = ASCIIToUTF16("month");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[4]);
+
+ expected.id_attribute = ASCIIToUTF16("month-disabled");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("2012-11");
+ expected.label = ASCIIToUTF16("2012-11");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[5]);
+
+ expected.form_control_type = "textarea";
+ expected.id_attribute = ASCIIToUTF16("textarea");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[6]);
+
+ expected.id_attribute = ASCIIToUTF16("textarea-disabled");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16(" Banana! ");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[7]);
+
+ expected.id_attribute = ASCIIToUTF16("textarea-noAC");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Carrot?");
+ expected.autocomplete_attribute = "off";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[8]);
+ expected.autocomplete_attribute.clear();
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, firstname.SelectionStart());
+ EXPECT_EQ(0, firstname.SelectionEnd());
+ }
+
+ void TestClearTwoSections(const char* html, bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the autofilled attribute and specify the section attribute.
+ WebInputElement firstname_shipping =
+ GetInputElementById("firstname-shipping");
+ firstname_shipping.SetAutofillValue("John");
+ firstname_shipping.SetAutofillState(WebAutofillState::kAutofilled);
+ firstname_shipping.SetAutofillSection("shipping");
+
+ WebInputElement lastname_shipping =
+ GetInputElementById("lastname-shipping");
+ lastname_shipping.SetAutofillValue("Smith");
+ lastname_shipping.SetAutofillState(WebAutofillState::kAutofilled);
+ lastname_shipping.SetAutofillSection("shipping");
+
+ WebInputElement city_shipping = GetInputElementById("city-shipping");
+ city_shipping.SetAutofillValue("Montreal");
+ city_shipping.SetAutofillState(WebAutofillState::kAutofilled);
+ city_shipping.SetAutofillSection("shipping");
+
+ WebInputElement firstname_billing =
+ GetInputElementById("firstname-billing");
+ firstname_billing.SetAutofillValue("John");
+ firstname_billing.SetAutofillState(WebAutofillState::kAutofilled);
+ firstname_billing.SetAutofillSection("billing");
+
+ WebInputElement lastname_billing = GetInputElementById("lastname-billing");
+ lastname_billing.SetAutofillValue("Smith");
+ lastname_billing.SetAutofillState(WebAutofillState::kAutofilled);
+ lastname_billing.SetAutofillSection("billing");
+
+ WebInputElement city_billing = GetInputElementById("city-billing");
+ city_billing.SetAutofillValue("Paris");
+ city_billing.SetAutofillState(WebAutofillState::kAutofilled);
+ city_billing.SetAutofillSection("billing");
+
+ // Clear the first (shipping) section.
+ EXPECT_TRUE(form_cache.ClearSectionWithElement(firstname_shipping));
+
+ // Verify that the autofilled attribute is false only for the shipping
+ // section.
+ EXPECT_FALSE(firstname_shipping.IsAutofilled());
+ EXPECT_FALSE(lastname_shipping.IsAutofilled());
+ EXPECT_FALSE(city_shipping.IsAutofilled());
+ EXPECT_TRUE(firstname_billing.IsAutofilled());
+ EXPECT_TRUE(lastname_billing.IsAutofilled());
+ EXPECT_TRUE(city_billing.IsAutofilled());
+
+ // Verify that the shipping section is cleared, but not the billing one.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(FindFormAndFieldForFormControlElement(firstname_shipping, &form,
+ &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_FALSE(form.url.is_empty());
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(6U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ // shipping section
+ expected.is_autofilled = false;
+ expected.id_attribute = ASCIIToUTF16("firstname-shipping");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname-shipping");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("city-shipping");
+ expected.name = expected.id_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // billing section
+ expected.is_autofilled = true;
+ expected.id_attribute = ASCIIToUTF16("firstname-billing");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname-billing");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[4]);
+
+ expected.id_attribute = ASCIIToUTF16("city-billing");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Paris");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[5]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, firstname_shipping.SelectionStart());
+ EXPECT_EQ(0, firstname_shipping.SelectionEnd());
+ }
+
+ void TestClearSectionWithNodeContainingSelectOne(const char* html,
+ bool unowned) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the auto-filled attribute.
+ WebInputElement firstname = GetInputElementById("firstname");
+ firstname.SetAutofillState(WebAutofillState::kAutofilled);
+ WebInputElement lastname = GetInputElementById("lastname");
+ lastname.SetAutofillState(WebAutofillState::kAutofilled);
+
+ // Set the value and auto-filled attribute of the state element.
+ WebSelectElement state =
+ web_frame->GetDocument().GetElementById("state").To<WebSelectElement>();
+ state.SetValue(WebString::FromUTF8("AK"));
+ state.SetAutofillState(WebAutofillState::kAutofilled);
+
+ // Clear the form.
+ EXPECT_TRUE(form_cache.ClearSectionWithElement(firstname));
+
+ // Verify that the auto-filled attribute has been turned off.
+ EXPECT_FALSE(firstname.IsAutofilled());
+
+ // Verify the form is cleared.
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(
+ FindFormAndFieldForFormControlElement(firstname, &form, &field));
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()),
+ form.url);
+ EXPECT_FALSE(form.url.is_empty());
+ if (!unowned) {
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GURL("http://abc.com"), form.action);
+ }
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("state");
+ expected.name_attribute = ASCIIToUTF16("state");
+ expected.name = expected.name_attribute;
+ expected.value = ASCIIToUTF16("?");
+ expected.form_control_type = "select-one";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, firstname.SelectionStart());
+ EXPECT_EQ(0, firstname.SelectionEnd());
+ }
+
+ void TestClearPreviewedFormWithElement(const char* html) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the auto-filled attribute.
+ WebInputElement firstname = GetInputElementById("firstname");
+ firstname.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement lastname = GetInputElementById("lastname");
+ lastname.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement email = GetInputElementById("email");
+ email.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement email2 = GetInputElementById("email2");
+ email2.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement phone = GetInputElementById("phone");
+ phone.SetAutofillState(WebAutofillState::kPreviewed);
+
+ // Set the suggested values on two of the elements.
+ lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
+ email.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+ email2.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+ phone.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
+
+ // Clear the previewed fields.
+ EXPECT_TRUE(
+ ClearPreviewedFormWithElement(lastname, WebAutofillState::kNotFilled));
+
+ // Fields with empty suggestions suggestions are not modified.
+ EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.Value().Utf16());
+ EXPECT_TRUE(firstname.SuggestedValue().IsEmpty());
+ EXPECT_TRUE(firstname.IsAutofilled());
+
+ // Verify the previewed fields are cleared.
+ EXPECT_TRUE(lastname.Value().IsEmpty());
+ EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(lastname.IsAutofilled());
+ EXPECT_TRUE(email.Value().IsEmpty());
+ EXPECT_TRUE(email.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email.IsAutofilled());
+ EXPECT_TRUE(email2.Value().IsEmpty());
+ EXPECT_TRUE(email2.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email2.IsAutofilled());
+ EXPECT_TRUE(phone.Value().IsEmpty());
+ EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(phone.IsAutofilled());
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, lastname.SelectionStart());
+ EXPECT_EQ(0, lastname.SelectionEnd());
+ }
+
+ void TestClearPreviewedFormWithNonEmptyInitiatingNode(const char* html) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the auto-filled attribute.
+ WebInputElement firstname = GetInputElementById("firstname");
+ firstname.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement lastname = GetInputElementById("lastname");
+ lastname.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement email = GetInputElementById("email");
+ email.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement email2 = GetInputElementById("email2");
+ email2.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement phone = GetInputElementById("phone");
+ phone.SetAutofillState(WebAutofillState::kPreviewed);
+
+ // Set the suggested values on all of the elements.
+ firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
+ lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
+ email.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+ email2.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+ phone.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
+
+ // Clear the previewed fields.
+ EXPECT_TRUE(
+ ClearPreviewedFormWithElement(firstname, WebAutofillState::kNotFilled));
+
+ // Fields with non-empty values are restored.
+ EXPECT_EQ(ASCIIToUTF16("W"), firstname.Value().Utf16());
+ EXPECT_TRUE(firstname.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(firstname.IsAutofilled());
+ EXPECT_EQ(1, firstname.SelectionStart());
+ EXPECT_EQ(1, firstname.SelectionEnd());
+
+ // Verify the previewed fields are cleared.
+ EXPECT_TRUE(lastname.Value().IsEmpty());
+ EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(lastname.IsAutofilled());
+ EXPECT_TRUE(email.Value().IsEmpty());
+ EXPECT_TRUE(email.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email.IsAutofilled());
+ EXPECT_TRUE(email2.Value().IsEmpty());
+ EXPECT_TRUE(email2.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email2.IsAutofilled());
+ EXPECT_TRUE(phone.Value().IsEmpty());
+ EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(phone.IsAutofilled());
+ }
+
+ void TestClearPreviewedFormWithAutofilledInitiatingNode(const char* html) {
+ LoadHTML(html);
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the auto-filled attribute.
+ WebInputElement firstname = GetInputElementById("firstname");
+ firstname.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement lastname = GetInputElementById("lastname");
+ lastname.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement email = GetInputElementById("email");
+ email.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement email2 = GetInputElementById("email2");
+ email2.SetAutofillState(WebAutofillState::kPreviewed);
+ WebInputElement phone = GetInputElementById("phone");
+ phone.SetAutofillState(WebAutofillState::kPreviewed);
+
+ // Set the suggested values on all of the elements.
+ firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
+ lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
+ email.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+ email2.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
+ phone.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
+
+ // Clear the previewed fields.
+ EXPECT_TRUE(ClearPreviewedFormWithElement(firstname,
+ WebAutofillState::kAutofilled));
+
+ // Fields with non-empty values are restored.
+ EXPECT_EQ(ASCIIToUTF16("W"), firstname.Value().Utf16());
+ EXPECT_TRUE(firstname.SuggestedValue().IsEmpty());
+ EXPECT_TRUE(firstname.IsAutofilled());
+ EXPECT_EQ(1, firstname.SelectionStart());
+ EXPECT_EQ(1, firstname.SelectionEnd());
+
+ // Verify the previewed fields are cleared.
+ EXPECT_TRUE(lastname.Value().IsEmpty());
+ EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(lastname.IsAutofilled());
+ EXPECT_TRUE(email.Value().IsEmpty());
+ EXPECT_TRUE(email.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email.IsAutofilled());
+ EXPECT_TRUE(email2.Value().IsEmpty());
+ EXPECT_TRUE(email2.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email2.IsAutofilled());
+ EXPECT_TRUE(phone.Value().IsEmpty());
+ EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(phone.IsAutofilled());
+ }
+
+ void TestClearOnlyAutofilledFields(const char* html) {
+ LoadHTML(html);
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the autofilled attribute.
+ WebInputElement firstname = GetInputElementById("firstname");
+ firstname.SetAutofillState(WebAutofillState::kNotFilled);
+ WebInputElement lastname = GetInputElementById("lastname");
+ lastname.SetAutofillState(WebAutofillState::kAutofilled);
+ WebInputElement email = GetInputElementById("email");
+ email.SetAutofillState(WebAutofillState::kAutofilled);
+ WebInputElement phone = GetInputElementById("phone");
+ phone.SetAutofillState(WebAutofillState::kAutofilled);
+
+ // Clear the fields.
+ EXPECT_TRUE(form_cache.ClearSectionWithElement(firstname));
+
+ // Verify only autofilled fields are cleared.
+ EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.Value().Utf16());
+ EXPECT_TRUE(firstname.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(firstname.IsAutofilled());
+ EXPECT_TRUE(lastname.Value().IsEmpty());
+ EXPECT_TRUE(lastname.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(lastname.IsAutofilled());
+ EXPECT_TRUE(email.Value().IsEmpty());
+ EXPECT_TRUE(email.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(email.IsAutofilled());
+ EXPECT_TRUE(phone.Value().IsEmpty());
+ EXPECT_TRUE(phone.SuggestedValue().IsEmpty());
+ EXPECT_FALSE(phone.IsAutofilled());
+ }
+
+ static WebString GetValueWrapper(WebFormControlElement element) {
+ if (element.FormControlType() == "textarea")
+ return element.To<WebFormControlElement>().Value();
+
+ if (element.FormControlType() == "select-one")
+ return element.To<WebSelectElement>().Value();
+
+ return element.To<WebInputElement>().Value();
+ }
+
+ static WebString GetSuggestedValueWrapper(WebFormControlElement element) {
+ if (element.FormControlType() == "textarea")
+ return element.To<WebFormControlElement>().SuggestedValue();
+
+ if (element.FormControlType() == "select-one")
+ return element.To<WebSelectElement>().SuggestedValue();
+
+ return element.To<WebInputElement>().SuggestedValue();
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+ DISALLOW_COPY_AND_ASSIGN(FormAutofillTest);
+};
+
+// We should be able to extract a normal text field.
+TEST_F(FormAutofillTest, WebFormControlElementToFormField) {
+ LoadHTML("<INPUT type='text' id='element' value='value'/>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result1;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_NONE, &result1);
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.value.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result1);
+
+ FormFieldData result2;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result2);
+
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("value");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result2);
+}
+
+// We should be able to extract a text field with autocomplete="off".
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldAutocompleteOff) {
+ LoadHTML("<INPUT type='text' id='element' value='value'"
+ " autocomplete='off'/>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("value");
+ expected.form_control_type = "text";
+ expected.autocomplete_attribute = "off";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+}
+
+// We should be able to extract a text field with maxlength specified.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldMaxLength) {
+ LoadHTML("<INPUT type='text' id='element' value='value'"
+ " maxlength='5'/>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("value");
+ expected.form_control_type = "text";
+ expected.max_length = 5;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+}
+
+// We should be able to extract a text field that has been autofilled.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldAutofilled) {
+ LoadHTML("<INPUT type='text' id='element' value='value'/>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebInputElement element = GetInputElementById("element");
+ element.SetAutofillState(WebAutofillState::kAutofilled);
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("value");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ expected.is_autofilled = true;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+}
+
+// We should be able to extract a radio or a checkbox field that has been
+// autofilled.
+TEST_F(FormAutofillTest, WebFormControlElementToClickableFormField) {
+ LoadHTML("<INPUT type='checkbox' id='checkbox' value='mail' checked/>"
+ "<INPUT type='radio' id='radio' value='male'/>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebInputElement element = GetInputElementById("checkbox");
+ element.SetAutofillState(WebAutofillState::kAutofilled);
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("checkbox");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("mail");
+ expected.form_control_type = "checkbox";
+ expected.is_autofilled = true;
+ expected.check_status = FormFieldData::CheckStatus::kChecked;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+
+ element = GetInputElementById("radio");
+ element.SetAutofillState(WebAutofillState::kAutofilled);
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ expected.id_attribute = ASCIIToUTF16("radio");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("male");
+ expected.form_control_type = "radio";
+ expected.is_autofilled = true;
+ expected.check_status = FormFieldData::CheckStatus::kCheckableButUnchecked;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+}
+
+// We should be able to extract a <select> field.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldSelect) {
+ LoadHTML("<SELECT id='element'/>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result1;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result1);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.max_length = 0;
+ expected.form_control_type = "select-one";
+
+ expected.value = ASCIIToUTF16("CA");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result1);
+
+ FormFieldData result2;
+ WebFormControlElementToFormField(
+ element, nullptr,
+ static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTION_TEXT), &result2);
+ expected.value = ASCIIToUTF16("California");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result2);
+
+ FormFieldData result3;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_OPTIONS, &result3);
+ expected.value.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result3);
+
+ ASSERT_EQ(2U, result3.option_values.size());
+ ASSERT_EQ(2U, result3.option_contents.size());
+ EXPECT_EQ(ASCIIToUTF16("CA"), result3.option_values[0]);
+ EXPECT_EQ(ASCIIToUTF16("California"), result3.option_contents[0]);
+ EXPECT_EQ(ASCIIToUTF16("TX"), result3.option_values[1]);
+ EXPECT_EQ(ASCIIToUTF16("Texas"), result3.option_contents[1]);
+}
+
+// We copy extra attributes for the select field.
+TEST_F(FormAutofillTest,
+ WebFormControlElementToFormFieldSelect_ExtraAttributes) {
+ LoadHTML("<SELECT id='element' autocomplete='off'/>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ element.SetAutofillState(WebAutofillState::kAutofilled);
+
+ FormFieldData result1;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result1);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.max_length = 0;
+ expected.form_control_type = "select-one";
+ // We check that the extra attributes have been copied to |result1|.
+ expected.is_autofilled = true;
+ expected.autocomplete_attribute = "off";
+ expected.should_autocomplete = false;
+ expected.is_focusable = true;
+ expected.text_direction = base::i18n::LEFT_TO_RIGHT;
+
+ expected.value = ASCIIToUTF16("CA");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result1);
+}
+
+// When faced with <select> field with *many* options, we should trim them to a
+// reasonable number.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldLongSelect) {
+ std::string html = "<SELECT id='element'/>";
+ for (size_t i = 0; i < 2 * kMaxListSize; ++i) {
+ html += base::StringPrintf("<OPTION value='%" PRIuS "'>"
+ "%" PRIuS "</OPTION>", i, i);
+ }
+ html += "</SELECT>";
+ LoadHTML(html.c_str());
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_TRUE(frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_OPTIONS, &result);
+
+ EXPECT_TRUE(result.option_values.empty());
+ EXPECT_TRUE(result.option_contents.empty());
+}
+
+// We should be able to extract a <textarea> field.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldTextArea) {
+ LoadHTML("<TEXTAREA id='element'>"
+ "This element's value&#10;"
+ "spans multiple lines."
+ "</TEXTAREA>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result_sans_value;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_NONE,
+ &result_sans_value);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.max_length = 0;
+ expected.form_control_type = "textarea";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result_sans_value);
+
+ FormFieldData result_with_value;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE,
+ &result_with_value);
+ expected.value = ASCIIToUTF16("This element's value\n"
+ "spans multiple lines.");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result_with_value);
+}
+
+// We should be able to extract an <input type="month"> field.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldMonthInput) {
+ LoadHTML("<INPUT type='month' id='element' value='2011-12'>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+ FormFieldData result_sans_value;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_NONE,
+ &result_sans_value);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("element");
+ expected.name = expected.id_attribute;
+ expected.max_length = 0;
+ expected.form_control_type = "month";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result_sans_value);
+
+ FormFieldData result_with_value;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE,
+ &result_with_value);
+ expected.value = ASCIIToUTF16("2011-12");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result_with_value);
+}
+
+// We should not extract the value for non-text and non-select fields.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldInvalidType) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <INPUT type='hidden' id='hidden' value='apple'/>"
+ " <INPUT type='submit' id='submit' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("hidden");
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+
+ FormFieldData expected;
+ expected.max_length = 0;
+
+ expected.id_attribute = ASCIIToUTF16("hidden");
+ expected.name = expected.id_attribute;
+ expected.form_control_type = "hidden";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+
+ element = GetFormControlElementById("submit");
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ expected.id_attribute = ASCIIToUTF16("submit");
+ expected.name = expected.id_attribute;
+ expected.form_control_type = "submit";
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+}
+
+// We should be able to extract password fields.
+TEST_F(FormAutofillTest, WebFormControlElementToPasswordFormField) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <INPUT type='password' id='password' value='secret'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("password");
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+
+ FormFieldData expected;
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ expected.id_attribute = ASCIIToUTF16("password");
+ expected.name = expected.id_attribute;
+ expected.form_control_type = "password";
+ expected.value = ASCIIToUTF16("secret");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+}
+
+// We should be able to extract the autocompletetype attribute.
+TEST_F(FormAutofillTest, WebFormControlElementToFormFieldAutocompletetype) {
+ std::string html =
+ "<INPUT type='text' id='absent'/>"
+ "<INPUT type='text' id='empty' autocomplete=''/>"
+ "<INPUT type='text' id='off' autocomplete='off'/>"
+ "<INPUT type='text' id='regular' autocomplete='email'/>"
+ "<INPUT type='text' id='multi-valued' "
+ " autocomplete='billing email'/>"
+ "<INPUT type='text' id='experimental' x-autocompletetype='email'/>"
+ "<INPUT type='month' id='month' autocomplete='cc-exp'/>"
+ "<SELECT id='select' autocomplete='state'/>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ "</SELECT>"
+ "<TEXTAREA id='textarea' autocomplete='street-address'>"
+ " Some multi-"
+ " lined value"
+ "</TEXTAREA>";
+ html +=
+ "<INPUT type='text' id='malicious' autocomplete='" +
+ std::string(10000, 'x') + "'/>";
+ LoadHTML(html.c_str());
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ struct TestCase {
+ const std::string element_id;
+ const std::string form_control_type;
+ const std::string autocomplete_attribute;
+ };
+ TestCase test_cases[] = {
+ // An absent attribute is equivalent to an empty one.
+ { "absent", "text", "" },
+ // Make sure there are no issues parsing an empty attribute.
+ { "empty", "text", "" },
+ // Make sure there are no issues parsing an attribute value that isn't a
+ // type hint.
+ { "off", "text", "off" },
+ // Common case: exactly one type specified.
+ { "regular", "text", "email" },
+ // Verify that we correctly extract multiple tokens as well.
+ { "multi-valued", "text", "billing email" },
+ // Verify that <input type="month"> fields are supported.
+ { "month", "month", "cc-exp" },
+ // We previously extracted this data from the experimental
+ // 'x-autocompletetype' attribute. Now that the field type hints are part
+ // of the spec under the autocomplete attribute, we no longer support the
+ // experimental version.
+ { "experimental", "text", "" },
+ // <select> elements should behave no differently from text fields here.
+ { "select", "select-one", "state" },
+ // <textarea> elements should also behave no differently from text fields.
+ { "textarea", "textarea", "street-address" },
+ // Very long attribute values should be replaced by a default string, to
+ // prevent malicious websites from DOSing the browser process.
+ { "malicious", "text", "x-max-data-length-exceeded" },
+ };
+
+ WebDocument document = frame->GetDocument();
+ for (size_t i = 0; i < base::size(test_cases); ++i) {
+ WebFormControlElement element = GetFormControlElementById(
+ WebString::FromASCII(test_cases[i].element_id));
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_NONE, &result);
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16(test_cases[i].element_id);
+ expected.name = expected.id_attribute;
+ expected.form_control_type = test_cases[i].form_control_type;
+ expected.autocomplete_attribute = test_cases[i].autocomplete_attribute;
+ if (test_cases[i].form_control_type == "text")
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ else
+ expected.max_length = 0;
+
+ SCOPED_TRACE(test_cases[i].element_id);
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
+ }
+}
+
+TEST_F(FormAutofillTest, DetectTextDirectionFromDirectStyle) {
+ LoadHTML("<STYLE>input{direction:rtl}</STYLE>"
+ "<FORM>"
+ " <INPUT type='text' id='element'>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, DetectTextDirectionFromDirectDIRAttribute) {
+ LoadHTML("<FORM>"
+ " <INPUT dir='rtl' type='text' id='element'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, DetectTextDirectionFromParentStyle) {
+ LoadHTML("<STYLE>form{direction:rtl}</STYLE>"
+ "<FORM>"
+ " <INPUT type='text' id='element'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, DetectTextDirectionFromParentDIRAttribute) {
+ LoadHTML("<FORM dir='rtl'>"
+ " <INPUT type='text' id='element'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, DetectTextDirectionWhenStyleAndDIRAttributMixed) {
+ LoadHTML("<STYLE>input{direction:ltr}</STYLE>"
+ "<FORM dir='rtl'>"
+ " <INPUT type='text' id='element'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, TextAlignOverridesDirection) {
+ // text-align: right
+ LoadHTML("<STYLE>input{direction:ltr;text-align:right}</STYLE>"
+ "<FORM>"
+ " <INPUT type='text' id='element'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction);
+
+ // text-align: left
+ LoadHTML("<STYLE>input{direction:rtl;text-align:left}</STYLE>"
+ "<FORM>"
+ " <INPUT type='text' id='element'/>"
+ "</FORM>");
+
+ frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ element = GetFormControlElementById("element");
+
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest,
+ DetectTextDirectionWhenParentHasBothDIRAttributeAndStyle) {
+ LoadHTML("<STYLE>form{direction:ltr}</STYLE>"
+ "<FORM dir='rtl'>"
+ " <INPUT type='text' id='element'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, DetectTextDirectionWhenAncestorHasInlineStyle) {
+ LoadHTML("<FORM style='direction:ltr'>"
+ " <SPAN dir='rtl'>"
+ " <INPUT type='text' id='element'/>"
+ " </SPAN>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormControlElement element = GetFormControlElementById("element");
+
+ FormFieldData result;
+ WebFormControlElementToFormField(element, nullptr, EXTRACT_VALUE, &result);
+ EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction);
+}
+
+TEST_F(FormAutofillTest, WebFormElementToFormData) {
+ LoadHTML(
+ "<FORM name='TestForm' action='http://cnn.com/submit/?a=1' method='post'>"
+ " <LABEL for='firstname'>First name:</LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <LABEL for='lastname'>Last name:</LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='street-address'>Address:</LABEL>"
+ " <TEXTAREA id='street-address'>"
+ "123 Fantasy Ln.&#10;"
+ "Apt. 42"
+ "</TEXTAREA>"
+ " <LABEL for='state'>State:</LABEL>"
+ " <SELECT id='state'/>"
+ " <OPTION value='CA'>California</OPTION>"
+ " <OPTION value='TX'>Texas</OPTION>"
+ " </SELECT>"
+ " <LABEL for='password'>Password:</LABEL>"
+ " <INPUT type='password' id='password' value='secret'/>"
+ " <LABEL for='month'>Card expiration:</LABEL>"
+ " <INPUT type='month' id='month' value='2011-12'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ // The below inputs should be ignored
+ " <LABEL for='notvisible'>Hidden:</LABEL>"
+ " <INPUT type='hidden' id='notvisible' value='apple'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebVector<WebFormElement> forms;
+ frame->GetDocument().Forms(forms);
+ ASSERT_EQ(1U, forms.size());
+
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(WebFormElementToFormData(forms[0], input_element, nullptr,
+ EXTRACT_VALUE, &form, &field));
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(forms[0].UniqueRendererFormId(), form.unique_renderer_id);
+ EXPECT_EQ(GetCanonicalOriginForDocument(frame->GetDocument()), form.url);
+ EXPECT_FALSE(form.url.is_empty());
+ EXPECT_EQ(GURL("http://cnn.com/submit/"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(6U, fields.size());
+
+ FormFieldData expected;
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("First name:");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Last name:");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("street-address");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("123 Fantasy Ln.\nApt. 42");
+ expected.label = ASCIIToUTF16("Address:");
+ expected.form_control_type = "textarea";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ expected.id_attribute = ASCIIToUTF16("state");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("CA");
+ expected.label = ASCIIToUTF16("State:");
+ expected.form_control_type = "select-one";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+
+ expected.id_attribute = ASCIIToUTF16("password");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("secret");
+ expected.label = ASCIIToUTF16("Password:");
+ expected.form_control_type = "password";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[4]);
+
+ expected.id_attribute = ASCIIToUTF16("month");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("2011-12");
+ expected.label = ASCIIToUTF16("Card expiration:");
+ expected.form_control_type = "month";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[5]);
+
+ // Check unique_renderer_id.
+ WebVector<WebFormControlElement> form_control_elements;
+ forms[0].GetFormControlElements(form_control_elements);
+ for (size_t i = 0; i < fields.size(); ++i)
+ EXPECT_EQ(form_control_elements[i].UniqueRendererFormControlId(),
+ fields[i].unique_renderer_id);
+}
+
+TEST_F(FormAutofillTest, WebFormElementConsiderNonControlLabelableElements) {
+ LoadHTML("<form id=form>"
+ " <label for='progress'>Progress:</label>"
+ " <progress id='progress'></progress>"
+ " <label for='firstname'>First name:</label>"
+ " <input type='text' id='firstname' value='John'>"
+ "</form>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, WebFormControlElement(),
+ nullptr, EXTRACT_NONE, &form, nullptr));
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(1U, fields.size());
+ EXPECT_EQ(ASCIIToUTF16("firstname"), fields[0].name);
+}
+
+// TODO(crbug.com/616730) Flaky.
+#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
+#define MAYBE_WebFormElementToFormDataTooManyFields \
+ DISABLED_WebFormElementToFormDataTooManyFields
+#else
+#define MAYBE_WebFormElementToFormDataTooManyFields \
+ WebFormElementToFormDataTooManyFields
+#endif
+// We should not be able to serialize a form with too many fillable fields.
+TEST_F(FormAutofillTest, MAYBE_WebFormElementToFormDataTooManyFields) {
+ std::string html =
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>";
+ for (size_t i = 0; i < (kMaxParseableFields + 1); ++i) {
+ html += "<INPUT type='text'/>";
+ }
+ html += "</FORM>";
+ LoadHTML(html.c_str());
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebVector<WebFormElement> forms;
+ frame->GetDocument().Forms(forms);
+ ASSERT_EQ(1U, forms.size());
+
+ WebInputElement input_element = GetInputElementById("firstname");
+
+ FormData form;
+ FormFieldData field;
+ EXPECT_FALSE(WebFormElementToFormData(forms[0], input_element, nullptr,
+ EXTRACT_VALUE, &form, &field));
+}
+
+// Tests that the |should_autocomplete| is set to false for all the fields when
+// an autocomplete='off' attribute is set for the form in HTML.
+TEST_F(FormAutofillTest, WebFormElementToFormData_AutocompleteOff_OnForm) {
+ LoadHTML(
+ "<FORM name='TestForm' id='form' action='http://cnn.com' method='post' "
+ "autocomplete='off'>"
+ " <LABEL for='firstname'>First name:</LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <LABEL for='lastname'>Last name:</LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='street-address'>Address:</LABEL>"
+ " <INPUT type='text' id='addressline1' value='123 Test st.'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, WebFormControlElement(),
+ nullptr, EXTRACT_NONE, &form, nullptr));
+
+ for (const FormFieldData& field : form.fields) {
+ EXPECT_FALSE(field.should_autocomplete);
+ }
+}
+
+// Tests that the |should_autocomplete| is set to false only for the field
+// which has an autocomplete='off' attribute set for it in HTML.
+TEST_F(FormAutofillTest, WebFormElementToFormData_AutocompleteOff_OnField) {
+ LoadHTML(
+ "<FORM name='TestForm' id='form' action='http://cnn.com' method='post'>"
+ " <LABEL for='firstname'>First name:</LABEL>"
+ " <INPUT type='text' id='firstname' value='John' autocomplete='off'/>"
+ " <LABEL for='lastname'>Last name:</LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='street-address'>Address:</LABEL>"
+ " <INPUT type='text' id='addressline1' value='123 Test st.'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, WebFormControlElement(),
+ nullptr, EXTRACT_NONE, &form, nullptr));
+
+ ASSERT_EQ(3U, form.fields.size());
+
+ EXPECT_FALSE(form.fields[0].should_autocomplete);
+ EXPECT_TRUE(form.fields[1].should_autocomplete);
+ EXPECT_TRUE(form.fields[2].should_autocomplete);
+}
+
+// Tests CSS classes are set.
+TEST_F(FormAutofillTest, WebFormElementToFormData_CssClasses) {
+ LoadHTML(
+ "<FORM name='TestForm' id='form' action='http://cnn.com' method='post' "
+ "autocomplete='off'>"
+ " <INPUT type='text' id='firstname' class='firstname_field' />"
+ " <INPUT type='text' id='lastname' class='lastname_field' />"
+ " <INPUT type='text' id='addressline1' />"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, WebFormControlElement(),
+ nullptr, EXTRACT_NONE, &form, nullptr));
+
+ EXPECT_EQ(3U, form.fields.size());
+ EXPECT_EQ(ASCIIToUTF16("firstname_field"), form.fields[0].css_classes);
+ EXPECT_EQ(ASCIIToUTF16("lastname_field"), form.fields[1].css_classes);
+ EXPECT_EQ(base::string16(), form.fields[2].css_classes);
+}
+
+// Tests id attributes are set.
+TEST_F(FormAutofillTest, WebFormElementToFormData_IdAttributes) {
+ LoadHTML(
+ "<FORM name='TestForm' id='form' action='http://cnn.com' method='post' "
+ "autocomplete='off'>"
+ " <INPUT type='text' name='name1' id='firstname' />"
+ " <INPUT type='text' name='name2' id='lastname' />"
+ " <INPUT type='text' name='same' id='same' />"
+ " <INPUT type='text' id='addressline1' />"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, WebFormControlElement(),
+ nullptr, EXTRACT_NONE, &form, nullptr));
+
+ EXPECT_EQ(4U, form.fields.size());
+
+ // id attributes.
+ EXPECT_EQ(ASCIIToUTF16("firstname"), form.fields[0].id_attribute);
+ EXPECT_EQ(ASCIIToUTF16("lastname"), form.fields[1].id_attribute);
+ EXPECT_EQ(ASCIIToUTF16("same"), form.fields[2].id_attribute);
+ EXPECT_EQ(ASCIIToUTF16("addressline1"), form.fields[3].id_attribute);
+
+ // name attributes.
+ EXPECT_EQ(ASCIIToUTF16("name1"), form.fields[0].name_attribute);
+ EXPECT_EQ(ASCIIToUTF16("name2"), form.fields[1].name_attribute);
+ EXPECT_EQ(ASCIIToUTF16("same"), form.fields[2].name_attribute);
+ EXPECT_EQ(ASCIIToUTF16(""), form.fields[3].name_attribute);
+
+ // name for autofill
+ EXPECT_EQ(ASCIIToUTF16("name1"), form.fields[0].name);
+ EXPECT_EQ(ASCIIToUTF16("name2"), form.fields[1].name);
+ EXPECT_EQ(ASCIIToUTF16("same"), form.fields[2].name);
+ EXPECT_EQ(ASCIIToUTF16("addressline1"), form.fields[3].name);
+}
+
+TEST_F(FormAutofillTest, ExtractForms) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " First name: <INPUT type='text' id='firstname' value='John'/>"
+ " Last name: <INPUT type='text' id='lastname' value='Smith'/>"
+ " Email: <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, ExtractMultipleForms) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>"
+ "<FORM name='TestForm2' action='http://zoo.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='Jack'/>"
+ " <INPUT type='text' id='lastname' value='Adams'/>"
+ " <INPUT type='text' id='email' value='jack@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(2U, forms.size());
+
+ // First form.
+ const FormData& form = forms[0];
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()), form.url);
+ EXPECT_FALSE(form.url.is_empty());
+ EXPECT_EQ(GURL("http://cnn.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("John");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Smith");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.label = ASCIIToUTF16("john@example.com");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ // Second form.
+ const FormData& form2 = forms[1];
+ EXPECT_EQ(ASCIIToUTF16("TestForm2"), form2.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(web_frame->GetDocument()), form2.url);
+ EXPECT_FALSE(form.url.is_empty());
+ EXPECT_EQ(GURL("http://zoo.com"), form2.action);
+
+ const std::vector<FormFieldData>& fields2 = form2.fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Jack");
+ expected.label = ASCIIToUTF16("Jack");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Adams");
+ expected.label = ASCIIToUTF16("Adams");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("jack@example.com");
+ expected.label = ASCIIToUTF16("jack@example.com");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+}
+
+TEST_F(FormAutofillTest, OnlyExtractNewForms) {
+ LoadHTML(
+ "<FORM id='testform' action='http://cnn.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ // Second call should give nothing as there are no new forms.
+ forms = form_cache.ExtractNewForms();
+ ASSERT_TRUE(forms.empty());
+
+ // Append to the current form will re-extract.
+ ExecuteJavaScriptForTests(
+ "var newInput = document.createElement('input');"
+ "newInput.setAttribute('type', 'text');"
+ "newInput.setAttribute('id', 'telephone');"
+ "newInput.value = '12345';"
+ "document.getElementById('testform').appendChild(newInput);");
+ base::RunLoop().RunUntilIdle();
+
+ forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ const std::vector<FormFieldData>& fields = forms[0].fields;
+ ASSERT_EQ(4U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("John");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Smith");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.label = ASCIIToUTF16("john@example.com");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ expected.id_attribute = ASCIIToUTF16("telephone");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("12345");
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+
+ forms.clear();
+
+ // Completely new form will also be extracted.
+ ExecuteJavaScriptForTests(
+ "var newForm=document.createElement('form');"
+ "newForm.id='new_testform';"
+ "newForm.action='http://google.com';"
+ "newForm.method='post';"
+ "var newFirstname=document.createElement('input');"
+ "newFirstname.setAttribute('type', 'text');"
+ "newFirstname.setAttribute('id', 'second_firstname');"
+ "newFirstname.value = 'Bob';"
+ "var newLastname=document.createElement('input');"
+ "newLastname.setAttribute('type', 'text');"
+ "newLastname.setAttribute('id', 'second_lastname');"
+ "newLastname.value = 'Hope';"
+ "var newEmail=document.createElement('input');"
+ "newEmail.setAttribute('type', 'text');"
+ "newEmail.setAttribute('id', 'second_email');"
+ "newEmail.value = 'bobhope@example.com';"
+ "newForm.appendChild(newFirstname);"
+ "newForm.appendChild(newLastname);"
+ "newForm.appendChild(newEmail);"
+ "document.body.appendChild(newForm);");
+ base::RunLoop().RunUntilIdle();
+
+ web_frame = GetMainFrame();
+ forms = form_cache.ExtractNewForms();
+ ASSERT_EQ(1U, forms.size());
+
+ const std::vector<FormFieldData>& fields2 = forms[0].fields;
+ ASSERT_EQ(3U, fields2.size());
+
+ expected.id_attribute = ASCIIToUTF16("second_firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Bob");
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
+
+ expected.id_attribute = ASCIIToUTF16("second_lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Hope");
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
+
+ expected.id_attribute = ASCIIToUTF16("second_email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("bobhope@example.com");
+ expected.label.clear();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
+}
+
+// We should not extract a form if it has too few fillable fields.
+TEST_F(FormAutofillTest, ExtractFormsTooFewFields) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ // If all minimums are enforced, we ignore this form.
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery,
+ kAutofillEnforceMinRequiredFieldsForUpload},
+ // Disabled.
+ {});
+ ASSERT_TRUE(FormCache(web_frame).ExtractNewForms().empty());
+ }
+
+ // If at least one of the minimums is not enforced, we parse the form.
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery},
+ // Disabled.
+ {kAutofillEnforceMinRequiredFieldsForUpload});
+ ASSERT_FALSE(FormCache(web_frame).ExtractNewForms().empty());
+ }
+}
+
+// We should not report additional forms for empty forms.
+TEST_F(FormAutofillTest, ExtractFormsNoFields) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "</FORM>");
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ ASSERT_TRUE(forms.empty());
+}
+
+// We should not extract a form if it has too few fillable fields.
+// Make sure radio and checkbox fields don't count.
+TEST_F(FormAutofillTest, ExtractFormsTooFewFieldsSkipsCheckable) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='radio' id='a_radio' value='0'/>"
+ " <INPUT type='checkbox' id='a_check' value='1'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ // Without small form support, the form is not parsed.
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery,
+ kAutofillEnforceMinRequiredFieldsForUpload},
+ // Disabled.
+ {});
+ ASSERT_TRUE(FormCache(web_frame).ExtractNewForms().empty());
+ }
+
+ // With small form support, the form is parsed.
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {},
+ // Disabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery,
+ kAutofillEnforceMinRequiredFieldsForUpload});
+ ASSERT_FALSE(FormCache(web_frame).ExtractNewForms().empty());
+ }
+}
+
+TEST_F(FormAutofillTest, WebFormElementToFormDataAutocomplete) {
+ {
+ // Form is still Autofill-able despite autocomplete=off.
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'"
+ " autocomplete=off>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ WebVector<WebFormElement> web_forms;
+ web_frame->GetDocument().Forms(web_forms);
+ ASSERT_EQ(1U, web_forms.size());
+ WebFormElement web_form = web_forms[0];
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, WebFormControlElement(),
+ nullptr, EXTRACT_NONE, &form,
+ nullptr));
+ }
+}
+
+TEST_F(FormAutofillTest, FindFormForInputElement) {
+ TestFindFormForInputElement(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='text' id='email' value='john@example.com'"
+ "autocomplete='off' />"
+ " <INPUT type='text' id='phone' value='1.800.555.1234'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, FindFormForInputElementForUnownedForm) {
+ TestFindFormForInputElement(
+ "<HEAD><TITLE>delivery recipient</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='John'/>"
+ "<INPUT type='text' id='lastname' value='Smith'/>"
+ "<INPUT type='text' id='email' value='john@example.com'"
+ "autocomplete='off' />"
+ "<INPUT type='text' id='phone' value='1.800.555.1234'/>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>",
+ true);
+}
+
+TEST_F(FormAutofillTest, FindFormForTextAreaElement) {
+ TestFindFormForTextAreaElement(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <INPUT type='text' id='email' value='john@example.com'"
+ "autocomplete='off' />"
+ " <TEXTAREA id='street-address'>"
+ "123 Fantasy Ln.&#10;"
+ "Apt. 42"
+ "</TEXTAREA>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, FindFormForTextAreaElementForUnownedForm) {
+ TestFindFormForTextAreaElement(
+ "<HEAD><TITLE>delivery address</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='John'/>"
+ "<INPUT type='text' id='lastname' value='Smith'/>"
+ "<INPUT type='text' id='email' value='john@example.com'"
+ "autocomplete='off' />"
+ "<TEXTAREA id='street-address'>"
+ "123 Fantasy Ln.&#10;"
+ "Apt. 42"
+ "</TEXTAREA>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>",
+ true);
+}
+
+// Test regular FillForm function.
+TEST_F(FormAutofillTest, FillForm) {
+ TestFillForm(kFormHtml, false, nullptr);
+}
+
+TEST_F(FormAutofillTest, FillFormForUnownedForm) {
+ TestFillForm(kUnownedFormHtml, true, nullptr);
+}
+
+TEST_F(FormAutofillTest, FillFormForUnownedUntitledForm) {
+ TestFillForm(kUnownedUntitledFormHtml, true,
+ "http://example.test/checkout_flow");
+}
+
+TEST_F(FormAutofillTest, FillFormForUnownedNonEnglishForm) {
+ TestFillForm(kUnownedNonEnglishFormHtml, true, nullptr);
+}
+
+TEST_F(FormAutofillTest, FillFormForUnownedNonASCIIForm) {
+ std::string html("<HEAD><TITLE>accented latin: \xC3\xA0, thai: \xE0\xB8\x81, "
+ "control: \x04, nbsp: \xEF\xBB\xBF, non-BMP: \xF0\x9F\x8C\x80; This "
+ "should match a CHECKOUT flow despite the non-ASCII chars"
+ "</TITLE></HEAD>");
+ html.append(kUnownedUntitledFormHtml);
+ TestFillForm(html.c_str(), true, nullptr);
+}
+
+TEST_F(FormAutofillTest, PreviewForm) {
+ TestPreviewForm(kFormHtml, false, nullptr);
+}
+
+TEST_F(FormAutofillTest, PreviewFormForUnownedForm) {
+ TestPreviewForm(kUnownedFormHtml, true, nullptr);
+}
+
+TEST_F(FormAutofillTest, PreviewFormForUnownedUntitledForm) {
+ // This test uses a mixed-case URL to be sure that the url match is not
+ // case-sensitive.
+ TestPreviewForm(kUnownedUntitledFormHtml, true,
+ "http://example.test/Enter_Shipping_Address/");
+}
+
+TEST_F(FormAutofillTest, PreviewFormForUnownedNonEnglishForm) {
+ TestPreviewForm(kUnownedNonEnglishFormHtml, true, nullptr);
+}
+
+// Data that looks like an unowned form should NOT be matched unless an
+// additional indicator is present, such as title tag or url, to prevent false
+// positives. The fields that have an autocomplete attribute should match since
+// there is no chance of making a prediction error.
+
+TEST_F(FormAutofillTest, UnmatchedFormNoURL) {
+ TestUnmatchedUnownedForm(kUnownedUntitledFormHtml, nullptr);
+}
+
+TEST_F(FormAutofillTest, UnmatchedFormPathWithoutKeywords) {
+ TestUnmatchedUnownedForm(kUnownedUntitledFormHtml,
+ "http://example.test/path_without_keywords");
+}
+
+TEST_F(FormAutofillTest, UnmatchedFormKeywordInQueryOnly) {
+ TestUnmatchedUnownedForm(kUnownedUntitledFormHtml,
+ "http://example.test/search?q=checkout+in+query");
+}
+
+TEST_F(FormAutofillTest, UnmatchedFormTitleWithoutKeywords) {
+ std::string wrong_title_html(
+ "<TITLE>This title has nothing to do with autofill</TITLE>");
+ wrong_title_html += kUnownedUntitledFormHtml;
+ TestUnmatchedUnownedForm(wrong_title_html.c_str(), nullptr);
+}
+
+TEST_F(FormAutofillTest, UnmatchedFormNonASCII) {
+ std::string html("<HEAD><TITLE>Non-ASCII soft hyphen in the middle of "
+ "keyword prevents a match here: check\xC2\xADout"
+ "</TITLE></HEAD>");
+ html.append(kUnownedUntitledFormHtml);
+ TestUnmatchedUnownedForm(html.c_str(), nullptr);
+}
+
+
+TEST_F(FormAutofillTest, Labels) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <LABEL for='firstname'> First name: </LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <LABEL for='lastname'> Last name: </LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='email'> Email: </LABEL>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsWithSpans) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <LABEL for='firstname'><span>First name: </span></LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <LABEL for='lastname'><span>Last name: </span></LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='email'><span>Email: </span></LABEL>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+// This test is different from FormAutofillTest.Labels in that the label
+// elements for= attribute is set to the name of the form control element it is
+// a label for instead of the id of the form control element. This is invalid
+// because the for= attribute must be set to the id of the form control element;
+// however, current label parsing code will extract the text from the previous
+// label element and apply it to the following input field.
+TEST_F(FormAutofillTest, InvalidLabels) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("firstname"));
+ labels.push_back(ASCIIToUTF16("First name:"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("lastname"));
+ labels.push_back(ASCIIToUTF16("Last name:"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("email"));
+ labels.push_back(ASCIIToUTF16("Email:"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <LABEL for='firstname'> First name: </LABEL>"
+ " <INPUT type='text' name='firstname' value='John'/>"
+ " <LABEL for='lastname'> Last name: </LABEL>"
+ " <INPUT type='text' name='lastname' value='Smith'/>"
+ " <LABEL for='email'> Email: </LABEL>"
+ " <INPUT type='text' name='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+// This test has three form control elements, only one of which has a label
+// element associated with it.
+TEST_F(FormAutofillTest, OneLabelElement) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " First name:"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <LABEL for='lastname'>Last name: </LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " Email:"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromText) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " First name:"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " Last name:"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " Email:"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromParagraph) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <P>First name:</P><INPUT type='text' "
+ " id='firstname' value='John'/>"
+ " <P>Last name:</P>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <P>Email:</P>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromBold) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <B>First name:</B><INPUT type='text' "
+ " id='firstname' value='John'/>"
+ " <B>Last name:</B>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <B>Email:</B>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredPriorToImgOrBr) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " First name:<IMG/><INPUT type='text' "
+ " id='firstname' value='John'/>"
+ " Last name:<IMG/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " Email:<BR/>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromTableCell) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>First name:</TD>"
+ " <TD><INPUT type='text' id='firstname' value='John'/></TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>Last name:</TD>"
+ " <TD><INPUT type='text' id='lastname' value='Smith'/></TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>Email:</TD>"
+ " <TD><INPUT type='text' id='email'"
+ " value='john@example.com'/></TD>"
+ " </TR>"
+ " <TR>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromTableCellTH) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TH>First name:</TH>"
+ " <TD><INPUT type='text' id='firstname' value='John'/></TD>"
+ " </TR>"
+ " <TR>"
+ " <TH>Last name:</TH>"
+ " <TD><INPUT type='text' id='lastname' value='Smith'/></TD>"
+ " </TR>"
+ " <TR>"
+ " <TH>Email:</TH>"
+ " <TD><INPUT type='text' id='email'"
+ " value='john@example.com'/></TD>"
+ " </TR>"
+ " <TR>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromTableCellNested) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("First name: Bogus"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Last name:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Email:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>"
+ " <FONT>"
+ " First name:"
+ " </FONT>"
+ " <FONT>"
+ " Bogus"
+ " </FONT>"
+ " </TD>"
+ " <TD>"
+ " <FONT>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </FONT>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <FONT>"
+ " Last name:"
+ " </FONT>"
+ " </TD>"
+ " <TD>"
+ " <FONT>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </FONT>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <FONT>"
+ " Email:"
+ " </FONT>"
+ " </TD>"
+ " <TD>"
+ " <FONT>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </FONT>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromTableEmptyTDs) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* First Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Last Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Email"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>First Name</B>"
+ " </TD>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>Last Name</B>"
+ " </TD>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>Email</B>"
+ " </TD>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromPreviousTD) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* First Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Last Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Email"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>* First Name</TD>"
+ " <TD>"
+ " Bogus"
+ " <INPUT type='hidden'/>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>* Last Name</TD>"
+ " <TD>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>* Email</TD>"
+ " <TD>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+// <script>, <noscript> and <option> tags are excluded when the labels are
+// inferred.
+// Also <!-- comment --> is excluded.
+TEST_F(FormAutofillTest, LabelsInferredFromTableWithSpecialElements) {
+ FormFieldData expected;
+ std::vector<FormFieldData> fields;
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name_attribute = ASCIIToUTF16("");
+ expected.label = ASCIIToUTF16("* First Name");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ fields.push_back(expected);
+
+ expected.id_attribute = ASCIIToUTF16("middlename");
+ expected.name_attribute = ASCIIToUTF16("");
+ expected.label = ASCIIToUTF16("* Middle Name");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Joe");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ fields.push_back(expected);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name_attribute = ASCIIToUTF16("");
+ expected.label = ASCIIToUTF16("* Last Name");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ fields.push_back(expected);
+
+ expected.id_attribute = ASCIIToUTF16("country");
+ expected.name_attribute = ASCIIToUTF16("");
+ expected.label = ASCIIToUTF16("* Country");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("US");
+ expected.form_control_type = "select-one";
+ expected.max_length = 0;
+ fields.push_back(expected);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name_attribute = ASCIIToUTF16("");
+ expected.label = ASCIIToUTF16("* Email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ fields.push_back(expected);
+
+ ExpectLabelsAndTypes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>First Name</B>"
+ " </TD>"
+ " <TD>"
+ " <SCRIPT> <!-- function test() { alert('ignored as label'); } -->"
+ " </SCRIPT>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>Middle Name</B>"
+ " </TD>"
+ " <TD>"
+ " <NOSCRIPT>"
+ " <P>Bad</P>"
+ " </NOSCRIPT>"
+ " <INPUT type='text' id='middlename' value='Joe'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>Last Name</B>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>Country</B>"
+ " </TD>"
+ " <TD>"
+ " <SELECT id='country'>"
+ " <OPTION VALUE='US'>The value should be ignored as label."
+ " </OPTION>"
+ " <OPTION VALUE='JP'>JAPAN</OPTION>"
+ " </SELECT>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN>"
+ " <B>Email</B>"
+ " </TD>"
+ " <TD>"
+ " <!-- This comment should be ignored as inferred label.-->"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD></TD>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>",
+ fields);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromTableLabels) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>"
+ " <LABEL>First name:</LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <LABEL>Last name:</LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <LABEL>Email:</LABEL>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromTableTDInterveningElements) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>"
+ " First name:"
+ " <BR>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " Last name:"
+ " <BR>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " Email:"
+ " <BR>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+}
+
+// Verify that we correctly infer labels when the label text spans multiple
+// adjacent HTML elements, not separated by whitespace.
+TEST_F(FormAutofillTest, LabelsInferredFromTableAdjacentElements) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("*First Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("*Last Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("*Email"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN><B>First Name</B>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN><B>Last Name</B>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <SPAN>*</SPAN><B>Email</B>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+// Verify that we correctly infer labels when the label text resides in the
+// previous row.
+TEST_F(FormAutofillTest, LabelsInferredFromTableRow) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("*First Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("*Last Name"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("*Email"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ id_attributes.push_back(ASCIIToUTF16("name2"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("NAME"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email2"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("EMAIL"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example2.com"));
+
+ id_attributes.push_back(ASCIIToUTF16("phone1"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Phone"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("123"));
+
+ id_attributes.push_back(ASCIIToUTF16("phone2"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Phone"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("456"));
+
+ id_attributes.push_back(ASCIIToUTF16("phone3"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Phone"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("7890"));
+
+ // Note that ccnumber uses the name attribute instead of the id attribute.
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("ccnumber"));
+ labels.push_back(ASCIIToUTF16("Credit Card Number"));
+ names.push_back(name_attributes.back());
+ values.push_back(ASCIIToUTF16("4444555544445555"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<TABLE>"
+ " <TR>"
+ " <TD>*First Name</TD>"
+ " <TD>*Last Name</TD>"
+ " <TD>*Email</TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD colspan='2'>NAME</TD>"
+ " <TD>EMAIL</TD>"
+ " </TR>"
+ " <TR>"
+ " <TD colspan='2'>"
+ " <INPUT type='text' id='name2' value='John Smith'/>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='email2' value='john@example2.com'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>Phone</TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <INPUT type='text' id='phone1' value='123'/>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='phone2' value='456'/>"
+ " </TD>"
+ " <TD>"
+ " <INPUT type='text' id='phone3' value='7890'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TH>"
+ " Credit Card Number"
+ " </TH>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <INPUT type='text' name='ccnumber' value='4444555544445555'/>"
+ " </TD>"
+ " </TR>"
+ " <TR>"
+ " <TD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </TD>"
+ " </TR>"
+ "</TABLE>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+// Verify that we correctly infer labels when enclosed within a list item.
+TEST_F(FormAutofillTest, LabelsInferredFromListItem) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("areacode"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Home Phone"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("415"));
+
+ id_attributes.push_back(ASCIIToUTF16("prefix"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Home Phone"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("555"));
+
+ id_attributes.push_back(ASCIIToUTF16("suffix"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* Home Phone"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("1212"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<DIV>"
+ " <LI>"
+ " <SPAN>Bogus</SPAN>"
+ " </LI>"
+ " <LI>"
+ " <LABEL><EM>*</EM> Home Phone</LABEL>"
+ " <INPUT type='text' id='areacode' value='415'/>"
+ " <INPUT type='text' id='prefix' value='555'/>"
+ " <INPUT type='text' id='suffix' value='1212'/>"
+ " </LI>"
+ " <LI>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </LI>"
+ "</DIV>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromDefinitionList) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("* First name: Bogus"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Last name:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(ASCIIToUTF16(""));
+ labels.push_back(ASCIIToUTF16("Email:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<DL>"
+ " <DT>"
+ " <SPAN>"
+ " *"
+ " </SPAN>"
+ " <SPAN>"
+ " First name:"
+ " </SPAN>"
+ " <SPAN>"
+ " Bogus"
+ " </SPAN>"
+ " </DT>"
+ " <DD>"
+ " <FONT>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </FONT>"
+ " </DD>"
+ " <DT>"
+ " <SPAN>"
+ " Last name:"
+ " </SPAN>"
+ " </DT>"
+ " <DD>"
+ " <FONT>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </FONT>"
+ " </DD>"
+ " <DT>"
+ " <SPAN>"
+ " Email:"
+ " </SPAN>"
+ " </DT>"
+ " <DD>"
+ " <FONT>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </FONT>"
+ " </DD>"
+ " <DT></DT>"
+ " <DD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </DD>"
+ "</DL>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredWithSameName) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("Address"));
+ labels.push_back(ASCIIToUTF16("Address Line 1:"));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("Address"));
+ labels.push_back(ASCIIToUTF16("Address Line 2:"));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("Address"));
+ labels.push_back(ASCIIToUTF16("Address Line 3:"));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " Address Line 1:"
+ " <INPUT type='text' name='Address'/>"
+ " Address Line 2:"
+ " <INPUT type='text' name='Address'/>"
+ " Address Line 3:"
+ " <INPUT type='text' name='Address'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredWithImageTags) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("dayphone1"));
+ labels.push_back(ASCIIToUTF16("Phone:"));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("dayphone2"));
+ labels.push_back(ASCIIToUTF16(""));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("dayphone3"));
+ labels.push_back(ASCIIToUTF16(""));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("dayphone4"));
+ labels.push_back(ASCIIToUTF16("ext.:"));
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ id_attributes.push_back(ASCIIToUTF16(""));
+ name_attributes.push_back(ASCIIToUTF16("dummy"));
+ labels.push_back(base::string16());
+ names.push_back(name_attributes.back());
+ values.push_back(base::string16());
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " Phone:"
+ " <input type='text' name='dayphone1'>"
+ " <img/>"
+ " -"
+ " <img/>"
+ " <input type='text' name='dayphone2'>"
+ " <img/>"
+ " -"
+ " <img/>"
+ " <input type='text' name='dayphone3'>"
+ " ext.:"
+ " <input type='text' name='dayphone4'>"
+ " <input type='text' name='dummy'>"
+ " <input type='submit' name='reply-send' value='Send'>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromDivTable) {
+ ExpectJohnSmithLabelsAndNameAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<DIV>First name:<BR>"
+ " <SPAN>"
+ " <INPUT type='text' name='firstname' value='John'>"
+ " </SPAN>"
+ "</DIV>"
+ "<DIV>Last name:<BR>"
+ " <SPAN>"
+ " <INPUT type='text' name='lastname' value='Smith'>"
+ " </SPAN>"
+ "</DIV>"
+ "<DIV>Email:<BR>"
+ " <SPAN>"
+ " <INPUT type='text' name='email' value='john@example.com'>"
+ " </SPAN>"
+ "</DIV>"
+ "<input type='submit' name='reply-send' value='Send'>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromDivSiblingTable) {
+ ExpectJohnSmithLabelsAndNameAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<DIV>First name:</DIV>"
+ "<DIV>"
+ " <SPAN>"
+ " <INPUT type='text' name='firstname' value='John'>"
+ " </SPAN>"
+ "</DIV>"
+ "<DIV>Last name:</DIV>"
+ "<DIV>"
+ " <SPAN>"
+ " <INPUT type='text' name='lastname' value='Smith'>"
+ " </SPAN>"
+ "</DIV>"
+ "<DIV>Email:</DIV>"
+ "<DIV>"
+ " <SPAN>"
+ " <INPUT type='text' name='email' value='john@example.com'>"
+ " </SPAN>"
+ "</DIV>"
+ "<input type='submit' name='reply-send' value='Send'>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromLabelInDivTable) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<LABEL>First name:</LABEL>"
+ "<LABEL for='lastname'>Last name:</LABEL>"
+ "<DIV>"
+ " <INPUT type='text' id='firstname' value='John'>"
+ "</DIV>"
+ "<DIV>"
+ " <INPUT type='text' id='lastname' value='Smith'>"
+ "</DIV>"
+ "<LABEL>Email:</LABEL>"
+ "<DIV>"
+ " <SPAN>"
+ " <INPUT type='text' id='email' value='john@example.com'>"
+ " </SPAN>"
+ "</DIV>"
+ "<input type='submit' name='reply-send' value='Send'>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, LabelsInferredFromDefinitionListRatherThanDivTable) {
+ ExpectJohnSmithLabelsAndIdAttributes(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ "<DIV>This is not a label.<BR>"
+ "<DL>"
+ " <DT>"
+ " <SPAN>"
+ " First name:"
+ " </SPAN>"
+ " </DT>"
+ " <DD>"
+ " <FONT>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " </FONT>"
+ " </DD>"
+ " <DT>"
+ " <SPAN>"
+ " Last name:"
+ " </SPAN>"
+ " </DT>"
+ " <DD>"
+ " <FONT>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </FONT>"
+ " </DD>"
+ " <DT>"
+ " <SPAN>"
+ " Email:"
+ " </SPAN>"
+ " </DT>"
+ " <DD>"
+ " <FONT>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </FONT>"
+ " </DD>"
+ " <DT></DT>"
+ " <DD>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ " </DD>"
+ "</DL>"
+ "</DIV>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, FillFormMaxLength) {
+ TestFillFormMaxLength(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' maxlength='5'/>"
+ " <INPUT type='text' id='lastname' maxlength='7'/>"
+ " <INPUT type='text' id='email' maxlength='9'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, FillFormMaxLengthForUnownedForm) {
+ TestFillFormMaxLength(
+ "<HEAD><TITLE>delivery recipient info</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' maxlength='5'/>"
+ "<INPUT type='text' id='lastname' maxlength='7'/>"
+ "<INPUT type='text' id='email' maxlength='9'/>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>",
+ true);
+}
+
+// This test uses negative values of the maxlength attribute for input elements.
+// In this case, the maxlength of the input elements is set to the default
+// maxlength (defined in WebKit.)
+TEST_F(FormAutofillTest, FillFormNegativeMaxLength) {
+ TestFillFormNegativeMaxLength(
+ "<HEAD><TITLE>delivery recipient info</TITLE></HEAD>"
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' maxlength='-1'/>"
+ " <INPUT type='text' id='lastname' maxlength='-10'/>"
+ " <INPUT type='text' id='email' maxlength='-13'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, FillFormNegativeMaxLengthForUnownedForm) {
+ TestFillFormNegativeMaxLength(
+ "<HEAD><TITLE>delivery recipient info</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' maxlength='-1'/>"
+ "<INPUT type='text' id='lastname' maxlength='-10'/>"
+ "<INPUT type='text' id='email' maxlength='-13'/>"
+ "<INPUT type='submit' name='reply-send' value='Send'/>",
+ true);
+}
+
+TEST_F(FormAutofillTest, FillFormEmptyName) {
+ TestFillFormEmptyName(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='text' id='email'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, FillFormEmptyNameForUnownedForm) {
+ TestFillFormEmptyName(
+ "<HEAD><TITLE>delivery recipient info</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='text' id='email'/>"
+ "<INPUT type='submit' value='Send'/>",
+ true);
+}
+
+TEST_F(FormAutofillTest, FillFormEmptyFormNames) {
+ TestFillFormEmptyFormNames(
+ "<FORM action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname'/>"
+ " <INPUT type='text' id='middlename'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>"
+ "<FORM action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='apple'/>"
+ " <INPUT type='text' id='banana'/>"
+ " <INPUT type='text' id='cantelope'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, FillFormEmptyFormNamesForUnownedForm) {
+ TestFillFormEmptyFormNames(
+ "<HEAD><TITLE>enter delivery preferences</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='text' id='middlename'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='text' id='apple'/>"
+ "<INPUT type='text' id='banana'/>"
+ "<INPUT type='text' id='cantelope'/>"
+ "<INPUT type='submit' value='Send'/>",
+ true);
+}
+
+TEST_F(FormAutofillTest, ThreePartPhone) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " Phone:"
+ " <input type='text' name='dayphone1'>"
+ " -"
+ " <input type='text' name='dayphone2'>"
+ " -"
+ " <input type='text' name='dayphone3'>"
+ " ext.:"
+ " <input type='text' name='dayphone4'>"
+ " <input type='submit' name='reply-send' value='Send'>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebVector<WebFormElement> forms;
+ frame->GetDocument().Forms(forms);
+ ASSERT_EQ(1U, forms.size());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(forms[0], WebFormControlElement(),
+ nullptr, EXTRACT_VALUE, &form, nullptr));
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(frame->GetDocument()), form.url);
+ EXPECT_FALSE(form.url.is_empty());
+ EXPECT_EQ(GURL("http://cnn.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(4U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.label = ASCIIToUTF16("Phone:");
+ expected.name_attribute = ASCIIToUTF16("dayphone1");
+ expected.name = expected.name_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.label = ASCIIToUTF16("");
+ expected.name_attribute = ASCIIToUTF16("dayphone2");
+ expected.name = expected.name_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.label = ASCIIToUTF16("");
+ expected.name_attribute = ASCIIToUTF16("dayphone3");
+ expected.name = expected.name_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ expected.label = ASCIIToUTF16("ext.:");
+ expected.name_attribute = ASCIIToUTF16("dayphone4");
+ expected.name = expected.name_attribute;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+}
+
+TEST_F(FormAutofillTest, MaxLengthFields) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " Phone:"
+ " <input type='text' maxlength='3' name='dayphone1'>"
+ " -"
+ " <input type='text' maxlength='3' name='dayphone2'>"
+ " -"
+ " <input type='text' maxlength='4' size='5'"
+ " name='dayphone3'>"
+ " ext.:"
+ " <input type='text' maxlength='5' name='dayphone4'>"
+ " <input type='text' name='default1'>"
+ " <input type='text' maxlength='-1' name='invalid1'>"
+ " <input type='submit' name='reply-send' value='Send'>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebVector<WebFormElement> forms;
+ frame->GetDocument().Forms(forms);
+ ASSERT_EQ(1U, forms.size());
+
+ FormData form;
+ EXPECT_TRUE(WebFormElementToFormData(forms[0], WebFormControlElement(),
+ nullptr, EXTRACT_VALUE, &form, nullptr));
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(frame->GetDocument()), form.url);
+ EXPECT_EQ(GURL("http://cnn.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(6U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+
+ expected.name_attribute = ASCIIToUTF16("dayphone1");
+ expected.label = ASCIIToUTF16("Phone:");
+ expected.name = expected.name_attribute;
+ expected.max_length = 3;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.name_attribute = ASCIIToUTF16("dayphone2");
+ expected.label = ASCIIToUTF16("");
+ expected.name = expected.name_attribute;
+ expected.max_length = 3;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.name_attribute = ASCIIToUTF16("dayphone3");
+ expected.label = ASCIIToUTF16("");
+ expected.name = expected.name_attribute;
+ expected.max_length = 4;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ expected.name_attribute = ASCIIToUTF16("dayphone4");
+ expected.label = ASCIIToUTF16("ext.:");
+ expected.name = expected.name_attribute;
+ expected.max_length = 5;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
+
+ // When unspecified |size|, default is returned.
+ expected.name_attribute = ASCIIToUTF16("default1");
+ expected.label.clear();
+ expected.name = expected.name_attribute;
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[4]);
+
+ // When invalid |size|, default is returned.
+ expected.name_attribute = ASCIIToUTF16("invalid1");
+ expected.label.clear();
+ expected.name = expected.name_attribute;
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[5]);
+}
+
+// This test re-creates the experience of typing in a field then selecting a
+// profile from the Autofill suggestions popup. The field that is being typed
+// into should be filled even though it's not technically empty.
+TEST_F(FormAutofillTest, FillFormNonEmptyField) {
+ TestFillFormNonEmptyField(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='text' id='email'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false, nullptr, nullptr, nullptr, nullptr, nullptr);
+}
+
+TEST_F(FormAutofillTest, FillFormNonEmptyFieldsWithDefaultValues) {
+ TestFillFormNonEmptyField(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='Enter first name'/>"
+ " <INPUT type='text' id='lastname' value='Enter last name'/>"
+ " <INPUT type='text' id='email' value='Enter email'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false, "Enter last name", "Enter email", nullptr, nullptr, nullptr);
+}
+
+TEST_F(FormAutofillTest, FillFormModifyValues) {
+ TestFillFormAndModifyValues(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' placeholder='First Name' "
+ "value='First Name'/>"
+ " <INPUT type='text' id='lastname' placeholder='Last Name' value='Last "
+ "Name'/>"
+ " <INPUT type='text' id='phone' placeholder='Phone' value='Phone'/>"
+ " <INPUT type='text' id='cc' placeholder='Credit Card Number' "
+ "value='Credit Card'/>"
+ " <INPUT type='text' id='city' placeholder='City' value='City'/>"
+ " <SELECT id='state' name='state' placeholder='State'>"
+ " <OPTION selected>?</OPTION>"
+ " <OPTION>AA</OPTION>"
+ " <OPTION>AE</OPTION>"
+ " <OPTION>AK</OPTION>"
+ " </SELECT>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ "First Name", "Last Name", "Phone", "Credit Card Number", "City",
+ "State");
+}
+
+TEST_F(FormAutofillTest, FillFormModifyInitiatingValue) {
+ TestFillFormAndModifyInitiatingValue(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='cc' placeholder='Credit Card Number' "
+ "value='Credit Card'/>"
+ " <INPUT type='text' id='expiration_date' placeholder='Expiration Date' "
+ "value='Expiration Date'/>"
+ " <INPUT type='text' id='name' placeholder='Full Name' "
+ "value='Full Name'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ "Credit Card Number", "Expiration Date", "Full Name");
+}
+
+TEST_F(FormAutofillTest, FillFormJSModifiesUserInputValue) {
+ TestFillFormJSModifiesUserInputValue(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='cc' placeholder='Credit Card Number' "
+ "value='Credit Card'/>"
+ " <INPUT type='text' id='expiration_date' placeholder='Expiration Date' "
+ "value='Expiration Date'/>"
+ " <INPUT type='text' id='name' placeholder='Full Name' "
+ "value='Full Name'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ "Credit Card Number", "Expiration Date", "Full Name");
+}
+
+TEST_F(FormAutofillTest, FillFormNonEmptyFieldsWithPlaceholderValues) {
+ TestFillFormNonEmptyField(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' placeholder='First Name' "
+ "value='First Name'/>"
+ " <INPUT type='text' id='lastname' placeholder='Last Name' value='Last "
+ "Name'/>"
+ " <INPUT type='text' id='email' placeholder='Email' value='Email'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false, nullptr, nullptr, "First Name", "Last Name", "Email");
+}
+
+TEST_F(FormAutofillTest, FillFormWithPlaceholderValues) {
+ TestFillFormWithPlaceholderValues(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' placeholder='First Name' "
+ "value='First Name'/>"
+ " <INPUT type='text' id='lastname' placeholder='Last Name'"
+ "Name'/>"
+ " <INPUT type='text' id='email' placeholder='Email' value='Email'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ "First Name", "Last Name", "Email");
+}
+
+TEST_F(FormAutofillTest, FillFormNonEmptyFieldForUnownedForm) {
+ TestFillFormNonEmptyField(
+ "<HEAD><TITLE>delivery recipient info</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='text' id='email'/>"
+ "<INPUT type='submit' value='Send'/>",
+ true, nullptr, nullptr, nullptr, nullptr, nullptr);
+}
+
+TEST_F(FormAutofillTest, ClearSectionWithNode) {
+ TestClearSectionWithNode(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='Wyatt'/>"
+ " <INPUT type='text' id='lastname' value='Earp'/>"
+ " <INPUT type='text' autocomplete='off' id='noAC' value='one'/>"
+ " <INPUT type='text' id='notenabled' disabled='disabled'>"
+ " <INPUT type='month' id='month' value='2012-11'>"
+ " <INPUT type='month' id='month-disabled' value='2012-11'"
+ " disabled='disabled'>"
+ " <TEXTAREA id='textarea'>Apple.</TEXTAREA>"
+ " <TEXTAREA id='textarea-disabled' disabled='disabled'>"
+ " Banana!"
+ " </TEXTAREA>"
+ " <TEXTAREA id='textarea-noAC' autocomplete='off'>Carrot?</TEXTAREA>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+// Test regular FillForm function.
+TEST_F(FormAutofillTest, ClearTwoSections) {
+ TestClearTwoSections(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname-shipping'/>"
+ " <INPUT type='text' id='lastname-shipping'/>"
+ " <INPUT type='text' id='city-shipping'/>"
+ " <INPUT type='text' id='firstname-billing'/>"
+ " <INPUT type='text' id='lastname-billing'/>"
+ " <INPUT type='text' id='city-billing'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest, ClearSectionWithNodeForUnownedForm) {
+ TestClearSectionWithNode(
+ "<HEAD><TITLE>store checkout</TITLE></HEAD>"
+ " <!-- Indented on purpose //-->"
+ " <INPUT type='text' id='firstname' value='Wyatt'/>"
+ " <INPUT type='text' id='lastname' value='Earp'/>"
+ " <INPUT type='text' autocomplete='off' id='noAC' value='one'/>"
+ " <INPUT type='text' id='notenabled' disabled='disabled'>"
+ " <INPUT type='month' id='month' value='2012-11'>"
+ " <INPUT type='month' id='month-disabled' value='2012-11'"
+ " disabled='disabled'>"
+ " <TEXTAREA id='textarea'>Apple.</TEXTAREA>"
+ " <TEXTAREA id='textarea-disabled' disabled='disabled'>"
+ " Banana!"
+ " </TEXTAREA>"
+ " <TEXTAREA id='textarea-noAC' autocomplete='off'>Carrot?</TEXTAREA>"
+ " <INPUT type='submit' value='Send'/>",
+ true);
+}
+
+TEST_F(FormAutofillTest, ClearSectionWithNodeContainingSelectOne) {
+ TestClearSectionWithNodeContainingSelectOne(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='Wyatt'/>"
+ " <INPUT type='text' id='lastname' value='Earp'/>"
+ " <SELECT id='state' name='state'>"
+ " <OPTION selected>?</OPTION>"
+ " <OPTION>AA</OPTION>"
+ " <OPTION>AE</OPTION>"
+ " <OPTION>AK</OPTION>"
+ " </SELECT>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ false);
+}
+
+TEST_F(FormAutofillTest,
+ ClearSectionWithNodeContainingSelectOneForUnownedForm) {
+ TestClearSectionWithNodeContainingSelectOne(
+ "<HEAD><TITLE>store checkout</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='Wyatt'/>"
+ "<INPUT type='text' id='lastname' value='Earp'/>"
+ "<SELECT id='state' name='state'>"
+ " <OPTION selected>?</OPTION>"
+ " <OPTION>AA</OPTION>"
+ " <OPTION>AE</OPTION>"
+ " <OPTION>AK</OPTION>"
+ "</SELECT>"
+ "<INPUT type='submit' value='Send'/>",
+ true);
+}
+
+TEST_F(FormAutofillTest, ClearPreviewedFormWithElement) {
+ TestClearPreviewedFormWithElement(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='Wyatt'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='text' id='email'/>"
+ " <INPUT type='email' id='email2'/>"
+ " <INPUT type='tel' id='phone'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, ClearPreviewedFormWithElementForUnownedForm) {
+ TestClearPreviewedFormWithElement(
+ "<HEAD><TITLE>store checkout</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='Wyatt'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='text' id='email'/>"
+ "<INPUT type='email' id='email2'/>"
+ "<INPUT type='tel' id='phone'/>"
+ "<INPUT type='submit' value='Send'/>");
+}
+
+TEST_F(FormAutofillTest, ClearPreviewedFormWithNonEmptyInitiatingNode) {
+ TestClearPreviewedFormWithNonEmptyInitiatingNode(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='W'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='text' id='email'/>"
+ " <INPUT type='email' id='email2'/>"
+ " <INPUT type='tel' id='phone'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest,
+ ClearPreviewedFormWithNonEmptyInitiatingNodeForUnownedForm) {
+ TestClearPreviewedFormWithNonEmptyInitiatingNode(
+ "<HEAD><TITLE>shipping details</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='W'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='text' id='email'/>"
+ "<INPUT type='email' id='email2'/>"
+ "<INPUT type='tel' id='phone'/>"
+ "<INPUT type='submit' value='Send'/>");
+}
+
+TEST_F(FormAutofillTest, ClearPreviewedFormWithAutofilledInitiatingNode) {
+ TestClearPreviewedFormWithAutofilledInitiatingNode(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='W'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='text' id='email'/>"
+ " <INPUT type='email' id='email2'/>"
+ " <INPUT type='tel' id='phone'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest,
+ ClearPreviewedFormWithAutofilledInitiatingNodeForUnownedForm) {
+ TestClearPreviewedFormWithAutofilledInitiatingNode(
+ "<HEAD><TITLE>shipping details</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='W'/>"
+ "<INPUT type='text' id='lastname'/>"
+ "<INPUT type='text' id='email'/>"
+ "<INPUT type='email' id='email2'/>"
+ "<INPUT type='tel' id='phone'/>"
+ "<INPUT type='submit' value='Send'/>");
+}
+
+// Autofill's "Clear Form" should clear only autofilled fields
+TEST_F(FormAutofillTest, ClearOnlyAutofilledFields) {
+ TestClearOnlyAutofilledFields(
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='Wyatt'/>"
+ " <INPUT type='text' id='lastname' value='Earp'/>"
+ " <INPUT type='email' id='email' value='wyatt@earp.com'/>"
+ " <INPUT type='tel' id='phone' value='650-777-9999'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>");
+}
+
+TEST_F(FormAutofillTest, ClearOnlyAutofilledFieldsForUnownedForm) {
+ TestClearOnlyAutofilledFields(
+ "<HEAD><TITLE>shipping details</TITLE></HEAD>"
+ "<INPUT type='text' id='firstname' value='Wyatt'/>"
+ "<INPUT type='text' id='lastname' value='Earp'/>"
+ "<INPUT type='email' id='email' value='wyatt@earp.com'/>"
+ "<INPUT type='tel' id='phone' value='650-777-9999'/>"
+ "<INPUT type='submit' value='Send'/>");
+}
+
+// If we have multiple labels per id, the labels concatenated into label string.
+TEST_F(FormAutofillTest, MultipleLabelsPerElement) {
+ std::vector<base::string16> id_attributes, name_attributes, labels, names,
+ values;
+
+ id_attributes.push_back(ASCIIToUTF16("firstname"));
+ name_attributes.push_back(base::string16());
+ labels.push_back(ASCIIToUTF16("First Name:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("John"));
+
+ id_attributes.push_back(ASCIIToUTF16("lastname"));
+ name_attributes.push_back(base::string16());
+ labels.push_back(ASCIIToUTF16("Last Name:"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("Smith"));
+
+ id_attributes.push_back(ASCIIToUTF16("email"));
+ name_attributes.push_back(base::string16());
+ labels.push_back(ASCIIToUTF16("Email: xxx@yyy.com"));
+ names.push_back(id_attributes.back());
+ values.push_back(ASCIIToUTF16("john@example.com"));
+
+ ExpectLabels(
+ "<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <LABEL for='firstname'> First Name: </LABEL>"
+ " <LABEL for='firstname'></LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <LABEL for='lastname'></LABEL>"
+ " <LABEL for='lastname'> Last Name: </LABEL>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='email'> Email: </LABEL>"
+ " <LABEL for='email'> xxx@yyy.com </LABEL>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>",
+ id_attributes, name_attributes, labels, names, values);
+}
+
+TEST_F(FormAutofillTest, ClickElement) {
+ LoadHTML("<BUTTON id='link'>Button</BUTTON>"
+ "<BUTTON name='button'>Button</BUTTON>");
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ // Successful retrieval by id.
+ WebElementDescriptor clicker;
+ clicker.retrieval_method = WebElementDescriptor::ID;
+ clicker.descriptor = "link";
+ EXPECT_TRUE(ClickElement(frame->GetDocument(), clicker));
+
+ // Successful retrieval by css selector.
+ clicker.retrieval_method = WebElementDescriptor::CSS_SELECTOR;
+ clicker.descriptor = "button[name='button']";
+ EXPECT_TRUE(ClickElement(frame->GetDocument(), clicker));
+
+ // Unsuccessful retrieval due to invalid CSS selector.
+ clicker.descriptor = "^*&";
+ EXPECT_FALSE(ClickElement(frame->GetDocument(), clicker));
+
+ // Unsuccessful retrieval because element does not exist.
+ clicker.descriptor = "#junk";
+ EXPECT_FALSE(ClickElement(frame->GetDocument(), clicker));
+}
+
+TEST_F(FormAutofillTest, SelectOneAsText) {
+ LoadHTML("<FORM name='TestForm' action='http://cnn.com' method='post'>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <SELECT id='country'>"
+ " <OPTION value='AF'>Afghanistan</OPTION>"
+ " <OPTION value='AL'>Albania</OPTION>"
+ " <OPTION value='DZ'>Algeria</OPTION>"
+ " </SELECT>"
+ " <INPUT type='submit' name='reply-send' value='Send'/>"
+ "</FORM>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ // Set the value of the select-one.
+ WebSelectElement select_element =
+ frame->GetDocument().GetElementById("country").To<WebSelectElement>();
+ select_element.SetValue(WebString::FromUTF8("AL"));
+
+ WebVector<WebFormElement> forms;
+ frame->GetDocument().Forms(forms);
+ ASSERT_EQ(1U, forms.size());
+
+ FormData form;
+
+ // Extract the country select-one value as text.
+ EXPECT_TRUE(WebFormElementToFormData(
+ forms[0], WebFormControlElement(), nullptr,
+ static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTION_TEXT), &form,
+ nullptr));
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(frame->GetDocument()), form.url);
+ EXPECT_EQ(GURL("http://cnn.com"), form.action);
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("John");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Smith");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("country");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Albania");
+ expected.label.clear();
+ expected.form_control_type = "select-one";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+
+ form.fields.clear();
+ // Extract the country select-one value as value.
+ EXPECT_TRUE(WebFormElementToFormData(forms[0], WebFormControlElement(),
+ nullptr, EXTRACT_VALUE, &form, nullptr));
+ EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name);
+ EXPECT_EQ(GetCanonicalOriginForDocument(frame->GetDocument()), form.url);
+ EXPECT_EQ(GURL("http://cnn.com"), form.action);
+
+ ASSERT_EQ(3U, fields.size());
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("John");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Smith");
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("country");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("AL");
+ expected.label.clear();
+ expected.form_control_type = "select-one";
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+}
+
+TEST_F(FormAutofillTest,
+ UnownedFormElementsAndFieldSetsToFormDataFieldsets) {
+ std::vector<WebElement> fieldsets;
+ std::vector<WebFormControlElement> control_elements;
+
+ const ExtractMask extract_mask =
+ static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS);
+
+ LoadHTML("<HEAD><TITLE>delivery info</TITLE></HEAD>"
+ "<DIV>"
+ " <FIELDSET>"
+ " <LABEL for='firstname'>First name:</LABEL>"
+ " <LABEL for='lastname'>Last name:</LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " </FIELDSET>"
+ " <FIELDSET>"
+ " <LABEL for='email'>Email:</LABEL>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ " </FIELDSET>"
+ "</DIV>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ control_elements = GetUnownedAutofillableFormFieldElements(
+ frame->GetDocument().All(), &fieldsets);
+ ASSERT_EQ(3U, control_elements.size());
+ ASSERT_EQ(2U, fieldsets.size());
+
+ FormData form;
+ EXPECT_TRUE(UnownedCheckoutFormElementsAndFieldSetsToFormData(
+ fieldsets, control_elements, nullptr, frame->GetDocument(), extract_mask,
+ &form, nullptr));
+
+ EXPECT_TRUE(form.name.empty());
+ EXPECT_EQ(GURL(frame->GetDocument().Url()), form.url);
+ EXPECT_FALSE(form.action.is_valid());
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("First name:");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Last name:");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.label = ASCIIToUTF16("Email:");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+}
+
+TEST_F(FormAutofillTest,
+ UnownedFormElementsAndFieldSetsToFormDataControlOutsideOfFieldset) {
+ std::vector<WebElement> fieldsets;
+ std::vector<WebFormControlElement> control_elements;
+
+ const ExtractMask extract_mask =
+ static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS);
+
+ LoadHTML("<HEAD><TITLE>shipping details</TITLE></HEAD>"
+ "<DIV>"
+ " <FIELDSET>"
+ " <LABEL for='firstname'>First name:</LABEL>"
+ " <LABEL for='lastname'>Last name:</LABEL>"
+ " <INPUT type='text' id='firstname' value='John'/>"
+ " <INPUT type='text' id='lastname' value='Smith'/>"
+ " <LABEL for='email'>Email:</LABEL>"
+ " </FIELDSET>"
+ " <INPUT type='text' id='email' value='john@example.com'/>"
+ "</DIV>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ control_elements = GetUnownedAutofillableFormFieldElements(
+ frame->GetDocument().All(), &fieldsets);
+ ASSERT_EQ(3U, control_elements.size());
+ ASSERT_EQ(1U, fieldsets.size());
+
+ FormData form;
+ EXPECT_TRUE(UnownedCheckoutFormElementsAndFieldSetsToFormData(
+ fieldsets, control_elements, nullptr, frame->GetDocument(), extract_mask,
+ &form, nullptr));
+
+ EXPECT_TRUE(form.name.empty());
+ EXPECT_EQ(GURL(frame->GetDocument().Url()), form.url);
+ EXPECT_FALSE(form.action.is_valid());
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ FormFieldData expected;
+ expected.form_control_type = "text";
+ expected.max_length = WebInputElement::DefaultMaxLength();
+
+ expected.id_attribute = ASCIIToUTF16("firstname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("John");
+ expected.label = ASCIIToUTF16("First name:");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
+
+ expected.id_attribute = ASCIIToUTF16("lastname");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("Smith");
+ expected.label = ASCIIToUTF16("Last name:");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
+
+ expected.id_attribute = ASCIIToUTF16("email");
+ expected.name = expected.id_attribute;
+ expected.value = ASCIIToUTF16("john@example.com");
+ expected.label = ASCIIToUTF16("Email:");
+ EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
+}
+
+TEST_F(FormAutofillTest, UnownedFormElementsAndFieldSetsToFormDataWithForm) {
+ std::vector<WebElement> fieldsets;
+ std::vector<WebFormControlElement> control_elements;
+
+ const ExtractMask extract_mask =
+ static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS);
+
+ LoadHTML(kFormHtml);
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ control_elements = GetUnownedAutofillableFormFieldElements(
+ frame->GetDocument().All(), &fieldsets);
+ ASSERT_TRUE(control_elements.empty());
+ ASSERT_TRUE(fieldsets.empty());
+
+ FormData form;
+ EXPECT_FALSE(UnownedCheckoutFormElementsAndFieldSetsToFormData(
+ fieldsets, control_elements, nullptr, frame->GetDocument(), extract_mask,
+ &form, nullptr));
+}
+
+TEST_F(FormAutofillTest, FormlessForms) {
+ std::vector<WebElement> fieldsets;
+ std::vector<WebFormControlElement> control_elements;
+
+ const ExtractMask extract_mask =
+ static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS);
+
+ LoadHTML(kUnownedUntitledFormHtml);
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ control_elements = GetUnownedAutofillableFormFieldElements(
+ frame->GetDocument().All(), &fieldsets);
+ ASSERT_FALSE(control_elements.empty());
+ ASSERT_TRUE(fieldsets.empty());
+
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ features::kAutofillRestrictUnownedFieldsToFormlessCheckout);
+ FormData form;
+ EXPECT_FALSE(UnownedCheckoutFormElementsAndFieldSetsToFormData(
+ fieldsets, control_elements, nullptr, frame->GetDocument(),
+ extract_mask, &form, nullptr));
+ }
+
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(
+ features::kAutofillRestrictUnownedFieldsToFormlessCheckout);
+ FormData form;
+ EXPECT_TRUE(UnownedCheckoutFormElementsAndFieldSetsToFormData(
+ fieldsets, control_elements, nullptr, frame->GetDocument(),
+ extract_mask, &form, nullptr));
+ }
+}
+
+TEST_F(FormAutofillTest, FormCache_ExtractNewForms) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ // Enabled.
+ {kAutofillEnforceMinRequiredFieldsForHeuristics,
+ kAutofillEnforceMinRequiredFieldsForQuery},
+ // Disabled.
+ {kAutofillEnforceMinRequiredFieldsForUpload});
+ struct {
+ const char* description;
+ const char* html;
+ const bool has_extracted_form;
+ const bool is_form_tag;
+ const bool is_formless_checkout;
+ } test_cases[] = {
+ // An empty form should not be extracted
+ {"Empty Form",
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ "</FORM>",
+ false, true, false},
+ // A form with less than three fields with no autocomplete type(s) should
+ // be extracted because no minimum is being enforced for upload.
+ {"Small Form no autocomplete",
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname'/>"
+ "</FORM>",
+ true, true, false},
+ // A form with less than three fields with at least one autocomplete type
+ // should be extracted.
+ {"Small Form w/ autocomplete",
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname' autocomplete='given-name'/>"
+ "</FORM>",
+ true, true, false},
+ // A form with three or more fields should be extracted.
+ {"3 Field Form",
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='text' id='firstname'/>"
+ " <INPUT type='text' id='lastname'/>"
+ " <INPUT type='text' id='email'/>"
+ " <INPUT type='submit' value='Send'/>"
+ "</FORM>",
+ true, true, false},
+ // An input field with an autocomplete attribute outside of a form should
+ // be extracted. The is_formless_checkout attribute should
+ // then be true.
+ {"Small, formless, with autocomplete",
+ "<INPUT type='text' id='firstname' autocomplete='given-name'/>"
+ "<INPUT type='submit' value='Send'/>",
+ true, false, false},
+ // An input field without an autocomplete attribute outside of a form,
+ // with no checkout hints, should not be extracted.
+ {"Small, formless, no autocomplete",
+ "<INPUT type='text' id='firstname'/>"
+ "<INPUT type='submit' value='Send'/>",
+ false, false, false},
+ // A form with one field which is password gets extracted.
+ {"Password-Only",
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='password' id='pw'/>"
+ "</FORM>",
+ true, true, false},
+ // A form with two fields which are passwords should be extracted.
+ {"two passwords",
+ "<FORM name='TestForm' action='http://abc.com' method='post'>"
+ " <INPUT type='password' id='pw'/>"
+ " <INPUT type='password' id='new_pw'/>"
+ "</FORM>",
+ true, true, false},
+ };
+
+ for (auto test_case : test_cases) {
+ SCOPED_TRACE(test_case.description);
+ LoadHTML(test_case.html);
+
+ WebLocalFrame* web_frame = GetMainFrame();
+ ASSERT_NE(nullptr, web_frame);
+
+ FormCache form_cache(web_frame);
+ std::vector<FormData> forms = form_cache.ExtractNewForms();
+ EXPECT_EQ(test_case.has_extracted_form, forms.size() == 1);
+
+ if (test_case.has_extracted_form) {
+ EXPECT_EQ(test_case.is_form_tag, forms[0].is_form_tag);
+ EXPECT_EQ(test_case.is_formless_checkout, forms[0].is_formless_checkout);
+ }
+ }
+}
+
+TEST_F(FormAutofillTest, WebFormElementNotFoundInForm) {
+ LoadHTML(
+ "<form id='form'>"
+ " <input type='text' id='firstname' value='John'>"
+ " <input type='text' id='lastname' value='John'>"
+ "</form>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ WebFormControlElement control_element = frame->GetDocument()
+ .GetElementById("firstname")
+ .To<WebFormControlElement>();
+ ASSERT_FALSE(control_element.IsNull());
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, control_element, nullptr,
+ EXTRACT_NONE, &form, &field));
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(2U, fields.size());
+ EXPECT_EQ(ASCIIToUTF16("firstname"), fields[0].name);
+ EXPECT_EQ(ASCIIToUTF16("firstname"), field.name);
+
+ frame->ExecuteScript(
+ WebString("document.getElementById('firstname').remove();"));
+ EXPECT_FALSE(WebFormElementToFormData(web_form, control_element, nullptr,
+ EXTRACT_NONE, &form, &field));
+}
+
+TEST_F(FormAutofillTest, AriaLabelAndDescription) {
+ LoadHTML(
+ "<form id='form'>"
+ " <div id='label'>aria label</div>"
+ " <div id='description'>aria description</div>"
+ " <input type='text' id='field0' aria-label='inline aria label'>"
+ " <input type='text' id='field1' aria-labelledby='label'>"
+ " <input type='text' id='field2' aria-describedby='description'>"
+ "</form>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ WebFormControlElement control_element =
+ frame->GetDocument().GetElementById("field0").To<WebFormControlElement>();
+ ASSERT_FALSE(control_element.IsNull());
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, control_element, nullptr,
+ EXTRACT_NONE, &form, &field));
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ // Field 0
+ EXPECT_EQ(ASCIIToUTF16("inline aria label"), fields[0].aria_label);
+ EXPECT_EQ(ASCIIToUTF16(""), fields[0].aria_description);
+
+ // Field 1
+ EXPECT_EQ(ASCIIToUTF16("aria label"), fields[1].aria_label);
+ EXPECT_EQ(ASCIIToUTF16(""), fields[1].aria_description);
+
+ // Field 2
+ EXPECT_EQ(ASCIIToUTF16(""), fields[2].aria_label);
+ EXPECT_EQ(ASCIIToUTF16("aria description"), fields[2].aria_description);
+}
+
+TEST_F(FormAutofillTest, AriaLabelAndDescription2) {
+ LoadHTML(
+ "<form id='form'>"
+ " <input type='text' id='field0' aria-label='inline aria label'>"
+ " <input type='text' id='field1' aria-labelledby='label'>"
+ " <input type='text' id='field2' aria-describedby='description'>"
+ "</form>"
+ " <div id='label'>aria label</div>"
+ " <div id='description'>aria description</div>");
+
+ WebLocalFrame* frame = GetMainFrame();
+ ASSERT_NE(nullptr, frame);
+
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById("form").To<WebFormElement>();
+ ASSERT_FALSE(web_form.IsNull());
+
+ WebFormControlElement control_element =
+ frame->GetDocument().GetElementById("field0").To<WebFormControlElement>();
+ ASSERT_FALSE(control_element.IsNull());
+ FormData form;
+ FormFieldData field;
+ EXPECT_TRUE(WebFormElementToFormData(web_form, control_element, nullptr,
+ EXTRACT_NONE, &form, &field));
+
+ const std::vector<FormFieldData>& fields = form.fields;
+ ASSERT_EQ(3U, fields.size());
+
+ // Field 0
+ EXPECT_EQ(ASCIIToUTF16("inline aria label"), fields[0].aria_label);
+ EXPECT_EQ(ASCIIToUTF16(""), fields[0].aria_description);
+
+ // Field 1
+ EXPECT_EQ(ASCIIToUTF16("aria label"), fields[1].aria_label);
+ EXPECT_EQ(ASCIIToUTF16(""), fields[1].aria_description);
+
+ // Field 2
+ EXPECT_EQ(ASCIIToUTF16(""), fields[2].aria_label);
+ EXPECT_EQ(ASCIIToUTF16("aria description"), fields[2].aria_description);
+}
+
+} // namespace form_util
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/form_control_click_detection_browsertest.cc b/chromium/chrome/renderer/autofill/form_control_click_detection_browsertest.cc
new file mode 100644
index 00000000000..2c7ed9d95d3
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/form_control_click_detection_browsertest.cc
@@ -0,0 +1,247 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "content/public/renderer/render_view.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_float_point.h"
+#include "third_party/blink/public/platform/web_size.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_input_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_settings.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "third_party/blink/public/web/web_widget.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace autofill {
+
+class FormControlClickDetectionTest : public ChromeRenderViewTest {
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+ // Must be set before loading HTML.
+ view_->GetWebView()->SetDefaultPageScaleLimits(1, 4);
+
+ LoadHTML(
+ "<form>"
+ " <input type='text' id='text_1'></input><br>"
+ " <input type='text' id='text_2'></input><br>"
+ " <textarea id='textarea_1'></textarea><br>"
+ " <textarea id='textarea_2'></textarea><br>"
+ " <input type='button' id='button'></input><br>"
+ " <input type='button' id='button_2' disabled></input><br>"
+ "</form>");
+ GetWebWidget()->Resize(blink::WebSize(500, 500));
+ GetWebWidget()->SetFocus(true);
+ blink::WebDocument document = GetMainFrame()->GetDocument();
+ text_ = document.GetElementById("text_1");
+ textarea_ = document.GetElementById("textarea_1");
+ ASSERT_FALSE(text_.IsNull());
+ ASSERT_FALSE(textarea_.IsNull());
+
+ // Enable show-ime event when element is focused by indicating that a user
+ // gesture has been processed since load.
+ EXPECT_TRUE(SimulateElementClick("button"));
+ }
+
+ void TearDown() override {
+ text_.Reset();
+ textarea_.Reset();
+ ChromeRenderViewTest::TearDown();
+ }
+
+ void ClearAutofillAgentTestState() {
+ autofill_agent_->last_clicked_form_control_element_for_testing_ =
+ blink::WebFormControlElement();
+ autofill_agent_
+ ->last_clicked_form_control_element_was_focused_for_testing_ = false;
+ }
+
+ const blink::WebFormControlElement& last_clicked_form_control_element()
+ const {
+ return autofill_agent_->last_clicked_form_control_element_for_testing_;
+ }
+
+ bool last_clicked_form_control_element_was_focused() const {
+ return autofill_agent_
+ ->last_clicked_form_control_element_was_focused_for_testing_;
+ }
+
+ bool form_control_element_clicked_called() const {
+ return !last_clicked_form_control_element().IsNull();
+ }
+
+ blink::WebElement text_;
+ blink::WebElement textarea_;
+};
+
+// Tests that a clicked input call is properly handled by AutofillAgent.
+TEST_F(FormControlClickDetectionTest, InputClicked) {
+ ClearAutofillAgentTestState();
+ EXPECT_NE(text_, text_.GetDocument().FocusedElement());
+ // Click the text field once.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+
+ // Click the text field again and verify that AutofillAgent knows about its
+ // focus.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_TRUE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+
+ // Click the button, no notification should happen (this is not a text-input).
+ EXPECT_TRUE(SimulateElementClick("button"));
+ EXPECT_FALSE(form_control_element_clicked_called());
+}
+
+// Tests that AutofillAgent ignores a right click.
+TEST_F(FormControlClickDetectionTest, InputRightClicked) {
+ ClearAutofillAgentTestState();
+ EXPECT_NE(text_, text_.GetDocument().FocusedElement());
+ // Right click the text field once.
+ EXPECT_TRUE(SimulateElementRightClick("text_1"));
+ EXPECT_FALSE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_NE(text_, last_clicked_form_control_element());
+}
+
+TEST_F(FormControlClickDetectionTest, InputFocusedAndClicked) {
+ ClearAutofillAgentTestState();
+ // Focus the text field without a click.
+ ExecuteJavaScriptForTests("document.getElementById('text_1').focus();");
+ EXPECT_FALSE(form_control_element_clicked_called());
+ ClearAutofillAgentTestState();
+
+ // Click the focused text field to test that was_focused_ is set correctly.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_TRUE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+}
+
+// Tests that AutofillAgent accepts form clicks for a textarea element which is
+// clicked.
+TEST_F(FormControlClickDetectionTest, TextAreaClicked) {
+ ClearAutofillAgentTestState();
+ // Click the textarea field once.
+ EXPECT_TRUE(SimulateElementClick("textarea_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(textarea_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+
+ // Click the text field again and verify that AutofillAgent knows about its
+ // focus.
+ EXPECT_TRUE(SimulateElementClick("textarea_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_TRUE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(textarea_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+
+ // Click the button, no notification should happen (this is not a text-input).
+ EXPECT_TRUE(SimulateElementClick("button"));
+ EXPECT_FALSE(form_control_element_clicked_called());
+}
+
+TEST_F(FormControlClickDetectionTest, TextAreaFocusedAndClicked) {
+ ClearAutofillAgentTestState();
+ // Focus the textarea without a click.
+ ExecuteJavaScriptForTests("document.getElementById('textarea_1').focus();");
+ EXPECT_FALSE(form_control_element_clicked_called());
+ ClearAutofillAgentTestState();
+
+ // Click the text field again and verify that AutofillAgent knows about its
+ // focus.
+ EXPECT_TRUE(SimulateElementClick("textarea_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_TRUE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(textarea_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+}
+
+TEST_F(FormControlClickDetectionTest, ScaledTextareaClicked) {
+ ClearAutofillAgentTestState();
+ EXPECT_NE(textarea_, textarea_.GetDocument().FocusedElement());
+ view_->GetWebView()->SetPageScaleFactor(3);
+ view_->GetWebView()->SetVisualViewportOffset(blink::WebFloatPoint(50, 50));
+
+ // Click textarea_1.
+ SimulatePointClick(gfx::Point(30, 30));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(textarea_, last_clicked_form_control_element());
+}
+
+TEST_F(FormControlClickDetectionTest, ScaledTextareaTapped) {
+ ClearAutofillAgentTestState();
+ EXPECT_NE(textarea_, textarea_.GetDocument().FocusedElement());
+ view_->GetWebView()->SetPageScaleFactor(3);
+ view_->GetWebView()->SetVisualViewportOffset(blink::WebFloatPoint(50, 50));
+
+ // Tap textarea_1.
+ SimulateRectTap(gfx::Rect(30, 30, 30, 30));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(textarea_, last_clicked_form_control_element());
+}
+
+TEST_F(FormControlClickDetectionTest, DisabledInputClickedNoEvent) {
+ ClearAutofillAgentTestState();
+ EXPECT_NE(text_, text_.GetDocument().FocusedElement());
+ // Click the text field once.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+
+ // Click the disabled element.
+ EXPECT_TRUE(SimulateElementClick("button_2"));
+ EXPECT_FALSE(form_control_element_clicked_called());
+}
+
+TEST_F(FormControlClickDetectionTest,
+ ClickDisabledInputDoesNotResetClickCounter) {
+ EXPECT_NE(text_, text_.GetDocument().FocusedElement());
+ // Click the text field once.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+ ClearAutofillAgentTestState();
+
+ // Click the disabled element.
+ EXPECT_TRUE(SimulateElementClick("button_2"));
+ EXPECT_FALSE(form_control_element_clicked_called());
+ ClearAutofillAgentTestState();
+
+ // Click the text field second time. AutofillClient should know that this is
+ // the second click.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_TRUE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+}
+
+TEST_F(FormControlClickDetectionTest, TapNearEdgeIsPageClick) {
+ EXPECT_NE(text_, text_.GetDocument().FocusedElement());
+ // Tap outside of element bounds, but tap width is overlapping the field.
+ gfx::Rect element_bounds = GetElementBounds("text_1");
+ SimulateRectTap(element_bounds -
+ gfx::Vector2d(element_bounds.width() / 2 + 1, 0));
+ EXPECT_TRUE(form_control_element_clicked_called());
+ EXPECT_FALSE(last_clicked_form_control_element_was_focused());
+ EXPECT_EQ(text_, last_clicked_form_control_element());
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/page_passwords_analyser_browsertest.cc b/chromium/chrome/renderer/autofill/page_passwords_analyser_browsertest.cc
new file mode 100644
index 00000000000..1af696b11ac
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/page_passwords_analyser_browsertest.cc
@@ -0,0 +1,279 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/content/renderer/page_passwords_analyser.h"
+
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/autofill/content/renderer/page_form_analyser_logger.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element_collection.h"
+#include "third_party/blink/public/web/web_form_element.h"
+
+namespace autofill {
+
+namespace {
+
+class MockPageFormAnalyserLogger : public PageFormAnalyserLogger {
+ public:
+ MockPageFormAnalyserLogger() : PageFormAnalyserLogger(nullptr) {}
+ virtual ~MockPageFormAnalyserLogger() {}
+
+ void Send(std::string message,
+ ConsoleLevel level,
+ blink::WebNode node) override {
+ Send(std::move(message), level,
+ std::vector<blink::WebNode>{std::move(node)});
+ }
+
+ MOCK_METHOD3(Send,
+ void(std::string message,
+ ConsoleLevel level,
+ std::vector<blink::WebNode> nodes));
+
+ MOCK_METHOD0(Flush, void());
+};
+
+const char kExpectedDocumentationLink[] = " (More info: https://goo.gl/9p2vKq)";
+
+const char kPasswordFieldNotInForm[] =
+ "<input type='password' autocomplete='new-password'>";
+
+const char kPasswordFormWithoutUsernameField[] =
+ "<form>"
+ " <input type='password' autocomplete='new-password'>"
+ "</form>";
+
+const char kElementsWithDuplicateIds[] =
+ "<input id='duplicate'>"
+ "<input id='duplicate'>";
+
+const char kPasswordFormTooComplex[] =
+ "<form>"
+ " <input type='text' autocomplete='username'>"
+ " <input type='password' autocomplete='current-password'>"
+ " <input type='text' autocomplete='username'>"
+ " <input type='password' autocomplete='current-password'>"
+ " <input type='password' autocomplete='new-password'>"
+ " <input type='password' autocomplete='new-password'>"
+ "</form>";
+
+const char kInferredPasswordAutocompleteAttributes[] =
+ // Login form.
+ "<form>"
+ " <input type='text'>"
+ " <input type='password'>"
+ "</form>"
+ // Registration form.
+ "<form>"
+ " <input type='text'>"
+ " <input type='password'>"
+ " <input type='password'>"
+ "</form>"
+ // Change password form.
+ "<form>"
+ " <input type='text'>"
+ " <input type='password'>"
+ " <input type='password'>"
+ " <input type='password'>"
+ "</form>";
+
+const char kInferredUsernameAutocompleteAttributes[] =
+ // Login form.
+ "<form>"
+ " <input type='text'>"
+ " <input type='password' autocomplete='current-password'>"
+ "</form>"
+ // Registration form.
+ "<form>"
+ " <input type='text'>"
+ " <input type='password' autocomplete='new-password'>"
+ " <input type='password' autocomplete='new-password'>"
+ "</form>"
+ // Change password form with username.
+ "<form>"
+ " <input type='text'>"
+ " <input type='password' autocomplete='current-password'>"
+ " <input type='password' autocomplete='new-password'>"
+ " <input type='password' autocomplete='new-password'>"
+ "</form>";
+
+const char kPasswordFieldsWithAndWithoutAutocomplete[] =
+ "<form>"
+ " <input type='password'>"
+ " <input type='text'>"
+ " <input type='password' autocomplete='current-password'>"
+ "</form>";
+
+const std::string AutocompleteSuggestionString(const std::string& suggestion) {
+ return "Input elements should have autocomplete "
+ "attributes (suggested: \"" +
+ suggestion + "\"):";
+}
+
+} // namespace
+
+class PagePasswordsAnalyserTest : public ChromeRenderViewTest {
+ protected:
+ PagePasswordsAnalyserTest()
+ : mock_logger_(new MockPageFormAnalyserLogger()) {}
+
+ void TearDown() override {
+ elements_.clear();
+ mock_logger_.reset();
+ page_passwords_analyser.Reset();
+ ChromeRenderViewTest::TearDown();
+ }
+
+ void LoadTestCase(const char* html) {
+ elements_.clear();
+ LoadHTML(html);
+ blink::WebLocalFrame* frame = GetMainFrame();
+ blink::WebElementCollection collection = frame->GetDocument().All();
+ for (blink::WebElement element = collection.FirstItem(); !element.IsNull();
+ element = collection.NextItem()) {
+ elements_.push_back(element);
+ }
+ // Remove the <html>, <head> and <body> elements.
+ elements_.erase(elements_.begin(), elements_.begin() + 3);
+ }
+
+ void Expect(const std::string& message,
+ const ConsoleLevel level,
+ const std::vector<size_t>& element_indices) {
+ std::vector<blink::WebNode> nodes;
+ std::string documented = message + kExpectedDocumentationLink;
+ for (size_t index : element_indices)
+ nodes.push_back(elements_[index]);
+ EXPECT_CALL(*mock_logger_, Send(documented, level, nodes))
+ .RetiresOnSaturation();
+ }
+
+ void RunTestCase() {
+ EXPECT_CALL(*mock_logger_, Flush());
+ page_passwords_analyser.AnalyseDocumentDOM(GetMainFrame(),
+ mock_logger_.get());
+ }
+
+ PagePasswordsAnalyser page_passwords_analyser;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PagePasswordsAnalyserTest);
+
+ std::vector<blink::WebElement> elements_;
+ std::unique_ptr<MockPageFormAnalyserLogger> mock_logger_;
+};
+
+TEST_F(PagePasswordsAnalyserTest, PasswordFieldNotInForm) {
+ LoadTestCase(kPasswordFieldNotInForm);
+
+ Expect("Password field is not contained in a form:",
+ PageFormAnalyserLogger::kVerbose, {0});
+
+ RunTestCase();
+}
+
+TEST_F(PagePasswordsAnalyserTest, PasswordFormWithoutUsernameField) {
+ LoadTestCase(kPasswordFormWithoutUsernameField);
+
+ Expect(
+ "Password forms should have (optionally hidden) "
+ "username fields for accessibility:",
+ PageFormAnalyserLogger::kVerbose, {0});
+
+ RunTestCase();
+}
+
+TEST_F(PagePasswordsAnalyserTest, ElementsWithDuplicateIds) {
+ LoadTestCase(kElementsWithDuplicateIds);
+
+ Expect("Found 2 elements with non-unique id #duplicate:",
+ PageFormAnalyserLogger::kWarning, {0, 1});
+
+ RunTestCase();
+}
+
+TEST_F(PagePasswordsAnalyserTest, PasswordFormTooComplex) {
+ LoadTestCase(kPasswordFormTooComplex);
+
+ Expect(
+ "Multiple forms should be contained in their own "
+ "form elements; break up complex forms into ones that represent a "
+ "single action:",
+ PageFormAnalyserLogger::kVerbose, {0});
+
+ RunTestCase();
+}
+
+TEST_F(PagePasswordsAnalyserTest, InferredPasswordAutocompleteAttributes) {
+ LoadTestCase(kInferredPasswordAutocompleteAttributes);
+ size_t element_index = 0;
+
+ // Login form.
+ element_index++; // Skip form element.
+ element_index++; // Skip username field.
+ Expect(AutocompleteSuggestionString("current-password"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+
+ // Registration form.
+ element_index++; // Skip form element.
+ element_index++; // Skip username field.
+ Expect(AutocompleteSuggestionString("new-password"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+ Expect(AutocompleteSuggestionString("new-password"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+
+ // Change password form.
+ element_index++; // Skip form element.
+ element_index++; // Skip username field.
+ Expect(AutocompleteSuggestionString("current-password"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+ Expect(AutocompleteSuggestionString("new-password"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+ Expect(AutocompleteSuggestionString("new-password"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+
+ RunTestCase();
+}
+
+TEST_F(PagePasswordsAnalyserTest, InferredUsernameAutocompleteAttributes) {
+ LoadTestCase(kInferredUsernameAutocompleteAttributes);
+ size_t element_index = 0;
+
+ // Login form.
+ element_index++; // Skip form element.
+ Expect(AutocompleteSuggestionString("username"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+ element_index++; // Skip already annotated password field.
+
+ // Registration form.
+ element_index++; // Skip form element.
+ Expect(AutocompleteSuggestionString("username"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+ element_index++; // Skip already annotated password field.
+ element_index++; // Skip already annotated password field.
+
+ // Change password form with username.
+ element_index++; // Skip form element.
+ Expect(AutocompleteSuggestionString("username"),
+ PageFormAnalyserLogger::kVerbose, {element_index++});
+ element_index++; // Skip already annotated password field.
+ element_index++; // Skip already annotated password field.
+ element_index++; // Skip already annotated password field.
+
+ RunTestCase();
+}
+
+TEST_F(PagePasswordsAnalyserTest, PasswordFieldWithAndWithoutAutocomplete) {
+ LoadTestCase(kPasswordFieldsWithAndWithoutAutocomplete);
+ Expect(
+ "Multiple forms should be contained in their own "
+ "form elements; break up complex forms into ones that represent a "
+ "single action:",
+ PageFormAnalyserLogger::kVerbose, {0});
+ RunTestCase();
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chromium/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
new file mode 100644
index 00000000000..b9a43b77f2b
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -0,0 +1,3811 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/content/renderer/password_autofill_agent.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/renderer/autofill/fake_mojo_password_manager_driver.h"
+#include "chrome/renderer/autofill/fake_password_generation_driver.h"
+#include "chrome/renderer/autofill/password_generation_test_utils.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "components/autofill/content/renderer/form_autofill_util.h"
+#include "components/autofill/content/renderer/form_tracker.h"
+#include "components/autofill/content/renderer/password_generation_agent.h"
+#include "components/autofill/content/renderer/test_password_autofill_agent.h"
+#include "components/autofill/core/common/autofill_constants.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_switches.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
+#include "components/autofill/core/common/password_form.h"
+#include "components/password_manager/core/common/password_manager_features.h"
+#include "components/safe_browsing/buildflags.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/test/browser_test_utils.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_widget.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+#if defined(OS_ANDROID)
+#include "components/autofill/core/common/autofill_features.h"
+#endif
+
+#if defined(OS_WIN)
+#include "third_party/blink/public/web/win/web_font_rendering.h"
+#endif
+
+using autofill::FormTracker;
+using autofill::PasswordForm;
+using autofill::mojom::FocusedFieldType;
+using autofill::mojom::PasswordFormFieldPredictionType;
+using autofill::mojom::SubmissionIndicatorEvent;
+using base::ASCIIToUTF16;
+using base::UTF16ToUTF8;
+using blink::WebAutofillState;
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebFormElement;
+using blink::WebFrame;
+using blink::WebInputElement;
+using blink::WebLocalFrame;
+using blink::WebString;
+using testing::_;
+using testing::AtMost;
+using testing::Eq;
+using testing::Truly;
+
+namespace {
+
+// The name of the username/password element in the form.
+const char kUsernameName[] = "username";
+const char kPasswordName[] = "password";
+const char kSearchField[] = "search";
+const char kSocialMediaTextArea[] = "new_chirp";
+
+const char kAliceUsername[] = "alice";
+const char kAlicePassword[] = "password";
+const char kBobUsername[] = "bob";
+const char kBobPassword[] = "secret";
+const char kCarolUsername[] = "Carol";
+const char kCarolPassword[] = "test";
+const char kCarolAlternateUsername[] = "RealCarolUsername";
+
+const char kFormHTML[] =
+ "<FORM id='LoginTestForm' action='http://www.bidule.com'>"
+ " <INPUT type='text' id='random_field'/>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+
+const char kSocialNetworkPostFormHTML[] =
+ "<FORM id='SocialMediaPostForm' action='http://www.chirper.com'>"
+ " <TEXTAREA id='new_chirp'>"
+ " </TEXTAREA>"
+ " <INPUT type='submit' value='Chirp'/>"
+ "</FORM>";
+
+const char kSearchFieldHTML[] =
+ "<FORM id='SearchFieldForm' action='http://www.gewgle.de'>"
+ " <INPUT type='search' id='search'/>"
+ " <INPUT type='submit' value='Chirp'/>"
+ "</FORM>";
+
+const char kVisibleFormWithNoUsernameHTML[] =
+ "<head> <style> form {display: inline;} </style> </head>"
+ "<body>"
+ " <form name='LoginTestForm' action='http://www.bidule.com'>"
+ " <div>"
+ " <input type='password' id='password'/>"
+ " </div>"
+ " </form>"
+ "</body>";
+
+const char kEmptyFormHTML[] =
+ "<head> <style> form {display: inline;} </style> </head>"
+ "<body> <form> </form> </body>";
+
+const char kFormWithoutPasswordsHTML[] =
+ "<FORM>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='text' id='random_field'/>"
+ "</FORM>";
+
+const char kNonVisibleFormHTML[] =
+ "<head> <style> form {visibility: hidden;} </style> </head>"
+ "<body>"
+ " <form>"
+ " <div>"
+ " <input type='password' id='password'/>"
+ " </div>"
+ " </form>"
+ "</body>";
+
+const char kNonDisplayedFormHTML[] =
+ "<head> <style> form {display: none;} </style> </head>"
+ "<body>"
+ " <form>"
+ " <div>"
+ " <input type='password' id='password'/>"
+ " </div>"
+ " </form>"
+ "</body>";
+
+const char kSignupFormHTML[] =
+ "<FORM id='LoginTestForm' name='LoginTestForm' "
+ " action='http://www.bidule.com'>"
+ " <INPUT type='text' id='random_info'/>"
+ " <INPUT type='password' id='new_password'/>"
+ " <INPUT type='password' id='confirm_password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+
+const char kEmptyWebpage[] =
+ "<html>"
+ " <head>"
+ " </head>"
+ " <body>"
+ " </body>"
+ "</html>";
+
+const char kRedirectionWebpage[] =
+ "<html>"
+ " <head>"
+ " <meta http-equiv='Content-Type' content='text/html'>"
+ " <title>Redirection page</title>"
+ " <script></script>"
+ " </head>"
+ " <body>"
+ " <script type='text/javascript'>"
+ " function test(){}"
+ " </script>"
+ " </body>"
+ "</html>";
+
+const char kSimpleWebpage[] =
+ "<html>"
+ " <head>"
+ " <meta charset='utf-8' />"
+ " <title>Title</title>"
+ " </head>"
+ " <body>"
+ " <form name='LoginTestForm'>"
+ " <input type='text' id='username'/>"
+ " <input type='password' id='password'/>"
+ " <input type='submit' value='Login'/>"
+ " </form>"
+ " </body>"
+ "</html>";
+
+const char kWebpageWithDynamicContent[] =
+ "<html>"
+ " <head>"
+ " <meta charset='utf-8' />"
+ " <title>Title</title>"
+ " </head>"
+ " <body>"
+ " <script type='text/javascript'>"
+ " function addParagraph() {"
+ " var p = document.createElement('p');"
+ " document.body.appendChild(p);"
+ " }"
+ " window.onload = addParagraph;"
+ " </script>"
+ " </body>"
+ "</html>";
+
+const char kJavaScriptClick[] =
+ "var event = new MouseEvent('click', {"
+ " 'view': window,"
+ " 'bubbles': true,"
+ " 'cancelable': true"
+ "});"
+ "var form = document.getElementById('myform1');"
+ "form.dispatchEvent(event);"
+ "console.log('clicked!');";
+
+const char kPasswordChangeFormHTML[] =
+ "<FORM name='ChangeWithUsernameForm' action='http://www.bidule.com'>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>"
+ " <INPUT type='password' id='newpassword'/>"
+ " <INPUT type='password' id='confirmpassword'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+
+const char kCreditCardFormHTML[] =
+ "<FORM name='ChangeWithUsernameForm' action='http://www.bidule.com'>"
+ " <INPUT type='text' id='creditcardowner'/>"
+ " <INPUT type='text' id='creditcardnumber'/>"
+ " <INPUT type='password' id='cvc'/>"
+ " <INPUT type='submit' value='Submit'/>"
+ "</FORM>";
+
+const char kNoFormHTML[] =
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>";
+
+const char kTwoNoUsernameFormsHTML[] =
+ "<FORM name='form1' action='http://www.bidule.com'>"
+ " <INPUT type='password' id='password1' name='password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>"
+ "<FORM name='form2' action='http://www.bidule.com'>"
+ " <INPUT type='password' id='password2' name='password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+
+const char kDivWrappedFormHTML[] =
+ "<DIV id='outer'>"
+ " <DIV id='inner'>"
+ " <FORM id='form' action='http://www.bidule.com'>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>"
+ " </FORM>"
+ " </DIV>"
+ "</DIV>";
+
+// Sets the "readonly" attribute of |element| to the value given by |read_only|.
+void SetElementReadOnly(WebInputElement& element, bool read_only) {
+ element.SetAttribute(WebString::FromUTF8("readonly"),
+ read_only ? WebString::FromUTF8("true") : WebString());
+}
+
+bool FormHasFieldWithValue(const autofill::FormData& form,
+ const std::string& value) {
+ base::string16 value_16 = ASCIIToUTF16(value);
+ for (const auto& field : form.fields) {
+ if (field.value == value_16)
+ return true;
+ if (field.typed_value == value_16)
+ return true;
+ }
+ return false;
+}
+
+enum PasswordFormSourceType {
+ PasswordFormSubmitted,
+ PasswordFormSameDocumentNavigation,
+};
+
+enum class FieldChangeSource { USER, AUTOFILL, USER_AUTOFILL };
+
+} // namespace
+
+namespace autofill {
+
+class PasswordAutofillAgentTest : public ChromeRenderViewTest {
+ public:
+ PasswordAutofillAgentTest() {}
+
+ // Simulates the fill password form message being sent to the renderer.
+ // We use that so we don't have to make RenderView::OnFillPasswordForm()
+ // protected.
+ void SimulateOnFillPasswordForm(const PasswordFormFillData& fill_data) {
+ password_autofill_agent_->FillPasswordForm(fill_data);
+ }
+
+ void SendVisiblePasswordForms() {
+ static_cast<content::RenderFrameObserver*>(password_autofill_agent_)
+ ->DidFinishLoad();
+ }
+
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+#if defined(OS_WIN)
+ // Autofill uses the system font to render suggestion previews. On Windows
+ // an extra step is required to ensure that the system font is configured.
+ blink::WebFontRendering::SetMenuFontMetrics(
+ base::ASCIIToUTF16("Arial").c_str(), 12);
+#endif
+
+ // TODO(crbug/862989): Remove workaround preventing non-test classes to bind
+ // fake_driver_ or fake_pw_client_.
+ password_autofill_agent_->GetPasswordManagerDriver();
+ password_generation_->RequestPasswordManagerClientForTesting();
+ base::RunLoop().RunUntilIdle(); // Executes binding the interfaces.
+ // Reject all requests to bind driver/client to anything but the test class:
+ view_->GetMainRenderFrame()
+ ->GetRemoteAssociatedInterfaces()
+ ->OverrideBinderForTesting(
+ mojom::PasswordGenerationDriver::Name_,
+ base::BindRepeating([](mojo::ScopedInterfaceEndpointHandle handle) {
+ handle.reset();
+ }));
+ view_->GetMainRenderFrame()
+ ->GetRemoteAssociatedInterfaces()
+ ->OverrideBinderForTesting(
+ mojom::PasswordManagerDriver::Name_,
+ base::BindRepeating([](mojo::ScopedInterfaceEndpointHandle handle) {
+ handle.reset();
+ }));
+
+ // Add a preferred login and an additional login to the FillData.
+ username1_ = ASCIIToUTF16(kAliceUsername);
+ password1_ = ASCIIToUTF16(kAlicePassword);
+ username2_ = ASCIIToUTF16(kBobUsername);
+ password2_ = ASCIIToUTF16(kBobPassword);
+ username3_ = ASCIIToUTF16(kCarolUsername);
+ password3_ = ASCIIToUTF16(kCarolPassword);
+ alternate_username3_ = ASCIIToUTF16(kCarolAlternateUsername);
+
+ FormFieldData username_field;
+ username_field.name = ASCIIToUTF16(kUsernameName);
+ username_field.value = username1_;
+ fill_data_.username_field = username_field;
+
+ FormFieldData password_field;
+ password_field.name = ASCIIToUTF16(kPasswordName);
+ password_field.value = password1_;
+ password_field.form_control_type = "password";
+ fill_data_.password_field = password_field;
+
+ PasswordAndRealm password2;
+ password2.password = password2_;
+ fill_data_.additional_logins[username2_] = password2;
+ PasswordAndRealm password3;
+ password3.password = password3_;
+ fill_data_.additional_logins[username3_] = password3;
+
+ // We need to set the origin so it matches the frame URL and the action so
+ // it matches the form action, otherwise we won't autocomplete.
+ UpdateOriginForHTML(kFormHTML);
+ fill_data_.action = GURL("http://www.bidule.com");
+
+ LoadHTML(kFormHTML);
+
+ // Necessary for SimulateElementClick() to work correctly.
+ GetWebWidget()->Resize(blink::WebSize(500, 500));
+ GetWebWidget()->SetFocus(true);
+
+ // Now retrieve the input elements so the test can access them.
+ UpdateUsernameAndPasswordElements();
+ }
+
+ void TearDown() override {
+ username_element_.Reset();
+ password_element_.Reset();
+ ChromeRenderViewTest::TearDown();
+ }
+
+ void RegisterMainFrameRemoteInterfaces() override {
+ // Because the test cases only involve the main frame in this test,
+ // the fake password client and the fake driver is only used on main frame.
+ blink::AssociatedInterfaceProvider* remote_associated_interfaces =
+ view_->GetMainRenderFrame()->GetRemoteAssociatedInterfaces();
+ remote_associated_interfaces->OverrideBinderForTesting(
+ mojom::PasswordGenerationDriver::Name_,
+ base::BindRepeating(
+ &PasswordAutofillAgentTest::BindPasswordManagerClient,
+ base::Unretained(this)));
+ remote_associated_interfaces->OverrideBinderForTesting(
+ mojom::PasswordManagerDriver::Name_,
+ base::BindRepeating(
+ &PasswordAutofillAgentTest::BindPasswordManagerDriver,
+ base::Unretained(this)));
+ }
+
+ void FocusElement(std::string element_id) {
+ std::string script =
+ "document.getElementById('" + element_id + "').focus()";
+ ExecuteJavaScriptForTests(script.c_str());
+ GetMainFrame()->AutofillClient()->DidCompleteFocusChangeInFrame();
+ }
+
+ void SetFillOnAccountSelect() {
+ scoped_feature_list_.InitAndEnableFeature(
+ password_manager::features::kFillOnAccountSelect);
+ }
+
+ void EnableShowAutofillSignatures() {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kShowAutofillSignatures);
+ }
+
+ void UpdateOriginForHTML(const std::string& html) {
+ std::string origin = "data:text/html;charset=utf-8," + html;
+ fill_data_.origin = GURL(origin);
+ }
+
+ void UpdateRendererIDs() {
+ fill_data_.has_renderer_ids = true;
+ if (!username_element_.IsNull()) {
+ fill_data_.username_field.unique_renderer_id =
+ username_element_.UniqueRendererFormControlId();
+ }
+ ASSERT_FALSE(password_element_.IsNull());
+ fill_data_.password_field.unique_renderer_id =
+ password_element_.UniqueRendererFormControlId();
+ WebFormElement form = password_element_.Form();
+ fill_data_.form_renderer_id = form.IsNull()
+ ? FormData::kNotSetFormRendererId
+ : form.UniqueRendererFormId();
+ }
+
+ void UpdateUsernameAndPasswordElements() {
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8(kUsernameName));
+ ASSERT_FALSE(element.IsNull());
+ username_element_ = element.To<WebInputElement>();
+ element = document.GetElementById(WebString::FromUTF8(kPasswordName));
+ ASSERT_FALSE(element.IsNull());
+ password_element_ = element.To<WebInputElement>();
+ UpdateRendererIDs();
+ }
+
+ void UpdateOnlyPasswordElement() {
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8(kPasswordName));
+ ASSERT_FALSE(element.IsNull());
+ password_element_ = element.To<WebInputElement>();
+ username_element_.Reset();
+ UpdateRendererIDs();
+ }
+
+ WebInputElement GetInputElementByID(const std::string& id) {
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8(id.c_str()));
+ return element.To<WebInputElement>();
+ }
+
+ void ClearUsernameAndPasswordFields() {
+ username_element_.SetValue(WebString());
+ username_element_.SetSuggestedValue(WebString());
+ username_element_.SetAutofillState(WebAutofillState::kNotFilled);
+ password_element_.SetValue(WebString());
+ password_element_.SetSuggestedValue(WebString());
+ password_element_.SetAutofillState(WebAutofillState::kNotFilled);
+ UpdateRendererIDs();
+ }
+
+ void SimulateSuggestionChoice(WebInputElement& username_input) {
+ base::string16 username(ASCIIToUTF16(kAliceUsername));
+ base::string16 password(ASCIIToUTF16(kAlicePassword));
+ SimulateSuggestionChoiceOfUsernameAndPassword(username_input, username,
+ password);
+ }
+
+ void SimulateSuggestionChoiceOfUsernameAndPassword(
+ WebInputElement& input,
+ const base::string16& username,
+ const base::string16& password) {
+ // This call is necessary to setup the autofill agent appropriate for the
+ // user selection; simulates the menu actually popping up.
+ SimulatePointClick(gfx::Point(1, 1));
+ autofill_agent_->FormControlElementClicked(input, false);
+
+ autofill_agent_->FillPasswordSuggestion(username, password);
+ }
+
+ void SimulateUsernameTyping(const std::string& username) {
+ SimulatePointClick(gfx::Point(1, 1));
+ SimulateUserInputChangeForElement(&username_element_, username);
+ }
+
+ void SimulatePasswordTyping(const std::string& password) {
+ SimulateUserInputChangeForElement(&password_element_, password);
+ }
+
+ void SimulateUsernameFieldAutofill(const std::string& text) {
+ // Simulate set |username_element_| in focus.
+ static_cast<content::RenderFrameObserver*>(autofill_agent_)
+ ->FocusedElementChanged(username_element_);
+ // Fill focused element (i.e. |username_element_|).
+ autofill_agent_->FillFieldWithValue(ASCIIToUTF16(text));
+ }
+
+ void SimulateUsernameFieldChange(FieldChangeSource change_source) {
+ switch (change_source) {
+ case FieldChangeSource::USER:
+ SimulateUsernameTyping("Alice");
+ break;
+ case FieldChangeSource::AUTOFILL:
+ SimulateUsernameFieldAutofill("Alice");
+ break;
+ case FieldChangeSource::USER_AUTOFILL:
+ SimulateUsernameTyping("A");
+ SimulateUsernameFieldAutofill("Alice");
+ break;
+ }
+ }
+
+ void CheckTextFieldsStateForElements(const WebInputElement& username_element,
+ const std::string& username,
+ bool username_autofilled,
+ const WebInputElement& password_element,
+ const std::string& password,
+ bool password_autofilled,
+ bool check_suggested_username,
+ bool check_suggested_password) {
+ EXPECT_EQ(username, check_suggested_username
+ ? username_element.SuggestedValue().Utf8()
+ : username_element.Value().Utf8())
+ << "check_suggested_username == " << check_suggested_username;
+ EXPECT_EQ(username_autofilled, username_element.IsAutofilled());
+
+ EXPECT_EQ(password, check_suggested_password
+ ? password_element.SuggestedValue().Utf8()
+ : password_element.Value().Utf8())
+ << "check_suggested_password == " << check_suggested_password;
+ EXPECT_EQ(password_autofilled, password_element.IsAutofilled());
+ }
+
+ // Checks the DOM-accessible value of the username element and the
+ // *suggested* value of the password element.
+ void CheckUsernameDOMStatePasswordSuggestedState(const std::string& username,
+ bool username_autofilled,
+ const std::string& password,
+ bool password_autofilled) {
+ CheckTextFieldsStateForElements(
+ username_element_, username, username_autofilled, password_element_,
+ password, password_autofilled, false /* check_suggested_username */,
+ true /* check_suggested_password */);
+ }
+
+ // Checks the DOM-accessible value of the username element and the
+ // DOM-accessible value of the password element.
+ void CheckTextFieldsDOMState(const std::string& username,
+ bool username_autofilled,
+ const std::string& password,
+ bool password_autofilled) {
+ CheckTextFieldsStateForElements(
+ username_element_, username, username_autofilled, password_element_,
+ password, password_autofilled, false /* check_suggested_username */,
+ false /* check_suggested_password */);
+ }
+
+ // Checks the suggested values of the |username| and |password| elements.
+ void CheckTextFieldsSuggestedState(const std::string& username,
+ bool username_autofilled,
+ const std::string& password,
+ bool password_autofilled) {
+ CheckTextFieldsStateForElements(
+ username_element_, username, username_autofilled, password_element_,
+ password, password_autofilled, true /* check_suggested_username */,
+ true /* check_suggested_password */);
+ }
+
+ void ResetFieldState(
+ WebInputElement* element,
+ const std::string& value = std::string(),
+ blink::WebAutofillState is_autofilled = WebAutofillState::kNotFilled) {
+ element->SetValue(WebString::FromUTF8(value));
+ element->SetSuggestedValue(WebString());
+ element->SetAutofillState(is_autofilled);
+ element->SetSelectionRange(value.size(), value.size());
+ }
+
+ void CheckUsernameSelection(int start, int end) {
+ EXPECT_EQ(start, username_element_.SelectionStart());
+ EXPECT_EQ(end, username_element_.SelectionEnd());
+ }
+
+ // Checks the message sent to PasswordAutofillManager to build the suggestion
+ // list. |username| is the expected username field value, and |show_all| is
+ // the expected flag for the PasswordAutofillManager, whether to show all
+ // suggestions, or only those starting with |username|.
+ void CheckSuggestions(const std::string& username, bool show_all) {
+ auto show_all_matches = [show_all](int options) {
+ return show_all == !!(options & autofill::SHOW_ALL);
+ };
+
+ EXPECT_CALL(fake_driver_,
+ ShowPasswordSuggestions(_, Eq(ASCIIToUTF16(username)),
+ Truly(show_all_matches), _));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void ExpectFieldPropertiesMasks(
+ PasswordFormSourceType expected_type,
+ const std::map<base::string16, FieldPropertiesMask>&
+ expected_properties_masks,
+ autofill::mojom::SubmissionIndicatorEvent expected_submission_event) {
+ base::RunLoop().RunUntilIdle();
+ autofill::PasswordForm form;
+ if (expected_type == PasswordFormSubmitted) {
+ ASSERT_TRUE(fake_driver_.called_password_form_submitted());
+ ASSERT_TRUE(static_cast<bool>(fake_driver_.password_form_submitted()));
+ form = *(fake_driver_.password_form_submitted());
+ } else {
+ ASSERT_EQ(PasswordFormSameDocumentNavigation, expected_type);
+ ASSERT_TRUE(fake_driver_.called_same_document_navigation());
+ ASSERT_TRUE(
+ static_cast<bool>(fake_driver_.password_form_maybe_submitted()));
+ form = *(fake_driver_.password_form_maybe_submitted());
+ EXPECT_EQ(expected_submission_event, form.submission_event);
+ EXPECT_EQ(expected_submission_event, form.form_data.submission_event);
+ }
+
+ size_t unchecked_masks = expected_properties_masks.size();
+ for (const FormFieldData& field : form.form_data.fields) {
+ const auto& it = expected_properties_masks.find(field.name);
+ if (it == expected_properties_masks.end())
+ continue;
+ EXPECT_EQ(field.properties_mask, it->second)
+ << "Wrong mask for the field " << field.name;
+ unchecked_masks--;
+ }
+ EXPECT_TRUE(unchecked_masks == 0)
+ << "Some expected masks are missed in FormData";
+ }
+
+ uint32_t GetFormUniqueRendererId(const WebString& form_id) {
+ WebLocalFrame* frame = GetMainFrame();
+ if (!frame)
+ return FormData::kNotSetFormRendererId;
+ WebFormElement web_form =
+ frame->GetDocument().GetElementById(form_id).To<WebFormElement>();
+ return web_form.UniqueRendererFormId();
+ }
+
+ void ExpectFormWithUsernameAndPasswordsAndEvent(
+ const autofill::PasswordForm& form,
+ uint32_t form_rendere_id,
+ const std::string& username_value,
+ const std::string& password_value,
+ const std::string& new_password_value,
+ SubmissionIndicatorEvent event) {
+ EXPECT_EQ(form_rendere_id, form.form_data.unique_renderer_id);
+ EXPECT_TRUE(FormHasFieldWithValue(form.form_data, username_value));
+ EXPECT_TRUE(FormHasFieldWithValue(form.form_data, password_value));
+ EXPECT_TRUE(FormHasFieldWithValue(form.form_data, new_password_value));
+ EXPECT_EQ(form.form_data.submission_event, event);
+ }
+
+ void ExpectFormSubmittedWithUsernameAndPasswords(
+ uint32_t form_rendere_id,
+ const std::string& username_value,
+ const std::string& password_value,
+ const std::string& new_password_value) {
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_driver_.called_password_form_submitted());
+ ASSERT_TRUE(static_cast<bool>(fake_driver_.password_form_submitted()));
+ ExpectFormWithUsernameAndPasswordsAndEvent(
+ *(fake_driver_.password_form_submitted()), form_rendere_id,
+ username_value, password_value, new_password_value,
+ SubmissionIndicatorEvent::HTML_FORM_SUBMISSION);
+ }
+
+ void ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ uint32_t form_rendere_id,
+ const std::string& username_value,
+ const std::string& password_value,
+ const std::string& new_password_value,
+ SubmissionIndicatorEvent event) {
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_driver_.called_same_document_navigation());
+ ASSERT_TRUE(
+ static_cast<bool>(fake_driver_.password_form_maybe_submitted()));
+ ExpectFormWithUsernameAndPasswordsAndEvent(
+ *(fake_driver_.password_form_maybe_submitted()), form_rendere_id,
+ username_value, password_value, new_password_value, event);
+ }
+
+ void CheckIfEventsAreCalled(const std::vector<base::string16>& checkers,
+ bool expected) {
+ for (const base::string16& variable : checkers) {
+ int value;
+ EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(variable, &value))
+ << variable;
+ EXPECT_EQ(expected, value == 1) << variable;
+ }
+ }
+
+ void BindPasswordManagerDriver(mojo::ScopedInterfaceEndpointHandle handle) {
+ fake_driver_.BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::PasswordManagerDriver>(
+ std::move(handle)));
+ }
+
+ void BindPasswordManagerClient(mojo::ScopedInterfaceEndpointHandle handle) {
+ fake_pw_client_.BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::PasswordGenerationDriver>(
+ std::move(handle)));
+ }
+
+ void SaveAndSubmitForm() { SaveAndSubmitForm(username_element_.Form()); }
+
+ void SaveAndSubmitForm(const WebFormElement& form_element) {
+ FormTracker* tracker = autofill_agent_->form_tracker_for_testing();
+ static_cast<content::RenderFrameObserver*>(tracker)->WillSendSubmitEvent(
+ form_element);
+ static_cast<content::RenderFrameObserver*>(tracker)->WillSubmitForm(
+ form_element);
+ }
+
+ void CheckFirstFillingResult(FillingResult result) {
+ histogram_tester_.ExpectUniqueSample(
+ "PasswordManager.FirstRendererFillingResult", result, 1);
+ }
+
+ void SubmitForm() {
+ FormTracker* tracker = autofill_agent_->form_tracker_for_testing();
+ static_cast<content::RenderFrameObserver*>(tracker)->WillSubmitForm(
+ username_element_.Form());
+ }
+
+ void FireAjaxSucceeded() {
+ FormTracker* tracker = autofill_agent_->form_tracker_for_testing();
+ tracker->AjaxSucceeded();
+ }
+
+ void FireDidCommitProvisionalLoad() {
+ FormTracker* tracker = autofill_agent_->form_tracker_for_testing();
+ static_cast<content::RenderFrameObserver*>(tracker)
+ ->DidCommitProvisionalLoad(true, ui::PAGE_TRANSITION_LINK);
+ }
+
+ void ClearField(FormFieldData* field) {
+ field->unique_renderer_id = std::numeric_limits<uint32_t>::max();
+ field->value.clear();
+ }
+
+ FakeMojoPasswordManagerDriver fake_driver_;
+ testing::NiceMock<FakePasswordGenerationDriver> fake_pw_client_;
+
+ base::string16 username1_;
+ base::string16 username2_;
+ base::string16 username3_;
+ base::string16 password1_;
+ base::string16 password2_;
+ base::string16 password3_;
+ base::string16 alternate_username3_;
+ PasswordFormFillData fill_data_;
+
+ WebInputElement username_element_;
+ WebInputElement password_element_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+
+ protected:
+ base::HistogramTester histogram_tester_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PasswordAutofillAgentTest);
+};
+
+// Tests that the password login is autocompleted as expected when the browser
+// sends back the password info.
+TEST_F(PasswordAutofillAgentTest, InitialAutocomplete) {
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should have been autocompleted.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ CheckFirstFillingResult(FillingResult::kSuccess);
+}
+
+// Tests that we correctly fill forms having an empty 'action' attribute.
+TEST_F(PasswordAutofillAgentTest, InitialAutocompleteForEmptyAction) {
+ const char kEmptyActionFormHTML[] =
+ "<FORM name='LoginTestForm'>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+ LoadHTML(kEmptyActionFormHTML);
+
+ // Retrieve the input elements so the test can access them.
+ UpdateUsernameAndPasswordElements();
+
+ // Set the expected form origin and action URLs.
+ UpdateOriginForHTML(kEmptyActionFormHTML);
+ fill_data_.action = GURL();
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should have been autocompleted.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+}
+
+// Tests that if a password is marked as readonly, neither field is autofilled
+// on page load.
+TEST_F(PasswordAutofillAgentTest, NoInitialAutocompleteForReadOnlyPassword) {
+ SetElementReadOnly(password_element_, true);
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsSuggestedState(std::string(), false, std::string(), false);
+
+ CheckFirstFillingResult(FillingResult::kPasswordElementIsNotAutocompleteable);
+}
+
+// Can still fill a password field if the username is set to a value that
+// matches.
+TEST_F(PasswordAutofillAgentTest,
+ AutocompletePasswordForReadonlyUsernameMatched) {
+ username_element_.SetValue(WebString::FromUTF16(username3_));
+ SetElementReadOnly(username_element_, true);
+
+ // Filled even though username is not the preferred match.
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckUsernameDOMStatePasswordSuggestedState(UTF16ToUTF8(username3_), false,
+ UTF16ToUTF8(password3_), true);
+
+ CheckFirstFillingResult(FillingResult::kSuccess);
+}
+
+// Fill username and password fields when username field contains a prefilled
+// value that matches the list of known possible prefilled values usually used
+// as placeholders.
+TEST_F(PasswordAutofillAgentTest, AutocompleteForPrefilledUsernameValue) {
+ // Set the username element to a value from the prefilled values list.
+ // Comparison should be insensitive to leading and trailing whitespaces.
+ username_element_.SetValue(
+ WebString::FromUTF16(base::UTF8ToUTF16(" User Name ")));
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should both have suggested values.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ // Simulate a user click so that the password field's real value is filled.
+ SimulateElementClick(kUsernameName);
+
+ // The username and password should have been autocompleted.
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+
+ histogram_tester_.ExpectUniqueSample(
+ "PasswordManager.PrefilledUsernameFillOutcome",
+ PrefilledUsernameFillOutcome::kPrefilledPlaceholderUsernameOverridden, 1);
+
+ CheckFirstFillingResult(FillingResult::kSuccess);
+}
+
+// Tests that if filling is invoked twice for the same autofill agent the
+// prefilled username and first filling metrics are only logged once.
+TEST_F(PasswordAutofillAgentTest, MetricsOnlyLoggedOnce) {
+ // Set the username element to a value from the prefilled values list.
+ // Comparison should be insensitive to leading and trailing whitespaces.
+ username_element_.SetValue(
+ WebString::FromUTF16(base::UTF8ToUTF16(" User Name ")));
+
+ // Simulate the browser sending back the login info multiple tims.
+ // This triggers the autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateOnFillPasswordForm(fill_data_);
+
+ histogram_tester_.ExpectUniqueSample(
+ "PasswordManager.PrefilledUsernameFillOutcome",
+ PrefilledUsernameFillOutcome::kPrefilledPlaceholderUsernameOverridden, 1);
+
+ CheckFirstFillingResult(FillingResult::kSuccess);
+}
+
+// Fill a password field if the stored username is a prefix of username in
+// read-only field.
+TEST_F(PasswordAutofillAgentTest,
+ AutocompletePasswordForReadonlyUsernamePrefixMatched) {
+ base::string16 username_at = username3_ + base::UTF8ToUTF16("@example.com");
+ username_element_.SetValue(WebString::FromUTF16(username_at));
+ SetElementReadOnly(username_element_, true);
+
+ // Filled even though the username in the form is only a proper prefix of the
+ // stored username.
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckUsernameDOMStatePasswordSuggestedState(UTF16ToUTF8(username_at), false,
+ UTF16ToUTF8(password3_), true);
+}
+
+// Credentials are sent to the renderer even for sign-up forms as these may be
+// eligible for filling via manual fall back. In this case, the username_field
+// and password_field are not set. This test verifies that no failures are
+// recorded in PasswordManager.FirstRendererFillingResult.
+TEST_F(PasswordAutofillAgentTest, NoFillingOnSignupForm_NoMetrics) {
+ LoadHTML(kSignupFormHTML);
+
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("random_info"));
+ ASSERT_FALSE(element.IsNull());
+ username_element_ = element.To<WebInputElement>();
+
+ fill_data_.username_field.unique_renderer_id =
+ FormFieldData::kNotSetFormControlRendererId;
+ fill_data_.password_field.unique_renderer_id =
+ FormFieldData::kNotSetFormControlRendererId;
+
+ WebFormElement form_element =
+ document.GetElementById("LoginTestForm").To<WebFormElement>();
+ fill_data_.form_renderer_id = form_element.UniqueRendererFormId();
+
+ SimulateOnFillPasswordForm(fill_data_);
+ histogram_tester_.ExpectTotalCount(
+ "PasswordManager.FirstRendererFillingResult", 0);
+}
+
+// Do not fill a password field if the stored username is a prefix without @
+// of username in read-only field.
+TEST_F(PasswordAutofillAgentTest,
+ DontAutocompletePasswordForReadonlyUsernamePrefixMatched) {
+ base::string16 prefilled_username =
+ username3_ + base::UTF8ToUTF16("example.com");
+ username_element_.SetValue(WebString::FromUTF16(prefilled_username));
+ SetElementReadOnly(username_element_, true);
+
+ // Filled even though the username in the form is only a proper prefix of the
+ // stored username.
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckUsernameDOMStatePasswordSuggestedState(UTF16ToUTF8(prefilled_username),
+ false, std::string(), false);
+
+ CheckFirstFillingResult(
+ FillingResult::kUsernamePrefilledWithIncompatibleValue);
+}
+
+// Do not fill a password field if the field isn't readonly despite the stored
+// username is a prefix without @ of username in read-only field.
+TEST_F(
+ PasswordAutofillAgentTest,
+ DontAutocompletePasswordForNotReadonlyUsernameFieldEvenWhenPrefixMatched) {
+ base::string16 prefilled_username =
+ username3_ + base::UTF8ToUTF16("@example.com");
+ username_element_.SetValue(WebString::FromUTF16(prefilled_username));
+
+ // Filled even though the username in the form is only a proper prefix of the
+ // stored username.
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckUsernameDOMStatePasswordSuggestedState(UTF16ToUTF8(prefilled_username),
+ false, std::string(), false);
+}
+
+// If a username field is empty and readonly, don't autofill.
+TEST_F(PasswordAutofillAgentTest,
+ NoAutocompletePasswordForReadonlyUsernameUnmatched) {
+ username_element_.SetValue(WebString::FromUTF8(""));
+ SetElementReadOnly(username_element_, true);
+
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckUsernameDOMStatePasswordSuggestedState(std::string(), false,
+ std::string(), false);
+
+ CheckFirstFillingResult(FillingResult::kFoundNoPasswordForUsername);
+}
+
+// Tests that having a non-matching username precludes the autocomplete.
+TEST_F(PasswordAutofillAgentTest, NoAutocompleteForFilledFieldUnmatched) {
+ username_element_.SetValue(WebString::FromUTF8("bogus"));
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Neither field should be autocompleted.
+ CheckUsernameDOMStatePasswordSuggestedState("bogus", false, std::string(),
+ false);
+
+ CheckFirstFillingResult(
+ FillingResult::kUsernamePrefilledWithIncompatibleValue);
+}
+
+// Don't try to complete a prefilled value that is a partial match
+// to a username if the prefilled value isn't on the list of known values
+// used as placeholders.
+TEST_F(PasswordAutofillAgentTest, NoPartialMatchForPrefilledUsername) {
+ username_element_.SetValue(WebString::FromUTF8("ali"));
+
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsSuggestedState("", false, std::string(), false);
+ CheckUsernameDOMStatePasswordSuggestedState("ali", false, std::string(),
+ false);
+
+ histogram_tester_.ExpectUniqueSample(
+ "PasswordManager.PrefilledUsernameFillOutcome",
+ autofill::PrefilledUsernameFillOutcome::kPrefilledUsernameNotOverridden,
+ 1);
+}
+
+TEST_F(PasswordAutofillAgentTest, InputWithNoForms) {
+ const char kNoFormInputs[] =
+ "<input type='text' id='username'/>"
+ "<input type='password' id='password'/>";
+ LoadHTML(kNoFormInputs);
+
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Input elements that aren't in a <form> won't autofill.
+ CheckTextFieldsSuggestedState(std::string(), false, std::string(), false);
+}
+
+// Tests that having a matching username does not preclude the autocomplete.
+TEST_F(PasswordAutofillAgentTest, InitialAutocompleteForMatchingFilledField) {
+ username_element_.SetValue(WebString::FromUTF8(kAliceUsername));
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should have been autocompleted.
+ CheckUsernameDOMStatePasswordSuggestedState(kAliceUsername, true,
+ kAlicePassword, true);
+
+ CheckFirstFillingResult(FillingResult::kSuccess);
+}
+
+TEST_F(PasswordAutofillAgentTest, PasswordNotClearedOnEdit) {
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Simulate the user changing the username to some unknown username.
+ SimulateUsernameTyping("alicia");
+
+ // The password should not have been cleared.
+ CheckTextFieldsDOMState("alicia", false, kAlicePassword, true);
+}
+
+// Tests that lost focus does not trigger filling when |wait_for_username| is
+// true.
+TEST_F(PasswordAutofillAgentTest, WaitUsername) {
+ // Simulate the browser sending back the login info.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // No auto-fill should have taken place.
+ CheckTextFieldsSuggestedState(std::string(), false, std::string(), false);
+
+ SimulateUsernameTyping(kAliceUsername);
+ // Change focus in between to make sure blur events don't trigger filling.
+ SetFocused(password_element_);
+ SetFocused(username_element_);
+ // No autocomplete should happen when text is entered in the username.
+ CheckUsernameDOMStatePasswordSuggestedState(kAliceUsername, false,
+ std::string(), false);
+
+ CheckFirstFillingResult(FillingResult::kWaitForUsername);
+}
+
+TEST_F(PasswordAutofillAgentTest, IsWebElementVisibleTest) {
+ blink::WebVector<WebFormElement> forms1, forms2, forms3;
+ blink::WebVector<blink::WebFormControlElement> web_control_elements;
+ blink::WebLocalFrame* frame;
+
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ frame = GetMainFrame();
+ frame->GetDocument().Forms(forms1);
+ ASSERT_EQ(1u, forms1.size());
+ forms1[0].GetFormControlElements(web_control_elements);
+ ASSERT_EQ(1u, web_control_elements.size());
+ EXPECT_TRUE(form_util::IsWebElementVisible(web_control_elements[0]));
+
+ LoadHTML(kNonVisibleFormHTML);
+ frame = GetMainFrame();
+ frame->GetDocument().Forms(forms2);
+ ASSERT_EQ(1u, forms2.size());
+ forms2[0].GetFormControlElements(web_control_elements);
+ ASSERT_EQ(1u, web_control_elements.size());
+ EXPECT_FALSE(form_util::IsWebElementVisible(web_control_elements[0]));
+
+ LoadHTML(kNonDisplayedFormHTML);
+ frame = GetMainFrame();
+ frame->GetDocument().Forms(forms3);
+ ASSERT_EQ(1u, forms3.size());
+ forms3[0].GetFormControlElements(web_control_elements);
+ ASSERT_EQ(1u, web_control_elements.size());
+ EXPECT_FALSE(form_util::IsWebElementVisible(web_control_elements[0]));
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ SendPasswordFormsTest_VisibleFormWithNoUsername) {
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_FALSE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_EmptyForm) {
+ base::RunLoop().RunUntilIdle();
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kEmptyFormHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_password_forms_parsed());
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_TRUE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_FormWithoutPasswords) {
+ base::RunLoop().RunUntilIdle();
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kFormWithoutPasswordsHTML);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_FALSE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ SendPasswordFormsTest_UndetectedPasswordField) {
+ base::RunLoop().RunUntilIdle();
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kFormWithoutPasswordsHTML);
+ // Emulate that a password field appears but we don't detect that.
+ std::string script =
+ "document.getElementById('random_field').type = 'password';";
+ ExecuteJavaScriptForTests(script.c_str());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+
+ // When the user clicks on the field, a request to the store will be sent.
+ EXPECT_TRUE(SimulateElementClick("random_field"));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_FALSE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_NonDisplayedForm) {
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kNonDisplayedFormHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_TRUE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_NonVisibleForm) {
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kNonVisibleFormHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_TRUE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_PasswordChangeForm) {
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kPasswordChangeFormHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_FALSE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ SendPasswordFormsTest_CannotCreatePasswordForm) {
+ // This test checks that a request to the store is sent even if it is a credit
+ // card form.
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kCreditCardFormHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+ ASSERT_TRUE(fake_driver_.password_forms_rendered());
+ EXPECT_FALSE(fake_driver_.password_forms_rendered()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_ReloadTab) {
+ // PasswordAutofillAgent::sent_request_to_store_ disables duplicate requests
+ // to the store. This test checks that new request will be sent if the frame
+ // has been reloaded.
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kNonVisibleFormHTML);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+
+ fake_driver_.reset_password_forms_calls();
+ std::string url_string = "data:text/html;charset=utf-8,";
+ url_string.append(kNonVisibleFormHTML);
+ Reload(GURL(url_string));
+ EXPECT_TRUE(fake_driver_.called_password_forms_parsed());
+ ASSERT_TRUE(fake_driver_.password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.password_forms_parsed()->empty());
+}
+
+TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_Redirection) {
+ base::RunLoop().RunUntilIdle();
+
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kEmptyWebpage);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_password_forms_rendered());
+
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kRedirectionWebpage);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_password_forms_rendered());
+
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kSimpleWebpage);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+
+ fake_driver_.reset_password_forms_calls();
+ LoadHTML(kWebpageWithDynamicContent);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_password_forms_rendered());
+}
+
+// Tests that a password will only be filled as a suggested and will not be
+// accessible by the DOM until a user gesture has occurred.
+TEST_F(PasswordAutofillAgentTest, GestureRequiredTest) {
+ // Trigger the initial autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should have been autocompleted.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ // However, it should only have completed with the suggested value, as tested
+ // above, and it should not have completed into the DOM accessible value for
+ // the password field.
+ CheckTextFieldsDOMState(std::string(), true, std::string(), true);
+
+ // Simulate a user click so that the password field's real value is filled.
+ SimulateElementClick(kUsernameName);
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+}
+
+// Verfies that a DOM-activated UI event will not cause an autofill.
+TEST_F(PasswordAutofillAgentTest, NoDOMActivationTest) {
+ // Trigger the initial autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ ExecuteJavaScriptForTests(kJavaScriptClick);
+ CheckTextFieldsDOMState("", true, "", true);
+}
+
+// Verifies that password autofill triggers events in JavaScript for forms that
+// are filled on page load.
+TEST_F(PasswordAutofillAgentTest,
+ PasswordAutofillTriggersOnChangeEventsOnLoad) {
+ std::vector<base::string16> username_event_checkers;
+ std::vector<base::string16> password_event_checkers;
+ std::string events_registration_script =
+ CreateScriptToRegisterListeners(kUsernameName, &username_event_checkers) +
+ CreateScriptToRegisterListeners(kPasswordName, &password_event_checkers);
+ std::string html = std::string(kFormHTML) + events_registration_script;
+ LoadHTML(html.c_str());
+ UpdateOriginForHTML(html);
+ UpdateUsernameAndPasswordElements();
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should have been autocompleted...
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+ // ... but since there hasn't been a user gesture yet, the autocompleted
+ // username and password should only be visible to the user.
+ CheckTextFieldsDOMState(std::string(), true, std::string(), true);
+
+ // JavaScript events shouldn't have been triggered for the username and the
+ // password yet.
+ CheckIfEventsAreCalled(username_event_checkers, false);
+ CheckIfEventsAreCalled(password_event_checkers, false);
+
+ // Simulate a user click so that the password field's real value is filled.
+ SimulateElementClick(kUsernameName);
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+
+ // Now, JavaScript events should have been triggered.
+ CheckIfEventsAreCalled(username_event_checkers, true);
+ CheckIfEventsAreCalled(password_event_checkers, true);
+}
+
+// Verifies that password autofill triggers events in JavaScript for forms that
+// are filled after page load.
+TEST_F(PasswordAutofillAgentTest,
+ PasswordAutofillTriggersOnChangeEventsWaitForUsername) {
+ std::vector<base::string16> event_checkers;
+ std::string events_registration_script =
+ CreateScriptToRegisterListeners(kUsernameName, &event_checkers) +
+ CreateScriptToRegisterListeners(kPasswordName, &event_checkers);
+ std::string html = std::string(kFormHTML) + events_registration_script;
+ LoadHTML(html.c_str());
+ UpdateOriginForHTML(html);
+ UpdateUsernameAndPasswordElements();
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should not yet have been autocompleted.
+ CheckTextFieldsSuggestedState(std::string(), false, std::string(), false);
+
+ // Simulate a click just to force a user gesture, since the username value is
+ // set directly.
+ SimulateElementClick(kUsernameName);
+
+ // Simulate the user entering the first letter of their username and selecting
+ // the matching autofill from the dropdown.
+ SimulateUsernameTyping("a");
+ // Since the username element has focus, blur event will be not triggered.
+ base::Erase(event_checkers, ASCIIToUTF16("username_blur_event"));
+ SimulateSuggestionChoice(username_element_);
+
+ // The username and password should now have been autocompleted.
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+
+ // JavaScript events should have been triggered both for the username and for
+ // the password.
+ CheckIfEventsAreCalled(event_checkers, true);
+}
+
+// Tests that |FillSuggestion| properly fills the username and password.
+TEST_F(PasswordAutofillAgentTest, FillSuggestion) {
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // If the password field is not autocompletable, it should not be affected.
+ SetElementReadOnly(password_element_, true);
+ EXPECT_FALSE(password_autofill_agent_->FillSuggestion(
+ selected_element, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+ SetElementReadOnly(password_element_, false);
+
+ // After filling with the suggestion, both fields should be autocompleted.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ selected_element, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+ int username_length = strlen(kAliceUsername);
+ CheckUsernameSelection(username_length, username_length);
+
+ // Try Filling with a suggestion with password different from the one that
+ // was initially sent to the renderer.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ selected_element, ASCIIToUTF16(kBobUsername),
+ ASCIIToUTF16(kCarolPassword)));
+ CheckTextFieldsDOMState(kBobUsername, true, kCarolPassword, true);
+ username_length = strlen(kBobUsername);
+ CheckUsernameSelection(username_length, username_length);
+
+ ClearUsernameAndPasswordFields();
+ }
+}
+
+// Tests that |FillSuggestion| properly fills the username and password when the
+// username field is created dynamically in JavaScript.
+TEST_F(PasswordAutofillAgentTest, FillSuggestionWithDynamicUsernameField) {
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ UpdateOnlyPasswordElement();
+
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ constexpr const char* kAddUsernameToFormScript =
+ "var new_input = document.createElement('input');"
+ "new_input.setAttribute('type', 'text');"
+ "new_input.setAttribute('id', 'username');"
+ "password_field = document.getElementById('password');"
+ "password_field.parentNode.insertBefore(new_input, password_field);";
+ ExecuteJavaScriptForTests(kAddUsernameToFormScript);
+ UpdateUsernameAndPasswordElements();
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // After filling with the suggestion, both fields should be autocompleted.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ password_element_, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+
+ CheckFirstFillingResult(FillingResult::kWaitForUsername);
+}
+
+// Tests that |FillSuggestion| doesn't change non-empty non-autofilled username
+// when interacting with the password field.
+TEST_F(PasswordAutofillAgentTest,
+ FillSuggestionFromPasswordFieldWithUsernameManuallyFilled) {
+ username_element_.SetValue(WebString::FromUTF8("user1"));
+
+ // Simulate the browser sending the login info, but set |wait_for_username| to
+ // prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+ // Neither field should have been autocompleted.
+ CheckTextFieldsDOMState("user1", false, std::string(), false);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Only password field should be autocompleted.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ password_element_, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+ CheckTextFieldsDOMState("user1", false, kAlicePassword, true);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Try Filling with a different password. Only password should be changed.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ password_element_, ASCIIToUTF16(kBobUsername),
+ ASCIIToUTF16(kCarolPassword)));
+ CheckTextFieldsDOMState("user1", false, kCarolPassword, true);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, fake_driver_.called_show_manual_fallback_for_saving_count());
+}
+
+// Tests that |FillSuggestion| properly fills the password if the username field
+// is read-only.
+TEST_F(PasswordAutofillAgentTest, FillSuggestionIfUsernameReadOnly) {
+ // Simulate the browser sending the login info.
+ SetElementReadOnly(username_element_, true);
+ SimulateOnFillPasswordForm(fill_data_);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // Username field is not autocompletable, it should not be affected.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ selected_element, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+ CheckTextFieldsDOMState(std::string(), false, kAlicePassword, true);
+
+ // Try Filling with a suggestion with password different from the one that
+ // was initially sent to the renderer.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ selected_element, ASCIIToUTF16(kBobUsername),
+ ASCIIToUTF16(kCarolPassword)));
+ CheckTextFieldsDOMState(std::string(), false, kCarolPassword, true);
+
+ ClearUsernameAndPasswordFields();
+ }
+}
+
+// Tests that |PreviewSuggestion| properly previews the username and password.
+TEST_F(PasswordAutofillAgentTest, PreviewSuggestion) {
+ // Simulate the browser sending the login info, but set |wait_for_username| to
+ // prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // If the password field is not autocompletable, it should not be affected.
+ SetElementReadOnly(password_element_, true);
+ EXPECT_FALSE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ CheckTextFieldsSuggestedState(std::string(), false, std::string(), false);
+ SetElementReadOnly(password_element_, false);
+
+ // After selecting the suggestion, both fields should be previewed with
+ // suggested values.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ CheckUsernameSelection(0, 0);
+
+ // Try previewing with a password different from the one that was initially
+ // sent to the renderer.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kBobUsername, kCarolPassword));
+ CheckTextFieldsSuggestedState(kBobUsername, true, kCarolPassword, true);
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ CheckUsernameSelection(0, 0);
+
+ ClearUsernameAndPasswordFields();
+ }
+}
+
+// Tests that |PreviewSuggestion| doesn't change non-empty non-autofilled
+// username when previewing autofills on interacting with the password field.
+TEST_F(PasswordAutofillAgentTest,
+ PreviewSuggestionFromPasswordFieldWithUsernameManuallyFilled) {
+ username_element_.SetValue(WebString::FromUTF8("user1"));
+
+ // Simulate the browser sending the login info, but set |wait_for_username| to
+ // prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+ // Neither field should have been autocompleted.
+ CheckTextFieldsDOMState("user1", false, std::string(), false);
+
+ // Only password field should be autocompleted.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ password_element_, kAliceUsername, kAlicePassword));
+ CheckTextFieldsSuggestedState(std::string(), false, kAlicePassword, true);
+ CheckTextFieldsDOMState("user1", false, std::string(), true);
+
+ // Try previewing with a different password. Only password should be changed.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ password_element_, kBobUsername, kCarolPassword));
+ CheckTextFieldsSuggestedState(std::string(), false, kCarolPassword, true);
+ CheckTextFieldsDOMState("user1", false, std::string(), true);
+}
+
+// Tests that |PreviewSuggestion| properly previews the password if username is
+// read-only.
+TEST_F(PasswordAutofillAgentTest, PreviewSuggestionIfUsernameReadOnly) {
+ // Simulate the browser sending the login info.
+ SetElementReadOnly(username_element_, true);
+ SimulateOnFillPasswordForm(fill_data_);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // Username field is not autocompletable, it should not be affected.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ // Password field must be autofilled.
+ CheckTextFieldsSuggestedState(std::string(), false, kAlicePassword, true);
+
+ // Try previewing with a password different from the one that was initially
+ // sent to the renderer.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kBobUsername, kCarolPassword));
+ CheckTextFieldsSuggestedState(std::string(), false, kCarolPassword, true);
+
+ ClearUsernameAndPasswordFields();
+ }
+}
+
+// Tests that |PreviewSuggestion| properly sets the username selection range.
+TEST_F(PasswordAutofillAgentTest, PreviewSuggestionSelectionRange) {
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ ResetFieldState(&username_element_, "ali", WebAutofillState::kPreviewed);
+ ResetFieldState(&password_element_);
+
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+ // The selection should be set after the third character.
+ CheckUsernameSelection(3, 3);
+ }
+}
+
+// Tests that |ClearPreview| properly clears previewed username and password
+// with password being previously autofilled.
+TEST_F(PasswordAutofillAgentTest, ClearPreviewWithPasswordAutofilled) {
+ ResetFieldState(&password_element_, "sec", WebAutofillState::kPreviewed);
+
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsDOMState(std::string(), false, "sec", true);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ EXPECT_TRUE(
+ password_autofill_agent_->DidClearAutofillSelection(selected_element));
+
+ EXPECT_TRUE(username_element_.SuggestedValue().IsEmpty());
+ EXPECT_TRUE(password_element_.SuggestedValue().IsEmpty());
+ CheckTextFieldsDOMState(std::string(), false, "sec", true);
+ CheckUsernameSelection(0, 0);
+ }
+}
+
+// Tests that |ClearPreview| properly clears previewed username and password
+// with username being previously autofilled.
+TEST_F(PasswordAutofillAgentTest, ClearPreviewWithUsernameAutofilled) {
+ ResetFieldState(&username_element_, "ali", WebAutofillState::kPreviewed);
+ username_element_.SetSelectionRange(3, 3);
+
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsDOMState("ali", true, std::string(), false);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ EXPECT_TRUE(
+ password_autofill_agent_->DidClearAutofillSelection(selected_element));
+
+ EXPECT_TRUE(username_element_.SuggestedValue().IsEmpty());
+ EXPECT_TRUE(password_element_.SuggestedValue().IsEmpty());
+ CheckTextFieldsDOMState("ali", true, std::string(), false);
+ CheckUsernameSelection(3, 3);
+ }
+}
+
+// Tests that |ClearPreview| properly clears previewed username and password
+// with username and password being previously autofilled.
+TEST_F(PasswordAutofillAgentTest,
+ ClearPreviewWithAutofilledUsernameAndPassword) {
+ ResetFieldState(&username_element_, "ali", WebAutofillState::kPreviewed);
+ ResetFieldState(&password_element_, "sec", WebAutofillState::kPreviewed);
+
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsDOMState("ali", true, "sec", true);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ EXPECT_TRUE(
+ password_autofill_agent_->DidClearAutofillSelection(selected_element));
+
+ EXPECT_TRUE(username_element_.SuggestedValue().IsEmpty());
+ EXPECT_TRUE(password_element_.SuggestedValue().IsEmpty());
+ CheckTextFieldsDOMState("ali", true, "sec", true);
+ CheckUsernameSelection(3, 3);
+ }
+}
+
+// Tests that TryToShowTouchToFill() works correctly for fillable and
+// non-fillable fields.
+TEST_F(PasswordAutofillAgentTest, TryToShowTouchToFillUsername) {
+ // Initially no fill data is available.
+ WebInputElement random_element = GetInputElementByID("random_field");
+ EXPECT_FALSE(
+ password_autofill_agent_->TryToShowTouchToFill(username_element_));
+ EXPECT_FALSE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+ EXPECT_FALSE(password_autofill_agent_->TryToShowTouchToFill(random_element));
+ EXPECT_FALSE(password_autofill_agent_->ShouldSuppressKeyboard());
+
+ // This changes once fill data is simulated. |random_element| continue to
+ // have no fill data, though.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ EXPECT_TRUE(
+ password_autofill_agent_->TryToShowTouchToFill(username_element_));
+ EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
+ EXPECT_EQ(WebAutofillState::kPreviewed, username_element_.GetAutofillState());
+ EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
+
+ EXPECT_CALL(fake_driver_, ShowTouchToFill);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(PasswordAutofillAgentTest, TryToShowTouchToFillPassword) {
+ SimulateOnFillPasswordForm(fill_data_);
+
+ EXPECT_TRUE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+ EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
+ EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
+
+ EXPECT_CALL(fake_driver_, ShowTouchToFill);
+ base::RunLoop().RunUntilIdle();
+}
+
+#if defined(OS_ANDROID)
+TEST_F(PasswordAutofillAgentTest, TouchToFillSuppressesPopups) {
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateSuggestionChoice(username_element_);
+ EXPECT_CALL(fake_driver_, ShowTouchToFill);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions).Times(0);
+ base::RunLoop().RunUntilIdle();
+}
+#endif
+
+TEST_F(PasswordAutofillAgentTest, DontTryToShowTouchToFillReadonlyPassword) {
+ SetElementReadOnly(password_element_, true);
+ SimulateOnFillPasswordForm(fill_data_);
+
+ EXPECT_FALSE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+}
+
+#if defined(OS_ANDROID)
+TEST_F(PasswordAutofillAgentTest, DontShowTouchToFillOnSecurePageIfParamIsSet) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeatureWithParameters(
+ features::kAutofillTouchToFill, {{"insecure-origins-only", "true"}});
+
+ // Reload the page with a secure origin.
+ LoadHTMLWithUrlOverride(kFormHTML, "https://example.com");
+ SimulateOnFillPasswordForm(fill_data_);
+
+ EXPECT_FALSE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+}
+#endif
+
+TEST_F(PasswordAutofillAgentTest, TouchToFillClosed) {
+ SimulateOnFillPasswordForm(fill_data_);
+
+ auto previous_state = password_element_.GetAutofillState();
+ // Touch to fill will be shown multiple times until TouchToFillClosed()
+ // gets called.
+ EXPECT_TRUE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+ EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
+ EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
+
+ EXPECT_CALL(fake_driver_, ShowTouchToFill);
+ base::RunLoop().RunUntilIdle();
+
+ // Make sure that resetting Touch To Fill resets the Autofill state.
+ password_autofill_agent_->TouchToFillClosed(true);
+ EXPECT_FALSE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+ EXPECT_FALSE(password_autofill_agent_->ShouldSuppressKeyboard());
+ EXPECT_EQ(previous_state, password_element_.GetAutofillState());
+
+ // Reload the page and simulate fill.
+ LoadHTML(kFormHTML);
+ UpdateOriginForHTML(kFormHTML);
+ UpdateUsernameAndPasswordElements();
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // After the reload touch to fill is shown again.
+ EXPECT_TRUE(
+ password_autofill_agent_->TryToShowTouchToFill(password_element_));
+ EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
+ EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
+
+ EXPECT_CALL(fake_driver_, ShowTouchToFill);
+ base::RunLoop().RunUntilIdle();
+}
+
+// Tests that |FillIntoFocusedField| doesn't fill read-only text fields.
+TEST_F(PasswordAutofillAgentTest, FillIntoFocusedReadonlyTextField) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // If the field is readonly, it should not be affected.
+ SetElementReadOnly(username_element_, true);
+ SimulateElementClick(kUsernameName);
+ password_autofill_agent_->FillIntoFocusedField(
+ /*is_password=*/false, ASCIIToUTF16(kAliceUsername));
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+}
+
+// Tests that |FillIntoFocusedField| properly fills user-provided credentials.
+TEST_F(PasswordAutofillAgentTest, FillIntoFocusedWritableTextField) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // The same field should be filled if it is writable.
+ FocusElement(kUsernameName);
+ SetElementReadOnly(username_element_, false);
+
+ password_autofill_agent_->FillIntoFocusedField(
+ /*is_password=*/false, ASCIIToUTF16(kAliceUsername));
+ CheckTextFieldsDOMState(kAliceUsername, true, std::string(), false);
+ CheckUsernameSelection(strlen(kAliceUsername), strlen(kAliceUsername));
+}
+
+// Tests that |FillIntoFocusedField| doesn't fill passwords in userfields.
+TEST_F(PasswordAutofillAgentTest, FillIntoFocusedFieldOnlyIntoPasswordFields) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // Filling a password into a username field doesn't work.
+ SimulateElementClick(kUsernameName);
+
+ password_autofill_agent_->FillIntoFocusedField(
+ /*is_password=*/true, ASCIIToUTF16(kAlicePassword));
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // When a password field is focus, the filling works.
+ SimulateElementClick(kPasswordName);
+
+ password_autofill_agent_->FillIntoFocusedField(
+ /*is_password=*/true, ASCIIToUTF16(kAlicePassword));
+ CheckTextFieldsDOMState(std::string(), false, kAlicePassword, true);
+}
+
+// Tests that |FillIntoFocusedField| fills last focused, not last clicked field.
+TEST_F(PasswordAutofillAgentTest, FillIntoFocusedFieldForNonClickFocus) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // Click the username but shift the focus without click to the password.
+ SimulateElementClick(kUsernameName);
+ FocusElement(kPasswordName);
+ // The completion should now affect ONLY the password field. Don't fill a
+ // password so the error on failure shows where the filling happened.
+ // (see FillIntoFocusedFieldOnlyIntoPasswordFields).
+
+ password_autofill_agent_->FillIntoFocusedField(
+ /*is_password=*/false, ASCIIToUTF16("TextToFill"));
+ CheckTextFieldsDOMState(std::string(), false, "TextToFill", true);
+}
+
+// Tests that |ClearPreview| properly clears previewed username and password
+// with neither username nor password being previously autofilled.
+TEST_F(PasswordAutofillAgentTest,
+ ClearPreviewWithNotAutofilledUsernameAndPassword) {
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ selected_element, kAliceUsername, kAlicePassword));
+ EXPECT_TRUE(
+ password_autofill_agent_->DidClearAutofillSelection(selected_element));
+
+ EXPECT_TRUE(username_element_.SuggestedValue().IsEmpty());
+ EXPECT_TRUE(password_element_.SuggestedValue().IsEmpty());
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+ CheckUsernameSelection(0, 0);
+ }
+}
+
+// Tests that logging is off by default.
+TEST_F(PasswordAutofillAgentTest, OnChangeLoggingState_NoMessage) {
+ SendVisiblePasswordForms();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_record_save_progress());
+}
+
+// Test that logging can be turned on by a message.
+TEST_F(PasswordAutofillAgentTest, OnChangeLoggingState_Activated) {
+ // Turn the logging on.
+ password_autofill_agent_->SetLoggingState(true);
+
+ SendVisiblePasswordForms();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(fake_driver_.called_record_save_progress());
+}
+
+// Test that logging can be turned off by a message.
+TEST_F(PasswordAutofillAgentTest, OnChangeLoggingState_Deactivated) {
+ // Turn the logging on and then off.
+ password_autofill_agent_->SetLoggingState(true);
+ password_autofill_agent_->SetLoggingState(false);
+
+ SendVisiblePasswordForms();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_record_save_progress());
+}
+
+// Tests that one user click on a username field is sufficient to bring up a
+// credential suggestion popup, and the user can autocomplete the password by
+// selecting the credential from the popup.
+TEST_F(PasswordAutofillAgentTest, ClickAndSelect) {
+ // SimulateElementClick() is called so that a user gesture is actually made
+ // and the password can be filled. However, SimulateElementClick() does not
+ // actually lead to the AutofillAgent's InputElementClicked() method being
+ // called, so SimulateSuggestionChoice has to manually call
+ // InputElementClicked().
+ ClearUsernameAndPasswordFields();
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateElementClick(kUsernameName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ SimulateSuggestionChoice(username_element_);
+ CheckSuggestions(kAliceUsername, true);
+
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+}
+
+// Tests the autosuggestions that are given when the element is clicked.
+// Specifically, tests when the user clicks on the username element after page
+// load and the element is autofilled, when the user clicks on an element that
+// has a non-matching username, and when the user clicks on an element that's
+// already been autofilled and they've already modified.
+TEST_F(PasswordAutofillAgentTest, CredentialsOnClick) {
+ // Simulate the browser sending back the login info.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Clear the text fields to start fresh.
+ ClearUsernameAndPasswordFields();
+
+ // Call SimulateElementClick() to produce a user gesture on the page so
+ // autofill will actually fill.
+ SimulateElementClick(kUsernameName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ // Simulate a user clicking on the username element. This should produce a
+ // message with all the usernames.
+ autofill_agent_->FormControlElementClicked(username_element_, false);
+ CheckSuggestions(std::string(), false);
+
+ // Now simulate a user typing in an unrecognized username and then
+ // clicking on the username element. This should also produce a message with
+ // all the usernames.
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions).Times(AtMost(3));
+ SimulateUsernameTyping("baz");
+
+ autofill_agent_->FormControlElementClicked(username_element_, true);
+ CheckSuggestions("baz", true);
+ ClearUsernameAndPasswordFields();
+}
+
+// Tests that there is an autosuggestion from the password manager when the
+// user clicks on the password field.
+TEST_F(PasswordAutofillAgentTest, NoCredentialsOnPasswordClick) {
+ // Simulate the browser sending back the login info.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Clear the text fields to start fresh.
+ ClearUsernameAndPasswordFields();
+
+ // Call SimulateElementClick() to produce a user gesture on the page so
+ // autofill will actually fill.
+ SimulateElementClick(kUsernameName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ // Simulate a user clicking on the password element. This should produce no
+ // message.
+ autofill_agent_->FormControlElementClicked(password_element_, false);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+}
+
+// The user types in a username and a password, but then just before sending
+// the form off, a script clears them. This test checks that
+// PasswordAutofillAgent can still remember the username and the password
+// typed by the user.
+TEST_F(PasswordAutofillAgentTest,
+ DISABLED_RememberLastNonEmptyUsernameAndPasswordOnSubmit_ScriptCleared) {
+ LoadHTML(kSignupFormHTML);
+ WebInputElement username_element = GetInputElementByID("random_info");
+ ASSERT_FALSE(username_element.IsNull());
+ SimulateUserInputChangeForElement(&username_element, "username");
+
+ WebInputElement new_password_element = GetInputElementByID("new_password");
+ ASSERT_FALSE(new_password_element.IsNull());
+ SimulateUserInputChangeForElement(&new_password_element, "random");
+
+ WebInputElement confirmation_password_element =
+ GetInputElementByID("confirm_password");
+ ASSERT_FALSE(confirmation_password_element.IsNull());
+ SimulateUserInputChangeForElement(&confirmation_password_element, "random");
+
+ // Simulate that the username and the password values were cleared by the
+ // site's JavaScript before submit.
+ username_element.SetValue(WebString());
+ new_password_element.SetValue(WebString());
+ confirmation_password_element.SetValue(WebString());
+
+ // Submit form.
+ FormTracker* tracker = autofill_agent_->form_tracker_for_testing();
+ static_cast<content::RenderFrameObserver*>(tracker)->WillSubmitForm(
+ username_element.Form());
+
+ // Observe that the PasswordAutofillAgent still remembered the last non-empty
+ // username and password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(GetFormUniqueRendererId("form"),
+ "username", "", "random");
+
+ // Also check that |*_element| fields are correct.
+ const autofill::PasswordForm& form =
+ *(fake_driver_.password_form_submitted());
+ EXPECT_EQ(ASCIIToUTF16("random_info"), form.username_element);
+ EXPECT_TRUE(form.password_element.empty());
+ EXPECT_EQ(ASCIIToUTF16("new_password"), form.new_password_element);
+ EXPECT_EQ(ASCIIToUTF16("confirm_password"),
+ form.confirmation_password_element);
+}
+
+// Similar to RememberLastNonEmptyPasswordOnSubmit_ScriptCleared, but this time
+// it's the user who clears the username and the password. This test checks
+// that in that case, the last non-empty username and password are not
+// remembered.
+TEST_F(PasswordAutofillAgentTest,
+ RememberLastNonEmptyUsernameAndPasswordOnSubmit_UserCleared) {
+ SimulateUsernameTyping("temp");
+ SimulatePasswordTyping("random");
+
+ // Simulate that the user actually cleared the username and password again.
+ SimulateUsernameTyping("");
+ SimulatePasswordTyping("");
+
+ SubmitForm();
+
+ // Observe that the PasswordAutofillAgent respects the user having cleared the
+ // password.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "", "", "");
+}
+
+// Similar to RememberLastNonEmptyPasswordOnSubmit_ScriptCleared, but uses the
+// new password instead of the current password.
+TEST_F(PasswordAutofillAgentTest,
+ DISABLED_RememberLastNonEmptyUsernameAndPasswordOnSubmit_New) {
+ const char kNewPasswordFormHTML[] =
+ "<FORM name='LoginTestForm' action='http://www.bidule.com'>"
+ " <INPUT type='text' id='username' autocomplete='username'/>"
+ " <INPUT type='password' id='password' autocomplete='new-password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+ LoadHTML(kNewPasswordFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("temp");
+ SimulatePasswordTyping("random");
+
+ // Simulate that the username and the password value was cleared by
+ // the site's JavaScript before submit.
+ username_element_.SetValue(WebString());
+ password_element_.SetValue(WebString());
+
+ SubmitForm();
+
+ // Observe that the PasswordAutofillAgent still remembered the last non-empty
+ // password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "temp", "", "random");
+}
+
+// The user first accepts a suggestion, but then overwrites the password. This
+// test checks that the overwritten password is not reverted back.
+TEST_F(PasswordAutofillAgentTest,
+ NoopEditingDoesNotOverwriteManuallyEditedPassword) {
+ fill_data_.wait_for_username = true;
+ SimulateUsernameTyping(kAliceUsername);
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateSuggestionChoice(username_element_);
+ const std::string old_username(username_element_.Value().Utf8());
+ const std::string old_password(password_element_.Value().Utf8());
+ const std::string new_password(old_password + "modify");
+
+ // The user changes the password.
+ SimulatePasswordTyping(new_password);
+
+ // Change focus in between to make sure blur events don't trigger filling.
+ SetFocused(password_element_);
+ SetFocused(username_element_);
+
+ // The password should have stayed as the user changed it.
+ // The username should not be autofilled, because it was typed by the user.
+ CheckTextFieldsDOMState(old_username, false, new_password, false);
+ // The password should not have a suggested value.
+ CheckUsernameDOMStatePasswordSuggestedState(old_username, false,
+ std::string(), false);
+}
+
+// The user types the username, then accepts a suggestion. This test checks
+// that autofilling does not rewrite the username, if the value is already
+// there.
+TEST_F(PasswordAutofillAgentTest, AcceptingSuggestionDoesntRewriteUsername) {
+ fill_data_.wait_for_username = true;
+ SimulateUsernameTyping(kAliceUsername);
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateSuggestionChoice(username_element_);
+
+ const std::string username(username_element_.Value().Utf8());
+ const std::string password(password_element_.Value().Utf8());
+
+ // The password was autofilled. The username was not.
+ CheckTextFieldsDOMState(username, false, password, true);
+}
+
+// The user types in a username and a password, but then just before sending
+// the form off, a script changes them. This test checks that
+// PasswordAutofillAgent can still remember the username and the password
+// typed by the user.
+TEST_F(PasswordAutofillAgentTest,
+ RememberLastTypedUsernameAndPasswordOnSubmit_ScriptChanged) {
+ SimulateUsernameTyping("temp");
+ SimulatePasswordTyping("random");
+
+ // Simulate that the username and the password value was changed by the
+ // site's JavaScript before submit.
+ username_element_.SetValue(WebString("new username"));
+ password_element_.SetValue(WebString("new password"));
+
+ SaveAndSubmitForm();
+
+ // Observe that the PasswordAutofillAgent still remembered the last typed
+ // username and password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "temp", "random", "");
+}
+
+TEST_F(PasswordAutofillAgentTest, RememberFieldPropertiesOnSubmit) {
+ SimulateUsernameTyping("temp");
+ SimulatePasswordTyping("random");
+
+ // Simulate that the username and the password value was changed by the
+ // site's JavaScript before submit.
+ username_element_.SetValue(WebString("new username"));
+ password_element_.SetValue(WebString("new password"));
+
+ SaveAndSubmitForm();
+
+ std::map<base::string16, FieldPropertiesMask> expected_properties_masks;
+ expected_properties_masks[ASCIIToUTF16("random_field")] =
+ FieldPropertiesFlags::HAD_FOCUS;
+ expected_properties_masks[ASCIIToUTF16("username")] =
+ FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::HAD_FOCUS;
+ expected_properties_masks[ASCIIToUTF16("password")] =
+ FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::HAD_FOCUS;
+
+ ExpectFieldPropertiesMasks(PasswordFormSubmitted, expected_properties_masks,
+ SubmissionIndicatorEvent::HTML_FORM_SUBMISSION);
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ RememberFieldPropertiesOnSameDocumentNavigation) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ std::string hide_elements =
+ "var password = document.getElementById('password');"
+ "password.style = 'display:none';"
+ "var username = document.getElementById('username');"
+ "username.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ FireAjaxSucceeded();
+
+ std::map<base::string16, FieldPropertiesMask> expected_properties_masks;
+ expected_properties_masks[ASCIIToUTF16("username")] =
+ FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::HAD_FOCUS;
+ expected_properties_masks[ASCIIToUTF16("password")] =
+ FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::HAD_FOCUS;
+
+ ExpectFieldPropertiesMasks(PasswordFormSameDocumentNavigation,
+ expected_properties_masks,
+ SubmissionIndicatorEvent::XHR_SUCCEEDED);
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ RememberFieldPropertiesOnSameDocumentNavigation_2) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ std::string hide_elements =
+ "var password = document.getElementById('password');"
+ "password.style = 'display:none';"
+ "var username = document.getElementById('username');"
+ "username.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ base::RunLoop().RunUntilIdle();
+
+ std::map<base::string16, FieldPropertiesMask> expected_properties_masks;
+ expected_properties_masks[ASCIIToUTF16("username")] =
+ FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::HAD_FOCUS;
+ expected_properties_masks[ASCIIToUTF16("password")] =
+ FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::HAD_FOCUS;
+
+ ExpectFieldPropertiesMasks(PasswordFormSameDocumentNavigation,
+ expected_properties_masks,
+ SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR);
+}
+
+// The username/password is autofilled by password manager then just before
+// sending the form off, a script changes them. This test checks that
+// PasswordAutofillAgent can still get the username and the password autofilled.
+TEST_F(PasswordAutofillAgentTest,
+ RememberLastAutofilledUsernameAndPasswordOnSubmit_ScriptChanged) {
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Simulate that the username and the password value was changed by the
+ // site's JavaScript before submit.
+ username_element_.SetValue(WebString("new username"));
+ password_element_.SetValue(WebString("new password"));
+
+ SaveAndSubmitForm();
+
+ // Observe that the PasswordAutofillAgent still remembered the autofilled
+ // username and password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), kAliceUsername, kAlicePassword,
+ "");
+}
+
+// The username/password is autofilled by password manager then user types in a
+// username and a password. Then just before sending the form off, a script
+// changes them. This test checks that PasswordAutofillAgent can still remember
+// the username and the password typed by the user.
+TEST_F(
+ PasswordAutofillAgentTest,
+ RememberLastTypedAfterAutofilledUsernameAndPasswordOnSubmit_ScriptChanged) {
+ SimulateOnFillPasswordForm(fill_data_);
+
+ SimulateUsernameTyping("temp");
+ SimulatePasswordTyping("random");
+
+ // Simulate that the username and the password value was changed by the
+ // site's JavaScript before submit.
+ username_element_.SetValue(WebString("new username"));
+ password_element_.SetValue(WebString("new password"));
+
+ SaveAndSubmitForm();
+
+ // Observe that the PasswordAutofillAgent still remembered the last typed
+ // username and password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "temp", "random", "");
+}
+
+// The user starts typing username then it is autofilled.
+// PasswordAutofillAgent should remember the username that was autofilled,
+// not last typed.
+TEST_F(PasswordAutofillAgentTest, RememberAutofilledUsername) {
+ SimulateUsernameTyping("Te");
+ // Simulate that the username was changed by autofilling.
+ username_element_.SetValue(WebString("temp"));
+ SimulatePasswordTyping("random");
+
+ SaveAndSubmitForm();
+
+ // Observe that the PasswordAutofillAgent still remembered the last typed
+ // username and password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "temp", "random", "");
+}
+
+// The user starts typing username then javascript suggests to select another
+// username that was generated based on typed field value (e.g. surname field).
+// PasswordAutofillAgent should remember the username that was selected,
+// not last typed.
+TEST_F(PasswordAutofillAgentTest,
+ RememberUsernameGeneratedBasingOnTypedFields) {
+ SimulateUsernameTyping("Temp");
+ SimulatePasswordTyping("random");
+
+ // Suppose that "random_field" contains surname.
+ WebInputElement surname_element = GetInputElementByID("random_field");
+ SimulateUserInputChangeForElement(&surname_element, "Smith");
+
+ // Simulate that the user selected username that was generated by script.
+ username_element_.SetValue(WebString("foo.smith"));
+
+ SaveAndSubmitForm();
+
+ // Observe that the PasswordAutofillAgent still remembered the last typed
+ // username and password and sent that to the browser.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "foo.smith", "random", "");
+}
+
+// If credentials contain username+password but the form contains only a
+// password field, we don't autofill on page load.
+TEST_F(PasswordAutofillAgentTest, DontFillFormWithNoUsername) {
+ // Load a form with no username and update test data.
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ UpdateOnlyPasswordElement();
+
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // As the credential contains a username, but the form does not, the
+ // credential is not filled.
+ CheckFirstFillingResult(FillingResult::kFoundNoPasswordForUsername);
+}
+
+TEST_F(PasswordAutofillAgentTest, ShowPopupOnEmptyPasswordField) {
+ // Load a form with no username and update test data.
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ UpdateOnlyPasswordElement();
+ fill_data_.username_field = FormFieldData();
+ UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML);
+ fill_data_.additional_logins.clear();
+
+ password_element_.SetValue("");
+ password_element_.SetAutofillState(WebAutofillState::kNotFilled);
+
+ // Simulate the browser sending back the login info for an initial page load.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Show popup suggesstion when the password field is empty.
+ password_element_.SetValue("");
+ password_element_.SetAutofillState(WebAutofillState::kNotFilled);
+
+ SimulateSuggestionChoiceOfUsernameAndPassword(
+ password_element_, base::string16(), ASCIIToUTF16(kAlicePassword));
+ CheckSuggestions(std::string(), false);
+ EXPECT_EQ(ASCIIToUTF16(kAlicePassword), password_element_.Value().Utf16());
+ EXPECT_TRUE(password_element_.IsAutofilled());
+}
+
+TEST_F(PasswordAutofillAgentTest, ShowPopupOnAutofilledPasswordField) {
+ // Load a form with no username and update test data.
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ UpdateOnlyPasswordElement();
+ fill_data_.username_field = FormFieldData();
+ UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML);
+ fill_data_.additional_logins.clear();
+
+ password_element_.SetValue("");
+ password_element_.SetAutofillState(WebAutofillState::kNotFilled);
+
+ // Simulate the browser sending back the login info for an initial page load.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Show popup suggesstion when the password field is autofilled.
+ password_element_.SetValue("123");
+ password_element_.SetAutofillState(WebAutofillState::kAutofilled);
+
+ SimulateSuggestionChoiceOfUsernameAndPassword(
+ password_element_, base::string16(), ASCIIToUTF16(kAlicePassword));
+ CheckSuggestions(std::string(), false);
+ EXPECT_EQ(ASCIIToUTF16(kAlicePassword), password_element_.Value().Utf16());
+ EXPECT_TRUE(password_element_.IsAutofilled());
+}
+
+TEST_F(PasswordAutofillAgentTest, NotShowPopupPasswordField) {
+ // Load a form with no username and update test data.
+ LoadHTML(kVisibleFormWithNoUsernameHTML);
+ UpdateOnlyPasswordElement();
+ fill_data_.username_field = FormFieldData();
+ UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML);
+ fill_data_.additional_logins.clear();
+
+ password_element_.SetValue("");
+ password_element_.SetAutofillState(WebAutofillState::kNotFilled);
+
+ // Simulate the browser sending back the login info for an initial page load.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Do not show popup suggesstion when the password field is not-empty and not
+ // autofilled.
+ password_element_.SetValue("123");
+ password_element_.SetAutofillState(WebAutofillState::kNotFilled);
+
+ SimulateSuggestionChoiceOfUsernameAndPassword(
+ password_element_, base::string16(), ASCIIToUTF16(kAlicePassword));
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions).Times(0);
+ base::RunLoop().RunUntilIdle();
+}
+
+// Tests with fill-on-account-select enabled that if the username element is
+// read-only and filled with an unknown username, then the password field is not
+// highlighted as autofillable (regression test for https://crbug.com/442564).
+TEST_F(PasswordAutofillAgentTest,
+ FillOnAccountSelectOnlyReadonlyUnknownUsername) {
+ SetFillOnAccountSelect();
+
+ ClearUsernameAndPasswordFields();
+
+ username_element_.SetValue("foobar");
+ SetElementReadOnly(username_element_, true);
+
+ CheckUsernameDOMStatePasswordSuggestedState(std::string("foobar"), false,
+ std::string(), false);
+}
+
+// The user types in a username and a password. Then JavaScript changes password
+// field to readonly state before submit. PasswordAutofillAgent can correctly
+// process readonly password field. This test models behaviour of gmail.com.
+TEST_F(PasswordAutofillAgentTest, ReadonlyPasswordFieldOnSubmit) {
+ SimulateUsernameTyping("temp");
+ SimulatePasswordTyping("random");
+
+ // Simulate that JavaScript makes password field readonly.
+ SetElementReadOnly(password_element_, true);
+
+ SubmitForm();
+
+ // Observe that the PasswordAutofillAgent can correctly process submitted
+ // form.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "temp", "random", "");
+}
+
+// Verify that typed passwords are saved correctly when autofill and generation
+// both trigger. Regression test for https://crbug.com/493455
+TEST_F(PasswordAutofillAgentTest, PasswordGenerationTriggered_TypedPassword) {
+ SimulateOnFillPasswordForm(fill_data_);
+
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "password" /* new_password_id */, nullptr /* confirm_password_id*/);
+
+ // Generation event is triggered due to focus events.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus())
+ .Times(testing::AnyNumber());
+ SimulateUsernameTyping("NewGuy");
+ SimulatePasswordTyping("NewPassword");
+
+ SaveAndSubmitForm();
+
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "NewGuy", "NewPassword", "");
+}
+
+// Verify that generated passwords are saved correctly when autofill and
+// generation both trigger. Regression test for https://crbug.com/493455.
+TEST_F(PasswordAutofillAgentTest,
+ PasswordGenerationTriggered_GeneratedPassword) {
+ SimulateOnFillPasswordForm(fill_data_);
+
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "password" /* new_password_id */, nullptr /* confirm_password_id*/);
+ // Simulate the user clicks on a password field, that leads to showing
+ // generaiton pop-up. GeneratedPasswordAccepted can't be called without it.
+ SimulateElementClick(kPasswordName);
+
+ base::string16 password = ASCIIToUTF16("NewPass22");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ SaveAndSubmitForm();
+
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), kAliceUsername, "NewPass22",
+ "");
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ ResetPasswordGenerationWhenFieldIsAutofilled) {
+ // A user generates password.
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "password" /* new_password_id */, nullptr /* confirm_password_id*/);
+ // Simulate the user clicks on a password field, that leads to showing
+ // generaiton pop-up. GeneratedPasswordAccepted can't be called without it.
+ SimulateElementClick(kPasswordName);
+ base::string16 password = ASCIIToUTF16("NewPass22");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ // A user fills username and password, the generated value is gone.
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
+ SimulateOnFillPasswordForm(fill_data_);
+ base::RunLoop().RunUntilIdle();
+
+ // The password field shoudln't reveal the value on focusing.
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element = document.GetElementById(WebString::FromUTF8("password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement password_element = element.To<WebInputElement>();
+ EXPECT_FALSE(password_element.ShouldRevealPassword());
+ EXPECT_TRUE(password_element.IsAutofilled());
+
+ SaveAndSubmitForm();
+
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), kAliceUsername, kAlicePassword,
+ "");
+}
+
+// If password generation is enabled for a field, password autofill should not
+// show UI.
+TEST_F(PasswordAutofillAgentTest, PasswordGenerationSupersedesAutofill) {
+ LoadHTML(kSignupFormHTML);
+
+ // Update password_element_;
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("new_password"));
+ ASSERT_FALSE(element.IsNull());
+ password_element_ = element.To<WebInputElement>();
+
+ // Update fill_data_ for the new form and simulate filling. Pretend as if
+ // the password manager didn't detect a username field so it will try to
+ // show UI when the password field is focused.
+ fill_data_.wait_for_username = true;
+ fill_data_.username_field = FormFieldData();
+ fill_data_.password_field.name = ASCIIToUTF16("new_password");
+ UpdateOriginForHTML(kSignupFormHTML);
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Simulate generation triggering.
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "new_password" /* new_password_id */,
+ "confirm_password" /* confirm_password_id*/);
+
+ // Simulate the field being clicked to start typing. This should trigger
+ // generation but not password autofill.
+ SetFocused(password_element_);
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ SimulateElementClick("new_password");
+ base::RunLoop().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions).Times(0);
+ base::RunLoop().RunUntilIdle();
+
+ // On destruction the state is updated.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus())
+ .Times(testing::AnyNumber());
+}
+
+// Tests that a password change form is properly filled with the username and
+// password.
+TEST_F(PasswordAutofillAgentTest, FillSuggestionPasswordChangeForms) {
+ LoadHTML(kPasswordChangeFormHTML);
+ UpdateOriginForHTML(kPasswordChangeFormHTML);
+ UpdateUsernameAndPasswordElements();
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ for (const auto& selected_element : {username_element_, password_element_}) {
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ selected_element, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+
+ ClearUsernameAndPasswordFields();
+ }
+}
+
+// Tests that one user click on a username field is sufficient to bring up a
+// credential suggestion popup on a change password form.
+TEST_F(PasswordAutofillAgentTest,
+ SuggestionsOnUsernameFieldOfChangePasswordForm) {
+ LoadHTML(kPasswordChangeFormHTML);
+ UpdateOriginForHTML(kPasswordChangeFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ ClearUsernameAndPasswordFields();
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+ // Simulate a user clicking on the username element. This should produce a
+ // message.
+ autofill_agent_->FormControlElementClicked(username_element_, true);
+ CheckSuggestions("", true);
+}
+
+// Tests that one user click on a password field is sufficient to bring up a
+// credential suggestion popup on a change password form.
+TEST_F(PasswordAutofillAgentTest,
+ SuggestionsOnPasswordFieldOfChangePasswordForm) {
+ LoadHTML(kPasswordChangeFormHTML);
+ UpdateOriginForHTML(kPasswordChangeFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ ClearUsernameAndPasswordFields();
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+ // Simulate a user clicking on the password element. This should produce a
+ // message.
+ autofill_agent_->FormControlElementClicked(password_element_, true);
+ CheckSuggestions("", false);
+}
+
+// Tests that only the password field is autocompleted when the browser sends
+// back data with only one credentials and empty username.
+TEST_F(PasswordAutofillAgentTest, NotAutofillNoUsername) {
+ ClearField(&fill_data_.username_field);
+ fill_data_.additional_logins.clear();
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsSuggestedState("", false, kAlicePassword, true);
+}
+
+// Tests that both the username and the password fields are autocompleted
+// despite the empty username, when the browser sends back data more than one
+// credential.
+TEST_F(PasswordAutofillAgentTest,
+ AutofillNoUsernameWhenOtherCredentialsStored) {
+ fill_data_.username_field.value.clear();
+ ASSERT_FALSE(fill_data_.additional_logins.empty());
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsSuggestedState("", true, kAlicePassword, true);
+}
+
+TEST_F(PasswordAutofillAgentTest, NoForm_PromptForAJAXSubmitWithoutNavigation) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ std::string hide_elements =
+ "var password = document.getElementById('password');"
+ "password.style = 'display:none';"
+ "var username = document.getElementById('username');"
+ "username.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ FireAjaxSucceeded();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ FormData::kNotSetFormRendererId, "Bob", "mypassword", "",
+ SubmissionIndicatorEvent::XHR_SUCCEEDED);
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ NoForm_PromptForAJAXSubmitWithoutNavigation_2) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ std::string hide_elements =
+ "var password = document.getElementById('password');"
+ "password.style = 'display:none';"
+ "var username = document.getElementById('username');"
+ "username.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ base::RunLoop().RunUntilIdle();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ FormData::kNotSetFormRendererId, "Bob", "mypassword", "",
+ SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR);
+}
+
+// In this test, a <div> wrapping a form is hidden via display:none after an
+// Ajax request. The test verifies that we offer to save the password, as hiding
+// the <div> also hiding the <form>.
+TEST_F(PasswordAutofillAgentTest, PromptForAJAXSubmitAfterHidingParentElement) {
+ LoadHTML(kDivWrappedFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ std::string hide_element =
+ "var outerDiv = document.getElementById('outer');"
+ "outerDiv.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_element.c_str());
+
+ base::RunLoop().RunUntilIdle();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ GetFormUniqueRendererId("form"), "Bob", "mypassword", "",
+ SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR);
+}
+
+// In this test, a <div> wrapping a form is removed from the DOM after an Ajax
+// request. The test verifies that we offer to save the password, as removing
+// the <div> also removes the <form>.
+TEST_F(PasswordAutofillAgentTest,
+ PromptForAJAXSubmitAfterDeletingParentElement) {
+ LoadHTML(kDivWrappedFormHTML);
+ UpdateUsernameAndPasswordElements();
+ uint32_t renderer_id = GetFormUniqueRendererId("form");
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ std::string delete_element =
+ "var outerDiv = document.getElementById('outer');"
+ "var innerDiv = document.getElementById('inner');"
+ "outerDiv.removeChild(innerDiv);";
+ ExecuteJavaScriptForTests(delete_element.c_str());
+
+ base::RunLoop().RunUntilIdle();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ renderer_id, "Bob", "mypassword", "",
+ SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR);
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ NoForm_NoPromptForAJAXSubmitWithoutNavigationAndElementsVisible) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_FALSE(fake_driver_.called_password_form_submitted());
+}
+
+// Tests that no save promt is shown when an unowned form is changed and AJAX
+// completed but the form is still visible.
+TEST_F(PasswordAutofillAgentTest,
+ NoForm_NoPromptForAJAXSubmitWithoutNavigationAndNewElementAppeared) {
+ const char kNoFormHTMLWithHiddenField[] =
+ "<INPUT type='text' id='username'/>"
+ "<INPUT type='password' id='password'/>"
+ "<INPUT type='text' id='captcha' style='display:none'/>";
+ LoadHTML(kNoFormHTMLWithHiddenField);
+
+ UpdateUsernameAndPasswordElements();
+ WebElement captcha_element = GetMainFrame()->GetDocument().GetElementById(
+ WebString::FromUTF8("captcha"));
+ ASSERT_FALSE(captcha_element.IsNull());
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ // Simulate captcha element show up right before AJAX completed.
+ std::string show_captcha =
+ "var captcha = document.getElementById('captcha');"
+ "captcha.style = 'display:inline';";
+ ExecuteJavaScriptForTests(show_captcha.c_str());
+
+ FireAjaxSucceeded();
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_same_document_navigation());
+ EXPECT_FALSE(fake_driver_.called_password_form_submitted());
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ NoForm_NoPromptForAJAXSubmitWithoutNavigationAndNewElementAppeared_2) {
+ const char kNoFormHTMLWithHiddenField[] =
+ "<INPUT type='text' id='username'/>"
+ "<INPUT type='password' id='password'/>"
+ "<INPUT type='text' id='captcha' style='display:none'/>";
+ LoadHTML(kNoFormHTMLWithHiddenField);
+
+ UpdateUsernameAndPasswordElements();
+ WebElement captcha_element = GetMainFrame()->GetDocument().GetElementById(
+ WebString::FromUTF8("captcha"));
+ ASSERT_FALSE(captcha_element.IsNull());
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ // Simulate captcha element show up right after AJAX completed.
+ std::string show_captcha =
+ "var captcha = document.getElementById('captcha');"
+ "captcha.style = 'display:inline';";
+ ExecuteJavaScriptForTests(show_captcha.c_str());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(fake_driver_.called_same_document_navigation());
+ EXPECT_FALSE(fake_driver_.called_password_form_submitted());
+}
+
+// Tests that no save promt is shown when a form with empty action URL is
+// changed and AJAX completed but the form is still visible.
+TEST_F(PasswordAutofillAgentTest,
+ NoAction_NoPromptForAJAXSubmitWithoutNavigationAndNewElementAppeared) {
+ // Form without an action URL.
+ const char kHTMLWithHiddenField[] =
+ "<FORM name='LoginTestForm'>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>"
+ " <INPUT type='text' id='captcha' style='display:none'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+ // Set the valid URL so the form action URL can be generated properly.
+ LoadHTMLWithUrlOverride(kHTMLWithHiddenField, "https://www.example.com");
+
+ UpdateUsernameAndPasswordElements();
+ WebElement captcha_element = GetMainFrame()->GetDocument().GetElementById(
+ WebString::FromUTF8("captcha"));
+ ASSERT_FALSE(captcha_element.IsNull());
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ // Simulate captcha element show up right before AJAX completed.
+ captcha_element.SetAttribute("style", "display:inline;");
+
+ FireAjaxSucceeded();
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_same_document_navigation());
+ EXPECT_FALSE(fake_driver_.called_password_form_submitted());
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ NoAction_NoPromptForAJAXSubmitWithoutNavigationAndNewElementAppeared_2) {
+ // Form without an action URL.
+ const char kHTMLWithHiddenField[] =
+ "<FORM name='LoginTestForm'>"
+ " <INPUT type='text' id='username'/>"
+ " <INPUT type='password' id='password'/>"
+ " <INPUT type='text' id='captcha' style='display:none'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+ // Set the valid URL so the form action URL can be generated properly.
+ LoadHTMLWithUrlOverride(kHTMLWithHiddenField, "https://www.example.com");
+
+ UpdateUsernameAndPasswordElements();
+ WebElement captcha_element = GetMainFrame()->GetDocument().GetElementById(
+ WebString::FromUTF8("captcha"));
+ ASSERT_FALSE(captcha_element.IsNull());
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ // Simulate captcha element show up right after AJAX completed.
+ captcha_element.SetAttribute("style", "display:inline;");
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(fake_driver_.called_same_document_navigation());
+ EXPECT_FALSE(fake_driver_.called_password_form_submitted());
+}
+
+TEST_F(PasswordAutofillAgentTest, DriverIsInformedAboutUnfillableField) {
+ EXPECT_EQ(FocusedFieldType::kUnknown, fake_driver_.last_focused_field_type());
+ SimulateElementClick(kPasswordName);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillablePasswordField,
+ fake_driver_.last_focused_field_type());
+
+ // Even though the focused element is a username field, it should be treated
+ // as unfillable, since it is read-only.
+ SetElementReadOnly(username_element_, true);
+ FocusElement(kUsernameName);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kUnfillableElement,
+ fake_driver_.last_focused_field_type());
+}
+
+TEST_F(PasswordAutofillAgentTest, DriverIsInformedAboutFillableFields) {
+ SimulateElementClick("random_field");
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillableNonSearchField,
+ fake_driver_.last_focused_field_type());
+
+ // A username field without fill data is indistinguishable from any other text
+ // field.
+ SimulateElementClick(kUsernameName);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillableNonSearchField,
+ fake_driver_.last_focused_field_type());
+
+ SimulateElementClick(kPasswordName);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillablePasswordField,
+ fake_driver_.last_focused_field_type());
+
+ // A username field with fill data should be detected.
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateElementClick(kUsernameName);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillableUsernameField,
+ fake_driver_.last_focused_field_type());
+}
+
+TEST_F(PasswordAutofillAgentTest, DriverIsInformedAboutFillablSearchField) {
+ LoadHTML(kSearchFieldHTML);
+ SimulateElementClick(kSearchField);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillableSearchField,
+ fake_driver_.last_focused_field_type());
+}
+
+TEST_F(PasswordAutofillAgentTest, DriverIsInformedAboutFillableTextArea) {
+ LoadHTML(kSocialNetworkPostFormHTML);
+
+ SimulateElementClick(kSocialMediaTextArea);
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillableTextArea,
+ fake_driver_.last_focused_field_type());
+}
+
+// Tests that credential suggestions are autofilled on a password (and change
+// password) forms having either ambiguous or empty name.
+TEST_F(PasswordAutofillAgentTest,
+ SuggestionsOnFormContainingAmbiguousOrEmptyNames) {
+ const char kEmpty[] = "";
+ const char kDummyUsernameField[] = "anonymous_username";
+ const char kDummyPasswordField[] = "anonymous_password";
+ const char kFormContainsEmptyNamesHTML[] =
+ "<FORM name='WithoutNameIdForm' action='http://www.bidule.com' >"
+ " <INPUT type='text' placeholder='username'/>"
+ " <INPUT type='password' placeholder='Password'/>"
+ " <INPUT type='submit' />"
+ "</FORM>";
+
+ const char kFormContainsAmbiguousNamesHTML[] =
+ "<FORM name='AmbiguousNameIdForm' action='http://www.bidule.com' >"
+ " <INPUT type='text' id='credentials' placeholder='username' />"
+ " <INPUT type='password' id='credentials' placeholder='Password' />"
+ " <INPUT type='submit' />"
+ "</FORM>";
+
+ const char kChangePasswordFormContainsEmptyNamesHTML[] =
+ "<FORM name='ChangePwd' action='http://www.bidule.com' >"
+ " <INPUT type='text' placeholder='username' />"
+ " <INPUT type='password' placeholder='Old Password' "
+ " autocomplete='current-password' />"
+ " <INPUT type='password' placeholder='New Password' "
+ " autocomplete='new-password' />"
+ " <INPUT type='submit' />"
+ "</FORM>";
+
+ const char kChangePasswordFormButNoUsername[] =
+ "<FORM name='ChangePwdButNoUsername' action='http://www.bidule.com' >"
+ " <INPUT type='password' placeholder='Old Password' "
+ " autocomplete='current-password' />"
+ " <INPUT type='password' placeholder='New Password' "
+ " autocomplete='new-password' />"
+ " <INPUT type='submit' />"
+ "</FORM>";
+
+ const char kChangePasswordFormButNoOldPassword[] =
+ "<FORM name='ChangePwdButNoOldPwd' action='http://www.bidule.com' >"
+ " <INPUT type='text' placeholder='username' />"
+ " <INPUT type='password' placeholder='New Password' "
+ " autocomplete='new-password' />"
+ " <INPUT type='password' placeholder='Retype Password' "
+ " autocomplete='new-password' />"
+ " <INPUT type='submit' />"
+ "</FORM>";
+
+ const char kChangePasswordFormButNoAutocompleteAttribute[] =
+ "<FORM name='ChangePwdButNoAutocomplete' action='http://www.bidule.com'>"
+ " <INPUT type='text' placeholder='username' />"
+ " <INPUT type='password' placeholder='Old Password' />"
+ " <INPUT type='password' placeholder='New Password' />"
+ " <INPUT type='submit' />"
+ "</FORM>";
+
+ const struct {
+ const char* html_form;
+ bool does_trigger_autocomplete_on_fill;
+ const char* fill_data_username_field_name;
+ const char* fill_data_password_field_name;
+ const char* expected_username_suggestions;
+ const char* expected_password_suggestions;
+ bool expected_is_username_autofillable;
+ bool expected_is_password_autofillable;
+ } test_cases[] = {
+ // Password form without name or id attributes specified for the input
+ // fields.
+ {kFormContainsEmptyNamesHTML, true, kDummyUsernameField,
+ kDummyPasswordField, kAliceUsername, kAlicePassword, true, true},
+
+ // Password form with ambiguous name or id attributes specified for the
+ // input fields.
+ {kFormContainsAmbiguousNamesHTML, true, "credentials", "credentials",
+ kAliceUsername, kAlicePassword, true, true},
+
+ // Change password form without name or id attributes specified for the
+ // input fields and |autocomplete='current-password'| attribute for old
+ // password field.
+ {kChangePasswordFormContainsEmptyNamesHTML, true, kDummyUsernameField,
+ kDummyPasswordField, kAliceUsername, kAlicePassword, true, true},
+
+ // Change password form without username field.
+ {kChangePasswordFormButNoUsername, true, kEmpty, kDummyPasswordField,
+ kEmpty, kAlicePassword, false, true},
+
+ // Change password form without name or id attributes specified for the
+ // input fields and |autocomplete='new-password'| attribute for new
+ // password fields. This form *do not* trigger |OnFillPasswordForm| from
+ // browser.
+ {kChangePasswordFormButNoOldPassword, false, kDummyUsernameField,
+ kDummyPasswordField, kEmpty, kEmpty, false, false},
+
+ // Change password form without name or id attributes specified for the
+ // input fields but |autocomplete='current-password'| or
+ // |autocomplete='new-password'| attributes are missing for old and new
+ // password fields respectively.
+ {kChangePasswordFormButNoAutocompleteAttribute, true, kDummyUsernameField,
+ kDummyPasswordField, kAliceUsername, kAlicePassword, true, true},
+ };
+
+ for (const auto& test_case : test_cases) {
+ SCOPED_TRACE(testing::Message() << "html_form: " << test_case.html_form
+ << ", fill_data_username_field_name: "
+ << test_case.fill_data_username_field_name
+ << ", fill_data_password_field_name: "
+ << test_case.fill_data_password_field_name);
+
+ // Load a password form.
+ LoadHTML(test_case.html_form);
+
+ // Get the username and password form input elelments.
+ blink::WebDocument document = GetMainFrame()->GetDocument();
+ blink::WebVector<WebFormElement> forms;
+ document.Forms(forms);
+ WebFormElement form_element = forms[0];
+ std::vector<blink::WebFormControlElement> control_elements =
+ form_util::ExtractAutofillableElementsInForm(form_element);
+ bool has_fillable_username =
+ (kEmpty != test_case.fill_data_username_field_name);
+ if (has_fillable_username) {
+ username_element_ = control_elements[0].To<WebInputElement>();
+ password_element_ = control_elements[1].To<WebInputElement>();
+ } else {
+ password_element_ = control_elements[0].To<WebInputElement>();
+ }
+
+ UpdateOriginForHTML(test_case.html_form);
+ if (test_case.does_trigger_autocomplete_on_fill) {
+ // Prepare |fill_data_| to trigger autocomplete.
+ fill_data_.username_field.name =
+ ASCIIToUTF16(test_case.fill_data_username_field_name);
+ fill_data_.password_field.name =
+ ASCIIToUTF16(test_case.fill_data_password_field_name);
+ fill_data_.additional_logins.clear();
+
+ ClearUsernameAndPasswordFields();
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ if (has_fillable_username) {
+ SimulateSuggestionChoice(username_element_);
+ } else {
+ SimulateSuggestionChoice(password_element_);
+ }
+
+ // The username and password should now have been autocompleted.
+ CheckTextFieldsDOMState(test_case.expected_username_suggestions,
+ test_case.expected_is_username_autofillable,
+ test_case.expected_password_suggestions,
+ test_case.expected_is_password_autofillable);
+ }
+ }
+}
+
+// The password manager autofills credentials, the user chooses another
+// credentials option from a suggestion dropdown and then the user submits a
+// form. This test verifies that the browser process receives submitted
+// username/password from the renderer process.
+TEST_F(PasswordAutofillAgentTest, RememberChosenUsernamePassword) {
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateSuggestionChoiceOfUsernameAndPassword(username_element_,
+ ASCIIToUTF16(kBobUsername),
+ ASCIIToUTF16(kBobPassword));
+
+ SaveAndSubmitForm();
+
+ // Observe that the PasswordAutofillAgent sends to the browser selected
+ // credentials.
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), kBobUsername, kBobPassword, "");
+}
+
+// Tests that we can correctly suggest to autofill two forms without username
+// fields.
+TEST_F(PasswordAutofillAgentTest, ShowSuggestionForNonUsernameFieldForms) {
+ LoadHTML(kTwoNoUsernameFormsHTML);
+ fill_data_.username_field.name.clear();
+ fill_data_.username_field.value.clear();
+ UpdateOriginForHTML(kTwoNoUsernameFormsHTML);
+ SimulateOnFillPasswordForm(fill_data_);
+
+ SimulateElementClick("password1");
+ CheckSuggestions(std::string(), false);
+ SimulateElementClick("password2");
+ CheckSuggestions(std::string(), false);
+}
+
+// Tests that password manager sees both autofill assisted and user entered
+// data on saving that is triggered by AJAX succeeded.
+TEST_F(PasswordAutofillAgentTest,
+ UsernameChangedAfterPasswordInput_AJAXSucceeded) {
+ for (auto change_source :
+ {FieldChangeSource::USER, FieldChangeSource::AUTOFILL,
+ FieldChangeSource::USER_AUTOFILL}) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+ SimulateUsernameFieldChange(change_source);
+
+ // Hide form elements to simulate successful login.
+ std::string hide_elements =
+ "var password = document.getElementById('password');"
+ "password.style = 'display:none';"
+ "var username = document.getElementById('username');"
+ "username.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+
+ FireAjaxSucceeded();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ FormData::kNotSetFormRendererId, "Alice", "mypassword", "",
+ SubmissionIndicatorEvent::XHR_SUCCEEDED);
+ }
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ UsernameChangedAfterPasswordInput_AJAXSucceeded_2) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+ SimulateUsernameTyping("Alice");
+
+ FireAjaxSucceeded();
+
+ // Hide form elements to simulate successful login.
+ std::string hide_elements =
+ "var password = document.getElementById('password');"
+ "password.style = 'display:none';"
+ "var username = document.getElementById('username');"
+ "username.style = 'display:none';";
+ ExecuteJavaScriptForTests(hide_elements.c_str());
+ base::RunLoop().RunUntilIdle();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ FormData::kNotSetFormRendererId, "Alice", "mypassword", "",
+ SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR);
+}
+
+// Tests that password manager sees both autofill assisted and user entered
+// data on saving that is triggered by form submission.
+// Disabled on Win due to https://crbug.com/835865.
+#if defined(OS_WIN)
+#define MAYBE_UsernameChangedAfterPasswordInput_FormSubmitted \
+ DISABLED_UsernameChangedAfterPasswordInput_FormSubmitted
+#else
+#define MAYBE_UsernameChangedAfterPasswordInput_FormSubmitted \
+ UsernameChangedAfterPasswordInput_FormSubmitted
+#endif
+TEST_F(PasswordAutofillAgentTest,
+ MAYBE_UsernameChangedAfterPasswordInput_FormSubmitted) {
+ for (auto change_source :
+ {FieldChangeSource::USER, FieldChangeSource::AUTOFILL,
+ FieldChangeSource::USER_AUTOFILL}) {
+ LoadHTML(kFormHTML);
+ UpdateUsernameAndPasswordElements();
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+ SimulateUsernameFieldChange(change_source);
+
+ SaveAndSubmitForm();
+
+ ExpectFormSubmittedWithUsernameAndPasswords(
+ GetFormUniqueRendererId("LoginTestForm"), "Alice", "mypassword", "");
+ }
+}
+
+// Tests that a suggestion dropdown is shown on a password field even if a
+// username field is present.
+TEST_F(PasswordAutofillAgentTest, SuggestPasswordFieldSignInForm) {
+ // Simulate the browser sending back the login info.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Call SimulateElementClick() to produce a user gesture on the page so
+ // autofill will actually fill.
+ SimulateElementClick(kUsernameName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ // Simulate a user clicking on the password element. This should produce a
+ // dropdown with suggestion of all available usernames.
+ autofill_agent_->FormControlElementClicked(password_element_, false);
+ CheckSuggestions("", false);
+}
+
+// Tests that a suggestion dropdown is shown on each password field. But when a
+// user chose one of the fields to autofill, a suggestion dropdown will be shown
+// only on this field.
+TEST_F(PasswordAutofillAgentTest, SuggestMultiplePasswordFields) {
+ LoadHTML(kPasswordChangeFormHTML);
+ UpdateOriginForHTML(kPasswordChangeFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ // Simulate the browser sending back the login info.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Call SimulateElementClick() to produce a user gesture on the page so
+ // autofill will actually fill.
+ SimulateElementClick(kUsernameName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ // Simulate a user clicking on the password elements. This should produce
+ // dropdowns with suggestion of all available usernames.
+ SimulateElementClick("password");
+ CheckSuggestions("", false);
+
+ SimulateElementClick("newpassword");
+ CheckSuggestions("", false);
+
+ SimulateElementClick("confirmpassword");
+ CheckSuggestions("", false);
+
+ // The user chooses to autofill the current password field.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ password_element_, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+
+ // Simulate a user clicking on not autofilled password fields. This should
+ // produce no suggestion dropdowns.
+ SimulateElementClick("newpassword");
+ SimulateElementClick("confirmpassword");
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions).Times(0);
+ base::RunLoop().RunUntilIdle();
+
+ // But when the user clicks on the autofilled password field again it should
+ // still produce a suggestion dropdown.
+ SimulateElementClick("password");
+ CheckSuggestions("", false);
+}
+
+TEST_F(PasswordAutofillAgentTest, ShowAutofillSignaturesFlag) {
+ // Tests that form signature is set iff the flag is enabled.
+ const bool kFalseTrue[] = {false, true};
+ for (bool show_signatures : kFalseTrue) {
+ if (show_signatures)
+ EnableShowAutofillSignatures();
+
+ LoadHTML(kFormHTML);
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebFormElement form_element =
+ document.GetElementById(WebString::FromASCII("LoginTestForm"))
+ .To<WebFormElement>();
+ ASSERT_FALSE(form_element.IsNull());
+
+ // Check only form signature attribute. The full test is in
+ // "PasswordGenerationAgentTestForHtmlAnnotation.*".
+ WebString form_signature_attribute = WebString::FromASCII("form_signature");
+ EXPECT_EQ(form_element.HasAttribute(form_signature_attribute),
+ show_signatures);
+ }
+}
+
+// Tests that a suggestion dropdown is shown even if JavaScripts updated field
+// names.
+TEST_F(PasswordAutofillAgentTest, SuggestWhenJavaScriptUpdatesFieldNames) {
+ // Simulate that JavaScript updated field names.
+ auto fill_data = fill_data_;
+ fill_data.username_field.name += ASCIIToUTF16("1");
+ fill_data.password_field.name += ASCIIToUTF16("1");
+ // Simulate the browser sending back the login info.
+ SimulateOnFillPasswordForm(fill_data);
+
+ // Call SimulateElementClick() to produce a user gesture on the page so
+ // autofill will actually fill.
+ SimulateElementClick(kUsernameName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ // Simulate a user clicking on the password element. This should produce a
+ // dropdown with suggestion of all available usernames.
+ autofill_agent_->FormControlElementClicked(password_element_, false);
+ CheckSuggestions("", false);
+}
+
+// Checks that a same-document navigation form submission could have an empty
+// username.
+TEST_F(PasswordAutofillAgentTest,
+ SameDocumentNavigationSubmissionUsernameIsEmpty) {
+ username_element_.SetValue(WebString());
+ SimulatePasswordTyping("random");
+ uint32_t renderer_id = GetFormUniqueRendererId("LoginTestForm");
+
+ // Simulate that JavaScript removes the submitted form from DOM. That means
+ // that a submission was successful.
+ std::string remove_form =
+ "var form = document.getElementById('LoginTestForm');"
+ "form.parentNode.removeChild(form);";
+ ExecuteJavaScriptForTests(remove_form.c_str());
+
+ FireDidCommitProvisionalLoad();
+
+ ExpectSameDocumentNavigationWithUsernameAndPasswords(
+ renderer_id, std::string(), "random", std::string(),
+ SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION);
+}
+
+#if BUILDFLAG(SAFE_BROWSING_DB_LOCAL)
+// Verify CheckSafeBrowsingReputation() is called when user starts filling
+// a password field, and that this function is only called once.
+TEST_F(PasswordAutofillAgentTest,
+ CheckSafeBrowsingReputationWhenUserStartsFillingUsernamePassword) {
+ ASSERT_EQ(0, fake_driver_.called_check_safe_browsing_reputation_cnt());
+ // Simulate a click on password field to set its on focus,
+ // CheckSafeBrowsingReputation() should be called.
+ SimulateElementClick(kPasswordName);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, fake_driver_.called_check_safe_browsing_reputation_cnt());
+
+ // Subsequent editing will not trigger CheckSafeBrowsingReputation.
+ SimulatePasswordTyping("modify");
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, fake_driver_.called_check_safe_browsing_reputation_cnt());
+
+ // No CheckSafeBrowsingReputation() call on username field click.
+ SimulateElementClick(kUsernameName);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, fake_driver_.called_check_safe_browsing_reputation_cnt());
+
+ SimulateElementClick(kPasswordName);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, fake_driver_.called_check_safe_browsing_reputation_cnt());
+
+ // Navigate to another page and click on password field,
+ // CheckSafeBrowsingReputation() should be triggered again.
+ LoadHTML(kFormHTML);
+ SimulateElementClick(kPasswordName);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, fake_driver_.called_check_safe_browsing_reputation_cnt());
+}
+#endif
+
+// Tests that username/password are autofilled when JavaScript is changing url
+// between discovering a form and receving credentials from the browser process.
+TEST_F(PasswordAutofillAgentTest, AutocompleteWhenPageUrlIsChanged) {
+ // Simulate that JavaScript changes url.
+ fill_data_.origin = GURL(fill_data_.origin.possibly_invalid_spec() + "/path");
+
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username and password should have been autocompleted.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+}
+
+// Regression test for https://crbug.com/728028.
+TEST_F(PasswordAutofillAgentTest, NoForm_MultipleAJAXEventsWithoutSubmission) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateUsernameTyping("Bob");
+ SimulatePasswordTyping("mypassword");
+
+ FireAjaxSucceeded();
+
+ base::RunLoop().RunUntilIdle();
+
+ // Repeatedly occurring AJAX events without removing the input elements
+ // shouldn't be treated as a password submission.
+
+ FireAjaxSucceeded();
+
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_FALSE(fake_driver_.called_password_form_submitted());
+ ASSERT_FALSE(static_cast<bool>(fake_driver_.password_form_submitted()));
+}
+
+TEST_F(PasswordAutofillAgentTest, ManualFallbackForSaving) {
+ EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(testing::_)).Times(0);
+ // The users enters a username. No password - no fallback.
+ SimulateUsernameTyping(kUsernameName);
+ EXPECT_EQ(0, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // The user enters a password.
+ SimulatePasswordTyping(kPasswordName);
+ // SimulateUsernameTyping/SimulatePasswordTyping calls
+ // PasswordAutofillAgent::UpdateStateForTextChange only once.
+ EXPECT_EQ(1, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Remove one character from the password value.
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
+ EXPECT_EQ(2, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Add one character to the username value.
+ SetFocused(username_element_);
+ SimulateUserTypingASCIICharacter('a', true);
+ EXPECT_EQ(3, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Remove username value.
+ SimulateUsernameTyping("");
+ EXPECT_EQ(4, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Change the password. Despite of empty username the fallback is still
+ // there.
+ SetFocused(password_element_);
+ SimulateUserTypingASCIICharacter('a', true);
+ EXPECT_EQ(5, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Remove password value. The fallback should be disabled.
+ SimulatePasswordTyping("");
+ EXPECT_EQ(0, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // The user enters new password. Show the fallback again.
+ SimulateUserTypingASCIICharacter('a', true);
+ EXPECT_EQ(1, fake_driver_.called_show_manual_fallback_for_saving_count());
+}
+
+TEST_F(PasswordAutofillAgentTest, ManualFallbackForSaving_PasswordChangeForm) {
+ LoadHTML(kPasswordChangeFormHTML);
+ UpdateOriginForHTML(kPasswordChangeFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ // No password to save yet - no fallback.
+ SimulateUsernameTyping(kUsernameName);
+ EXPECT_EQ(0, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // The user enters in the current password field. The fallback should be
+ // available to save the entered value.
+ SimulatePasswordTyping(kPasswordName);
+ // SimulateUsernameTyping/SimulatePasswordTyping calls
+ // PasswordAutofillAgent::UpdateStateForTextChange only once.
+ EXPECT_EQ(1, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // The user types into the new password field. The fallback should be updated.
+ WebInputElement new_password = GetInputElementByID("newpassword");
+ ASSERT_FALSE(new_password.IsNull());
+ SetFocused(new_password);
+ SimulateUserTypingASCIICharacter('a', true);
+ EXPECT_EQ(2, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Edits of the confirmation password field trigger fallback updates.
+ WebInputElement confirmation_password =
+ GetInputElementByID("confirmpassword");
+ ASSERT_FALSE(confirmation_password.IsNull());
+ SetFocused(confirmation_password);
+ SimulateUserTypingASCIICharacter('a', true);
+ EXPECT_EQ(3, fake_driver_.called_show_manual_fallback_for_saving_count());
+
+ // Clear all password fields. The fallback should be disabled.
+ SimulatePasswordTyping("");
+ SimulateUserInputChangeForElement(&new_password, "");
+ SimulateUserInputChangeForElement(&confirmation_password, "");
+ EXPECT_EQ(0, fake_driver_.called_show_manual_fallback_for_saving_count());
+}
+
+// Tests that information about Gaia reauthentication form is sent to the
+// browser with information that the password should not be saved.
+TEST_F(PasswordAutofillAgentTest, GaiaReauthenticationFormIgnored) {
+ // HTML is already loaded in test SetUp method, so information about password
+ // forms was already sent to the |fake_driver_|. Hence it should be reset.
+ fake_driver_.reset_password_forms_calls();
+
+ const char kGaiaReauthenticationFormHTML[] =
+ "<FORM id='ReauthenticationForm'>"
+ " <INPUT type='hidden' name='continue' "
+ "value='https://passwords.google.com/'>"
+ " <INPUT type='hidden' name='rart'>"
+ " <INPUT type='password' id='password'/>"
+ " <INPUT type='submit' value='Login'/>"
+ "</FORM>";
+
+ LoadHTMLWithUrlOverride(kGaiaReauthenticationFormHTML,
+ "https://accounts.google.com");
+ UpdateOnlyPasswordElement();
+
+ // Simulate a user clicking on the password element.
+ autofill_agent_->FormControlElementClicked(password_element_, false);
+
+ fake_driver_.Flush();
+ // Check that information about Gaia reauthentication is sent to the browser.
+ ASSERT_TRUE(fake_driver_.called_password_forms_parsed());
+ const std::vector<autofill::PasswordForm>& parsed_forms =
+ fake_driver_.password_forms_parsed().value();
+ ASSERT_EQ(1u, parsed_forms.size());
+ EXPECT_TRUE(parsed_forms[0].form_data.is_gaia_with_skip_save_password_form);
+}
+
+TEST_F(PasswordAutofillAgentTest,
+ UpdateSuggestionsIfNewerCredentialsAreSupplied) {
+ // Supply old fill data
+ password_autofill_agent_->FillPasswordForm(fill_data_);
+ // The username and password should have been autocompleted.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ // Change fill data
+ fill_data_.password_field.value = ASCIIToUTF16("a-changed-password");
+ // Supply changed fill data
+ password_autofill_agent_->FillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, "a-changed-password",
+ true);
+}
+
+TEST_F(PasswordAutofillAgentTest, SuggestLatestCredentials) {
+ password_autofill_agent_->FillPasswordForm(fill_data_);
+ SimulateElementClick(kPasswordName);
+ EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+ base::RunLoop().RunUntilIdle();
+
+ // Change fill data
+ fill_data_.username_field.value = ASCIIToUTF16("a-changed-username");
+
+ password_autofill_agent_->FillPasswordForm(fill_data_);
+ SimulateElementClick(kPasswordName);
+ // Empty value because nothing was typed into the field.
+ CheckSuggestions("", false);
+}
+
+// Tests that PSL matched password is not autofilled even when there is
+// a prefilled username.
+TEST_F(PasswordAutofillAgentTest, PSLMatchedPasswordIsNotAutofill) {
+ const char kFormWithPrefilledUsernameHTML[] =
+ "<FORM id='LoginTestForm' action='http://www.bidule.com'>"
+ " <INPUT type='text' id='username' value='prefilledusername'/>"
+ " <INPUT type='password' id='password'/>"
+ "</FORM>";
+ LoadHTML(kFormWithPrefilledUsernameHTML);
+
+ // Retrieve the input elements so the test can access them.
+ UpdateUsernameAndPasswordElements();
+
+ // Set the expected form origin and action URLs.
+ UpdateOriginForHTML(kFormWithPrefilledUsernameHTML);
+
+ // Add PSL matched credentials with username equal to prefilled one.
+ PasswordAndRealm psl_credentials;
+ psl_credentials.password = ASCIIToUTF16("pslpassword");
+ // Non-empty realm means PSL matched credentials.
+ psl_credentials.realm = "example.com";
+ fill_data_.additional_logins[ASCIIToUTF16("prefilledusername")] =
+ psl_credentials;
+
+ // Simulate the browser sending back the login info, it triggers the
+ // autocomplete.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Test that PSL matched password is not autofilled.
+ CheckUsernameDOMStatePasswordSuggestedState("prefilledusername", false, "",
+ false);
+}
+
+// Tests that the password form is filled as expected on load.
+TEST_F(PasswordAutofillAgentTest, FillOnLoadWith) {
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ CheckFirstFillingResult(FillingResult::kSuccess);
+}
+
+// Tests that the password form is filled as expected on load even if form/field
+// attributes were changed between from load and filling.
+TEST_F(PasswordAutofillAgentTest, FillOnLoadFormChanged) {
+ // Simulate JavaScript changed field names and form name.
+ fill_data_.name += ASCIIToUTF16("1");
+ fill_data_.username_field.name += ASCIIToUTF16("1");
+ fill_data_.password_field.name += ASCIIToUTF16("1");
+
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+}
+
+TEST_F(PasswordAutofillAgentTest, FillOnLoadNoForm) {
+ LoadHTML(kNoFormHTML);
+ UpdateUsernameAndPasswordElements();
+
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+}
+
+TEST_F(PasswordAutofillAgentTest, FillOnLoadNoUsername) {
+ LoadHTML(kTwoNoUsernameFormsHTML);
+ username_element_.Reset();
+ fill_data_.username_field.value.clear();
+ password_element_ = GetInputElementByID("password2");
+ UpdateRendererIDs();
+ SimulateOnFillPasswordForm(fill_data_);
+ EXPECT_EQ(kAlicePassword, password_element_.SuggestedValue().Utf8());
+}
+
+TEST_F(PasswordAutofillAgentTest, MayUsePlaceholderNoPlaceholder) {
+ fill_data_.username_may_use_prefilled_placeholder = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+}
+
+TEST_F(PasswordAutofillAgentTest, MayUsePlaceholderAndPlaceholderOnForm) {
+ username_element_.SetValue(WebString::FromUTF8("placeholder"));
+ fill_data_.username_may_use_prefilled_placeholder = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+}
+
+TEST_F(PasswordAutofillAgentTest, NoMayUsePlaceholderAndPlaceholderOnForm) {
+ username_element_.SetValue(WebString::FromUTF8("placeholder"));
+ fill_data_.username_may_use_prefilled_placeholder = false;
+
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsDOMState("placeholder", false, "", false);
+}
+
+TEST_F(PasswordAutofillAgentTest, AutofillsAfterUserGesture) {
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ password_autofill_agent_->UserGestureObserved();
+ // It's a way to call PasswordValueGatekeeper::Reset().
+ password_autofill_agent_->ReadyToCommitNavigation(nullptr);
+
+ fill_data_.username_may_use_prefilled_placeholder = true;
+ fill_data_.password_field.value = ASCIIToUTF16(kBobPassword);
+
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsStateForElements(
+ username_element_, kAliceUsername,
+ /* username_autofilled */ true, password_element_, kBobPassword,
+ /* password_autofilled */ true, /* check_suggested_username */ false,
+ /* check_suggested_username */ true);
+ /// CheckTextFieldsSuggestedState(kAliceUsername, true, kBobPassword, true);
+}
+
+TEST_F(PasswordAutofillAgentTest, RestoresAfterJavaScriptModification) {
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ fake_driver_.reset_password_forms_calls();
+
+ static const char script[] = "document.getElementById('username').value = ''";
+ ExecuteJavaScriptForTests(script);
+ CheckTextFieldsSuggestedState("", false, kAlicePassword, true);
+
+ password_autofill_agent_->OnDynamicFormsSeen();
+ CheckTextFieldsSuggestedState(kAliceUsername, false, kAlicePassword, true);
+
+ EXPECT_FALSE(fake_driver_.called_password_forms_parsed());
+ EXPECT_FALSE(fake_driver_.called_password_forms_rendered());
+}
+
+TEST_F(PasswordAutofillAgentTest, DoNotRestoreWhenFormStructureWasChanged) {
+ SimulateOnFillPasswordForm(fill_data_);
+ CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
+
+ static const char clear_username_script[] =
+ "document.getElementById('username').value = ''";
+ ExecuteJavaScriptForTests(clear_username_script);
+ static const char add_input_element_script[] =
+ "document.getElementById('LoginTestForm').appendChild(document."
+ "createElement('input'))";
+ ExecuteJavaScriptForTests(add_input_element_script);
+ CheckTextFieldsSuggestedState("", false, kAlicePassword, true);
+
+ password_autofill_agent_->OnDynamicFormsSeen();
+ CheckTextFieldsSuggestedState("", false, kAlicePassword, true);
+}
+
+// Tests that a single username is filled and is exposed to JavaScript only
+// after user gesture.
+TEST_F(PasswordAutofillAgentTest, FillOnLoadSingleUsername) {
+ // Simulate filling single username by clearing password fill data.
+ ClearField(&fill_data_.password_field);
+
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // The username should have been autofilled.
+ CheckTextFieldsSuggestedState(kAliceUsername, true, std::string(), false);
+
+ // However, it should have filled with the suggested value, it should not have
+ // filled with DOM accessible value.
+ CheckTextFieldsDOMState(std::string(), true, std::string(), false);
+
+ // Simulate a user click so that the username field's real value is filled.
+ SimulateElementClick(kUsernameName);
+ CheckTextFieldsDOMState(kAliceUsername, true, std::string(), false);
+}
+
+// Tests that |PreviewSuggestion| properly previews the single username.
+TEST_F(PasswordAutofillAgentTest, SingleUsernamePreviewSuggestion) {
+ ClearField(&fill_data_.password_field);
+ // Simulate the browser sending the login info, but set |wait_for_username| to
+ // prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ username_element_, kAliceUsername, kAlicePassword));
+ CheckTextFieldsSuggestedState(kAliceUsername, true, std::string(), false);
+
+ // Try previewing with a username different from the one that was initially
+ // sent to the renderer.
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ username_element_, kBobUsername, kCarolPassword));
+ CheckTextFieldsSuggestedState(kBobUsername, true, std::string(), false);
+}
+
+// Tests that |FillSuggestion| properly fills the single username.
+TEST_F(PasswordAutofillAgentTest, SingleUsernameFillSuggestion) {
+ ClearField(&fill_data_.password_field);
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Neither field should be autocompleted.
+ CheckTextFieldsDOMState(std::string(), false, std::string(), false);
+
+ // After filling with the suggestion, the username field should be filled.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ username_element_, ASCIIToUTF16(kAliceUsername),
+ ASCIIToUTF16(kAlicePassword)));
+ CheckTextFieldsDOMState(kAliceUsername, true, std::string(), false);
+ int username_length = strlen(kAliceUsername);
+ CheckUsernameSelection(username_length, username_length);
+
+ // Try Filling with a suggestion with a username different from the one that
+ // was initially sent to the renderer.
+ EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
+ username_element_, ASCIIToUTF16(kBobUsername),
+ ASCIIToUTF16(kCarolPassword)));
+ CheckTextFieldsDOMState(kBobUsername, true, std::string(), false);
+ username_length = strlen(kBobUsername);
+ CheckUsernameSelection(username_length, username_length);
+}
+
+// Tests that |ClearPreview| properly clears previewed single username.
+TEST_F(PasswordAutofillAgentTest, SingleUsernameClearPreview) {
+ ClearField(&fill_data_.password_field);
+ ResetFieldState(&username_element_, "ali", WebAutofillState::kPreviewed);
+ username_element_.SetSelectionRange(3, 3);
+
+ // Simulate the browser sending the login info, but set |wait_for_username|
+ // to prevent the form from being immediately filled.
+ fill_data_.wait_for_username = true;
+ SimulateOnFillPasswordForm(fill_data_);
+
+ CheckTextFieldsDOMState("ali", true, std::string(), false);
+
+ EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
+ username_element_, kAliceUsername, kAlicePassword));
+ EXPECT_TRUE(
+ password_autofill_agent_->DidClearAutofillSelection(username_element_));
+
+ EXPECT_TRUE(username_element_.SuggestedValue().IsEmpty());
+ CheckTextFieldsDOMState("ali", true, std::string(), false);
+ CheckUsernameSelection(3, 3);
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/password_generation_agent_browsertest.cc b/chromium/chrome/renderer/autofill/password_generation_agent_browsertest.cc
new file mode 100644
index 00000000000..96a335c934a
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/password_generation_agent_browsertest.cc
@@ -0,0 +1,1193 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/renderer/autofill/fake_mojo_password_manager_driver.h"
+#include "chrome/renderer/autofill/fake_password_generation_driver.h"
+#include "chrome/renderer/autofill/password_generation_test_utils.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "components/autofill/content/renderer/form_autofill_util.h"
+#include "components/autofill/content/renderer/password_generation_agent.h"
+#include "components/autofill/content/renderer/test_password_autofill_agent.h"
+#include "components/autofill/core/common/autofill_switches.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/password_generation_util.h"
+#include "components/password_manager/core/common/password_manager_features.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_widget.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+using autofill::mojom::FocusedFieldType;
+using base::ASCIIToUTF16;
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebInputElement;
+using blink::WebNode;
+using blink::WebString;
+using testing::_;
+using testing::AnyNumber;
+using testing::AtMost;
+
+namespace autofill {
+
+constexpr char kSigninFormHTML[] =
+ "<FORM name = 'blah' action = 'http://www.random.com/'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'password'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>";
+
+constexpr char kAccountCreationFormHTML[] =
+ "<FORM id = 'blah' action = 'http://www.random.com/pa/th?q=1&p=3#first'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'first_password' size = 5/>"
+ " <INPUT type = 'password' id = 'second_password' size = 5/> "
+ " <INPUT type = 'text' id = 'address'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>";
+
+constexpr char kAccountCreationNoForm[] =
+ "<INPUT type = 'text' id = 'username'/> "
+ "<INPUT type = 'password' id = 'first_password' size = 5/>"
+ "<INPUT type = 'password' id = 'second_password' size = 5/> "
+ "<INPUT type = 'text' id = 'address'/> "
+ "<INPUT type = 'button' id = 'dummy'/> "
+ "<INPUT type = 'submit' value = 'LOGIN' />";
+
+constexpr char kAccountCreationNoIds[] =
+ "<FORM action = 'http://www.random.com/pa/th?q=1&p=3#first'> "
+ " <INPUT type = 'text'/> "
+ " <INPUT type = 'password' class='first_password'/>"
+ " <INPUT type = 'password' class='second_password'/> "
+ " <INPUT type = 'text'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN'/>"
+ "</FORM>";
+
+constexpr char kDisabledElementAccountCreationFormHTML[] =
+ "<FORM name = 'blah' action = 'http://www.random.com/'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'first_password' "
+ " autocomplete = 'off' size = 5/>"
+ " <INPUT type = 'password' id = 'second_password' size = 5/> "
+ " <INPUT type = 'text' id = 'address'/> "
+ " <INPUT type = 'text' id = 'disabled' disabled/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>";
+
+constexpr char kHiddenPasswordAccountCreationFormHTML[] =
+ "<FORM name = 'blah' action = 'http://www.random.com/'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'first_password'/> "
+ " <INPUT type = 'password' id = 'second_password' style='display:none'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>";
+
+constexpr char kMultipleAccountCreationFormHTML[] =
+ "<FORM name = 'login' action = 'http://www.random.com/'> "
+ " <INPUT type = 'text' id = 'random'/> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'password'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>"
+ "<FORM name = 'signup' action = 'http://www.random.com/signup'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'first_password' "
+ " autocomplete = 'off' size = 5/>"
+ " <INPUT type = 'password' id = 'second_password' size = 5/> "
+ " <INPUT type = 'text' id = 'address'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>";
+
+constexpr char kPasswordChangeFormHTML[] =
+ "<FORM name = 'ChangeWithUsernameForm' action = 'http://www.bidule.com'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'password'/> "
+ " <INPUT type = 'password' id = 'newpassword'/> "
+ " <INPUT type = 'password' id = 'confirmpassword'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'Login'/> "
+ "</FORM>";
+
+constexpr char kPasswordFormAndSpanHTML[] =
+ "<FORM name = 'blah' action = 'http://www.random.com/pa/th?q=1&p=3#first'>"
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'password'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ "</FORM>"
+ "<SPAN id='span'>Text to click on</SPAN>";
+
+class PasswordGenerationAgentTest : public ChromeRenderViewTest {
+ public:
+ enum AutomaticGenerationStatus {
+ kNotReported,
+ kAvailable,
+ };
+ enum class GenerationAvailableForFormStatus {
+ kAvailable,
+ kUnavailable,
+ };
+
+ PasswordGenerationAgentTest() = default;
+
+ // ChromeRenderViewTest:
+ void RegisterMainFrameRemoteInterfaces() override;
+ void SetUp() override;
+ void TearDown() override;
+
+ void LoadHTMLWithUserGesture(const char* html);
+ void FocusField(const char* element_id);
+ void ExpectAutomaticGenerationAvailable(const char* element_id,
+ AutomaticGenerationStatus available);
+ void ExpectGenerationElementLostFocus(const char* new_element_id);
+ void ExpectFormClassifierVoteReceived(
+ bool received,
+ const base::string16& expected_generation_element);
+ void SelectGenerationFallbackAndExpect(bool available);
+
+ void BindPasswordManagerDriver(mojo::ScopedInterfaceEndpointHandle handle);
+ void BindPasswordManagerClient(mojo::ScopedInterfaceEndpointHandle handle);
+
+ // Callback for UserTriggeredGeneratePassword.
+ MOCK_METHOD1(UserTriggeredGeneratePasswordReply,
+ void(const base::Optional<
+ autofill::password_generation::PasswordGenerationUIData>&));
+
+ FakeMojoPasswordManagerDriver fake_driver_;
+ testing::StrictMock<FakePasswordGenerationDriver> fake_pw_client_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest);
+};
+
+void PasswordGenerationAgentTest::RegisterMainFrameRemoteInterfaces() {
+ // Because the test cases only involve the main frame in this test,
+ // the fake password client is only used for the main frame.
+ blink::AssociatedInterfaceProvider* remote_associated_interfaces =
+ view_->GetMainRenderFrame()->GetRemoteAssociatedInterfaces();
+ remote_associated_interfaces->OverrideBinderForTesting(
+ mojom::PasswordGenerationDriver::Name_,
+ base::BindRepeating(
+ &PasswordGenerationAgentTest::BindPasswordManagerClient,
+ base::Unretained(this)));
+ remote_associated_interfaces->OverrideBinderForTesting(
+ mojom::PasswordManagerDriver::Name_,
+ base::BindRepeating(
+ &PasswordGenerationAgentTest::BindPasswordManagerDriver,
+ base::Unretained(this)));
+}
+
+void PasswordGenerationAgentTest::SetUp() {
+ ChromeRenderViewTest::SetUp();
+
+ // TODO(crbug/862989): Remove workaround preventing non-test classes to bind
+ // fake_driver_ or fake_pw_client_.
+ password_autofill_agent_->GetPasswordManagerDriver();
+ password_generation_->RequestPasswordManagerClientForTesting();
+ base::RunLoop().RunUntilIdle(); // Executes binding the interfaces.
+ // Reject all requests to bind driver/client to anything but the test class:
+ view_->GetMainRenderFrame()
+ ->GetRemoteAssociatedInterfaces()
+ ->OverrideBinderForTesting(
+ mojom::PasswordGenerationDriver::Name_,
+ base::BindRepeating([](mojo::ScopedInterfaceEndpointHandle handle) {
+ handle.reset();
+ }));
+ view_->GetMainRenderFrame()
+ ->GetRemoteAssociatedInterfaces()
+ ->OverrideBinderForTesting(
+ mojom::PasswordManagerDriver::Name_,
+ base::BindRepeating([](mojo::ScopedInterfaceEndpointHandle handle) {
+ handle.reset();
+ }));
+
+ // Necessary for focus changes to work correctly and dispatch blur events
+ // when a field was previously focused.
+ GetWebWidget()->SetFocus(true);
+}
+
+void PasswordGenerationAgentTest::TearDown() {
+ // Unloading the document may trigger the event.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus()).Times(AtMost(1));
+ ChromeRenderViewTest::TearDown();
+}
+
+void PasswordGenerationAgentTest::LoadHTMLWithUserGesture(const char* html) {
+ LoadHTML(html);
+
+ // Enable show-ime event when element is focused by indicating that a user
+ // gesture has been processed since load.
+ EXPECT_TRUE(SimulateElementClick("dummy"));
+}
+
+void PasswordGenerationAgentTest::FocusField(const char* element_id) {
+ WebDocument document = GetMainFrame()->GetDocument();
+ blink::WebElement element =
+ document.GetElementById(blink::WebString::FromUTF8(element_id));
+ ASSERT_FALSE(element.IsNull());
+ ExecuteJavaScriptForTests(
+ base::StringPrintf("document.getElementById('%s').focus();", element_id)
+ .c_str());
+}
+
+void PasswordGenerationAgentTest::ExpectAutomaticGenerationAvailable(
+ const char* element_id,
+ AutomaticGenerationStatus status) {
+ SCOPED_TRACE(testing::Message()
+ << "element_id = " << element_id << "available = " << status);
+ if (status == kNotReported) {
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_)).Times(0);
+ } else {
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ }
+
+ FocusField(element_id);
+ base::RunLoop().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Check that aria-autocomplete attribute is set correctly.
+ if (status == kAvailable) {
+ WebDocument doc = GetMainFrame()->GetDocument();
+ WebElement element = doc.GetElementById(WebString::FromUTF8(element_id));
+ EXPECT_EQ("list", element.GetAttribute("aria-autocomplete"));
+ }
+}
+
+void PasswordGenerationAgentTest::ExpectGenerationElementLostFocus(
+ const char* new_element_id) {
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ FocusField(new_element_id);
+ base::RunLoop().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+}
+
+void PasswordGenerationAgentTest::ExpectFormClassifierVoteReceived(
+ bool received,
+ const base::string16& expected_generation_element) {
+ base::RunLoop().RunUntilIdle();
+ if (received) {
+ ASSERT_TRUE(fake_driver_.called_save_generation_field());
+ EXPECT_EQ(expected_generation_element,
+ fake_driver_.save_generation_field());
+ } else {
+ ASSERT_FALSE(fake_driver_.called_save_generation_field());
+ }
+
+ fake_driver_.reset_save_generation_field();
+}
+
+void PasswordGenerationAgentTest::SelectGenerationFallbackAndExpect(
+ bool available) {
+ if (available) {
+ EXPECT_CALL(*this,
+ UserTriggeredGeneratePasswordReply(testing::Ne(base::nullopt)));
+ } else {
+ EXPECT_CALL(*this,
+ UserTriggeredGeneratePasswordReply(testing::Eq(base::nullopt)));
+ }
+ password_generation_->UserTriggeredGeneratePassword(base::BindOnce(
+ &PasswordGenerationAgentTest::UserTriggeredGeneratePasswordReply,
+ base::Unretained(this)));
+ testing::Mock::VerifyAndClearExpectations(this);
+}
+
+void PasswordGenerationAgentTest::BindPasswordManagerDriver(
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ fake_driver_.BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::PasswordManagerDriver>(
+ std::move(handle)));
+}
+
+void PasswordGenerationAgentTest::BindPasswordManagerClient(
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ fake_pw_client_.BindReceiver(
+ mojo::PendingAssociatedReceiver<mojom::PasswordGenerationDriver>(
+ std::move(handle)));
+}
+
+class PasswordGenerationAgentTestForHtmlAnnotation
+ : public PasswordGenerationAgentTest {
+ public:
+ PasswordGenerationAgentTestForHtmlAnnotation() = default;
+
+ void SetUp() override {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kShowAutofillSignatures);
+ PasswordGenerationAgentTest::SetUp();
+ }
+
+ void TestAnnotateForm(bool has_form_tag);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTestForHtmlAnnotation);
+};
+
+void PasswordGenerationAgentTestForHtmlAnnotation::TestAnnotateForm(
+ bool has_form_tag) {
+ SCOPED_TRACE(testing::Message() << "has_form_tag = " << has_form_tag);
+ const char* kHtmlForm =
+ has_form_tag ? kAccountCreationFormHTML : kAccountCreationNoForm;
+ LoadHTMLWithUserGesture(kHtmlForm);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+ WebDocument document = GetMainFrame()->GetDocument();
+
+ const char* kFormSignature =
+ has_form_tag ? "3524919054660658462" : "7671707438749847833";
+ if (has_form_tag) {
+ // Check the form signature is set in the <form> tag.
+ blink::WebElement form_element =
+ document.GetElementById(blink::WebString::FromUTF8("blah"));
+ ASSERT_FALSE(form_element.IsNull());
+ blink::WebString form_signature =
+ form_element.GetAttribute(blink::WebString::FromUTF8("form_signature"));
+ ASSERT_FALSE(form_signature.IsNull());
+ EXPECT_EQ(kFormSignature, form_signature.Ascii());
+ }
+
+ // Check field signatures and form signature are set in the <input>s.
+ blink::WebElement username_element =
+ document.GetElementById(blink::WebString::FromUTF8("username"));
+ ASSERT_FALSE(username_element.IsNull());
+ blink::WebString username_signature = username_element.GetAttribute(
+ blink::WebString::FromUTF8("field_signature"));
+ ASSERT_FALSE(username_signature.IsNull());
+ EXPECT_EQ("239111655", username_signature.Ascii());
+ blink::WebString form_signature_in_username = username_element.GetAttribute(
+ blink::WebString::FromUTF8("form_signature"));
+ EXPECT_EQ(kFormSignature, form_signature_in_username.Ascii());
+
+ blink::WebElement password_element =
+ document.GetElementById(blink::WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(password_element.IsNull());
+ blink::WebString password_signature = password_element.GetAttribute(
+ blink::WebString::FromUTF8("field_signature"));
+ ASSERT_FALSE(password_signature.IsNull());
+ EXPECT_EQ("3933215845", password_signature.Ascii());
+ blink::WebString form_signature_in_password = password_element.GetAttribute(
+ blink::WebString::FromUTF8("form_signature"));
+ EXPECT_EQ(kFormSignature, form_signature_in_password.Ascii());
+
+ // Check the generation element is marked.
+ blink::WebString generation_mark = password_element.GetAttribute(
+ blink::WebString::FromUTF8("password_creation_field"));
+ ASSERT_FALSE(generation_mark.IsNull());
+ EXPECT_EQ("1", generation_mark.Utf8());
+
+ blink::WebElement confirmation_password_element =
+ document.GetElementById(blink::WebString::FromUTF8("second_password"));
+}
+
+TEST_F(PasswordGenerationAgentTest, HiddenSecondPasswordDetectionTest) {
+ // Hidden fields are not treated differently.
+ LoadHTMLWithUserGesture(kHiddenPasswordAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+}
+
+TEST_F(PasswordGenerationAgentTest, DetectionTestNoForm) {
+ LoadHTMLWithUserGesture(kAccountCreationNoForm);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+ ExpectGenerationElementLostFocus("second_password");
+}
+
+TEST_F(PasswordGenerationAgentTest, FillTest) {
+ // Add event listeners for password fields.
+ std::vector<base::string16> variables_to_check;
+ std::string events_registration_script =
+ CreateScriptToRegisterListeners("first_password", &variables_to_check) +
+ CreateScriptToRegisterListeners("second_password", &variables_to_check);
+
+ // Make sure that we are enabled before loading HTML.
+ std::string html =
+ std::string(kAccountCreationFormHTML) + events_registration_script;
+ // Begin with no gesture and therefore no focused element.
+ LoadHTMLWithUserGesture(html.c_str());
+ WebDocument document = GetMainFrame()->GetDocument();
+ SetFoundFormEligibleForGeneration(password_generation_,
+ GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */,
+ "second_password" /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement first_password_element = element.To<WebInputElement>();
+ element = document.GetElementById(WebString::FromUTF8("second_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement second_password_element = element.To<WebInputElement>();
+
+ // Both password fields should be empty.
+ EXPECT_TRUE(first_password_element.Value().IsNull());
+ EXPECT_TRUE(second_password_element.Value().IsNull());
+
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ // Password fields are filled out and set as being autofilled.
+ EXPECT_EQ(password, first_password_element.Value().Utf16());
+ EXPECT_EQ(password, second_password_element.Value().Utf16());
+ EXPECT_TRUE(first_password_element.IsAutofilled());
+ EXPECT_TRUE(second_password_element.IsAutofilled());
+
+ // Make sure all events are called.
+ for (const base::string16& variable : variables_to_check) {
+ int value;
+ EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(variable, &value));
+ EXPECT_EQ(1, value) << variable;
+ }
+
+ // Check that focus returns to previously focused element.
+ element = document.GetElementById(WebString::FromUTF8("address"));
+ ASSERT_FALSE(element.IsNull());
+ EXPECT_EQ(element, document.FocusedElement());
+}
+
+TEST_F(PasswordGenerationAgentTest, EditingTest) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(password_generation_,
+ GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */,
+ "second_password" /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement first_password_element = element.To<WebInputElement>();
+ element = document.GetElementById(WebString::FromUTF8("second_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement second_password_element = element.To<WebInputElement>();
+
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ // Passwords start out the same.
+ EXPECT_EQ(password, first_password_element.Value().Utf16());
+ EXPECT_EQ(password, second_password_element.Value().Utf16());
+
+ // After editing the first field they are still the same.
+ std::string edited_password_ascii = "edited_password";
+ base::string16 edited_password = base::ASCIIToUTF16(edited_password_ascii);
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, edited_password)));
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ SimulateUserInputChangeForElement(&first_password_element,
+ edited_password_ascii);
+ EXPECT_EQ(edited_password, first_password_element.Value().Utf16());
+ EXPECT_EQ(edited_password, second_password_element.Value().Utf16());
+ EXPECT_TRUE(first_password_element.IsAutofilled());
+ EXPECT_TRUE(second_password_element.IsAutofilled());
+
+ // Verify that password mirroring works correctly even when the password
+ // is deleted.
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(_));
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ SimulateUserInputChangeForElement(&first_password_element, std::string());
+ EXPECT_EQ(base::string16(), first_password_element.Value().Utf16());
+ EXPECT_EQ(base::string16(), second_password_element.Value().Utf16());
+ EXPECT_FALSE(first_password_element.IsAutofilled());
+ EXPECT_FALSE(second_password_element.IsAutofilled());
+}
+
+TEST_F(PasswordGenerationAgentTest, EditingEventsTest) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+
+ // Generate password.
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Start removing characters one by one and observe the events sent to the
+ // browser.
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ FocusField("first_password");
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+ size_t max_chars_to_delete_before_editing =
+ password.length() -
+ PasswordGenerationAgent::kMinimumLengthForEditedPassword;
+ for (size_t i = 0; i < max_chars_to_delete_before_editing; ++i) {
+ password.erase(password.end() - 1);
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
+ fake_pw_client_.Flush();
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillablePasswordField,
+ fake_driver_.last_focused_field_type());
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+ }
+
+ // Delete one more character and move back to the generation state.
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(_));
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
+ fake_pw_client_.Flush();
+ // Last focused element shouldn't change while editing.
+ fake_driver_.Flush();
+ EXPECT_EQ(FocusedFieldType::kFillablePasswordField,
+ fake_driver_.last_focused_field_type());
+}
+
+TEST_F(PasswordGenerationAgentTest, UnblacklistedMultipleTest) {
+ // Receive two not blacklisted messages, one is for account creation form and
+ // the other is not. Show password generation icon.
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+}
+
+TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) {
+ // Did not receive account creation forms detected message. Don't show
+ // password generation icon.
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+
+ // Receive the account creation forms detected message. Show password
+ // generation icon.
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+}
+
+TEST_F(PasswordGenerationAgentTest, MaximumCharsForGenerationOffer) {
+ base::HistogramTester histogram_tester;
+
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ // There should now be a message to show the UI.
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement first_password_element = element.To<WebInputElement>();
+
+ // Make a password just under maximum offer size.
+ // Due to implementation details it's OK to get one more trigger for the
+ // automatic generation.
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_))
+ .Times(AtMost(1));
+ SimulateUserInputChangeForElement(
+ &first_password_element,
+ std::string(PasswordGenerationAgent::kMaximumCharsForGenerationOffer,
+ 'a'));
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Simulate a user typing a password just over maximum offer size.
+ EXPECT_CALL(fake_pw_client_, PasswordGenerationRejectedByTyping());
+ SimulateUserTypingASCIICharacter('a', true);
+ // There should now be a message that generation was rejected.
+ fake_pw_client_.Flush();
+
+ // Simulate the user deleting characters. The generation popup should be
+ // shown again.
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Change focus. Bubble should be hidden.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ ExecuteJavaScriptForTests("document.getElementById('username').focus();");
+ fake_pw_client_.Flush();
+
+ // Focusing the password field will bring up the generation UI again.
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ ExecuteJavaScriptForTests(
+ "document.getElementById('first_password').focus();");
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Loading a different page triggers UMA stat upload. Verify that only one
+ // display event is sent.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ LoadHTMLWithUserGesture(kSigninFormHTML);
+
+ histogram_tester.ExpectBucketCount(
+ "PasswordGeneration.Event",
+ autofill::password_generation::GENERATION_POPUP_SHOWN, 1);
+}
+
+TEST_F(PasswordGenerationAgentTest, MinimumLengthForEditedPassword) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+
+ // Generate a new password.
+ base::string16 password = base::ASCIIToUTF16("random_password");
+
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Delete most of the password.
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_)).Times(0);
+ FocusField("first_password");
+ size_t max_chars_to_delete =
+ password.length() -
+ PasswordGenerationAgent::kMinimumLengthForEditedPassword;
+ EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(testing::_))
+ .Times(testing::AtLeast(1));
+ for (size_t i = 0; i < max_chars_to_delete; ++i)
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, false);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Delete one more character. The state should move to offering generation.
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // The first password field is still non empty. The second one should be
+ // cleared.
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement first_password_element = element.To<WebInputElement>();
+ element = document.GetElementById(WebString::FromUTF8("second_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement second_password_element = element.To<WebInputElement>();
+ EXPECT_NE(base::string16(), first_password_element.Value().Utf16());
+ EXPECT_EQ(base::string16(), second_password_element.Value().Utf16());
+}
+
+TEST_F(PasswordGenerationAgentTest, DynamicFormTest) {
+ LoadHTMLWithUserGesture(kSigninFormHTML);
+
+ ExecuteJavaScriptForTests(
+ "var form = document.createElement('form');"
+ "form.action='http://www.random.com';"
+ "var username = document.createElement('input');"
+ "username.type = 'text';"
+ "username.id = 'dynamic_username';"
+ "var first_password = document.createElement('input');"
+ "first_password.type = 'password';"
+ "first_password.id = 'first_password';"
+ "first_password.name = 'first_password';"
+ "var second_password = document.createElement('input');"
+ "second_password.type = 'password';"
+ "second_password.id = 'second_password';"
+ "second_password.name = 'second_password';"
+ "form.appendChild(username);"
+ "form.appendChild(first_password);"
+ "form.appendChild(second_password);"
+ "document.body.appendChild(form);");
+ WaitForAutofillDidAssociateFormControl();
+
+ // This needs to come after the DOM has been modified.
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+
+ // TODO(gcasto): I'm slightly worried about flakes in this test where
+ // didAssociateFormControls() isn't called. If this turns out to be a problem
+ // adding a call to OnDynamicFormsSeen(GetMainFrame()) will fix it, though
+ // it will weaken the test.
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+}
+
+// Losing focus should not trigger a password generation popup.
+TEST_F(PasswordGenerationAgentTest, BlurTest) {
+ LoadHTMLWithUserGesture(kDisabledElementAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+
+ // Focus on the first password field: password generation popup should show
+ // up.
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+
+ // Remove focus from everywhere by clicking an unfocusable element: password
+ // generation popup should not show up.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ EXPECT_TRUE(SimulateElementClick("disabled"));
+ fake_pw_client_.Flush();
+}
+
+TEST_F(PasswordGenerationAgentTest, ChangePasswordFormDetectionTest) {
+ // Verify that generation is shown on correct field after message receiving.
+ LoadHTMLWithUserGesture(kPasswordChangeFormHTML);
+ ExpectAutomaticGenerationAvailable("password", kNotReported);
+ ExpectAutomaticGenerationAvailable("newpassword", kNotReported);
+ ExpectAutomaticGenerationAvailable("confirmpassword", kNotReported);
+
+ SetFoundFormEligibleForGeneration(password_generation_,
+ GetMainFrame()->GetDocument(),
+ "newpassword" /* new_passwod_id */,
+ "confirmpassword" /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("password", kNotReported);
+ ExpectAutomaticGenerationAvailable("newpassword", kAvailable);
+ ExpectGenerationElementLostFocus("confirmpassword");
+}
+
+TEST_F(PasswordGenerationAgentTest, ManualGenerationInFormTest) {
+ LoadHTMLWithUserGesture(kSigninFormHTML);
+ SimulateElementRightClick("password");
+ SelectGenerationFallbackAndExpect(true);
+ // Re-focusing a password field for which manual generation was requested
+ // should not re-trigger generation.
+ ExpectAutomaticGenerationAvailable("password", kNotReported);
+}
+
+TEST_F(PasswordGenerationAgentTest, ManualGenerationNoFormTest) {
+ LoadHTMLWithUserGesture(kAccountCreationNoForm);
+ SimulateElementRightClick("first_password");
+ SelectGenerationFallbackAndExpect(true);
+}
+
+TEST_F(PasswordGenerationAgentTest, ManualGenerationDoesntSuppressAutomatic) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+ // The browser may show a standard password dropdown with the "Generate"
+ // option. In this case manual generation is triggered.
+ SelectGenerationFallbackAndExpect(true);
+
+ // Move the focus away to somewhere.
+ ExpectGenerationElementLostFocus("address");
+
+ // Moving the focus back should trigger the automatic generation again.
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+}
+
+TEST_F(PasswordGenerationAgentTest, ManualGenerationNoIds) {
+ LoadHTMLWithUserGesture(kAccountCreationNoIds);
+ WebDocument document = GetMainFrame()->GetDocument();
+
+ ExecuteJavaScriptForTests(
+ "document.getElementsByClassName('first_password')[0].focus();");
+ WebInputElement first_password_element =
+ document.FocusedElement().To<WebInputElement>();
+ ASSERT_FALSE(first_password_element.IsNull());
+ SelectGenerationFallbackAndExpect(true);
+
+ // Simulate that the user accepts a generated password.
+ base::string16 password = ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ // Check that the first password field is autofilled with the generated
+ // password.
+ EXPECT_EQ(password, first_password_element.Value().Utf16());
+ EXPECT_TRUE(first_password_element.IsAutofilled());
+
+ // Check that the second password field is autofilled with the generated
+ // password (since it is chosen as a confirmation password field).
+ ExecuteJavaScriptForTests(
+ "document.getElementsByClassName('second_password')[0].focus();");
+ WebInputElement second_password_element =
+ document.FocusedElement().To<WebInputElement>();
+ ASSERT_FALSE(second_password_element.IsNull());
+ EXPECT_EQ(password, second_password_element.Value().Utf16());
+ EXPECT_TRUE(second_password_element.IsAutofilled());
+}
+
+TEST_F(PasswordGenerationAgentTest, PresavingGeneratedPassword) {
+ const struct {
+ const char* form;
+ const char* generation_element;
+ } kTestCases[] = {{kAccountCreationFormHTML, "first_password"},
+ {kAccountCreationNoForm, "first_password"},
+ {kPasswordChangeFormHTML, "newpassword"}};
+ for (auto& test_case : kTestCases) {
+ SCOPED_TRACE(testing::Message("form: ") << test_case.form);
+ LoadHTMLWithUserGesture(test_case.form);
+ // To be able to work with input elements outside <form>'s, use manual
+ // generation.
+ SimulateElementRightClick(test_case.generation_element);
+ SelectGenerationFallbackAndExpect(true);
+
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ FocusField(test_case.generation_element);
+ EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(testing::_));
+ SimulateUserTypingASCIICharacter('a', true);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ FocusField("username");
+ EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(testing::_));
+ SimulateUserTypingASCIICharacter('X', true);
+ base::RunLoop().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ FocusField(test_case.generation_element);
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
+ for (size_t i = 0; i < password.length(); ++i)
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, false);
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(testing::_)).Times(0);
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ FocusField("username");
+ SimulateUserTypingASCIICharacter('Y', true);
+ base::RunLoop().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+ }
+}
+
+TEST_F(PasswordGenerationAgentTest, FallbackForSaving) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SimulateElementRightClick("first_password");
+ SelectGenerationFallbackAndExpect(true);
+ EXPECT_EQ(0, fake_driver_.called_show_manual_fallback_for_saving_count());
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)))
+ .WillOnce(testing::InvokeWithoutArgs([this]() {
+ // Make sure that generation event was propagated to the browser before
+ // the fallback showing. Otherwise, the fallback for saving provides a
+ // save bubble instead of a confirmation bubble.
+ EXPECT_EQ(0,
+ fake_driver_.called_show_manual_fallback_for_saving_count());
+ }));
+ password_generation_->GeneratedPasswordAccepted(password);
+ fake_driver_.Flush();
+ // Two fallback requests are expected because generation changes either new
+ // password and confirmation fields.
+ EXPECT_EQ(2, fake_driver_.called_show_manual_fallback_for_saving_count());
+}
+
+TEST_F(PasswordGenerationAgentTest, FormClassifierDisabled) {
+ LoadHTMLWithUserGesture(kSigninFormHTML);
+ ExpectFormClassifierVoteReceived(false /* vote is not expected */,
+ base::string16());
+}
+
+TEST_F(PasswordGenerationAgentTest, RevealPassword) {
+ // Checks that revealed password is masked when the field lost focus.
+ // Test cases: user click on another input field and on non-focusable element.
+ LoadHTMLWithUserGesture(kPasswordFormAndSpanHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ const char* kGenerationElementId = "password";
+ const char* kSpanId = "span";
+ const char* kTextFieldId = "username";
+
+ ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
+ base::string16 password = base::ASCIIToUTF16("long_pwd");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ for (bool clickOnInputField : {false, true}) {
+ SCOPED_TRACE(testing::Message("clickOnInputField = ") << clickOnInputField);
+ // Click on the generation field to reveal the password value.
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ FocusField(kGenerationElementId);
+ fake_pw_client_.Flush();
+
+ WebDocument document = GetMainFrame()->GetDocument();
+ blink::WebElement element = document.GetElementById(
+ blink::WebString::FromUTF8(kGenerationElementId));
+ ASSERT_FALSE(element.IsNull());
+ blink::WebInputElement input = element.To<WebInputElement>();
+ EXPECT_TRUE(input.ShouldRevealPassword());
+
+ // Click on another HTML element.
+ const char* const click_target_name =
+ clickOnInputField ? kTextFieldId : kSpanId;
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ EXPECT_TRUE(SimulateElementClick(click_target_name));
+ EXPECT_FALSE(input.ShouldRevealPassword());
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+ }
+}
+
+TEST_F(PasswordGenerationAgentTest, JavascriptClearedTheField) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+
+ const char kGenerationElementId[] = "first_password";
+ ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
+ base::string16 password = base::ASCIIToUTF16("long_pwd");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ ExecuteJavaScriptForTests(
+ "document.getElementById('first_password').value = '';");
+ FocusField(kGenerationElementId);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(PasswordGenerationAgentTest, GenerationFallbackTest) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(element.IsNull());
+ WebInputElement first_password_element = element.To<WebInputElement>();
+ EXPECT_TRUE(first_password_element.Value().IsNull());
+ SimulateElementRightClick("first_password");
+ SelectGenerationFallbackAndExpect(true);
+ EXPECT_TRUE(first_password_element.Value().IsNull());
+}
+
+TEST_F(PasswordGenerationAgentTest, GenerationFallback_NoFocusedElement) {
+ // Checks the fallback doesn't cause a crash just in case no password element
+ // had focus so far.
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SelectGenerationFallbackAndExpect(false);
+}
+
+TEST_F(PasswordGenerationAgentTest, AutofillToGenerationField) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+ ExpectAutomaticGenerationAvailable("first_password", kAvailable);
+
+ WebDocument document = GetMainFrame()->GetDocument();
+ WebElement element =
+ document.GetElementById(WebString::FromUTF8("first_password"));
+ ASSERT_FALSE(element.IsNull());
+ const WebInputElement input_element = element.To<WebInputElement>();
+ // Since password isn't generated (just suitable field was detected),
+ // |OnFieldAutofilled| wouldn't trigger any actions.
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_)).Times(0);
+ password_generation_->OnFieldAutofilled(input_element);
+}
+
+TEST_F(PasswordGenerationAgentTestForHtmlAnnotation, AnnotateForm) {
+ TestAnnotateForm(true);
+}
+
+TEST_F(PasswordGenerationAgentTestForHtmlAnnotation, AnnotateNoForm) {
+ TestAnnotateForm(false);
+}
+
+TEST_F(PasswordGenerationAgentTest, PasswordUnmaskedUntilCompleteDeletion) {
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
+ SetFoundFormEligibleForGeneration(
+ password_generation_, GetMainFrame()->GetDocument(),
+ "first_password" /* new_passwod_id */, nullptr /* confirm_password_id*/);
+
+ constexpr char kGenerationElementId[] = "first_password";
+
+ // Generate a new password.
+ ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Delete characters of the generated password until only
+ // |kMinimumLengthForEditedPassword| - 1 chars remain.
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ FocusField(kGenerationElementId);
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ size_t max_chars_to_delete =
+ password.length() -
+ PasswordGenerationAgent::kMinimumLengthForEditedPassword + 1;
+ for (size_t i = 0; i < max_chars_to_delete; ++i)
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, false);
+ base::RunLoop().RunUntilIdle();
+ fake_pw_client_.Flush();
+ // The remaining characters no longer count as a generated password, so
+ // generation should be offered again.
+
+ // Check that the characters remain unmasked.
+ WebDocument document = GetMainFrame()->GetDocument();
+ blink::WebElement element =
+ document.GetElementById(blink::WebString::FromUTF8(kGenerationElementId));
+ ASSERT_FALSE(element.IsNull());
+ blink::WebInputElement input = element.To<WebInputElement>();
+ EXPECT_TRUE(input.ShouldRevealPassword());
+
+ // Delete the rest of the characters. The field should now mask new
+ // characters. Due to implementation details it's possible to get pings about
+ // password generation available.
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_))
+ .Times(AnyNumber());
+ for (size_t i = 0;
+ i < PasswordGenerationAgent::kMinimumLengthForEditedPassword; ++i)
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, false);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(input.ShouldRevealPassword());
+}
+
+TEST_F(PasswordGenerationAgentTest, ShortPasswordMaskedAfterChangingFocus) {
+ LoadHTMLWithUserGesture(kPasswordFormAndSpanHTML);
+ constexpr char kGenerationElementId[] = "password";
+ SetFoundFormEligibleForGeneration(password_generation_,
+ GetMainFrame()->GetDocument(),
+ kGenerationElementId /* new_passwod_id */,
+ nullptr /* confirm_password_id*/);
+
+ // Generate a new password.
+ ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
+ base::string16 password = base::ASCIIToUTF16("random_password");
+ EXPECT_CALL(fake_pw_client_,
+ PresaveGeneratedPassword(testing::Field(
+ &autofill::PasswordForm::password_value, password)));
+ password_generation_->GeneratedPasswordAccepted(password);
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Delete characters of the generated password until only
+ // |kMinimumLengthForEditedPassword| - 1 chars remain.
+ EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup(_, _, _));
+ FocusField(kGenerationElementId);
+ EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
+ size_t max_chars_to_delete =
+ password.length() -
+ PasswordGenerationAgent::kMinimumLengthForEditedPassword + 1;
+ EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable(_));
+ for (size_t i = 0; i < max_chars_to_delete; ++i)
+ SimulateUserTypingASCIICharacter(ui::VKEY_BACK, false);
+ // The remaining characters no longer count as a generated password, so
+ // generation should be offered again.
+ base::RunLoop().RunUntilIdle();
+ fake_pw_client_.Flush();
+ testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
+
+ // Check that the characters remain unmasked.
+ WebDocument document = GetMainFrame()->GetDocument();
+ blink::WebElement element =
+ document.GetElementById(blink::WebString::FromUTF8(kGenerationElementId));
+ ASSERT_FALSE(element.IsNull());
+ blink::WebInputElement input = element.To<WebInputElement>();
+ EXPECT_TRUE(input.ShouldRevealPassword());
+
+ // Focus another element on the page. The password should be masked.
+ EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
+ ASSERT_TRUE(SimulateElementClick("span"));
+ EXPECT_FALSE(input.ShouldRevealPassword());
+
+ // Focus the password field again. As the remaining characters are not
+ // a generated password, they should remain masked.
+ ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
+ EXPECT_FALSE(input.ShouldRevealPassword());
+}
+
+TEST_F(PasswordGenerationAgentTest, GenerationAvailableByRendererIds) {
+ LoadHTMLWithUserGesture(kMultipleAccountCreationFormHTML);
+
+ constexpr const char* kPasswordElementsIds[] = {"password", "first_password",
+ "second_password"};
+
+ WebDocument document = GetMainFrame()->GetDocument();
+ std::vector<WebInputElement> password_elements;
+ for (const char* id : kPasswordElementsIds) {
+ WebElement element = document.GetElementById(WebString::FromUTF8(id));
+ WebInputElement* input = ToWebInputElement(&element);
+ ASSERT_TRUE(input);
+ password_elements.push_back(*input);
+ }
+
+ // Simulate that the browser informs about eligible for generation form.
+ // Check that generation is available only on new password field of this form.
+ PasswordFormGenerationData generation_data;
+ generation_data.new_password_renderer_id =
+ password_elements[0].UniqueRendererFormControlId();
+
+ password_generation_->FoundFormEligibleForGeneration(generation_data);
+ ExpectAutomaticGenerationAvailable(kPasswordElementsIds[0], kAvailable);
+ ExpectGenerationElementLostFocus(kPasswordElementsIds[1]);
+ ExpectAutomaticGenerationAvailable(kPasswordElementsIds[2], kNotReported);
+
+ // Simulate that the browser informs about the second eligible for generation
+ // form. Check that generation is available on both forms.
+ generation_data.new_password_renderer_id =
+ password_elements[2].UniqueRendererFormControlId();
+ password_generation_->FoundFormEligibleForGeneration(generation_data);
+ ExpectAutomaticGenerationAvailable(kPasswordElementsIds[0], kAvailable);
+ ExpectGenerationElementLostFocus(kPasswordElementsIds[1]);
+ ExpectAutomaticGenerationAvailable(kPasswordElementsIds[2], kAvailable);
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/password_generation_test_utils.cc b/chromium/chrome/renderer/autofill/password_generation_test_utils.cc
new file mode 100644
index 00000000000..17034b13cb2
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/password_generation_test_utils.cc
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/autofill/password_generation_test_utils.h"
+
+#include <base/strings/utf_string_conversions.h>
+#include "base/strings/stringprintf.h"
+#include "components/autofill/content/renderer/form_autofill_util.h"
+#include "components/autofill/content/renderer/password_generation_agent.h"
+#include "components/autofill/core/common/password_form_generation_data.h"
+#include "components/autofill/core/common/signatures_util.h"
+#include "net/base/escape.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_form_element.h"
+
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebFormControlElement;
+using blink::WebFormElement;
+using blink::WebString;
+using blink::WebVector;
+
+namespace autofill {
+
+namespace {
+
+// Events that should be triggered when Chrome fills a field.
+const char* const kEvents[] = {"focus", "keydown", "input",
+ "change", "keyup", "blur"};
+
+// Returns renderer id of WebInput element with id attribute |input_id|.
+uint32_t GetRendererId(WebDocument document, const char* input_id) {
+ WebElement element = document.GetElementById(WebString::FromUTF8(input_id));
+ auto* input = ToWebInputElement(&element);
+ return input->UniqueRendererFormControlId();
+}
+
+} // namespace
+
+void SetFoundFormEligibleForGeneration(
+ PasswordGenerationAgent* generation_agent,
+ WebDocument document,
+ const char* new_password_id,
+ const char* cofirm_password_id) {
+ PasswordFormGenerationData data;
+ data.new_password_renderer_id = GetRendererId(document, new_password_id);
+ if (cofirm_password_id) {
+ data.confirmation_password_renderer_id =
+ GetRendererId(document, cofirm_password_id);
+ }
+
+ generation_agent->FoundFormEligibleForGeneration(data);
+}
+
+// Creates script that registers event listeners for |element_name| field. To
+// check whether the listeners are called, check that the variables from
+// |variables_to_check| are set to 1.
+std::string CreateScriptToRegisterListeners(
+ const char* const element_name,
+ std::vector<base::string16>* variables_to_check) {
+ DCHECK(variables_to_check);
+ std::string element = element_name;
+
+ std::string all_scripts = "<script>";
+ for (const char* const event : kEvents) {
+ std::string script = base::StringPrintf(
+ "%s_%s_event = 0;"
+ "document.getElementById('%s').on%s = function() {"
+ " %s_%s_event = 1;"
+ "};",
+ element_name, event, element_name, event, element_name, event);
+ all_scripts += script;
+ variables_to_check->push_back(base::UTF8ToUTF16(
+ base::StringPrintf("%s_%s_event", element_name, event)));
+ }
+
+ all_scripts += "</script>";
+ return all_scripts;
+}
+
+} // namespace autofill
diff --git a/chromium/chrome/renderer/autofill/password_generation_test_utils.h b/chromium/chrome/renderer/autofill/password_generation_test_utils.h
new file mode 100644
index 00000000000..06907a53053
--- /dev/null
+++ b/chromium/chrome/renderer/autofill/password_generation_test_utils.h
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_AUTOFILL_PASSWORD_GENERATION_TEST_UTILS_H_
+#define CHROME_RENDERER_AUTOFILL_PASSWORD_GENERATION_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+
+namespace blink {
+class WebDocument;
+}
+
+namespace autofill {
+
+class PasswordGenerationAgent;
+
+// Sets that automatic generation available with |generation_agent| for fields
+// |new_password_id| and |cofirm_password_id| which are in document |document|.
+void SetFoundFormEligibleForGeneration(
+ PasswordGenerationAgent* generation_agent,
+ blink::WebDocument document,
+ const char* new_password_id,
+ const char* cofirm_password_id);
+
+std::string CreateScriptToRegisterListeners(
+ const char* const element_name,
+ std::vector<base::string16>* variables_to_check);
+
+} // namespace autofill
+
+#endif // CHROME_RENDERER_AUTOFILL_PASSWORD_GENERATION_TEST_UTILS_H_
diff --git a/chromium/chrome/renderer/benchmarking_extension.cc b/chromium/chrome/renderer/benchmarking_extension.cc
new file mode 100644
index 00000000000..68ed3660df7
--- /dev/null
+++ b/chromium/chrome/renderer/benchmarking_extension.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/benchmarking_extension.h"
+
+#include "base/command_line.h"
+#include "base/time/time.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/renderer/render_thread.h"
+#include "v8/include/v8.h"
+
+const char kBenchmarkingExtensionName[] = "v8/Benchmarking";
+
+namespace extensions_v8 {
+
+class BenchmarkingWrapper : public v8::Extension {
+ public:
+ BenchmarkingWrapper() :
+ v8::Extension(kBenchmarkingExtensionName,
+ "if (typeof(chrome) == 'undefined') {"
+ " chrome = {};"
+ "};"
+ "if (typeof(chrome.benchmarking) == 'undefined') {"
+ " chrome.benchmarking = {};"
+ "};"
+ "chrome.benchmarking.isSingleProcess = function() {"
+ " native function IsSingleProcess();"
+ " return IsSingleProcess();"
+ "};"
+ "chrome.Interval = function() {"
+ " var start_ = 0;"
+ " var stop_ = 0;"
+ " native function HiResTime();"
+ " this.start = function() {"
+ " stop_ = 0;"
+ " start_ = HiResTime();"
+ " };"
+ " this.stop = function() {"
+ " stop_ = HiResTime();"
+ " if (start_ == 0)"
+ " stop_ = 0;"
+ " };"
+ " this.microseconds = function() {"
+ " var stop = stop_;"
+ " if (stop == 0 && start_ != 0)"
+ " stop = HiResTime();"
+ " return Math.ceil(stop - start_);"
+ " };"
+ "}"
+ ) {}
+
+ v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::String> name) override {
+ if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "IsSingleProcess",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, IsSingleProcess);
+ } else if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "HiResTime",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, HiResTime);
+ }
+
+ return v8::Local<v8::FunctionTemplate>();
+ }
+
+ static void IsSingleProcess(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSingleProcess));
+ }
+
+ static void HiResTime(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ static_cast<double>(base::TimeTicks::Now().ToInternalValue()));
+ }
+};
+
+std::unique_ptr<v8::Extension> BenchmarkingExtension::Get() {
+ return std::make_unique<BenchmarkingWrapper>();
+}
+
+} // namespace extensions_v8
diff --git a/chromium/chrome/renderer/benchmarking_extension.h b/chromium/chrome/renderer/benchmarking_extension.h
new file mode 100644
index 00000000000..750560d22ed
--- /dev/null
+++ b/chromium/chrome/renderer/benchmarking_extension.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_BENCHMARKING_EXTENSION_H_
+#define CHROME_RENDERER_BENCHMARKING_EXTENSION_H_
+
+#include <memory>
+
+namespace v8 {
+class Extension;
+}
+
+namespace extensions_v8 {
+
+// Profiler is an extension to allow javascript access to the API for
+// an external profiler program (such as Quantify). The "External" part of the
+// name is to distinguish it from the built-in V8 Profiler.
+class BenchmarkingExtension {
+ public:
+ static std::unique_ptr<v8::Extension> Get();
+};
+
+} // namespace extensions_v8
+
+#endif // CHROME_RENDERER_BENCHMARKING_EXTENSION_H_
+
diff --git a/chromium/chrome/renderer/chrome_content_renderer_client.cc b/chromium/chrome/renderer/chrome_content_renderer_client.cc
new file mode 100644
index 00000000000..0948ebbbd19
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_content_renderer_client.cc
@@ -0,0 +1,1614 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_content_renderer_client.h"
+
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/crash_logging.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_content_client.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_isolated_world_ids.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/crash_keys.h"
+#include "chrome/common/pdf_util.h"
+#include "chrome/common/pepper_permission_util.h"
+#include "chrome/common/plugin.mojom.h"
+#include "chrome/common/prerender_types.h"
+#include "chrome/common/prerender_url_loader_throttle.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/secure_origin_whitelist.h"
+#include "chrome/common/thread_profiler.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/grit/renderer_resources.h"
+#include "chrome/renderer/benchmarking_extension.h"
+#include "chrome/renderer/chrome_render_frame_observer.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "chrome/renderer/content_settings_observer.h"
+#include "chrome/renderer/loadtimes_extension_bindings.h"
+#include "chrome/renderer/media/flash_embed_rewrite.h"
+#include "chrome/renderer/media/webrtc_logging_agent_impl.h"
+#include "chrome/renderer/net/net_error_helper.h"
+#include "chrome/renderer/net_benchmarking_extension.h"
+#include "chrome/renderer/pepper/pepper_helper.h"
+#include "chrome/renderer/plugins/non_loadable_plugin_placeholder.h"
+#include "chrome/renderer/plugins/pdf_plugin_placeholder.h"
+#include "chrome/renderer/plugins/plugin_preroller.h"
+#include "chrome/renderer/plugins/plugin_uma.h"
+#include "chrome/renderer/prerender/prerender_dispatcher.h"
+#include "chrome/renderer/prerender/prerender_helper.h"
+#include "chrome/renderer/prerender/prerenderer_client.h"
+#include "chrome/renderer/previews/resource_loading_hints_agent.h"
+#include "chrome/renderer/sync_encryption_keys_extension.h"
+#include "chrome/renderer/url_loader_throttle_provider_impl.h"
+#include "chrome/renderer/v8_unwinder.h"
+#include "chrome/renderer/websocket_handshake_throttle_provider_impl.h"
+#include "chrome/renderer/worker_content_settings_client.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "components/autofill/content/renderer/password_autofill_agent.h"
+#include "components/autofill/content/renderer/password_generation_agent.h"
+#include "components/content_capture/common/content_capture_features.h"
+#include "components/content_capture/renderer/content_capture_sender.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/contextual_search/content/renderer/overlay_js_render_frame_observer.h"
+#include "components/data_reduction_proxy/content/renderer/content_previews_render_frame_observer.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
+#include "components/dom_distiller/content/renderer/distillability_agent.h"
+#include "components/dom_distiller/content/renderer/distiller_js_render_frame_observer.h"
+#include "components/dom_distiller/core/dom_distiller_features.h"
+#include "components/dom_distiller/core/dom_distiller_switches.h"
+#include "components/dom_distiller/core/url_constants.h"
+#include "components/error_page/common/error.h"
+#include "components/error_page/common/localized_error.h"
+#include "components/network_hints/renderer/prescient_networking_dispatcher.h"
+#include "components/page_load_metrics/renderer/metrics_render_frame_observer.h"
+#include "components/pdf/renderer/pepper_pdf_host.h"
+#include "components/safe_browsing/buildflags.h"
+#include "components/safe_browsing/renderer/threat_dom_details.h"
+#include "components/spellcheck/spellcheck_buildflags.h"
+#include "components/startup_metric_utils/common/startup_metric.mojom.h"
+#include "components/subresource_filter/content/renderer/subresource_filter_agent.h"
+#include "components/subresource_filter/content/renderer/unverified_ruleset_dealer.h"
+#include "components/subresource_filter/core/common/common_features.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "components/variations/variations_switches.h"
+#include "components/version_info/version_info.h"
+#include "components/visitedlink/renderer/visitedlink_slave.h"
+#include "components/web_cache/renderer/web_cache_impl.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/mime_handler_view_mode.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/plugin_instance_throttler.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_visitor.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ipc/ipc_sync_channel.h"
+#include "media/base/media_switches.h"
+#include "media/media_buildflags.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/net_errors.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "ppapi/shared_impl/ppapi_switches.h"
+#include "printing/buildflags/buildflags.h"
+#include "services/network/public/cpp/is_potentially_trustworthy.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/platform/scheduler/web_renderer_process_type.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_cache.h"
+#include "third_party/blink/public/platform/web_runtime_features.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_error.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_response.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_heap.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_origin_trials.h"
+#include "third_party/blink/public/web/web_plugin_container.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#include "third_party/blink/public/web/web_security_policy.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/webui/jstemplate_builder.h"
+#include "url/origin.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/renderer/sandbox_status_extension_android.h"
+#else
+#include "chrome/renderer/searchbox/search_bouncer.h"
+#include "chrome/renderer/searchbox/searchbox.h"
+#include "chrome/renderer/searchbox/searchbox_extension.h"
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+#include "components/nacl/common/nacl_constants.h"
+#include "components/nacl/renderer/nacl_helper.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/common/initialize_extensions_client.h"
+#include "chrome/renderer/extensions/chrome_extensions_renderer_client.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "third_party/blink/public/common/css/preferred_color_scheme.h"
+#include "third_party/blink/public/web/web_settings.h"
+#include "third_party/blink/public/web/web_view.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "chrome/common/plugin_utils.h"
+#include "chrome/renderer/plugins/chrome_plugin_placeholder.h"
+#include "chrome/renderer/plugins/power_saver_info.h"
+#else
+#include "components/plugins/renderer/plugin_placeholder.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PRINTING)
+#include "chrome/renderer/printing/chrome_print_render_frame_helper_delegate.h"
+#include "components/printing/renderer/print_render_frame_helper.h"
+#include "printing/print_settings.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+#include "chrome/renderer/pepper/chrome_pdf_print_client.h"
+#endif
+
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+#include "components/spellcheck/renderer/spellcheck.h"
+#include "components/spellcheck/renderer/spellcheck_provider.h"
+
+#if BUILDFLAG(HAS_SPELLCHECK_PANEL)
+#include "components/spellcheck/renderer/spellcheck_panel.h"
+#endif // BUILDFLAG(HAS_SPELLCHECK_PANEL)
+#endif // BUILDFLAG(ENABLE_SPELLCHECK)
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+#include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h"
+#endif
+
+using autofill::AutofillAgent;
+using autofill::PasswordAutofillAgent;
+using autofill::PasswordGenerationAgent;
+using base::ASCIIToUTF16;
+using base::UserMetricsAction;
+using blink::WebCache;
+using blink::WebConsoleMessage;
+using blink::WebDocument;
+using blink::WebFrame;
+using blink::WebLocalFrame;
+using blink::WebPlugin;
+using blink::WebPluginParams;
+using blink::WebSecurityOrigin;
+using blink::WebSecurityPolicy;
+using blink::WebString;
+using blink::WebURL;
+using blink::WebURLError;
+using blink::WebURLRequest;
+using blink::WebURLResponse;
+using blink::WebVector;
+using blink::mojom::FetchCacheMode;
+using content::PluginInstanceThrottler;
+using content::RenderFrame;
+using content::RenderThread;
+using content::WebPluginInfo;
+using content::WebPluginMimeType;
+using extensions::Extension;
+
+namespace {
+
+// Whitelist PPAPI for Android Runtime for Chromium. (See crbug.com/383937)
+#if BUILDFLAG(ENABLE_PLUGINS)
+const char* const kPredefinedAllowedCameraDeviceOrigins[] = {
+ "6EAED1924DB611B6EEF2A664BD077BE7EAD33B8F",
+ "4EB74897CB187C7633357C2FE832E0AD6A44883A"};
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+void AppendParams(
+ const std::vector<WebPluginMimeType::Param>& additional_params,
+ WebVector<WebString>* existing_names,
+ WebVector<WebString>* existing_values) {
+ DCHECK(existing_names->size() == existing_values->size());
+ size_t existing_size = existing_names->size();
+ size_t total_size = existing_size + additional_params.size();
+
+ WebVector<WebString> names(total_size);
+ WebVector<WebString> values(total_size);
+
+ for (size_t i = 0; i < existing_size; ++i) {
+ names[i] = (*existing_names)[i];
+ values[i] = (*existing_values)[i];
+ }
+
+ for (size_t i = 0; i < additional_params.size(); ++i) {
+ names[existing_size + i] = WebString::FromUTF16(additional_params[i].name);
+ values[existing_size + i] =
+ WebString::FromUTF16(additional_params[i].value);
+ }
+
+ existing_names->Swap(names);
+ existing_values->Swap(values);
+}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+bool IsStandaloneContentExtensionProcess() {
+#if !BUILDFLAG(ENABLE_EXTENSIONS)
+ return false;
+#else
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kExtensionProcess);
+#endif
+}
+
+// Defers media player loading in background pages until they're visible.
+class MediaLoadDeferrer : public content::RenderFrameObserver {
+ public:
+ MediaLoadDeferrer(content::RenderFrame* render_frame,
+ base::OnceClosure continue_loading_cb)
+ : content::RenderFrameObserver(render_frame),
+ continue_loading_cb_(std::move(continue_loading_cb)) {}
+ ~MediaLoadDeferrer() override {}
+
+ private:
+ // content::RenderFrameObserver implementation:
+ void WasShown() override {
+ std::move(continue_loading_cb_).Run();
+ delete this;
+ }
+ void OnDestruct() override { delete this; }
+
+ base::OnceClosure continue_loading_cb_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaLoadDeferrer);
+};
+
+std::unique_ptr<base::Unwinder> CreateV8Unwinder(
+ const v8::UnwindState& unwind_state) {
+ return std::make_unique<V8Unwinder>(unwind_state);
+}
+
+} // namespace
+
+ChromeContentRendererClient::ChromeContentRendererClient()
+ : main_entry_time_(base::TimeTicks::Now()),
+#if defined(OS_WIN)
+ remote_module_watcher_(nullptr, base::OnTaskRunnerDeleter(nullptr)),
+#endif
+ main_thread_profiler_(ThreadProfiler::CreateAndStartOnMainThread()) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ EnsureExtensionsClientInitialized();
+ extensions::ExtensionsRendererClient::Set(
+ ChromeExtensionsRendererClient::GetInstance());
+#endif
+#if BUILDFLAG(ENABLE_PLUGINS)
+ for (const char* origin : kPredefinedAllowedCameraDeviceOrigins)
+ allowed_camera_device_origins_.insert(origin);
+#endif
+}
+
+ChromeContentRendererClient::~ChromeContentRendererClient() {}
+
+void ChromeContentRendererClient::RenderThreadStarted() {
+ RenderThread* thread = RenderThread::Get();
+
+ main_thread_profiler_->SetAuxUnwinderFactory(base::BindRepeating(
+ &CreateV8Unwinder, v8::Isolate::GetCurrent()->GetUnwindState()));
+
+ thread->SetRendererProcessType(
+ IsStandaloneContentExtensionProcess()
+ ? blink::scheduler::WebRendererProcessType::kExtensionRenderer
+ : blink::scheduler::WebRendererProcessType::kRenderer);
+
+ {
+ mojo::Remote<startup_metric_utils::mojom::StartupMetricHost>
+ startup_metric_host;
+ thread->BindHostReceiver(startup_metric_host.BindNewPipeAndPassReceiver());
+ startup_metric_host->RecordRendererMainEntryTime(main_entry_time_);
+ }
+
+#if defined(OS_WIN)
+ mojo::PendingRemote<mojom::ModuleEventSink> module_event_sink;
+ thread->BindHostReceiver(module_event_sink.InitWithNewPipeAndPassReceiver());
+ remote_module_watcher_ = RemoteModuleWatcher::Create(
+ thread->GetIOTaskRunner(), std::move(module_event_sink));
+#endif
+
+ browser_interface_broker_ =
+ blink::Platform::Current()->GetBrowserInterfaceBrokerProxy();
+
+ chrome_observer_.reset(new ChromeRenderThreadObserver());
+ web_cache_impl_.reset(new web_cache::WebCacheImpl());
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()->RenderThreadStarted();
+#endif
+
+ prescient_networking_dispatcher_.reset(
+ new network_hints::PrescientNetworkingDispatcher());
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+ if (!spellcheck_)
+ InitSpellCheck();
+#endif
+
+ prerender_dispatcher_.reset(new prerender::PrerenderDispatcher());
+ subresource_filter_ruleset_dealer_.reset(
+ new subresource_filter::UnverifiedRulesetDealer());
+
+ thread->AddObserver(chrome_observer_.get());
+ thread->AddObserver(prerender_dispatcher_.get());
+ thread->AddObserver(subresource_filter_ruleset_dealer_.get());
+
+#if !defined(OS_ANDROID)
+ thread->AddObserver(SearchBouncer::GetInstance());
+#endif
+
+ thread->RegisterExtension(extensions_v8::LoadTimesExtension::Get());
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(variations::switches::kEnableBenchmarking))
+ thread->RegisterExtension(extensions_v8::BenchmarkingExtension::Get());
+ if (command_line->HasSwitch(switches::kEnableNetBenchmarking))
+ thread->RegisterExtension(extensions_v8::NetBenchmarkingExtension::Get());
+
+ // chrome-search: and chrome-distiller: pages should not be accessible by
+ // normal content, and should also be unable to script anything but themselves
+ // (to help limit the damage that a corrupt page could cause).
+ WebString chrome_search_scheme(
+ WebString::FromASCII(chrome::kChromeSearchScheme));
+
+ // The Instant process can only display the content but not read it. Other
+ // processes can't display it or read it.
+ if (!command_line->HasSwitch(switches::kInstantProcess))
+ WebSecurityPolicy::RegisterURLSchemeAsDisplayIsolated(chrome_search_scheme);
+
+ WebString dom_distiller_scheme(
+ WebString::FromASCII(dom_distiller::kDomDistillerScheme));
+ // TODO(nyquist): Add test to ensure this happens when the flag is set.
+ WebSecurityPolicy::RegisterURLSchemeAsDisplayIsolated(dom_distiller_scheme);
+
+#if defined(OS_ANDROID)
+ WebSecurityPolicy::RegisterURLSchemeAsAllowedForReferrer(
+ WebString::FromUTF8(chrome::kAndroidAppScheme));
+#endif
+
+ // chrome-search: pages should not be accessible by bookmarklets
+ // or javascript: URLs typed in the omnibox.
+ WebSecurityPolicy::RegisterURLSchemeAsNotAllowingJavascriptURLs(
+ chrome_search_scheme);
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+ pdf_print_client_.reset(new ChromePDFPrintClient());
+ pdf::PepperPDFHost::SetPrintClient(pdf_print_client_.get());
+#endif
+
+ for (auto& origin_or_hostname_pattern :
+ network::SecureOriginAllowlist::GetInstance().GetCurrentAllowlist()) {
+ WebSecurityPolicy::AddOriginToTrustworthySafelist(
+ WebString::FromUTF8(origin_or_hostname_pattern));
+ }
+
+ for (auto& scheme :
+ secure_origin_whitelist::GetSchemesBypassingSecureContextCheck()) {
+ WebSecurityPolicy::AddSchemeToSecureContextSafelist(
+ WebString::FromASCII(scheme));
+ }
+
+ // This doesn't work in single-process mode.
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSingleProcess)) {
+ ThreadProfiler::SetMainThreadTaskRunner(
+ base::ThreadTaskRunnerHandle::Get());
+ mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> collector;
+ thread->BindHostReceiver(collector.InitWithNewPipeAndPassReceiver());
+ ThreadProfiler::SetCollectorForChildProcess(std::move(collector));
+ }
+}
+
+void ChromeContentRendererClient::RenderFrameCreated(
+ content::RenderFrame* render_frame) {
+ ChromeRenderFrameObserver* render_frame_observer =
+ new ChromeRenderFrameObserver(render_frame, web_cache_impl_.get());
+ service_manager::BinderRegistry* registry = render_frame_observer->registry();
+
+ bool should_whitelist_for_content_settings =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kInstantProcess);
+ ContentSettingsObserver* content_settings = new ContentSettingsObserver(
+ render_frame, should_whitelist_for_content_settings, registry);
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ content_settings->SetExtensionDispatcher(
+ ChromeExtensionsRendererClient::GetInstance()->extension_dispatcher());
+#endif
+ if (chrome_observer_.get()) {
+ content_settings->SetContentSettingRules(
+ chrome_observer_->content_setting_rules());
+ }
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()->RenderFrameCreated(
+ render_frame, registry);
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ new PepperHelper(render_frame);
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+ new nacl::NaClHelper(render_frame);
+#endif
+
+#if BUILDFLAG(SAFE_BROWSING_DB_LOCAL) || BUILDFLAG(SAFE_BROWSING_DB_REMOTE)
+ safe_browsing::ThreatDOMDetails::Create(render_frame, registry);
+#endif
+
+#if BUILDFLAG(ENABLE_PRINTING)
+ new printing::PrintRenderFrameHelper(
+ render_frame, std::make_unique<ChromePrintRenderFrameHelperDelegate>());
+#endif
+
+#if defined(OS_ANDROID)
+ SandboxStatusExtension::Create(render_frame);
+#endif
+
+#if !defined(OS_ANDROID)
+ if (base::FeatureList::IsEnabled(features::kSyncEncryptionKeysWebApi)) {
+ SyncEncryptionKeysExtension::Create(render_frame);
+ }
+#endif
+
+ new NetErrorHelper(render_frame);
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+ new SupervisedUserErrorPageControllerDelegateImpl(render_frame);
+#endif
+
+ if (!render_frame->IsMainFrame()) {
+ auto* prerender_helper = prerender::PrerenderHelper::Get(
+ render_frame->GetRenderView()->GetMainRenderFrame());
+ if (prerender_helper) {
+ // Avoid any race conditions from having the browser tell subframes that
+ // they're prerendering.
+ new prerender::PrerenderHelper(render_frame,
+ prerender_helper->prerender_mode(),
+ prerender_helper->histogram_prefix());
+ }
+ }
+
+ // Set up a mojo service to test if this page is a distiller page.
+ new dom_distiller::DistillerJsRenderFrameObserver(
+ render_frame, ISOLATED_WORLD_ID_CHROME_INTERNAL, registry);
+
+ if (dom_distiller::ShouldStartDistillabilityService()) {
+ // Create DistillabilityAgent to send distillability updates to
+ // DistillabilityDriver in the browser process.
+ new dom_distiller::DistillabilityAgent(render_frame, DCHECK_IS_ON());
+ }
+
+ // Set up a mojo service to test if this page is a contextual search page.
+ new contextual_search::OverlayJsRenderFrameObserver(render_frame, registry);
+
+ blink::AssociatedInterfaceRegistry* associated_interfaces =
+ render_frame_observer->associated_interfaces();
+ PasswordAutofillAgent* password_autofill_agent =
+ new PasswordAutofillAgent(render_frame, associated_interfaces);
+ PasswordGenerationAgent* password_generation_agent =
+ new PasswordGenerationAgent(render_frame, password_autofill_agent,
+ associated_interfaces);
+ new AutofillAgent(render_frame, password_autofill_agent,
+ password_generation_agent, associated_interfaces);
+
+ if (content_capture::features::IsContentCaptureEnabled()) {
+ new content_capture::ContentCaptureSender(render_frame,
+ associated_interfaces);
+ }
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+ associated_interfaces->AddInterface(base::BindRepeating(
+ &extensions::MimeHandlerViewContainerManager::BindReceiver,
+ render_frame->GetRoutingID()));
+ }
+#endif
+
+ // Owned by |render_frame|.
+ page_load_metrics::MetricsRenderFrameObserver* metrics_render_frame_observer =
+ new page_load_metrics::MetricsRenderFrameObserver(render_frame);
+ // There is no render thread, thus no UnverifiedRulesetDealer in
+ // ChromeRenderViewTests.
+ if (subresource_filter_ruleset_dealer_) {
+ // Create AdResourceTracker to tracker ad resource loads at the chrome
+ // layer.
+ auto ad_resource_tracker =
+ std::make_unique<subresource_filter::AdResourceTracker>();
+ metrics_render_frame_observer->SetAdResourceTracker(
+ ad_resource_tracker.get());
+ new subresource_filter::SubresourceFilterAgent(
+ render_frame, subresource_filter_ruleset_dealer_.get(),
+ std::move(ad_resource_tracker));
+ }
+ if (render_frame->IsMainFrame()) {
+ new previews::ResourceLoadingHintsAgent(
+ render_frame_observer->associated_interfaces(), render_frame);
+ }
+
+#if !defined(OS_ANDROID)
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kInstantProcess) &&
+ render_frame->IsMainFrame()) {
+ new SearchBox(render_frame);
+ }
+#endif // !defined(OS_ANDROID)
+
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+ new SpellCheckProvider(render_frame, spellcheck_.get(), this);
+
+#if BUILDFLAG(HAS_SPELLCHECK_PANEL)
+ new SpellCheckPanel(render_frame, registry, this);
+#endif // BUILDFLAG(HAS_SPELLCHECK_PANEL)
+#endif
+
+ if (render_frame->IsMainFrame())
+ new data_reduction_proxy::ContentPreviewsRenderFrameObserver(render_frame);
+}
+
+void ChromeContentRendererClient::RenderViewCreated(
+ content::RenderView* render_view) {
+ new prerender::PrerendererClient(render_view);
+}
+
+SkBitmap* ChromeContentRendererClient::GetSadPluginBitmap() {
+ return const_cast<SkBitmap*>(ui::ResourceBundle::GetSharedInstance()
+ .GetImageNamed(IDR_SAD_PLUGIN)
+ .ToSkBitmap());
+}
+
+SkBitmap* ChromeContentRendererClient::GetSadWebViewBitmap() {
+ return const_cast<SkBitmap*>(ui::ResourceBundle::GetSharedInstance()
+ .GetImageNamed(IDR_SAD_WEBVIEW)
+ .ToSkBitmap());
+}
+
+bool ChromeContentRendererClient::IsPluginHandledExternally(
+ content::RenderFrame* render_frame,
+ const blink::WebElement& plugin_element,
+ const GURL& original_url,
+ const std::string& mime_type) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ DCHECK(plugin_element.HasHTMLTagName("object") ||
+ plugin_element.HasHTMLTagName("embed"));
+ if (!content::MimeHandlerViewMode::UsesCrossProcessFrame())
+ return false;
+ // Blink will next try to load a WebPlugin which would end up in
+ // OverrideCreatePlugin, sending another IPC only to find out the plugin is
+ // not supported. Here it suffices to return false but there should perhaps be
+ // a more unified approach to avoid sending the IPC twice.
+ chrome::mojom::PluginInfoPtr plugin_info = chrome::mojom::PluginInfo::New();
+ GetPluginInfoHost()->GetPluginInfo(
+ render_frame->GetRoutingID(), original_url,
+ render_frame->GetWebFrame()->Top()->GetSecurityOrigin(), mime_type,
+ &plugin_info);
+ // TODO(ekaramad): Not continuing here due to a disallowed status should take
+ // us to CreatePlugin. See if more in depths investigation of |status| is
+ // necessary here (see https://crbug.com/965747). For now, returning false
+ // should take us to CreatePlugin after HTMLPlugInElement which is called
+ // through HTMLPlugInElement::LoadPlugin code path.
+ if (plugin_info->status != chrome::mojom::PluginStatus::kAllowed &&
+ plugin_info->status !=
+ chrome::mojom::PluginStatus::kPlayImportantContent) {
+ // We could get here when a MimeHandlerView is loaded inside a <webview>
+ // which is using permissions API (see WebViewPluginTests).
+ ChromeExtensionsRendererClient::DidBlockMimeHandlerViewForDisallowedPlugin(
+ plugin_element);
+ return false;
+ }
+ return ChromeExtensionsRendererClient::MaybeCreateMimeHandlerView(
+ plugin_element, original_url, plugin_info->actual_mime_type,
+ plugin_info->plugin);
+#else
+ return false;
+#endif
+}
+
+v8::Local<v8::Object> ChromeContentRendererClient::GetScriptableObject(
+ const blink::WebElement& plugin_element,
+ v8::Isolate* isolate) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return ChromeExtensionsRendererClient::GetInstance()->GetScriptableObject(
+ plugin_element, isolate);
+#else
+ return v8::Local<v8::Object>();
+#endif
+}
+
+bool ChromeContentRendererClient::OverrideCreatePlugin(
+ content::RenderFrame* render_frame,
+ const WebPluginParams& params,
+ WebPlugin** plugin) {
+ std::string orig_mime_type = params.mime_type.Utf8();
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (!ChromeExtensionsRendererClient::GetInstance()->OverrideCreatePlugin(
+ render_frame, params)) {
+ return false;
+ }
+#endif
+
+ GURL url(params.url);
+#if BUILDFLAG(ENABLE_PLUGINS)
+ chrome::mojom::PluginInfoPtr plugin_info = chrome::mojom::PluginInfo::New();
+ GetPluginInfoHost()->GetPluginInfo(
+ render_frame->GetRoutingID(), url,
+ render_frame->GetWebFrame()->Top()->GetSecurityOrigin(), orig_mime_type,
+ &plugin_info);
+ *plugin = CreatePlugin(render_frame, params, *plugin_info);
+#else // !BUILDFLAG(ENABLE_PLUGINS)
+ PluginUMAReporter::GetInstance()->ReportPluginMissing(orig_mime_type, url);
+ if (orig_mime_type == kPDFMimeType) {
+ ReportPDFLoadStatus(
+ PDFLoadStatus::kShowedDisabledPluginPlaceholderForEmbeddedPdf);
+ if (base::FeatureList::IsEnabled(features::kClickToOpenPDFPlaceholder)) {
+ PDFPluginPlaceholder* placeholder =
+ PDFPluginPlaceholder::CreatePDFPlaceholder(render_frame, params);
+ *plugin = placeholder->plugin();
+ return true;
+ }
+ }
+ auto* placeholder = NonLoadablePluginPlaceholder::CreateNotSupportedPlugin(
+ render_frame, params);
+ *plugin = placeholder->plugin();
+
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+ return true;
+}
+
+WebPlugin* ChromeContentRendererClient::CreatePluginReplacement(
+ content::RenderFrame* render_frame,
+ const base::FilePath& plugin_path) {
+ auto* placeholder = NonLoadablePluginPlaceholder::CreateErrorPlugin(
+ render_frame, plugin_path);
+ return placeholder->plugin();
+}
+
+bool ChromeContentRendererClient::DeferMediaLoad(
+ content::RenderFrame* render_frame,
+ bool has_played_media_before,
+ base::OnceClosure closure) {
+ // Don't allow autoplay/autoload of media resources in a page that is hidden
+ // and has never played any media before. We want to allow future loads even
+ // when hidden to allow playlist-like functionality.
+ //
+ // NOTE: This is also used to defer media loading for prerender.
+ if ((render_frame->GetRenderView()->GetWebView()->IsHidden() &&
+ !has_played_media_before) ||
+ prerender::PrerenderHelper::IsPrerendering(render_frame)) {
+ new MediaLoadDeferrer(render_frame, std::move(closure));
+ return true;
+ }
+
+ std::move(closure).Run();
+ return false;
+}
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+
+mojo::AssociatedRemote<chrome::mojom::PluginInfoHost>&
+ChromeContentRendererClient::GetPluginInfoHost() {
+ struct PluginInfoHostHolder {
+ PluginInfoHostHolder() {
+ RenderThread::Get()->GetChannel()->GetRemoteAssociatedInterface(
+ &plugin_info_host);
+ }
+ ~PluginInfoHostHolder() {}
+ mojo::AssociatedRemote<chrome::mojom::PluginInfoHost> plugin_info_host;
+ };
+ static base::NoDestructor<PluginInfoHostHolder> holder;
+ return holder->plugin_info_host;
+}
+
+// static
+WebPlugin* ChromeContentRendererClient::CreatePlugin(
+ content::RenderFrame* render_frame,
+ const WebPluginParams& original_params,
+ const chrome::mojom::PluginInfo& plugin_info) {
+ const WebPluginInfo& info = plugin_info.plugin;
+ const std::string& actual_mime_type = plugin_info.actual_mime_type;
+ const base::string16& group_name = plugin_info.group_name;
+ const std::string& identifier = plugin_info.group_identifier;
+ chrome::mojom::PluginStatus status = plugin_info.status;
+ GURL url(original_params.url);
+ std::string orig_mime_type = original_params.mime_type.Utf8();
+ ChromePluginPlaceholder* placeholder = nullptr;
+
+ // If the browser plugin is to be enabled, this should be handled by the
+ // renderer, so the code won't reach here due to the early exit in
+ // OverrideCreatePlugin.
+ if (status == chrome::mojom::PluginStatus::kNotFound ||
+ orig_mime_type == content::kBrowserPluginMimeType) {
+ PluginUMAReporter::GetInstance()->ReportPluginMissing(orig_mime_type, url);
+ placeholder = ChromePluginPlaceholder::CreateLoadableMissingPlugin(
+ render_frame, original_params);
+ } else {
+ // TODO(bauerb): This should be in content/.
+ WebPluginParams params(original_params);
+ for (const auto& mime_type : info.mime_types) {
+ if (mime_type.mime_type == actual_mime_type) {
+ AppendParams(mime_type.additional_params, &params.attribute_names,
+ &params.attribute_values);
+ break;
+ }
+ }
+ if (params.mime_type.IsNull() && (actual_mime_type.size() > 0)) {
+ // Webkit might say that mime type is null while we already know the
+ // actual mime type via ChromeViewHostMsg_GetPluginInfo. In that case
+ // we should use what we know since WebpluginDelegateProxy does some
+ // specific initializations based on this information.
+ params.mime_type = WebString::FromUTF8(actual_mime_type);
+ }
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(render_frame);
+
+ const ContentSettingsType content_type =
+ ShouldUseJavaScriptSettingForPlugin(info)
+ ? CONTENT_SETTINGS_TYPE_JAVASCRIPT
+ : CONTENT_SETTINGS_TYPE_PLUGINS;
+
+ if ((status == chrome::mojom::PluginStatus::kUnauthorized ||
+ status == chrome::mojom::PluginStatus::kBlocked) &&
+ observer->IsPluginTemporarilyAllowed(identifier)) {
+ status = chrome::mojom::PluginStatus::kAllowed;
+ }
+
+ auto create_blocked_plugin = [&render_frame, &params, &info, &identifier,
+ &group_name](int template_id,
+ const base::string16& message) {
+ return ChromePluginPlaceholder::CreateBlockedPlugin(
+ render_frame, params, info, identifier, group_name, template_id,
+ message, PowerSaverInfo());
+ };
+ WebLocalFrame* frame = render_frame->GetWebFrame();
+ switch (status) {
+ case chrome::mojom::PluginStatus::kNotFound: {
+ NOTREACHED();
+ break;
+ }
+ case chrome::mojom::PluginStatus::kAllowed:
+ case chrome::mojom::PluginStatus::kPlayImportantContent: {
+#if BUILDFLAG(ENABLE_NACL) && BUILDFLAG(ENABLE_EXTENSIONS)
+ const bool is_nacl_plugin =
+ info.name == ASCIIToUTF16(nacl::kNaClPluginName);
+ const bool is_nacl_mime_type =
+ actual_mime_type == nacl::kNaClPluginMimeType;
+ const bool is_pnacl_mime_type =
+ actual_mime_type == nacl::kPnaclPluginMimeType;
+ if (is_nacl_plugin || is_nacl_mime_type || is_pnacl_mime_type) {
+ bool has_enable_nacl_switch =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableNaCl);
+ bool is_nacl_unrestricted =
+ has_enable_nacl_switch || is_pnacl_mime_type;
+ GURL manifest_url;
+ GURL app_url;
+ if (is_nacl_mime_type || is_pnacl_mime_type) {
+ // Normal NaCl/PNaCl embed. The app URL is the page URL.
+ manifest_url = url;
+ app_url = frame->GetDocument().Url();
+ } else {
+ // NaCl is being invoked as a content handler. Look up the NaCl
+ // module using the MIME type. The app URL is the manifest URL.
+ manifest_url = GetNaClContentHandlerURL(actual_mime_type, info);
+ app_url = manifest_url;
+ }
+ bool is_module_allowed = false;
+ const Extension* extension =
+ extensions::RendererExtensionRegistry::Get()
+ ->GetExtensionOrAppByURL(manifest_url);
+ if (extension) {
+ is_module_allowed =
+ IsNativeNaClAllowed(app_url, is_nacl_unrestricted, extension);
+ } else {
+ WebDocument document = frame->GetDocument();
+ is_module_allowed =
+ has_enable_nacl_switch ||
+ (is_pnacl_mime_type &&
+ blink::WebOriginTrials::isTrialEnabled(&document, "PNaCl"));
+ }
+ if (!is_module_allowed) {
+ WebString error_message;
+ if (is_nacl_mime_type) {
+ error_message =
+ "Only unpacked extensions and apps installed from the Chrome "
+ "Web Store can load NaCl modules without enabling Native "
+ "Client in about:flags.";
+ } else if (is_pnacl_mime_type) {
+ error_message =
+ "PNaCl modules can only be used on the open web (non-app/"
+ "extension) when the PNaCl Origin Trial is enabled";
+ }
+ frame->AddMessageToConsole(WebConsoleMessage(
+ blink::mojom::ConsoleMessageLevel::kError, error_message));
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+#if defined(OS_CHROMEOS)
+ l10n_util::GetStringUTF16(IDS_NACL_PLUGIN_BLOCKED));
+#else
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_BLOCKED, group_name));
+#endif
+ break;
+ }
+ }
+#endif // BUILDFLAG(ENABLE_NACL) && BUILDFLAG(ENABLE_EXTENSIONS)
+
+ if (GURL(frame->GetDocument().Url()).host_piece() ==
+ extension_misc::kPdfExtensionId) {
+ if (!base::FeatureList::IsEnabled(features::kWebUIDarkMode)) {
+ auto* render_view = render_frame->GetRenderView();
+ auto* web_view = render_view ? render_view->GetWebView() : nullptr;
+ if (web_view) {
+ web_view->GetSettings()->SetPreferredColorScheme(
+ blink::PreferredColorScheme::kLight);
+ }
+ }
+ } else if (info.name ==
+ ASCIIToUTF16(ChromeContentClient::kPDFExtensionPluginName)) {
+ // Report PDF load metrics. Since the PDF plugin is comprised of an
+ // extension that loads a second plugin, avoid double counting by
+ // ignoring the creation of the second plugin.
+ bool is_main_frame_plugin_document =
+ render_frame->IsMainFrame() &&
+ render_frame->GetWebFrame()->GetDocument().IsPluginDocument();
+ ReportPDFLoadStatus(
+ is_main_frame_plugin_document
+ ? PDFLoadStatus::kLoadedFullPagePdfWithPdfium
+ : PDFLoadStatus::kLoadedEmbeddedPdfWithPdfium);
+ }
+
+ // Delay loading plugins if prerendering.
+ // TODO(mmenke): In the case of prerendering, feed into
+ // ChromeContentRendererClient::CreatePlugin instead, to
+ // reduce the chance of future regressions.
+ bool is_prerendering =
+ prerender::PrerenderHelper::IsPrerendering(render_frame);
+
+ bool power_saver_setting_on =
+ status == chrome::mojom::PluginStatus::kPlayImportantContent;
+ PowerSaverInfo power_saver_info =
+ PowerSaverInfo::Get(render_frame, power_saver_setting_on, params,
+ info, frame->GetDocument().Url());
+ if (power_saver_info.blocked_for_background_tab || is_prerendering ||
+ !power_saver_info.poster_attribute.empty() ||
+ power_saver_info.power_saver_enabled) {
+ placeholder = ChromePluginPlaceholder::CreateBlockedPlugin(
+ render_frame, params, info, identifier, group_name,
+ power_saver_info.poster_attribute.empty()
+ ? IDR_BLOCKED_PLUGIN_HTML
+ : IDR_PLUGIN_POSTER_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_BLOCKED, group_name),
+ power_saver_info);
+ placeholder->set_blocked_for_prerendering(is_prerendering);
+
+ // Because we can't determine the size of a plugin until it loads,
+ // all plugins are treated as tiny until proven otherwise.
+ placeholder->set_blocked_for_tinyness(
+ power_saver_info.power_saver_enabled);
+
+ placeholder->AllowLoading();
+ break;
+ }
+
+ // Skip the placeholder for non-Flash plugins or if Plugin Power Saver
+ // is disabled for testing.
+ return render_frame->CreatePlugin(info, params, nullptr);
+ }
+ case chrome::mojom::PluginStatus::kDisabled: {
+ PluginUMAReporter::GetInstance()->ReportPluginDisabled(orig_mime_type,
+ url);
+ if (info.name ==
+ ASCIIToUTF16(ChromeContentClient::kPDFExtensionPluginName)) {
+ ReportPDFLoadStatus(
+ PDFLoadStatus::kShowedDisabledPluginPlaceholderForEmbeddedPdf);
+
+ if (base::FeatureList::IsEnabled(
+ features::kClickToOpenPDFPlaceholder)) {
+ return PDFPluginPlaceholder::CreatePDFPlaceholder(render_frame,
+ params)
+ ->plugin();
+ }
+ }
+
+ placeholder = create_blocked_plugin(
+ IDR_DISABLED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_DISABLED, group_name));
+ break;
+ }
+ case chrome::mojom::PluginStatus::kFlashHiddenPreferHtml: {
+ placeholder = create_blocked_plugin(
+ IDR_PREFER_HTML_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_PREFER_HTML_BY_DEFAULT,
+ group_name));
+ break;
+ }
+ case chrome::mojom::PluginStatus::kOutdatedBlocked: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_OUTDATED, group_name));
+ placeholder->AllowLoading();
+ mojo::AssociatedRemote<chrome::mojom::PluginHost> plugin_host;
+ render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
+ plugin_host.BindNewEndpointAndPassReceiver());
+ plugin_host->BlockedOutdatedPlugin(placeholder->BindPluginRenderer(),
+ identifier);
+ break;
+ }
+ case chrome::mojom::PluginStatus::kOutdatedDisallowed: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_OUTDATED, group_name));
+ break;
+ }
+ case chrome::mojom::PluginStatus::kUnauthorized: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_NOT_AUTHORIZED, group_name));
+ placeholder->AllowLoading();
+ mojo::AssociatedRemote<chrome::mojom::PluginAuthHost> plugin_auth_host;
+ render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
+ plugin_auth_host.BindNewEndpointAndPassReceiver());
+ plugin_auth_host->BlockedUnauthorizedPlugin(group_name, identifier);
+ observer->DidBlockContentType(content_type, group_name);
+ break;
+ }
+ case chrome::mojom::PluginStatus::kBlocked: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_BLOCKED, group_name));
+ placeholder->AllowLoading();
+ RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Blocked"));
+ observer->DidBlockContentType(content_type, group_name);
+ break;
+ }
+ case chrome::mojom::PluginStatus::kBlockedByPolicy: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_BLOCKED_BY_POLICY,
+ group_name));
+ RenderThread::Get()->RecordAction(
+ UserMetricsAction("Plugin_BlockedByPolicy"));
+ observer->DidBlockContentType(content_type, group_name);
+ break;
+ }
+ case chrome::mojom::PluginStatus::kBlockedNoLoading: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_BLOCKED_NO_LOADING,
+ group_name));
+ observer->DidBlockContentType(content_type, group_name);
+ break;
+ }
+ case chrome::mojom::PluginStatus::kComponentUpdateRequired: {
+ placeholder = create_blocked_plugin(
+ IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_OUTDATED, group_name));
+ placeholder->AllowLoading();
+ mojo::AssociatedRemote<chrome::mojom::PluginHost> plugin_host;
+ render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
+ plugin_host.BindNewEndpointAndPassReceiver());
+ plugin_host->BlockedComponentUpdatedPlugin(
+ placeholder->BindPluginRenderer(), identifier);
+ break;
+ }
+
+ case chrome::mojom::PluginStatus::kRestartRequired: {
+#if defined(OS_LINUX)
+ placeholder =
+ create_blocked_plugin(IDR_BLOCKED_PLUGIN_HTML,
+ l10n_util::GetStringFUTF16(
+ IDS_PLUGIN_RESTART_REQUIRED, group_name));
+#endif
+ break;
+ }
+ }
+ }
+ placeholder->SetStatus(status);
+ return placeholder->plugin();
+}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+// For NaCl content handling plugins, the NaCl manifest is stored in an
+// additonal 'nacl' param associated with the MIME type.
+// static
+GURL ChromeContentRendererClient::GetNaClContentHandlerURL(
+ const std::string& actual_mime_type,
+ const content::WebPluginInfo& plugin) {
+ // Look for the manifest URL among the MIME type's additonal parameters.
+ const char kNaClPluginManifestAttribute[] = "nacl";
+ base::string16 nacl_attr = ASCIIToUTF16(kNaClPluginManifestAttribute);
+ for (size_t i = 0; i < plugin.mime_types.size(); ++i) {
+ if (plugin.mime_types[i].mime_type == actual_mime_type) {
+ for (const auto& p : plugin.mime_types[i].additional_params) {
+ if (p.name == nacl_attr)
+ return GURL(p.value);
+ }
+ break;
+ }
+ }
+ return GURL();
+}
+
+void ChromeContentRendererClient::GetInterface(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle interface_pipe) {
+ // TODO(crbug.com/977637): Get rid of the use of this implementation of
+ // |service_manager::LocalInterfaceProvider|. This was done only to avoid
+ // churning spellcheck code while eliminting the "chrome" and
+ // "chrome_renderer" services. Spellcheck is (and should remain) the only
+ // consumer of this implementation.
+ RenderThread::Get()->BindHostReceiver(
+ mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe)));
+}
+
+#if BUILDFLAG(ENABLE_NACL)
+// static
+bool ChromeContentRendererClient::IsNativeNaClAllowed(
+ const GURL& app_url,
+ bool is_nacl_unrestricted,
+ const Extension* extension) {
+ bool is_invoked_by_webstore_installed_extension = false;
+ bool is_extension_unrestricted = false;
+ bool is_extension_force_installed = false;
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ bool is_extension_from_webstore = extension && extension->from_webstore();
+
+ bool is_invoked_by_extension = app_url.SchemeIs(extensions::kExtensionScheme);
+ bool is_invoked_by_hosted_app = extension && extension->is_hosted_app() &&
+ extension->web_extent().MatchesURL(app_url);
+
+ is_invoked_by_webstore_installed_extension =
+ is_extension_from_webstore &&
+ (is_invoked_by_extension || is_invoked_by_hosted_app);
+
+ // Allow built-in extensions and developer mode extensions.
+ is_extension_unrestricted =
+ extension &&
+ (extensions::Manifest::IsUnpackedLocation(extension->location()) ||
+ extensions::Manifest::IsComponentLocation(extension->location()));
+ // Allow extensions force installed by admin policy.
+ is_extension_force_installed =
+ extension &&
+ extensions::Manifest::IsPolicyLocation(extension->location());
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+ // Allow NaCl under any of the following circumstances:
+ // 1) An extension is loaded unpacked or built-in (component) to Chrome.
+ // 2) An extension is force installed by policy.
+ // 3) An extension is installed from the webstore, and invoked in that
+ // context (hosted app URL or chrome-extension:// scheme).
+ // 4) --enable-nacl is set.
+ bool is_nacl_allowed_by_location = is_extension_unrestricted ||
+ is_extension_force_installed ||
+ is_invoked_by_webstore_installed_extension;
+ bool is_nacl_allowed = is_nacl_allowed_by_location || is_nacl_unrestricted;
+ return is_nacl_allowed;
+}
+#endif // BUILDFLAG(ENABLE_NACL)
+
+bool ChromeContentRendererClient::HasErrorPage(int http_status_code) {
+ // Use an internal error page, if we have one for the status code.
+ return error_page::LocalizedError::HasStrings(
+ error_page::Error::kHttpErrorDomain, http_status_code);
+}
+
+bool ChromeContentRendererClient::ShouldSuppressErrorPage(
+ content::RenderFrame* render_frame,
+ const GURL& url) {
+ // Unit tests for ChromeContentRendererClient pass a NULL RenderFrame here.
+ // Unfortunately it's very difficult to construct a mock RenderView, so skip
+ // this functionality in this case.
+ if (render_frame &&
+ NetErrorHelper::Get(render_frame)->ShouldSuppressErrorPage(url)) {
+ return true;
+ }
+
+ // Do not flash an error page if the Instant new tab page fails to load.
+ bool is_instant_ntp = false;
+#if !defined(OS_ANDROID)
+ is_instant_ntp = SearchBouncer::GetInstance()->IsNewTabPage(url);
+#endif
+ return is_instant_ntp;
+}
+
+bool ChromeContentRendererClient::ShouldTrackUseCounter(const GURL& url) {
+ bool is_instant_ntp = false;
+#if !defined(OS_ANDROID)
+ is_instant_ntp = SearchBouncer::GetInstance()->IsNewTabPage(url);
+#endif
+ return !is_instant_ntp;
+}
+
+void ChromeContentRendererClient::PrepareErrorPage(
+ content::RenderFrame* render_frame,
+ const blink::WebURLError& web_error,
+ const std::string& http_method,
+ std::string* error_html) {
+ NetErrorHelper::Get(render_frame)
+ ->PrepareErrorPage(
+ error_page::Error::NetError(web_error.url(), web_error.reason(),
+ web_error.has_copy_in_cache()),
+ http_method == "POST", error_html);
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+ SupervisedUserErrorPageControllerDelegateImpl::Get(render_frame)
+ ->PrepareForErrorPage();
+#endif
+}
+
+void ChromeContentRendererClient::PrepareErrorPageForHttpStatusError(
+ content::RenderFrame* render_frame,
+ const GURL& unreachable_url,
+ const std::string& http_method,
+ int http_status,
+ std::string* error_html) {
+ NetErrorHelper::Get(render_frame)
+ ->PrepareErrorPage(
+ error_page::Error::HttpError(unreachable_url, http_status),
+ http_method == "POST", error_html);
+}
+
+void ChromeContentRendererClient::GetErrorDescription(
+ const blink::WebURLError& web_error,
+ const std::string& http_method,
+ base::string16* error_description) {
+ error_page::Error error = error_page::Error::NetError(
+ web_error.url(), web_error.reason(), web_error.has_copy_in_cache());
+ if (error_description) {
+ *error_description = error_page::LocalizedError::GetErrorDetails(
+ error.domain(), error.reason(), http_method == "POST");
+ }
+}
+
+void ChromeContentRendererClient::PostIOThreadCreated(
+ base::SingleThreadTaskRunner* io_thread_task_runner) {
+ io_thread_task_runner->PostTask(
+ FROM_HERE, base::BindOnce(&ThreadProfiler::StartOnChildThread,
+ metrics::CallStackProfileParams::IO_THREAD));
+}
+
+void ChromeContentRendererClient::PostCompositorThreadCreated(
+ base::SingleThreadTaskRunner* compositor_thread_task_runner) {
+ compositor_thread_task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ThreadProfiler::StartOnChildThread,
+ metrics::CallStackProfileParams::COMPOSITOR_THREAD));
+}
+
+bool ChromeContentRendererClient::RunIdleHandlerWhenWidgetsHidden() {
+ return !IsStandaloneContentExtensionProcess();
+}
+
+bool ChromeContentRendererClient::AllowPopup() {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return ChromeExtensionsRendererClient::GetInstance()->AllowPopup();
+#else
+ return false;
+#endif
+}
+
+bool ChromeContentRendererClient::ShouldFork(WebLocalFrame* frame,
+ const GURL& url,
+ const std::string& http_method,
+ bool is_initial_navigation,
+ bool is_server_redirect) {
+ // TODO(lukasza): https://crbug.com/650694: For now, we skip the rest for POST
+ // submissions. This is because 1) in M54 there are some remaining issues
+ // with POST in OpenURL path (e.g. https://crbug.com/648648) and 2) OpenURL
+ // path regresses (blocks) navigations that result in downloads
+ // (https://crbug.com/646261). In the long-term we should avoid forking for
+ // extensions (not hosted apps though) altogether and rely on transfers logic
+ // instead.
+ if (http_method != "GET")
+ return false;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ bool should_fork = ChromeExtensionsRendererClient::ShouldFork(
+ frame, url, is_initial_navigation, is_server_redirect);
+ if (should_fork)
+ return true;
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+ return false;
+}
+
+void ChromeContentRendererClient::WillSendRequest(
+ WebLocalFrame* frame,
+ ui::PageTransition transition_type,
+ const blink::WebURL& url,
+ const url::Origin* initiator_origin,
+ GURL* new_url,
+ bool* attach_same_site_cookies) {
+// Check whether the request should be allowed. If not allowed, we reset the
+// URL to something invalid to prevent the request and cause an error.
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()->WillSendRequest(
+ frame, transition_type, url, initiator_origin, new_url,
+ attach_same_site_cookies);
+ if (!new_url->is_empty())
+ return;
+#endif
+
+ if (!url.ProtocolIs(chrome::kChromeSearchScheme))
+ return;
+
+#if !defined(OS_ANDROID)
+ SearchBox* search_box =
+ SearchBox::Get(content::RenderFrame::FromWebFrame(frame->LocalRoot()));
+ if (search_box) {
+ // Note: this GURL copy could be avoided if host() were added to WebURL.
+ GURL gurl(url);
+ if (gurl.host_piece() == chrome::kChromeUIFaviconHost)
+ search_box->GenerateImageURLFromTransientURL(url, new_url);
+ }
+#endif // !defined(OS_ANDROID)
+}
+
+bool ChromeContentRendererClient::IsPrefetchOnly(
+ content::RenderFrame* render_frame,
+ const blink::WebURLRequest& request) {
+ return prerender::PrerenderHelper::GetPrerenderMode(render_frame) ==
+ prerender::PREFETCH_ONLY;
+}
+
+uint64_t ChromeContentRendererClient::VisitedLinkHash(const char* canonical_url,
+ size_t length) {
+ return chrome_observer_->visited_link_slave()->ComputeURLFingerprint(
+ canonical_url, length);
+}
+
+bool ChromeContentRendererClient::IsLinkVisited(uint64_t link_hash) {
+ return chrome_observer_->visited_link_slave()->IsVisited(link_hash);
+}
+
+blink::WebPrescientNetworking*
+ChromeContentRendererClient::GetPrescientNetworking() {
+ return prescient_networking_dispatcher_.get();
+}
+
+bool ChromeContentRendererClient::IsExternalPepperPlugin(
+ const std::string& module_name) {
+ // TODO(bbudge) remove this when the trusted NaCl plugin has been removed.
+ // We must defer certain plugin events for NaCl instances since we switch
+ // from the in-process to the out-of-process proxy after instantiating them.
+ return module_name == "Native Client";
+}
+
+bool ChromeContentRendererClient::IsOriginIsolatedPepperPlugin(
+ const base::FilePath& plugin_path) {
+ return plugin_path.value() == ChromeContentClient::kPDFPluginPath;
+}
+
+#if BUILDFLAG(ENABLE_PLUGINS) && BUILDFLAG(ENABLE_EXTENSIONS)
+bool ChromeContentRendererClient::IsExtensionOrSharedModuleWhitelisted(
+ const GURL& url,
+ const std::set<std::string>& whitelist) {
+ const extensions::ExtensionSet* extension_set =
+ extensions::RendererExtensionRegistry::Get()->GetMainThreadExtensionSet();
+ return ::IsExtensionOrSharedModuleWhitelisted(url, extension_set, whitelist);
+}
+#endif
+
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+void ChromeContentRendererClient::InitSpellCheck() {
+ spellcheck_ = std::make_unique<SpellCheck>(&registry_, this);
+}
+#endif
+
+ChromeRenderThreadObserver* ChromeContentRendererClient::GetChromeObserver()
+ const {
+ return chrome_observer_.get();
+}
+
+std::unique_ptr<content::WebSocketHandshakeThrottleProvider>
+ChromeContentRendererClient::CreateWebSocketHandshakeThrottleProvider() {
+ return std::make_unique<WebSocketHandshakeThrottleProviderImpl>(
+ browser_interface_broker_.get());
+}
+
+void ChromeContentRendererClient::AddSupportedKeySystems(
+ std::vector<std::unique_ptr<::media::KeySystemProperties>>* key_systems) {
+ key_systems_provider_.AddSupportedKeySystems(key_systems);
+}
+
+bool ChromeContentRendererClient::IsKeySystemsUpdateNeeded() {
+ return key_systems_provider_.IsKeySystemsUpdateNeeded();
+}
+
+bool ChromeContentRendererClient::ShouldReportDetailedMessageForSource(
+ const base::string16& source) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return extensions::IsSourceFromAnExtension(source);
+#else
+ return false;
+#endif
+}
+
+std::unique_ptr<blink::WebContentSettingsClient>
+ChromeContentRendererClient::CreateWorkerContentSettingsClient(
+ content::RenderFrame* render_frame) {
+ return std::make_unique<WorkerContentSettingsClient>(render_frame);
+}
+
+bool ChromeContentRendererClient::IsPluginAllowedToUseDevChannelAPIs() {
+#if BUILDFLAG(ENABLE_PLUGINS)
+ // Allow access for tests.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnablePepperTesting)) {
+ return true;
+ }
+
+ version_info::Channel channel = chrome::GetChannel();
+ // Allow dev channel APIs to be used on "Canary", "Dev", and "Unknown"
+ // releases of Chrome. Permitting "Unknown" allows these APIs to be used on
+ // Chromium builds as well.
+ return channel <= version_info::Channel::DEV;
+#else
+ return false;
+#endif
+}
+
+bool ChromeContentRendererClient::IsPluginAllowedToUseCameraDeviceAPI(
+ const GURL& url) {
+#if BUILDFLAG(ENABLE_PLUGINS) && BUILDFLAG(ENABLE_EXTENSIONS)
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnablePepperTesting))
+ return true;
+
+ if (IsExtensionOrSharedModuleWhitelisted(url, allowed_camera_device_origins_))
+ return true;
+#endif
+
+ return false;
+}
+
+content::BrowserPluginDelegate*
+ChromeContentRendererClient::CreateBrowserPluginDelegate(
+ content::RenderFrame* render_frame,
+ const content::WebPluginInfo& info,
+ const std::string& mime_type,
+ const GURL& original_url) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return ChromeExtensionsRendererClient::CreateBrowserPluginDelegate(
+ render_frame, info, mime_type, original_url);
+#else
+ return nullptr;
+#endif
+}
+
+void ChromeContentRendererClient::RecordRappor(const std::string& metric,
+ const std::string& sample) {
+ if (!rappor_recorder_)
+ RenderThread::Get()->BindHostReceiver(
+ rappor_recorder_.BindNewPipeAndPassReceiver());
+ rappor_recorder_->RecordRappor(metric, sample);
+}
+
+void ChromeContentRendererClient::RecordRapporURL(const std::string& metric,
+ const GURL& url) {
+ if (!rappor_recorder_)
+ RenderThread::Get()->BindHostReceiver(
+ rappor_recorder_.BindNewPipeAndPassReceiver());
+ rappor_recorder_->RecordRapporURL(metric, url);
+}
+
+void ChromeContentRendererClient::RunScriptsAtDocumentStart(
+ content::RenderFrame* render_frame) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()->RunScriptsAtDocumentStart(
+ render_frame);
+ // |render_frame| might be dead by now.
+#endif
+}
+
+void ChromeContentRendererClient::RunScriptsAtDocumentEnd(
+ content::RenderFrame* render_frame) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()->RunScriptsAtDocumentEnd(
+ render_frame);
+ // |render_frame| might be dead by now.
+#endif
+}
+
+void ChromeContentRendererClient::RunScriptsAtDocumentIdle(
+ content::RenderFrame* render_frame) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()->RunScriptsAtDocumentIdle(
+ render_frame);
+ // |render_frame| might be dead by now.
+#endif
+}
+
+void ChromeContentRendererClient::
+ SetRuntimeFeaturesDefaultsBeforeBlinkInitialization() {
+ // The performance manager service interfaces are provided by the chrome
+ // embedder only.
+ blink::WebRuntimeFeatures::EnablePerformanceManagerInstrumentation(true);
+
+// Web Share is shipped on Android, experimental otherwise. It is enabled here,
+// in chrome/, to avoid it being made available in other clients of content/
+// that do not have a Web Share Mojo implementation.
+#if defined(OS_ANDROID)
+ blink::WebRuntimeFeatures::EnableWebShare(true);
+ blink::WebRuntimeFeatures::EnableWebShareV2(true);
+#endif
+
+ if (base::FeatureList::IsEnabled(subresource_filter::kAdTagging))
+ blink::WebRuntimeFeatures::EnableAdTagging(true);
+}
+
+void ChromeContentRendererClient::
+ WillInitializeServiceWorkerContextOnWorkerThread() {
+ // This is called on the service worker thread.
+ ThreadProfiler::StartOnChildThread(
+ metrics::CallStackProfileParams::SERVICE_WORKER_THREAD);
+}
+
+void ChromeContentRendererClient::
+ DidInitializeServiceWorkerContextOnWorkerThread(
+ blink::WebServiceWorkerContextProxy* context_proxy,
+ const GURL& service_worker_scope,
+ const GURL& script_url) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()
+ ->extension_dispatcher()
+ ->DidInitializeServiceWorkerContextOnWorkerThread(
+ context_proxy, service_worker_scope, script_url);
+#endif
+}
+
+void ChromeContentRendererClient::WillEvaluateServiceWorkerOnWorkerThread(
+ blink::WebServiceWorkerContextProxy* context_proxy,
+ v8::Local<v8::Context> v8_context,
+ int64_t service_worker_version_id,
+ const GURL& service_worker_scope,
+ const GURL& script_url) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()
+ ->extension_dispatcher()
+ ->WillEvaluateServiceWorkerOnWorkerThread(
+ context_proxy, v8_context, service_worker_version_id,
+ service_worker_scope, script_url);
+#endif
+}
+
+void ChromeContentRendererClient::DidStartServiceWorkerContextOnWorkerThread(
+ int64_t service_worker_version_id,
+ const GURL& service_worker_scope,
+ const GURL& script_url) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()
+ ->extension_dispatcher()
+ ->DidStartServiceWorkerContextOnWorkerThread(
+ service_worker_version_id, service_worker_scope, script_url);
+#endif
+}
+
+void ChromeContentRendererClient::WillDestroyServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> context,
+ int64_t service_worker_version_id,
+ const GURL& service_worker_scope,
+ const GURL& script_url) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ ChromeExtensionsRendererClient::GetInstance()
+ ->extension_dispatcher()
+ ->WillDestroyServiceWorkerContextOnWorkerThread(
+ context, service_worker_version_id, service_worker_scope, script_url);
+#endif
+}
+
+bool ChromeContentRendererClient::IsExcludedHeaderForServiceWorkerFetchEvent(
+ const std::string& header_name) {
+ return variations::IsVariationsHeader(header_name);
+}
+
+// If we're in an extension, there is no need disabling multiple routes as
+// chrome.system.network.getNetworkInterfaces provides the same
+// information. Also, the enforcement of sending and binding UDP is already done
+// by chrome extension permission model.
+bool ChromeContentRendererClient::ShouldEnforceWebRTCRoutingPreferences() {
+ return !IsStandaloneContentExtensionProcess();
+}
+
+GURL ChromeContentRendererClient::OverrideFlashEmbedWithHTML(const GURL& url) {
+ if (!url.is_valid())
+ return GURL();
+
+ return FlashEmbedRewrite::RewriteFlashEmbedURL(url);
+}
+
+std::unique_ptr<content::URLLoaderThrottleProvider>
+ChromeContentRendererClient::CreateURLLoaderThrottleProvider(
+ content::URLLoaderThrottleProviderType provider_type) {
+ return std::make_unique<URLLoaderThrottleProviderImpl>(
+ browser_interface_broker_.get(), provider_type, this);
+}
+
+blink::WebFrame* ChromeContentRendererClient::FindFrame(
+ blink::WebLocalFrame* relative_to_frame,
+ const std::string& name) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return ChromeExtensionsRendererClient::FindFrame(relative_to_frame, name);
+#else
+ return nullptr;
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+}
+
+bool ChromeContentRendererClient::IsSafeRedirectTarget(const GURL& url) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (url.SchemeIs(extensions::kExtensionScheme)) {
+ const extensions::Extension* extension =
+ extensions::RendererExtensionRegistry::Get()->GetByID(url.host());
+ if (!extension)
+ return false;
+ return extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ extension, url.path());
+ }
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+ return true;
+}
+
+void ChromeContentRendererClient::DidSetUserAgent(
+ const std::string& user_agent) {
+#if BUILDFLAG(ENABLE_PRINTING)
+ printing::SetAgent(user_agent);
+#endif
+}
+
+bool ChromeContentRendererClient::RequiresHtmlImports(const GURL& url) {
+ // Chrome Web UI pages are in the process of being migrated to use the HTML
+ // Imports Polyfill so that they will not require native imports. Return true
+ // for only pages that have not been updated yet. See
+ // https://crbug.com/937747.
+ bool can_use_polyfill = url.host() == chrome::kChromeUIExtensionsHost ||
+ url.host() == chrome::kChromeUIDownloadsHost;
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+ can_use_polyfill |= url.host() == chrome::kChromeUIPrintHost;
+#endif
+ return url.SchemeIs(content::kChromeUIScheme) && !can_use_polyfill;
+}
diff --git a/chromium/chrome/renderer/chrome_content_renderer_client.h b/chromium/chrome/renderer/chrome_content_renderer_client.h
new file mode 100644
index 00000000000..d306846f872
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_content_renderer_client.h
@@ -0,0 +1,308 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CHROME_CONTENT_RENDERER_CLIENT_H_
+#define CHROME_RENDERER_CHROME_CONTENT_RENDERER_CLIENT_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "chrome/common/plugin.mojom.h"
+#include "chrome/renderer/media/chrome_key_systems_provider.h"
+#include "components/nacl/common/buildflags.h"
+#include "components/rappor/public/mojom/rappor_recorder.mojom.h"
+#include "components/spellcheck/spellcheck_buildflags.h"
+#include "content/public/renderer/content_renderer_client.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "media/media_buildflags.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "printing/buildflags/buildflags.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "services/service_manager/public/cpp/local_interface_provider.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+#include "v8/include/v8.h"
+
+#if defined(OS_WIN)
+#include "chrome/common/conflicts/remote_module_watcher_win.h"
+#endif
+
+class ChromeRenderThreadObserver;
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+class ChromePDFPrintClient;
+#endif
+class PrescientNetworkingDispatcher;
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+class SpellCheck;
+#endif
+class ThreadProfiler;
+
+namespace blink {
+class WebServiceWorkerContextProxy;
+}
+
+namespace chrome {
+class WebRtcLoggingAgentImpl;
+} // namespace chrome
+
+namespace content {
+class BrowserPluginDelegate;
+struct WebPluginInfo;
+} // namespace content
+
+namespace network_hints {
+class PrescientNetworkingDispatcher;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace prerender {
+class PrerenderDispatcher;
+}
+
+namespace subresource_filter {
+class UnverifiedRulesetDealer;
+}
+
+namespace web_cache {
+class WebCacheImpl;
+}
+
+class ChromeContentRendererClient
+ : public content::ContentRendererClient,
+ public service_manager::LocalInterfaceProvider {
+ public:
+ ChromeContentRendererClient();
+ ~ChromeContentRendererClient() override;
+
+ void RenderThreadStarted() override;
+ void RenderFrameCreated(content::RenderFrame* render_frame) override;
+ void RenderViewCreated(content::RenderView* render_view) override;
+ SkBitmap* GetSadPluginBitmap() override;
+ SkBitmap* GetSadWebViewBitmap() override;
+ bool IsPluginHandledExternally(content::RenderFrame* render_frame,
+ const blink::WebElement& plugin_element,
+ const GURL& original_url,
+ const std::string& mime_type) override;
+ v8::Local<v8::Object> GetScriptableObject(
+ const blink::WebElement& plugin_element,
+ v8::Isolate* isolate) override;
+ bool OverrideCreatePlugin(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ blink::WebPlugin** plugin) override;
+ blink::WebPlugin* CreatePluginReplacement(
+ content::RenderFrame* render_frame,
+ const base::FilePath& plugin_path) override;
+ bool HasErrorPage(int http_status_code) override;
+ bool ShouldSuppressErrorPage(content::RenderFrame* render_frame,
+ const GURL& url) override;
+ bool ShouldTrackUseCounter(const GURL& url) override;
+ void PrepareErrorPage(content::RenderFrame* render_frame,
+ const blink::WebURLError& error,
+ const std::string& http_method,
+ std::string* error_html) override;
+ void PrepareErrorPageForHttpStatusError(content::RenderFrame* render_frame,
+ const GURL& unreachable_url,
+ const std::string& http_method,
+ int http_status,
+ std::string* error_html) override;
+
+ void GetErrorDescription(const blink::WebURLError& error,
+ const std::string& http_method,
+ base::string16* error_description) override;
+
+ bool DeferMediaLoad(content::RenderFrame* render_frame,
+ bool has_played_media_before,
+ base::OnceClosure closure) override;
+ void PostIOThreadCreated(
+ base::SingleThreadTaskRunner* io_thread_task_runner) override;
+ void PostCompositorThreadCreated(
+ base::SingleThreadTaskRunner* compositor_thread_task_runner) override;
+ bool RunIdleHandlerWhenWidgetsHidden() override;
+ bool AllowPopup() override;
+ bool ShouldFork(blink::WebLocalFrame* frame,
+ const GURL& url,
+ const std::string& http_method,
+ bool is_initial_navigation,
+ bool is_server_redirect) override;
+ void WillSendRequest(blink::WebLocalFrame* frame,
+ ui::PageTransition transition_type,
+ const blink::WebURL& url,
+ const url::Origin* initiator_origin,
+ GURL* new_url,
+ bool* attach_same_site_cookies) override;
+ bool IsPrefetchOnly(content::RenderFrame* render_frame,
+ const blink::WebURLRequest& request) override;
+ uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override;
+ bool IsLinkVisited(uint64_t link_hash) override;
+ blink::WebPrescientNetworking* GetPrescientNetworking() override;
+ bool IsExternalPepperPlugin(const std::string& module_name) override;
+ bool IsOriginIsolatedPepperPlugin(const base::FilePath& plugin_path) override;
+ std::unique_ptr<content::WebSocketHandshakeThrottleProvider>
+ CreateWebSocketHandshakeThrottleProvider() override;
+ bool ShouldReportDetailedMessageForSource(
+ const base::string16& source) override;
+ std::unique_ptr<blink::WebContentSettingsClient>
+ CreateWorkerContentSettingsClient(
+ content::RenderFrame* render_frame) override;
+ void AddSupportedKeySystems(
+ std::vector<std::unique_ptr<::media::KeySystemProperties>>* key_systems)
+ override;
+ bool IsKeySystemsUpdateNeeded() override;
+ bool IsPluginAllowedToUseDevChannelAPIs() override;
+ bool IsPluginAllowedToUseCameraDeviceAPI(const GURL& url) override;
+ content::BrowserPluginDelegate* CreateBrowserPluginDelegate(
+ content::RenderFrame* render_frame,
+ const content::WebPluginInfo& info,
+ const std::string& mime_type,
+ const GURL& original_url) override;
+ void RecordRappor(const std::string& metric,
+ const std::string& sample) override;
+ void RecordRapporURL(const std::string& metric, const GURL& url) override;
+ void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
+ void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
+ void RunScriptsAtDocumentIdle(content::RenderFrame* render_frame) override;
+ void SetRuntimeFeaturesDefaultsBeforeBlinkInitialization() override;
+ void WillInitializeServiceWorkerContextOnWorkerThread() override;
+ void DidInitializeServiceWorkerContextOnWorkerThread(
+ blink::WebServiceWorkerContextProxy* context_proxy,
+ const GURL& service_worker_scope,
+ const GURL& script_url) override;
+ void WillEvaluateServiceWorkerOnWorkerThread(
+ blink::WebServiceWorkerContextProxy* context_proxy,
+ v8::Local<v8::Context> v8_context,
+ int64_t service_worker_version_id,
+ const GURL& service_worker_scope,
+ const GURL& script_url) override;
+ void DidStartServiceWorkerContextOnWorkerThread(
+ int64_t service_worker_version_id,
+ const GURL& service_worker_scope,
+ const GURL& script_url) override;
+ void WillDestroyServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> context,
+ int64_t service_worker_version_id,
+ const GURL& service_worker_scope,
+ const GURL& script_url) override;
+ bool IsExcludedHeaderForServiceWorkerFetchEvent(
+ const std::string& header_name) override;
+ bool ShouldEnforceWebRTCRoutingPreferences() override;
+ GURL OverrideFlashEmbedWithHTML(const GURL& url) override;
+ std::unique_ptr<content::URLLoaderThrottleProvider>
+ CreateURLLoaderThrottleProvider(
+ content::URLLoaderThrottleProviderType provider_type) override;
+ blink::WebFrame* FindFrame(blink::WebLocalFrame* relative_to_frame,
+ const std::string& name) override;
+ bool IsSafeRedirectTarget(const GURL& url) override;
+ void DidSetUserAgent(const std::string& user_agent) override;
+ bool RequiresHtmlImports(const GURL& url) override;
+ void BindReceiverOnMainThread(mojo::GenericPendingReceiver receiver) override;
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ static mojo::AssociatedRemote<chrome::mojom::PluginInfoHost>&
+ GetPluginInfoHost();
+
+ static blink::WebPlugin* CreatePlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const chrome::mojom::PluginInfo& plugin_info);
+#endif
+
+#if BUILDFLAG(ENABLE_PLUGINS) && BUILDFLAG(ENABLE_EXTENSIONS)
+ static bool IsExtensionOrSharedModuleWhitelisted(
+ const GURL& url,
+ const std::set<std::string>& whitelist);
+#endif
+
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+ void InitSpellCheck();
+#endif
+
+ prerender::PrerenderDispatcher* prerender_dispatcher() const {
+ return prerender_dispatcher_.get();
+ }
+
+ ChromeRenderThreadObserver* GetChromeObserver() const;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ChromeContentRendererClientTest, NaClRestriction);
+ FRIEND_TEST_ALL_PREFIXES(ChromeContentRendererClientTest,
+ ShouldSuppressErrorPage);
+
+ static GURL GetNaClContentHandlerURL(const std::string& actual_mime_type,
+ const content::WebPluginInfo& plugin);
+
+ // service_manager::LocalInterfaceProvider:
+ void GetInterface(const std::string& name,
+ mojo::ScopedMessagePipeHandle request_handle) override;
+
+ // Time at which this object was created. This is very close to the time at
+ // which the RendererMain function was entered.
+ base::TimeTicks main_entry_time_;
+
+#if BUILDFLAG(ENABLE_NACL)
+ // Determines if a page/app/extension is allowed to run native (non-PNaCl)
+ // NaCl modules.
+ static bool IsNativeNaClAllowed(const GURL& app_url,
+ bool is_nacl_unrestricted,
+ const extensions::Extension* extension);
+#endif
+
+#if defined(OS_WIN)
+ // Observes module load events and notifies the ModuleDatabase in the browser
+ // process. This instance is created on the main thread but then lives on the
+ // IO task runner.
+ RemoteModuleWatcher::UniquePtr remote_module_watcher_;
+#endif
+
+ // Used to profile main thread.
+ std::unique_ptr<ThreadProfiler> main_thread_profiler_;
+
+ mojo::Remote<rappor::mojom::RapporRecorder> rappor_recorder_;
+
+ std::unique_ptr<ChromeRenderThreadObserver> chrome_observer_;
+ std::unique_ptr<web_cache::WebCacheImpl> web_cache_impl_;
+ std::unique_ptr<chrome::WebRtcLoggingAgentImpl> webrtc_logging_agent_impl_;
+
+ std::unique_ptr<network_hints::PrescientNetworkingDispatcher>
+ prescient_networking_dispatcher_;
+
+ ChromeKeySystemsProvider key_systems_provider_;
+
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+ std::unique_ptr<SpellCheck> spellcheck_;
+#endif
+ std::unique_ptr<subresource_filter::UnverifiedRulesetDealer>
+ subresource_filter_ruleset_dealer_;
+ std::unique_ptr<prerender::PrerenderDispatcher> prerender_dispatcher_;
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+ std::unique_ptr<ChromePDFPrintClient> pdf_print_client_;
+#endif
+#if BUILDFLAG(ENABLE_PLUGINS)
+ std::set<std::string> allowed_camera_device_origins_;
+#endif
+
+ service_manager::BinderRegistry registry_;
+ scoped_refptr<blink::ThreadSafeBrowserInterfaceBrokerProxy>
+ browser_interface_broker_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeContentRendererClient);
+};
+
+#endif // CHROME_RENDERER_CHROME_CONTENT_RENDERER_CLIENT_H_
diff --git a/chromium/chrome/renderer/chrome_content_renderer_client_browsertest.cc b/chromium/chrome/renderer/chrome_content_renderer_client_browsertest.cc
new file mode 100644
index 00000000000..976eab2438e
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_content_renderer_client_browsertest.cc
@@ -0,0 +1,201 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_content_renderer_client.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/renderer/searchbox/search_bouncer.h"
+#include "chrome/renderer/searchbox/searchbox.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/mock_render_thread.h"
+#include "content/public/test/test_utils.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_test_sink.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#include "url/gurl.h"
+
+using ChromeContentRendererClientSearchBoxTest = ChromeRenderViewTest;
+
+const char kHtmlWithIframe[] ="<iframe srcdoc=\"Nothing here\"></iframe>";
+
+TEST_F(ChromeContentRendererClientSearchBoxTest, RewriteThumbnailURL) {
+ // Instantiate a SearchBox for the main render frame.
+ content::RenderFrame* render_frame =
+ content::RenderFrame::FromWebFrame(GetMainFrame());
+ new SearchBox(render_frame);
+
+ // Load a page that contains an iframe.
+ LoadHTML(kHtmlWithIframe);
+
+ ChromeContentRendererClient* client =
+ static_cast<ChromeContentRendererClient*>(content_renderer_client_.get());
+
+ // Create a thumbnail URL containing the correct render view ID and an
+ // arbitrary instant restricted ID.
+ GURL thumbnail_url(base::StringPrintf(
+ "chrome-search:/thumb/%i/1",
+ render_frame->GetRenderView()->GetRoutingID()));
+
+ GURL result;
+ bool attach_same_site_cookies;
+ // Make sure the SearchBox rewrites a thumbnail request from the main frame.
+ client->WillSendRequest(GetMainFrame(), ui::PAGE_TRANSITION_LINK,
+ blink::WebURL(thumbnail_url), nullptr, &result,
+ &attach_same_site_cookies);
+ EXPECT_NE(result, thumbnail_url);
+
+ // Make sure the SearchBox rewrites a thumbnail request from the iframe.
+ blink::WebFrame* child_frame = GetMainFrame()->FirstChild();
+ ASSERT_TRUE(child_frame);
+ ASSERT_TRUE(child_frame->IsWebLocalFrame());
+ blink::WebLocalFrame* local_child =
+ static_cast<blink::WebLocalFrame*>(child_frame);
+ client->WillSendRequest(local_child, ui::PAGE_TRANSITION_LINK,
+ blink::WebURL(thumbnail_url), nullptr, &result,
+ &attach_same_site_cookies);
+ EXPECT_NE(result, thumbnail_url);
+}
+
+// The tests below examine Youtube requests that use the Flash API and ensure
+// that the requests have been modified to instead use HTML5. The tests also
+// check the MIME type of the request to ensure that it is "text/html".
+namespace {
+
+struct FlashEmbedsTestData {
+ std::string name;
+ std::string host;
+ std::string path;
+ std::string type;
+ std::string expected_url;
+};
+
+const FlashEmbedsTestData kFlashEmbedsTestData[] = {
+ {"Valid URL, no parameters", "www.youtube.com", "/v/deadbeef",
+ "application/x-shockwave-flash", "/embed/deadbeef"},
+ {"Valid URL, no parameters, subdomain", "www.foo.youtube.com",
+ "/v/deadbeef", "application/x-shockwave-flash", "/embed/deadbeef"},
+ {"Valid URL, many parameters", "www.youtube.com",
+ "/v/deadbeef?start=4&fs=1", "application/x-shockwave-flash",
+ "/embed/deadbeef?start=4&fs=1"},
+ {"Invalid parameter construct, many parameters", "www.youtube.com",
+ "/v/deadbeef&bar=4&foo=6", "application/x-shockwave-flash",
+ "/embed/deadbeef?bar=4&foo=6"},
+ {"Valid URL, enablejsapi=1", "www.youtube.com", "/v/deadbeef?enablejsapi=1",
+ "application/x-shockwave-flash", "/embed/deadbeef?enablejsapi=1"}};
+
+} // namespace
+
+class ChromeContentRendererClientBrowserTest :
+ public InProcessBrowserTest,
+ public ::testing::WithParamInterface<FlashEmbedsTestData> {
+ public:
+ ChromeContentRendererClientBrowserTest()
+ : https_server_(std::make_unique<net::EmbeddedTestServer>(
+ net::EmbeddedTestServer::TYPE_HTTPS)) {}
+
+ void MonitorRequestHandler(const net::test_server::HttpRequest& request) {
+ // We're only interested in YouTube video embeds
+ if (request.headers.at("Host").find("youtube.com") == std::string::npos)
+ return;
+
+ if (request.relative_url.find("/embed") != 0 &&
+ request.relative_url.find("/v") != 0)
+ return;
+
+ auto type = request.headers.find("Accept");
+ EXPECT_NE(std::string::npos, type->second.find("text/html"))
+ << "Type is not text/html for test " << GetParam().name;
+
+ EXPECT_EQ(request.relative_url, GetParam().expected_url)
+ << "URL is wrong for test " << GetParam().name;
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ message_runner_->QuitClosure());
+ }
+
+ void WaitForYouTubeRequest() {
+ message_runner_->Run();
+ }
+
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+
+ https_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+ https_server_->RegisterRequestMonitor(base::Bind(
+ &ChromeContentRendererClientBrowserTest::MonitorRequestHandler,
+ base::Unretained(this)));
+ ASSERT_TRUE(https_server_->Start());
+ message_runner_ = new content::MessageLoopRunner();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // HTTPS server only serves a valid cert for localhost, so this is needed
+ // to load pages from other hosts without an error.
+ command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
+ }
+
+ protected:
+ net::EmbeddedTestServer* https_server() const { return https_server_.get(); }
+
+ private:
+ scoped_refptr<content::MessageLoopRunner> message_runner_;
+ std::unique_ptr<net::EmbeddedTestServer> https_server_;
+};
+
+IN_PROC_BROWSER_TEST_P(ChromeContentRendererClientBrowserTest,
+ RewriteYouTubeFlashEmbed) {
+ GURL url(https_server()->GetURL("/flash_embeds.html"));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ GURL video_url = https_server()->GetURL(GetParam().host, GetParam().path);
+ EXPECT_TRUE(ExecuteScript(web_contents, "appendEmbedToDOM('" +
+ video_url.spec() + "','" +
+ GetParam().type + "');"));
+ WaitForYouTubeRequest();
+}
+
+IN_PROC_BROWSER_TEST_P(ChromeContentRendererClientBrowserTest,
+ RewriteYouTubeFlashEmbedObject) {
+ GURL url(https_server()->GetURL("/flash_embeds.html"));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ GURL video_url = https_server()->GetURL(GetParam().host, GetParam().path);
+ EXPECT_TRUE(ExecuteScript(web_contents, "appendDataEmbedToDOM('" +
+ video_url.spec() + "','" +
+ GetParam().type + "');"));
+ WaitForYouTubeRequest();
+}
+
+INSTANTIATE_TEST_SUITE_P(FlashEmbeds,
+ ChromeContentRendererClientBrowserTest,
+ ::testing::ValuesIn(kFlashEmbedsTestData));
diff --git a/chromium/chrome/renderer/chrome_content_renderer_client_receiver_bindings.cc b/chromium/chrome/renderer/chrome_content_renderer_client_receiver_bindings.cc
new file mode 100644
index 00000000000..a92b76748ba
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_content_renderer_client_receiver_bindings.cc
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file exposes services in the renderer to the browser.
+
+#include "chrome/renderer/chrome_content_renderer_client.h"
+
+#include <memory>
+
+#include "build/build_config.h"
+#include "chrome/renderer/media/webrtc_logging_agent_impl.h"
+#include "components/safe_browsing/buildflags.h"
+
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+#include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
+#endif
+
+#if defined(OS_LINUX)
+#include "base/allocator/buildflags.h"
+#if BUILDFLAG(USE_TCMALLOC)
+#include "chrome/common/performance_manager/mojom/tcmalloc.mojom.h"
+#include "chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.h"
+#endif // BUILDFLAG(USE_TCMALLOC)
+#endif // defined(OS_LINUX)
+
+void ChromeContentRendererClient::BindReceiverOnMainThread(
+ mojo::GenericPendingReceiver receiver) {
+ if (auto agent_receiver = receiver.As<chrome::mojom::WebRtcLoggingAgent>()) {
+ if (!webrtc_logging_agent_impl_) {
+ webrtc_logging_agent_impl_ =
+ std::make_unique<chrome::WebRtcLoggingAgentImpl>();
+ }
+ webrtc_logging_agent_impl_->AddReceiver(std::move(agent_receiver));
+ return;
+ }
+
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+ if (auto setter_receiver =
+ receiver.As<safe_browsing::mojom::PhishingModelSetter>()) {
+ safe_browsing::PhishingClassifierFilter::Create(std::move(setter_receiver));
+ return;
+ }
+#endif
+
+#if defined(OS_LINUX)
+#if BUILDFLAG(USE_TCMALLOC)
+ if (auto setter_receiver = receiver.As<tcmalloc::mojom::TcmallocTunables>()) {
+ performance_manager::mechanism::TcmallocTunablesImpl::Create(
+ std::move(setter_receiver));
+ return;
+ }
+#endif // BUILDFLAG(USE_TCMALLOC)
+#endif // defined(OS_LINUX)
+
+ // TODO(crbug.com/977637): Get rid of the use of BinderRegistry here. This was
+ // done only to avoid churning spellcheck code while eliminting the "chrome"
+ // and "chrome_renderer" services. Spellcheck is (and should remain) the only
+ // user of |registry_|.
+ std::string interface_name = *receiver.interface_name();
+ auto pipe = receiver.PassPipe();
+ registry_.TryBindInterface(interface_name, &pipe);
+}
diff --git a/chromium/chrome/renderer/chrome_content_renderer_client_unittest.cc b/chromium/chrome/renderer/chrome_content_renderer_client_unittest.cc
new file mode 100644
index 00000000000..a2dbfe830d3
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_content_renderer_client_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_content_renderer_client.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/renderer/searchbox/search_bouncer.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/webplugininfo.h"
+#include "extensions/buildflags/buildflags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url_response.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/manifest_constants.h"
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+using blink::WebPluginParams;
+using blink::WebString;
+using blink::WebVector;
+#endif
+
+using content::WebPluginInfo;
+using content::WebPluginMimeType;
+
+namespace {
+
+#if BUILDFLAG(ENABLE_NACL)
+const bool kNaClRestricted = false;
+const bool kNaClUnrestricted = true;
+const bool kExtensionNotFromWebStore = false;
+const bool kExtensionFromWebStore = true;
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+const bool kNotHostedApp = false;
+const bool kHostedApp = true;
+#endif
+
+#if BUILDFLAG(ENABLE_NACL)
+const char kExtensionUrl[] = "chrome-extension://extension_id/background.html";
+
+#endif
+
+void AddContentTypeHandler(content::WebPluginInfo* info,
+ const char* mime_type,
+ const char* manifest_url) {
+ content::WebPluginMimeType mime_type_info;
+ mime_type_info.mime_type = mime_type;
+ mime_type_info.additional_params.emplace_back(
+ base::UTF8ToUTF16("nacl"), base::UTF8ToUTF16(manifest_url));
+ info->mime_types.push_back(mime_type_info);
+}
+
+} // namespace
+
+class ChromeContentRendererClientTest : public testing::Test {
+ public:
+ void SetUp() override {
+ // Ensure that this looks like the renderer process based on the command
+ // line.
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kProcessType, switches::kRendererProcess);
+ }
+};
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+scoped_refptr<const extensions::Extension> CreateTestExtension(
+ extensions::Manifest::Location location, bool is_from_webstore,
+ bool is_hosted_app, const std::string& app_url) {
+ int flags = is_from_webstore ?
+ extensions::Extension::FROM_WEBSTORE:
+ extensions::Extension::NO_FLAGS;
+
+ base::DictionaryValue manifest;
+ manifest.SetString("name", "NaCl Extension");
+ manifest.SetString("version", "1");
+ manifest.SetInteger("manifest_version", 2);
+ if (is_hosted_app) {
+ auto url_list = std::make_unique<base::ListValue>();
+ url_list->AppendString(app_url);
+ manifest.Set(extensions::manifest_keys::kWebURLs, std::move(url_list));
+ manifest.SetString(extensions::manifest_keys::kLaunchWebURL, app_url);
+ }
+ std::string error;
+ return extensions::Extension::Create(base::FilePath(), location, manifest,
+ flags, &error);
+}
+
+scoped_refptr<const extensions::Extension> CreateExtension(
+ bool is_from_webstore) {
+ return CreateTestExtension(
+ extensions::Manifest::INTERNAL, is_from_webstore, kNotHostedApp,
+ std::string());
+}
+
+scoped_refptr<const extensions::Extension> CreateExtensionWithLocation(
+ extensions::Manifest::Location location, bool is_from_webstore) {
+ return CreateTestExtension(
+ location, is_from_webstore, kNotHostedApp, std::string());
+}
+
+scoped_refptr<const extensions::Extension> CreateHostedApp(
+ bool is_from_webstore, const std::string& app_url) {
+ return CreateTestExtension(extensions::Manifest::INTERNAL,
+ is_from_webstore,
+ kHostedApp,
+ app_url);
+}
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+TEST_F(ChromeContentRendererClientTest, NaClRestriction) {
+ // Unknown content types have no NaCl module.
+ {
+ WebPluginInfo info;
+ EXPECT_EQ(GURL(),
+ ChromeContentRendererClient::GetNaClContentHandlerURL(
+ "application/x-foo", info));
+ }
+ // Known content types have a NaCl module.
+ {
+ WebPluginInfo info;
+ AddContentTypeHandler(&info, "application/x-foo", "www.foo.com");
+ EXPECT_EQ(GURL("www.foo.com"),
+ ChromeContentRendererClient::GetNaClContentHandlerURL(
+ "application/x-foo", info));
+ }
+#if BUILDFLAG(ENABLE_NACL)
+ // --enable-nacl allows all NaCl apps.
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(), kNaClUnrestricted,
+ CreateExtension(kExtensionNotFromWebStore).get()));
+ }
+ // Unpacked extensions are allowed without --enable-nacl.
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(kExtensionUrl), kNaClRestricted,
+ CreateExtensionWithLocation(extensions::Manifest::UNPACKED,
+ kExtensionNotFromWebStore)
+ .get()));
+ }
+ // Component extensions are allowed without --enable-nacl.
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(kExtensionUrl), kNaClRestricted,
+ CreateExtensionWithLocation(extensions::Manifest::COMPONENT,
+ kExtensionNotFromWebStore)
+ .get()));
+ }
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(kExtensionUrl), kNaClRestricted,
+ CreateExtensionWithLocation(extensions::Manifest::EXTERNAL_COMPONENT,
+ kExtensionNotFromWebStore)
+ .get()));
+ }
+ // Extensions that are force installed by policy are allowed without
+ // --enable-nacl.
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(kExtensionUrl), kNaClRestricted,
+ CreateExtensionWithLocation(extensions::Manifest::EXTERNAL_POLICY,
+ kExtensionNotFromWebStore)
+ .get()));
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(kExtensionUrl), kNaClRestricted,
+ CreateExtensionWithLocation(
+ extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD,
+ kExtensionNotFromWebStore)
+ .get()));
+ }
+ // CWS extensions are allowed without --enable-nacl if called from an
+ // extension url.
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL(kExtensionUrl), kNaClRestricted,
+ CreateExtension(kExtensionFromWebStore).get()));
+ }
+ // Other URLs (including previously-whitelisted URLs) are blocked
+ // without --enable-nacl.
+ {
+ EXPECT_FALSE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL("https://plus.google.com.evil.com/foo1"), kNaClRestricted,
+ nullptr));
+ EXPECT_FALSE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL("https://talkgadget.google.com/hangouts/foo1"), kNaClRestricted,
+ nullptr));
+ }
+ // Non chrome-extension:// URLs belonging to hosted apps are allowed for
+ // webstore installed hosted apps.
+ {
+ EXPECT_TRUE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL("http://example.com/test.html"), kNaClRestricted,
+ CreateHostedApp(kExtensionFromWebStore, "http://example.com/").get()));
+ EXPECT_FALSE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL("http://example.com/test.html"), kNaClRestricted,
+ CreateHostedApp(kExtensionNotFromWebStore, "http://example.com/")
+ .get()));
+ EXPECT_FALSE(ChromeContentRendererClient::IsNativeNaClAllowed(
+ GURL("http://example.evil.com/test.html"), kNaClRestricted,
+ CreateHostedApp(kExtensionNotFromWebStore, "http://example.com/")
+ .get()));
+ }
+#endif // BUILDFLAG(ENABLE_NACL)
+}
+
+// SearchBouncer doesn't exist on Android.
+#if !defined(OS_ANDROID)
+TEST_F(ChromeContentRendererClientTest, ShouldSuppressErrorPage) {
+ ChromeContentRendererClient client;
+ SearchBouncer::GetInstance()->SetNewTabPageURL(GURL("http://example.com/n"));
+ EXPECT_FALSE(client.ShouldSuppressErrorPage(nullptr,
+ GURL("http://example.com")));
+ EXPECT_TRUE(client.ShouldSuppressErrorPage(nullptr,
+ GURL("http://example.com/n")));
+ SearchBouncer::GetInstance()->SetNewTabPageURL(GURL::EmptyGURL());
+}
+
+TEST_F(ChromeContentRendererClientTest, ShouldTrackUseCounter) {
+ ChromeContentRendererClient client;
+ SearchBouncer::GetInstance()->SetNewTabPageURL(GURL("http://example.com/n"));
+ EXPECT_TRUE(client.ShouldTrackUseCounter(GURL("http://example.com")));
+ EXPECT_FALSE(client.ShouldTrackUseCounter(GURL("http://example.com/n")));
+ SearchBouncer::GetInstance()->SetNewTabPageURL(GURL::EmptyGURL());
+}
+#endif
diff --git a/chromium/chrome/renderer/chrome_mock_render_thread.cc b/chromium/chrome/renderer/chrome_mock_render_thread.cc
new file mode 100644
index 00000000000..20eaa7c0564
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_mock_render_thread.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_mock_render_thread.h"
+
+#include "base/single_thread_task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+ChromeMockRenderThread::ChromeMockRenderThread() {
+}
+
+ChromeMockRenderThread::~ChromeMockRenderThread() {
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+ChromeMockRenderThread::GetIOTaskRunner() {
+ return io_task_runner_;
+}
+
+void ChromeMockRenderThread::set_io_task_runner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
+ io_task_runner_ = task_runner;
+}
diff --git a/chromium/chrome/renderer/chrome_mock_render_thread.h b/chromium/chrome/renderer/chrome_mock_render_thread.h
new file mode 100644
index 00000000000..405466abf74
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_mock_render_thread.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CHROME_MOCK_RENDER_THREAD_H_
+#define CHROME_RENDERER_CHROME_MOCK_RENDER_THREAD_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "content/public/test/mock_render_thread.h"
+
+// Extends content::MockRenderThread to know about extension messages.
+class ChromeMockRenderThread : public content::MockRenderThread {
+ public:
+ ChromeMockRenderThread();
+ ~ChromeMockRenderThread() override;
+
+ // content::RenderThread overrides.
+ scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() override;
+
+ //////////////////////////////////////////////////////////////////////////
+ // The following functions are called by the test itself.
+
+ void set_io_task_runner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+
+ protected:
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeMockRenderThread);
+};
+
+#endif // CHROME_RENDERER_CHROME_MOCK_RENDER_THREAD_H_
diff --git a/chromium/chrome/renderer/chrome_render_frame_observer.cc b/chromium/chrome/renderer/chrome_render_frame_observer.cc
new file mode 100644
index 00000000000..fa001df3912
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_render_frame_observer.cc
@@ -0,0 +1,515 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_render_frame_observer.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_isolated_world_ids.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/open_search_description_document_handler.mojom.h"
+#include "chrome/common/prerender_messages.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/renderer/prerender/prerender_helper.h"
+#include "chrome/renderer/web_apps.h"
+#include "components/crash/core/common/crash_key.h"
+#include "components/offline_pages/buildflags/buildflags.h"
+#include "components/safe_browsing/buildflags.h"
+#include "components/translate/content/renderer/translate_helper.h"
+#include "components/web_cache/renderer/web_cache_impl.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/window_features_converter.h"
+#include "extensions/common/constants.h"
+#include "printing/buildflags/buildflags.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/platform/web_image.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/web/web_console_message.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_document_loader.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_frame_content_dumper.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_node.h"
+#include "third_party/blink/public/web/web_security_policy.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/geometry/size_f.h"
+#include "url/gurl.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/renderer/searchbox/searchbox_extension.h"
+#endif // !defined(OS_ANDROID)
+
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+#include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+#include "chrome/common/mhtml_page_notifier.mojom.h"
+#endif
+
+#if BUILDFLAG(ENABLE_PRINTING)
+#include "components/printing/common/print_messages.h"
+#include "components/printing/renderer/print_render_frame_helper.h"
+#endif
+
+using blink::WebDocumentLoader;
+using blink::WebElement;
+using blink::WebFrameContentDumper;
+using blink::WebLocalFrame;
+using blink::WebNode;
+using blink::WebString;
+using content::RenderFrame;
+
+// Maximum number of characters in the document to index.
+// Any text beyond this point will be clipped.
+static const size_t kMaxIndexChars = 65535;
+
+// Constants for UMA statistic collection.
+static const char kTranslateCaptureText[] = "Translate.CaptureText";
+
+// For a page that auto-refreshes, we still show the bubble, if
+// the refresh delay is less than this value (in seconds).
+static constexpr base::TimeDelta kLocationChangeInterval =
+ base::TimeDelta::FromSeconds(10);
+
+// For the context menu, we want to keep transparency as is instead of
+// replacing transparent pixels with black ones
+static const bool kDiscardTransparencyForContextMenu = false;
+
+namespace {
+
+// If the source image is null or occupies less area than
+// |thumbnail_min_area_pixels|, we return the image unmodified. Otherwise, we
+// scale down the image so that the width and height do not exceed
+// |thumbnail_max_size_pixels|, preserving the original aspect ratio.
+SkBitmap Downscale(const SkBitmap& image,
+ int thumbnail_min_area_pixels,
+ const gfx::Size& thumbnail_max_size_pixels) {
+ if (image.isNull())
+ return SkBitmap();
+
+ gfx::Size image_size(image.width(), image.height());
+
+ if (image_size.GetArea() < thumbnail_min_area_pixels)
+ return image;
+
+ if (image_size.width() <= thumbnail_max_size_pixels.width() &&
+ image_size.height() <= thumbnail_max_size_pixels.height())
+ return image;
+
+ gfx::SizeF scaled_size = gfx::SizeF(image_size);
+
+ if (scaled_size.width() > thumbnail_max_size_pixels.width()) {
+ scaled_size.Scale(thumbnail_max_size_pixels.width() / scaled_size.width());
+ }
+
+ if (scaled_size.height() > thumbnail_max_size_pixels.height()) {
+ scaled_size.Scale(
+ thumbnail_max_size_pixels.height() / scaled_size.height());
+ }
+
+ return skia::ImageOperations::Resize(image,
+ skia::ImageOperations::RESIZE_GOOD,
+ static_cast<int>(scaled_size.width()),
+ static_cast<int>(scaled_size.height()));
+}
+
+} // namespace
+
+ChromeRenderFrameObserver::ChromeRenderFrameObserver(
+ content::RenderFrame* render_frame,
+ web_cache::WebCacheImpl* web_cache_impl)
+ : content::RenderFrameObserver(render_frame),
+ translate_helper_(nullptr),
+ phishing_classifier_(nullptr),
+ web_cache_impl_(web_cache_impl) {
+ render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
+ base::Bind(&ChromeRenderFrameObserver::OnRenderFrameObserverRequest,
+ base::Unretained(this)));
+ // Don't do anything else for subframes.
+ if (!render_frame->IsMainFrame())
+ return;
+
+#if BUILDFLAG(SAFE_BROWSING_CSD)
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+ if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection))
+ SetClientSidePhishingDetection(true);
+#endif
+ translate_helper_ = new translate::TranslateHelper(
+ render_frame, ISOLATED_WORLD_ID_TRANSLATE, extensions::kExtensionScheme);
+}
+
+ChromeRenderFrameObserver::~ChromeRenderFrameObserver() {
+}
+
+void ChromeRenderFrameObserver::OnInterfaceRequestForFrame(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle* interface_pipe) {
+ registry_.TryBindInterface(interface_name, interface_pipe);
+}
+
+bool ChromeRenderFrameObserver::OnAssociatedInterfaceRequestForFrame(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle* handle) {
+ return associated_interfaces_.TryBindInterface(interface_name, handle);
+}
+
+bool ChromeRenderFrameObserver::OnMessageReceived(const IPC::Message& message) {
+ // Filter only.
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ChromeRenderFrameObserver, message)
+ IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetIsPrerendering)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ if (handled)
+ return false;
+
+ IPC_BEGIN_MESSAGE_MAP(ChromeRenderFrameObserver, message)
+#if BUILDFLAG(ENABLE_PRINTING)
+ IPC_MESSAGE_HANDLER(PrintMsg_PrintNodeUnderContextMenu,
+ OnPrintNodeUnderContextMenu)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+
+void ChromeRenderFrameObserver::OnSetIsPrerendering(
+ prerender::PrerenderMode mode,
+ const std::string& histogram_prefix) {
+ if (mode != prerender::NO_PRERENDER) {
+ // If the PrerenderHelper for this frame already exists, don't create it. It
+ // can already be created for subframes during handling of
+ // RenderFrameCreated, if the parent frame was prerendering at time of
+ // subframe creation.
+ if (prerender::PrerenderHelper::Get(render_frame()))
+ return;
+
+ // The PrerenderHelper will destroy itself either after recording histograms
+ // or on destruction of the RenderView.
+ new prerender::PrerenderHelper(render_frame(), mode, histogram_prefix);
+ }
+}
+
+void ChromeRenderFrameObserver::RequestReloadImageForContextNode() {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ // TODO(dglazkov): This code is clearly in the wrong place. Need
+ // to investigate what it is doing and fix (http://crbug.com/606164).
+ WebNode context_node = frame->ContextMenuNode();
+ if (!context_node.IsNull()) {
+ frame->ReloadImage(context_node);
+ }
+}
+
+void ChromeRenderFrameObserver::RequestThumbnailForContextNode(
+ int32_t thumbnail_min_area_pixels,
+ const gfx::Size& thumbnail_max_size_pixels,
+ chrome::mojom::ImageFormat image_format,
+ RequestThumbnailForContextNodeCallback callback) {
+ WebNode context_node = render_frame()->GetWebFrame()->ContextMenuNode();
+ SkBitmap thumbnail;
+ gfx::Size original_size;
+ if (!context_node.IsNull() && context_node.IsElementNode()) {
+ SkBitmap image = context_node.To<WebElement>().ImageContents();
+ original_size = gfx::Size(image.width(), image.height());
+ thumbnail = Downscale(image,
+ thumbnail_min_area_pixels,
+ thumbnail_max_size_pixels);
+ }
+
+ SkBitmap bitmap;
+ if (thumbnail.colorType() == kN32_SkColorType) {
+ bitmap = thumbnail;
+ } else {
+ SkImageInfo info = thumbnail.info().makeColorType(kN32_SkColorType);
+ if (bitmap.tryAllocPixels(info)) {
+ thumbnail.readPixels(info, bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
+ }
+ }
+
+ std::vector<uint8_t> thumbnail_data;
+ constexpr int kDefaultQuality = 90;
+ std::vector<unsigned char> data;
+
+ switch (image_format) {
+ case chrome::mojom::ImageFormat::PNG:
+ if (gfx::PNGCodec::EncodeBGRASkBitmap(
+ bitmap, kDiscardTransparencyForContextMenu, &data)) {
+ thumbnail_data.swap(data);
+ }
+ break;
+ case chrome::mojom::ImageFormat::JPEG:
+ if (gfx::JPEGCodec::Encode(bitmap, kDefaultQuality, &data))
+ thumbnail_data.swap(data);
+ break;
+ }
+ std::move(callback).Run(thumbnail_data, original_size);
+}
+
+void ChromeRenderFrameObserver::OnPrintNodeUnderContextMenu() {
+#if BUILDFLAG(ENABLE_PRINTING)
+ printing::PrintRenderFrameHelper* helper =
+ printing::PrintRenderFrameHelper::Get(render_frame());
+ if (helper)
+ helper->PrintNode(render_frame()->GetWebFrame()->ContextMenuNode());
+#endif
+}
+
+void ChromeRenderFrameObserver::GetWebApplicationInfo(
+ GetWebApplicationInfoCallback callback) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+
+ WebApplicationInfo web_app_info;
+ web_apps::ParseWebAppFromWebDocument(frame, &web_app_info);
+
+ // The warning below is specific to mobile but it doesn't hurt to show it even
+ // if the Chromium build is running on a desktop. It will get more exposition.
+ if (web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE_APPLE) {
+ blink::WebConsoleMessage message(
+ blink::mojom::ConsoleMessageLevel::kWarning,
+ "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
+ "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
+ "content=\"yes\">");
+ frame->AddMessageToConsole(message);
+ }
+
+ // Prune out any data URLs in the set of icons. The browser process expects
+ // any icon with a data URL to have originated from a favicon. We don't want
+ // to decode arbitrary data URLs in the browser process. See
+ // http://b/issue?id=1162972
+ for (auto it = web_app_info.icons.begin(); it != web_app_info.icons.end();) {
+ if (it->url.SchemeIs(url::kDataScheme))
+ it = web_app_info.icons.erase(it);
+ else
+ ++it;
+ }
+
+ // Truncate the strings we send to the browser process.
+ web_app_info.title =
+ web_app_info.title.substr(0, chrome::kMaxMetaTagAttributeLength);
+ web_app_info.description =
+ web_app_info.description.substr(0, chrome::kMaxMetaTagAttributeLength);
+
+ std::move(callback).Run(web_app_info);
+}
+
+void ChromeRenderFrameObserver::SetClientSidePhishingDetection(
+ bool enable_phishing_detection) {
+#if BUILDFLAG(SAFE_BROWSING_CSD)
+ phishing_classifier_ =
+ enable_phishing_detection
+ ? safe_browsing::PhishingClassifierDelegate::Create(render_frame(),
+ nullptr)
+ : nullptr;
+#endif
+}
+
+void ChromeRenderFrameObserver::ExecuteWebUIJavaScript(
+ const base::string16& javascript) {
+#if !defined(OS_ANDROID)
+ webui_javascript_.push_back(javascript);
+#endif
+}
+
+void ChromeRenderFrameObserver::DidFinishLoad() {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ // Don't do anything for subframes.
+ if (frame->Parent())
+ return;
+
+ GURL osdd_url = frame->GetDocument().OpenSearchDescriptionURL();
+ if (!osdd_url.is_empty()) {
+ mojo::AssociatedRemote<chrome::mojom::OpenSearchDescriptionDocumentHandler>
+ osdd_handler;
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
+ &osdd_handler);
+ osdd_handler->PageHasOpenSearchDescriptionDocument(
+ frame->GetDocument().Url(), osdd_url);
+ }
+}
+
+void ChromeRenderFrameObserver::DidCreateNewDocument() {
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+ DCHECK(render_frame());
+ if (!render_frame()->IsMainFrame())
+ return;
+
+ DCHECK(render_frame()->GetWebFrame());
+ blink::WebDocumentLoader* doc_loader =
+ render_frame()->GetWebFrame()->GetDocumentLoader();
+ DCHECK(doc_loader);
+
+ if (!doc_loader->HasBeenLoadedAsWebArchive())
+ return;
+
+ // Connect to Mojo service on browser to notify it of the page's archive
+ // properties.
+ mojo::AssociatedRemote<offline_pages::mojom::MhtmlPageNotifier>
+ mhtml_notifier;
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
+ &mhtml_notifier);
+ DCHECK(mhtml_notifier);
+ blink::WebArchiveInfo info = doc_loader->GetArchiveInfo();
+
+ mhtml_notifier->NotifyMhtmlPageLoadAttempted(info.load_result, info.url,
+ info.date);
+#endif
+}
+
+void ChromeRenderFrameObserver::ReadyToCommitNavigation(
+ WebDocumentLoader* document_loader) {
+ // Execute cache clear operations that were postponed until a navigation
+ // event (including tab reload).
+ if (render_frame()->IsMainFrame() && web_cache_impl_)
+ web_cache_impl_->ExecutePendingClearCache();
+
+ // Let translate_helper do any preparatory work for loading a URL.
+ if (!translate_helper_)
+ return;
+
+ translate_helper_->PrepareForUrl(
+ render_frame()->GetWebFrame()->GetDocument().Url());
+}
+
+void ChromeRenderFrameObserver::DidCommitProvisionalLoad(
+ bool is_same_document_navigation,
+ ui::PageTransition transition) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+
+ // Don't do anything for subframes.
+ if (frame->Parent())
+ return;
+
+ static crash_reporter::CrashKeyString<8> view_count_key("view-count");
+ view_count_key.Set(
+ base::NumberToString(content::RenderView::GetRenderViewCount()));
+
+#if !defined(OS_ANDROID)
+ if (render_frame()->GetEnabledBindings() &
+ content::kWebUIBindingsPolicyMask) {
+ for (const auto& script : webui_javascript_)
+ render_frame()->ExecuteJavaScript(script);
+ webui_javascript_.clear();
+ }
+#endif
+}
+
+void ChromeRenderFrameObserver::DidClearWindowObject() {
+#if !defined(OS_ANDROID)
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kInstantProcess))
+ SearchBoxExtension::Install(render_frame()->GetWebFrame());
+#endif // !defined(OS_ANDROID)
+}
+
+void ChromeRenderFrameObserver::CapturePageText(TextCaptureType capture_type) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (!frame)
+ return;
+
+ // Don't capture pages that have pending redirect or location change.
+ if (frame->IsNavigationScheduledWithin(kLocationChangeInterval))
+ return;
+
+ // Don't index/capture pages that are in view source mode.
+ if (frame->IsViewSourceModeEnabled())
+ return;
+
+ // Don't capture text of the error pages.
+ WebDocumentLoader* document_loader = frame->GetDocumentLoader();
+ if (document_loader && document_loader->HasUnreachableURL())
+ return;
+
+ // Don't index/capture pages that are being prerendered.
+ if (prerender::PrerenderHelper::IsPrerendering(render_frame()))
+ return;
+
+ base::TimeTicks capture_begin_time = base::TimeTicks::Now();
+
+ // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
+ // translate helper for language detection and possible translation.
+ // TODO(dglazkov): WebFrameContentDumper should only be used for
+ // testing purposes. See http://crbug.com/585164.
+ base::string16 contents =
+ WebFrameContentDumper::DeprecatedDumpFrameTreeAsText(frame,
+ kMaxIndexChars)
+ .Utf16();
+
+ UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
+ base::TimeTicks::Now() - capture_begin_time);
+
+ // We should run language detection only once. Parsing finishes before
+ // the page loads, so let's pick that timing.
+ if (translate_helper_ && capture_type == PRELIMINARY_CAPTURE) {
+ translate_helper_->PageCaptured(contents);
+ }
+
+ TRACE_EVENT0("renderer", "ChromeRenderFrameObserver::CapturePageText");
+
+#if BUILDFLAG(SAFE_BROWSING_CSD)
+ // Will swap out the string.
+ if (phishing_classifier_)
+ phishing_classifier_->PageCaptured(&contents,
+ capture_type == PRELIMINARY_CAPTURE);
+#endif
+}
+
+void ChromeRenderFrameObserver::DidMeaningfulLayout(
+ blink::WebMeaningfulLayout layout_type) {
+ // Don't do any work for subframes.
+ if (!render_frame()->IsMainFrame())
+ return;
+
+ switch (layout_type) {
+ case blink::WebMeaningfulLayout::kFinishedParsing:
+ CapturePageText(PRELIMINARY_CAPTURE);
+ break;
+ case blink::WebMeaningfulLayout::kFinishedLoading:
+ CapturePageText(FINAL_CAPTURE);
+ break;
+ default:
+ break;
+ }
+}
+
+void ChromeRenderFrameObserver::OnDestruct() {
+ delete this;
+}
+
+void ChromeRenderFrameObserver::OnRenderFrameObserverRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame>
+ receiver) {
+ receivers_.Add(this, std::move(receiver));
+}
+
+void ChromeRenderFrameObserver::SetWindowFeatures(
+ blink::mojom::WindowFeaturesPtr window_features) {
+ render_frame()->GetRenderView()->GetWebView()->SetWindowFeatures(
+ content::ConvertMojoWindowFeaturesToWebWindowFeatures(*window_features));
+}
diff --git a/chromium/chrome/renderer/chrome_render_frame_observer.h b/chromium/chrome/renderer/chrome_render_frame_observer.h
new file mode 100644
index 00000000000..98ea5b68f6c
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_render_frame_observer.h
@@ -0,0 +1,127 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CHROME_RENDER_FRAME_OBSERVER_H_
+#define CHROME_RENDERER_CHROME_RENDER_FRAME_OBSERVER_H_
+
+#include "base/macros.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_render_frame.mojom.h"
+#include "chrome/common/prerender_types.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace safe_browsing {
+class PhishingClassifierDelegate;
+}
+
+namespace translate {
+class TranslateHelper;
+}
+
+namespace web_cache {
+class WebCacheImpl;
+}
+
+// This class holds the Chrome specific parts of RenderFrame, and has the same
+// lifetime.
+class ChromeRenderFrameObserver : public content::RenderFrameObserver,
+ public chrome::mojom::ChromeRenderFrame {
+ public:
+ ChromeRenderFrameObserver(content::RenderFrame* render_frame,
+ web_cache::WebCacheImpl* web_cache_impl);
+ ~ChromeRenderFrameObserver() override;
+
+ service_manager::BinderRegistry* registry() { return &registry_; }
+ blink::AssociatedInterfaceRegistry* associated_interfaces() {
+ return &associated_interfaces_;
+ }
+
+ private:
+ enum TextCaptureType { PRELIMINARY_CAPTURE, FINAL_CAPTURE };
+
+ // RenderFrameObserver implementation.
+ void OnInterfaceRequestForFrame(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle* interface_pipe) override;
+ bool OnAssociatedInterfaceRequestForFrame(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle* handle) override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void ReadyToCommitNavigation(
+ blink::WebDocumentLoader* document_loader) override;
+ void DidFinishLoad() override;
+ void DidCreateNewDocument() override;
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override;
+ void DidClearWindowObject() override;
+ void DidMeaningfulLayout(blink::WebMeaningfulLayout layout_type) override;
+ void OnDestruct() override;
+
+ // IPC handlers
+ void OnSetIsPrerendering(prerender::PrerenderMode mode,
+ const std::string& histogram_prefix);
+ void OnRequestThumbnailForContextNode(
+ int thumbnail_min_area_pixels,
+ const gfx::Size& thumbnail_max_size_pixels,
+ int callback_id);
+ void OnPrintNodeUnderContextMenu();
+ void OnSetClientSidePhishingDetection(bool enable_phishing_detection);
+
+ // chrome::mojom::ChromeRenderFrame:
+ void SetWindowFeatures(
+ blink::mojom::WindowFeaturesPtr window_features) override;
+ void ExecuteWebUIJavaScript(const base::string16& javascript) override;
+ void RequestThumbnailForContextNode(
+ int32_t thumbnail_min_area_pixels,
+ const gfx::Size& thumbnail_max_size_pixels,
+ chrome::mojom::ImageFormat image_format,
+ RequestThumbnailForContextNodeCallback callback) override;
+ void RequestReloadImageForContextNode() override;
+ void SetClientSidePhishingDetection(bool enable_phishing_detection) override;
+ void GetWebApplicationInfo(GetWebApplicationInfoCallback callback) override;
+
+ void OnRenderFrameObserverRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame>
+ receiver);
+
+ // Captures page information using the top (main) frame of a frame tree.
+ // Currently, this page information is just the text content of the all
+ // frames, collected and concatenated until a certain limit (kMaxIndexChars)
+ // is reached.
+ // TODO(dglazkov): This is incompatible with OOPIF and needs to be updated.
+ void CapturePageText(TextCaptureType capture_type);
+
+ void CapturePageTextLater(TextCaptureType capture_type,
+ base::TimeDelta delay);
+
+ // Have the same lifetime as us.
+ translate::TranslateHelper* translate_helper_;
+ safe_browsing::PhishingClassifierDelegate* phishing_classifier_;
+
+ // Owned by ChromeContentRendererClient and outlive us.
+ web_cache::WebCacheImpl* web_cache_impl_;
+
+#if !defined(OS_ANDROID)
+ // Save the JavaScript to preload if ExecuteWebUIJavaScript is invoked.
+ std::vector<base::string16> webui_javascript_;
+#endif
+
+ mojo::AssociatedReceiverSet<chrome::mojom::ChromeRenderFrame> receivers_;
+
+ service_manager::BinderRegistry registry_;
+ blink::AssociatedInterfaceRegistry associated_interfaces_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderFrameObserver);
+};
+
+#endif // CHROME_RENDERER_CHROME_RENDER_FRAME_OBSERVER_H_
diff --git a/chromium/chrome/renderer/chrome_render_frame_observer_browsertest.cc b/chromium/chrome/renderer/chrome_render_frame_observer_browsertest.cc
new file mode 100644
index 00000000000..be42e42f39e
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_render_frame_observer_browsertest.cc
@@ -0,0 +1,94 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_render_frame_observer.h"
+
+#include <tuple>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/translate/content/common/translate.mojom.h"
+#include "components/translate/content/renderer/translate_helper.h"
+#include "components/translate/core/common/translate_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "third_party/blink/public/web/web_widget.h"
+
+namespace {
+
+class FakeContentTranslateDriver
+ : public translate::mojom::ContentTranslateDriver {
+ public:
+ FakeContentTranslateDriver()
+ : called_new_page_(false), page_needs_translation_(false) {}
+ ~FakeContentTranslateDriver() override {}
+
+ void BindHandle(mojo::ScopedMessagePipeHandle handle) {
+ receivers_.Add(
+ this, mojo::PendingReceiver<translate::mojom::ContentTranslateDriver>(
+ std::move(handle)));
+ }
+
+ // translate::mojom::ContentTranslateDriver implementation.
+ void RegisterPage(mojo::PendingRemote<translate::mojom::Page> page,
+ const translate::LanguageDetectionDetails& details,
+ bool page_needs_translation) override {
+ called_new_page_ = true;
+ page_needs_translation_ = page_needs_translation;
+ }
+
+ bool called_new_page_;
+ bool page_needs_translation_;
+
+ private:
+ mojo::ReceiverSet<translate::mojom::ContentTranslateDriver> receivers_;
+};
+
+} // namespace
+
+// Constants for UMA statistic collection.
+static const char kTranslateCaptureText[] = "Translate.CaptureText";
+
+class ChromeRenderFrameObserverTest : public ChromeRenderViewTest {
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+ service_manager::InterfaceProvider* remote_interfaces =
+ view_->GetMainRenderFrame()->GetRemoteInterfaces();
+ service_manager::InterfaceProvider::TestApi test_api(remote_interfaces);
+ test_api.SetBinderForName(
+ translate::mojom::ContentTranslateDriver::Name_,
+ base::Bind(&FakeContentTranslateDriver::BindHandle,
+ base::Unretained(&fake_translate_driver_)));
+ }
+
+ FakeContentTranslateDriver fake_translate_driver_;
+};
+
+TEST_F(ChromeRenderFrameObserverTest, SkipCapturingSubFrames) {
+ base::HistogramTester histogram_tester;
+ LoadHTML(
+ "<!DOCTYPE html><body>"
+ "This is a main document"
+ "<iframe srcdoc=\"This a document in an iframe.\">"
+ "</body>");
+ view_->GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
+ blink::WebWidget::LifecycleUpdateReason::kTest);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_TRUE(fake_translate_driver_.page_needs_translation_)
+ << "Page should be translatable.";
+ // Should have 2 samples: one for preliminary capture, one for final capture.
+ // If there are more, then subframes are being captured more than once.
+ histogram_tester.ExpectTotalCount(kTranslateCaptureText, 2);
+}
diff --git a/chromium/chrome/renderer/chrome_render_thread_observer.cc b/chromium/chrome/renderer/chrome_render_thread_observer.cc
new file mode 100644
index 00000000000..3cc9905f7a6
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_render_thread_observer.cc
@@ -0,0 +1,286 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chrome_render_thread_observer.h"
+
+#include <stddef.h>
+
+#include <limits>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/no_destructor.h"
+#include "base/path_service.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/common/cache_stats_recorder.mojom.h"
+#include "chrome/common/child_process_logging.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/media/media_resource_provider.h"
+#include "chrome/common/net/net_resource_provider.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/renderer/content_settings_observer.h"
+#include "components/visitedlink/renderer/visitedlink_slave.h"
+#include "content/public/child/child_thread.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/resource_usage_reporter_type_converters.h"
+#include "content/public/common/service_manager_connection.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/common/simple_connection_filter.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/render_view_visitor.h"
+#include "content/public/renderer/resource_dispatcher_delegate.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ipc/ipc_sync_channel.h"
+#include "media/base/localized_strings.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_module.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_security_policy.h"
+#include "third_party/blink/public/web/web_view.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/renderer/extensions/extension_localization_peer.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "chrome/renderer/chromeos_merge_session_loader_throttle.h"
+#endif
+
+using blink::WebCache;
+using blink::WebSecurityPolicy;
+using blink::WebString;
+using content::RenderThread;
+
+namespace {
+
+const int kCacheStatsDelayMS = 2000;
+
+class RendererResourceDelegate : public content::ResourceDispatcherDelegate {
+ public:
+ RendererResourceDelegate() {}
+
+ void OnRequestComplete() override {
+ // Update the browser about our cache.
+ // Rate limit informing the host of our cache stats.
+ if (!weak_factory_.HasWeakPtrs()) {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&RendererResourceDelegate::InformHostOfCacheStats,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kCacheStatsDelayMS));
+ }
+ }
+
+ std::unique_ptr<content::RequestPeer> OnReceivedResponse(
+ std::unique_ptr<content::RequestPeer> current_peer,
+ const std::string& mime_type,
+ const GURL& url) override {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ return ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+ std::move(current_peer), RenderThread::Get(), mime_type, url);
+#else
+ return current_peer;
+#endif
+ }
+
+ private:
+ void InformHostOfCacheStats() {
+ WebCache::UsageStats stats;
+ WebCache::GetUsageStats(&stats);
+ if (!cache_stats_recorder_) {
+ RenderThread::Get()->GetChannel()->GetRemoteAssociatedInterface(
+ &cache_stats_recorder_);
+ }
+ cache_stats_recorder_->RecordCacheStats(stats.capacity, stats.size);
+ }
+
+ chrome::mojom::CacheStatsRecorderAssociatedPtr cache_stats_recorder_;
+
+ base::WeakPtrFactory<RendererResourceDelegate> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(RendererResourceDelegate);
+};
+
+#if defined(OS_CHROMEOS)
+scoped_refptr<base::SequencedTaskRunner> GetCallbackGroupTaskRunner() {
+ content::ChildThread* child_thread = content::ChildThread::Get();
+ if (child_thread)
+ return child_thread->GetIOTaskRunner();
+
+ // This will happen when running via tests.
+ return base::SequencedTaskRunnerHandle::Get();
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace
+
+bool ChromeRenderThreadObserver::is_incognito_process_ = false;
+
+#if defined(OS_CHROMEOS)
+// static
+scoped_refptr<ChromeRenderThreadObserver::ChromeOSListener>
+ChromeRenderThreadObserver::ChromeOSListener::Create(
+ mojo::PendingReceiver<chrome::mojom::ChromeOSListener>
+ chromeos_listener_receiver) {
+ scoped_refptr<ChromeOSListener> helper = new ChromeOSListener();
+ content::ChildThread::Get()->GetIOTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&ChromeOSListener::BindOnIOThread, helper,
+ std::move(chromeos_listener_receiver)));
+ return helper;
+}
+
+bool ChromeRenderThreadObserver::ChromeOSListener::IsMergeSessionRunning()
+ const {
+ base::AutoLock lock(lock_);
+ return merge_session_running_;
+}
+
+void ChromeRenderThreadObserver::ChromeOSListener::RunWhenMergeSessionFinished(
+ DelayedCallbackGroup::Callback callback) {
+ base::AutoLock lock(lock_);
+ DCHECK(merge_session_running_);
+ session_merged_callbacks_->Add(std::move(callback));
+}
+
+void ChromeRenderThreadObserver::ChromeOSListener::MergeSessionComplete() {
+ {
+ base::AutoLock lock(lock_);
+ merge_session_running_ = false;
+ }
+ session_merged_callbacks_->RunAll();
+}
+
+ChromeRenderThreadObserver::ChromeOSListener::ChromeOSListener()
+ : session_merged_callbacks_(base::MakeRefCounted<DelayedCallbackGroup>(
+ MergeSessionLoaderThrottle::GetMergeSessionTimeout(),
+ GetCallbackGroupTaskRunner())),
+ merge_session_running_(true) {}
+
+ChromeRenderThreadObserver::ChromeOSListener::~ChromeOSListener() {}
+
+void ChromeRenderThreadObserver::ChromeOSListener::BindOnIOThread(
+ mojo::PendingReceiver<chrome::mojom::ChromeOSListener>
+ chromeos_listener_receiver) {
+ receiver_.Bind(std::move(chromeos_listener_receiver));
+}
+#endif // defined(OS_CHROMEOS)
+
+chrome::mojom::DynamicParams* GetDynamicConfigParams() {
+ static base::NoDestructor<chrome::mojom::DynamicParams> dynamic_params;
+ return dynamic_params.get();
+}
+
+ChromeRenderThreadObserver::ChromeRenderThreadObserver()
+ : visited_link_slave_(new visitedlink::VisitedLinkSlave) {
+ RenderThread* thread = RenderThread::Get();
+ resource_delegate_.reset(new RendererResourceDelegate());
+ thread->SetResourceDispatcherDelegate(resource_delegate_.get());
+
+ // Configure modules that need access to resources.
+ net::NetModule::SetResourceProvider(ChromeNetResourceProvider);
+ media::SetLocalizedStringProvider(ChromeMediaLocalizedStringProvider);
+
+ // chrome-native: is a scheme used for placeholder navigations that allow
+ // UIs to be drawn with platform native widgets instead of HTML. These pages
+ // should not be accessible. No code should be runnable in these pages,
+ // so it should not need to access anything nor should it allow javascript
+ // URLs since it should never be visible to the user.
+ // See also ChromeContentClient::AddAdditionalSchemes that adds it as an
+ // empty document scheme.
+ WebString native_scheme(WebString::FromASCII(chrome::kChromeNativeScheme));
+ WebSecurityPolicy::RegisterURLSchemeAsDisplayIsolated(native_scheme);
+ WebSecurityPolicy::RegisterURLSchemeAsNotAllowingJavascriptURLs(
+ native_scheme);
+
+ auto registry = std::make_unique<service_manager::BinderRegistry>();
+ registry->AddInterface(visited_link_slave_->GetBindCallback(),
+ base::ThreadTaskRunnerHandle::Get());
+ if (content::ChildThread::Get()) {
+ content::ChildThread::Get()
+ ->GetServiceManagerConnection()
+ ->AddConnectionFilter(std::make_unique<content::SimpleConnectionFilter>(
+ std::move(registry)));
+ }
+}
+
+ChromeRenderThreadObserver::~ChromeRenderThreadObserver() {}
+
+// static
+const chrome::mojom::DynamicParams&
+ChromeRenderThreadObserver::GetDynamicParams() {
+ return *GetDynamicConfigParams();
+}
+
+void ChromeRenderThreadObserver::RegisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) {
+ associated_interfaces->AddInterface(base::Bind(
+ &ChromeRenderThreadObserver::OnRendererConfigurationAssociatedRequest,
+ base::Unretained(this)));
+}
+
+void ChromeRenderThreadObserver::UnregisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) {
+ associated_interfaces->RemoveInterface(
+ chrome::mojom::RendererConfiguration::Name_);
+}
+
+void ChromeRenderThreadObserver::SetInitialConfiguration(
+ bool is_incognito_process,
+ mojo::PendingReceiver<chrome::mojom::ChromeOSListener>
+ chromeos_listener_receiver) {
+ is_incognito_process_ = is_incognito_process;
+#if defined(OS_CHROMEOS)
+ if (chromeos_listener_receiver) {
+ chromeos_listener_ =
+ ChromeOSListener::Create(std::move(chromeos_listener_receiver));
+ }
+#endif // defined(OS_CHROMEOS)
+}
+
+void ChromeRenderThreadObserver::SetConfiguration(
+ chrome::mojom::DynamicParamsPtr params) {
+ *GetDynamicConfigParams() = std::move(*params);
+}
+
+void ChromeRenderThreadObserver::SetContentSettingRules(
+ const RendererContentSettingRules& rules) {
+ content_setting_rules_ = rules;
+}
+
+void ChromeRenderThreadObserver::SetFieldTrialGroup(
+ const std::string& trial_name,
+ const std::string& group_name) {
+ RenderThread::Get()->SetFieldTrialGroup(trial_name, group_name);
+}
+
+void ChromeRenderThreadObserver::OnRendererConfigurationAssociatedRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::RendererConfiguration>
+ receiver) {
+ renderer_configuration_receivers_.Add(this, std::move(receiver));
+}
+
+const RendererContentSettingRules*
+ChromeRenderThreadObserver::content_setting_rules() const {
+ return &content_setting_rules_;
+}
diff --git a/chromium/chrome/renderer/chrome_render_thread_observer.h b/chromium/chrome/renderer/chrome_render_thread_observer.h
new file mode 100644
index 00000000000..1714c08cacf
--- /dev/null
+++ b/chromium/chrome/renderer/chrome_render_thread_observer.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CHROME_RENDER_THREAD_OBSERVER_H_
+#define CHROME_RENDERER_CHROME_RENDER_THREAD_OBSERVER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "chrome/common/renderer_configuration.mojom.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/renderer/chromeos_delayed_callback_group.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace content {
+class ResourceDispatcherDelegate;
+}
+
+namespace visitedlink {
+class VisitedLinkSlave;
+}
+
+// This class filters the incoming control messages (i.e. ones not destined for
+// a RenderView) for Chrome specific messages that the content layer doesn't
+// happen. If a few messages are related, they should probably have their own
+// observer.
+class ChromeRenderThreadObserver : public content::RenderThreadObserver,
+ public chrome::mojom::RendererConfiguration {
+ public:
+#if defined(OS_CHROMEOS)
+ // A helper class to handle Mojo calls that need to be dispatched to the IO
+ // thread instead of the main thread as is the norm.
+ // This class is thread-safe.
+ class ChromeOSListener : public chrome::mojom::ChromeOSListener,
+ public base::RefCountedThreadSafe<ChromeOSListener> {
+ public:
+ static scoped_refptr<ChromeOSListener> Create(
+ mojo::PendingReceiver<chrome::mojom::ChromeOSListener>
+ chromeos_listener_receiver);
+
+ // Is the merge session still running?
+ bool IsMergeSessionRunning() const;
+
+ // Run |callback| on the calling sequence when the merge session has
+ // finished (or timed out).
+ void RunWhenMergeSessionFinished(DelayedCallbackGroup::Callback callback);
+
+ protected:
+ // chrome::mojom::ChromeOSListener:
+ void MergeSessionComplete() override;
+
+ private:
+ friend class base::RefCountedThreadSafe<ChromeOSListener>;
+
+ ChromeOSListener();
+ ~ChromeOSListener() override;
+
+ void BindOnIOThread(mojo::PendingReceiver<chrome::mojom::ChromeOSListener>
+ chromeos_listener_receiver);
+
+ scoped_refptr<DelayedCallbackGroup> session_merged_callbacks_;
+ bool merge_session_running_ GUARDED_BY(lock_);
+ mutable base::Lock lock_;
+ mojo::Receiver<chrome::mojom::ChromeOSListener> receiver_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeOSListener);
+ };
+#endif // defined(OS_CHROMEOS)
+
+ ChromeRenderThreadObserver();
+ ~ChromeRenderThreadObserver() override;
+
+ static bool is_incognito_process() { return is_incognito_process_; }
+
+ // Return the dynamic parameters - those that may change while the
+ // render process is running.
+ static const chrome::mojom::DynamicParams& GetDynamicParams();
+
+ // Returns a pointer to the content setting rules owned by
+ // |ChromeRenderThreadObserver|.
+ const RendererContentSettingRules* content_setting_rules() const;
+
+ visitedlink::VisitedLinkSlave* visited_link_slave() {
+ return visited_link_slave_.get();
+ }
+
+#if defined(OS_CHROMEOS)
+ scoped_refptr<ChromeOSListener> chromeos_listener() const {
+ return chromeos_listener_;
+ }
+#endif // defined(OS_CHROMEOS)
+
+ private:
+ // content::RenderThreadObserver:
+ void RegisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) override;
+ void UnregisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) override;
+
+ // chrome::mojom::RendererConfiguration:
+ void SetInitialConfiguration(
+ bool is_incognito_process,
+ mojo::PendingReceiver<chrome::mojom::ChromeOSListener>
+ chromeos_listener_receiver) override;
+ void SetConfiguration(chrome::mojom::DynamicParamsPtr params) override;
+ void SetContentSettingRules(
+ const RendererContentSettingRules& rules) override;
+ void SetFieldTrialGroup(const std::string& trial_name,
+ const std::string& group_name) override;
+
+ void OnRendererConfigurationAssociatedRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::RendererConfiguration>
+ receiver);
+
+ static bool is_incognito_process_;
+ std::unique_ptr<content::ResourceDispatcherDelegate> resource_delegate_;
+ RendererContentSettingRules content_setting_rules_;
+
+ std::unique_ptr<visitedlink::VisitedLinkSlave> visited_link_slave_;
+
+ mojo::AssociatedReceiverSet<chrome::mojom::RendererConfiguration>
+ renderer_configuration_receivers_;
+
+#if defined(OS_CHROMEOS)
+ // Only set if the Chrome OS merge session was running when the renderer
+ // was started.
+ scoped_refptr<ChromeOSListener> chromeos_listener_;
+#endif // defined(OS_CHROMEOS)
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeRenderThreadObserver);
+};
+
+#endif // CHROME_RENDERER_CHROME_RENDER_THREAD_OBSERVER_H_
diff --git a/chromium/chrome/renderer/chromeos_delayed_callback_group.cc b/chromium/chrome/renderer/chromeos_delayed_callback_group.cc
new file mode 100644
index 00000000000..8ce4700e5c0
--- /dev/null
+++ b/chromium/chrome/renderer/chromeos_delayed_callback_group.cc
@@ -0,0 +1,115 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chromeos_delayed_callback_group.h"
+
+#include <utility>
+
+#include "base/task/post_task.h"
+#include "base/bind.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+DelayedCallbackGroup::CallbackEntry::CallbackEntry(
+ Callback callback,
+ const scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+ base::TimeTicks expiration_time)
+ : callback_(std::move(callback)),
+ callback_task_runner_(std::move(callback_task_runner)),
+ expiration_time_(expiration_time) {}
+
+DelayedCallbackGroup::CallbackEntry::~CallbackEntry() {}
+
+DelayedCallbackGroup::DelayedCallbackGroup(
+ base::TimeDelta expiration_delay,
+ scoped_refptr<base::SequencedTaskRunner> expiration_task_runner)
+ : expiration_delay_(expiration_delay),
+ expiration_task_runner_(expiration_task_runner) {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+DelayedCallbackGroup::~DelayedCallbackGroup() {
+ base::AutoLock lock(callbacks_lock_);
+ CancelExpirationTimer();
+ ExpireAllCallbacks();
+}
+
+void DelayedCallbackGroup::Add(Callback callback) {
+ DCHECK(base::SequencedTaskRunnerHandle::IsSet());
+ {
+ base::AutoLock lock(callbacks_lock_);
+ base::TimeTicks expiration_time =
+ base::TimeTicks::Now() + expiration_delay_;
+ callbacks_.emplace(std::move(callback),
+ base::SequencedTaskRunnerHandle::Get(), expiration_time);
+ }
+ expiration_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&DelayedCallbackGroup::StartExpirationTimer, this));
+}
+
+void DelayedCallbackGroup::CancelExpirationTimer() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ expiration_timeout_.Cancel();
+}
+
+void DelayedCallbackGroup::StartExpirationTimer() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ base::AutoLock lock(callbacks_lock_);
+ if (callbacks_.empty() || !expiration_timeout_.IsCancelled())
+ return;
+
+ base::TimeDelta delay_until_next_expiration =
+ callbacks_.front().expiration_time_ - base::TimeTicks::Now();
+ expiration_timeout_.Reset(
+ base::BindOnce(&DelayedCallbackGroup::OnExpirationTimer, this));
+ expiration_task_runner_->PostDelayedTask(
+ FROM_HERE, expiration_timeout_.callback(), delay_until_next_expiration);
+}
+
+void DelayedCallbackGroup::OnExpirationTimer() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ProcessExpiredCallbacks(base::TimeTicks::Now());
+ StartExpirationTimer();
+}
+
+void DelayedCallbackGroup::RunAll() {
+ base::AutoLock lock(callbacks_lock_);
+ while (!callbacks_.empty()) {
+ CallbackEntry& entry = callbacks_.front();
+ entry.callback_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(entry.callback_), RunReason::NORMAL));
+ callbacks_.pop();
+ }
+}
+
+void DelayedCallbackGroup::ProcessExpiredCallbacks(
+ base::TimeTicks expiration_time) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ base::AutoLock lock(callbacks_lock_);
+ CancelExpirationTimer();
+ while (!callbacks_.empty()) {
+ CallbackEntry& entry = callbacks_.front();
+ if (entry.expiration_time_ <= expiration_time) {
+ entry.callback_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(entry.callback_), RunReason::TIMEOUT));
+ callbacks_.pop();
+ } else {
+ // All others in this queue expire after |expiration_time|.
+ return;
+ }
+ }
+}
+
+void DelayedCallbackGroup::ExpireAllCallbacks() {
+ while (!callbacks_.empty()) {
+ CallbackEntry& entry = callbacks_.front();
+ entry.callback_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(entry.callback_), RunReason::TIMEOUT));
+ callbacks_.pop();
+ }
+}
diff --git a/chromium/chrome/renderer/chromeos_delayed_callback_group.h b/chromium/chrome/renderer/chromeos_delayed_callback_group.h
new file mode 100644
index 00000000000..e8785462eac
--- /dev/null
+++ b/chromium/chrome/renderer/chromeos_delayed_callback_group.h
@@ -0,0 +1,97 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CHROMEOS_DELAYED_CALLBACK_GROUP_H_
+#define CHROME_RENDERER_CHROMEOS_DELAYED_CALLBACK_GROUP_H_
+
+#include <functional>
+#include <list>
+#include <queue>
+
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "base/time/time.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+// Manage a collection of callbacks to be run en mass when RunAll() is called.
+// This class is thread-safe.
+class DelayedCallbackGroup
+ : public base::RefCountedThreadSafe<DelayedCallbackGroup> {
+ public:
+ // The reason for the callback being invokes.
+ enum class RunReason {
+ // The callback is being run normally i.e. RunAll() was called.
+ NORMAL,
+ // The timeout period elapsed before RunAll() was invoked.
+ TIMEOUT
+ };
+
+ using Callback = base::OnceCallback<void(RunReason)>;
+
+ // All callbacks will be run when RunAll() is called or after the expiration
+ // delay specified by |expiration_delay|.
+ DelayedCallbackGroup(
+ base::TimeDelta expiration_delay,
+ scoped_refptr<base::SequencedTaskRunner> expiration_task_runner);
+
+ // Add a |callback| to the queue to be called at a later time on the calling
+ // sequence task runner. |callback| will either be called when RunAll() is
+ // called or if a delay of |expiration_delay| has elapsed after calling
+ // Add() without RunAll() being called first.
+ //
+ // Callbacks are called in the same order they were added.
+ void Add(Callback callback);
+
+ // Run all non-expired callback managed by this instance in the order in which
+ // they were added via Add(). All callbacks will be passed the
+ // RunReason::NORMAL parameter value.
+ void RunAll();
+
+ private:
+ friend class base::RefCountedThreadSafe<DelayedCallbackGroup>;
+
+ struct CallbackEntry {
+ CallbackEntry(
+ Callback callback,
+ const scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+ base::TimeTicks expiration_time);
+ ~CallbackEntry();
+
+ Callback callback_;
+ const scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
+ base::TimeTicks expiration_time_;
+ };
+
+ ~DelayedCallbackGroup();
+
+ void StartExpirationTimer();
+ void CancelExpirationTimer();
+ void OnExpirationTimer();
+
+ // Call all expired callbacks with the RunReason::TIMEOUT parameter value.
+ void ProcessExpiredCallbacks(base::TimeTicks expiration_time);
+
+ // Call all remaining callbacks with the RunReason::TIMEOUT parameter value.
+ void ExpireAllCallbacks() EXCLUSIVE_LOCKS_REQUIRED(callbacks_lock_);
+
+ std::queue<CallbackEntry, std::list<CallbackEntry>> callbacks_
+ GUARDED_BY(callbacks_lock_);
+ base::TimeDelta expiration_delay_ GUARDED_BY(callbacks_lock_);
+ base::CancelableOnceClosure expiration_timeout_;
+ mutable base::Lock callbacks_lock_;
+
+ scoped_refptr<base::SequencedTaskRunner> expiration_task_runner_;
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(DelayedCallbackGroup);
+};
+
+#endif // CHROME_RENDERER_CHROMEOS_DELAYED_CALLBACK_GROUP_H_
diff --git a/chromium/chrome/renderer/chromeos_delayed_callback_group_unittest.cc b/chromium/chrome/renderer/chromeos_delayed_callback_group_unittest.cc
new file mode 100644
index 00000000000..c21ff35bcb6
--- /dev/null
+++ b/chromium/chrome/renderer/chromeos_delayed_callback_group_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chromeos_delayed_callback_group.h"
+
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+TEST(DelayedCallbackGroup, RunEmpty) {
+ base::test::TaskEnvironment task_environment;
+ auto callback_group = base::MakeRefCounted<DelayedCallbackGroup>(
+ TimeDelta::FromSeconds(1), base::SequencedTaskRunnerHandle::Get());
+ callback_group->RunAll();
+}
+
+TEST(DelayedCallbackGroup, RunSimple) {
+ const TimeDelta kTimeout = TimeDelta::FromMilliseconds(500);
+ base::test::TaskEnvironment task_environment;
+ auto callback_group = base::MakeRefCounted<DelayedCallbackGroup>(
+ TimeDelta::FromSeconds(1), base::SequencedTaskRunnerHandle::Get());
+
+ base::Time time_before_add = base::Time::Now();
+ base::Time callback_time;
+ base::RunLoop run_loop;
+ callback_group->Add(
+ base::BindLambdaForTesting([&](DelayedCallbackGroup::RunReason reason) {
+ callback_time = base::Time::Now();
+ EXPECT_EQ(DelayedCallbackGroup::RunReason::NORMAL, reason);
+ run_loop.Quit();
+ }));
+ callback_group->RunAll();
+ run_loop.Run();
+
+ TimeDelta delta = callback_time - time_before_add;
+ EXPECT_LT(delta, kTimeout);
+}
+
+TEST(DelayedCallbackGroup, TimeoutSimple) {
+ const TimeDelta kTimeout = TimeDelta::FromMilliseconds(500);
+ base::test::TaskEnvironment task_environment;
+ auto callback_group = base::MakeRefCounted<DelayedCallbackGroup>(
+ TimeDelta::FromSeconds(1), base::SequencedTaskRunnerHandle::Get());
+
+ base::Time time_before_add = base::Time::Now();
+ base::Time callback_time;
+ base::RunLoop run_loop;
+ callback_group->Add(
+ base::BindLambdaForTesting([&](DelayedCallbackGroup::RunReason reason) {
+ callback_time = base::Time::Now();
+ EXPECT_EQ(DelayedCallbackGroup::RunReason::TIMEOUT, reason);
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+
+ TimeDelta delta = callback_time - time_before_add;
+ EXPECT_GE(delta, kTimeout);
+}
+
+TEST(DelayedCallbackGroup, TimeoutAndRun) {
+ const TimeDelta kTimeout = TimeDelta::FromMilliseconds(500);
+ base::test::TaskEnvironment task_environment;
+ auto callback_group = base::MakeRefCounted<DelayedCallbackGroup>(
+ TimeDelta::FromSeconds(1), base::SequencedTaskRunnerHandle::Get());
+
+ base::Time start_time = base::Time::Now();
+ base::Time callback_time_1;
+ base::Time callback_time_2;
+ base::RunLoop run_loop_1;
+ bool callback_1_called = false;
+ callback_group->Add(
+ base::BindLambdaForTesting([&](DelayedCallbackGroup::RunReason reason) {
+ EXPECT_FALSE(callback_1_called);
+ callback_1_called = true;
+ callback_time_1 = base::Time::Now();
+ EXPECT_EQ(DelayedCallbackGroup::RunReason::TIMEOUT, reason);
+ run_loop_1.Quit();
+ }));
+ base::PlatformThread::Sleep(kTimeout + TimeDelta::FromMilliseconds(100));
+ base::RunLoop run_loop_2;
+ bool callback_2_called = false;
+ callback_group->Add(
+ base::BindLambdaForTesting([&](DelayedCallbackGroup::RunReason reason) {
+ EXPECT_FALSE(callback_2_called);
+ callback_2_called = true;
+ callback_time_2 = base::Time::Now();
+ EXPECT_EQ(DelayedCallbackGroup::RunReason::NORMAL, reason);
+ run_loop_2.Quit();
+ }));
+ run_loop_1.Run();
+
+ TimeDelta delta = callback_time_1 - start_time;
+ EXPECT_GE(delta, kTimeout);
+ // Only the first callback should have timed out.
+ EXPECT_TRUE(callback_time_2.is_null());
+ callback_group->RunAll();
+ run_loop_2.Run();
+ delta = callback_time_2 - start_time;
+ EXPECT_GE(delta, kTimeout + TimeDelta::FromMilliseconds(100));
+}
+
+TEST(DelayedCallbackGroup, DoubleExpiration) {
+ const TimeDelta kTimeout = TimeDelta::FromMilliseconds(500);
+ base::test::TaskEnvironment task_environment;
+ auto callback_group = base::MakeRefCounted<DelayedCallbackGroup>(
+ TimeDelta::FromSeconds(1), base::SequencedTaskRunnerHandle::Get());
+
+ base::Time start_time = base::Time::Now();
+ base::Time callback_time_1;
+ base::Time callback_time_2;
+ base::RunLoop run_loop_1;
+ bool callback_1_called = false;
+ callback_group->Add(
+ base::BindLambdaForTesting([&](DelayedCallbackGroup::RunReason reason) {
+ EXPECT_FALSE(callback_1_called);
+ callback_1_called = true;
+ callback_time_1 = base::Time::Now();
+ EXPECT_EQ(DelayedCallbackGroup::RunReason::TIMEOUT, reason);
+ run_loop_1.Quit();
+ }));
+ base::PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ base::RunLoop run_loop_2;
+ bool callback_2_called = false;
+ callback_group->Add(
+ base::BindLambdaForTesting([&](DelayedCallbackGroup::RunReason reason) {
+ EXPECT_FALSE(callback_2_called);
+ callback_2_called = true;
+ callback_time_2 = base::Time::Now();
+ EXPECT_EQ(DelayedCallbackGroup::RunReason::TIMEOUT, reason);
+ run_loop_2.Quit();
+ }));
+ run_loop_1.Run();
+
+ TimeDelta delta = callback_time_1 - start_time;
+ EXPECT_GE(delta, kTimeout);
+ // Only the first callback should have timed out.
+ EXPECT_TRUE(callback_time_2.is_null());
+ run_loop_2.Run();
+ delta = callback_time_2 - start_time;
+ EXPECT_GE(delta, kTimeout + TimeDelta::FromMilliseconds(100));
+}
diff --git a/chromium/chrome/renderer/chromeos_merge_session_loader_throttle.cc b/chromium/chrome/renderer/chromeos_merge_session_loader_throttle.cc
new file mode 100644
index 00000000000..0354ac58fc3
--- /dev/null
+++ b/chromium/chrome/renderer/chromeos_merge_session_loader_throttle.cc
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/chromeos_merge_session_loader_throttle.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "content/public/common/resource_type.h"
+
+// static
+base::TimeDelta MergeSessionLoaderThrottle::GetMergeSessionTimeout() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kShortMergeSessionTimeoutForTest)) {
+ return base::TimeDelta::FromSeconds(1);
+ } else {
+ return base::TimeDelta::FromSeconds(20);
+ }
+}
+
+MergeSessionLoaderThrottle::MergeSessionLoaderThrottle(
+ scoped_refptr<ChromeRenderThreadObserver::ChromeOSListener>
+ chromeos_listener)
+ : chromeos_listener_(std::move(chromeos_listener)) {}
+
+MergeSessionLoaderThrottle::~MergeSessionLoaderThrottle() = default;
+
+bool MergeSessionLoaderThrottle::MaybeDeferForMergeSession(
+ const GURL& url,
+ DelayedCallbackGroup::Callback resume_callback) {
+ if (!chromeos_listener_ || !chromeos_listener_->IsMergeSessionRunning())
+ return false;
+
+ chromeos_listener_->RunWhenMergeSessionFinished(std::move(resume_callback));
+ return true;
+}
+
+void MergeSessionLoaderThrottle::WillStartRequest(
+ network::ResourceRequest* request,
+ bool* defer) {
+ is_xhr_ =
+ request->resource_type == static_cast<int>(content::ResourceType::kXhr);
+ if (is_xhr_ && request->url.SchemeIsHTTPOrHTTPS() &&
+ MaybeDeferForMergeSession(
+ request->url,
+ base::BindOnce(&MergeSessionLoaderThrottle::ResumeLoader,
+ weak_ptr_factory_.GetWeakPtr()))) {
+ *defer = true;
+ }
+}
+
+void MergeSessionLoaderThrottle::WillRedirectRequest(
+ net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& /* response_head */,
+ bool* defer,
+ std::vector<std::string>* to_be_removed_headers,
+ net::HttpRequestHeaders* modified_headers) {
+ if (is_xhr_ && redirect_info->new_url.SchemeIsHTTPOrHTTPS() &&
+ MaybeDeferForMergeSession(
+ redirect_info->new_url,
+ base::BindOnce(&MergeSessionLoaderThrottle::ResumeLoader,
+ weak_ptr_factory_.GetWeakPtr()))) {
+ *defer = true;
+ }
+}
+
+void MergeSessionLoaderThrottle::DetachFromCurrentSequence() {}
+
+void MergeSessionLoaderThrottle::ResumeLoader(
+ DelayedCallbackGroup::RunReason run_reason) {
+ LOG_IF(ERROR, run_reason == DelayedCallbackGroup::RunReason::TIMEOUT)
+ << "Merge session loader throttle timeout.";
+ DVLOG(1) << "Resuming deferred XHR request.";
+ delegate_->Resume();
+}
diff --git a/chromium/chrome/renderer/chromeos_merge_session_loader_throttle.h b/chromium/chrome/renderer/chromeos_merge_session_loader_throttle.h
new file mode 100644
index 00000000000..e75a856581f
--- /dev/null
+++ b/chromium/chrome/renderer/chromeos_merge_session_loader_throttle.h
@@ -0,0 +1,55 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CHROMEOS_MERGE_SESSION_LOADER_THROTTLE_H_
+#define CHROME_RENDERER_CHROMEOS_MERGE_SESSION_LOADER_THROTTLE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "chrome/renderer/chromeos_delayed_callback_group.h"
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+
+// This is used to throttle XHR resource requests on Chrome OS while the
+// merge session is running (or a timeout).
+class MergeSessionLoaderThrottle
+ : public blink::URLLoaderThrottle,
+ public base::SupportsWeakPtr<MergeSessionLoaderThrottle> {
+ public:
+ static base::TimeDelta GetMergeSessionTimeout();
+
+ explicit MergeSessionLoaderThrottle(
+ scoped_refptr<ChromeRenderThreadObserver::ChromeOSListener>
+ chromeos_listener);
+ ~MergeSessionLoaderThrottle() override;
+
+ private:
+ bool MaybeDeferForMergeSession(
+ const GURL& url,
+ DelayedCallbackGroup::Callback resume_callback);
+
+ // blink::URLLoaderThrottle:
+ void WillStartRequest(network::ResourceRequest* request,
+ bool* defer) override;
+ void WillRedirectRequest(net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* defer,
+ std::vector<std::string>* to_be_removed_headers,
+ net::HttpRequestHeaders* modified_headers) override;
+ void DetachFromCurrentSequence() override;
+ void ResumeLoader(DelayedCallbackGroup::RunReason run_reason);
+
+ bool is_xhr_ = false;
+ scoped_refptr<ChromeRenderThreadObserver::ChromeOSListener>
+ chromeos_listener_;
+ base::WeakPtrFactory<MergeSessionLoaderThrottle> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(MergeSessionLoaderThrottle);
+};
+
+#endif // CHROME_RENDERER_CHROMEOS_MERGE_SESSION_LOADER_THROTTLE_H_
diff --git a/chromium/chrome/renderer/content_settings_observer.cc b/chromium/chrome/renderer/content_settings_observer.cc
new file mode 100644
index 00000000000..e45e69f5164
--- /dev/null
+++ b/chromium/chrome/renderer/content_settings_observer.cc
@@ -0,0 +1,669 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/content_settings_observer.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/client_hints.mojom.h"
+#include "chrome/common/client_hints/client_hints.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/ssl_insecure_content.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings.mojom.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/content_settings/core/common/content_settings_utils.h"
+#include "content/public/common/origin_util.h"
+#include "content/public/common/previews_state.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/document_state.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/buildflags/buildflags.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_client_hints_type.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_local_frame_client.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#endif
+
+using blink::WebDocument;
+using blink::WebFrame;
+using blink::WebLocalFrame;
+using blink::WebSecurityOrigin;
+using blink::WebString;
+using blink::WebURL;
+using blink::WebView;
+using content::DocumentState;
+
+namespace {
+
+GURL GetOriginOrURL(const WebFrame* frame) {
+ url::Origin top_origin = url::Origin(frame->Top()->GetSecurityOrigin());
+ // The |top_origin| is unique ("null") e.g., for file:// URLs. Use the
+ // document URL as the primary URL in those cases.
+ // TODO(alexmos): This is broken for --site-per-process, since top() can be a
+ // WebRemoteFrame which does not have a document(), and the WebRemoteFrame's
+ // URL is not replicated. See https://crbug.com/628759.
+ if (top_origin.opaque() && frame->Top()->IsWebLocalFrame())
+ return frame->Top()->ToWebLocalFrame()->GetDocument().Url();
+ return top_origin.GetURL();
+}
+
+// Allow passing both WebURL and GURL here, so that we can early return without
+// allocating a new backing string if only the default rule matches.
+template <typename URL>
+ContentSetting GetContentSettingFromRules(
+ const ContentSettingsForOneType& rules,
+ const WebFrame* frame,
+ const URL& secondary_url) {
+ // If there is only one rule, it's the default rule and we don't need to match
+ // the patterns.
+ if (rules.size() == 1) {
+ DCHECK(rules[0].primary_pattern == ContentSettingsPattern::Wildcard());
+ DCHECK(rules[0].secondary_pattern == ContentSettingsPattern::Wildcard());
+ return rules[0].GetContentSetting();
+ }
+ const GURL& primary_url = GetOriginOrURL(frame);
+ const GURL& secondary_gurl = secondary_url;
+ for (const auto& rule : rules) {
+ if (rule.primary_pattern.Matches(primary_url) &&
+ rule.secondary_pattern.Matches(secondary_gurl)) {
+ return rule.GetContentSetting();
+ }
+ }
+ NOTREACHED();
+ return CONTENT_SETTING_DEFAULT;
+}
+
+bool IsScriptDisabledForPreview(content::RenderFrame* render_frame) {
+ return render_frame->GetPreviewsState() & content::NOSCRIPT_ON;
+}
+
+bool IsUniqueFrame(WebFrame* frame) {
+ return frame->GetSecurityOrigin().IsUnique() ||
+ frame->Top()->GetSecurityOrigin().IsUnique();
+}
+
+} // namespace
+
+ContentSettingsObserver::ContentSettingsObserver(
+ content::RenderFrame* render_frame,
+ bool should_whitelist,
+ service_manager::BinderRegistry* registry)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<ContentSettingsObserver>(
+ render_frame),
+ should_whitelist_(should_whitelist) {
+ ClearBlockedContentSettings();
+ render_frame->GetWebFrame()->SetContentSettingsClient(this);
+
+ render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
+ base::Bind(&ContentSettingsObserver::OnContentSettingsRendererRequest,
+ base::Unretained(this)));
+
+ content::RenderFrame* main_frame =
+ render_frame->GetRenderView()->GetMainRenderFrame();
+ // TODO(nasko): The main frame is not guaranteed to be in the same process
+ // with this frame with --site-per-process. This code needs to be updated
+ // to handle this case. See https://crbug.com/496670.
+ if (main_frame && main_frame != render_frame) {
+ // Copy all the settings from the main render frame to avoid race conditions
+ // when initializing this data. See https://crbug.com/333308.
+ ContentSettingsObserver* parent = ContentSettingsObserver::Get(main_frame);
+ allow_running_insecure_content_ = parent->allow_running_insecure_content_;
+ temporarily_allowed_plugins_ = parent->temporarily_allowed_plugins_;
+ is_interstitial_page_ = parent->is_interstitial_page_;
+ }
+}
+
+ContentSettingsObserver::~ContentSettingsObserver() {
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+void ContentSettingsObserver::SetExtensionDispatcher(
+ extensions::Dispatcher* extension_dispatcher) {
+ DCHECK(!extension_dispatcher_)
+ << "SetExtensionDispatcher() should only be called once.";
+ extension_dispatcher_ = extension_dispatcher;
+}
+#endif
+
+void ContentSettingsObserver::SetContentSettingRules(
+ const RendererContentSettingRules* content_setting_rules) {
+ content_setting_rules_ = content_setting_rules;
+ UMA_HISTOGRAM_COUNTS_1M("ClientHints.CountRulesReceived",
+ content_setting_rules_->client_hints_rules.size());
+}
+
+const RendererContentSettingRules*
+ContentSettingsObserver::GetContentSettingRules() {
+ return content_setting_rules_;
+}
+
+bool ContentSettingsObserver::IsPluginTemporarilyAllowed(
+ const std::string& identifier) {
+ // If the empty string is in here, it means all plugins are allowed.
+ // TODO(bauerb): Remove this once we only pass in explicit identifiers.
+ return base::Contains(temporarily_allowed_plugins_, identifier) ||
+ base::Contains(temporarily_allowed_plugins_, std::string());
+}
+
+void ContentSettingsObserver::DidBlockContentType(
+ ContentSettingsType settings_type) {
+ DidBlockContentType(settings_type, base::string16());
+}
+
+void ContentSettingsObserver::DidBlockContentType(
+ ContentSettingsType settings_type,
+ const base::string16& details) {
+ // Send multiple ContentBlocked messages if details are provided.
+ bool newly_blocked = content_blocked_.insert(settings_type).second;
+ if (newly_blocked || !details.empty()) {
+ Send(new ChromeViewHostMsg_ContentBlocked(routing_id(), settings_type,
+ details));
+ }
+}
+
+bool ContentSettingsObserver::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver, message)
+ IPC_MESSAGE_HANDLER(ChromeViewMsg_RequestFileSystemAccessAsyncResponse,
+ OnRequestFileSystemAccessAsyncResponse)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ if (handled)
+ return true;
+
+ // Don't swallow LoadBlockedPlugins messages, as they're sent to every
+ // blocked plugin.
+ IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver, message)
+ IPC_MESSAGE_HANDLER(ChromeViewMsg_LoadBlockedPlugins, OnLoadBlockedPlugins)
+ IPC_END_MESSAGE_MAP()
+
+ return false;
+}
+
+void ContentSettingsObserver::DidCommitProvisionalLoad(
+ bool is_same_document_navigation,
+ ui::PageTransition transition) {
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (frame->Parent())
+ return; // Not a top-level navigation.
+
+ if (!is_same_document_navigation) {
+ // Clear "block" flags for the new page. This needs to happen before any of
+ // |allowScript()|, |allowScriptFromSource()|, |allowImage()|, or
+ // |allowPlugins()| is called for the new page so that these functions can
+ // correctly detect that a piece of content flipped from "not blocked" to
+ // "blocked".
+ ClearBlockedContentSettings();
+ temporarily_allowed_plugins_.clear();
+ }
+
+ GURL url = frame->GetDocument().Url();
+ // If we start failing this DCHECK, please makes sure we don't regress
+ // this bug: http://code.google.com/p/chromium/issues/detail?id=79304
+ DCHECK(frame->GetDocument().GetSecurityOrigin().ToString() == "null" ||
+ !url.SchemeIs(url::kDataScheme));
+}
+
+void ContentSettingsObserver::OnDestruct() {
+ delete this;
+}
+
+void ContentSettingsObserver::SetAllowRunningInsecureContent() {
+ allow_running_insecure_content_ = true;
+
+ // Reload if we are the main frame.
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (!frame->Parent())
+ frame->StartReload(blink::WebFrameLoadType::kReload);
+}
+
+void ContentSettingsObserver::SetAsInterstitial() {
+ is_interstitial_page_ = true;
+}
+
+void ContentSettingsObserver::OnContentSettingsRendererRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::ContentSettingsRenderer>
+ receiver) {
+ receivers_.Add(this, std::move(receiver));
+}
+
+bool ContentSettingsObserver::AllowDatabase() {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (IsUniqueFrame(frame))
+ return false;
+
+ bool result = false;
+ Send(new ChromeViewHostMsg_AllowDatabase(
+ routing_id(), frame->GetSecurityOrigin(),
+ frame->GetDocument().SiteForCookies(),
+ frame->GetDocument().TopFrameOrigin(), &result));
+ return result;
+}
+
+void ContentSettingsObserver::RequestFileSystemAccessAsync(
+ base::OnceCallback<void(bool)> callback) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (IsUniqueFrame(frame)) {
+ std::move(callback).Run(false);
+ return;
+ }
+ ++current_request_id_;
+ bool inserted =
+ permission_requests_
+ .insert(std::make_pair(current_request_id_, std::move(callback)))
+ .second;
+
+ // Verify there are no duplicate insertions.
+ DCHECK(inserted);
+
+ Send(new ChromeViewHostMsg_RequestFileSystemAccessAsync(
+ routing_id(), current_request_id_, frame->GetSecurityOrigin(),
+ frame->GetDocument().SiteForCookies(),
+ frame->GetDocument().TopFrameOrigin()));
+}
+
+bool ContentSettingsObserver::AllowImage(bool enabled_per_settings,
+ const WebURL& image_url) {
+ bool allow = enabled_per_settings;
+ if (enabled_per_settings) {
+ if (is_interstitial_page_)
+ return true;
+
+ if (IsWhitelistedForContentSettings())
+ return true;
+
+ if (content_setting_rules_) {
+ allow = GetContentSettingFromRules(content_setting_rules_->image_rules,
+ render_frame()->GetWebFrame(),
+ image_url) != CONTENT_SETTING_BLOCK;
+ }
+ }
+ if (!allow)
+ DidBlockContentType(CONTENT_SETTINGS_TYPE_IMAGES);
+ return allow;
+}
+
+bool ContentSettingsObserver::AllowIndexedDB(const WebSecurityOrigin& origin) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (IsUniqueFrame(frame))
+ return false;
+
+ bool result = false;
+ Send(new ChromeViewHostMsg_AllowIndexedDB(
+ routing_id(), frame->GetSecurityOrigin(),
+ frame->GetDocument().SiteForCookies(),
+ frame->GetDocument().TopFrameOrigin(), &result));
+ return result;
+}
+
+bool ContentSettingsObserver::AllowCacheStorage(
+ const blink::WebSecurityOrigin& origin) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (IsUniqueFrame(frame))
+ return false;
+
+ bool result = false;
+ Send(new ChromeViewHostMsg_AllowCacheStorage(
+ routing_id(), frame->GetSecurityOrigin(),
+ frame->GetDocument().SiteForCookies(),
+ frame->GetDocument().TopFrameOrigin(), &result));
+ return result;
+}
+
+bool ContentSettingsObserver::AllowScript(bool enabled_per_settings) {
+ if (!enabled_per_settings)
+ return false;
+ if (IsScriptDisabledForPreview(render_frame()))
+ return false;
+ if (is_interstitial_page_)
+ return true;
+
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ const auto it = cached_script_permissions_.find(frame);
+ if (it != cached_script_permissions_.end())
+ return it->second;
+
+ // Evaluate the content setting rules before
+ // IsWhitelistedForContentSettings(); if there is only the default rule
+ // allowing all scripts, it's quicker this way.
+ bool allow = true;
+ if (content_setting_rules_) {
+ ContentSetting setting = GetContentSettingFromRules(
+ content_setting_rules_->script_rules, frame,
+ url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL());
+ allow = setting != CONTENT_SETTING_BLOCK;
+ }
+ allow = allow || IsWhitelistedForContentSettings();
+
+ cached_script_permissions_[frame] = allow;
+ return allow;
+}
+
+bool ContentSettingsObserver::AllowScriptFromSource(
+ bool enabled_per_settings,
+ const blink::WebURL& script_url) {
+ if (!enabled_per_settings)
+ return false;
+ if (IsScriptDisabledForPreview(render_frame()))
+ return false;
+ if (is_interstitial_page_)
+ return true;
+
+ bool allow = true;
+ if (content_setting_rules_) {
+ ContentSetting setting =
+ GetContentSettingFromRules(content_setting_rules_->script_rules,
+ render_frame()->GetWebFrame(), script_url);
+ allow = setting != CONTENT_SETTING_BLOCK;
+ }
+ return allow || IsWhitelistedForContentSettings();
+}
+
+bool ContentSettingsObserver::AllowStorage(bool local) {
+ WebLocalFrame* frame = render_frame()->GetWebFrame();
+ if (IsUniqueFrame(frame))
+ return false;
+
+ StoragePermissionsKey key(
+ url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL(), local);
+ const auto permissions = cached_storage_permissions_.find(key);
+ if (permissions != cached_storage_permissions_.end())
+ return permissions->second;
+
+ bool result = false;
+ Send(new ChromeViewHostMsg_AllowDOMStorage(
+ routing_id(), frame->GetSecurityOrigin(),
+ frame->GetDocument().SiteForCookies(),
+ frame->GetDocument().TopFrameOrigin(), local, &result));
+ cached_storage_permissions_[key] = result;
+ return result;
+}
+
+bool ContentSettingsObserver::AllowReadFromClipboard(bool default_value) {
+ bool allowed = default_value;
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ extensions::ScriptContext* current_context =
+ extension_dispatcher_->script_context_set().GetCurrent();
+ if (current_context) {
+ allowed |= current_context->HasAPIPermission(
+ extensions::APIPermission::kClipboardRead);
+ }
+#endif
+ return allowed;
+}
+
+bool ContentSettingsObserver::AllowWriteToClipboard(bool default_value) {
+ bool allowed = default_value;
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // All blessed extension pages could historically write to the clipboard, so
+ // preserve that for compatibility.
+ extensions::ScriptContext* current_context =
+ extension_dispatcher_->script_context_set().GetCurrent();
+ if (current_context) {
+ if (current_context->effective_context_type() ==
+ extensions::Feature::BLESSED_EXTENSION_CONTEXT &&
+ !current_context->IsForServiceWorker()) {
+ allowed = true;
+ } else {
+ allowed |= current_context->HasAPIPermission(
+ extensions::APIPermission::kClipboardWrite);
+ }
+ }
+#endif
+ return allowed;
+}
+
+bool ContentSettingsObserver::AllowMutationEvents(bool default_value) {
+ return IsPlatformApp() ? false : default_value;
+}
+
+bool ContentSettingsObserver::AllowRunningInsecureContent(
+ bool allowed_per_settings,
+ const blink::WebSecurityOrigin& origin,
+ const blink::WebURL& resource_url) {
+ bool allow = allowed_per_settings;
+
+ if (base::FeatureList::IsEnabled(features::kMixedContentSiteSetting)) {
+ if (content_setting_rules_) {
+ auto setting = GetContentSettingFromRules(
+ content_setting_rules_->mixed_content_rules,
+ render_frame()->GetWebFrame(), GURL());
+ allow |= (setting == CONTENT_SETTING_ALLOW);
+ }
+ } else {
+ allow |= allow_running_insecure_content_;
+ if (!allow) {
+ DidBlockContentType(CONTENT_SETTINGS_TYPE_MIXEDSCRIPT);
+ }
+ }
+
+ // Note: this implementation is a mirror of
+ // Browser::ShouldAllowRunningInsecureContent.
+ FilteredReportInsecureContentRan(GURL(resource_url));
+
+ return allow;
+}
+
+bool ContentSettingsObserver::AllowAutoplay(bool default_value) {
+ if (!content_setting_rules_)
+ return default_value;
+
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ return GetContentSettingFromRules(
+ content_setting_rules_->autoplay_rules, frame,
+ url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL()) ==
+ CONTENT_SETTING_ALLOW;
+}
+
+bool ContentSettingsObserver::AllowPopupsAndRedirects(bool default_value) {
+ if (!content_setting_rules_)
+ return default_value;
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ return GetContentSettingFromRules(
+ content_setting_rules_->popup_redirect_rules, frame,
+ url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL()) ==
+ CONTENT_SETTING_ALLOW;
+}
+
+void ContentSettingsObserver::PassiveInsecureContentFound(
+ const blink::WebURL& resource_url) {
+ // Note: this implementation is a mirror of
+ // Browser::PassiveInsecureContentFound.
+ ReportInsecureContent(SslInsecureContentType::DISPLAY);
+ FilteredReportInsecureContentDisplayed(GURL(resource_url));
+}
+
+void ContentSettingsObserver::PersistClientHints(
+ const blink::WebEnabledClientHints& enabled_client_hints,
+ base::TimeDelta duration,
+ const blink::WebURL& url) {
+ if (duration <= base::TimeDelta())
+ return;
+
+ const GURL primary_url(url);
+ const url::Origin primary_origin = url::Origin::Create(primary_url);
+ if (!content::IsOriginSecure(primary_url))
+ return;
+
+ // TODO(tbansal): crbug.com/735518. Determine if the value should be
+ // merged or overridden. Also, determine if the merger should happen on the
+ // browser side or the renderer. If the value needs to be overridden,
+ // this method should not return early if |update_count| is 0.
+ std::vector<::blink::mojom::WebClientHintsType> client_hints;
+ static constexpr size_t kWebClientHintsCount =
+ static_cast<size_t>(blink::mojom::WebClientHintsType::kMaxValue) + 1;
+ client_hints.reserve(kWebClientHintsCount);
+
+ for (size_t i = 0; i < kWebClientHintsCount; ++i) {
+ if (enabled_client_hints.IsEnabled(
+ static_cast<blink::mojom::WebClientHintsType>(i))) {
+ client_hints.push_back(static_cast<blink::mojom::WebClientHintsType>(i));
+ }
+ }
+ size_t update_count = client_hints.size();
+ if (update_count == 0)
+ return;
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "ClientHints.PersistDuration", duration, base::TimeDelta::FromSeconds(1),
+ // TODO(crbug.com/949034): Rename and fix this histogram to have some
+ // intended max value. We throw away the 32 most-significant bits of the
+ // 64-bit time delta in milliseconds. Before it happened silently in
+ // histogram.cc, now it is explicit here. The previous value of 365 days
+ // effectively turns into roughly 17 days when getting cast to int.
+ base::TimeDelta::FromMilliseconds(
+ static_cast<int>(base::TimeDelta::FromDays(365).InMilliseconds())),
+ 100);
+
+ UMA_HISTOGRAM_COUNTS_100("ClientHints.UpdateSize", update_count);
+
+ // Notify the embedder.
+ mojo::AssociatedRemote<client_hints::mojom::ClientHints> host_observer;
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&host_observer);
+ host_observer->PersistClientHints(primary_origin, std::move(client_hints),
+ duration);
+}
+
+void ContentSettingsObserver::GetAllowedClientHintsFromSource(
+ const blink::WebURL& url,
+ blink::WebEnabledClientHints* client_hints) const {
+ if (!content_setting_rules_)
+ return;
+
+ if (content_setting_rules_->client_hints_rules.empty())
+ return;
+
+ client_hints::GetAllowedClientHintsFromSource(
+ url,
+ content_setting_rules_->client_hints_rules, client_hints);
+}
+
+void ContentSettingsObserver::DidNotAllowPlugins() {
+ DidBlockContentType(CONTENT_SETTINGS_TYPE_PLUGINS);
+}
+
+void ContentSettingsObserver::DidNotAllowScript() {
+ DidBlockContentType(CONTENT_SETTINGS_TYPE_JAVASCRIPT);
+}
+
+void ContentSettingsObserver::OnLoadBlockedPlugins(
+ const std::string& identifier) {
+ temporarily_allowed_plugins_.insert(identifier);
+}
+
+void ContentSettingsObserver::OnRequestFileSystemAccessAsyncResponse(
+ int request_id,
+ bool allowed) {
+ auto it = permission_requests_.find(request_id);
+ if (it == permission_requests_.end())
+ return;
+
+ base::OnceCallback<void(bool)> callback = std::move(it->second);
+ permission_requests_.erase(it);
+
+ std::move(callback).Run(allowed);
+}
+
+void ContentSettingsObserver::ClearBlockedContentSettings() {
+ content_blocked_.clear();
+ cached_storage_permissions_.clear();
+ cached_script_permissions_.clear();
+}
+
+bool ContentSettingsObserver::IsPlatformApp() {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ WebSecurityOrigin origin = frame->GetDocument().GetSecurityOrigin();
+ const extensions::Extension* extension = GetExtension(origin);
+ return extension && extension->is_platform_app();
+#else
+ return false;
+#endif
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+const extensions::Extension* ContentSettingsObserver::GetExtension(
+ const WebSecurityOrigin& origin) const {
+ if (origin.Protocol().Ascii() != extensions::kExtensionScheme)
+ return nullptr;
+
+ const std::string extension_id = origin.Host().Utf8().data();
+ if (!extension_dispatcher_->IsExtensionActive(extension_id))
+ return nullptr;
+
+ return extensions::RendererExtensionRegistry::Get()->GetByID(extension_id);
+}
+#endif
+
+// static
+bool ContentSettingsObserver::IsWhitelistedForContentSettings() const {
+ if (should_whitelist_)
+ return true;
+
+ // Whitelist ftp directory listings, as they require JavaScript to function
+ // properly.
+ if (render_frame()->IsFTPDirectoryListing())
+ return true;
+
+ const WebDocument& document = render_frame()->GetWebFrame()->GetDocument();
+ return IsWhitelistedForContentSettings(document.GetSecurityOrigin(),
+ document.Url());
+}
+
+bool ContentSettingsObserver::IsWhitelistedForContentSettings(
+ const WebSecurityOrigin& origin,
+ const WebURL& document_url) {
+ if (document_url.GetString() == content::kUnreachableWebDataURL)
+ return true;
+
+ if (origin.IsUnique())
+ return false; // Uninitialized document?
+
+ blink::WebString protocol = origin.Protocol();
+
+ if (protocol == content::kChromeUIScheme)
+ return true; // Browser UI elements should still work.
+
+ if (protocol == content::kChromeDevToolsScheme)
+ return true; // DevTools UI elements should still work.
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (protocol == extensions::kExtensionScheme)
+ return true;
+#endif
+
+ // If the scheme is file:, an empty file name indicates a directory listing,
+ // which requires JavaScript to function properly.
+ if (protocol == url::kFileScheme &&
+ document_url.ProtocolIs(url::kFileScheme)) {
+ return GURL(document_url).ExtractFileName().empty();
+ }
+ return false;
+}
diff --git a/chromium/chrome/renderer/content_settings_observer.h b/chromium/chrome/renderer/content_settings_observer.h
new file mode 100644
index 00000000000..e55f9c2026b
--- /dev/null
+++ b/chromium/chrome/renderer/content_settings_observer.h
@@ -0,0 +1,202 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CONTENT_SETTINGS_OBSERVER_H_
+#define CHROME_RENDERER_CONTENT_SETTINGS_OBSERVER_H_
+
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/gtest_prod_util.h"
+#include "base/time/time.h"
+#include "chrome/common/content_settings_renderer.mojom.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "extensions/buildflags/buildflags.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "third_party/blink/public/platform/web_content_settings_client.h"
+#include "url/gurl.h"
+
+namespace blink {
+struct WebEnabledClientHints;
+class WebFrame;
+class WebSecurityOrigin;
+class WebURL;
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+namespace extensions {
+class Dispatcher;
+class Extension;
+}
+#endif
+
+// Handles blocking content per content settings for each RenderFrame.
+class ContentSettingsObserver
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<ContentSettingsObserver>,
+ public blink::WebContentSettingsClient,
+ public chrome::mojom::ContentSettingsRenderer {
+ public:
+ // Set |should_whitelist| to true if |render_frame()| contains content that
+ // should be whitelisted for content settings.
+ ContentSettingsObserver(content::RenderFrame* render_frame,
+ bool should_whitelist,
+ service_manager::BinderRegistry* registry);
+ ~ContentSettingsObserver() override;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // Sets the extension dispatcher. Call this right after constructing this
+ // class. This should only be called once.
+ void SetExtensionDispatcher(extensions::Dispatcher* extension_dispatcher);
+#endif
+
+ // Sets the content setting rules which back |allowImage()|, |allowScript()|,
+ // |allowScriptFromSource()| and |allowAutoplay()|. |content_setting_rules|
+ // must outlive this |ContentSettingsObserver|.
+ void SetContentSettingRules(
+ const RendererContentSettingRules* content_setting_rules);
+ const RendererContentSettingRules* GetContentSettingRules();
+
+ bool IsPluginTemporarilyAllowed(const std::string& identifier);
+
+ // Sends an IPC notification that the specified content type was blocked.
+ void DidBlockContentType(ContentSettingsType settings_type);
+
+ // Sends an IPC notification that the specified content type was blocked
+ // with additional metadata.
+ void DidBlockContentType(ContentSettingsType settings_type,
+ const base::string16& details);
+
+ // blink::WebContentSettingsClient:
+ bool AllowDatabase() override;
+ void RequestFileSystemAccessAsync(
+ base::OnceCallback<void(bool)> callback) override;
+ bool AllowImage(bool enabled_per_settings,
+ const blink::WebURL& image_url) override;
+ bool AllowIndexedDB(const blink::WebSecurityOrigin& origin) override;
+ bool AllowCacheStorage(const blink::WebSecurityOrigin& origin) override;
+ bool AllowScript(bool enabled_per_settings) override;
+ bool AllowScriptFromSource(bool enabled_per_settings,
+ const blink::WebURL& script_url) override;
+ bool AllowStorage(bool local) override;
+ bool AllowReadFromClipboard(bool default_value) override;
+ bool AllowWriteToClipboard(bool default_value) override;
+ bool AllowMutationEvents(bool default_value) override;
+ void DidNotAllowPlugins() override;
+ void DidNotAllowScript() override;
+ bool AllowRunningInsecureContent(bool allowed_per_settings,
+ const blink::WebSecurityOrigin& context,
+ const blink::WebURL& url) override;
+ bool AllowAutoplay(bool default_value) override;
+ bool AllowPopupsAndRedirects(bool default_value) override;
+ void PassiveInsecureContentFound(const blink::WebURL&) override;
+ void PersistClientHints(
+ const blink::WebEnabledClientHints& enabled_client_hints,
+ base::TimeDelta duration,
+ const blink::WebURL& url) override;
+ void GetAllowedClientHintsFromSource(
+ const blink::WebURL& url,
+ blink::WebEnabledClientHints* client_hints) const override;
+
+ bool allow_running_insecure_content() const {
+ return allow_running_insecure_content_;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ContentSettingsObserverTest, WhitelistedSchemes);
+ FRIEND_TEST_ALL_PREFIXES(ContentSettingsObserverBrowserTest,
+ ContentSettingsInterstitialPages);
+ FRIEND_TEST_ALL_PREFIXES(ContentSettingsObserverBrowserTest,
+ PluginsTemporarilyAllowed);
+
+ // RenderFrameObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override;
+ void OnDestruct() override;
+
+ // chrome::mojom::ContentSettingsRenderer:
+ void SetAllowRunningInsecureContent() override;
+ void SetAsInterstitial() override;
+
+ void OnContentSettingsRendererRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::ContentSettingsRenderer>
+ receiver);
+
+ // Message handlers.
+ void OnLoadBlockedPlugins(const std::string& identifier);
+ void OnRequestFileSystemAccessAsyncResponse(int request_id, bool allowed);
+
+ // Resets the |content_blocked_| array.
+ void ClearBlockedContentSettings();
+
+ // Whether the observed RenderFrame is for a platform app.
+ bool IsPlatformApp();
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // If |origin| corresponds to an installed extension, returns that extension.
+ // Otherwise returns NULL.
+ const extensions::Extension* GetExtension(
+ const blink::WebSecurityOrigin& origin) const;
+#endif
+
+ // Helpers.
+ // True if |render_frame()| contains content that is white-listed for content
+ // settings.
+ bool IsWhitelistedForContentSettings() const;
+
+ // Exposed for unit tests.
+ static bool IsWhitelistedForContentSettings(
+ const blink::WebSecurityOrigin& origin,
+ const blink::WebURL& document_url);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // Owned by ChromeContentRendererClient and outlive us.
+ extensions::Dispatcher* extension_dispatcher_ = nullptr;
+#endif
+
+ // Insecure content may be permitted for the duration of this render view.
+ bool allow_running_insecure_content_ = false;
+
+ // A pointer to content setting rules stored by the renderer. Normally, the
+ // |RendererContentSettingRules| object is owned by
+ // |ChromeRenderThreadObserver|. In the tests it is owned by the caller of
+ // |SetContentSettingRules|.
+ const RendererContentSettingRules* content_setting_rules_ = nullptr;
+
+ // Stores if images, scripts, and plugins have actually been blocked.
+ base::flat_set<ContentSettingsType> content_blocked_;
+
+ // Caches the result of AllowStorage.
+ using StoragePermissionsKey = std::pair<GURL, bool>;
+ base::flat_map<StoragePermissionsKey, bool> cached_storage_permissions_;
+
+ // Caches the result of AllowScript.
+ base::flat_map<blink::WebFrame*, bool> cached_script_permissions_;
+
+ base::flat_set<std::string> temporarily_allowed_plugins_;
+ bool is_interstitial_page_ = false;
+
+ int current_request_id_ = 0;
+ base::flat_map<int, base::OnceCallback<void(bool)>> permission_requests_;
+
+ // If true, IsWhitelistedForContentSettings will always return true.
+ const bool should_whitelist_;
+
+ mojo::AssociatedReceiverSet<chrome::mojom::ContentSettingsRenderer>
+ receivers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentSettingsObserver);
+};
+
+#endif // CHROME_RENDERER_CONTENT_SETTINGS_OBSERVER_H_
diff --git a/chromium/chrome/renderer/content_settings_observer_browsertest.cc b/chromium/chrome/renderer/content_settings_observer_browsertest.cc
new file mode 100644
index 00000000000..08ff54d8566
--- /dev/null
+++ b/chromium/chrome/renderer/content_settings_observer_browsertest.cc
@@ -0,0 +1,586 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/renderer/content_settings_observer.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_utils.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_test_sink.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/web/web_frame_content_dumper.h"
+#include "third_party/blink/public/web/web_view.h"
+
+using testing::_;
+using testing::DeleteArg;
+
+namespace {
+
+constexpr char kScriptHtml[] = R"HTML(
+ <html>
+ <head>
+ <script src='data:foo'></script>
+ </head>
+ <body></body>
+ </html>;
+)HTML";
+
+constexpr char kScriptWithSrcHtml[] = R"HTML(
+ <html>
+ <head>
+ <script src="http://www.example.com/script.js"></script>
+ </head>
+ <body></body>
+ </html>
+)HTML";
+
+class MockContentSettingsObserver : public ContentSettingsObserver {
+ public:
+ MockContentSettingsObserver(content::RenderFrame* render_frame,
+ service_manager::BinderRegistry* registry);
+ ~MockContentSettingsObserver() override {}
+
+ bool Send(IPC::Message* message) override;
+
+ MOCK_METHOD2(OnContentBlocked,
+ void(ContentSettingsType, const base::string16&));
+
+ MOCK_METHOD6(OnAllowDOMStorage,
+ void(int,
+ const url::Origin&,
+ const GURL&,
+ const url::Origin&,
+ bool,
+ IPC::Message*));
+
+ const GURL& image_url() const { return image_url_; }
+ const std::string& image_origin() const { return image_origin_; }
+
+ private:
+ const GURL image_url_;
+ const std::string image_origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockContentSettingsObserver);
+};
+
+MockContentSettingsObserver::MockContentSettingsObserver(
+ content::RenderFrame* render_frame,
+ service_manager::BinderRegistry* registry)
+ : ContentSettingsObserver(render_frame, false, registry),
+ image_url_("http://www.foo.com/image.jpg"),
+ image_origin_("http://www.foo.com") {}
+
+bool MockContentSettingsObserver::Send(IPC::Message* message) {
+ IPC_BEGIN_MESSAGE_MAP(MockContentSettingsObserver, *message)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_ContentBlocked, OnContentBlocked)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ChromeViewHostMsg_AllowDOMStorage,
+ OnAllowDOMStorage)
+ IPC_MESSAGE_UNHANDLED(ADD_FAILURE())
+ IPC_END_MESSAGE_MAP()
+
+ // Our super class deletes the message.
+ return RenderFrameObserver::Send(message);
+}
+
+// Evaluates a boolean |predicate| every time a provisional load is committed in
+// the given |frame| while the instance of this class is in scope, and verifies
+// that the result matches the |expectation|.
+class CommitTimeConditionChecker : public content::RenderFrameObserver {
+ public:
+ using Predicate = base::RepeatingCallback<bool()>;
+
+ CommitTimeConditionChecker(content::RenderFrame* frame,
+ const Predicate& predicate,
+ bool expectation)
+ : content::RenderFrameObserver(frame),
+ predicate_(predicate),
+ expectation_(expectation) {}
+
+ protected:
+ // RenderFrameObserver:
+ void OnDestruct() override {}
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override {
+ EXPECT_EQ(expectation_, predicate_.Run());
+ }
+
+ private:
+ const Predicate predicate_;
+ const bool expectation_;
+
+ DISALLOW_COPY_AND_ASSIGN(CommitTimeConditionChecker);
+};
+
+} // namespace
+
+class ContentSettingsObserverBrowserTest : public ChromeRenderViewTest {
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+ // Set up a fake url loader factory to ensure that script loader can create
+ // a WebURLLoader.
+ CreateFakeWebURLLoaderFactory();
+
+ // Unbind the ContentSettingsRenderer interface that would be registered by
+ // the ContentSettingsObserver created when the render frame is created.
+ view_->GetMainRenderFrame()
+ ->GetAssociatedInterfaceRegistry()
+ ->RemoveInterface(chrome::mojom::ContentSettingsRenderer::Name_);
+ }
+};
+
+TEST_F(ContentSettingsObserverBrowserTest, DidBlockContentType) {
+ MockContentSettingsObserver observer(view_->GetMainRenderFrame(),
+ registry_.get());
+ EXPECT_CALL(observer, OnContentBlocked(CONTENT_SETTINGS_TYPE_COOKIES,
+ base::string16()));
+ observer.DidBlockContentType(CONTENT_SETTINGS_TYPE_COOKIES);
+
+ // Blocking the same content type a second time shouldn't send a notification.
+ observer.DidBlockContentType(CONTENT_SETTINGS_TYPE_COOKIES);
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
+// Tests that multiple invokations of AllowDOMStorage result in a single IPC.
+TEST_F(ContentSettingsObserverBrowserTest, AllowDOMStorage) {
+ // Load some HTML, so we have a valid security origin.
+ LoadHTMLWithUrlOverride("<html></html>", "https://example.com/");
+ MockContentSettingsObserver observer(view_->GetMainRenderFrame(),
+ registry_.get());
+ ON_CALL(observer, OnAllowDOMStorage(_, _, _, _, _, _))
+ .WillByDefault(DeleteArg<5>());
+ EXPECT_CALL(observer, OnAllowDOMStorage(_, _, _, _, _, _));
+ observer.AllowStorage(true);
+
+ // Accessing localStorage from the same origin again shouldn't result in a
+ // new IPC.
+ observer.AllowStorage(true);
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
+// Regression test for http://crbug.com/35011
+TEST_F(ContentSettingsObserverBrowserTest, JSBlockSentAfterPageLoad) {
+ // 1. Load page with JS.
+ const char kHtml[] =
+ "<html>"
+ "<head>"
+ "<script>document.createElement('div');</script>"
+ "</head>"
+ "<body>"
+ "</body>"
+ "</html>";
+ render_thread_->sink().ClearMessages();
+ LoadHTML(kHtml);
+
+ // 2. Block JavaScript.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+ ContentSettingsObserver* observer = ContentSettingsObserver::Get(
+ view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // Make sure no pending messages are in the queue.
+ base::RunLoop().RunUntilIdle();
+ render_thread_->sink().ClearMessages();
+
+ const auto HasSentChromeViewHostMsgContentBlocked =
+ [](content::MockRenderThread* render_thread) {
+ return !!render_thread->sink().GetFirstMessageMatching(
+ ChromeViewHostMsg_ContentBlocked::ID);
+ };
+
+ // 3. Reload page. Verify that the notification that javascript was blocked
+ // has not yet been sent at the time when the navigation commits.
+ CommitTimeConditionChecker checker(
+ view_->GetMainRenderFrame(),
+ base::Bind(HasSentChromeViewHostMsgContentBlocked,
+ base::Unretained(render_thread_.get())),
+ false);
+
+ std::string url_str = "data:text/html;charset=utf-8,";
+ url_str.append(kHtml);
+ GURL url(url_str);
+ Reload(url);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(HasSentChromeViewHostMsgContentBlocked(render_thread_.get()));
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, PluginsTemporarilyAllowed) {
+ // Load some HTML.
+ LoadHTML("<html>Foo</html>");
+
+ std::string foo_plugin = "foo";
+ std::string bar_plugin = "bar";
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(foo_plugin));
+
+ // Temporarily allow the "foo" plugin.
+ observer->OnLoadBlockedPlugins(foo_plugin);
+ EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(foo_plugin));
+ EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(bar_plugin));
+
+ // Simulate same document navigation.
+ OnSameDocumentNavigation(GetMainFrame(), true);
+ EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(foo_plugin));
+ EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(bar_plugin));
+
+ // Navigate to a different page.
+ LoadHTML("<html>Bar</html>");
+ EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(foo_plugin));
+ EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(bar_plugin));
+
+ // Temporarily allow all plugins.
+ observer->OnLoadBlockedPlugins(std::string());
+ EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(foo_plugin));
+ EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(bar_plugin));
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, ImagesBlockedByDefault) {
+ MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame(),
+ registry_.get());
+
+ // Load some HTML.
+ LoadHTML("<html>Foo</html>");
+
+ // Set the default image blocking setting.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& image_setting_rules =
+ content_setting_rules.image_rules;
+ image_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer = ContentSettingsObserver::Get(
+ view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+ EXPECT_CALL(mock_observer,
+ OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES, base::string16()));
+ EXPECT_FALSE(observer->AllowImage(true, mock_observer.image_url()));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+
+ // Create an exception which allows the image.
+ image_setting_rules.insert(
+ image_setting_rules.begin(),
+ ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(),
+ ContentSettingsPattern::FromString(mock_observer.image_origin()),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+ std::string(), false));
+
+ EXPECT_CALL(mock_observer, OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES,
+ base::string16())).Times(0);
+ EXPECT_TRUE(observer->AllowImage(true, mock_observer.image_url()));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, ImagesAllowedByDefault) {
+ MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame(),
+ registry_.get());
+
+ // Load some HTML.
+ LoadHTML("<html>Foo</html>");
+
+ // Set the default image blocking setting.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& image_setting_rules =
+ content_setting_rules.image_rules;
+ image_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+ EXPECT_CALL(mock_observer, OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES,
+ base::string16())).Times(0);
+ EXPECT_TRUE(observer->AllowImage(true, mock_observer.image_url()));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+
+ // Create an exception which blocks the image.
+ image_setting_rules.insert(
+ image_setting_rules.begin(),
+ ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(),
+ ContentSettingsPattern::FromString(mock_observer.image_origin()),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+ EXPECT_CALL(mock_observer,
+ OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES, base::string16()));
+ EXPECT_FALSE(observer->AllowImage(true, mock_observer.image_url()));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, ContentSettingsBlockScripts) {
+ // Set the content settings for scripts.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // Load a page which contains a script.
+ LoadHTML(kScriptHtml);
+
+ // Verify that the script was blocked.
+ EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching(
+ ChromeViewHostMsg_ContentBlocked::ID));
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, ContentSettingsAllowScripts) {
+ // Set the content settings for scripts.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // Load a page which contains a script.
+ LoadHTML(kScriptHtml);
+
+ // Verify that the script was not blocked.
+ EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching(
+ ChromeViewHostMsg_ContentBlocked::ID));
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, ContentSettingsAllowScriptsWithSrc) {
+ // Set the content settings for scripts.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // Load a page which contains a script.
+ LoadHTML(kScriptWithSrcHtml);
+
+ // Verify that the script was not blocked.
+ EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching(
+ ChromeViewHostMsg_ContentBlocked::ID));
+}
+
+// Regression test for crbug.com/232410: Load a page with JS blocked. Then,
+// allow JS and reload the page. In each case, only one of noscript or script
+// tags should be enabled, but never both.
+TEST_F(ContentSettingsObserverBrowserTest, ContentSettingsNoscriptTag) {
+ // 1. Block JavaScript.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // 2. Load a page which contains a noscript tag and a script tag. Note that
+ // the page doesn't have a body tag.
+ const char kHtml[] =
+ "<html>"
+ "<noscript>JS_DISABLED</noscript>"
+ "<script>document.write('JS_ENABLED');</script>"
+ "</html>";
+ LoadHTML(kHtml);
+ EXPECT_NE(
+ std::string::npos,
+ blink::WebFrameContentDumper::DumpLayoutTreeAsText(
+ GetMainFrame(), blink::WebFrameContentDumper::kLayoutAsTextNormal)
+ .Utf8()
+ .find("JS_DISABLED"));
+ EXPECT_EQ(
+ std::string::npos,
+ blink::WebFrameContentDumper::DumpLayoutTreeAsText(
+ GetMainFrame(), blink::WebFrameContentDumper::kLayoutAsTextNormal)
+ .Utf8()
+ .find("JS_ENABLED"));
+
+ // 3. Allow JavaScript.
+ script_setting_rules.clear();
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+ std::string(), false));
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // 4. Reload the page.
+ std::string url_str = "data:text/html;charset=utf-8,";
+ url_str.append(kHtml);
+ GURL url(url_str);
+ Reload(url);
+ EXPECT_NE(
+ std::string::npos,
+ blink::WebFrameContentDumper::DumpLayoutTreeAsText(
+ GetMainFrame(), blink::WebFrameContentDumper::kLayoutAsTextNormal)
+ .Utf8()
+ .find("JS_ENABLED"));
+ EXPECT_EQ(
+ std::string::npos,
+ blink::WebFrameContentDumper::DumpLayoutTreeAsText(
+ GetMainFrame(), blink::WebFrameContentDumper::kLayoutAsTextNormal)
+ .Utf8()
+ .find("JS_DISABLED"));
+}
+
+// Checks that same document navigations don't update content settings for the
+// page.
+TEST_F(ContentSettingsObserverBrowserTest,
+ ContentSettingsSameDocumentNavigation) {
+ MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame(),
+ registry_.get());
+ // Load a page which contains a script.
+ LoadHTML(kScriptHtml);
+
+ // Verify that the script was not blocked.
+ EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching(
+ ChromeViewHostMsg_ContentBlocked::ID));
+
+ // Block JavaScript.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ // The page shouldn't see the change to script blocking setting after a
+ // same document navigation.
+ OnSameDocumentNavigation(GetMainFrame(), true);
+ EXPECT_TRUE(observer->AllowScript(true));
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, ContentSettingsInterstitialPages) {
+ MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame(),
+ registry_.get());
+ // Block scripts.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& script_setting_rules =
+ content_setting_rules.script_rules;
+ script_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+ // Block images.
+ ContentSettingsForOneType& image_setting_rules =
+ content_setting_rules.image_rules;
+ image_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+ observer->SetAsInterstitial();
+
+ // Load a page which contains a script.
+ LoadHTML(kScriptHtml);
+
+ // Verify that the script was allowed.
+ EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching(
+ ChromeViewHostMsg_ContentBlocked::ID));
+
+ // Verify that images are allowed.
+ EXPECT_CALL(mock_observer, OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES,
+ base::string16())).Times(0);
+ EXPECT_TRUE(observer->AllowImage(true, mock_observer.image_url()));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
+TEST_F(ContentSettingsObserverBrowserTest, AutoplayContentSettings) {
+ MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame(),
+ registry_.get());
+
+ // Load some HTML.
+ LoadHTML("<html>Foo</html>");
+
+ // Set the default setting.
+ RendererContentSettingRules content_setting_rules;
+ ContentSettingsForOneType& autoplay_setting_rules =
+ content_setting_rules.autoplay_rules;
+ autoplay_setting_rules.push_back(ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+ std::string(), false));
+
+ ContentSettingsObserver* observer =
+ ContentSettingsObserver::Get(view_->GetMainRenderFrame());
+ observer->SetContentSettingRules(&content_setting_rules);
+
+ EXPECT_TRUE(observer->AllowAutoplay(false));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+
+ // Add rule to block autoplay.
+ autoplay_setting_rules.insert(
+ autoplay_setting_rules.begin(),
+ ContentSettingPatternSource(
+ ContentSettingsPattern::Wildcard(),
+ ContentSettingsPattern::Wildcard(),
+ base::Value::FromUniquePtrValue(
+ content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+ std::string(), false));
+
+ EXPECT_FALSE(observer->AllowAutoplay(true));
+ ::testing::Mock::VerifyAndClearExpectations(&observer);
+}
diff --git a/chromium/chrome/renderer/content_settings_observer_unittest.cc b/chromium/chrome/renderer/content_settings_observer_unittest.cc
new file mode 100644
index 00000000000..7eb7dfc7851
--- /dev/null
+++ b/chromium/chrome/renderer/content_settings_observer_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/content_settings_observer.h"
+
+#include "chrome/common/url_constants.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/buildflags/buildflags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/constants.h"
+#endif
+
+using blink::WebSecurityOrigin;
+
+typedef testing::Test ContentSettingsObserverTest;
+
+TEST_F(ContentSettingsObserverTest, WhitelistedSchemes) {
+ std::string end_url = ":something";
+
+ GURL chrome_ui_url =
+ GURL(std::string(content::kChromeUIScheme).append(end_url));
+ EXPECT_TRUE(ContentSettingsObserver::IsWhitelistedForContentSettings(
+ WebSecurityOrigin::Create(chrome_ui_url), GURL()));
+
+ GURL chrome_dev_tools_url =
+ GURL(std::string(content::kChromeDevToolsScheme).append(end_url));
+ EXPECT_TRUE(ContentSettingsObserver::IsWhitelistedForContentSettings(
+ WebSecurityOrigin::Create(chrome_dev_tools_url), GURL()));
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ GURL extension_url =
+ GURL(std::string(extensions::kExtensionScheme).append(end_url));
+ EXPECT_TRUE(ContentSettingsObserver::IsWhitelistedForContentSettings(
+ WebSecurityOrigin::Create(extension_url), GURL()));
+#endif
+
+ GURL file_url("file:///dir/");
+ EXPECT_TRUE(ContentSettingsObserver::IsWhitelistedForContentSettings(
+ WebSecurityOrigin::Create(file_url), GURL("file:///dir/")));
+ EXPECT_FALSE(ContentSettingsObserver::IsWhitelistedForContentSettings(
+ WebSecurityOrigin::Create(file_url), GURL("file:///dir/file")));
+
+ GURL http_url =
+ GURL("http://server.com/path");
+ EXPECT_FALSE(ContentSettingsObserver::IsWhitelistedForContentSettings(
+ WebSecurityOrigin::Create(http_url), GURL()));
+}
diff --git a/chromium/chrome/renderer/custom_menu_commands.h b/chromium/chrome/renderer/custom_menu_commands.h
new file mode 100644
index 00000000000..b5127610e22
--- /dev/null
+++ b/chromium/chrome/renderer/custom_menu_commands.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_CUSTOM_MENU_COMMANDS_H_
+#define CHROME_RENDERER_CUSTOM_MENU_COMMANDS_H_
+
+enum MenuCommands {
+ MENU_COMMAND_PLUGIN_RUN = 1,
+ MENU_COMMAND_PLUGIN_HIDE = 2,
+ MENU_COMMAND_ENABLE_FLASH = 3,
+};
+
+#endif // CHROME_RENDERER_CUSTOM_MENU_COMMANDS_H_
diff --git a/chromium/chrome/renderer/extensions/DEPS b/chromium/chrome/renderer/extensions/DEPS
new file mode 100644
index 00000000000..747331dca49
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+media/audio",
+ "+services/network/public/cpp",
+ "+services/network/public/mojom",
+]
diff --git a/chromium/chrome/renderer/extensions/OWNERS b/chromium/chrome/renderer/extensions/OWNERS
new file mode 100644
index 00000000000..60dc5a8b5a6
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/OWNERS
@@ -0,0 +1,7 @@
+file://extensions/OWNERS
+
+per-file automation_ax_tree_wrapper*=file://ui/accessibility/OWNERS
+per-file automation_internal_custom_bindings*=file://ui/accessibility/OWNERS
+
+# COMPONENT: Platform>Extensions
+# TEAM: extensions-reviews@chromium.org
diff --git a/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.cc b/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.cc
new file mode 100644
index 00000000000..096197ae6ba
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.cc
@@ -0,0 +1,121 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/accessibility_private_hooks_delegate.h"
+
+#include "base/i18n/unicodestring.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/extension.h"
+#include "extensions/renderer/bindings/api_signature.h"
+#include "extensions/renderer/get_script_context.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/converter.h"
+#include "third_party/icu/source/common/unicode/locid.h"
+
+namespace extensions {
+
+namespace {
+constexpr char kGetDisplayLanguage[] =
+ "accessibilityPrivate.getDisplayLanguage";
+constexpr char kUndeterminedLanguage[] = "und";
+} // namespace
+
+using RequestResult = APIBindingHooks::RequestResult;
+
+AccessibilityPrivateHooksDelegate::AccessibilityPrivateHooksDelegate() =
+ default;
+AccessibilityPrivateHooksDelegate::~AccessibilityPrivateHooksDelegate() =
+ default;
+
+RequestResult AccessibilityPrivateHooksDelegate::HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) {
+ // Error checks.
+ // Ensure we would like to call the GetDisplayLanguage function.
+ if (method_name != kGetDisplayLanguage)
+ return RequestResult(RequestResult::NOT_HANDLED);
+ // Ensure arguments are successfully parsed and converted.
+ APISignature::V8ParseResult parse_result =
+ signature->ParseArgumentsToV8(context, *arguments, refs);
+ if (!parse_result.succeeded()) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(*parse_result.error);
+ return result;
+ }
+ return HandleGetDisplayLanguage(GetScriptContextFromV8ContextChecked(context),
+ *parse_result.arguments);
+}
+
+// Called to translate |language_code_to_translate| into human-readable string
+// in the language specified by |target_language_code|. For example, if
+// language_code_to_translate = 'en' and target_language_code = 'fr', then this
+// function returns 'anglais'.
+// If language_code_to_translate = 'fr' and target_language_code = 'en', then
+// this function returns 'french'.
+RequestResult AccessibilityPrivateHooksDelegate::HandleGetDisplayLanguage(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& parsed_arguments) {
+ DCHECK(script_context->extension());
+ DCHECK_EQ(2u, parsed_arguments.size());
+ DCHECK(parsed_arguments[0]->IsString());
+ DCHECK(parsed_arguments[1]->IsString());
+
+ std::string language_code_to_translate =
+ gin::V8ToString(script_context->isolate(), parsed_arguments[0]);
+ std::string target_language_code =
+ gin::V8ToString(script_context->isolate(), parsed_arguments[1]);
+ // The locale whose language code we want to translate.
+ icu::Locale locale_to_translate =
+ icu::Locale(language_code_to_translate.c_str());
+ // The locale that |display_language| should be in.
+ icu::Locale target_locale = icu::Locale(target_language_code.c_str());
+
+ // Validate locales.
+ // Get list of available locales. Please see the ICU User Guide for more
+ // details: http://userguide.icu-project.org/locale.
+ bool valid_arg1 = false;
+ bool valid_arg2 = false;
+ int32_t num_locales = 0;
+ const icu::Locale* available_locales =
+ icu::Locale::getAvailableLocales(num_locales);
+ for (int32_t i = 0; i < num_locales; ++i) {
+ // Check both the language and country for each locale.
+ const char* current_language = available_locales[i].getLanguage();
+ const char* current_country = available_locales[i].getCountry();
+ if (strcmp(locale_to_translate.getLanguage(), current_language) == 0 &&
+ strcmp(locale_to_translate.getCountry(), current_country) == 0) {
+ valid_arg1 = true;
+ }
+ if (strcmp(target_locale.getLanguage(), current_language) == 0 &&
+ strcmp(target_locale.getCountry(), current_country) == 0) {
+ valid_arg2 = true;
+ }
+ }
+
+ // If either of the language codes is invalid, we should return empty string.
+ if (!(valid_arg1 && valid_arg2)) {
+ RequestResult empty_result(RequestResult::HANDLED);
+ empty_result.return_value = gin::StringToV8(script_context->isolate(), "");
+ return empty_result;
+ }
+
+ icu::UnicodeString display_language;
+ locale_to_translate.getDisplayLanguage(target_locale, display_language);
+ std::string language_result =
+ base::UTF16ToUTF8(base::i18n::UnicodeStringToString16(display_language));
+ // Instead of returning "und", which is what the ICU Locale class returns for
+ // undetermined languages, we would simply like to return an empty string
+ // to communicate that we could not determine the display language.
+ if (language_result == kUndeterminedLanguage)
+ language_result = "";
+ RequestResult result(RequestResult::HANDLED);
+ result.return_value =
+ gin::StringToV8(script_context->isolate(), language_result);
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.h b/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.h
new file mode 100644
index 00000000000..733e79a4e6b
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_ACCESSIBILITY_PRIVATE_HOOKS_DELEGATE_H_
+#define CHROME_RENDERER_EXTENSIONS_ACCESSIBILITY_PRIVATE_HOOKS_DELEGATE_H_
+
+#include <vector>
+
+#include "extensions/renderer/bindings/api_binding_hooks_delegate.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Custom native hooks for the accessibilityPrivate API.
+// Methods are implemented as handlers in the implementation file
+// e.g. Handle<FunctionName>.
+// Handlers are implemented for synchronous methods of the API.
+class AccessibilityPrivateHooksDelegate : public APIBindingHooksDelegate {
+ public:
+ AccessibilityPrivateHooksDelegate();
+ ~AccessibilityPrivateHooksDelegate() override;
+
+ // APIBindingHooksDelegate:
+ APIBindingHooks::RequestResult HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) override;
+
+ private:
+ // Method handlers:
+ APIBindingHooks::RequestResult HandleGetDisplayLanguage(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& parsed_arguments);
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityPrivateHooksDelegate);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_ACCESSIBILITY_PRIVATE_HOOKS_DELEGATE_H_
diff --git a/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate_unittest.cc b/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate_unittest.cc
new file mode 100644
index 00000000000..da69b47febb
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/accessibility_private_hooks_delegate_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/accessibility_private_hooks_delegate.h"
+
+#include "base/strings/stringprintf.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_extension_bindings_system_test_base.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+using AccessibilityPrivateHooksDelegateTest =
+ NativeExtensionBindingsSystemUnittest;
+
+TEST_F(AccessibilityPrivateHooksDelegateTest, TestGetDisplayLanguage) {
+ // Initialize bindings system.
+ bindings_system()
+ ->api_system()
+ ->GetHooksForAPI("accessibilityPrivate")
+ ->SetDelegate(std::make_unique<AccessibilityPrivateHooksDelegate>());
+ // Register extension.
+ // Ensure that the extension has access to the accessibilityPrivate API by
+ // setting the extension ID to the ChromeVox extension ID, as well as giving
+ // it permission to the accessibilityPrivate API.
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder("testExtension")
+ .AddPermission("accessibilityPrivate")
+ .SetLocation(Manifest::COMPONENT)
+ .Build();
+ RegisterExtension(extension);
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+ bindings_system()->UpdateBindingsForContext(script_context);
+
+ // Ensure that the extension has access to the accessibilityPrivate API.
+ const Feature* a11y_private =
+ FeatureProvider::GetAPIFeature("accessibilityPrivate");
+ ASSERT_TRUE(a11y_private);
+ Feature::Availability availability =
+ a11y_private->IsAvailableToExtension(extension.get());
+ ASSERT_TRUE(availability.is_available()) << availability.message();
+
+ // Setup function that will run extension api.
+ auto run_get_display_language = [context](const char* args) {
+ SCOPED_TRACE(args);
+ constexpr char kRunGetDisplayLanguageFunction[] =
+ R"((function() {
+ return chrome.accessibilityPrivate.getDisplayLanguage(%s);
+ }))";
+ v8::Local<v8::Function> function = FunctionFromString(
+ context, base::StringPrintf(kRunGetDisplayLanguageFunction, args));
+ v8::Local<v8::Value> result = RunFunction(function, context, 0, nullptr);
+ return V8ToString(result, context);
+ };
+
+ // Test behavior.
+ EXPECT_EQ(R"("")", run_get_display_language("'',''"));
+ EXPECT_EQ(R"("")", run_get_display_language("'not a language code','ja-JP'"));
+ EXPECT_EQ(R"("")",
+ run_get_display_language("'zh-TW', 'not a language code'"));
+ EXPECT_EQ(R"("English")", run_get_display_language("'en','en'"));
+ EXPECT_EQ(R"("English")", run_get_display_language("'en-US','en'"));
+ EXPECT_EQ(R"("français")", run_get_display_language("'fr','fr'"));
+ EXPECT_EQ(R"("español")", run_get_display_language("'es','es'"));
+ EXPECT_EQ(R"("日本語")", run_get_display_language("'ja','ja'"));
+ EXPECT_EQ(R"("anglais")", run_get_display_language("'en','fr'"));
+ EXPECT_EQ(R"("Japanese")", run_get_display_language("'ja','en'"));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/app_hooks_delegate.cc b/chromium/chrome/renderer/extensions/app_hooks_delegate.cc
new file mode 100644
index 00000000000..8c2d148d292
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/app_hooks_delegate.cc
@@ -0,0 +1,238 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/app_hooks_delegate.h"
+
+#include <memory>
+
+#include "base/values.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/bindings/api_request_handler.h"
+#include "extensions/renderer/bindings/api_signature.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "gin/converter.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace extensions {
+
+namespace {
+
+void IsInstalledGetterCallback(
+ v8::Local<v8::String> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ v8::HandleScope handle_scope(info.GetIsolate());
+ v8::Local<v8::Context> context = info.Holder()->CreationContext();
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+
+ // The ScriptContext may have been invalidated if e.g. the frame was removed.
+ // Return undefined in this case.
+ if (!script_context)
+ return;
+
+ auto* hooks_delegate =
+ static_cast<AppHooksDelegate*>(info.Data().As<v8::External>()->Value());
+ // Since this is more-or-less an API, log it as an API call.
+ APIActivityLogger::LogAPICall(context, "app.getIsInstalled",
+ std::vector<v8::Local<v8::Value>>());
+ info.GetReturnValue().Set(hooks_delegate->GetIsInstalled(script_context));
+}
+
+} // namespace
+
+AppHooksDelegate::IPCHelper::IPCHelper(AppHooksDelegate* owner)
+ : owner_(owner) {}
+AppHooksDelegate::IPCHelper::~IPCHelper() = default;
+
+void AppHooksDelegate::IPCHelper::SendGetAppInstallStateMessage(
+ content::RenderFrame* render_frame,
+ const GURL& url,
+ int request_id) {
+ Send(new ExtensionHostMsg_GetAppInstallState(
+ render_frame->GetRoutingID(), url, GetRoutingID(), request_id));
+}
+
+bool AppHooksDelegate::IPCHelper::OnMessageReceived(
+ const IPC::Message& message) {
+ IPC_BEGIN_MESSAGE_MAP(AppHooksDelegate::IPCHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_GetAppInstallStateResponse,
+ OnAppInstallStateResponse)
+ IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
+ IPC_END_MESSAGE_MAP()
+ return true;
+}
+
+void AppHooksDelegate::IPCHelper::OnAppInstallStateResponse(
+ const std::string& state,
+ int request_id) {
+ owner_->OnAppInstallStateResponse(state, request_id);
+}
+
+AppHooksDelegate::AppHooksDelegate(Dispatcher* dispatcher,
+ APIRequestHandler* request_handler)
+ : dispatcher_(dispatcher),
+ request_handler_(request_handler),
+ ipc_helper_(this) {}
+AppHooksDelegate::~AppHooksDelegate() {}
+
+bool AppHooksDelegate::GetIsInstalled(ScriptContext* script_context) const {
+ const Extension* extension = script_context->extension();
+
+ // TODO(aa): Why only hosted app?
+ return extension && extension->is_hosted_app() &&
+ dispatcher_->IsExtensionActive(extension->id());
+}
+
+APIBindingHooks::RequestResult AppHooksDelegate::HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) {
+ using RequestResult = APIBindingHooks::RequestResult;
+
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::TryCatch try_catch(isolate);
+ APISignature::V8ParseResult parse_result =
+ signature->ParseArgumentsToV8(context, *arguments, refs);
+ if (!parse_result.succeeded()) {
+ if (try_catch.HasCaught()) {
+ try_catch.ReThrow();
+ return RequestResult(RequestResult::THROWN);
+ }
+ return RequestResult(RequestResult::INVALID_INVOCATION);
+ }
+
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ DCHECK(script_context);
+
+ APIBindingHooks::RequestResult result(
+ APIBindingHooks::RequestResult::HANDLED);
+ if (method_name == "app.getIsInstalled") {
+ result.return_value =
+ v8::Boolean::New(isolate, GetIsInstalled(script_context));
+ } else if (method_name == "app.getDetails") {
+ result.return_value = GetDetails(script_context);
+ } else if (method_name == "app.runningState") {
+ result.return_value =
+ gin::StringToSymbol(isolate, GetRunningState(script_context));
+ } else if (method_name == "app.installState") {
+ DCHECK_EQ(1u, parse_result.arguments->size());
+ DCHECK((*parse_result.arguments)[0]->IsFunction());
+ int request_id = request_handler_->AddPendingRequest(
+ context, (*parse_result.arguments)[0].As<v8::Function>());
+ GetInstallState(script_context, request_id);
+ } else {
+ NOTREACHED();
+ }
+
+ return result;
+}
+
+void AppHooksDelegate::InitializeTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::ObjectTemplate> object_template,
+ const APITypeReferenceMap& type_refs) {
+ // We expose a boolean isInstalled on the chrome.app API object, as well as
+ // the getIsInstalled() method.
+ // TODO(devlin): :(. Hopefully we can just remove this whole API, but this is
+ // particularly silly.
+ // This object should outlive contexts, so the |this| v8::External is safe.
+ // TODO(devlin): This is getting pretty common. We should find a generalized
+ // solution, or make gin::ObjectTemplateBuilder work for these use cases.
+ object_template->SetAccessor(gin::StringToSymbol(isolate, "isInstalled"),
+ &IsInstalledGetterCallback, nullptr,
+ v8::External::New(isolate, this));
+}
+
+v8::Local<v8::Value> AppHooksDelegate::GetDetails(
+ ScriptContext* script_context) const {
+ blink::WebLocalFrame* web_frame = script_context->web_frame();
+ CHECK(web_frame);
+
+ v8::Isolate* isolate = script_context->isolate();
+ if (web_frame->GetDocument().GetSecurityOrigin().IsUnique())
+ return v8::Null(isolate);
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
+ web_frame->GetDocument().Url());
+
+ if (!extension)
+ return v8::Null(isolate);
+
+ std::unique_ptr<base::DictionaryValue> manifest_copy =
+ extension->manifest()->value()->CreateDeepCopy();
+ manifest_copy->SetString("id", extension->id());
+ return content::V8ValueConverter::Create()->ToV8Value(
+ manifest_copy.get(), script_context->v8_context());
+}
+
+void AppHooksDelegate::GetInstallState(ScriptContext* script_context,
+ int request_id) {
+ content::RenderFrame* render_frame = script_context->GetRenderFrame();
+ CHECK(render_frame);
+
+ ipc_helper_.SendGetAppInstallStateMessage(
+ render_frame, script_context->web_frame()->GetDocument().Url(),
+ request_id);
+}
+
+const char* AppHooksDelegate::GetRunningState(
+ ScriptContext* script_context) const {
+ // To distinguish between ready_to_run and cannot_run states, we need the app
+ // from the top frame.
+ const RendererExtensionRegistry* extensions =
+ RendererExtensionRegistry::Get();
+
+ url::Origin top_origin =
+ script_context->web_frame()->Top()->GetSecurityOrigin();
+ // The app associated with the top level frame.
+ const Extension* top_app = extensions->GetHostedAppByURL(top_origin.GetURL());
+
+ // The app associated with this frame.
+ const Extension* this_app = extensions->GetHostedAppByURL(
+ script_context->web_frame()->GetDocument().Url());
+
+ if (!this_app || !top_app)
+ return extension_misc::kAppStateCannotRun;
+
+ const char* state = nullptr;
+ if (dispatcher_->IsExtensionActive(top_app->id())) {
+ if (top_app == this_app)
+ state = extension_misc::kAppStateRunning;
+ else
+ state = extension_misc::kAppStateCannotRun;
+ } else if (top_app == this_app) {
+ state = extension_misc::kAppStateReadyToRun;
+ } else {
+ state = extension_misc::kAppStateCannotRun;
+ }
+
+ return state;
+}
+
+void AppHooksDelegate::OnAppInstallStateResponse(const std::string& state,
+ int request_id) {
+ // Note: it's kind of lame that we serialize the install state to a
+ // base::Value here when we're just going to later convert it to v8, but it's
+ // not worth the specialization on APIRequestHandler for this oddball API.
+ base::ListValue response;
+ response.AppendString(state);
+ request_handler_->CompleteRequest(request_id, response, std::string());
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/app_hooks_delegate.h b/chromium/chrome/renderer/extensions/app_hooks_delegate.h
new file mode 100644
index 00000000000..da546fb5eb5
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/app_hooks_delegate.h
@@ -0,0 +1,108 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_APP_HOOKS_DELEGATE_H_
+#define CHROME_RENDERER_EXTENSIONS_APP_HOOKS_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/renderer/extensions/chrome_v8_extension_handler.h"
+#include "extensions/renderer/bindings/api_binding_hooks_delegate.h"
+#include "v8/include/v8.h"
+
+class GURL;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class APIRequestHandler;
+class Dispatcher;
+class ScriptContext;
+
+// The custom hooks for the chrome.app API.
+class AppHooksDelegate : public APIBindingHooksDelegate {
+ public:
+ using GetterCallback =
+ base::Callback<void(const v8::PropertyCallbackInfo<v8::Value>& info)>;
+
+ AppHooksDelegate(Dispatcher* dispatcher, APIRequestHandler* request_handler);
+ ~AppHooksDelegate() override;
+
+ // APIBindingHooksDelegate:
+ APIBindingHooks::RequestResult HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) override;
+ void InitializeTemplate(v8::Isolate* isolate,
+ v8::Local<v8::ObjectTemplate> object_template,
+ const APITypeReferenceMap& type_refs) override;
+
+ // Total misnomer. Returns true if there is a hosted app associated with
+ // |script_context| active in this process. This naming is to match the
+ // chrome.app function it implements.
+ bool GetIsInstalled(ScriptContext* script_context) const;
+
+ private:
+ // A helper class to handle IPC message sending/receiving. Isolated from
+ // AppHooksDelegate to avoid multiple inheritence.
+ class IPCHelper : public ChromeV8ExtensionHandler {
+ public:
+ explicit IPCHelper(AppHooksDelegate* owner);
+ ~IPCHelper() override;
+
+ // Sends the IPC message to the browser to get the install state of the
+ // app.
+ void SendGetAppInstallStateMessage(content::RenderFrame* render_frame,
+ const GURL& url,
+ int request_id);
+
+ private:
+ // IPC::Listener:
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ // Handle for ExtensionMsg_GetAppInstallStateResponse; just forwards to
+ // AppHooksDelegate.
+ void OnAppInstallStateResponse(const std::string& state, int request_id);
+
+ AppHooksDelegate* owner_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(IPCHelper);
+ };
+
+ // Returns the manifest of the extension associated with the frame.
+ v8::Local<v8::Value> GetDetails(ScriptContext* script_context) const;
+
+ // Determines the install state for the extension associated with the frame.
+ // Note that this could be "disabled" for hosted apps when the user visits the
+ // site but doesn't have the app enabled. The response is determined
+ // asynchronously before completing the request with the given |request_id|.
+ void GetInstallState(ScriptContext* script_context, int request_id);
+
+ // Returns the "running state" (one of running, cannot run, and ready to run)
+ // for the extension associated with the frame of the script context.
+ const char* GetRunningState(ScriptContext* script_context) const;
+
+ // Handle for ExtensionMsg_GetAppInstallStateResponse.
+ void OnAppInstallStateResponse(const std::string& state, int request_id);
+
+ // Dispatcher handle. Not owned.
+ Dispatcher* dispatcher_ = nullptr;
+
+ APIRequestHandler* request_handler_ = nullptr;
+
+ IPCHelper ipc_helper_;
+
+ GetterCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppHooksDelegate);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_APP_HOOKS_DELEGATE_H_
diff --git a/chromium/chrome/renderer/extensions/cast_streaming_native_handler.cc b/chromium/chrome/renderer/extensions/cast_streaming_native_handler.cc
new file mode 100644
index 00000000000..dd9509e45ee
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/cast_streaming_native_handler.cc
@@ -0,0 +1,933 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/cast_streaming_native_handler.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/system/sys_info.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "chrome/common/extensions/api/cast_streaming_receiver_session.h"
+#include "chrome/common/extensions/api/cast_streaming_rtp_stream.h"
+#include "chrome/common/extensions/api/cast_streaming_udp_transport.h"
+#include "chrome/renderer/media/cast_receiver_session.h"
+#include "chrome/renderer/media/cast_rtp_stream.h"
+#include "chrome/renderer/media/cast_session.h"
+#include "chrome/renderer/media/cast_udp_transport.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "extensions/common/extension.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/script_context.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/limits.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_address.h"
+#include "third_party/blink/public/platform/web_media_stream.h"
+#include "third_party/blink/public/platform/web_media_stream_track.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_dom_media_stream_track.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "url/gurl.h"
+
+using content::V8ValueConverter;
+using media::cast::FrameSenderConfig;
+
+// Extension types.
+using extensions::api::cast_streaming_receiver_session::RtpReceiverParams;
+using extensions::api::cast_streaming_rtp_stream::RtpParams;
+using extensions::api::cast_streaming_rtp_stream::RtpPayloadParams;
+using extensions::api::cast_streaming_udp_transport::IPEndPoint;
+
+namespace extensions {
+
+namespace {
+
+constexpr char kInvalidAesIvMask[] = "Invalid value for AES IV mask";
+constexpr char kInvalidAesKey[] = "Invalid value for AES key";
+constexpr char kInvalidDestination[] = "Invalid destination";
+constexpr char kInvalidRtpParams[] = "Invalid value for RTP params";
+constexpr char kInvalidLatency[] = "Invalid value for max_latency. (0-1000)";
+constexpr char kInvalidRtpTimebase[] = "Invalid rtp_timebase. (1000-1000000)";
+constexpr char kInvalidStreamArgs[] = "Invalid stream arguments";
+constexpr char kRtpStreamNotFound[] = "The RTP stream cannot be found";
+constexpr char kUdpTransportNotFound[] = "The UDP transport cannot be found";
+constexpr char kUnableToConvertArgs[] = "Unable to convert arguments";
+constexpr char kUnableToConvertParams[] = "Unable to convert params";
+constexpr char kCodecNameOpus[] = "OPUS";
+constexpr char kCodecNameVp8[] = "VP8";
+constexpr char kCodecNameH264[] = "H264";
+constexpr char kCodecNameRemoteAudio[] = "REMOTE_AUDIO";
+constexpr char kCodecNameRemoteVideo[] = "REMOTE_VIDEO";
+
+// To convert from kilobits per second to bits per second.
+constexpr int kBitsPerKilobit = 1000;
+
+bool HexDecode(const std::string& input, std::string* output) {
+ std::vector<uint8_t> bytes;
+ if (!base::HexStringToBytes(input, &bytes))
+ return false;
+ output->assign(reinterpret_cast<const char*>(&bytes[0]), bytes.size());
+ return true;
+}
+
+int NumberOfEncodeThreads() {
+ // Do not saturate CPU utilization just for encoding. On a lower-end system
+ // with only 1 or 2 cores, use only one thread for encoding. On systems with
+ // more cores, allow half of the cores to be used for encoding.
+ return std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
+}
+
+bool ToFrameSenderConfigOrThrow(v8::Isolate* isolate,
+ const RtpPayloadParams& ext_params,
+ FrameSenderConfig* config) {
+ config->sender_ssrc = ext_params.ssrc;
+ config->receiver_ssrc = ext_params.feedback_ssrc;
+ if (config->sender_ssrc == config->receiver_ssrc) {
+ DVLOG(1) << "sender_ssrc " << config->sender_ssrc
+ << " cannot be equal to receiver_ssrc";
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ config->min_playout_delay = base::TimeDelta::FromMilliseconds(
+ ext_params.min_latency ? *ext_params.min_latency
+ : ext_params.max_latency);
+ config->max_playout_delay =
+ base::TimeDelta::FromMilliseconds(ext_params.max_latency);
+ config->animated_playout_delay = base::TimeDelta::FromMilliseconds(
+ ext_params.animated_latency ? *ext_params.animated_latency
+ : ext_params.max_latency);
+ if (config->min_playout_delay <= base::TimeDelta()) {
+ DVLOG(1) << "min_playout_delay " << config->min_playout_delay
+ << " must be greater than zero";
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (config->min_playout_delay > config->max_playout_delay) {
+ DVLOG(1) << "min_playout_delay " << config->min_playout_delay
+ << " must be less than or equal to max_palyout_delay";
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (config->animated_playout_delay < config->min_playout_delay ||
+ config->animated_playout_delay > config->max_playout_delay) {
+ DVLOG(1) << "animated_playout_delay " << config->animated_playout_delay
+ << " must be between (inclusive) the min and max playout delay";
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (ext_params.codec_name == kCodecNameOpus) {
+ config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_OPUS;
+ config->use_external_encoder = false;
+ config->rtp_timebase = ext_params.clock_rate
+ ? *ext_params.clock_rate
+ : media::cast::kDefaultAudioSamplingRate;
+ // Sampling rate must be one of the Opus-supported values.
+ switch (config->rtp_timebase) {
+ case 48000:
+ case 24000:
+ case 16000:
+ case 12000:
+ case 8000:
+ break;
+ default:
+ DVLOG(1) << "rtp_timebase " << config->rtp_timebase << " is invalid";
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ config->channels = ext_params.channels ? *ext_params.channels : 2;
+ if (config->channels != 1 && config->channels != 2) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ DVLOG(1) << "channels " << config->channels << " is invalid";
+ return false;
+ }
+ config->min_bitrate = config->start_bitrate = config->max_bitrate =
+ ext_params.max_bitrate ? (*ext_params.max_bitrate) * kBitsPerKilobit
+ : media::cast::kDefaultAudioEncoderBitrate;
+ config->max_frame_rate = 100; // 10ms audio frames.
+ config->codec = media::cast::CODEC_AUDIO_OPUS;
+ } else if (ext_params.codec_name == kCodecNameVp8 ||
+ ext_params.codec_name == kCodecNameH264) {
+ config->rtp_timebase = media::cast::kVideoFrequency;
+ config->channels = ext_params.channels ? *ext_params.channels : 1;
+ if (config->channels != 1) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ DVLOG(1) << "channels " << config->channels << " is invalid";
+ return false;
+ }
+ config->min_bitrate = ext_params.min_bitrate
+ ? (*ext_params.min_bitrate) * kBitsPerKilobit
+ : media::cast::kDefaultMinVideoBitrate;
+ config->max_bitrate = ext_params.max_bitrate
+ ? (*ext_params.max_bitrate) * kBitsPerKilobit
+ : media::cast::kDefaultMaxVideoBitrate;
+ if (config->min_bitrate > config->max_bitrate) {
+ DVLOG(1) << "min_bitrate " << config->min_bitrate << " is larger than "
+ << "max_bitrate " << config->max_bitrate;
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ config->start_bitrate = config->min_bitrate;
+ config->max_frame_rate = std::max(
+ 1.0, ext_params.max_frame_rate ? *ext_params.max_frame_rate : 0.0);
+ if (config->max_frame_rate > media::limits::kMaxFramesPerSecond) {
+ DVLOG(1) << "max_frame_rate " << config->max_frame_rate << " is invalid";
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (ext_params.codec_name == kCodecNameVp8) {
+ config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_VP8;
+ config->codec = media::cast::CODEC_VIDEO_VP8;
+ config->use_external_encoder =
+ CastRtpStream::IsHardwareVP8EncodingSupported();
+ } else {
+ config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_H264;
+ config->codec = media::cast::CODEC_VIDEO_H264;
+ config->use_external_encoder =
+ CastRtpStream::IsHardwareH264EncodingSupported();
+ }
+ if (!config->use_external_encoder)
+ config->video_codec_params.number_of_encode_threads =
+ NumberOfEncodeThreads();
+ } else if (ext_params.codec_name == kCodecNameRemoteAudio) {
+ config->rtp_payload_type = media::cast::RtpPayloadType::REMOTE_AUDIO;
+ config->codec = media::cast::CODEC_AUDIO_REMOTE;
+ } else if (ext_params.codec_name == kCodecNameRemoteVideo) {
+ config->rtp_payload_type = media::cast::RtpPayloadType::REMOTE_VIDEO;
+ config->codec = media::cast::CODEC_VIDEO_REMOTE;
+ } else {
+ DVLOG(1) << "codec_name " << ext_params.codec_name << " is invalid";
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (ext_params.aes_key && !HexDecode(*ext_params.aes_key, &config->aes_key)) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidAesKey,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (ext_params.aes_iv_mask &&
+ !HexDecode(*ext_params.aes_iv_mask, &config->aes_iv_mask)) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidAesIvMask,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ return true;
+}
+
+void FromFrameSenderConfig(const FrameSenderConfig& config,
+ RtpPayloadParams* ext_params) {
+ ext_params->payload_type = static_cast<int>(config.rtp_payload_type);
+ ext_params->max_latency = config.max_playout_delay.InMilliseconds();
+ ext_params->min_latency.reset(
+ new int(config.min_playout_delay.InMilliseconds()));
+ ext_params->animated_latency.reset(
+ new int(config.animated_playout_delay.InMilliseconds()));
+ switch (config.codec) {
+ case media::cast::CODEC_AUDIO_OPUS:
+ ext_params->codec_name = kCodecNameOpus;
+ break;
+ case media::cast::CODEC_VIDEO_VP8:
+ ext_params->codec_name = kCodecNameVp8;
+ break;
+ case media::cast::CODEC_VIDEO_H264:
+ ext_params->codec_name = kCodecNameH264;
+ break;
+ case media::cast::CODEC_AUDIO_REMOTE:
+ ext_params->codec_name = kCodecNameRemoteAudio;
+ break;
+ case media::cast::CODEC_VIDEO_REMOTE:
+ ext_params->codec_name = kCodecNameRemoteVideo;
+ break;
+ default:
+ NOTREACHED();
+ }
+ ext_params->ssrc = config.sender_ssrc;
+ ext_params->feedback_ssrc = config.receiver_ssrc;
+ if (config.rtp_timebase)
+ ext_params->clock_rate.reset(new int(config.rtp_timebase));
+ if (config.min_bitrate)
+ ext_params->min_bitrate.reset(
+ new int(config.min_bitrate / kBitsPerKilobit));
+ if (config.max_bitrate)
+ ext_params->max_bitrate.reset(
+ new int(config.max_bitrate / kBitsPerKilobit));
+ if (config.channels)
+ ext_params->channels.reset(new int(config.channels));
+ if (config.max_frame_rate > 0.0)
+ ext_params->max_frame_rate.reset(new double(config.max_frame_rate));
+}
+
+} // namespace
+
+// |last_transport_id_| is the identifier for the next created RTP stream. To
+// create globally unique IDs used for referring to RTP stream objects in
+// browser process, we set its higher 16 bits as HASH(extension_id)&0x7fff, and
+// lower 16 bits as the sequence number of the RTP stream created in the same
+// extension. Collision will happen when the first RTP stream keeps alive after
+// creating another 64k-1 RTP streams in the same extension, which is very
+// unlikely to happen in normal use cases.
+CastStreamingNativeHandler::CastStreamingNativeHandler(
+ ScriptContext* context,
+ NativeExtensionBindingsSystem* bindings_system)
+ : ObjectBackedNativeHandler(context),
+ last_transport_id_(
+ context->extension()
+ ? (((base::Hash(context->extension()->id()) & 0x7fff) << 16) + 1)
+ : 1),
+ bindings_system_(bindings_system) {}
+
+CastStreamingNativeHandler::~CastStreamingNativeHandler() {
+ // Note: A superclass's destructor will call Invalidate(), but Invalidate()
+ // may also be called at any time before destruction.
+}
+
+void CastStreamingNativeHandler::AddRoutes() {
+ RouteHandlerFunction(
+ "CreateSession", "cast.streaming.session",
+ base::BindRepeating(&CastStreamingNativeHandler::CreateCastSession,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "DestroyCastRtpStream", "cast.streaming.rtpStream",
+ base::BindRepeating(&CastStreamingNativeHandler::DestroyCastRtpStream,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "GetSupportedParamsCastRtpStream", "cast.streaming.rtpStream",
+ base::BindRepeating(
+ &CastStreamingNativeHandler::GetSupportedParamsCastRtpStream,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "StartCastRtpStream", "cast.streaming.rtpStream",
+ base::BindRepeating(&CastStreamingNativeHandler::StartCastRtpStream,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "StopCastRtpStream", "cast.streaming.rtpStream",
+ base::BindRepeating(&CastStreamingNativeHandler::StopCastRtpStream,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "DestroyCastUdpTransport", "cast.streaming.udpTransport",
+ base::BindRepeating(&CastStreamingNativeHandler::DestroyCastUdpTransport,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "SetDestinationCastUdpTransport", "cast.streaming.udpTransport",
+ base::BindRepeating(
+ &CastStreamingNativeHandler::SetDestinationCastUdpTransport,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "SetOptionsCastUdpTransport", "cast.streaming.udpTransport",
+ base::BindRepeating(
+ &CastStreamingNativeHandler::SetOptionsCastUdpTransport,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "ToggleLogging", "cast.streaming.rtpStream",
+ base::BindRepeating(&CastStreamingNativeHandler::ToggleLogging,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "GetRawEvents", "cast.streaming.rtpStream",
+ base::BindRepeating(&CastStreamingNativeHandler::GetRawEvents,
+ weak_factory_.GetWeakPtr()));
+ RouteHandlerFunction(
+ "GetStats", "cast.streaming.rtpStream",
+ base::BindRepeating(&CastStreamingNativeHandler::GetStats,
+ weak_factory_.GetWeakPtr()));
+}
+
+void CastStreamingNativeHandler::Invalidate() {
+ // Cancel all function call routing and callbacks.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Clear all references to V8 and Cast objects, which will trigger
+ // auto-destructions (effectively stopping all sessions).
+ get_stats_callbacks_.clear();
+ get_raw_events_callbacks_.clear();
+ create_callback_.Reset();
+ udp_transport_map_.clear();
+ rtp_stream_map_.clear();
+
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+void CastStreamingNativeHandler::CreateCastSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(3, args.Length());
+ CHECK(args[2]->IsFunction());
+
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+
+ auto session = base::MakeRefCounted<CastSession>(
+ context()->web_frame()->GetTaskRunner(blink::TaskType::kInternalMedia));
+ std::unique_ptr<CastRtpStream> stream1, stream2;
+ if ((args[0]->IsNull() || args[0]->IsUndefined()) &&
+ (args[1]->IsNull() || args[1]->IsUndefined())) {
+ DVLOG(3) << "CreateCastSession for remoting.";
+ // Creates audio/video RTP streams for media remoting.
+ stream1.reset(new CastRtpStream(true, session));
+ stream2.reset(new CastRtpStream(false, session));
+ } else {
+ // Creates RTP streams that consume from an audio and/or a video
+ // MediaStreamTrack.
+ if (!args[0]->IsNull() && !args[0]->IsUndefined()) {
+ CHECK(args[0]->IsObject());
+ blink::WebDOMMediaStreamTrack track =
+ blink::WebDOMMediaStreamTrack::FromV8Value(args[0]);
+ if (track.IsNull()) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return;
+ }
+ stream1.reset(new CastRtpStream(track.Component(), session));
+ }
+ if (!args[1]->IsNull() && !args[1]->IsUndefined()) {
+ CHECK(args[1]->IsObject());
+ blink::WebDOMMediaStreamTrack track =
+ blink::WebDOMMediaStreamTrack::FromV8Value(args[1]);
+ if (track.IsNull()) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return;
+ }
+ stream2.reset(new CastRtpStream(track.Component(), session));
+ }
+ }
+ std::unique_ptr<CastUdpTransport> udp_transport(
+ new CastUdpTransport(session));
+
+ create_callback_.Reset(isolate, args[2].As<v8::Function>());
+
+ context()
+ ->web_frame()
+ ->GetTaskRunner(blink::TaskType::kInternalMedia)
+ ->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CastStreamingNativeHandler::CallCreateCallback,
+ weak_factory_.GetWeakPtr(), base::Passed(&stream1),
+ base::Passed(&stream2), base::Passed(&udp_transport)));
+}
+
+void CastStreamingNativeHandler::CallCreateCallback(
+ std::unique_ptr<CastRtpStream> stream1,
+ std::unique_ptr<CastRtpStream> stream2,
+ std::unique_ptr<CastUdpTransport> udp_transport) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ v8::Local<v8::Value> callback_args[3];
+ callback_args[0] = v8::Null(isolate);
+ callback_args[1] = v8::Null(isolate);
+
+ if (stream1) {
+ const int stream1_id = last_transport_id_++;
+ callback_args[0] = v8::Integer::New(isolate, stream1_id);
+ rtp_stream_map_[stream1_id] = std::move(stream1);
+ }
+ if (stream2) {
+ const int stream2_id = last_transport_id_++;
+ callback_args[1] = v8::Integer::New(isolate, stream2_id);
+ rtp_stream_map_[stream2_id] = std::move(stream2);
+ }
+ const int udp_id = last_transport_id_++;
+ udp_transport_map_[udp_id] = std::move(udp_transport);
+ callback_args[2] = v8::Integer::New(isolate, udp_id);
+ context()->SafeCallFunction(
+ v8::Local<v8::Function>::New(isolate, create_callback_), 3,
+ callback_args);
+ create_callback_.Reset();
+}
+
+void CastStreamingNativeHandler::CallStartCallback(int stream_id) const {
+ base::ListValue event_args;
+ event_args.AppendInteger(stream_id);
+ bindings_system_->DispatchEventInContext("cast.streaming.rtpStream.onStarted",
+ &event_args, nullptr, context());
+}
+
+void CastStreamingNativeHandler::CallStopCallback(int stream_id) const {
+ base::ListValue event_args;
+ event_args.AppendInteger(stream_id);
+ bindings_system_->DispatchEventInContext("cast.streaming.rtpStream.onStopped",
+ &event_args, nullptr, context());
+}
+
+void CastStreamingNativeHandler::CallErrorCallback(
+ int stream_id,
+ const std::string& message) const {
+ base::ListValue event_args;
+ event_args.AppendInteger(stream_id);
+ event_args.AppendString(message);
+ bindings_system_->DispatchEventInContext("cast.streaming.rtpStream.onError",
+ &event_args, nullptr, context());
+}
+
+void CastStreamingNativeHandler::DestroyCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ if (!GetRtpStreamOrThrow(transport_id))
+ return;
+ rtp_stream_map_.erase(transport_id);
+}
+
+void CastStreamingNativeHandler::GetSupportedParamsCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args) const {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
+ if (!transport)
+ return;
+
+ std::unique_ptr<V8ValueConverter> converter = V8ValueConverter::Create();
+ std::vector<FrameSenderConfig> configs = transport->GetSupportedConfigs();
+ v8::Local<v8::Array> result =
+ v8::Array::New(args.GetIsolate(), static_cast<int>(configs.size()));
+ for (size_t i = 0; i < configs.size(); ++i) {
+ RtpParams params;
+ FromFrameSenderConfig(configs[i], &params.payload);
+ std::unique_ptr<base::DictionaryValue> params_value = params.ToValue();
+ result
+ ->CreateDataProperty(
+ context()->v8_context(), static_cast<int>(i),
+ converter->ToV8Value(params_value.get(), context()->v8_context()))
+ .Check();
+ }
+ args.GetReturnValue().Set(result);
+}
+
+void CastStreamingNativeHandler::StartCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsObject());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
+ if (!transport)
+ return;
+
+ std::unique_ptr<base::Value> params_value =
+ V8ValueConverter::Create()->FromV8Value(args[1], context()->v8_context());
+ if (!params_value) {
+ args.GetIsolate()->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return;
+ }
+ std::unique_ptr<RtpParams> params = RtpParams::FromValue(*params_value);
+ if (!params) {
+ args.GetIsolate()->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(args.GetIsolate(), kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return;
+ }
+
+ FrameSenderConfig config;
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ if (!ToFrameSenderConfigOrThrow(isolate, params->payload, &config))
+ return;
+
+ base::Closure start_callback =
+ base::Bind(&CastStreamingNativeHandler::CallStartCallback,
+ weak_factory_.GetWeakPtr(),
+ transport_id);
+ base::Closure stop_callback =
+ base::Bind(&CastStreamingNativeHandler::CallStopCallback,
+ weak_factory_.GetWeakPtr(),
+ transport_id);
+ CastRtpStream::ErrorCallback error_callback =
+ base::Bind(&CastStreamingNativeHandler::CallErrorCallback,
+ weak_factory_.GetWeakPtr(),
+ transport_id);
+
+ // |transport_id| is a globally unique identifier for the RTP stream.
+ transport->Start(transport_id, config, start_callback, stop_callback,
+ error_callback);
+}
+
+void CastStreamingNativeHandler::StopCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
+ if (!transport)
+ return;
+ transport->Stop();
+}
+
+void CastStreamingNativeHandler::DestroyCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ if (!GetUdpTransportOrThrow(transport_id))
+ return;
+ udp_transport_map_.erase(transport_id);
+}
+
+void CastStreamingNativeHandler::SetDestinationCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsObject());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ CastUdpTransport* transport = GetUdpTransportOrThrow(transport_id);
+ if (!transport)
+ return;
+
+ net::IPEndPoint dest;
+ if (!IPEndPointFromArg(args.GetIsolate(),
+ args[1],
+ &dest)) {
+ return;
+ }
+ transport->SetDestination(
+ dest,
+ base::Bind(&CastStreamingNativeHandler::CallErrorCallback,
+ weak_factory_.GetWeakPtr(),
+ transport_id));
+}
+
+void CastStreamingNativeHandler::SetOptionsCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsObject());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ CastUdpTransport* transport = GetUdpTransportOrThrow(transport_id);
+ if (!transport)
+ return;
+
+ std::unique_ptr<base::DictionaryValue> options =
+ base::DictionaryValue::From(V8ValueConverter::Create()->FromV8Value(
+ args[1], context()->v8_context()));
+ if (!options) {
+ args.GetIsolate()->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return;
+ }
+ transport->SetOptions(std::move(options));
+}
+
+void CastStreamingNativeHandler::ToggleLogging(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsBoolean());
+
+ const int stream_id = args[0].As<v8::Int32>()->Value();
+ CastRtpStream* stream = GetRtpStreamOrThrow(stream_id);
+ if (!stream)
+ return;
+
+ const bool enable = args[1]->ToBoolean(args.GetIsolate())->Value();
+ stream->ToggleLogging(enable);
+}
+
+void CastStreamingNativeHandler::GetRawEvents(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(3, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsNull() || args[1]->IsString());
+ CHECK(args[2]->IsFunction());
+
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ v8::Isolate* isolate = args.GetIsolate();
+ v8::Global<v8::Function> callback(isolate, args[2].As<v8::Function>());
+ std::string extra_data;
+ if (!args[1]->IsNull()) {
+ extra_data = *v8::String::Utf8Value(isolate, args[1]);
+ }
+
+ CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
+ if (!transport)
+ return;
+
+ get_raw_events_callbacks_.insert(
+ std::make_pair(transport_id, std::move(callback)));
+
+ transport->GetRawEvents(
+ base::Bind(&CastStreamingNativeHandler::CallGetRawEventsCallback,
+ weak_factory_.GetWeakPtr(),
+ transport_id),
+ extra_data);
+}
+
+void CastStreamingNativeHandler::GetStats(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsFunction());
+ const int transport_id = args[0].As<v8::Int32>()->Value();
+ CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
+ if (!transport)
+ return;
+
+ v8::Global<v8::Function> callback(args.GetIsolate(),
+ args[1].As<v8::Function>());
+ get_stats_callbacks_.insert(
+ std::make_pair(transport_id, std::move(callback)));
+
+ transport->GetStats(
+ base::Bind(&CastStreamingNativeHandler::CallGetStatsCallback,
+ weak_factory_.GetWeakPtr(),
+ transport_id));
+}
+
+void CastStreamingNativeHandler::CallGetRawEventsCallback(
+ int transport_id,
+ std::unique_ptr<base::Value> raw_events) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ auto it = get_raw_events_callbacks_.find(transport_id);
+ if (it == get_raw_events_callbacks_.end())
+ return;
+ v8::Local<v8::Value> callback_args[] = {V8ValueConverter::Create()->ToV8Value(
+ raw_events.get(), context()->v8_context())};
+ context()->SafeCallFunction(v8::Local<v8::Function>::New(isolate, it->second),
+ base::size(callback_args), callback_args);
+ get_raw_events_callbacks_.erase(it);
+}
+
+void CastStreamingNativeHandler::CallGetStatsCallback(
+ int transport_id,
+ std::unique_ptr<base::DictionaryValue> stats) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ auto it = get_stats_callbacks_.find(transport_id);
+ if (it == get_stats_callbacks_.end())
+ return;
+
+ v8::Local<v8::Value> callback_args[] = {V8ValueConverter::Create()->ToV8Value(
+ stats.get(), context()->v8_context())};
+ context()->SafeCallFunction(v8::Local<v8::Function>::New(isolate, it->second),
+ base::size(callback_args), callback_args);
+ get_stats_callbacks_.erase(it);
+}
+
+CastRtpStream* CastStreamingNativeHandler::GetRtpStreamOrThrow(
+ int transport_id) const {
+ auto iter = rtp_stream_map_.find(transport_id);
+ if (iter != rtp_stream_map_.end())
+ return iter->second.get();
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ isolate->ThrowException(v8::Exception::RangeError(
+ v8::String::NewFromUtf8(isolate, kRtpStreamNotFound,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return NULL;
+}
+
+CastUdpTransport* CastStreamingNativeHandler::GetUdpTransportOrThrow(
+ int transport_id) const {
+ auto iter = udp_transport_map_.find(transport_id);
+ if (iter != udp_transport_map_.end())
+ return iter->second.get();
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ isolate->ThrowException(v8::Exception::RangeError(
+ v8::String::NewFromUtf8(isolate, kUdpTransportNotFound,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return NULL;
+}
+
+bool CastStreamingNativeHandler::FrameReceiverConfigFromArg(
+ v8::Isolate* isolate,
+ const v8::Local<v8::Value>& arg,
+ media::cast::FrameReceiverConfig* config) const {
+ std::unique_ptr<base::Value> params_value =
+ V8ValueConverter::Create()->FromV8Value(arg, context()->v8_context());
+ if (!params_value) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kUnableToConvertParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ std::unique_ptr<RtpReceiverParams> params =
+ RtpReceiverParams::FromValue(*params_value);
+ if (!params) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpParams,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+
+ config->receiver_ssrc = params->receiver_ssrc;
+ config->sender_ssrc = params->sender_ssrc;
+ config->rtp_max_delay_ms = params->max_latency;
+ if (config->rtp_max_delay_ms < 0 || config->rtp_max_delay_ms > 1000) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kInvalidLatency,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ config->channels = 2;
+ if (params->codec_name == "OPUS") {
+ config->codec = media::cast::CODEC_AUDIO_OPUS;
+ config->rtp_timebase = 48000;
+ config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_OPUS;
+ } else if (params->codec_name == "PCM16") {
+ config->codec = media::cast::CODEC_AUDIO_PCM16;
+ config->rtp_timebase = 48000;
+ config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_PCM16;
+ } else if (params->codec_name == "AAC") {
+ config->codec = media::cast::CODEC_AUDIO_AAC;
+ config->rtp_timebase = 48000;
+ config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_AAC;
+ } else if (params->codec_name == "VP8") {
+ config->codec = media::cast::CODEC_VIDEO_VP8;
+ config->rtp_timebase = 90000;
+ config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_VP8;
+ } else if (params->codec_name == "H264") {
+ config->codec = media::cast::CODEC_VIDEO_H264;
+ config->rtp_timebase = 90000;
+ config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_H264;
+ }
+ if (params->rtp_timebase) {
+ config->rtp_timebase = *params->rtp_timebase;
+ if (config->rtp_timebase < 1000 || config->rtp_timebase > 1000000) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kInvalidRtpTimebase,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ }
+ if (params->aes_key &&
+ !HexDecode(*params->aes_key, &config->aes_key)) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidAesKey,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ if (params->aes_iv_mask &&
+ !HexDecode(*params->aes_iv_mask, &config->aes_iv_mask)) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidAesIvMask,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ return true;
+}
+
+bool CastStreamingNativeHandler::IPEndPointFromArg(
+ v8::Isolate* isolate,
+ const v8::Local<v8::Value>& arg,
+ net::IPEndPoint* ip_endpoint) const {
+ std::unique_ptr<base::Value> destination_value =
+ V8ValueConverter::Create()->FromV8Value(arg, context()->v8_context());
+ if (!destination_value) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kInvalidAesIvMask,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ std::unique_ptr<IPEndPoint> destination =
+ IPEndPoint::FromValue(*destination_value);
+ if (!destination) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kInvalidDestination,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ net::IPAddress ip;
+ if (!ip.AssignFromIPLiteral(destination->address)) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ v8::String::NewFromUtf8(isolate, kInvalidDestination,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+ return false;
+ }
+ *ip_endpoint = net::IPEndPoint(ip, destination->port);
+ return true;
+}
+
+void CastStreamingNativeHandler::CallReceiverErrorCallback(
+ v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent function,
+ const std::string& error_message) {
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ v8::Local<v8::Value> arg =
+ v8::String::NewFromUtf8(isolate, error_message.data(),
+ v8::NewStringType::kNormal, error_message.size())
+ .ToLocalChecked();
+ context()->SafeCallFunction(v8::Local<v8::Function>::New(isolate, function),
+ 1, &arg);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/cast_streaming_native_handler.h b/chromium/chrome/renderer/extensions/cast_streaming_native_handler.h
new file mode 100644
index 00000000000..6fdcc7b2a3a
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/cast_streaming_native_handler.h
@@ -0,0 +1,142 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_CAST_STREAMING_NATIVE_HANDLER_H_
+#define CHROME_RENDERER_EXTENSIONS_CAST_STREAMING_NATIVE_HANDLER_H_
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+class CastRtpStream;
+class CastUdpTransport;
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+namespace net {
+class IPEndPoint;
+}
+
+namespace media {
+namespace cast {
+struct FrameReceiverConfig;
+}
+}
+
+namespace extensions {
+class NativeExtensionBindingsSystem;
+
+// Native code that handle chrome.webrtc custom bindings.
+class CastStreamingNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ CastStreamingNativeHandler(ScriptContext* context,
+ NativeExtensionBindingsSystem* bindings_system);
+ ~CastStreamingNativeHandler() override;
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ protected:
+ // Shut down all sessions and cancel any in-progress operations because the
+ // ScriptContext is about to become invalid.
+ void Invalidate() override;
+
+ private:
+ void CreateCastSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void DestroyCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void CreateParamsCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetSupportedParamsCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args) const;
+ void StartCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void StopCastRtpStream(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void DestroyCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void SetDestinationCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void SetOptionsCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void StopCastUdpTransport(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void ToggleLogging(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetRawEvents(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetStats(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Helper method to call the v8 callback function after a session is
+ // created.
+ void CallCreateCallback(std::unique_ptr<CastRtpStream> stream1,
+ std::unique_ptr<CastRtpStream> stream2,
+ std::unique_ptr<CastUdpTransport> udp_transport);
+
+ void CallStartCallback(int stream_id) const;
+ void CallStopCallback(int stream_id) const;
+ void CallErrorCallback(int stream_id, const std::string& message) const;
+
+ // |function| is a javascript function that will take |error_message| as
+ // an argument. Called when something goes wrong in a cast receiver.
+ void CallReceiverErrorCallback(
+ v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent function,
+ const std::string& error_message);
+
+ void CallGetRawEventsCallback(int transport_id,
+ std::unique_ptr<base::Value> raw_events);
+ void CallGetStatsCallback(int transport_id,
+ std::unique_ptr<base::DictionaryValue> stats);
+
+ // Gets the RTP stream or UDP transport indexed by an ID.
+ // If not found, returns NULL and throws a V8 exception.
+ CastRtpStream* GetRtpStreamOrThrow(int stream_id) const;
+ CastUdpTransport* GetUdpTransportOrThrow(int transport_id) const;
+
+ // Fills out a media::cast::FrameReceiverConfig from the v8
+ // equivialent. (cast.streaming.receiverSession.RtpReceiverParams)
+ // Returns true if everything was ok, raises a v8 exception and
+ // returns false if anything went wrong.
+ bool FrameReceiverConfigFromArg(
+ v8::Isolate* isolate,
+ const v8::Local<v8::Value>& arg,
+ media::cast::FrameReceiverConfig* config) const;
+
+ bool IPEndPointFromArg(v8::Isolate* isolate,
+ const v8::Local<v8::Value>& arg,
+ net::IPEndPoint* ip_endpoint) const;
+
+ int last_transport_id_;
+
+ using RtpStreamMap = std::map<int, std::unique_ptr<CastRtpStream>>;
+ RtpStreamMap rtp_stream_map_;
+
+ using UdpTransportMap = std::map<int, std::unique_ptr<CastUdpTransport>>;
+ UdpTransportMap udp_transport_map_;
+
+ v8::Global<v8::Function> create_callback_;
+
+ using RtpStreamCallbackMap = std::map<int, v8::Global<v8::Function>>;
+ RtpStreamCallbackMap get_raw_events_callbacks_;
+ RtpStreamCallbackMap get_stats_callbacks_;
+
+ NativeExtensionBindingsSystem* bindings_system_;
+
+ base::WeakPtrFactory<CastStreamingNativeHandler> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(CastStreamingNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_CAST_STREAMING_NATIVE_HANDLER_H_
diff --git a/chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
new file mode 100644
index 00000000000..7890cd1d223
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
@@ -0,0 +1,278 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/crash_keys.h"
+#include "chrome/grit/renderer_resources.h"
+#include "chrome/renderer/extensions/accessibility_private_hooks_delegate.h"
+#include "chrome/renderer/extensions/app_hooks_delegate.h"
+#include "chrome/renderer/extensions/cast_streaming_native_handler.h"
+#include "chrome/renderer/extensions/extension_hooks_delegate.h"
+#include "chrome/renderer/extensions/media_galleries_custom_bindings.h"
+#include "chrome/renderer/extensions/notifications_native_handler.h"
+#include "chrome/renderer/extensions/page_capture_custom_bindings.h"
+#include "chrome/renderer/extensions/sync_file_system_custom_bindings.h"
+#include "chrome/renderer/extensions/tabs_hooks_delegate.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/permissions/manifest_permission_set.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/bindings/api_bindings_system.h"
+#include "extensions/renderer/css_native_handler.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/lazy_background_page_native_handler.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_handler.h"
+#include "extensions/renderer/resource_bundle_source_map.h"
+#include "extensions/renderer/script_context.h"
+#include "media/media_buildflags.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_security_policy.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/renderer/extensions/file_browser_handler_custom_bindings.h"
+#include "chrome/renderer/extensions/file_manager_private_custom_bindings.h"
+#include "chrome/renderer/extensions/platform_keys_natives.h"
+#endif
+
+using extensions::NativeHandler;
+
+ChromeExtensionsDispatcherDelegate::ChromeExtensionsDispatcherDelegate() {}
+
+ChromeExtensionsDispatcherDelegate::~ChromeExtensionsDispatcherDelegate() {}
+
+void ChromeExtensionsDispatcherDelegate::RegisterNativeHandlers(
+ extensions::Dispatcher* dispatcher,
+ extensions::ModuleSystem* module_system,
+ extensions::NativeExtensionBindingsSystem* bindings_system,
+ extensions::ScriptContext* context) {
+ module_system->RegisterNativeHandler(
+ "sync_file_system",
+ std::unique_ptr<NativeHandler>(
+ new extensions::SyncFileSystemCustomBindings(context)));
+#if defined(OS_CHROMEOS)
+ module_system->RegisterNativeHandler(
+ "file_browser_handler",
+ std::unique_ptr<NativeHandler>(
+ new extensions::FileBrowserHandlerCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "file_manager_private",
+ std::unique_ptr<NativeHandler>(
+ new extensions::FileManagerPrivateCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "platform_keys_natives",
+ std::unique_ptr<NativeHandler>(
+ new extensions::PlatformKeysNatives(context)));
+#endif // defined(OS_CHROMEOS)
+ module_system->RegisterNativeHandler(
+ "notifications_private",
+ std::unique_ptr<NativeHandler>(
+ new extensions::NotificationsNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "mediaGalleries",
+ std::unique_ptr<NativeHandler>(
+ new extensions::MediaGalleriesCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "page_capture", std::unique_ptr<NativeHandler>(
+ new extensions::PageCaptureCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "cast_streaming_natives",
+ std::make_unique<extensions::CastStreamingNativeHandler>(
+ context, bindings_system));
+
+ // The following are native handlers that are defined in //extensions, but
+ // are only used for APIs defined in Chrome.
+ // TODO(devlin): We should clean this up. If an API is defined in Chrome,
+ // there's no reason to have its native handlers residing and being compiled
+ // in //extensions.
+ module_system->RegisterNativeHandler(
+ "lazy_background_page",
+ std::unique_ptr<NativeHandler>(
+ new extensions::LazyBackgroundPageNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "css_natives", std::unique_ptr<NativeHandler>(
+ new extensions::CssNativeHandler(context)));
+}
+
+void ChromeExtensionsDispatcherDelegate::PopulateSourceMap(
+ extensions::ResourceBundleSourceMap* source_map) {
+ // Custom bindings.
+ source_map->RegisterSource("action", IDR_ACTION_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("browserAction",
+ IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("declarativeContent",
+ IDR_DECLARATIVE_CONTENT_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("desktopCapture",
+ IDR_DESKTOP_CAPTURE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("developerPrivate",
+ IDR_DEVELOPER_PRIVATE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("downloads", IDR_DOWNLOADS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("gcm", IDR_GCM_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("identity", IDR_IDENTITY_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("imageWriterPrivate",
+ IDR_IMAGE_WRITER_PRIVATE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("input.ime", IDR_INPUT_IME_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("mediaGalleries",
+ IDR_MEDIA_GALLERIES_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("notifications",
+ IDR_NOTIFICATIONS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("omnibox", IDR_OMNIBOX_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("pageAction", IDR_PAGE_ACTION_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("pageCapture",
+ IDR_PAGE_CAPTURE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("syncFileSystem",
+ IDR_SYNC_FILE_SYSTEM_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("systemIndicator",
+ IDR_SYSTEM_INDICATOR_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("tabCapture", IDR_TAB_CAPTURE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("tts", IDR_TTS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("ttsEngine", IDR_TTS_ENGINE_CUSTOM_BINDINGS_JS);
+
+#if defined(OS_CHROMEOS)
+ source_map->RegisterSource("certificateProvider",
+ IDR_CERTIFICATE_PROVIDER_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("enterprise.platformKeys",
+ IDR_ENTERPRISE_PLATFORM_KEYS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("enterprise.platformKeys.internalAPI",
+ IDR_ENTERPRISE_PLATFORM_KEYS_INTERNAL_API_JS);
+ source_map->RegisterSource("enterprise.platformKeys.KeyPair",
+ IDR_ENTERPRISE_PLATFORM_KEYS_KEY_PAIR_JS);
+ source_map->RegisterSource("enterprise.platformKeys.SubtleCrypto",
+ IDR_ENTERPRISE_PLATFORM_KEYS_SUBTLE_CRYPTO_JS);
+ source_map->RegisterSource("enterprise.platformKeys.Token",
+ IDR_ENTERPRISE_PLATFORM_KEYS_TOKEN_JS);
+ source_map->RegisterSource("fileBrowserHandler",
+ IDR_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("fileManagerPrivate",
+ IDR_FILE_MANAGER_PRIVATE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("fileSystemProvider",
+ IDR_FILE_SYSTEM_PROVIDER_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("platformKeys",
+ IDR_PLATFORM_KEYS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("platformKeys.getPublicKey",
+ IDR_PLATFORM_KEYS_GET_PUBLIC_KEY_JS);
+ source_map->RegisterSource("platformKeys.internalAPI",
+ IDR_PLATFORM_KEYS_INTERNAL_API_JS);
+ source_map->RegisterSource("platformKeys.Key", IDR_PLATFORM_KEYS_KEY_JS);
+ source_map->RegisterSource("platformKeys.SubtleCrypto",
+ IDR_PLATFORM_KEYS_SUBTLE_CRYPTO_JS);
+ source_map->RegisterSource("platformKeys.utils", IDR_PLATFORM_KEYS_UTILS_JS);
+ source_map->RegisterSource("terminalPrivate",
+ IDR_TERMINAL_PRIVATE_CUSTOM_BINDINGS_JS);
+
+ // IME service on Chrome OS.
+ source_map->RegisterSource("chromeos.ime.mojom.input_engine.mojom",
+ IDR_IME_SERVICE_MOJOM_JS);
+ source_map->RegisterSource("chromeos.ime.service",
+ IDR_IME_SERVICE_BINDINGS_JS);
+#endif // defined(OS_CHROMEOS)
+
+ source_map->RegisterSource("cast.streaming.rtpStream",
+ IDR_CAST_STREAMING_RTP_STREAM_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("cast.streaming.session",
+ IDR_CAST_STREAMING_SESSION_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource(
+ "cast.streaming.udpTransport",
+ IDR_CAST_STREAMING_UDP_TRANSPORT_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource(
+ "cast.streaming.receiverSession",
+ IDR_CAST_STREAMING_RECEIVER_SESSION_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource(
+ "webrtcDesktopCapturePrivate",
+ IDR_WEBRTC_DESKTOP_CAPTURE_PRIVATE_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("webrtcLoggingPrivate",
+ IDR_WEBRTC_LOGGING_PRIVATE_CUSTOM_BINDINGS_JS);
+
+ // Platform app sources that are not API-specific..
+ source_map->RegisterSource("chromeWebViewInternal",
+ IDR_CHROME_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("chromeWebView", IDR_CHROME_WEB_VIEW_JS);
+
+ // Media router.
+ source_map->RegisterSource(
+ "chrome/common/media_router/mojom/media_controller.mojom",
+ IDR_MEDIA_CONTROLLER_MOJOM_JS);
+ source_map->RegisterSource(
+ "chrome/common/media_router/mojom/media_router.mojom",
+ IDR_MEDIA_ROUTER_MOJOM_JS);
+ source_map->RegisterSource(
+ "chrome/common/media_router/mojom/media_status.mojom",
+ IDR_MEDIA_STATUS_MOJOM_JS);
+ source_map->RegisterSource("media_router_bindings",
+ IDR_MEDIA_ROUTER_BINDINGS_JS);
+ source_map->RegisterSource("mojo/public/mojom/base/time.mojom",
+ IDR_MOJO_TIME_MOJOM_JS);
+ source_map->RegisterSource("mojo/public/mojom/base/unguessable_token.mojom",
+ IDR_MOJO_UNGUESSABLE_TOKEN_MOJOM_JS);
+ source_map->RegisterSource("net/interfaces/ip_address.mojom",
+ IDR_MOJO_IP_ADDRESS_MOJOM_JS);
+ source_map->RegisterSource("net/interfaces/ip_endpoint.mojom",
+ IDR_MOJO_IP_ENDPOINT_MOJOM_JS);
+ source_map->RegisterSource("url/mojom/origin.mojom", IDR_ORIGIN_MOJOM_JS);
+ source_map->RegisterSource("url/mojom/url.mojom", IDR_MOJO_URL_MOJOM_JS);
+ source_map->RegisterSource("media/mojo/mojom/remoting_common.mojom",
+ IDR_REMOTING_COMMON_JS);
+ source_map->RegisterSource(
+ "media/mojo/mojom/mirror_service_remoting.mojom",
+ IDR_MEDIA_REMOTING_JS);
+ source_map->RegisterSource(
+ "components/mirroring/mojom/mirroring_service_host.mojom",
+ IDR_MIRRORING_SERVICE_HOST_MOJOM_JS);
+ source_map->RegisterSource(
+ "components/mirroring/mojom/cast_message_channel.mojom",
+ IDR_MIRRORING_CAST_MESSAGE_CHANNEL_MOJOM_JS);
+ source_map->RegisterSource(
+ "components/mirroring/mojom/session_observer.mojom",
+ IDR_MIRRORING_SESSION_OBSERVER_MOJOM_JS);
+ source_map->RegisterSource(
+ "components/mirroring/mojom/session_parameters.mojom",
+ IDR_MIRRORING_SESSION_PARAMETERS_JS);
+}
+
+void ChromeExtensionsDispatcherDelegate::RequireWebViewModules(
+ extensions::ScriptContext* context) {
+ DCHECK(context->GetAvailability("webViewInternal").is_available());
+ context->module_system()->Require("chromeWebView");
+}
+
+void ChromeExtensionsDispatcherDelegate::OnActiveExtensionsUpdated(
+ const std::set<std::string>& extension_ids) {
+ // In single-process mode, the browser process reports the active extensions.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kSingleProcess))
+ return;
+ crash_keys::SetActiveExtensions(extension_ids);
+}
+
+void ChromeExtensionsDispatcherDelegate::InitializeBindingsSystem(
+ extensions::Dispatcher* dispatcher,
+ extensions::NativeExtensionBindingsSystem* bindings_system) {
+ extensions::APIBindingsSystem* bindings = bindings_system->api_system();
+ bindings->GetHooksForAPI("app")->SetDelegate(
+ std::make_unique<extensions::AppHooksDelegate>(
+ dispatcher, bindings->request_handler()));
+ bindings->GetHooksForAPI("extension")
+ ->SetDelegate(std::make_unique<extensions::ExtensionHooksDelegate>(
+ bindings_system->messaging_service()));
+ bindings->GetHooksForAPI("tabs")->SetDelegate(
+ std::make_unique<extensions::TabsHooksDelegate>(
+ bindings_system->messaging_service()));
+ bindings->GetHooksForAPI("accessibilityPrivate")
+ ->SetDelegate(
+ std::make_unique<extensions::AccessibilityPrivateHooksDelegate>());
+}
diff --git a/chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h b/chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h
new file mode 100644
index 00000000000..726d6731a43
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_CHROME_EXTENSIONS_DISPATCHER_DELEGATE_H_
+#define CHROME_RENDERER_EXTENSIONS_CHROME_EXTENSIONS_DISPATCHER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/dispatcher_delegate.h"
+
+class ChromeExtensionsDispatcherDelegate
+ : public extensions::DispatcherDelegate {
+ public:
+ ChromeExtensionsDispatcherDelegate();
+ ~ChromeExtensionsDispatcherDelegate() override;
+
+ private:
+ // extensions::DispatcherDelegate implementation.
+ void RegisterNativeHandlers(
+ extensions::Dispatcher* dispatcher,
+ extensions::ModuleSystem* module_system,
+ extensions::NativeExtensionBindingsSystem* bindings_system,
+ extensions::ScriptContext* context) override;
+ void PopulateSourceMap(
+ extensions::ResourceBundleSourceMap* source_map) override;
+ void RequireWebViewModules(extensions::ScriptContext* context) override;
+ void OnActiveExtensionsUpdated(
+ const std::set<std::string>& extensions_ids) override;
+ void InitializeBindingsSystem(
+ extensions::Dispatcher* dispatcher,
+ extensions::NativeExtensionBindingsSystem* bindings_system) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeExtensionsDispatcherDelegate);
+};
+
+#endif // CHROME_RENDERER_EXTENSIONS_CHROME_EXTENSIONS_DISPATCHER_DELEGATE_H_
diff --git a/chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.cc b/chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.cc
new file mode 100644
index 00000000000..216f9623033
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.cc
@@ -0,0 +1,413 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/chrome_extensions_renderer_client.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "chrome/common/chrome_isolated_world_ids.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_metrics.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h"
+#include "chrome/renderer/extensions/extension_process_policy.h"
+#include "chrome/renderer/extensions/renderer_permissions_policy_delegate.h"
+#include "chrome/renderer/extensions/resource_request_policy.h"
+#include "chrome/renderer/media/cast_ipc_dispatcher.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/mime_handler_view_mode.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extensions_render_frame_observer.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/guest_view/extensions_guest_view_container.h"
+#include "extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h"
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h"
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#include "url/origin.h"
+
+using extensions::Extension;
+
+namespace {
+
+bool IsStandaloneExtensionProcess() {
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kExtensionProcess);
+}
+
+void IsGuestViewApiAvailableToScriptContext(
+ bool* api_is_available,
+ extensions::ScriptContext* context) {
+ if (context->GetAvailability("guestViewInternal").is_available()) {
+ *api_is_available = true;
+ }
+}
+
+// Returns true if the frame is navigating to an URL either into or out of an
+// extension app's extent.
+bool CrossesExtensionExtents(blink::WebLocalFrame* frame,
+ const GURL& new_url,
+ bool is_extension_url,
+ bool is_initial_navigation) {
+ DCHECK(!frame->Parent());
+ GURL old_url(frame->GetDocument().Url());
+
+ extensions::RendererExtensionRegistry* extension_registry =
+ extensions::RendererExtensionRegistry::Get();
+
+ // If old_url is still empty and this is an initial navigation, then this is
+ // a window.open operation. We should look at the opener URL. Note that the
+ // opener is a local frame in this case.
+ if (is_initial_navigation && old_url.is_empty() && frame->Opener()) {
+ blink::WebLocalFrame* opener_frame = frame->Opener()->ToWebLocalFrame();
+
+ // We want to compare against the URL that determines the type of
+ // process. Use the URL of the opener's local frame root, which will
+ // correctly handle any site isolation modes (e.g. --site-per-process).
+ blink::WebLocalFrame* local_root = opener_frame->LocalRoot();
+ old_url = local_root->GetDocument().Url();
+
+ // If we're about to open a normal web page from a same-origin opener stuck
+ // in an extension process (other than the Chrome Web Store), we want to
+ // keep it in process to allow the opener to script it.
+ blink::WebDocument opener_document = opener_frame->GetDocument();
+ blink::WebSecurityOrigin opener_origin =
+ opener_document.GetSecurityOrigin();
+ bool opener_is_extension_url =
+ !opener_origin.IsUnique() && extension_registry->GetExtensionOrAppByURL(
+ opener_document.Url()) != nullptr;
+ const Extension* opener_top_extension =
+ extension_registry->GetExtensionOrAppByURL(old_url);
+ bool opener_is_web_store =
+ opener_top_extension &&
+ opener_top_extension->id() == extensions::kWebStoreAppId;
+ if (!is_extension_url && !opener_is_extension_url && !opener_is_web_store &&
+ IsStandaloneExtensionProcess() &&
+ opener_origin.CanRequest(blink::WebURL(new_url)))
+ return false;
+ }
+
+ return extensions::CrossesExtensionProcessBoundary(
+ *extension_registry->GetMainThreadExtensionSet(), old_url, new_url);
+}
+
+} // namespace
+
+ChromeExtensionsRendererClient::ChromeExtensionsRendererClient() {}
+
+ChromeExtensionsRendererClient::~ChromeExtensionsRendererClient() {}
+
+// static
+ChromeExtensionsRendererClient* ChromeExtensionsRendererClient::GetInstance() {
+ static base::LazyInstance<ChromeExtensionsRendererClient>::Leaky client =
+ LAZY_INSTANCE_INITIALIZER;
+ return client.Pointer();
+}
+
+bool ChromeExtensionsRendererClient::IsIncognitoProcess() const {
+ return ChromeRenderThreadObserver::is_incognito_process();
+}
+
+int ChromeExtensionsRendererClient::GetLowestIsolatedWorldId() const {
+ return ISOLATED_WORLD_ID_EXTENSIONS;
+}
+
+extensions::Dispatcher* ChromeExtensionsRendererClient::GetDispatcher() {
+ return extension_dispatcher_.get();
+}
+
+void ChromeExtensionsRendererClient::OnExtensionLoaded(
+ const extensions::Extension& extension) {
+ resource_request_policy_->OnExtensionLoaded(extension);
+}
+
+void ChromeExtensionsRendererClient::OnExtensionUnloaded(
+ const extensions::ExtensionId& extension_id) {
+ resource_request_policy_->OnExtensionUnloaded(extension_id);
+}
+
+bool ChromeExtensionsRendererClient::ExtensionAPIEnabledForServiceWorkerScript(
+ const GURL& scope,
+ const GURL& script_url) const {
+ if (!script_url.SchemeIs(extensions::kExtensionScheme))
+ return false;
+
+ if (!extensions::ExtensionsClient::Get()
+ ->ExtensionAPIEnabledInExtensionServiceWorkers())
+ return false;
+
+ const Extension* extension =
+ extensions::RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
+ script_url);
+
+ if (!extension ||
+ !extensions::BackgroundInfo::IsServiceWorkerBased(extension))
+ return false;
+
+ if (scope != extension->url())
+ return false;
+
+ const std::string& sw_script =
+ extensions::BackgroundInfo::GetBackgroundServiceWorkerScript(extension);
+
+ return extension->GetResourceURL(sw_script) == script_url;
+}
+
+void ChromeExtensionsRendererClient::RenderThreadStarted() {
+ content::RenderThread* thread = content::RenderThread::Get();
+ // ChromeRenderViewTest::SetUp() creates its own ExtensionDispatcher and
+ // injects it using SetExtensionDispatcher(). Don't overwrite it.
+ if (!extension_dispatcher_) {
+ extension_dispatcher_ = std::make_unique<extensions::Dispatcher>(
+ std::make_unique<ChromeExtensionsDispatcherDelegate>());
+ }
+ extension_dispatcher_->OnRenderThreadStarted(thread);
+ permissions_policy_delegate_.reset(
+ new extensions::RendererPermissionsPolicyDelegate(
+ extension_dispatcher_.get()));
+ resource_request_policy_.reset(
+ new extensions::ResourceRequestPolicy(extension_dispatcher_.get()));
+ guest_view_container_dispatcher_.reset(
+ new extensions::ExtensionsGuestViewContainerDispatcher());
+
+ thread->AddObserver(extension_dispatcher_.get());
+ thread->AddObserver(guest_view_container_dispatcher_.get());
+ thread->AddFilter(new CastIPCDispatcher(thread->GetIOTaskRunner()));
+}
+
+void ChromeExtensionsRendererClient::RenderFrameCreated(
+ content::RenderFrame* render_frame,
+ service_manager::BinderRegistry* registry) {
+ new extensions::ExtensionsRenderFrameObserver(render_frame, registry);
+ new extensions::ExtensionFrameHelper(render_frame,
+ extension_dispatcher_.get());
+ extension_dispatcher_->OnRenderFrameCreated(render_frame);
+}
+
+bool ChromeExtensionsRendererClient::OverrideCreatePlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params) {
+ if (params.mime_type.Utf8() != content::kBrowserPluginMimeType)
+ return true;
+
+ bool guest_view_api_available = false;
+ extension_dispatcher_->script_context_set_iterator()->ForEach(
+ render_frame, base::Bind(&IsGuestViewApiAvailableToScriptContext,
+ &guest_view_api_available));
+ return !guest_view_api_available;
+}
+
+bool ChromeExtensionsRendererClient::AllowPopup() {
+ extensions::ScriptContext* current_context =
+ extension_dispatcher_->script_context_set().GetCurrent();
+ if (!current_context || !current_context->extension())
+ return false;
+
+ // See http://crbug.com/117446 for the subtlety of this check.
+ switch (current_context->context_type()) {
+ case extensions::Feature::UNSPECIFIED_CONTEXT:
+ case extensions::Feature::WEB_PAGE_CONTEXT:
+ case extensions::Feature::UNBLESSED_EXTENSION_CONTEXT:
+ case extensions::Feature::WEBUI_CONTEXT:
+ case extensions::Feature::LOCK_SCREEN_EXTENSION_CONTEXT:
+ return false;
+ case extensions::Feature::BLESSED_EXTENSION_CONTEXT:
+ return !current_context->IsForServiceWorker();
+ case extensions::Feature::CONTENT_SCRIPT_CONTEXT:
+ return true;
+ case extensions::Feature::BLESSED_WEB_PAGE_CONTEXT:
+ return !current_context->web_frame()->Parent();
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+void ChromeExtensionsRendererClient::WillSendRequest(
+ blink::WebLocalFrame* frame,
+ ui::PageTransition transition_type,
+ const blink::WebURL& url,
+ const url::Origin* initiator_origin,
+ GURL* new_url,
+ bool* attach_same_site_cookies) {
+ if (initiator_origin &&
+ initiator_origin->scheme() == extensions::kExtensionScheme) {
+ const extensions::RendererExtensionRegistry* extension_registry =
+ extensions::RendererExtensionRegistry::Get();
+ const Extension* extension =
+ extension_registry->GetByID(initiator_origin->host());
+ if (extension) {
+ int tab_id = extensions::ExtensionFrameHelper::Get(
+ content::RenderFrame::FromWebFrame(frame))
+ ->tab_id();
+ GURL request_url(url);
+ if (extension->permissions_data()->GetPageAccess(request_url, tab_id,
+ nullptr) ==
+ extensions::PermissionsData::PageAccess::kAllowed ||
+ extension->permissions_data()->GetContentScriptAccess(
+ request_url, tab_id, nullptr) ==
+ extensions::PermissionsData::PageAccess::kAllowed) {
+ *attach_same_site_cookies = true;
+ }
+ } else {
+ // If there is no extension installed for the origin, it may be from a
+ // recently uninstalled extension. The tabs of such extensions are
+ // automatically closed, but subframes and content scripts may stick
+ // around. Fail such requests without killing the process.
+ *new_url = GURL(chrome::kExtensionInvalidRequestURL);
+ }
+ }
+
+ if (url.ProtocolIs(extensions::kExtensionScheme) &&
+ !resource_request_policy_->CanRequestResource(GURL(url), frame,
+ transition_type)) {
+ *new_url = GURL(chrome::kExtensionInvalidRequestURL);
+ }
+}
+
+void ChromeExtensionsRendererClient::SetExtensionDispatcherForTest(
+ std::unique_ptr<extensions::Dispatcher> extension_dispatcher) {
+ extension_dispatcher_ = std::move(extension_dispatcher);
+ permissions_policy_delegate_.reset(
+ new extensions::RendererPermissionsPolicyDelegate(
+ extension_dispatcher_.get()));
+}
+
+extensions::Dispatcher*
+ChromeExtensionsRendererClient::GetExtensionDispatcherForTest() {
+ return extension_dispatcher();
+}
+
+// static
+bool ChromeExtensionsRendererClient::ShouldFork(blink::WebLocalFrame* frame,
+ const GURL& url,
+ bool is_initial_navigation,
+ bool is_server_redirect) {
+ const extensions::RendererExtensionRegistry* extension_registry =
+ extensions::RendererExtensionRegistry::Get();
+
+ // Determine if the new URL is an extension (excluding bookmark apps).
+ const Extension* new_url_extension = extensions::GetNonBookmarkAppExtension(
+ *extension_registry->GetMainThreadExtensionSet(), url);
+ bool is_extension_url = !!new_url_extension;
+
+ // If the navigation would cross an app extent boundary, we also need
+ // to defer to the browser to ensure process isolation. This is not necessary
+ // for server redirects, which will be transferred to a new process by the
+ // browser process when they are ready to commit. It is necessary for client
+ // redirects, which won't be transferred in the same way.
+ if (!is_server_redirect &&
+ CrossesExtensionExtents(frame, url, is_extension_url,
+ is_initial_navigation)) {
+ const Extension* extension =
+ extension_registry->GetExtensionOrAppByURL(url);
+ if (extension && extension->is_app()) {
+ extensions::RecordAppLaunchType(
+ extension_misc::APP_LAUNCH_CONTENT_NAVIGATION, extension->GetType());
+ }
+ return true;
+ }
+
+ return false;
+}
+
+// static
+content::BrowserPluginDelegate*
+ChromeExtensionsRendererClient::CreateBrowserPluginDelegate(
+ content::RenderFrame* render_frame,
+ const content::WebPluginInfo& info,
+ const std::string& mime_type,
+ const GURL& original_url) {
+ if (mime_type == content::kBrowserPluginMimeType)
+ return new extensions::ExtensionsGuestViewContainer(render_frame);
+ return new extensions::MimeHandlerViewContainer(render_frame, info, mime_type,
+ original_url);
+}
+
+// static
+void ChromeExtensionsRendererClient::DidBlockMimeHandlerViewForDisallowedPlugin(
+ const blink::WebElement& plugin_element) {
+ extensions::MimeHandlerViewContainerManager::Get(
+ content::RenderFrame::FromWebFrame(
+ plugin_element.GetDocument().GetFrame()),
+ true /* create_if_does_not_exist */)
+ ->DidBlockMimeHandlerViewForDisallowedPlugin(plugin_element);
+}
+
+// static
+bool ChromeExtensionsRendererClient::MaybeCreateMimeHandlerView(
+ const blink::WebElement& plugin_element,
+ const GURL& resource_url,
+ const std::string& mime_type,
+ const content::WebPluginInfo& plugin_info) {
+ CHECK(content::MimeHandlerViewMode::UsesCrossProcessFrame());
+ return extensions::MimeHandlerViewContainerManager::Get(
+ content::RenderFrame::FromWebFrame(
+ plugin_element.GetDocument().GetFrame()),
+ true /* create_if_does_not_exist */)
+ ->CreateFrameContainer(plugin_element, resource_url, mime_type,
+ plugin_info);
+}
+
+v8::Local<v8::Object> ChromeExtensionsRendererClient::GetScriptableObject(
+ const blink::WebElement& plugin_element,
+ v8::Isolate* isolate) {
+ CHECK(content::MimeHandlerViewMode::UsesCrossProcessFrame());
+ // If there is a MimeHandlerView that can provide the scriptable object then
+ // MaybeCreateMimeHandlerView must have been called before and a container
+ // manager should exist.
+ auto* container_manager = extensions::MimeHandlerViewContainerManager::Get(
+ content::RenderFrame::FromWebFrame(
+ plugin_element.GetDocument().GetFrame()),
+ false /* create_if_does_not_exist */);
+ if (container_manager)
+ return container_manager->GetScriptableObject(plugin_element, isolate);
+ return v8::Local<v8::Object>();
+}
+
+// static
+blink::WebFrame* ChromeExtensionsRendererClient::FindFrame(
+ blink::WebLocalFrame* relative_to_frame,
+ const std::string& name) {
+ content::RenderFrame* result = extensions::ExtensionFrameHelper::FindFrame(
+ content::RenderFrame::FromWebFrame(relative_to_frame), name);
+ return result ? result->GetWebFrame() : nullptr;
+}
+
+void ChromeExtensionsRendererClient::RunScriptsAtDocumentStart(
+ content::RenderFrame* render_frame) {
+ extension_dispatcher_->RunScriptsAtDocumentStart(render_frame);
+}
+
+void ChromeExtensionsRendererClient::RunScriptsAtDocumentEnd(
+ content::RenderFrame* render_frame) {
+ extension_dispatcher_->RunScriptsAtDocumentEnd(render_frame);
+}
+
+void ChromeExtensionsRendererClient::RunScriptsAtDocumentIdle(
+ content::RenderFrame* render_frame) {
+ extension_dispatcher_->RunScriptsAtDocumentIdle(render_frame);
+}
diff --git a/chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.h b/chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.h
new file mode 100644
index 00000000000..4b821fc6af4
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_extensions_renderer_client.h
@@ -0,0 +1,129 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_CHROME_EXTENSIONS_RENDERER_CLIENT_H_
+#define CHROME_RENDERER_EXTENSIONS_CHROME_EXTENSIONS_RENDERER_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "ui/base/page_transition_types.h"
+
+class GURL;
+
+namespace blink {
+class WebElement;
+class WebFrame;
+class WebLocalFrame;
+struct WebPluginParams;
+class WebURL;
+}
+
+namespace content {
+class BrowserPluginDelegate;
+class RenderFrame;
+struct WebPluginInfo;
+}
+
+namespace extensions {
+class Dispatcher;
+class ExtensionsGuestViewContainerDispatcher;
+class RendererPermissionsPolicyDelegate;
+class ResourceRequestPolicy;
+}
+
+namespace url {
+class Origin;
+}
+
+namespace v8 {
+class Isolate;
+template <typename T>
+class Local;
+class Object;
+} // namespace v8
+
+class ChromeExtensionsRendererClient
+ : public extensions::ExtensionsRendererClient {
+ public:
+ ChromeExtensionsRendererClient();
+ ~ChromeExtensionsRendererClient() override;
+
+ // Get the LazyInstance for ChromeExtensionsRendererClient.
+ static ChromeExtensionsRendererClient* GetInstance();
+
+ // extensions::ExtensionsRendererClient implementation.
+ bool IsIncognitoProcess() const override;
+ int GetLowestIsolatedWorldId() const override;
+ extensions::Dispatcher* GetDispatcher() override;
+ void OnExtensionLoaded(const extensions::Extension& extension) override;
+ void OnExtensionUnloaded(
+ const extensions::ExtensionId& extension_id) override;
+
+ bool ExtensionAPIEnabledForServiceWorkerScript(
+ const GURL& scope,
+ const GURL& script_url) const override;
+
+ // See ChromeContentRendererClient methods with the same names.
+ void RenderThreadStarted();
+ void RenderFrameCreated(content::RenderFrame* render_frame,
+ service_manager::BinderRegistry* registry);
+ bool OverrideCreatePlugin(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params);
+ bool AllowPopup();
+ void WillSendRequest(blink::WebLocalFrame* frame,
+ ui::PageTransition transition_type,
+ const blink::WebURL& url,
+ const url::Origin* initiator_origin,
+ GURL* new_url,
+ bool* attach_same_site_cookies);
+ v8::Local<v8::Object> GetScriptableObject(
+ const blink::WebElement& plugin_element,
+ v8::Isolate* isolate);
+ void SetExtensionDispatcherForTest(
+ std::unique_ptr<extensions::Dispatcher> extension_dispatcher);
+ extensions::Dispatcher* GetExtensionDispatcherForTest();
+
+ static bool ShouldFork(blink::WebLocalFrame* frame,
+ const GURL& url,
+ bool is_initial_navigation,
+ bool is_server_redirect);
+ static content::BrowserPluginDelegate* CreateBrowserPluginDelegate(
+ content::RenderFrame* render_frame,
+ const content::WebPluginInfo& info,
+ const std::string& mime_type,
+ const GURL& original_url);
+ static void DidBlockMimeHandlerViewForDisallowedPlugin(
+ const blink::WebElement& plugin_element);
+ static bool MaybeCreateMimeHandlerView(
+ const blink::WebElement& plugin_element,
+ const GURL& resource_url,
+ const std::string& mime_type,
+ const content::WebPluginInfo& plugin_info);
+ static blink::WebFrame* FindFrame(blink::WebLocalFrame* relative_to_frame,
+ const std::string& name);
+
+ void RunScriptsAtDocumentStart(content::RenderFrame* render_frame);
+ void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame);
+ void RunScriptsAtDocumentIdle(content::RenderFrame* render_frame);
+
+ extensions::Dispatcher* extension_dispatcher() {
+ return extension_dispatcher_.get();
+ }
+
+ private:
+ std::unique_ptr<extensions::Dispatcher> extension_dispatcher_;
+ std::unique_ptr<extensions::RendererPermissionsPolicyDelegate>
+ permissions_policy_delegate_;
+ std::unique_ptr<extensions::ExtensionsGuestViewContainerDispatcher>
+ guest_view_container_dispatcher_;
+ std::unique_ptr<extensions::ResourceRequestPolicy> resource_request_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeExtensionsRendererClient);
+};
+
+#endif // CHROME_RENDERER_EXTENSIONS_CHROME_EXTENSIONS_RENDERER_CLIENT_H_
diff --git a/chromium/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc b/chromium/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc
new file mode 100644
index 00000000000..331ad4c14d1
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_builder.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_extension_bindings_system_test_base.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+TEST_F(NativeExtensionBindingsSystemUnittest, InitializeContext) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder("foo")
+ .AddPermissions({"idle", "power", "webRequest", "tabs"})
+ .Build();
+ RegisterExtension(extension);
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+
+ bindings_system()->UpdateBindingsForContext(script_context);
+
+ // Test that chrome-specified APIs (like tabs) are present.
+ v8::Local<v8::Value> chrome =
+ GetPropertyFromObject(context->Global(), context, "chrome");
+ ASSERT_FALSE(chrome.IsEmpty());
+ ASSERT_TRUE(chrome->IsObject());
+
+ v8::Local<v8::Value> tabs =
+ GetPropertyFromObject(chrome.As<v8::Object>(), context, "tabs");
+ ASSERT_FALSE(tabs.IsEmpty());
+ ASSERT_TRUE(tabs->IsObject());
+
+ v8::Local<v8::Value> query =
+ GetPropertyFromObject(tabs.As<v8::Object>(), context, "query");
+ ASSERT_FALSE(query.IsEmpty());
+ ASSERT_TRUE(query->IsFunction());
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/chrome_v8_extension_handler.cc b/chromium/chrome/renderer/extensions/chrome_v8_extension_handler.cc
new file mode 100644
index 00000000000..c76d38fe100
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_v8_extension_handler.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/chrome_v8_extension_handler.h"
+
+#include "base/logging.h"
+#include "content/public/renderer/render_thread.h"
+
+using content::RenderThread;
+
+namespace extensions {
+
+ChromeV8ExtensionHandler::ChromeV8ExtensionHandler()
+ : routing_id_(MSG_ROUTING_NONE) {
+}
+
+ChromeV8ExtensionHandler::~ChromeV8ExtensionHandler() {
+ if (routing_id_ != MSG_ROUTING_NONE)
+ RenderThread::Get()->RemoveRoute(routing_id_);
+}
+
+int ChromeV8ExtensionHandler::GetRoutingID() {
+ if (routing_id_ == MSG_ROUTING_NONE) {
+ routing_id_ = RenderThread::Get()->GenerateRoutingID();
+ RenderThread::Get()->AddRoute(routing_id_, this);
+ }
+
+ return routing_id_;
+}
+
+void ChromeV8ExtensionHandler::Send(IPC::Message* message) {
+ RenderThread::Get()->Send(message);
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/chrome_v8_extension_handler.h b/chromium/chrome/renderer/extensions/chrome_v8_extension_handler.h
new file mode 100644
index 00000000000..cdb733fe157
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/chrome_v8_extension_handler.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_CHROME_V8_EXTENSION_HANDLER_H_
+#define CHROME_RENDERER_EXTENSIONS_CHROME_V8_EXTENSION_HANDLER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "ipc/ipc_listener.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// Base class for context-scoped handlers used with ChromeV8Extension.
+// TODO(koz): Rename/refactor this somehow. Maybe just pull it into
+// ChromeV8Extension.
+class ChromeV8ExtensionHandler : public IPC::Listener {
+ public:
+ ~ChromeV8ExtensionHandler() override;
+
+ // IPC::Listener
+ bool OnMessageReceived(const IPC::Message& message) override = 0;
+
+ protected:
+ ChromeV8ExtensionHandler();
+ int GetRoutingID();
+ void Send(IPC::Message* message);
+
+ private:
+ int routing_id_;
+ DISALLOW_COPY_AND_ASSIGN(ChromeV8ExtensionHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_CHROME_V8_EXTENSION_HANDLER_H_
diff --git a/chromium/chrome/renderer/extensions/custom_types_unittest.cc b/chromium/chrome/renderer/extensions/custom_types_unittest.cc
new file mode 100644
index 00000000000..971519e18da
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/custom_types_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/storage_area.h"
+
+#include "base/command_line.h"
+#include "base/stl_util.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/bindings/api_binding_util.h"
+#include "extensions/renderer/bindings/api_invocation_errors.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_extension_bindings_system_test_base.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+class CustomTypesTest : public NativeExtensionBindingsSystemUnittest {
+ public:
+ CustomTypesTest()
+ : extension_id_(crx_file::id_util::GenerateId("id")),
+ allowlisted_extension_id_(extension_id_) {}
+
+ ~CustomTypesTest() override = default;
+
+ // Checks behavior of script after the main context is invalidated.
+ // Creates an extension with the given |permission|, and then runs
+ // |use_api_script| as a function with a single argument, the result of
+ // |api_script|. This expects the function to succeed while the function is
+ // valid, and then fail when the function is invalidated with the expected
+ // error.
+ // Note that no other validations are made (e.g., around the correctness of
+ // the call made to the API).
+ void RunContextInvalidationTest(const char* permission,
+ const char* api_script,
+ const char* use_api_script) {
+ scoped_refptr<const Extension> extension = ExtensionBuilder("foo")
+ .AddPermission(permission)
+ .SetID(extension_id_)
+ .Build();
+ RegisterExtension(extension);
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+
+ bindings_system()->UpdateBindingsForContext(script_context);
+
+ v8::Local<v8::Value> api_object =
+ V8ValueFromScriptSource(context, api_script);
+ ASSERT_TRUE(api_object->IsObject());
+
+ v8::Local<v8::Function> use_api =
+ FunctionFromString(context, use_api_script);
+ v8::Local<v8::Value> args[] = {api_object};
+ RunFunction(use_api, context, base::size(args), args);
+
+ DisposeContext(context);
+
+ EXPECT_FALSE(binding::IsContextValid(context));
+ RunFunctionAndExpectError(use_api, context, base::size(args), args,
+ "Uncaught Error: Extension context invalidated.");
+ }
+
+ private:
+ std::string extension_id_;
+ SimpleFeature::ScopedThreadUnsafeAllowlistForTest allowlisted_extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomTypesTest);
+};
+
+TEST_F(CustomTypesTest, ContentSettingsUseAfterInvalidation) {
+ RunContextInvalidationTest("contentSettings",
+ "chrome.contentSettings.javascript",
+ R"((function(setting) {
+ setting.set({
+ primaryPattern: '<all_urls>',
+ setting: 'block' });
+ });)");
+}
+
+TEST_F(CustomTypesTest, ChromeSettingsAPIUseAfterInvalidation) {
+ RunContextInvalidationTest(
+ "privacy", "chrome.privacy.websites.doNotTrackEnabled",
+ R"((function(setting) { setting.set({value: true}); }))");
+}
+
+TEST_F(CustomTypesTest, ChromeSettingsEventUseAfterInvalidation) {
+ RunContextInvalidationTest("privacy",
+ "chrome.privacy.websites.doNotTrackEnabled",
+ R"((function(setting) {
+ setting.onChange.addListener(function() {});
+ });)");
+}
+
+TEST_F(CustomTypesTest, ContentSettingsInvalidInvocationError) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder("foo").AddPermission("contentSettings").Build();
+ RegisterExtension(extension);
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+
+ bindings_system()->UpdateBindingsForContext(script_context);
+
+ v8::Local<v8::Value> settings =
+ V8ValueFromScriptSource(context, "chrome.contentSettings");
+ ASSERT_TRUE(settings->IsObject());
+
+ // Invoke ContentSetting.set() without a required argument to trigger an
+ // error.
+ constexpr char kRunSetContentSetting[] =
+ "(function(settings) { settings.javascript.set(); })";
+ v8::Local<v8::Function> run_set_content_setting =
+ FunctionFromString(context, kRunSetContentSetting);
+ v8::Local<v8::Value> args[] = {settings};
+ RunFunctionAndExpectError(
+ run_set_content_setting, context, base::size(args), args,
+ "Uncaught TypeError: " + api_errors::InvocationError(
+ "contentSettings.ContentSetting.set",
+ "object details, optional function callback",
+ "No matching signature."));
+}
+
+TEST_F(CustomTypesTest, ChromeSettingsInvalidInvocationError) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder("foo").AddPermission("privacy").Build();
+ RegisterExtension(extension);
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+
+ bindings_system()->UpdateBindingsForContext(script_context);
+
+ v8::Local<v8::Value> settings =
+ V8ValueFromScriptSource(context, "chrome.privacy");
+ ASSERT_TRUE(settings->IsObject());
+
+ // Invoke ChromeSetting.set() without a required argument to trigger an
+ // error.
+ constexpr char kRunSetChromeSetting[] =
+ "(function(settings) { settings.websites.doNotTrackEnabled.set(); })";
+ v8::Local<v8::Function> run_set_chrome_setting =
+ FunctionFromString(context, kRunSetChromeSetting);
+ v8::Local<v8::Value> args[] = {settings};
+ RunFunctionAndExpectError(
+ run_set_chrome_setting, context, base::size(args), args,
+ "Uncaught TypeError: " + api_errors::InvocationError(
+ "types.ChromeSetting.set",
+ "object details, optional function callback",
+ "No matching signature."));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/extension_hooks_delegate.cc b/chromium/chrome/renderer/extensions/extension_hooks_delegate.cc
new file mode 100644
index 00000000000..b4e8bef3065
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_hooks_delegate.cc
@@ -0,0 +1,332 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/extension_hooks_delegate.h"
+
+#include "content/public/renderer/v8_value_converter.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/view_type.h"
+#include "extensions/renderer/bindings/api_signature.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/get_script_context.h"
+#include "extensions/renderer/message_target.h"
+#include "extensions/renderer/messaging_util.h"
+#include "extensions/renderer/native_renderer_messaging_service.h"
+#include "extensions/renderer/runtime_hooks_delegate.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+
+namespace extensions {
+
+namespace {
+
+using RequestResult = APIBindingHooks::RequestResult;
+
+constexpr char kSendExtensionRequest[] = "extension.sendRequest";
+constexpr char kGetURL[] = "extension.getURL";
+constexpr char kGetBackgroundPage[] = "extension.getBackgroundPage";
+constexpr char kGetViews[] = "extension.getViews";
+constexpr char kGetExtensionTabs[] = "extension.getExtensionTabs";
+
+// We alias a bunch of chrome.extension APIs to their chrome.runtime
+// counterparts.
+// NOTE(devlin): This is a very simple alias, in which we just return the
+// runtime version from the chrome.runtime object. This is important to note
+// for a few reasons:
+// - Modifications to the chrome.runtime object will affect the return result
+// here. i.e., if script does chrome.runtime.sendMessage = 'some string',
+// then chrome.extension.sendMessage will also be 'some string'.
+// - Events will share listeners. i.e., a listener added to
+// chrome.extension.onMessage will fire from a runtime.onMessage event, and
+// vice versa.
+// All of these APIs have been deprecated, and are no longer even documented,
+// but still have usage. This is the cheap workaround that JS bindings have been
+// using, and, while not robust, it should be secure, so use it native bindings,
+// too.
+void GetAliasedFeature(v8::Local<v8::Name> property_name,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ v8::Isolate* isolate = info.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = info.Holder()->CreationContext();
+
+ v8::TryCatch try_catch(isolate);
+ v8::Local<v8::Value> chrome;
+ if (!context->Global()
+ ->Get(context, gin::StringToSymbol(isolate, "chrome"))
+ .ToLocal(&chrome) ||
+ !chrome->IsObject()) {
+ return;
+ }
+
+ v8::Local<v8::Value> runtime;
+ if (!chrome.As<v8::Object>()
+ ->Get(context, gin::StringToSymbol(isolate, "runtime"))
+ .ToLocal(&runtime) ||
+ !runtime->IsObject()) {
+ return;
+ }
+
+ v8::Local<v8::Object> runtime_obj = runtime.As<v8::Object>();
+ v8::Maybe<bool> has_property =
+ runtime_obj->HasRealNamedProperty(context, property_name);
+ if (!has_property.IsJust() || !has_property.FromJust())
+ return;
+
+ v8::Local<v8::Value> property_value;
+ // Try and grab the chrome.runtime version. It's possible this has been
+ // tampered with, so early-out if an exception is thrown.
+ if (!runtime_obj->Get(context, property_name).ToLocal(&property_value))
+ return;
+
+ info.GetReturnValue().Set(property_value);
+}
+
+// A helper method to throw a deprecation error on access.
+void ThrowDeprecatedAccessError(
+ v8::Local<v8::Name> name,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ static constexpr char kError[] =
+ "extension.sendRequest, extension.onRequest, and "
+ "extension.onRequestExternal are deprecated. Please use "
+ "runtime.sendMessage, runtime.onMessage, and runtime.onMessageExternal "
+ "instead.";
+ v8::Isolate* isolate = info.GetIsolate();
+ isolate->ThrowException(
+ v8::Exception::Error(gin::StringToV8(isolate, kError)));
+}
+
+} // namespace
+
+ExtensionHooksDelegate::ExtensionHooksDelegate(
+ NativeRendererMessagingService* messaging_service)
+ : messaging_service_(messaging_service) {}
+ExtensionHooksDelegate::~ExtensionHooksDelegate() {}
+
+RequestResult ExtensionHooksDelegate::HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) {
+ // TODO(devlin): This logic is the same in the RuntimeCustomHooksDelegate -
+ // would it make sense to share it?
+ using Handler = RequestResult (ExtensionHooksDelegate::*)(
+ ScriptContext*, const std::vector<v8::Local<v8::Value>>&);
+ static struct {
+ Handler handler;
+ base::StringPiece method;
+ } kHandlers[] = {
+ {&ExtensionHooksDelegate::HandleSendRequest, kSendExtensionRequest},
+ {&ExtensionHooksDelegate::HandleGetURL, kGetURL},
+ {&ExtensionHooksDelegate::HandleGetBackgroundPage, kGetBackgroundPage},
+ {&ExtensionHooksDelegate::HandleGetExtensionTabs, kGetExtensionTabs},
+ {&ExtensionHooksDelegate::HandleGetViews, kGetViews},
+ };
+
+ ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
+
+ Handler handler = nullptr;
+ for (const auto& handler_entry : kHandlers) {
+ if (handler_entry.method == method_name) {
+ handler = handler_entry.handler;
+ break;
+ }
+ }
+
+ if (!handler)
+ return RequestResult(RequestResult::NOT_HANDLED);
+
+ if (method_name == kSendExtensionRequest) {
+ messaging_util::MassageSendMessageArguments(context->GetIsolate(), false,
+ arguments);
+ }
+
+ APISignature::V8ParseResult parse_result =
+ signature->ParseArgumentsToV8(context, *arguments, refs);
+ if (!parse_result.succeeded()) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(*parse_result.error);
+ return result;
+ }
+
+ return (this->*handler)(script_context, *parse_result.arguments);
+}
+
+void ExtensionHooksDelegate::InitializeTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::ObjectTemplate> object_template,
+ const APITypeReferenceMap& type_refs) {
+ static constexpr const char* kAliases[] = {
+ "connect", "connectNative", "sendMessage", "sendNativeMessage",
+ "onConnect", "onConnectExternal", "onMessage", "onMessageExternal",
+ };
+
+ for (const auto* alias : kAliases) {
+ object_template->SetAccessor(gin::StringToSymbol(isolate, alias),
+ &GetAliasedFeature);
+ }
+
+ bool is_incognito = ExtensionsRendererClient::Get()->IsIncognitoProcess();
+ object_template->Set(isolate, "inIncognitoContext",
+ v8::Boolean::New(isolate, is_incognito));
+}
+
+void ExtensionHooksDelegate::InitializeInstance(
+ v8::Local<v8::Context> context,
+ v8::Local<v8::Object> instance) {
+ // Throw access errors for deprecated sendRequest-related properties. This
+ // isn't terribly efficient, but is only done for certain unpacked extensions
+ // and only if they access the chrome.extension module.
+ if (messaging_util::IsSendRequestDisabled(
+ GetScriptContextFromV8ContextChecked(context))) {
+ static constexpr const char* kDeprecatedSendRequestProperties[] = {
+ "sendRequest", "onRequest", "onRequestExternal"};
+ for (const char* property : kDeprecatedSendRequestProperties) {
+ v8::Maybe<bool> success = instance->SetAccessor(
+ context, gin::StringToV8(context->GetIsolate(), property),
+ &ThrowDeprecatedAccessError);
+ DCHECK(success.IsJust());
+ DCHECK(success.FromJust());
+ }
+ }
+}
+
+RequestResult ExtensionHooksDelegate::HandleSendRequest(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ DCHECK_EQ(3u, arguments.size());
+ // This DCHECK() is correct because no context with sendRequest-related
+ // APIs disabled should have scriptable access to a context with them
+ // enabled.
+ DCHECK(!messaging_util::IsSendRequestDisabled(script_context));
+
+ std::string target_id;
+ std::string error;
+ if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
+ "extension.sendRequest", &target_id,
+ &error)) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(error);
+ return result;
+ }
+
+ v8::Local<v8::Value> v8_message = arguments[1];
+ std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+ script_context->v8_context(), v8_message, &error);
+ if (!message) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(error);
+ return result;
+ }
+
+ v8::Local<v8::Function> response_callback;
+ if (!arguments[2]->IsNull())
+ response_callback = arguments[2].As<v8::Function>();
+
+ messaging_service_->SendOneTimeMessage(
+ script_context, MessageTarget::ForExtension(target_id),
+ messaging_util::kSendRequestChannel, false, *message, response_callback);
+
+ return RequestResult(RequestResult::HANDLED);
+}
+
+RequestResult ExtensionHooksDelegate::HandleGetURL(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ // We call a static implementation here rather using an alias due to not being
+ // able to remove the extension.json GetURL entry, as it is used for generated
+ // documentation and api feature lists some other methods refer to.
+ return RuntimeHooksDelegate::GetURL(script_context, arguments);
+}
+
+APIBindingHooks::RequestResult ExtensionHooksDelegate::HandleGetViews(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ const Extension* extension = script_context->extension();
+ DCHECK(extension);
+
+ ViewType view_type = VIEW_TYPE_INVALID;
+ int window_id = extension_misc::kUnknownWindowId;
+ int tab_id = extension_misc::kUnknownTabId;
+
+ if (!arguments[0]->IsNull()) {
+ gin::Dictionary options_dict(script_context->isolate(),
+ arguments[0].As<v8::Object>());
+ v8::Local<v8::Value> v8_window_id;
+ v8::Local<v8::Value> v8_tab_id;
+ v8::Local<v8::Value> v8_view_type;
+ if (!options_dict.Get("windowId", &v8_window_id) ||
+ !options_dict.Get("tabId", &v8_tab_id) ||
+ !options_dict.Get("type", &v8_view_type)) {
+ NOTREACHED()
+ << "Unexpected exception: argument parsing produces plain objects";
+ return RequestResult(RequestResult::THROWN);
+ }
+
+ if (!v8_window_id->IsUndefined()) {
+ DCHECK(v8_window_id->IsInt32());
+ window_id = v8_window_id.As<v8::Int32>()->Value();
+ }
+
+ if (!v8_tab_id->IsUndefined()) {
+ DCHECK(v8_tab_id->IsInt32());
+ tab_id = v8_tab_id.As<v8::Int32>()->Value();
+ }
+
+ if (!v8_view_type->IsUndefined()) {
+ DCHECK(v8_view_type->IsString());
+ std::string view_type_string = base::ToUpperASCII(
+ gin::V8ToString(script_context->isolate(), v8_view_type));
+ if (view_type_string != "ALL") {
+ bool success = GetViewTypeFromString(view_type_string, &view_type);
+ DCHECK(success);
+ }
+ }
+ }
+
+ RequestResult result(RequestResult::HANDLED);
+ result.return_value = ExtensionFrameHelper::GetV8MainFrames(
+ script_context->v8_context(), extension->id(), window_id, tab_id,
+ view_type);
+ return result;
+}
+
+RequestResult ExtensionHooksDelegate::HandleGetExtensionTabs(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ const Extension* extension = script_context->extension();
+ DCHECK(extension);
+
+ ViewType view_type = VIEW_TYPE_TAB_CONTENTS;
+ int window_id = extension_misc::kUnknownWindowId;
+ int tab_id = extension_misc::kUnknownTabId;
+
+ if (!arguments[0]->IsNull())
+ window_id = arguments[0].As<v8::Int32>()->Value();
+
+ RequestResult result(RequestResult::HANDLED);
+ result.return_value = ExtensionFrameHelper::GetV8MainFrames(
+ script_context->v8_context(), extension->id(), window_id, tab_id,
+ view_type);
+ return result;
+}
+
+RequestResult ExtensionHooksDelegate::HandleGetBackgroundPage(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ const Extension* extension = script_context->extension();
+ DCHECK(extension);
+
+ RequestResult result(RequestResult::HANDLED);
+ result.return_value = ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
+ script_context->isolate(), extension->id());
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/extension_hooks_delegate.h b/chromium/chrome/renderer/extensions/extension_hooks_delegate.h
new file mode 100644
index 00000000000..3ac2b1bd064
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_hooks_delegate.h
@@ -0,0 +1,65 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_EXTENSION_HOOKS_DELEGATE_H_
+#define CHROME_RENDERER_EXTENSIONS_EXTENSION_HOOKS_DELEGATE_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/renderer/bindings/api_binding_hooks_delegate.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class NativeRendererMessagingService;
+class ScriptContext;
+
+// The custom hooks for the chrome.extension API.
+class ExtensionHooksDelegate : public APIBindingHooksDelegate {
+ public:
+ explicit ExtensionHooksDelegate(
+ NativeRendererMessagingService* messaging_service);
+ ~ExtensionHooksDelegate() override;
+
+ // APIBindingHooksDelegate:
+ APIBindingHooks::RequestResult HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) override;
+ void InitializeTemplate(v8::Isolate* isolate,
+ v8::Local<v8::ObjectTemplate> object_template,
+ const APITypeReferenceMap& type_refs) override;
+ void InitializeInstance(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> instance) override;
+
+ private:
+ // Request handlers for the corresponding API methods.
+ APIBindingHooks::RequestResult HandleSendRequest(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+ APIBindingHooks::RequestResult HandleGetURL(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+ APIBindingHooks::RequestResult HandleGetViews(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+ APIBindingHooks::RequestResult HandleGetExtensionTabs(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+ APIBindingHooks::RequestResult HandleGetBackgroundPage(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+
+ // The messaging service to handle messaging calls.
+ // Guaranteed to outlive this object.
+ NativeRendererMessagingService* const messaging_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionHooksDelegate);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_HOOKS_DELEGATE_H_
diff --git a/chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc b/chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
new file mode 100644
index 00000000000..613dfb92d9e
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
@@ -0,0 +1,266 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/extension_hooks_delegate.h"
+
+#include "base/strings/stringprintf.h"
+#include "content/public/common/child_process_host.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/value_builder.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/message_target.h"
+#include "extensions/renderer/messaging_util.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_extension_bindings_system_test_base.h"
+#include "extensions/renderer/native_renderer_messaging_service.h"
+#include "extensions/renderer/runtime_hooks_delegate.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/send_message_tester.h"
+
+namespace extensions {
+
+class ExtensionHooksDelegateTest
+ : public NativeExtensionBindingsSystemUnittest {
+ public:
+ ExtensionHooksDelegateTest() {}
+ ~ExtensionHooksDelegateTest() override {}
+
+ // NativeExtensionBindingsSystemUnittest:
+ void SetUp() override {
+ NativeExtensionBindingsSystemUnittest::SetUp();
+ messaging_service_ =
+ std::make_unique<NativeRendererMessagingService>(bindings_system());
+
+ bindings_system()
+ ->api_system()
+ ->GetHooksForAPI("extension")
+ ->SetDelegate(
+ std::make_unique<ExtensionHooksDelegate>(messaging_service_.get()));
+ bindings_system()->api_system()->GetHooksForAPI("runtime")->SetDelegate(
+ std::make_unique<RuntimeHooksDelegate>(messaging_service_.get()));
+
+ scoped_refptr<const Extension> mutable_extension = BuildExtension();
+ RegisterExtension(mutable_extension);
+ extension_ = mutable_extension;
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ script_context_ = CreateScriptContext(context, mutable_extension.get(),
+ Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context_->set_url(extension_->url());
+ bindings_system()->UpdateBindingsForContext(script_context_);
+ }
+ void TearDown() override {
+ script_context_ = nullptr;
+ extension_ = nullptr;
+ messaging_service_.reset();
+ NativeExtensionBindingsSystemUnittest::TearDown();
+ }
+ bool UseStrictIPCMessageSender() override { return true; }
+
+ virtual scoped_refptr<const Extension> BuildExtension() {
+ return ExtensionBuilder("foo").Build();
+ }
+
+ NativeRendererMessagingService* messaging_service() {
+ return messaging_service_.get();
+ }
+ ScriptContext* script_context() { return script_context_; }
+ const Extension* extension() { return extension_.get(); }
+
+ private:
+ std::unique_ptr<NativeRendererMessagingService> messaging_service_;
+
+ ScriptContext* script_context_ = nullptr;
+ scoped_refptr<const Extension> extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionHooksDelegateTest);
+};
+
+// Test chrome.extension messaging methods. Many of these are just aliased to
+// chrome.runtime counterparts, but some others (like sendRequest) are
+// implemented as hooks.
+TEST_F(ExtensionHooksDelegateTest, MessagingSanityChecks) {
+ v8::HandleScope handle_scope(isolate());
+
+ MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
+ SendMessageTester tester(ipc_message_sender(), script_context(), 0,
+ "extension");
+
+ const bool kExpectIncludeTlsChannelId = false;
+ tester.TestConnect("", "", self_target, kExpectIncludeTlsChannelId);
+
+ constexpr char kStandardMessage[] = R"({"data":"hello"})";
+ tester.TestSendMessage("{data: 'hello'}", kStandardMessage, self_target,
+ false, SendMessageTester::CLOSED);
+ tester.TestSendMessage("{data: 'hello'}, function() {}", kStandardMessage,
+ self_target, false, SendMessageTester::OPEN);
+
+ tester.TestSendRequest("{data: 'hello'}", kStandardMessage, self_target,
+ SendMessageTester::CLOSED);
+ tester.TestSendRequest("{data: 'hello'}, function() {}", kStandardMessage,
+ self_target, SendMessageTester::OPEN);
+
+ // Ambiguous argument case ('hi' could be an id or a message); we massage it
+ // into being the message because that's a required argument.
+ tester.TestSendRequest("'hi', function() {}", "\"hi\"", self_target,
+ SendMessageTester::OPEN);
+}
+
+TEST_F(ExtensionHooksDelegateTest, SendRequestDisabled) {
+ // Construct an extension for which sendRequest is disabled (unpacked
+ // extension with an event page).
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder("foo")
+ .SetBackgroundContext(ExtensionBuilder::BackgroundContext::EVENT_PAGE)
+ .SetLocation(Manifest::UNPACKED)
+ .Build();
+ RegisterExtension(extension);
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = AddContext();
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+ bindings_system()->UpdateBindingsForContext(script_context);
+ ASSERT_TRUE(messaging_util::IsSendRequestDisabled(script_context));
+
+ enum AccessBehavior {
+ THROWS,
+ DOESNT_THROW,
+ };
+
+ auto check_access = [context](const char* object, AccessBehavior behavior) {
+ SCOPED_TRACE(object);
+ constexpr char kExpectedError[] =
+ "Uncaught Error: extension.sendRequest, extension.onRequest, and "
+ "extension.onRequestExternal are deprecated. Please use "
+ "runtime.sendMessage, runtime.onMessage, and runtime.onMessageExternal "
+ "instead.";
+ v8::Local<v8::Function> function = FunctionFromString(
+ context, base::StringPrintf("(function() {%s;})", object));
+ if (behavior == THROWS)
+ RunFunctionAndExpectError(function, context, 0, nullptr, kExpectedError);
+ else
+ RunFunction(function, context, 0, nullptr);
+ };
+
+ check_access("chrome.extension.sendRequest", THROWS);
+ check_access("chrome.extension.onRequest", THROWS);
+ check_access("chrome.extension.onRequestExternal", THROWS);
+ check_access("chrome.extension.sendMessage", DOESNT_THROW);
+ check_access("chrome.extension.onMessage", DOESNT_THROW);
+ check_access("chrome.extension.onMessageExternal", DOESNT_THROW);
+}
+
+// Ensure that the extension.sendRequest() method doesn't close the channel if
+// the listener does not reply and also does not return `true` (unlike the
+// runtime.sendMessage() method, which will).
+TEST_F(ExtensionHooksDelegateTest, SendRequestChannelLeftOpenToReplyAsync) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ constexpr char kRegisterListener[] =
+ "(function() {\n"
+ " chrome.extension.onRequest.addListener(\n"
+ " function(message, sender, reply) {});\n"
+ "})";
+ v8::Local<v8::Function> add_listener =
+ FunctionFromString(context, kRegisterListener);
+ RunFunctionOnGlobal(add_listener, context, 0, nullptr);
+
+ const std::string kChannel = "chrome.extension.sendRequest";
+ base::UnguessableToken other_context_id = base::UnguessableToken::Create();
+ const PortId port_id(other_context_id, 0, false);
+
+ ExtensionMsg_TabConnectionInfo tab_connection_info;
+ tab_connection_info.frame_id = 0;
+ const int tab_id = 10;
+ GURL source_url("http://example.com");
+ tab_connection_info.tab.Swap(
+ DictionaryBuilder().Set("tabId", tab_id).Build().get());
+ ExtensionMsg_ExternalConnectionInfo external_connection_info;
+ external_connection_info.target_id = extension()->id();
+ external_connection_info.source_endpoint =
+ MessagingEndpoint::ForExtension(extension()->id());
+ external_connection_info.source_url = source_url;
+ external_connection_info.guest_process_id =
+ content::ChildProcessHost::kInvalidUniqueID;
+ external_connection_info.guest_render_frame_routing_id = 0;
+
+ // Open a receiver for the message.
+ EXPECT_CALL(*ipc_message_sender(),
+ SendOpenMessagePort(MSG_ROUTING_NONE, port_id));
+ messaging_service()->DispatchOnConnect(script_context_set(), port_id,
+ kChannel, tab_connection_info,
+ external_connection_info, nullptr);
+ ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
+ EXPECT_TRUE(
+ messaging_service()->HasPortForTesting(script_context(), port_id));
+
+ // Post the message to the receiver. Since the receiver doesn't respond, the
+ // channel should remain open.
+ messaging_service()->DeliverMessage(script_context_set(), port_id,
+ Message("\"message\"", false), nullptr);
+ ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
+ EXPECT_TRUE(
+ messaging_service()->HasPortForTesting(script_context(), port_id));
+}
+
+// Tests that overriding the runtime equivalents of chrome.extension methods
+// with accessors that throw does not cause a crash on access. Regression test
+// for https://crbug.com/949170.
+TEST_F(ExtensionHooksDelegateTest, RuntimeAliasesCorrupted) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ // Set a trap on chrome.runtime.sendMessage.
+ constexpr char kMutateChromeRuntime[] =
+ R"((function() {
+ Object.defineProperty(
+ chrome.runtime, 'sendMessage',
+ { get() { throw new Error('haha'); } });
+ }))";
+ RunFunctionOnGlobal(FunctionFromString(context, kMutateChromeRuntime),
+ context, 0, nullptr);
+
+ // Touch chrome.extension.sendMessage, which is aliased to the runtime
+ // version. Though an error is thrown, we shouldn't crash.
+ constexpr char kTouchExtensionSendMessage[] =
+ "(function() { chrome.extension.sendMessage; })";
+ RunFunctionOnGlobal(FunctionFromString(context, kTouchExtensionSendMessage),
+ context, 0, nullptr);
+}
+
+// Ensure that HandleGetURL allows extension URLs and doesn't allow arbitrary
+// non-extension URLs. Very similar to RuntimeHooksDeligateTest that tests a
+// similar function.
+TEST_F(ExtensionHooksDelegateTest, GetURL) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ auto get_url = [this, context](const char* args, const GURL& expected_url) {
+ SCOPED_TRACE(base::StringPrintf("Args: `%s`", args));
+ constexpr char kGetUrlTemplate[] =
+ "(function() { return chrome.extension.getURL(%s); })";
+ v8::Local<v8::Function> get_url =
+ FunctionFromString(context, base::StringPrintf(kGetUrlTemplate, args));
+ v8::Local<v8::Value> url = RunFunction(get_url, context, 0, nullptr);
+ ASSERT_FALSE(url.IsEmpty());
+ ASSERT_TRUE(url->IsString());
+ EXPECT_EQ(expected_url.spec(), gin::V8ToString(isolate(), url));
+ };
+
+ get_url("''", extension()->url());
+ get_url("'foo'", extension()->GetResourceURL("foo"));
+ get_url("'/foo'", extension()->GetResourceURL("foo"));
+ get_url("'https://www.google.com'",
+ GURL(extension()->url().spec() + "https://www.google.com"));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/extension_localization_peer.cc b/chromium/chrome/renderer/extensions/extension_localization_peer.cc
new file mode 100644
index 00000000000..f1cc0303af4
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_localization_peer.cc
@@ -0,0 +1,286 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/extension_localization_peer.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "chrome/common/url_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/message_bundle.h"
+#include "ipc/ipc_sender.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+
+ExtensionLocalizationPeer::DataPipeState::DataPipeState()
+ : source_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
+ destination_watcher_(FROM_HERE,
+ mojo::SimpleWatcher::ArmingPolicy::MANUAL) {}
+
+ExtensionLocalizationPeer::DataPipeState::~DataPipeState() = default;
+
+ExtensionLocalizationPeer::ExtensionLocalizationPeer(
+ std::unique_ptr<content::RequestPeer> peer,
+ IPC::Sender* message_sender,
+ const GURL& request_url)
+ : original_peer_(std::move(peer)),
+ message_sender_(message_sender),
+ request_url_(request_url) {}
+
+ExtensionLocalizationPeer::~ExtensionLocalizationPeer() {
+}
+
+// static
+std::unique_ptr<content::RequestPeer>
+ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+ std::unique_ptr<content::RequestPeer> peer,
+ IPC::Sender* message_sender,
+ const std::string& mime_type,
+ const GURL& request_url) {
+ // Return the given |peer| as is if content is not text/css or it doesn't
+ // belong to extension scheme.
+ return (request_url.SchemeIs(extensions::kExtensionScheme) &&
+ base::StartsWith(mime_type, "text/css",
+ base::CompareCase::INSENSITIVE_ASCII))
+ ? base::WrapUnique(new ExtensionLocalizationPeer(
+ std::move(peer), message_sender, request_url))
+ : std::move(peer);
+}
+
+void ExtensionLocalizationPeer::OnUploadProgress(uint64_t position,
+ uint64_t size) {
+ NOTREACHED();
+}
+
+bool ExtensionLocalizationPeer::OnReceivedRedirect(
+ const net::RedirectInfo& redirect_info,
+ network::mojom::URLResponseHeadPtr head) {
+ NOTREACHED();
+ return false;
+}
+
+void ExtensionLocalizationPeer::OnReceivedResponse(
+ network::mojom::URLResponseHeadPtr head) {
+ response_head_ = std::move(head);
+}
+
+void ExtensionLocalizationPeer::OnStartLoadingResponseBody(
+ mojo::ScopedDataPipeConsumerHandle body) {
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kReadingBody;
+ data_pipe_state_.source_handle_ = std::move(body);
+ data_pipe_state_.source_watcher_.Watch(
+ data_pipe_state_.source_handle_.get(),
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&ExtensionLocalizationPeer::OnReadableBody,
+ base::Unretained(this)));
+ data_pipe_state_.source_watcher_.ArmOrNotify();
+}
+
+void ExtensionLocalizationPeer::OnTransferSizeUpdated(int transfer_size_diff) {
+ original_peer_->OnTransferSizeUpdated(transfer_size_diff);
+}
+
+void ExtensionLocalizationPeer::OnCompletedRequest(
+ const network::URLLoaderCompletionStatus& status) {
+ if (completion_status_.has_value()) {
+ // This means that we've already returned error status to the original peer
+ // due to an error on the data pipe.
+ return;
+ }
+ completion_status_ = status;
+
+ if (status.error_code != net::OK) {
+ data_pipe_state_.source_watcher_.Cancel();
+ data_pipe_state_.source_handle_.reset();
+ data_pipe_state_.destination_watcher_.Cancel();
+ data_pipe_state_.destination_handle_.reset();
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kDone;
+ }
+
+ if (data_pipe_state_.body_state_ != DataPipeState::BodyState::kDone) {
+ // Still reading, or sending the body. Wait until all data has been read,
+ // and sent to the |original_peer_|.
+ return;
+ }
+
+ // We've sent all the body to the peer. Complete the request.
+ CompleteRequest();
+}
+
+scoped_refptr<base::TaskRunner> ExtensionLocalizationPeer::GetTaskRunner() {
+ return original_peer_->GetTaskRunner();
+}
+
+void ExtensionLocalizationPeer::OnReadableBody(
+ MojoResult,
+ const mojo::HandleSignalsState&) {
+ DCHECK(data_pipe_state_.source_handle_.is_valid());
+ DCHECK_EQ(DataPipeState::BodyState::kReadingBody,
+ data_pipe_state_.body_state_);
+
+ const void* buffer;
+ uint32_t read_bytes = 0;
+ MojoResult result = data_pipe_state_.source_handle_->BeginReadData(
+ &buffer, &read_bytes, MOJO_READ_DATA_FLAG_NONE);
+
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ data_pipe_state_.source_watcher_.ArmOrNotify();
+ return;
+ }
+
+ if (result == MOJO_RESULT_FAILED_PRECONDITION) {
+ data_pipe_state_.source_watcher_.Cancel();
+ data_pipe_state_.source_handle_.reset();
+ StartSendingBody();
+ return;
+ }
+
+ if (result != MOJO_RESULT_OK) {
+ // Something went wrong.
+ data_pipe_state_.source_watcher_.Cancel();
+ data_pipe_state_.source_handle_.reset();
+ completion_status_ = network::URLLoaderCompletionStatus(net::ERR_FAILED);
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kDone;
+ CompleteRequest();
+ return;
+ }
+
+ data_.append(static_cast<const char*>(buffer), read_bytes);
+
+ result = data_pipe_state_.source_handle_->EndReadData(read_bytes);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ data_pipe_state_.source_watcher_.ArmOrNotify();
+}
+
+void ExtensionLocalizationPeer::StartSendingBody() {
+ DCHECK(!data_pipe_state_.source_handle_.is_valid());
+ DCHECK_EQ(DataPipeState::BodyState::kReadingBody,
+ data_pipe_state_.body_state_);
+
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kSendingBody;
+
+ ReplaceMessages();
+
+ mojo::ScopedDataPipeConsumerHandle consumer_to_send;
+ MojoResult result = mojo::CreateDataPipe(
+ nullptr, &data_pipe_state_.destination_handle_, &consumer_to_send);
+ if (result != MOJO_RESULT_OK) {
+ completion_status_ =
+ network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES);
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kDone;
+ CompleteRequest();
+ return;
+ }
+
+ original_peer_->OnReceivedResponse(std::move(response_head_));
+ original_peer_->OnStartLoadingResponseBody(std::move(consumer_to_send));
+
+ data_pipe_state_.destination_watcher_.Watch(
+ data_pipe_state_.destination_handle_.get(),
+ MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&ExtensionLocalizationPeer::OnWritableBody,
+ base::Unretained(this)));
+ data_pipe_state_.destination_watcher_.ArmOrNotify();
+}
+
+void ExtensionLocalizationPeer::OnWritableBody(
+ MojoResult,
+ const mojo::HandleSignalsState&) {
+ DCHECK(data_pipe_state_.destination_handle_.is_valid());
+ DCHECK_EQ(DataPipeState::BodyState::kSendingBody,
+ data_pipe_state_.body_state_);
+
+ uint32_t available = data_.size() - data_pipe_state_.sent_in_bytes_;
+ MojoResult result = data_pipe_state_.destination_handle_->WriteData(
+ data_.data() + data_pipe_state_.sent_in_bytes_, &available,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ data_pipe_state_.destination_watcher_.ArmOrNotify();
+ return;
+ }
+
+ if (result != MOJO_RESULT_OK) {
+ // The pipe is closed on the receiver side.
+ data_ = std::string();
+ data_pipe_state_.destination_watcher_.Cancel();
+ data_pipe_state_.destination_handle_.reset();
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kDone;
+ completion_status_ = network::URLLoaderCompletionStatus(net::ERR_FAILED);
+ CompleteRequest();
+ return;
+ }
+
+ data_pipe_state_.sent_in_bytes_ += available;
+
+ if (data_pipe_state_.sent_in_bytes_ == data_.size()) {
+ // We sent all of the data.
+ data_ = std::string();
+ data_pipe_state_.destination_watcher_.Cancel();
+ data_pipe_state_.destination_handle_.reset();
+ data_pipe_state_.body_state_ = DataPipeState::BodyState::kDone;
+ if (completion_status_.has_value())
+ CompleteRequest();
+ return;
+ }
+
+ // Wait until the pipe is ready to send the next chunk of data.
+ data_pipe_state_.destination_watcher_.ArmOrNotify();
+}
+
+void ExtensionLocalizationPeer::ReplaceMessages() {
+ if (!message_sender_ || data_.empty())
+ return;
+
+ if (!request_url_.is_valid())
+ return;
+
+ std::string extension_id = request_url_.host();
+ extensions::L10nMessagesMap* l10n_messages =
+ extensions::GetL10nMessagesMap(extension_id);
+ if (!l10n_messages) {
+ extensions::L10nMessagesMap messages;
+ message_sender_->Send(new ExtensionHostMsg_GetMessageBundle(
+ extension_id, &messages));
+
+ // Save messages we got, so we don't have to ask again.
+ // Messages map is never empty, it contains at least @@extension_id value.
+ extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
+ *extensions::GetExtensionToL10nMessagesMap();
+ l10n_messages_map[extension_id] = messages;
+
+ l10n_messages = extensions::GetL10nMessagesMap(extension_id);
+ }
+
+ std::string error;
+ if (extensions::MessageBundle::ReplaceMessagesWithExternalDictionary(
+ *l10n_messages, &data_, &error)) {
+ data_.resize(data_.size());
+ }
+}
+
+void ExtensionLocalizationPeer::CompleteRequest() {
+ DCHECK(completion_status_.has_value());
+ // Body should have been sent to the origial peer at this point when it's
+ // from a data pipe.
+ DCHECK_EQ(DataPipeState::BodyState::kDone, data_pipe_state_.body_state_);
+
+ if (completion_status_->error_code != net::OK) {
+ // We failed to load the resource.
+ network::URLLoaderCompletionStatus aborted_status =
+ completion_status_.value();
+ aborted_status.error_code = net::ERR_ABORTED;
+ original_peer_->OnCompletedRequest(aborted_status);
+ return;
+ }
+
+ original_peer_->OnCompletedRequest(completion_status_.value());
+}
diff --git a/chromium/chrome/renderer/extensions/extension_localization_peer.h b/chromium/chrome/renderer/extensions/extension_localization_peer.h
new file mode 100644
index 00000000000..757c3baf65a
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_localization_peer.h
@@ -0,0 +1,144 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_EXTENSION_LOCALIZATION_PEER_H_
+#define CHROME_RENDERER_EXTENSIONS_EXTENSION_LOCALIZATION_PEER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "content/public/renderer/request_peer.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace IPC {
+class Sender;
+}
+
+// The ExtensionLocalizationPeer is a proxy to a
+// content::RequestPeer instance. It is used to pre-process
+// CSS files requested by extensions to replace localization templates with the
+// appropriate localized strings.
+//
+// Call the factory method CreateExtensionLocalizationPeer() to obtain an
+// instance of ExtensionLocalizationPeer based on the original Peer.
+//
+// The main flow of method calls is like this:
+// 1. OnReceivedResponse() when the response header is ready.
+// 2-a. OnStartLoadingResponseBody() when the body streaming starts. It starts
+// to read the body from the data pipe. After finishing to read the whole
+// body, this class replaces the body using the message catalogs, sends the
+// response header, sends a data pipe to the original peer, and starts to
+// send the body over the data pipe.
+// 2-b. OnCompletedRequest() when the final status is available. The status code
+// is stored as a member.
+// 3. CompleteRequest() when both of 2-a and 2-b finish. Sends the stored
+// status code to the original peer.
+//
+// Note that OnCompletedRequest() can be called at any time, even before
+// OnReceivedResponse().
+class ExtensionLocalizationPeer : public content::RequestPeer {
+ public:
+ ~ExtensionLocalizationPeer() override;
+
+ static std::unique_ptr<content::RequestPeer> CreateExtensionLocalizationPeer(
+ std::unique_ptr<content::RequestPeer> peer,
+ IPC::Sender* message_sender,
+ const std::string& mime_type,
+ const GURL& request_url);
+
+ // content::RequestPeer methods.
+ void OnUploadProgress(uint64_t position, uint64_t size) override;
+ bool OnReceivedRedirect(const net::RedirectInfo& redirect_info,
+ network::mojom::URLResponseHeadPtr head) override;
+ void OnReceivedResponse(network::mojom::URLResponseHeadPtr head) override;
+ void OnStartLoadingResponseBody(
+ mojo::ScopedDataPipeConsumerHandle body) override;
+ void OnTransferSizeUpdated(int transfer_size_diff) override;
+ void OnCompletedRequest(
+ const network::URLLoaderCompletionStatus& status) override;
+ scoped_refptr<base::TaskRunner> GetTaskRunner() override;
+
+ private:
+ friend class ExtensionLocalizationPeerTest;
+
+ // Use CreateExtensionLocalizationPeer to create an instance.
+ ExtensionLocalizationPeer(std::unique_ptr<content::RequestPeer> peer,
+ IPC::Sender* message_sender,
+ const GURL& request_url);
+
+ void OnReadableBody(MojoResult, const mojo::HandleSignalsState&);
+ void StartSendingBody();
+ void OnWritableBody(MojoResult, const mojo::HandleSignalsState&);
+
+ // Loads message catalogs, and replaces all __MSG_some_name__ templates within
+ // loaded file.
+ void ReplaceMessages();
+
+ void CompleteRequest();
+
+ // Original peer that handles the request once we are done processing data_.
+ std::unique_ptr<content::RequestPeer> original_peer_;
+
+ // We just pass though the response info. This holds the copy of the original.
+ network::mojom::URLResponseHeadPtr response_head_;
+
+ struct DataPipeState {
+ DataPipeState();
+ ~DataPipeState();
+
+ // Data pipe for reading the body which is passed on
+ // OnStartLoadingResponseBody() and its watcher. When reading the body
+ // reaches to the end, the handle will be reset.
+ mojo::ScopedDataPipeConsumerHandle source_handle_;
+ mojo::SimpleWatcher source_watcher_;
+
+ // Data pipe for pushing the body to the |original_peer_| and its
+ // watcher.
+ mojo::ScopedDataPipeProducerHandle destination_handle_;
+ mojo::SimpleWatcher destination_watcher_;
+
+ // Size sent to the destination.
+ size_t sent_in_bytes_ = 0;
+
+ // Shows the state of streaming the body to the |original_peer_|.
+ enum class BodyState {
+ // Before getting |source_handle_|.
+ kInitial,
+ // Reading the body from |source_handle_|.
+ kReadingBody,
+ // Sending the body via |destination_handle_|.
+ kSendingBody,
+ // Sent all the body to |destination_handle_|.
+ kDone
+ };
+ BodyState body_state_ = BodyState::kInitial;
+ };
+
+ DataPipeState data_pipe_state_;
+
+ // Set when OnCompletedRequest() is called, and sent to the original peer on
+ // CompleteRequest().
+ base::Optional<network::URLLoaderCompletionStatus> completion_status_;
+
+ // Sends ExtensionHostMsg_GetMessageBundle message to the browser to fetch
+ // message catalog.
+ IPC::Sender* message_sender_;
+
+ // Buffer for incoming data. We wait until OnCompletedRequest before using it.
+ std::string data_;
+
+ // Original request URL.
+ GURL request_url_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionLocalizationPeer);
+};
+
+#endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_LOCALIZATION_PEER_H_
diff --git a/chromium/chrome/renderer/extensions/extension_localization_peer_unittest.cc b/chromium/chrome/renderer/extensions/extension_localization_peer_unittest.cc
new file mode 100644
index 00000000000..96c8519c937
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_localization_peer_unittest.cc
@@ -0,0 +1,300 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/extension_localization_peer.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "extensions/common/message_bundle.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_sync_message.h"
+#include "mojo/public/cpp/system/data_pipe_utils.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/redirect_info.h"
+#include "net/url_request/url_request_status.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::StrEq;
+using testing::Return;
+
+static const char* const kExtensionUrl_1 =
+ "chrome-extension://some_id/popup.css";
+
+static const char* const kExtensionUrl_2 =
+ "chrome-extension://some_id2/popup.css";
+
+static const char* const kExtensionUrl_3 =
+ "chrome-extension://some_id3/popup.css";
+
+void MessageDeleter(IPC::Message* message) {
+ delete message;
+}
+
+class MockIpcMessageSender : public IPC::Sender {
+ public:
+ MockIpcMessageSender() {
+ ON_CALL(*this, Send(_))
+ .WillByDefault(DoAll(Invoke(MessageDeleter), Return(true)));
+ }
+
+ ~MockIpcMessageSender() override {}
+
+ MOCK_METHOD1(Send, bool(IPC::Message* message));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockIpcMessageSender);
+};
+
+class MockRequestPeer : public content::RequestPeer {
+ public:
+ MockRequestPeer()
+ : body_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {
+ }
+ ~MockRequestPeer() override {}
+
+ MOCK_METHOD2(OnUploadProgress, void(uint64_t position, uint64_t size));
+ MOCK_METHOD2(OnReceivedRedirect,
+ bool(const net::RedirectInfo& redirect_info,
+ network::mojom::URLResponseHeadPtr head));
+ MOCK_METHOD1(OnReceivedResponse,
+ void(network::mojom::URLResponseHeadPtr head));
+ void OnStartLoadingResponseBody(
+ mojo::ScopedDataPipeConsumerHandle body) override {
+ body_handle_ = std::move(body);
+ body_watcher_.Watch(
+ body_handle_.get(),
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&MockRequestPeer::OnReadable,
+ base::Unretained(this)));
+ }
+ MOCK_METHOD2(OnDownloadedData, void(int len, int encoded_data_length));
+ MOCK_METHOD1(OnReceivedDataInternal, void(std::string data));
+ MOCK_METHOD1(OnTransferSizeUpdated, void(int transfer_size_diff));
+ MOCK_METHOD1(OnCompletedRequest,
+ void(const network::URLLoaderCompletionStatus& status));
+ scoped_refptr<base::TaskRunner> GetTaskRunner() override {
+ NOTREACHED();
+ return nullptr;
+ }
+
+ void RunUntilBodyBecomesReady() {
+ base::RunLoop loop;
+ EXPECT_FALSE(wait_for_body_callback_);
+ wait_for_body_callback_ = loop.QuitClosure();
+ loop.Run();
+ }
+
+ private:
+ void OnReadable(MojoResult, const mojo::HandleSignalsState&) {
+ uint32_t available_bytes = 64 * 1024;
+ std::vector<char> buffer(available_bytes);
+ MojoResult result = body_handle_->ReadData(buffer.data(), &available_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+
+ if (result == MOJO_RESULT_SHOULD_WAIT)
+ return;
+
+ if (result == MOJO_RESULT_FAILED_PRECONDITION) {
+ body_watcher_.Cancel();
+ OnReceivedDataInternal(body_);
+ DCHECK(wait_for_body_callback_);
+ std::move(wait_for_body_callback_).Run();
+ return;
+ }
+
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ buffer.resize(available_bytes);
+ body_.append(buffer.begin(), buffer.end());
+ }
+
+ std::string body_;
+ mojo::SimpleWatcher body_watcher_;
+ mojo::ScopedDataPipeConsumerHandle body_handle_;
+ base::OnceClosure wait_for_body_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockRequestPeer);
+};
+
+} // namespace
+
+class ExtensionLocalizationPeerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ sender_.reset(new MockIpcMessageSender());
+ }
+
+ void SetUpExtensionLocalizationPeer(const std::string& mime_type,
+ const GURL& request_url) {
+ auto original_peer =
+ std::make_unique<testing::StrictMock<MockRequestPeer>>();
+ original_peer_ = original_peer.get();
+ auto extension_peer =
+ ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+ std::move(original_peer), sender_.get(), mime_type, request_url);
+ filter_peer_.reset(
+ static_cast<ExtensionLocalizationPeer*>(extension_peer.release()));
+ }
+
+ std::string GetData() { return filter_peer_->data_; }
+
+ void SetData(const std::string& data) {
+ MojoCreateDataPipeOptions options;
+ options.struct_size = sizeof(MojoCreateDataPipeOptions);
+ options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = data.size();
+ mojo::ScopedDataPipeProducerHandle producer;
+ mojo::ScopedDataPipeConsumerHandle consumer;
+ MojoResult result = mojo::CreateDataPipe(&options, &producer, &consumer);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ filter_peer_->OnStartLoadingResponseBody(std::move(consumer));
+ mojo::BlockingCopyFromString(data, producer);
+ producer.reset();
+ }
+
+ mojo::ScopedDataPipeConsumerHandle CreateEmptyBodyDataPipe() const {
+ mojo::ScopedDataPipeConsumerHandle consumer;
+ mojo::ScopedDataPipeProducerHandle producer;
+ MojoResult result = mojo::CreateDataPipe(nullptr, &producer, &consumer);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ return consumer;
+ }
+
+ base::test::TaskEnvironment scoped_environment_;
+ std::unique_ptr<MockIpcMessageSender> sender_;
+ MockRequestPeer* original_peer_;
+ std::unique_ptr<ExtensionLocalizationPeer> filter_peer_;
+};
+
+TEST_F(ExtensionLocalizationPeerTest, CreateWithWrongMimeType) {
+ std::unique_ptr<content::RequestPeer> peer =
+ ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+ nullptr, sender_.get(), "text/html", GURL(kExtensionUrl_1));
+ EXPECT_EQ(nullptr, peer);
+}
+
+TEST_F(ExtensionLocalizationPeerTest, CreateWithValidInput) {
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_1));
+ EXPECT_TRUE(NULL != filter_peer_.get());
+}
+
+MATCHER_P(IsURLRequestEqual, status, "") { return arg.status() == status; }
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestBadURLRequestStatus) {
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_1));
+
+ // This test simulates completion before receiving the response header.
+ network::URLLoaderCompletionStatus status(net::ERR_ABORTED);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(status));
+
+ filter_peer_->OnCompletedRequest(status);
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestEmptyData) {
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_1));
+
+ EXPECT_CALL(*original_peer_, OnReceivedDataInternal(std::string()));
+ EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+ network::URLLoaderCompletionStatus status(net::OK);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(status));
+
+ filter_peer_->OnStartLoadingResponseBody(CreateEmptyBodyDataPipe());
+ filter_peer_->OnCompletedRequest(status);
+ original_peer_->RunUntilBodyBecomesReady();
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestNoCatalogs) {
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_1));
+
+ const std::string kExpectedData = "some text";
+ EXPECT_CALL(*sender_, Send(_));
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_)).Times(1);
+ EXPECT_CALL(*original_peer_, OnReceivedDataInternal(kExpectedData)).Times(1);
+ network::URLLoaderCompletionStatus status(net::OK);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(status)).Times(1);
+
+ SetData(kExpectedData);
+ filter_peer_->OnCompletedRequest(status);
+ original_peer_->RunUntilBodyBecomesReady();
+
+ // Test if Send gets called again (it shouldn't be) when first call returned
+ // an empty dictionary.
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_1));
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_)).Times(1);
+ EXPECT_CALL(*original_peer_, OnReceivedDataInternal(kExpectedData)).Times(1);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(status)).Times(1);
+ SetData(kExpectedData);
+ filter_peer_->OnCompletedRequest(status);
+ original_peer_->RunUntilBodyBecomesReady();
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestWithCatalogs) {
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_2));
+
+ extensions::L10nMessagesMap messages;
+ messages.insert(std::make_pair("text", "new text"));
+ extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
+ *extensions::GetExtensionToL10nMessagesMap();
+ l10n_messages_map["some_id2"] = messages;
+
+ // We already have messages in memory, Send will be skipped.
+ EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+ // __MSG_text__ gets replaced with "new text".
+ std::string data("some new text");
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+ EXPECT_CALL(*original_peer_, OnReceivedDataInternal(data));
+
+ network::URLLoaderCompletionStatus status(net::OK);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(status));
+
+ SetData("some __MSG_text__");
+ filter_peer_->OnCompletedRequest(status);
+ original_peer_->RunUntilBodyBecomesReady();
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestReplaceMessagesFails) {
+ SetUpExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_3));
+
+ extensions::L10nMessagesMap messages;
+ messages.insert(std::make_pair("text", "new text"));
+ extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
+ *extensions::GetExtensionToL10nMessagesMap();
+ l10n_messages_map["some_id3"] = messages;
+
+ std::string message("some __MSG_missing_message__");
+
+ // We already have messages in memory, Send will be skipped.
+ EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+ // __MSG_missing_message__ is missing, so message stays the same.
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+ EXPECT_CALL(*original_peer_, OnReceivedDataInternal(message));
+
+ network::URLLoaderCompletionStatus status(net::OK);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(status));
+
+ SetData(message);
+ filter_peer_->OnCompletedRequest(status);
+ original_peer_->RunUntilBodyBecomesReady();
+}
diff --git a/chromium/chrome/renderer/extensions/extension_process_policy.cc b/chromium/chrome/renderer/extensions/extension_process_policy.cc
new file mode 100644
index 00000000000..38342dde6e8
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_process_policy.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/extension_process_policy.h"
+
+#include "base/strings/string_util.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest_handlers/app_isolation_info.h"
+#include "extensions/common/switches.h"
+
+namespace extensions {
+
+const extensions::Extension* GetNonBookmarkAppExtension(
+ const ExtensionSet& extensions,
+ const GURL& url) {
+ // Exclude bookmark apps, which do not use the app process model.
+ const extensions::Extension* extension =
+ extensions.GetExtensionOrAppByURL(url);
+ if (extension && extension->from_bookmark())
+ extension = NULL;
+ return extension;
+}
+
+bool CrossesExtensionProcessBoundary(const ExtensionSet& extensions,
+ const GURL& old_url,
+ const GURL& new_url) {
+ const extensions::Extension* old_url_extension =
+ GetNonBookmarkAppExtension(extensions, old_url);
+ const extensions::Extension* new_url_extension =
+ GetNonBookmarkAppExtension(extensions, new_url);
+
+ // If there are no extensions associated with either url, we check if the new
+ // url points to an extension origin. If it does, fork - extension
+ // installation should not be a factor.
+ if (!old_url_extension && !new_url_extension) {
+ // Hypothetically, we could also do an origin check here to make sure that
+ // the two urls point two different extensions, but it's not really
+ // necesary since we know there wasn't an associated extension with the old
+ // url.
+ return new_url.SchemeIs(kExtensionScheme);
+ }
+
+ return old_url_extension != new_url_extension;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/extension_process_policy.h b/chromium/chrome/renderer/extensions/extension_process_policy.h
new file mode 100644
index 00000000000..d7605f388b2
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_process_policy.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_POLICY_H_
+#define CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_POLICY_H_
+
+class GURL;
+
+namespace extensions {
+
+class Extension;
+class ExtensionSet;
+
+// Returns the extension for the given URL. Excludes extension objects for
+// bookmark apps, which do not use the app process model.
+const Extension* GetNonBookmarkAppExtension(const ExtensionSet& extensions,
+ const GURL& url);
+
+// Check if navigating a toplevel page from |old_url| to |new_url| would cross
+// an extension process boundary (e.g. navigating from a web URL into an
+// extension URL).
+bool CrossesExtensionProcessBoundary(const ExtensionSet& extensions,
+ const GURL& old_url,
+ const GURL& new_url);
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_POLICY_H_
diff --git a/chromium/chrome/renderer/extensions/extension_process_policy_unittest.cc b/chromium/chrome/renderer/extensions/extension_process_policy_unittest.cc
new file mode 100644
index 00000000000..af687354e7f
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/extension_process_policy_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/extension_process_policy.h"
+
+#include "components/crx_file/id_util.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+scoped_refptr<const Extension> CreateExtension(const std::string& id_seed) {
+ std::unique_ptr<base::DictionaryValue> manifest =
+ DictionaryBuilder()
+ .Set("name", "test")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Build();
+ return ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .SetID(crx_file::id_util::GenerateId(id_seed))
+ .Build();
+}
+
+} // namespace
+
+TEST(CrossesExtensionBoundaryTest, InstalledExtensions) {
+ ExtensionSet extensions;
+ scoped_refptr<const Extension> extension1 = CreateExtension("a");
+ scoped_refptr<const Extension> extension2 = CreateExtension("b");
+ extensions.Insert(extension1);
+ extensions.Insert(extension2);
+
+ GURL web_url("https://example.com");
+
+ EXPECT_TRUE(
+ CrossesExtensionProcessBoundary(extensions, web_url, extension1->url()));
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(extensions, extension1->url(),
+ extension2->url()));
+ EXPECT_TRUE(
+ CrossesExtensionProcessBoundary(extensions, extension1->url(), web_url));
+}
+
+TEST(CrossesExtensionBoundaryTest, UninstalledExtensions) {
+ ExtensionSet extensions;
+ scoped_refptr<const Extension> extension1 = CreateExtension("a");
+ extensions.Insert(extension1);
+ GURL web_url("https://example.com");
+ GURL non_existent_extension_url("chrome-extension://" + std::string(32, 'a') +
+ "/foo");
+
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(extensions, web_url,
+ non_existent_extension_url));
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(extensions, extension1->url(),
+ non_existent_extension_url));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.cc b/chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.cc
new file mode 100644
index 00000000000..512ba7cb34d
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/file_browser_handler_custom_bindings.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_dom_file_system.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace extensions {
+
+FileBrowserHandlerCustomBindings::FileBrowserHandlerCustomBindings(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void FileBrowserHandlerCustomBindings::AddRoutes() {
+ RouteHandlerFunction(
+ "GetExternalFileEntry", "fileBrowserHandler",
+ base::BindRepeating(
+ &FileBrowserHandlerCustomBindings::GetExternalFileEntryCallback,
+ base::Unretained(this)));
+}
+
+void FileBrowserHandlerCustomBindings::GetExternalFileEntry(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ ScriptContext* context) {
+// TODO(zelidrag): Make this magic work on other platforms when file browser
+// matures enough on ChromeOS.
+#if defined(OS_CHROMEOS)
+ CHECK(args.Length() == 1);
+ CHECK(args[0]->IsObject());
+ v8::Local<v8::Object> file_def = args[0].As<v8::Object>();
+ v8::Isolate* isolate = args.GetIsolate();
+ v8::Local<v8::Context> v8_context = context->v8_context();
+
+ std::string file_system_name(*v8::String::Utf8Value(
+ isolate, file_def
+ ->Get(v8_context, v8::String::NewFromUtf8(
+ isolate, "fileSystemName",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())
+ .ToLocalChecked()));
+ GURL file_system_root(*v8::String::Utf8Value(
+ isolate, file_def
+ ->Get(v8_context, v8::String::NewFromUtf8(
+ isolate, "fileSystemRoot",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())
+ .ToLocalChecked()));
+ std::string file_full_path(*v8::String::Utf8Value(
+ isolate, file_def
+ ->Get(v8_context, v8::String::NewFromUtf8(
+ isolate, "fileFullPath",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())
+ .ToLocalChecked()));
+ bool is_directory =
+ file_def
+ ->Get(v8_context,
+ v8::String::NewFromUtf8(isolate, "fileIsDirectory",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())
+ .ToLocalChecked()
+ ->BooleanValue(isolate);
+ blink::WebDOMFileSystem::EntryType entry_type =
+ is_directory ? blink::WebDOMFileSystem::kEntryTypeDirectory
+ : blink::WebDOMFileSystem::kEntryTypeFile;
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::FrameForContext(v8_context);
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::Create(
+ webframe, blink::kWebFileSystemTypeExternal,
+ blink::WebString::FromUTF8(file_system_name), file_system_root)
+ .CreateV8Entry(blink::WebString::FromUTF8(file_full_path),
+ entry_type, args.Holder(), isolate));
+#endif
+}
+
+void FileBrowserHandlerCustomBindings::GetExternalFileEntryCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetExternalFileEntry(args, context());
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.h b/chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.h
new file mode 100644
index 00000000000..5da8afa3586
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/file_browser_handler_custom_bindings.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_H_
+#define CHROME_RENDERER_EXTENSIONS_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+// Custom bindings for the fileBrowserHandler API.
+class FileBrowserHandlerCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit FileBrowserHandlerCustomBindings(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ // Public static implementation of GetExternalFileEntry() for use by
+ // FileManagerPrivate native handler.
+ static void GetExternalFileEntry(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ ScriptContext* context);
+
+ private:
+ void GetExternalFileEntryCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerCustomBindings);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_H_
diff --git a/chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.cc b/chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.cc
new file mode 100644
index 00000000000..c57d0b123a0
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/file_manager_private_custom_bindings.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "chrome/renderer/extensions/file_browser_handler_custom_bindings.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_dom_file_system.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace extensions {
+
+FileManagerPrivateCustomBindings::FileManagerPrivateCustomBindings(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void FileManagerPrivateCustomBindings::AddRoutes() {
+ RouteHandlerFunction(
+ "GetFileSystem", "fileManagerPrivate",
+ base::BindRepeating(&FileManagerPrivateCustomBindings::GetFileSystem,
+ base::Unretained(this)));
+ RouteHandlerFunction(
+ "GetExternalFileEntry", "fileManagerPrivate",
+ base::BindRepeating(
+ &FileManagerPrivateCustomBindings::GetExternalFileEntry,
+ base::Unretained(this)));
+ RouteHandlerFunction(
+ "GetEntryURL", "fileManagerPrivate",
+ base::BindRepeating(&FileManagerPrivateCustomBindings::GetEntryURL,
+ base::Unretained(this)));
+}
+
+void FileManagerPrivateCustomBindings::GetFileSystem(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DCHECK(args.Length() == 2);
+ DCHECK(args[0]->IsString());
+ DCHECK(args[1]->IsString());
+ v8::Isolate* isolate = args.GetIsolate();
+ std::string name(*v8::String::Utf8Value(isolate, args[0]));
+ std::string root_url(*v8::String::Utf8Value(isolate, args[1]));
+
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::FrameForContext(context()->v8_context());
+ DCHECK(webframe);
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::Create(
+ webframe, blink::kWebFileSystemTypeExternal,
+ blink::WebString::FromUTF8(name), GURL(root_url))
+ .ToV8Value(context()->v8_context()->Global(), isolate));
+}
+
+void FileManagerPrivateCustomBindings::GetExternalFileEntry(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ FileBrowserHandlerCustomBindings::GetExternalFileEntry(args, context());
+}
+
+void FileManagerPrivateCustomBindings::GetEntryURL(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1);
+ CHECK(args[0]->IsObject());
+ const blink::WebURL& url =
+ blink::WebDOMFileSystem::CreateFileSystemURL(args[0]);
+ args.GetReturnValue().Set(v8_helpers::ToV8StringUnsafe(
+ args.GetIsolate(), url.GetString().Utf8().c_str()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.h b/chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.h
new file mode 100644
index 00000000000..9d2b88839dd
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/file_manager_private_custom_bindings.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_FILE_MANAGER_PRIVATE_CUSTOM_BINDINGS_H_
+#define CHROME_RENDERER_EXTENSIONS_FILE_MANAGER_PRIVATE_CUSTOM_BINDINGS_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+// Custom bindings for the fileManagerPrivate API.
+class FileManagerPrivateCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit FileManagerPrivateCustomBindings(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ private:
+ void GetFileSystem(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetExternalFileEntry(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetEntryURL(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(FileManagerPrivateCustomBindings);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_FILE_MANAGER_PRIVATE_CUSTOM_BINDINGS_H_
diff --git a/chromium/chrome/renderer/extensions/i18n_hooks_delegate_unittest.cc b/chromium/chrome/renderer/extensions/i18n_hooks_delegate_unittest.cc
new file mode 100644
index 00000000000..0e37101c92c
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/i18n_hooks_delegate_unittest.cc
@@ -0,0 +1,110 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/i18n_hooks_delegate.h"
+
+#include "base/strings/stringprintf.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/message_bundle.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_extension_bindings_system_test_base.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+using I18nHooksDelegateTest = NativeExtensionBindingsSystemUnittest;
+
+// NOTE(devlin): This test lives in //chrome (rather than //extensions) since
+// the feature is defined at the chrome level (in
+// chrome/common/extensions/api/i18n.json). However, all the custom bindings
+// for i18n live at the //extensions level. We should move these to all be in
+// the same location.
+TEST_F(I18nHooksDelegateTest, TestI18nGetMessage) {
+ scoped_refptr<const Extension> extension = ExtensionBuilder("foo").Build();
+ RegisterExtension(extension);
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ ScriptContext* script_context = CreateScriptContext(
+ context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context->set_url(extension->url());
+ bindings_system()->UpdateBindingsForContext(script_context);
+
+ // In practice, messages will be retrieved from the browser process on first
+ // request. Since this is a unittest, pre-populate the message bundle.
+ L10nMessagesMap messages = {
+ {"simple", "simple message"},
+ {"one_placeholder", "placeholder $1 end"},
+ {"multi_placeholders", "placeholder $1 and $2 end"},
+ {"special_characters", "< Hello $1 World &gt;"}};
+ GetExtensionToL10nMessagesMap()->emplace(extension->id(), messages);
+
+ auto run_get_message = [context](const char* args) {
+ SCOPED_TRACE(args);
+ constexpr char kRunGetMessageFunction[] =
+ "(function() { return chrome.i18n.getMessage(%s); })";
+ v8::Local<v8::Function> function = FunctionFromString(
+ context, base::StringPrintf(kRunGetMessageFunction, args));
+ v8::Local<v8::Value> result = RunFunction(function, context, 0, nullptr);
+ return V8ToString(result, context);
+ };
+
+ // Simple tests.
+
+ EXPECT_EQ(R"("simple message")", run_get_message("'simple'"));
+ EXPECT_EQ(R"("placeholder foo end")",
+ run_get_message("'one_placeholder', 'foo'"));
+ EXPECT_EQ(R"("placeholder foo end")",
+ run_get_message("'one_placeholder', ['foo']"));
+ EXPECT_EQ(R"("placeholder foo and bar end")",
+ run_get_message("'multi_placeholders', ['foo', 'bar']"));
+ EXPECT_EQ(R"("\u003C Hello \u003Cbr> World &gt;")",
+ run_get_message("'special_characters', ['<br>'], {}"));
+ EXPECT_EQ(
+ R"("\u003C Hello \u003Cbr> World &gt;")",
+ run_get_message("'special_characters', ['<br>'], {escapeLt: false}"));
+ EXPECT_EQ(
+ R"("&lt; Hello \u003Cbr> World &gt;")",
+ run_get_message("'special_characters', ['<br>'], {escapeLt: true}"));
+
+ // We place the somewhat-arbitrary (but documented) limit of 9 substitutions
+ // on the call.
+ EXPECT_EQ("undefined",
+ run_get_message("'one_placeholder',"
+ "['one', 'two', 'three', 'four', 'five', 'six',"
+ " 'seven', 'eight', 'nine', 'ten']"));
+
+ // Oddities. All of these should probably behave differently. These tests are
+ // more for documentation than for desirable functionality.
+
+ // Non-string values passed in the array of placeholders will be implicitly
+ // converted to strings...
+ EXPECT_EQ(R"("placeholder [object Object] end")",
+ run_get_message("'one_placeholder', [{}]"));
+ // ... While non-string values passed as a single placeholder are silently
+ // ignored.
+ EXPECT_EQ(R"("placeholder end")", run_get_message("'one_placeholder', {}"));
+ // And values can throw errors (which are silently caught) in string
+ // conversions, in which case the value is silently ignored.
+ EXPECT_EQ(R"("placeholder end")",
+ run_get_message("'one_placeholder',"
+ "[{toString() { throw new Error('haha'); } }]"));
+ EXPECT_EQ("undefined",
+ run_get_message("'one_placeholder',"
+ "(function() {"
+ " var x = [];"
+ " Object.defineProperty(x, 0, {"
+ " get() { throw new Error('haha'); }"
+ " });"
+ " return x;"
+ " })()"));
+ EXPECT_EQ(
+ R"("placeholder foo end")",
+ run_get_message("'one_placeholder',"
+ "[{toString() { throw new Error('haha'); } }, 'foo']"));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/media_galleries_custom_bindings.cc b/chromium/chrome/renderer/extensions/media_galleries_custom_bindings.cc
new file mode 100644
index 00000000000..591248853cb
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/media_galleries_custom_bindings.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/media_galleries_custom_bindings.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_dom_file_system.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+MediaGalleriesCustomBindings::MediaGalleriesCustomBindings(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void MediaGalleriesCustomBindings::AddRoutes() {
+ RouteHandlerFunction(
+ "GetMediaFileSystemObject", "mediaGalleries",
+ base::BindRepeating(
+ &MediaGalleriesCustomBindings::GetMediaFileSystemObject,
+ base::Unretained(this)));
+}
+
+// FileSystemObject GetMediaFileSystem(string file_system_url): construct
+// a file system object from a file system url.
+void MediaGalleriesCustomBindings::GetMediaFileSystemObject(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsString());
+
+ std::string fs_mount(*v8::String::Utf8Value(args.GetIsolate(), args[0]));
+ CHECK(!fs_mount.empty());
+
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::FrameForCurrentContext();
+ const GURL origin =
+ url::Origin(webframe->GetDocument().GetSecurityOrigin()).GetURL();
+ std::string fs_name =
+ storage::GetFileSystemName(origin, storage::kFileSystemTypeExternal);
+ fs_name.append("_");
+ fs_name.append(fs_mount);
+ const GURL root_url(
+ storage::GetExternalFileSystemRootURIString(origin, fs_mount));
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::Create(
+ webframe, blink::kWebFileSystemTypeExternal,
+ blink::WebString::FromUTF8(fs_name), root_url)
+ .ToV8Value(context()->v8_context()->Global(), args.GetIsolate()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/media_galleries_custom_bindings.h b/chromium/chrome/renderer/extensions/media_galleries_custom_bindings.h
new file mode 100644
index 00000000000..18b4d1daf45
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/media_galleries_custom_bindings.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_MEDIA_GALLERIES_CUSTOM_BINDINGS_H_
+#define CHROME_RENDERER_EXTENSIONS_MEDIA_GALLERIES_CUSTOM_BINDINGS_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+// Implements custom bindings for the media galleries API.
+class MediaGalleriesCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit MediaGalleriesCustomBindings(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ private:
+ void GetMediaFileSystemObject(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(MediaGalleriesCustomBindings);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_MEDIA_GALLERIES_CUSTOM_BINDINGS_H_
diff --git a/chromium/chrome/renderer/extensions/notifications_native_handler.cc b/chromium/chrome/renderer/extensions/notifications_native_handler.cc
new file mode 100644
index 00000000000..22c9fd0f373
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/notifications_native_handler.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/notifications_native_handler.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/common/extensions/api/notifications/notification_style.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/data_object_builder.h"
+#include "ui/base/layout.h"
+
+namespace extensions {
+
+NotificationsNativeHandler::NotificationsNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void NotificationsNativeHandler::AddRoutes() {
+ RouteHandlerFunction(
+ "GetNotificationImageSizes", "notifications",
+ base::BindRepeating(
+ &NotificationsNativeHandler::GetNotificationImageSizes,
+ base::Unretained(this)));
+}
+
+void NotificationsNativeHandler::GetNotificationImageSizes(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
+
+ float scale_factor =
+ ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
+
+ v8::Isolate* isolate = GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+
+ struct {
+ const char* key;
+ const gfx::Size& size;
+ } entries[] = {
+ {"icon", bitmap_sizes.icon_size},
+ {"image", bitmap_sizes.image_size},
+ {"buttonIcon", bitmap_sizes.button_icon_size},
+ {"appIconMask", bitmap_sizes.app_icon_mask_size},
+ };
+
+ gin::DataObjectBuilder builder(isolate);
+ builder.Set("scaleFactor", scale_factor);
+ for (const auto& entry : entries) {
+ builder.Set(entry.key, gin::DataObjectBuilder(isolate)
+ .Set("width", entry.size.width())
+ .Set("height", entry.size.height())
+ .Build());
+ }
+
+ args.GetReturnValue().Set(builder.Build());
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/notifications_native_handler.h b/chromium/chrome/renderer/extensions/notifications_native_handler.h
new file mode 100644
index 00000000000..4ab7e8c1f2f
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/notifications_native_handler.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_NOTIFICATIONS_NATIVE_HANDLER_H_
+#define CHROME_RENDERER_EXTENSIONS_NOTIFICATIONS_NATIVE_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+class NotificationsNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit NotificationsNativeHandler(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ private:
+ // This implements notifications_private.GetImageSizes() which
+ // informs the renderer of the actual rendered size of each
+ // component of a notification. It additionally includes
+ // information about the system's maximum scale factor so that
+ // larger images specified in DP can be interpreted as scaled
+ // versions of the DIP size.
+ // * |args| is used only to get the return value.
+ // * The return value contains the following keys:
+ // scaleFactor - a float a la devicePixelRatio
+ // icon - a dictionary with integer keys "height" and "width" (DIPs)
+ // image - a dictionary of the same format as |icon|
+ // buttonIcon - a dictionary of the same format as |icon|
+ void GetNotificationImageSizes(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationsNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_NOTIFICATIONS_NATIVE_HANDLER_H_
diff --git a/chromium/chrome/renderer/extensions/page_capture_custom_bindings.cc b/chromium/chrome/renderer/extensions/page_capture_custom_bindings.cc
new file mode 100644
index 00000000000..36882667692
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/page_capture_custom_bindings.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/page_capture_custom_bindings.h"
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/blink/public/web/web_blob.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+PageCaptureCustomBindings::PageCaptureCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void PageCaptureCustomBindings::AddRoutes() {
+ RouteHandlerFunction(
+ "CreateBlob", "pageCapture",
+ base::BindRepeating(&PageCaptureCustomBindings::CreateBlob,
+ base::Unretained(this)));
+ RouteHandlerFunction(
+ "SendResponseAck", "pageCapture",
+ base::BindRepeating(&PageCaptureCustomBindings::SendResponseAck,
+ base::Unretained(this)));
+}
+
+void PageCaptureCustomBindings::CreateBlob(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 2);
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsInt32());
+ v8::Isolate* isolate = args.GetIsolate();
+ blink::WebString path(
+ blink::WebString::FromUTF8(*v8::String::Utf8Value(isolate, args[0])));
+ blink::WebBlob blob =
+ blink::WebBlob::CreateFromFile(path, args[1].As<v8::Int32>()->Value());
+ args.GetReturnValue().Set(
+ blob.ToV8Value(context()->v8_context()->Global(), isolate));
+}
+
+void PageCaptureCustomBindings::SendResponseAck(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1);
+ CHECK(args[0]->IsInt32());
+
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (render_frame) {
+ render_frame->Send(new ExtensionHostMsg_ResponseAck(
+ render_frame->GetRoutingID(), args[0].As<v8::Int32>()->Value()));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/page_capture_custom_bindings.h b/chromium/chrome/renderer/extensions/page_capture_custom_bindings.h
new file mode 100644
index 00000000000..9aac731af3a
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/page_capture_custom_bindings.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_PAGE_CAPTURE_CUSTOM_BINDINGS_H_
+#define CHROME_RENDERER_EXTENSIONS_PAGE_CAPTURE_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+// Implements custom bindings for the pageCapture API.
+class PageCaptureCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit PageCaptureCustomBindings(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ private:
+ // Creates a Blob with the content of the specified file.
+ void CreateBlob(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void SendResponseAck(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_PAGE_CAPTURE_CUSTOM_BINDINGS_H_
diff --git a/chromium/chrome/renderer/extensions/platform_keys_natives.cc b/chromium/chrome/renderer/extensions/platform_keys_natives.cc
new file mode 100644
index 00000000000..c7d3c11877c
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/platform_keys_natives.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/platform_keys_natives.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/blink/public/platform/web_crypto_algorithm.h"
+#include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "third_party/blink/public/web/web_crypto_normalize.h"
+
+namespace extensions {
+
+namespace {
+
+bool StringToWebCryptoOperation(const std::string& str,
+ blink::WebCryptoOperation* op) {
+ if (str == "GenerateKey") {
+ *op = blink::kWebCryptoOperationGenerateKey;
+ return true;
+ }
+ if (str == "ImportKey") {
+ *op = blink::kWebCryptoOperationImportKey;
+ return true;
+ }
+ if (str == "Sign") {
+ *op = blink::kWebCryptoOperationSign;
+ return true;
+ }
+ if (str == "Verify") {
+ *op = blink::kWebCryptoOperationVerify;
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<base::DictionaryValue> WebCryptoAlgorithmToBaseValue(
+ const blink::WebCryptoAlgorithm& algorithm) {
+ DCHECK(!algorithm.IsNull());
+
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ const blink::WebCryptoAlgorithmInfo* info =
+ blink::WebCryptoAlgorithm::LookupAlgorithmInfo(algorithm.Id());
+ dict->SetKey("name", base::Value(info->name));
+
+ const blink::WebCryptoAlgorithm* hash = nullptr;
+
+ const blink::WebCryptoRsaHashedKeyGenParams* rsaHashedKeyGen =
+ algorithm.RsaHashedKeyGenParams();
+ if (rsaHashedKeyGen) {
+ dict->SetKey(
+ "modulusLength",
+ base::Value(static_cast<int>(rsaHashedKeyGen->ModulusLengthBits())));
+ const blink::WebVector<unsigned char>& public_exponent =
+ rsaHashedKeyGen->PublicExponent();
+ dict->SetWithoutPathExpansion(
+ "publicExponent",
+ base::Value::CreateWithCopiedBuffer(
+ reinterpret_cast<const char*>(public_exponent.Data()),
+ public_exponent.size()));
+
+ hash = &rsaHashedKeyGen->GetHash();
+ DCHECK(!hash->IsNull());
+ }
+
+ const blink::WebCryptoRsaHashedImportParams* rsaHashedImport =
+ algorithm.RsaHashedImportParams();
+ if (rsaHashedImport) {
+ hash = &rsaHashedImport->GetHash();
+ DCHECK(!hash->IsNull());
+ }
+
+ if (hash) {
+ const blink::WebCryptoAlgorithmInfo* hash_info =
+ blink::WebCryptoAlgorithm::LookupAlgorithmInfo(hash->Id());
+
+ std::unique_ptr<base::DictionaryValue> hash_dict(new base::DictionaryValue);
+ hash_dict->SetKey("name", base::Value(hash_info->name));
+ dict->SetWithoutPathExpansion("hash", std::move(hash_dict));
+ }
+ // Otherwise, |algorithm| is missing support here or no parameters were
+ // required.
+ return dict;
+}
+
+} // namespace
+
+PlatformKeysNatives::PlatformKeysNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void PlatformKeysNatives::AddRoutes() {
+ RouteHandlerFunction(
+ "NormalizeAlgorithm",
+ base::BindRepeating(&PlatformKeysNatives::NormalizeAlgorithm,
+ base::Unretained(this)));
+}
+
+void PlatformKeysNatives::NormalizeAlgorithm(
+ const v8::FunctionCallbackInfo<v8::Value>& call_info) {
+ DCHECK_EQ(call_info.Length(), 2);
+ DCHECK(call_info[0]->IsObject());
+ DCHECK(call_info[1]->IsString());
+
+ blink::WebCryptoOperation operation;
+ if (!StringToWebCryptoOperation(
+ *v8::String::Utf8Value(call_info.GetIsolate(), call_info[1]),
+ &operation)) {
+ return;
+ }
+
+ blink::WebString error_details;
+ int exception_code = 0;
+
+ blink::WebCryptoAlgorithm algorithm = blink::NormalizeCryptoAlgorithm(
+ v8::Local<v8::Object>::Cast(call_info[0]), operation, &exception_code,
+ &error_details, call_info.GetIsolate());
+
+ std::unique_ptr<base::DictionaryValue> algorithm_dict;
+ if (!algorithm.IsNull())
+ algorithm_dict = WebCryptoAlgorithmToBaseValue(algorithm);
+
+ if (!algorithm_dict)
+ return;
+
+ call_info.GetReturnValue().Set(content::V8ValueConverter::Create()->ToV8Value(
+ algorithm_dict.get(), context()->v8_context()));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/platform_keys_natives.h b/chromium/chrome/renderer/extensions/platform_keys_natives.h
new file mode 100644
index 00000000000..4d28406cbea
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/platform_keys_natives.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_PLATFORM_KEYS_NATIVES_H_
+#define CHROME_RENDERER_EXTENSIONS_PLATFORM_KEYS_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+class PlatformKeysNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit PlatformKeysNatives(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ private:
+ // Normalizes algorithm parameters, and then validates the expected parameters
+ // for the algorithm/operation combination.
+ // Expects the following arguments in |call_info|:
+ // |parameters|: An object containing the parameters to normalize.
+ // |operation|: A string describing the operation. Supported operations are
+ // "GenerateKey", "Sign" and "Verify".
+ // Returns the normalized dictionary on success, or null if some required
+ // parameters are missing or not supported. Note that it returns untyped
+ // arrays instead of typed arrays (e.g. for RSA publicExponent).
+ void NormalizeAlgorithm(const v8::FunctionCallbackInfo<v8::Value>& call_info);
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformKeysNatives);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_PLATFORM_KEYS_NATIVES_H_
diff --git a/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.cc b/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.cc
new file mode 100644
index 00000000000..22fcfa02bd9
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.cc
@@ -0,0 +1,47 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/renderer_permissions_policy_delegate.h"
+
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/dispatcher.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+RendererPermissionsPolicyDelegate::RendererPermissionsPolicyDelegate(
+ Dispatcher* dispatcher) : dispatcher_(dispatcher) {
+ PermissionsData::SetPolicyDelegate(this);
+}
+RendererPermissionsPolicyDelegate::~RendererPermissionsPolicyDelegate() {
+ PermissionsData::SetPolicyDelegate(NULL);
+}
+
+bool RendererPermissionsPolicyDelegate::IsRestrictedUrl(
+ const GURL& document_url,
+ std::string* error) {
+ if (dispatcher_->IsExtensionActive(kWebStoreAppId)) {
+ if (error)
+ *error = errors::kCannotScriptGallery;
+ return true;
+ }
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kInstantProcess)) {
+ if (error)
+ *error = errors::kCannotScriptNtp;
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.h b/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.h
new file mode 100644
index 00000000000..cbe1b332922
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate.h
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_RENDERER_PERMISSIONS_POLICY_DELEGATE_H_
+#define CHROME_RENDERER_EXTENSIONS_RENDERER_PERMISSIONS_POLICY_DELEGATE_H_
+
+#include "base/macros.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+
+class Dispatcher;
+
+// Policy delegate for the renderer process.
+class RendererPermissionsPolicyDelegate
+ : public PermissionsData::PolicyDelegate {
+ public:
+ explicit RendererPermissionsPolicyDelegate(Dispatcher* dispatcher);
+ ~RendererPermissionsPolicyDelegate() override;
+
+ // PermissionsData::PolicyDelegate:
+ bool IsRestrictedUrl(const GURL& document_url, std::string* error) override;
+
+ private:
+ Dispatcher* dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererPermissionsPolicyDelegate);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_RENDERER_PERMISSIONS_POLICY_DELEGATE_H_
diff --git a/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc b/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc
new file mode 100644
index 00000000000..b4b71253a3e
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/renderer_permissions_policy_delegate.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/command_line.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/mock_render_thread.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/test_extensions_renderer_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+class RendererPermissionsPolicyDelegateTest : public testing::Test {
+ public:
+ RendererPermissionsPolicyDelegateTest() {
+ }
+
+ void SetUp() override {
+ testing::Test::SetUp();
+ render_thread_.reset(new content::MockRenderThread());
+ renderer_client_.reset(new TestExtensionsRendererClient);
+ ExtensionsRendererClient::Set(renderer_client_.get());
+ extension_dispatcher_ = std::make_unique<Dispatcher>(
+ std::make_unique<ChromeExtensionsDispatcherDelegate>());
+ policy_delegate_.reset(
+ new RendererPermissionsPolicyDelegate(extension_dispatcher_.get()));
+ }
+
+ protected:
+ std::unique_ptr<content::MockRenderThread> render_thread_;
+ std::unique_ptr<ExtensionsRendererClient> renderer_client_;
+ std::unique_ptr<Dispatcher> extension_dispatcher_;
+ std::unique_ptr<RendererPermissionsPolicyDelegate> policy_delegate_;
+};
+
+scoped_refptr<const Extension> CreateTestExtension(const std::string& id) {
+ return ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "Extension with ID " + id)
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Set("permissions", ListBuilder().Append("<all_urls>").Build())
+ .Build())
+ .SetID(id)
+ .Build();
+}
+
+} // namespace
+
+// Tests that CanAccessPage returns false for the any process
+// which hosts the webstore.
+TEST_F(RendererPermissionsPolicyDelegateTest, CannotScriptWebstore) {
+ GURL kAnyUrl("http://example.com/");
+ scoped_refptr<const Extension> extension(CreateTestExtension("a"));
+ std::string error;
+
+ EXPECT_TRUE(extension->permissions_data()->CanAccessPage(kAnyUrl, -1, &error))
+ << error;
+
+ // Pretend we are in the webstore process. We should not be able to execute
+ // script.
+ scoped_refptr<const Extension> webstore_extension(
+ CreateTestExtension(extensions::kWebStoreAppId));
+ RendererExtensionRegistry::Get()->Insert(webstore_extension.get());
+ extension_dispatcher_->OnActivateExtension(extensions::kWebStoreAppId);
+ EXPECT_FALSE(
+ extension->permissions_data()->CanAccessPage(kAnyUrl, -1, &error))
+ << error;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/resource_request_policy.cc b/chromium/chrome/renderer/extensions/resource_request_policy.cc
new file mode 100644
index 00000000000..4cc3378841b
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/resource_request_policy.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/resource_request_policy.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/common/extensions/chrome_manifest_url_handlers.h"
+#include "chrome/common/url_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "extensions/common/manifest_handlers/webview_info.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_console_message.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace extensions {
+
+ResourceRequestPolicy::ResourceRequestPolicy(Dispatcher* dispatcher)
+ : dispatcher_(dispatcher) {}
+ResourceRequestPolicy::~ResourceRequestPolicy() = default;
+
+void ResourceRequestPolicy::OnExtensionLoaded(const Extension& extension) {
+ if (WebAccessibleResourcesInfo::HasWebAccessibleResources(&extension) ||
+ WebviewInfo::HasWebviewAccessibleResources(
+ extension, dispatcher_->webview_partition_id()) ||
+ // Hosted app icons are accessible.
+ // TODO(devlin): Should we incorporate this into
+ // WebAccessibleResourcesInfo?
+ (extension.is_hosted_app() && !IconsInfo::GetIcons(&extension).empty())) {
+ web_accessible_ids_.insert(extension.id());
+ }
+}
+
+void ResourceRequestPolicy::OnExtensionUnloaded(
+ const ExtensionId& extension_id) {
+ web_accessible_ids_.erase(extension_id);
+}
+
+// This method does a security check whether chrome-extension:// URLs can be
+// requested by the renderer. Since this is in an untrusted process, the browser
+// has a similar check to enforce the policy, in case this process is exploited.
+// If you are changing this function, ensure equivalent checks are added to
+// extension_protocols.cc's AllowExtensionResourceLoad.
+bool ResourceRequestPolicy::CanRequestResource(
+ const GURL& resource_url,
+ blink::WebLocalFrame* frame,
+ ui::PageTransition transition_type) {
+ CHECK(resource_url.SchemeIs(kExtensionScheme));
+
+ GURL frame_url = frame->GetDocument().Url();
+ url::Origin frame_origin = frame->GetDocument().GetSecurityOrigin();
+
+ // Navigations from chrome://, devtools:// or chrome-search:// pages need to
+ // be allowed, even if the target |url| is not web-accessible. See also:
+ // - https://crbug.com/662602
+ // - similar scheme checks in ExtensionNavigationThrottle
+ if (frame_origin.scheme() == content::kChromeUIScheme ||
+ frame_origin.scheme() == content::kChromeDevToolsScheme ||
+ frame_origin.scheme() == chrome::kChromeSearchScheme) {
+ return true;
+ }
+
+ // The page_origin may be GURL("null") for unique origins like data URLs,
+ // but this is ok for the checks below. We only care if it matches the
+ // current extension or has a devtools scheme.
+ GURL page_origin = url::Origin(frame->Top()->GetSecurityOrigin()).GetURL();
+
+ GURL extension_origin = resource_url.GetOrigin();
+
+ // We always allow loads in the following cases, regardless of web accessible
+ // resources:
+
+ // Empty urls (needed for some edge cases when we have empty urls).
+ if (frame_url.is_empty())
+ return true;
+
+ // Extensions requesting their own resources (frame_url check is for images,
+ // page_url check is for iframes).
+ // TODO(devlin): We should be checking the ancestor chain, not just the
+ // top-level frame. Additionally, we should be checking the security origin
+ // of the frame, to account for about:blank subframes being scripted by an
+ // extension parent (though we'll still need the frame origin check for
+ // sandboxed frames).
+ if (frame_url.GetOrigin() == extension_origin ||
+ page_origin == extension_origin) {
+ return true;
+ }
+
+ if (!ui::PageTransitionIsWebTriggerable(transition_type))
+ return true;
+
+ // Unreachable web page error page (to allow showing the icon of the
+ // unreachable app on this page).
+ if (frame_url == content::kUnreachableWebDataURL)
+ return true;
+
+ bool is_dev_tools = page_origin.SchemeIs(content::kChromeDevToolsScheme);
+ // Note: we check |web_accessible_ids_| (rather than first looking up the
+ // extension in the registry and checking that) to be more resistant against
+ // timing attacks. This way, determining access for an extension that isn't
+ // installed takes the same amount of time as determining access for an
+ // extension with no web accessible resources. We aren't worried about any
+ // extensions with web accessible resources, since those are inherently
+ // identifiable.
+ if (!is_dev_tools && !web_accessible_ids_.count(extension_origin.host()))
+ return false;
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(resource_url);
+ if (is_dev_tools) {
+ // Allow the load in the case of a non-existent extension. We'll just get a
+ // 404 from the browser process.
+ // TODO(devlin): Can this happen? Does devtools potentially make requests
+ // to non-existent extensions?
+ if (!extension)
+ return true;
+ // Devtools (chrome-extension:// URLs are loaded into frames of devtools to
+ // support the devtools extension APIs).
+ if (!chrome_manifest_urls::GetDevToolsPage(extension).is_empty())
+ return true;
+ }
+
+ DCHECK(extension);
+
+ // Disallow loading of packaged resources for hosted apps. We don't allow
+ // hybrid hosted/packaged apps. The one exception is access to icons, since
+ // some extensions want to be able to do things like create their own
+ // launchers.
+ base::StringPiece resource_root_relative_path =
+ resource_url.path_piece().empty() ? base::StringPiece()
+ : resource_url.path_piece().substr(1);
+ if (extension->is_hosted_app() &&
+ !IconsInfo::GetIcons(extension)
+ .ContainsPath(resource_root_relative_path)) {
+ LOG(ERROR) << "Denying load of " << resource_url.spec() << " from "
+ << "hosted app.";
+ return false;
+ }
+
+ // Disallow loading of extension resources which are not explicitly listed
+ // as web or WebView accessible if the manifest version is 2 or greater.
+ if (!WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ extension, resource_url.path()) &&
+ !WebviewInfo::IsResourceWebviewAccessible(
+ extension, dispatcher_->webview_partition_id(),
+ resource_url.path())) {
+ std::string message = base::StringPrintf(
+ "Denying load of %s. Resources must be listed in the "
+ "web_accessible_resources manifest key in order to be loaded by "
+ "pages outside the extension.",
+ resource_url.spec().c_str());
+ frame->AddMessageToConsole(
+ blink::WebConsoleMessage(blink::mojom::ConsoleMessageLevel::kError,
+ blink::WebString::FromUTF8(message)));
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/resource_request_policy.h b/chromium/chrome/renderer/extensions/resource_request_policy.h
new file mode 100644
index 00000000000..c487a6a8cb1
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/resource_request_policy.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_RESOURCE_REQUEST_POLICY_H_
+#define CHROME_RENDERER_EXTENSIONS_RESOURCE_REQUEST_POLICY_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "extensions/common/extension_id.h"
+#include "ui/base/page_transition_types.h"
+
+class GURL;
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace extensions {
+class Dispatcher;
+class Extension;
+
+// Encapsulates the policy for when chrome-extension:// URLs can be requested.
+class ResourceRequestPolicy {
+ public:
+ explicit ResourceRequestPolicy(Dispatcher* dispatcher);
+ ~ResourceRequestPolicy();
+
+ void OnExtensionLoaded(const Extension& extension);
+ void OnExtensionUnloaded(const ExtensionId& extension);
+
+ // Returns true if the chrome-extension:// |resource_url| can be requested
+ // from |frame_url|. In some cases this decision is made based upon how
+ // this request was generated. Web triggered transitions are more restrictive
+ // than those triggered through UI.
+ bool CanRequestResource(const GURL& resource_url,
+ blink::WebLocalFrame* frame,
+ ui::PageTransition transition_type);
+
+ private:
+ Dispatcher* dispatcher_;
+
+ // The set of extension IDs with any potentially web- or webview-accessible
+ // resources.
+ std::set<ExtensionId> web_accessible_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceRequestPolicy);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_RESOURCE_REQUEST_POLICY_H_
diff --git a/chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.cc b/chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.cc
new file mode 100644
index 00000000000..34583e978a9
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/sync_file_system_custom_bindings.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "third_party/blink/public/web/web_dom_file_system.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+SyncFileSystemCustomBindings::SyncFileSystemCustomBindings(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {}
+
+void SyncFileSystemCustomBindings::AddRoutes() {
+ RouteHandlerFunction(
+ "GetSyncFileSystemObject", "syncFileSystem",
+ base::BindRepeating(
+ &SyncFileSystemCustomBindings::GetSyncFileSystemObject,
+ base::Unretained(this)));
+}
+
+void SyncFileSystemCustomBindings::GetSyncFileSystemObject(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 2) {
+ NOTREACHED();
+ return;
+ }
+ if (!args[0]->IsString()) {
+ NOTREACHED();
+ return;
+ }
+ if (!args[1]->IsString()) {
+ NOTREACHED();
+ return;
+ }
+
+ v8::Isolate* isolate = args.GetIsolate();
+ std::string name(*v8::String::Utf8Value(isolate, args[0]));
+ if (name.empty()) {
+ NOTREACHED();
+ return;
+ }
+ std::string root_url(*v8::String::Utf8Value(isolate, args[1]));
+ if (root_url.empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::FrameForContext(context()->v8_context());
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::Create(
+ webframe, blink::kWebFileSystemTypeExternal,
+ blink::WebString::FromUTF8(name), GURL(root_url))
+ .ToV8Value(context()->v8_context()->Global(), isolate));
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.h b/chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.h
new file mode 100644
index 00000000000..dcd0a63ed59
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/sync_file_system_custom_bindings.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_SYNC_FILE_SYSTEM_CUSTOM_BINDINGS_H_
+#define CHROME_RENDERER_EXTENSIONS_SYNC_FILE_SYSTEM_CUSTOM_BINDINGS_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// Implements custom bindings for the sync file system API.
+class SyncFileSystemCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit SyncFileSystemCustomBindings(ScriptContext* context);
+
+ // ObjectBackedNativeHandler:
+ void AddRoutes() override;
+
+ private:
+ // FileSystemObject GetSyncFileSystemObject(string name, string root_url):
+ // construct a file system object from the given name and root_url.
+ void GetSyncFileSystemObject(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(SyncFileSystemCustomBindings);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_SYNC_FILE_SYSTEM_CUSTOM_BINDINGS_H_
diff --git a/chromium/chrome/renderer/extensions/tabs_hooks_delegate.cc b/chromium/chrome/renderer/extensions/tabs_hooks_delegate.cc
new file mode 100644
index 00000000000..1e264427288
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/tabs_hooks_delegate.cc
@@ -0,0 +1,166 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/tabs_hooks_delegate.h"
+
+#include "base/strings/stringprintf.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/renderer/bindings/api_signature.h"
+#include "extensions/renderer/get_script_context.h"
+#include "extensions/renderer/message_target.h"
+#include "extensions/renderer/messaging_util.h"
+#include "extensions/renderer/native_renderer_messaging_service.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/converter.h"
+
+namespace extensions {
+
+namespace {
+
+using RequestResult = APIBindingHooks::RequestResult;
+
+constexpr char kConnect[] = "tabs.connect";
+constexpr char kSendMessage[] = "tabs.sendMessage";
+constexpr char kSendTabsRequest[] = "tabs.sendRequest";
+
+} // namespace
+
+TabsHooksDelegate::TabsHooksDelegate(
+ NativeRendererMessagingService* messaging_service)
+ : messaging_service_(messaging_service) {}
+TabsHooksDelegate::~TabsHooksDelegate() {}
+
+RequestResult TabsHooksDelegate::HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) {
+ // TODO(devlin): This logic is the same in the RuntimeCustomHooksDelegate -
+ // would it make sense to share it?
+ using Handler = RequestResult (TabsHooksDelegate::*)(
+ ScriptContext*, const std::vector<v8::Local<v8::Value>>&);
+ static const struct {
+ Handler handler;
+ base::StringPiece method;
+ } kHandlers[] = {
+ {&TabsHooksDelegate::HandleSendMessage, kSendMessage},
+ {&TabsHooksDelegate::HandleSendRequest, kSendTabsRequest},
+ {&TabsHooksDelegate::HandleConnect, kConnect},
+ };
+
+ ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
+
+ Handler handler = nullptr;
+ for (const auto& handler_entry : kHandlers) {
+ if (handler_entry.method == method_name) {
+ handler = handler_entry.handler;
+ break;
+ }
+ }
+
+ if (!handler)
+ return RequestResult(RequestResult::NOT_HANDLED);
+
+ APISignature::V8ParseResult parse_result =
+ signature->ParseArgumentsToV8(context, *arguments, refs);
+ if (!parse_result.succeeded()) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(*parse_result.error);
+ return result;
+ }
+
+ return (this->*handler)(script_context, *parse_result.arguments);
+}
+
+RequestResult TabsHooksDelegate::HandleSendRequest(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ DCHECK_EQ(3u, arguments.size());
+
+ int tab_id = messaging_util::ExtractIntegerId(arguments[0]);
+ v8::Local<v8::Value> v8_message = arguments[1];
+ std::string error;
+ std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+ script_context->v8_context(), v8_message, &error);
+ if (!message) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(error);
+ return result;
+ }
+
+ v8::Local<v8::Function> response_callback;
+ if (!arguments[2]->IsNull())
+ response_callback = arguments[2].As<v8::Function>();
+
+ messaging_service_->SendOneTimeMessage(
+ script_context, MessageTarget::ForTab(tab_id, messaging_util::kNoFrameId),
+ messaging_util::kSendRequestChannel, false, *message, response_callback);
+
+ return RequestResult(RequestResult::HANDLED);
+}
+
+RequestResult TabsHooksDelegate::HandleSendMessage(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ DCHECK_EQ(4u, arguments.size());
+
+ int tab_id = messaging_util::ExtractIntegerId(arguments[0]);
+ messaging_util::MessageOptions options;
+ if (!arguments[2]->IsNull()) {
+ options = messaging_util::ParseMessageOptions(
+ script_context->v8_context(), arguments[2].As<v8::Object>(),
+ messaging_util::PARSE_FRAME_ID);
+ }
+
+ v8::Local<v8::Value> v8_message = arguments[1];
+ DCHECK(!v8_message.IsEmpty());
+ std::string error;
+ std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+ script_context->v8_context(), v8_message, &error);
+ if (!message) {
+ RequestResult result(RequestResult::INVALID_INVOCATION);
+ result.error = std::move(error);
+ return result;
+ }
+
+ v8::Local<v8::Function> response_callback;
+ if (!arguments[3]->IsNull())
+ response_callback = arguments[3].As<v8::Function>();
+
+ messaging_service_->SendOneTimeMessage(
+ script_context, MessageTarget::ForTab(tab_id, options.frame_id),
+ messaging_util::kSendMessageChannel, false, *message, response_callback);
+
+ return RequestResult(RequestResult::HANDLED);
+}
+
+RequestResult TabsHooksDelegate::HandleConnect(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments) {
+ DCHECK_EQ(2u, arguments.size());
+
+ int tab_id = messaging_util::ExtractIntegerId(arguments[0]);
+
+ messaging_util::MessageOptions options;
+ if (!arguments[1]->IsNull()) {
+ options = messaging_util::ParseMessageOptions(
+ script_context->v8_context(), arguments[1].As<v8::Object>(),
+ messaging_util::PARSE_FRAME_ID | messaging_util::PARSE_CHANNEL_NAME);
+ }
+
+ gin::Handle<GinPort> port = messaging_service_->Connect(
+ script_context, MessageTarget::ForTab(tab_id, options.frame_id),
+ options.channel_name, false);
+ DCHECK(!port.IsEmpty());
+
+ RequestResult result(RequestResult::HANDLED);
+ result.return_value = port.ToV8();
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/extensions/tabs_hooks_delegate.h b/chromium/chrome/renderer/extensions/tabs_hooks_delegate.h
new file mode 100644
index 00000000000..cf812261cb7
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/tabs_hooks_delegate.h
@@ -0,0 +1,53 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_EXTENSIONS_TABS_HOOKS_DELEGATE_H_
+#define CHROME_RENDERER_EXTENSIONS_TABS_HOOKS_DELEGATE_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/renderer/bindings/api_binding_hooks_delegate.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class NativeRendererMessagingService;
+class ScriptContext;
+
+// The custom hooks for the tabs API.
+class TabsHooksDelegate : public APIBindingHooksDelegate {
+ public:
+ explicit TabsHooksDelegate(NativeRendererMessagingService* messaging_service);
+ ~TabsHooksDelegate() override;
+
+ // APIBindingHooksDelegate:
+ APIBindingHooks::RequestResult HandleRequest(
+ const std::string& method_name,
+ const APISignature* signature,
+ v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* arguments,
+ const APITypeReferenceMap& refs) override;
+
+ private:
+ // Request handlers for the corresponding API methods.
+ APIBindingHooks::RequestResult HandleSendRequest(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+ APIBindingHooks::RequestResult HandleSendMessage(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+ APIBindingHooks::RequestResult HandleConnect(
+ ScriptContext* script_context,
+ const std::vector<v8::Local<v8::Value>>& arguments);
+
+ // The messaging service to handle connect() and sendMessage() calls.
+ // Guaranteed to outlive this object.
+ NativeRendererMessagingService* const messaging_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabsHooksDelegate);
+};
+
+} // namespace extensions
+
+#endif // CHROME_RENDERER_EXTENSIONS_TABS_HOOKS_DELEGATE_H_
diff --git a/chromium/chrome/renderer/extensions/tabs_hooks_delegate_unittest.cc b/chromium/chrome/renderer/extensions/tabs_hooks_delegate_unittest.cc
new file mode 100644
index 00000000000..d1802aa1673
--- /dev/null
+++ b/chromium/chrome/renderer/extensions/tabs_hooks_delegate_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/extensions/tabs_hooks_delegate.h"
+
+#include "base/strings/stringprintf.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/message_target.h"
+#include "extensions/renderer/messaging_util.h"
+#include "extensions/renderer/native_extension_bindings_system.h"
+#include "extensions/renderer/native_extension_bindings_system_test_base.h"
+#include "extensions/renderer/native_renderer_messaging_service.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/send_message_tester.h"
+
+namespace extensions {
+
+namespace {
+
+void CallAPIAndExpectError(v8::Local<v8::Context> context,
+ const char* method_name,
+ const char* args) {
+ SCOPED_TRACE(base::StringPrintf("Args: `%s`", args));
+ constexpr char kTemplate[] = "(function() { chrome.tabs.%s(%s); })";
+
+ v8::Isolate* isolate = context->GetIsolate();
+
+ // Just verify some error was thrown. Expecting the exact error message
+ // tends to rely too much on our argument spec code, which is tested
+ // separately.
+ v8::Local<v8::Function> function = FunctionFromString(
+ context, base::StringPrintf(kTemplate, method_name, args));
+ v8::TryCatch try_catch(isolate);
+ v8::MaybeLocal<v8::Value> result =
+ function->Call(context, v8::Undefined(isolate), 0, nullptr);
+ EXPECT_TRUE(result.IsEmpty());
+ EXPECT_TRUE(try_catch.HasCaught());
+}
+
+} // namespace
+
+class TabsHooksDelegateTest : public NativeExtensionBindingsSystemUnittest {
+ public:
+ TabsHooksDelegateTest() {}
+ ~TabsHooksDelegateTest() override {}
+
+ // NativeExtensionBindingsSystemUnittest:
+ void SetUp() override {
+ NativeExtensionBindingsSystemUnittest::SetUp();
+ messaging_service_ =
+ std::make_unique<NativeRendererMessagingService>(bindings_system());
+
+ bindings_system()->api_system()->GetHooksForAPI("tabs")->SetDelegate(
+ std::make_unique<TabsHooksDelegate>(messaging_service_.get()));
+
+ scoped_refptr<const Extension> mutable_extension = BuildExtension();
+ RegisterExtension(mutable_extension);
+ extension_ = mutable_extension;
+
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ script_context_ = CreateScriptContext(context, mutable_extension.get(),
+ Feature::BLESSED_EXTENSION_CONTEXT);
+ script_context_->set_url(extension_->url());
+ bindings_system()->UpdateBindingsForContext(script_context_);
+ }
+ void TearDown() override {
+ script_context_ = nullptr;
+ extension_ = nullptr;
+ messaging_service_.reset();
+ NativeExtensionBindingsSystemUnittest::TearDown();
+ }
+ bool UseStrictIPCMessageSender() override { return true; }
+
+ virtual scoped_refptr<const Extension> BuildExtension() {
+ return ExtensionBuilder("foo").Build();
+ }
+
+ NativeRendererMessagingService* messaging_service() {
+ return messaging_service_.get();
+ }
+ ScriptContext* script_context() { return script_context_; }
+ const Extension* extension() { return extension_.get(); }
+
+ private:
+ std::unique_ptr<NativeRendererMessagingService> messaging_service_;
+
+ ScriptContext* script_context_ = nullptr;
+ scoped_refptr<const Extension> extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabsHooksDelegateTest);
+};
+
+TEST_F(TabsHooksDelegateTest, Connect) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");
+
+ const bool kExpectIncludeTlsChannelId = false;
+ tester.TestConnect("1", "",
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId);
+ tester.TestConnect("-0", "",
+ MessageTarget::ForTab(0, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId);
+ tester.TestConnect("4, {name: 'channel'}", "channel",
+ MessageTarget::ForTab(4, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId);
+ tester.TestConnect("9, {frameId: null}", "",
+ MessageTarget::ForTab(9, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId);
+ tester.TestConnect("9, {frameId: 16}", "", MessageTarget::ForTab(9, 16),
+ kExpectIncludeTlsChannelId);
+ tester.TestConnect("25, {}", "",
+ MessageTarget::ForTab(25, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId);
+
+ CallAPIAndExpectError(context, "connect", "36, {includeTlsChannelId: true}");
+}
+
+TEST_F(TabsHooksDelegateTest, SendMessage) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ const bool kExpectIncludeTlsChannelId = false;
+
+ SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");
+
+ tester.TestSendMessage("1, ''", R"("")",
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId, SendMessageTester::CLOSED);
+
+ constexpr char kStandardMessage[] = R"({"data":"hello"})";
+ tester.TestSendMessage("1, {data: 'hello'}", kStandardMessage,
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId, SendMessageTester::CLOSED);
+ tester.TestSendMessage("-0, {data: 'hello'}", kStandardMessage,
+ MessageTarget::ForTab(0, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId, SendMessageTester::CLOSED);
+ tester.TestSendMessage("1, {data: 'hello'}, function() {}", kStandardMessage,
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId, SendMessageTester::OPEN);
+ tester.TestSendMessage("1, {data: 'hello'}, {frameId: null}",
+ kStandardMessage,
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ kExpectIncludeTlsChannelId, SendMessageTester::CLOSED);
+ tester.TestSendMessage("1, {data: 'hello'}, {frameId: 10}", kStandardMessage,
+ MessageTarget::ForTab(1, 10),
+ kExpectIncludeTlsChannelId, SendMessageTester::CLOSED);
+ tester.TestSendMessage("1, {data: 'hello'}, {frameId: 10}, function() {}",
+ kStandardMessage, MessageTarget::ForTab(1, 10),
+ kExpectIncludeTlsChannelId, SendMessageTester::OPEN);
+
+ CallAPIAndExpectError(context, "sendMessage",
+ "1, 'hello', {includeTlsChannelId: true}");
+}
+
+TEST_F(TabsHooksDelegateTest, SendRequest) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");
+
+ tester.TestSendRequest("1, ''", R"("")",
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ SendMessageTester::CLOSED);
+
+ constexpr char kStandardMessage[] = R"({"data":"hello"})";
+ tester.TestSendRequest("1, {data: 'hello'}", kStandardMessage,
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ SendMessageTester::CLOSED);
+ tester.TestSendRequest("1, {data: 'hello'}, function() {}", kStandardMessage,
+ MessageTarget::ForTab(1, messaging_util::kNoFrameId),
+ SendMessageTester::OPEN);
+
+ CallAPIAndExpectError(context, "sendRequest", "1, 'hello', {frameId: 10}");
+}
+
+} // namespace extensions
diff --git a/chromium/chrome/renderer/instant_restricted_id_cache.h b/chromium/chrome/renderer/instant_restricted_id_cache.h
new file mode 100644
index 00000000000..3062768bc22
--- /dev/null
+++ b/chromium/chrome/renderer/instant_restricted_id_cache.h
@@ -0,0 +1,168 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_INSTANT_RESTRICTED_ID_CACHE_H_
+#define CHROME_RENDERER_INSTANT_RESTRICTED_ID_CACHE_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/containers/mru_cache.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "chrome/common/search/instant_types.h"
+
+// In InstantExtended, iframes are used to display objects which can only be
+// referenced by the Instant page using an ID (restricted ID). These IDs need to
+// be unique and cached for a while so that the SearchBox API can fetch the
+// object info based on the ID when required by the Instant page. The reason to
+// use a cache of N items as against just the last set of results is that there
+// may be race conditions - e.g. the user clicks on a result being shown but the
+// result set has internally changed but not yet been displayed.
+//
+// The cache can be used in two modes:
+//
+// 1. To store items and assign restricted IDs to them. The cache will store
+// a max of |max_cache_size_| items and assign them unique IDs.
+//
+// 2. To store items that already have restricted IDs assigned to them (e.g.
+// from another instance of the cache). The cache will then not generate IDs
+// and does not make any guarantees of the uniqueness of the IDs. If multiple
+// items are inserted with the same ID, the cache will return the last
+// inserted item in GetItemWithRestrictedID() call.
+
+// T needs to be copyable.
+template <typename T>
+class InstantRestrictedIDCache {
+ public:
+ typedef std::pair<InstantRestrictedID, T> ItemIDPair;
+ typedef std::vector<T> ItemVector;
+ typedef std::vector<ItemIDPair> ItemIDVector;
+
+ explicit InstantRestrictedIDCache(size_t max_cache_size);
+ ~InstantRestrictedIDCache();
+
+ // Adds items to the cache, assigning restricted IDs in the process. May
+ // delete older items from the cache. |items.size()| has to be less than max
+ // cache size.
+ void AddItems(const ItemVector& items);
+
+ // Adds items to the cache using the supplied restricted IDs. May delete
+ // older items from the cache. No two entries in |items| should have the same
+ // InstantRestrictedID. |items.size()| has to be less than max cache size.
+ void AddItemsWithRestrictedID(const ItemIDVector& items);
+
+ // Returns the last set of items added to the cache either via AddItems() or
+ // AddItemsWithRestrictedID().
+ void GetCurrentItems(ItemIDVector* items) const;
+
+ // Returns true if the |restricted_id| is present in the cache and if so,
+ // returns a copy of the item.
+ bool GetItemWithRestrictedID(InstantRestrictedID restricted_id,
+ T* item) const;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(InstantRestrictedIDCacheTest, AutoIDGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InstantRestrictedIDCacheTest, CrazyIDGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InstantRestrictedIDCacheTest, ManualIDGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InstantRestrictedIDCacheTest, MixIDGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InstantRestrictedIDCacheTest, AddEmptySet);
+ FRIEND_TEST_ALL_PREFIXES(InstantRestrictedIDCacheTest,
+ AddItemsWithRestrictedID);
+
+ typedef base::MRUCache<InstantRestrictedID, T> CacheImpl;
+
+ mutable CacheImpl cache_;
+ typename CacheImpl::reverse_iterator last_add_start_;
+ InstantRestrictedID last_restricted_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantRestrictedIDCache);
+};
+
+template <typename T>
+InstantRestrictedIDCache<T>::InstantRestrictedIDCache(size_t max_cache_size)
+ : cache_(max_cache_size),
+ last_add_start_(cache_.rend()),
+ last_restricted_id_(0) {
+ DCHECK(max_cache_size);
+}
+
+template <typename T>
+InstantRestrictedIDCache<T>::~InstantRestrictedIDCache() {
+}
+
+template <typename T>
+void InstantRestrictedIDCache<T>::AddItems(const ItemVector& items) {
+ DCHECK_LE(items.size(), cache_.max_size());
+
+ if (items.empty()) {
+ last_add_start_ = cache_.rend();
+ return;
+ }
+
+ for (size_t i = 0; i < items.size(); ++i) {
+ InstantRestrictedID id = ++last_restricted_id_;
+ cache_.Put(id, items[i]);
+ if (i == 0)
+ last_add_start_ = --cache_.rend();
+ }
+}
+
+template <typename T>
+void InstantRestrictedIDCache<T>::AddItemsWithRestrictedID(
+ const ItemIDVector& items) {
+ DCHECK_LE(items.size(), cache_.max_size());
+
+ if (items.empty()) {
+ last_add_start_ = cache_.rend();
+ return;
+ }
+
+ std::set<InstantRestrictedID> ids_added;
+ for (size_t i = 0; i < items.size(); ++i) {
+ const ItemIDPair& item_id = items[i];
+
+ DCHECK(ids_added.find(item_id.first) == ids_added.end());
+ ids_added.insert(item_id.first);
+
+ cache_.Put(item_id.first, item_id.second);
+ last_restricted_id_ = std::max(item_id.first, last_restricted_id_);
+ }
+
+ // cache_.Put() can invalidate the iterator |last_add_start_| is pointing to.
+ // Therefore, update |last_add_start_| after adding all the items to the
+ // |cache_|.
+ last_add_start_ = cache_.rend();
+ for (size_t i = 0; i < items.size(); ++i)
+ --last_add_start_;
+}
+
+template <typename T>
+void InstantRestrictedIDCache<T>::GetCurrentItems(ItemIDVector* items) const {
+ items->clear();
+
+ for (typename CacheImpl::reverse_iterator it = last_add_start_;
+ it != cache_.rend(); ++it) {
+ items->push_back(std::make_pair(it->first, it->second));
+ }
+}
+
+template <typename T>
+bool InstantRestrictedIDCache<T>::GetItemWithRestrictedID(
+ InstantRestrictedID restricted_id,
+ T* item) const {
+ DCHECK(item);
+
+ typename CacheImpl::const_iterator cache_it = cache_.Peek(restricted_id);
+ if (cache_it == cache_.end())
+ return false;
+ *item = cache_it->second;
+ return true;
+}
+
+#endif // CHROME_RENDERER_INSTANT_RESTRICTED_ID_CACHE_H_
diff --git a/chromium/chrome/renderer/instant_restricted_id_cache_unittest.cc b/chromium/chrome/renderer/instant_restricted_id_cache_unittest.cc
new file mode 100644
index 00000000000..ea06a226f65
--- /dev/null
+++ b/chromium/chrome/renderer/instant_restricted_id_cache_unittest.cc
@@ -0,0 +1,433 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "chrome/renderer/instant_restricted_id_cache.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct TestData {
+ TestData() {}
+ explicit TestData(const std::string& i_value) : value(i_value) {}
+
+ bool operator==(const TestData& rhs) const {
+ return rhs.value == value;
+ }
+
+ std::string value;
+};
+
+// For printing failures nicely.
+void PrintTo(const TestData& data, std::ostream* os) {
+ *os << data.value;
+}
+
+} // namespace
+
+typedef testing::Test InstantRestrictedIDCacheTest;
+typedef InstantRestrictedIDCache<TestData>::ItemIDPair ItemIDPair;
+
+TEST_F(InstantRestrictedIDCacheTest, AutoIDGeneration) {
+ InstantRestrictedIDCache<TestData> cache(7);
+ EXPECT_EQ(0u, cache.cache_.size());
+ EXPECT_EQ(0, cache.last_restricted_id_);
+
+ // Check first addition.
+ std::vector<TestData> input1;
+ input1.push_back(TestData("A"));
+ input1.push_back(TestData("B"));
+ input1.push_back(TestData("C"));
+ cache.AddItems(input1);
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(3, cache.last_restricted_id_);
+
+ std::vector<ItemIDPair> output;
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(i + 1, output[i].first);
+ EXPECT_EQ(input1[i], output[i].second);
+ }
+
+ TestData t;
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_EQ(input1[2], t);
+
+ // Add more items, no overflow.
+ std::vector<TestData> input2;
+ input2.push_back(TestData("D"));
+ input2.push_back(TestData("E"));
+ cache.AddItems(input2);
+ EXPECT_EQ(5u, cache.cache_.size());
+ EXPECT_EQ(5, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(2u, output.size());
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(i + 4, output[i].first);
+ EXPECT_EQ(input2[i], output[i].second);
+ }
+
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(6, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_EQ(input1[2], t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(5, &t));
+ EXPECT_EQ(input2[1], t);
+
+ // Add another set, overflows.
+ std::vector<TestData> input3;
+ input3.push_back(TestData("F"));
+ input3.push_back(TestData("G"));
+ input3.push_back(TestData("H"));
+ input3.push_back(TestData("I"));
+ cache.AddItems(input3);
+ EXPECT_EQ(7u, cache.cache_.size());
+ EXPECT_EQ(9, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(4u, output.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(i + 6, output[i].first);
+ EXPECT_EQ(input3[i], output[i].second);
+ }
+
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(1, &t));
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(2, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_EQ(input1[2], t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(5, &t));
+ EXPECT_EQ(input2[1], t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(7, &t));
+ EXPECT_EQ(input3[1], t);
+}
+
+TEST_F(InstantRestrictedIDCacheTest, ManualIDGeneration) {
+ InstantRestrictedIDCache<TestData> cache(5);
+ EXPECT_EQ(0u, cache.cache_.size());
+ EXPECT_EQ(0, cache.last_restricted_id_);
+
+ // Check first addition.
+ std::vector<ItemIDPair> input1;
+ input1.push_back(std::make_pair(1, TestData("A")));
+ input1.push_back(std::make_pair(2, TestData("B")));
+ input1.push_back(std::make_pair(4, TestData("C")));
+ cache.AddItemsWithRestrictedID(input1);
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(4, cache.last_restricted_id_);
+
+ std::vector<ItemIDPair> output;
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(input1[i].first, output[i].first);
+ EXPECT_EQ(input1[i].second, output[i].second);
+ }
+
+ TestData t;
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_EQ(input1[2].second, t);
+
+
+ // Add more items, one with same rid, no overflow.
+ std::vector<ItemIDPair> input2;
+ input2.push_back(std::make_pair(4, TestData("D")));
+ input2.push_back(std::make_pair(7, TestData("E")));
+ cache.AddItemsWithRestrictedID(input2);
+ EXPECT_EQ(4u, cache.cache_.size());
+ EXPECT_EQ(7, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(2u, output.size());
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(input2[i].first, output[i].first);
+ EXPECT_EQ(input2[i].second, output[i].second);
+ }
+
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(6, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(2, &t));
+ EXPECT_EQ(input1[1].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_EQ(input2[0].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(7, &t));
+ EXPECT_EQ(input2[1].second, t);
+
+ // Add another set, duplicate rids, overflows.
+ std::vector<ItemIDPair> input3;
+ input3.push_back(std::make_pair(1, TestData("F")));
+ input3.push_back(std::make_pair(6, TestData("G")));
+ input3.push_back(std::make_pair(9, TestData("H")));
+ cache.AddItemsWithRestrictedID(input3);
+ EXPECT_EQ(5u, cache.cache_.size());
+ EXPECT_EQ(9, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(input3[i].first, output[i].first);
+ EXPECT_EQ(input3[i].second, output[i].second);
+ }
+
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(1, &t));
+ EXPECT_EQ(input3[0].second, t);
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(2, &t));
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_EQ(input2[0].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(7, &t));
+ EXPECT_EQ(input2[1].second, t);
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(8, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(9, &t));
+ EXPECT_EQ(input3[2].second, t);
+}
+
+TEST_F(InstantRestrictedIDCacheTest, CrazyIDGeneration) {
+ InstantRestrictedIDCache<TestData> cache(4);
+ EXPECT_EQ(0u, cache.cache_.size());
+ EXPECT_EQ(0, cache.last_restricted_id_);
+
+ // Check first addition.
+ std::vector<ItemIDPair> input1;
+ input1.push_back(std::make_pair(0, TestData("A")));
+ input1.push_back(
+ std::make_pair(std::numeric_limits<int32_t>::max(), TestData("B")));
+ input1.push_back(std::make_pair(-100, TestData("C")));
+ cache.AddItemsWithRestrictedID(input1);
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(std::numeric_limits<int32_t>::max(), cache.last_restricted_id_);
+
+ std::vector<ItemIDPair> output;
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(input1[i].first, output[i].first);
+ EXPECT_EQ(input1[i].second, output[i].second);
+ }
+
+ TestData t;
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(1, &t));
+ EXPECT_TRUE(
+ cache.GetItemWithRestrictedID(std::numeric_limits<int32_t>::max(), &t));
+ EXPECT_EQ(input1[1].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(-100, &t));
+ EXPECT_EQ(input1[2].second, t);
+
+ // Add more items, one with same rid, no overflow.
+ std::vector<ItemIDPair> input2;
+ input2.push_back(
+ std::make_pair(std::numeric_limits<int32_t>::min(), TestData("D")));
+ input2.push_back(std::make_pair(7, TestData("E")));
+ cache.AddItemsWithRestrictedID(input2);
+ EXPECT_EQ(4u, cache.cache_.size());
+ EXPECT_EQ(std::numeric_limits<int32_t>::max(), cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(2u, output.size());
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(input2[i].first, output[i].first);
+ EXPECT_EQ(input2[i].second, output[i].second);
+ }
+
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(0, &t));
+ EXPECT_TRUE(
+ cache.GetItemWithRestrictedID(std::numeric_limits<int32_t>::max(), &t));
+ EXPECT_EQ(input1[1].second, t);
+ EXPECT_TRUE(
+ cache.GetItemWithRestrictedID(std::numeric_limits<int32_t>::min(), &t));
+ EXPECT_EQ(input2[0].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(7, &t));
+ EXPECT_EQ(input2[1].second, t);
+
+ // Add an item without RID. last_restricted_id_ will overflow.
+ std::vector<TestData> input3;
+ input3.push_back(TestData("F"));
+ input3.push_back(TestData("G"));
+ cache.AddItems(input3);
+ EXPECT_EQ(4u, cache.cache_.size());
+ EXPECT_EQ(std::numeric_limits<int32_t>::min() + 1, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(2u, output.size());
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(std::numeric_limits<int32_t>::min() + i, output[i].first);
+ EXPECT_EQ(input3[i], output[i].second);
+ }
+
+ EXPECT_TRUE(
+ cache.GetItemWithRestrictedID(std::numeric_limits<int32_t>::min(), &t));
+ EXPECT_EQ(input3[0], t);
+}
+
+TEST_F(InstantRestrictedIDCacheTest, MixIDGeneration) {
+ InstantRestrictedIDCache<TestData> cache(5);
+ EXPECT_EQ(0u, cache.cache_.size());
+ EXPECT_EQ(0, cache.last_restricted_id_);
+
+ // Add some items with manually assigned ids.
+ std::vector<ItemIDPair> input1;
+ input1.push_back(std::make_pair(1, TestData("A")));
+ input1.push_back(std::make_pair(2, TestData("B")));
+ input1.push_back(std::make_pair(4, TestData("C")));
+ cache.AddItemsWithRestrictedID(input1);
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(4, cache.last_restricted_id_);
+
+ std::vector<ItemIDPair> output;
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(input1[i].first, output[i].first);
+ EXPECT_EQ(input1[i].second, output[i].second);
+ }
+
+ TestData t;
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_EQ(input1[2].second, t);
+
+ // Add items with auto id generation.
+ std::vector<TestData> input2;
+ input2.push_back(TestData("D"));
+ input2.push_back(TestData("E"));
+ cache.AddItems(input2);
+ EXPECT_EQ(5u, cache.cache_.size());
+ EXPECT_EQ(6, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(2u, output.size());
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(i + 5, output[i].first);
+ EXPECT_EQ(input2[i], output[i].second);
+ }
+
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(3, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(2, &t));
+ EXPECT_EQ(input1[1].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_EQ(input1[2].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(5, &t));
+ EXPECT_EQ(input2[0], t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(6, &t));
+ EXPECT_EQ(input2[1], t);
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(7, &t));
+
+ // Add manually assigned ids again.
+ std::vector<ItemIDPair> input3;
+ input3.push_back(std::make_pair(1, TestData("F")));
+ input3.push_back(std::make_pair(5, TestData("G")));
+ input3.push_back(std::make_pair(7, TestData("H")));
+ cache.AddItemsWithRestrictedID(input3);
+ EXPECT_EQ(5u, cache.cache_.size());
+ EXPECT_EQ(7, cache.last_restricted_id_);
+
+ output.clear();
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(input3[i].first, output[i].first);
+ EXPECT_EQ(input3[i].second, output[i].second);
+ }
+
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(1, &t));
+ EXPECT_EQ(input3[0].second, t);
+ EXPECT_FALSE(cache.GetItemWithRestrictedID(2, &t));
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(4, &t));
+ EXPECT_EQ(input1[2].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(5, &t));
+ EXPECT_EQ(input3[1].second, t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(6, &t));
+ EXPECT_EQ(input2[1], t);
+ EXPECT_TRUE(cache.GetItemWithRestrictedID(7, &t));
+ EXPECT_EQ(input3[2].second, t);
+}
+
+TEST_F(InstantRestrictedIDCacheTest, AddEmptySet) {
+ InstantRestrictedIDCache<TestData> cache(9);
+ EXPECT_EQ(0u, cache.cache_.size());
+ EXPECT_EQ(0, cache.last_restricted_id_);
+
+ // Add a non-empty set of items.
+ std::vector<TestData> input1;
+ input1.push_back(TestData("A"));
+ input1.push_back(TestData("B"));
+ input1.push_back(TestData("C"));
+ cache.AddItems(input1);
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(3, cache.last_restricted_id_);
+
+ std::vector<ItemIDPair> output;
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+
+ // Add an empty set.
+ cache.AddItems(std::vector<TestData>());
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(3, cache.last_restricted_id_);
+
+ cache.GetCurrentItems(&output);
+ EXPECT_TRUE(output.empty());
+
+ // Manual IDs.
+ std::vector<ItemIDPair> input2;
+ input2.push_back(std::make_pair(10, TestData("A")));
+ input2.push_back(std::make_pair(11, TestData("B")));
+ input2.push_back(std::make_pair(12, TestData("C")));
+ cache.AddItemsWithRestrictedID(input2);
+ EXPECT_EQ(6u, cache.cache_.size());
+ EXPECT_EQ(12, cache.last_restricted_id_);
+
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+
+ cache.AddItemsWithRestrictedID(std::vector<ItemIDPair>());
+ EXPECT_EQ(6u, cache.cache_.size());
+ EXPECT_EQ(12, cache.last_restricted_id_);
+
+ cache.GetCurrentItems(&output);
+ EXPECT_TRUE(output.empty());
+}
+
+TEST_F(InstantRestrictedIDCacheTest, AddItemsWithRestrictedID) {
+ InstantRestrictedIDCache<TestData> cache(29);
+ EXPECT_EQ(0u, cache.cache_.size());
+ EXPECT_EQ(0, cache.last_restricted_id_);
+
+ std::vector<ItemIDPair> input1;
+ input1.push_back(std::make_pair(10, TestData("A")));
+ input1.push_back(std::make_pair(11, TestData("B")));
+ input1.push_back(std::make_pair(12, TestData("C")));
+ cache.AddItemsWithRestrictedID(input1);
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(12, cache.last_restricted_id_);
+ EXPECT_EQ(10, cache.last_add_start_->first);
+
+ std::vector<ItemIDPair> output;
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+
+ // Add the same items again.
+ cache.AddItemsWithRestrictedID(input1);
+
+ // Make sure |cache.last_add_start_| is still valid.
+ cache.GetCurrentItems(&output);
+ EXPECT_EQ(3u, output.size());
+ EXPECT_EQ(3u, cache.cache_.size());
+ EXPECT_EQ(12, cache.last_restricted_id_);
+ EXPECT_EQ(10, cache.last_add_start_->first);
+}
diff --git a/chromium/chrome/renderer/loadtimes_extension_bindings.cc b/chromium/chrome/renderer/loadtimes_extension_bindings.cc
new file mode 100644
index 00000000000..89922874851
--- /dev/null
+++ b/chromium/chrome/renderer/loadtimes_extension_bindings.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/loadtimes_extension_bindings.h"
+
+#include <math.h>
+
+#include "base/time/time.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "net/http/http_response_info.h"
+#include "third_party/blink/public/platform/web_url_response.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_performance.h"
+#include "v8/include/v8.h"
+
+using blink::WebDocumentLoader;
+using blink::WebLocalFrame;
+using blink::WebNavigationType;
+using blink::WebPerformance;
+
+// Values for CSI "tran" property
+const int kTransitionLink = 0;
+const int kTransitionForwardBack = 6;
+const int kTransitionOther = 15;
+const int kTransitionReload = 16;
+
+namespace extensions_v8 {
+
+static const char* const kLoadTimesExtensionName = "v8/LoadTimes";
+
+class LoadTimesExtensionWrapper : public v8::Extension {
+ public:
+ // Creates an extension which adds a new function, chrome.loadTimes()
+ // This function returns an object containing the following members:
+ // requestTime: The time the request to load the page was received
+ // loadTime: The time the renderer started the load process
+ // finishDocumentLoadTime: The time the document itself was loaded
+ // (this is before the onload() method is fired)
+ // finishLoadTime: The time all loading is done, after the onload()
+ // method and all resources
+ // navigationType: A string describing what user action initiated the load
+ //
+ // Note that chrome.loadTimes() is deprecated in favor of performance.timing.
+ // Many of the timings reported via chrome.loadTimes() match timings available
+ // in performance.timing. Timing data will be removed from chrome.loadTimes()
+ // in a future release. No new timings or other information should be exposed
+ // via these APIs.
+ LoadTimesExtensionWrapper() :
+ v8::Extension(kLoadTimesExtensionName,
+ "var chrome;"
+ "if (!chrome)"
+ " chrome = {};"
+ "chrome.loadTimes = function() {"
+ " native function GetLoadTimes();"
+ " return GetLoadTimes();"
+ "};"
+ "chrome.csi = function() {"
+ " native function GetCSI();"
+ " return GetCSI();"
+ "}") {}
+
+ v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::String> name) override {
+ if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "GetLoadTimes",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, GetLoadTimes);
+ } else if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "GetCSI",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, GetCSI);
+ }
+ return v8::Local<v8::FunctionTemplate>();
+ }
+
+ static const char* GetNavigationType(WebNavigationType nav_type) {
+ switch (nav_type) {
+ case blink::kWebNavigationTypeLinkClicked:
+ return "LinkClicked";
+ case blink::kWebNavigationTypeFormSubmitted:
+ return "FormSubmitted";
+ case blink::kWebNavigationTypeBackForward:
+ return "BackForward";
+ case blink::kWebNavigationTypeReload:
+ return "Reload";
+ case blink::kWebNavigationTypeFormResubmitted:
+ return "Resubmitted";
+ case blink::kWebNavigationTypeOther:
+ return "Other";
+ }
+ return "";
+ }
+
+ static int GetCSITransitionType(WebNavigationType nav_type) {
+ switch (nav_type) {
+ case blink::kWebNavigationTypeLinkClicked:
+ case blink::kWebNavigationTypeFormSubmitted:
+ case blink::kWebNavigationTypeFormResubmitted:
+ return kTransitionLink;
+ case blink::kWebNavigationTypeBackForward:
+ return kTransitionForwardBack;
+ case blink::kWebNavigationTypeReload:
+ return kTransitionReload;
+ case blink::kWebNavigationTypeOther:
+ return kTransitionOther;
+ }
+ return kTransitionOther;
+ }
+
+ static void LoadtimesGetter(
+ v8::Local<v8::Name> name,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ if (WebLocalFrame* frame = WebLocalFrame::FrameForCurrentContext()) {
+ frame->UsageCountChromeLoadTimes(blink::WebString::FromUTF8(
+ *v8::String::Utf8Value(info.GetIsolate(), name)));
+ }
+ info.GetReturnValue().Set(info.Data());
+ }
+
+ static void GetLoadTimes(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().SetNull();
+ WebLocalFrame* frame = WebLocalFrame::FrameForCurrentContext();
+ if (!frame) {
+ return;
+ }
+ WebDocumentLoader* document_loader = frame->GetDocumentLoader();
+ if (!document_loader) {
+ return;
+ }
+ const blink::WebURLResponse& response = document_loader->GetResponse();
+ WebPerformance web_performance = frame->Performance();
+ // Though request time now tends to be used to describe the time that the
+ // request for the main resource was issued, when chrome.loadTimes() was
+ // added, it was used to describe 'The time the request to load the page was
+ // received', which is the time now known as navigation start. For backward
+ // compatibility, we continue to provide request_time, setting its value to
+ // navigation start.
+ double request_time = web_performance.NavigationStart();
+ // Developers often use start_load_time as the time the navigation was
+ // started, so we return navigationStart for this value as well. See
+ // https://gist.github.com/search?utf8=%E2%9C%93&q=startLoadTime.
+ // Note that, historically, start_load_time reported the time that a
+ // provisional load was first processed in the render process. For
+ // browser-initiated navigations, this is some time after navigation start,
+ // which means that developers who used this value as a way to track the
+ // start of a navigation were misusing this timestamp and getting the wrong
+ // value - they should be using navigationStart instead. Provisional loads
+ // will not be processed by the render process for browser-initiated
+ // navigations, so reporting the time a provisional load was processed in
+ // the render process will no longer make sense. Thus, we now report the
+ // time for navigationStart, which is a value more consistent with what
+ // developers currently use start_load_time for.
+ double start_load_time = web_performance.NavigationStart();
+ // TODO(bmcquade): Remove this. 'commit' time is a concept internal to
+ // chrome that shouldn't be exposed to the web platform.
+ double commit_load_time = web_performance.ResponseStart();
+ double finish_document_load_time =
+ web_performance.DomContentLoadedEventEnd();
+ double finish_load_time = web_performance.LoadEventEnd();
+ double first_paint_time = web_performance.FirstPaint();
+ // TODO(bmcquade): remove this. It's misleading to track the first paint
+ // after the load event, since many pages perform their meaningful paints
+ // long before the load event fires. We report a time of zero for the
+ // time being.
+ double first_paint_after_load_time = 0.0;
+ std::string navigation_type =
+ GetNavigationType(document_loader->GetNavigationType());
+ bool was_fetched_via_spdy = response.WasFetchedViaSPDY();
+ bool was_alpn_negotiated = response.WasAlpnNegotiated();
+ std::string alpn_negotiated_protocol =
+ response.AlpnNegotiatedProtocol().Utf8();
+ bool was_alternate_protocol_available =
+ response.WasAlternateProtocolAvailable();
+ std::string connection_info = net::HttpResponseInfo::ConnectionInfoToString(
+ response.ConnectionInfo());
+
+ // Important: |frame| and |document_loader| should not be
+ // referred to below this line, as JS setters below can invalidate these
+ // pointers.
+ v8::Isolate* isolate = args.GetIsolate();
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
+ v8::Local<v8::Object> load_times = v8::Object::New(isolate);
+
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "requestTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate, request_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "startLoadTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate, start_load_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "commitLoadTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate, commit_load_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "finishDocumentLoadTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate, finish_document_load_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "finishLoadTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate, finish_load_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "firstPaintTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate, first_paint_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "firstPaintAfterLoadTime", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Number::New(isolate,first_paint_after_load_time))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "navigationType", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::String::NewFromUtf8(isolate, navigation_type.c_str(),
+ v8::NewStringType::kNormal)
+ .ToLocalChecked())
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "wasFetchedViaSpdy", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Boolean::New(isolate, was_fetched_via_spdy))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times
+ ->SetAccessor(ctx,
+ v8::String::NewFromUtf8(isolate, "wasNpnNegotiated",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter, nullptr,
+ v8::Boolean::New(isolate, was_alpn_negotiated))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times
+ ->SetAccessor(
+ ctx, v8::String::NewFromUtf8(isolate, "npnNegotiatedProtocol",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter, nullptr,
+ v8::String::NewFromUtf8(isolate,
+ alpn_negotiated_protocol.c_str(),
+ v8::NewStringType::kNormal)
+ .ToLocalChecked())
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "wasAlternateProtocolAvailable",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::Boolean::New(isolate, was_alternate_protocol_available))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!load_times->SetAccessor(
+ ctx,
+ v8::String::NewFromUtf8(
+ isolate, "connectionInfo", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ LoadtimesGetter,
+ nullptr,
+ v8::String::NewFromUtf8(isolate, connection_info.c_str(),
+ v8::NewStringType::kNormal)
+ .ToLocalChecked())
+ .FromMaybe(false)) {
+ return;
+ }
+
+ args.GetReturnValue().Set(load_times);
+ }
+
+ static void GetCSI(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().SetNull();
+ WebLocalFrame* frame = WebLocalFrame::FrameForCurrentContext();
+ if (!frame) {
+ return;
+ }
+ WebDocumentLoader* document_loader = frame->GetDocumentLoader();
+ if (!document_loader) {
+ return;
+ }
+ WebPerformance web_performance = frame->Performance();
+ base::Time now = base::Time::Now();
+ base::Time start =
+ base::Time::FromDoubleT(web_performance.NavigationStart());
+
+ base::Time dom_content_loaded_end =
+ base::Time::FromDoubleT(web_performance.DomContentLoadedEventEnd());
+ base::TimeDelta page = now - start;
+ int navigation_type =
+ GetCSITransitionType(document_loader->GetNavigationType());
+ // Important: |frame| and |document_loader| should not be referred to below
+ // this line, as JS setters below can invalidate these pointers.
+ v8::Isolate* isolate = args.GetIsolate();
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
+ v8::Local<v8::Object> csi = v8::Object::New(isolate);
+ if (!csi->Set(ctx, v8::String::NewFromUtf8(isolate, "startE",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ v8::Number::New(isolate, floor(start.ToDoubleT() * 1000)))
+ .FromMaybe(false)) {
+ return;
+ }
+ // NOTE: historically, the CSI onload field has reported the time the
+ // document finishes parsing, which is DOMContentLoaded. Thus, we continue
+ // to report that here, despite the fact that the field is named onloadT.
+ if (!csi->Set(ctx, v8::String::NewFromUtf8(isolate, "onloadT",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ v8::Number::New(isolate,
+ floor(dom_content_loaded_end.ToDoubleT() *
+ 1000))).FromMaybe(false)) {
+ return;
+ }
+ if (!csi->Set(ctx, v8::String::NewFromUtf8(isolate, "pageT",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ v8::Number::New(isolate, page.InMillisecondsF()))
+ .FromMaybe(false)) {
+ return;
+ }
+ if (!csi->Set(ctx, v8::String::NewFromUtf8(isolate, "tran",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ v8::Number::New(isolate, navigation_type))
+ .FromMaybe(false)) {
+ return;
+ }
+ args.GetReturnValue().Set(csi);
+ }
+};
+
+std::unique_ptr<v8::Extension> LoadTimesExtension::Get() {
+ return std::make_unique<LoadTimesExtensionWrapper>();
+}
+
+} // namespace extensions_v8
diff --git a/chromium/chrome/renderer/loadtimes_extension_bindings.h b/chromium/chrome/renderer/loadtimes_extension_bindings.h
new file mode 100644
index 00000000000..e6a7e83c124
--- /dev/null
+++ b/chromium/chrome/renderer/loadtimes_extension_bindings.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The LoadTimesExtension is a v8 extension to access the time it took
+// to load a page.
+
+#ifndef CHROME_RENDERER_LOADTIMES_EXTENSION_BINDINGS_H_
+#define CHROME_RENDERER_LOADTIMES_EXTENSION_BINDINGS_H_
+
+#include <memory>
+
+namespace v8 {
+class Extension;
+}
+
+namespace extensions_v8 {
+
+class LoadTimesExtension {
+ public:
+ static std::unique_ptr<v8::Extension> Get();
+};
+
+} // namespace extensions_v8
+
+#endif // CHROME_RENDERER_LOADTIMES_EXTENSION_BINDINGS_H_
diff --git a/chromium/chrome/renderer/media/DEPS b/chromium/chrome/renderer/media/DEPS
new file mode 100644
index 00000000000..a87ac814b6f
--- /dev/null
+++ b/chromium/chrome/renderer/media/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+components/webrtc_logging/common",
+ "+media/audio", # For basic audio functions.
+ "+media/video", # For basic video functions.
+ "+media/base", # For basic media functions.
+ "+media/cast", # For cast streaming library.
+ "+media/capture", # For capture library.
+ "+media/mojo/mojom", # For mojo interfaces.
+]
diff --git a/chromium/chrome/renderer/media/OWNERS b/chromium/chrome/renderer/media/OWNERS
new file mode 100644
index 00000000000..eaa0adc6bb2
--- /dev/null
+++ b/chromium/chrome/renderer/media/OWNERS
@@ -0,0 +1,11 @@
+file://media/OWNERS
+sergeyu@chromium.org
+tommi@chromium.org
+
+# For Cast-related changes.
+per-file cast_*=miu@chromium.org
+
+# FlashEmbedRewrite
+per-file flash_embed_rewrite*=mlamouri@chromium.org
+
+# COMPONENT: Internals>Media
diff --git a/chromium/chrome/renderer/media/cast_ipc_dispatcher.cc b/chromium/chrome/renderer/media/cast_ipc_dispatcher.cc
new file mode 100644
index 00000000000..3025cc4fbc7
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_ipc_dispatcher.cc
@@ -0,0 +1,147 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_ipc_dispatcher.h"
+
+#include "base/single_thread_task_runner.h"
+#include "chrome/common/cast_messages.h"
+#include "chrome/renderer/media/cast_transport_ipc.h"
+#include "ipc/ipc_message_macros.h"
+
+CastIPCDispatcher* CastIPCDispatcher::global_instance_ = NULL;
+
+CastIPCDispatcher::CastIPCDispatcher(
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
+ : sender_(NULL),
+ io_task_runner_(io_task_runner) {
+ DCHECK(io_task_runner_.get());
+ DCHECK(!global_instance_);
+}
+
+CastIPCDispatcher::~CastIPCDispatcher() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(!global_instance_);
+}
+
+CastIPCDispatcher* CastIPCDispatcher::Get() {
+ return global_instance_;
+}
+
+void CastIPCDispatcher::Send(IPC::Message* message) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (sender_) {
+ sender_->Send(message);
+ } else {
+ delete message;
+ }
+}
+
+int32_t CastIPCDispatcher::AddSender(CastTransportIPC* sender) {
+ return id_map_.Add(sender);
+}
+
+void CastIPCDispatcher::RemoveSender(int32_t channel_id) {
+ return id_map_.Remove(channel_id);
+}
+
+bool CastIPCDispatcher::OnMessageReceived(const IPC::Message& message) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(CastIPCDispatcher, message)
+ IPC_MESSAGE_HANDLER(CastMsg_NotifyStatusChange, OnNotifyStatusChange)
+ IPC_MESSAGE_HANDLER(CastMsg_RawEvents, OnRawEvents)
+ IPC_MESSAGE_HANDLER(CastMsg_Rtt, OnRtt)
+ IPC_MESSAGE_HANDLER(CastMsg_RtcpCastMessage, OnRtcpCastMessage)
+ IPC_MESSAGE_HANDLER(CastMsg_Pli, OnReceivedPli);
+ IPC_MESSAGE_HANDLER(CastMsg_ReceivedPacket, OnReceivedPacket)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void CastIPCDispatcher::OnFilterAdded(IPC::Channel* channel) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(!global_instance_);
+ global_instance_ = this;
+ sender_ = channel;
+}
+
+void CastIPCDispatcher::OnFilterRemoved() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(this, global_instance_);
+ global_instance_ = NULL;
+ sender_ = NULL;
+}
+
+void CastIPCDispatcher::OnChannelClosing() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(this, global_instance_);
+}
+
+void CastIPCDispatcher::OnNotifyStatusChange(
+ int32_t channel_id,
+ media::cast::CastTransportStatus status) {
+ CastTransportIPC* sender = id_map_.Lookup(channel_id);
+ if (sender) {
+ sender->OnNotifyStatusChange(status);
+ } else {
+ DVLOG(1)
+ << "CastIPCDispatcher::OnNotifystatusChange on non-existing channel.";
+ }
+}
+
+void CastIPCDispatcher::OnRawEvents(
+ int32_t channel_id,
+ const std::vector<media::cast::PacketEvent>& packet_events,
+ const std::vector<media::cast::FrameEvent>& frame_events) {
+ CastTransportIPC* sender = id_map_.Lookup(channel_id);
+ if (sender) {
+ sender->OnRawEvents(packet_events, frame_events);
+ } else {
+ DVLOG(1) << "CastIPCDispatcher::OnRawEvents on non-existing channel.";
+ }
+}
+
+void CastIPCDispatcher::OnRtt(int32_t channel_id,
+ uint32_t ssrc,
+ base::TimeDelta rtt) {
+ CastTransportIPC* sender = id_map_.Lookup(channel_id);
+ if (sender) {
+ sender->OnRtt(ssrc, rtt);
+ } else {
+ DVLOG(1) << "CastIPCDispatcher::OnRtt on non-existing channel.";
+ }
+}
+
+void CastIPCDispatcher::OnRtcpCastMessage(
+ int32_t channel_id,
+ uint32_t ssrc,
+ const media::cast::RtcpCastMessage& cast_message) {
+ CastTransportIPC* sender = id_map_.Lookup(channel_id);
+ if (sender) {
+ sender->OnRtcpCastMessage(ssrc, cast_message);
+ } else {
+ DVLOG(1) << "CastIPCDispatcher::OnRtt on non-existing channel.";
+ }
+}
+
+void CastIPCDispatcher::OnReceivedPli(int32_t channel_id, int32_t ssrc) {
+ CastTransportIPC* sender = id_map_.Lookup(channel_id);
+ if (sender) {
+ sender->OnReceivedPli(ssrc);
+ } else {
+ DVLOG(1) << "CastIPCDispatcher::OnReceivedPli on non-existing "
+ "channel.";
+ }
+}
+
+void CastIPCDispatcher::OnReceivedPacket(int32_t channel_id,
+ const media::cast::Packet& packet) {
+ CastTransportIPC* sender = id_map_.Lookup(channel_id);
+ if (sender) {
+ sender->OnReceivedPacket(packet);
+ } else {
+ DVLOG(1) << "CastIPCDispatcher::OnReceievdPacket on non-existing channel.";
+ }
+}
diff --git a/chromium/chrome/renderer/media/cast_ipc_dispatcher.h b/chromium/chrome/renderer/media/cast_ipc_dispatcher.h
new file mode 100644
index 00000000000..32f898fddb2
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_ipc_dispatcher.h
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_IPC_DISPATCHER_H_
+#define CHROME_RENDERER_MEDIA_CAST_IPC_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/containers/id_map.h"
+#include "base/macros.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/message_filter.h"
+#include "media/cast/cast_sender.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/net/cast_transport.h"
+
+class CastTransportIPC;
+
+// This dispatcher listens to incoming IPC messages and sends
+// the call to the correct CastTransportIPC instance.
+class CastIPCDispatcher : public IPC::MessageFilter {
+ public:
+ explicit CastIPCDispatcher(
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner);
+
+ static CastIPCDispatcher* Get();
+ void Send(IPC::Message* message);
+ int32_t AddSender(CastTransportIPC* sender);
+ void RemoveSender(int32_t channel_id);
+
+ // IPC::MessageFilter implementation
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void OnFilterAdded(IPC::Channel* channel) override;
+ void OnFilterRemoved() override;
+ void OnChannelClosing() override;
+
+ protected:
+ ~CastIPCDispatcher() override;
+
+ private:
+ void OnNotifyStatusChange(int32_t channel_id,
+ media::cast::CastTransportStatus status);
+ void OnRtpStatistics(int32_t channel_id,
+ bool audio,
+ const media::cast::RtcpSenderInfo& sender_info,
+ base::TimeTicks time_sent,
+ uint32_t rtp_timestamp);
+ void OnRawEvents(int32_t channel_id,
+ const std::vector<media::cast::PacketEvent>& packet_events,
+ const std::vector<media::cast::FrameEvent>& frame_events);
+ void OnRtt(int32_t channel_id, uint32_t ssrc, base::TimeDelta rtt);
+ void OnRtcpCastMessage(int32_t channel_id,
+ uint32_t ssrc,
+ const media::cast::RtcpCastMessage& cast_message);
+ void OnReceivedPli(int32_t channel_id, int32_t ssrc);
+ void OnReceivedPacket(int32_t channel_id, const media::cast::Packet& packet);
+
+ static CastIPCDispatcher* global_instance_;
+
+ // For IPC Send(); must only be accesed on |io_message_loop_|.
+ IPC::Sender* sender_;
+
+ // Task runner on which IPC calls are driven.
+ const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ // A map of stream ids to delegates; must only be accessed on
+ // |io_message_loop_|.
+ base::IDMap<CastTransportIPC*> id_map_;
+ DISALLOW_COPY_AND_ASSIGN(CastIPCDispatcher);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_IPC_DISPATCHER_H_
diff --git a/chromium/chrome/renderer/media/cast_ipc_dispatcher_unittest.cc b/chromium/chrome/renderer/media/cast_ipc_dispatcher_unittest.cc
new file mode 100644
index 00000000000..7dd002ae427
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_ipc_dispatcher_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_ipc_dispatcher.h"
+
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/common/cast_messages.h"
+#include "ipc/ipc_message_macros.h"
+#include "media/cast/logging/logging_defines.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class CastIPCDispatcherTest : public testing::Test {
+ public:
+ CastIPCDispatcherTest() {
+ dispatcher_ = new CastIPCDispatcher(base::ThreadTaskRunnerHandle::Get());
+ }
+
+ protected:
+ void FakeSend(const IPC::Message& message) {
+ EXPECT_TRUE(dispatcher_->OnMessageReceived(message));
+ }
+
+ scoped_refptr<CastIPCDispatcher> dispatcher_;
+ base::test::SingleThreadTaskEnvironment task_environment_{
+ base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
+};
+
+TEST_F(CastIPCDispatcherTest, RawEvents) {
+ const int kChannelId = 17;
+
+ media::cast::PacketEvent packet_event;
+ packet_event.rtp_timestamp =
+ media::cast::RtpTimeTicks().Expand(UINT32_C(100));
+ packet_event.max_packet_id = 10;
+ packet_event.packet_id = 5;
+ packet_event.size = 512;
+ packet_event.timestamp = base::SimpleTestTickClock().NowTicks();
+ packet_event.type = media::cast::PACKET_SENT_TO_NETWORK;
+ packet_event.media_type = media::cast::VIDEO_EVENT;
+ std::vector<media::cast::PacketEvent> packet_events;
+ packet_events.push_back(packet_event);
+
+ media::cast::FrameEvent frame_event;
+ frame_event.rtp_timestamp = media::cast::RtpTimeTicks().Expand(UINT32_C(100));
+ frame_event.frame_id = media::cast::FrameId::first() + 5;
+ frame_event.size = 512;
+ frame_event.timestamp = base::SimpleTestTickClock().NowTicks();
+ frame_event.media_type = media::cast::VIDEO_EVENT;
+ std::vector<media::cast::FrameEvent> frame_events;
+ frame_events.push_back(frame_event);
+
+ packet_events.push_back(packet_event);
+ CastMsg_RawEvents raw_events_msg(kChannelId, packet_events,
+ frame_events);
+
+ FakeSend(raw_events_msg);
+}
+
+} // namespace
diff --git a/chromium/chrome/renderer/media/cast_receiver_audio_valve.cc b/chromium/chrome/renderer/media/cast_receiver_audio_valve.cc
new file mode 100644
index 00000000000..c2d5b856727
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_receiver_audio_valve.cc
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_receiver_audio_valve.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "media/base/audio_parameters.h"
+
+CastReceiverAudioValve::CastReceiverAudioValve(
+ const media::AudioParameters& params,
+ media::AudioCapturerSource::CaptureCallback* cb)
+ : cb_(cb),
+ fifo_(base::Bind(&CastReceiverAudioValve::DeliverRebufferedAudio,
+ base::Unretained(this))),
+ sample_rate_(params.sample_rate()) {
+ fifo_.Reset(params.frames_per_buffer());
+}
+
+CastReceiverAudioValve::~CastReceiverAudioValve() {}
+
+void CastReceiverAudioValve::DeliverDecodedAudio(
+ const media::AudioBus* audio_bus,
+ base::TimeTicks playout_time) {
+ current_playout_time_ = playout_time;
+ // The following will result in zero, one, or multiple synchronous calls to
+ // DeliverRebufferedAudio().
+ fifo_.Push(*audio_bus);
+}
+
+void CastReceiverAudioValve::DeliverRebufferedAudio(
+ const media::AudioBus& audio_bus,
+ int frame_delay) {
+ const base::TimeTicks playout_time =
+ current_playout_time_ +
+ base::TimeDelta::FromMicroseconds(
+ frame_delay * base::Time::kMicrosecondsPerSecond / sample_rate_);
+
+ base::AutoLock lock(lock_);
+ if (cb_) {
+ cb_->Capture(&audio_bus, playout_time, 1.0 /* volume */,
+ false /* key_pressed */);
+ }
+}
+
+void CastReceiverAudioValve::Stop() {
+ base::AutoLock lock(lock_);
+ cb_ = nullptr;
+}
diff --git a/chromium/chrome/renderer/media/cast_receiver_audio_valve.h b/chromium/chrome/renderer/media/cast_receiver_audio_valve.h
new file mode 100644
index 00000000000..f6c09a74a26
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_receiver_audio_valve.h
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_RECEIVER_AUDIO_VALVE_H_
+#define CHROME_RENDERER_MEDIA_CAST_RECEIVER_AUDIO_VALVE_H_
+
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "media/base/audio_capturer_source.h"
+#include "media/base/audio_push_fifo.h"
+
+namespace media {
+class AudioBus;
+}
+
+// Forwards calls to |cb| until Stop is called. If the client requested a
+// different buffer size than that provided by the Cast Receiver, AudioPushFifo
+// is used to rectify that.
+//
+// Thread-safe.
+// All functions may block depending on contention.
+class CastReceiverAudioValve :
+ public base::RefCountedThreadSafe<CastReceiverAudioValve> {
+ public:
+ CastReceiverAudioValve(const media::AudioParameters& params,
+ media::AudioCapturerSource::CaptureCallback* cb);
+
+ // Called on an unknown thread to provide more decoded audio data from the
+ // Cast Receiver.
+ void DeliverDecodedAudio(const media::AudioBus* audio_bus,
+ base::TimeTicks playout_time);
+
+ // When this returns, no more calls will be forwarded to |cb|.
+ void Stop();
+
+ private:
+ friend class base::RefCountedThreadSafe<CastReceiverAudioValve>;
+
+ ~CastReceiverAudioValve();
+
+ // Called by AudioPushFifo zero or more times during the call to Capture().
+ // Delivers audio data in the required buffer size to |cb_|.
+ void DeliverRebufferedAudio(const media::AudioBus& audio_bus,
+ int frame_delay);
+
+ media::AudioCapturerSource::CaptureCallback* cb_;
+ base::Lock lock_;
+
+ media::AudioPushFifo fifo_;
+ const int sample_rate_;
+
+ // Used to pass the current playout time between DeliverDecodedAudio() and
+ // DeviliverRebufferedAudio().
+ base::TimeTicks current_playout_time_;
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_RECEIVER_AUDIO_VALVE_H_
diff --git a/chromium/chrome/renderer/media/cast_receiver_session.cc b/chromium/chrome/renderer/media/cast_receiver_session.cc
new file mode 100644
index 00000000000..659bf04ca4d
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_receiver_session.cc
@@ -0,0 +1,184 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_receiver_session.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/renderer/media/cast_receiver_audio_valve.h"
+#include "content/public/renderer/render_thread.h"
+#include "media/base/audio_capturer_source.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/capture/video_capturer_source.h"
+#include "third_party/blink/public/platform/web_media_stream.h"
+#include "third_party/blink/public/platform/web_media_stream_source.h"
+#include "third_party/blink/public/platform/web_media_stream_track.h"
+
+// This is a render thread object.
+class CastReceiverSession::AudioCapturerSource :
+ public media::AudioCapturerSource {
+ public:
+ AudioCapturerSource(
+ const scoped_refptr<CastReceiverSession> cast_receiver_session);
+ void Initialize(const media::AudioParameters& params,
+ CaptureCallback* callback) override;
+ void Start() override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void SetAutomaticGainControl(bool enable) override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ ~AudioCapturerSource() override;
+ const scoped_refptr<CastReceiverSession> cast_receiver_session_;
+ scoped_refptr<CastReceiverAudioValve> audio_valve_;
+};
+
+// This is a render thread object.
+class CastReceiverSession::VideoCapturerSource
+ : public media::VideoCapturerSource {
+ public:
+ explicit VideoCapturerSource(
+ const scoped_refptr<CastReceiverSession> cast_receiver_session);
+ protected:
+ media::VideoCaptureFormats GetPreferredFormats() override;
+ void StartCapture(const media::VideoCaptureParams& params,
+ const VideoCaptureDeliverFrameCB& frame_callback,
+ const RunningCallback& running_callback) override;
+ void StopCapture() override;
+ private:
+ const scoped_refptr<CastReceiverSession> cast_receiver_session_;
+};
+
+CastReceiverSession::CastReceiverSession()
+ : delegate_(new CastReceiverSessionDelegate()),
+ io_task_runner_(content::RenderThread::Get()->GetIOTaskRunner()) {}
+
+CastReceiverSession::~CastReceiverSession() {
+ // We should always be able to delete the object on the IO thread.
+ CHECK(io_task_runner_->DeleteSoon(FROM_HERE, delegate_.release()));
+}
+
+void CastReceiverSession::Start(
+ const media::cast::FrameReceiverConfig& audio_config,
+ const media::cast::FrameReceiverConfig& video_config,
+ const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const media::VideoCaptureFormat& capture_format,
+ const StartCB& start_callback,
+ const CastReceiverSessionDelegate::ErrorCallback& error_callback) {
+ audio_config_ = audio_config;
+ video_config_ = video_config;
+ format_ = capture_format;
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&CastReceiverSessionDelegate::Start,
+ base::Unretained(delegate_.get()), audio_config,
+ video_config, local_endpoint, remote_endpoint,
+ std::move(options), format_,
+ media::BindToCurrentLoop(error_callback)));
+ scoped_refptr<media::AudioCapturerSource> audio(
+ new CastReceiverSession::AudioCapturerSource(this));
+ std::unique_ptr<media::VideoCapturerSource> video(
+ new CastReceiverSession::VideoCapturerSource(this));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(start_callback, audio, std::move(video)));
+}
+
+void CastReceiverSession::StartAudio(
+ scoped_refptr<CastReceiverAudioValve> audio_valve) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CastReceiverSessionDelegate::StartAudio,
+ base::Unretained(delegate_.get()), audio_valve));
+}
+
+void CastReceiverSession::StartVideo(
+ blink::VideoCaptureDeliverFrameCB frame_callback) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CastReceiverSessionDelegate::StartVideo,
+ base::Unretained(delegate_.get()), frame_callback));
+}
+
+void CastReceiverSession::StopVideo() {
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&CastReceiverSessionDelegate::StopVideo,
+ base::Unretained(delegate_.get())));
+}
+
+CastReceiverSession::VideoCapturerSource::VideoCapturerSource(
+ const scoped_refptr<CastReceiverSession> cast_receiver_session)
+ : cast_receiver_session_(cast_receiver_session) {
+}
+
+media::VideoCaptureFormats
+CastReceiverSession::VideoCapturerSource::GetPreferredFormats() {
+ media::VideoCaptureFormats formats;
+ if (cast_receiver_session_->format_.IsValid())
+ formats.push_back(cast_receiver_session_->format_);
+ return formats;
+}
+
+void CastReceiverSession::VideoCapturerSource::StartCapture(
+ const media::VideoCaptureParams& params,
+ const VideoCaptureDeliverFrameCB& frame_callback,
+ const RunningCallback& running_callback) {
+ cast_receiver_session_->StartVideo(frame_callback);
+ running_callback.Run(true);
+}
+
+void CastReceiverSession::VideoCapturerSource::StopCapture() {
+ cast_receiver_session_->StopVideo();
+}
+
+CastReceiverSession::AudioCapturerSource::AudioCapturerSource(
+ const scoped_refptr<CastReceiverSession> cast_receiver_session)
+ : cast_receiver_session_(cast_receiver_session) {
+}
+
+CastReceiverSession::AudioCapturerSource::~AudioCapturerSource() {
+ DCHECK(!audio_valve_);
+}
+
+void CastReceiverSession::AudioCapturerSource::Initialize(
+ const media::AudioParameters& params,
+ CaptureCallback* callback) {
+ // TODO(hubbe): Consider converting the audio to whatever the caller wants.
+ if (params.sample_rate() !=
+ cast_receiver_session_->audio_config_.rtp_timebase ||
+ params.channels() != cast_receiver_session_->audio_config_.channels) {
+ callback->OnCaptureError(std::string());
+ return;
+ }
+ audio_valve_ = new CastReceiverAudioValve(params, callback);
+}
+
+void CastReceiverSession::AudioCapturerSource::Start() {
+ DCHECK(audio_valve_);
+ cast_receiver_session_->StartAudio(audio_valve_);
+}
+
+void CastReceiverSession::AudioCapturerSource::Stop() {
+ audio_valve_->Stop();
+ audio_valve_ = nullptr;
+}
+
+void CastReceiverSession::AudioCapturerSource::SetVolume(double volume) {
+ // not supported
+}
+
+void CastReceiverSession::AudioCapturerSource::SetAutomaticGainControl(
+ bool enable) {
+ // not supported
+}
+
+void CastReceiverSession::AudioCapturerSource::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // not supported
+}
diff --git a/chromium/chrome/renderer/media/cast_receiver_session.h b/chromium/chrome/renderer/media/cast_receiver_session.h
new file mode 100644
index 00000000000..c9bfa5a1181
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_receiver_session.h
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_RECEIVER_SESSION_H_
+#define CHROME_RENDERER_MEDIA_CAST_RECEIVER_SESSION_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "chrome/renderer/media/cast_receiver_session_delegate.h"
+
+namespace media {
+class AudioCapturerSource;
+struct VideoCaptureFormat;
+class VideoCapturerSource;
+}
+
+namespace net {
+class IPEndPoint;
+}
+
+namespace base {
+class DictionaryValue;
+}
+
+// This a render thread object, all methods, construction and
+// destruction must happen on the render thread.
+class CastReceiverSession : public base::RefCounted<CastReceiverSession> {
+ public:
+ CastReceiverSession();
+
+ typedef base::Callback<void(scoped_refptr<media::AudioCapturerSource>,
+ std::unique_ptr<media::VideoCapturerSource>)>
+ StartCB;
+
+ // Note that the cast receiver will start responding to
+ // incoming network streams immediately, buffering input until
+ // StartAudio/StartVideo is called.
+ // Five first parameters are passed to cast receiver.
+ // |start_callback| is called when initialization is done.
+ // TODO(hubbe): Currently the audio component of the returned media
+ // stream only the exact format that the sender is sending us.
+ void Start(const media::cast::FrameReceiverConfig& audio_config,
+ const media::cast::FrameReceiverConfig& video_config,
+ const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const media::VideoCaptureFormat& capture_format,
+ const StartCB& start_callback,
+ const CastReceiverSessionDelegate::ErrorCallback& error_callback);
+
+ private:
+ class VideoCapturerSource;
+ class AudioCapturerSource;
+ friend class base::RefCounted<CastReceiverSession>;
+ virtual ~CastReceiverSession();
+ void StartAudio(scoped_refptr<CastReceiverAudioValve> audio_valve);
+
+ void StartVideo(blink::VideoCaptureDeliverFrameCB frame_callback);
+ // Stop Video callbacks.
+ // Note that this returns immediately, but callbacks do not stop immediately.
+ void StopVideo();
+
+ media::cast::FrameReceiverConfig audio_config_;
+ media::cast::FrameReceiverConfig video_config_;
+ std::unique_ptr<CastReceiverSessionDelegate> delegate_;
+ const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ media::VideoCaptureFormat format_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastReceiverSession);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_RECEIVER_SESSION_H_
diff --git a/chromium/chrome/renderer/media/cast_receiver_session_delegate.cc b/chromium/chrome/renderer/media/cast_receiver_session_delegate.cc
new file mode 100644
index 00000000000..c04659d88ff
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_receiver_session_delegate.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_receiver_session_delegate.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/values.h"
+
+CastReceiverSessionDelegate::CastReceiverSessionDelegate() {}
+CastReceiverSessionDelegate::~CastReceiverSessionDelegate() {}
+
+void CastReceiverSessionDelegate::Start(
+ const media::cast::FrameReceiverConfig& audio_config,
+ const media::cast::FrameReceiverConfig& video_config,
+ const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const media::VideoCaptureFormat& format,
+ const ErrorCallback& error_callback) {
+ format_ = format;
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ CastSessionDelegateBase::StartUDP(local_endpoint, remote_endpoint,
+ std::move(options), error_callback);
+ cast_receiver_ = media::cast::CastReceiver::Create(cast_environment_,
+ audio_config,
+ video_config,
+ cast_transport_.get());
+ on_audio_decoded_cb_ = base::Bind(
+ &CastReceiverSessionDelegate::OnDecodedAudioFrame,
+ weak_factory_.GetWeakPtr());
+ on_video_decoded_cb_ = base::Bind(
+ &CastReceiverSessionDelegate::OnDecodedVideoFrame,
+ weak_factory_.GetWeakPtr());
+}
+
+void CastReceiverSessionDelegate::ReceivePacket(
+ std::unique_ptr<media::cast::Packet> packet) {
+ cast_receiver_->ReceivePacket(std::move(packet));
+}
+
+void CastReceiverSessionDelegate::StartAudio(
+ scoped_refptr<CastReceiverAudioValve> audio_valve) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ audio_valve_ = audio_valve;
+ cast_receiver_->RequestDecodedAudioFrame(on_audio_decoded_cb_);
+}
+
+void CastReceiverSessionDelegate::OnDecodedAudioFrame(
+ std::unique_ptr<media::AudioBus> audio_bus,
+ base::TimeTicks playout_time,
+ bool is_continuous) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (!audio_valve_)
+ return;
+
+ // We're on the IO thread, which doesn't allow blocking
+ // operations. Since we don't know what the Capture callback
+ // will do exactly, we need to jump to a different thread.
+ // Let's re-use the audio decoder thread.
+ cast_environment_->PostTask(
+ media::cast::CastEnvironment::AUDIO, FROM_HERE,
+ base::Bind(&CastReceiverAudioValve::DeliverDecodedAudio, audio_valve_,
+ base::Owned(audio_bus.release()), playout_time));
+ cast_receiver_->RequestDecodedAudioFrame(on_audio_decoded_cb_);
+}
+
+void CastReceiverSessionDelegate::StartVideo(
+ blink::VideoCaptureDeliverFrameCB video_callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ frame_callback_ = video_callback;
+ cast_receiver_->RequestDecodedVideoFrame(on_video_decoded_cb_);
+}
+
+void CastReceiverSessionDelegate::StopVideo() {
+ frame_callback_ = blink::VideoCaptureDeliverFrameCB();
+}
+
+void CastReceiverSessionDelegate::OnDecodedVideoFrame(
+ scoped_refptr<media::VideoFrame> video_frame,
+ base::TimeTicks playout_time,
+ bool is_continuous) {
+ if (frame_callback_.is_null())
+ return;
+ frame_callback_.Run(std::move(video_frame), playout_time);
+ cast_receiver_->RequestDecodedVideoFrame(on_video_decoded_cb_);
+}
diff --git a/chromium/chrome/renderer/media/cast_receiver_session_delegate.h b/chromium/chrome/renderer/media/cast_receiver_session_delegate.h
new file mode 100644
index 00000000000..e7708cb3e2f
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_receiver_session_delegate.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_RECEIVER_SESSION_DELEGATE_H_
+#define CHROME_RENDERER_MEDIA_CAST_RECEIVER_SESSION_DELEGATE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/renderer/media/cast_receiver_audio_valve.h"
+#include "chrome/renderer/media/cast_session_delegate.h"
+#include "media/capture/video_capture_types.h"
+#include "media/cast/cast_receiver.h"
+#include "third_party/blink/public/common/media/video_capture.h"
+
+class CastReceiverSessionDelegate : public CastSessionDelegateBase {
+ public:
+ typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+ CastReceiverSessionDelegate();
+ ~CastReceiverSessionDelegate() override;
+
+ void ReceivePacket(std::unique_ptr<media::cast::Packet> packet) override;
+
+ void Start(const media::cast::FrameReceiverConfig& audio_config,
+ const media::cast::FrameReceiverConfig& video_config,
+ const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const media::VideoCaptureFormat& format,
+ const ErrorCallback& error_callback);
+
+ void StartAudio(scoped_refptr<CastReceiverAudioValve> audio_valve);
+
+ void StartVideo(blink::VideoCaptureDeliverFrameCB frame_callback);
+ // Stop Video callbacks (eventually).
+ void StopVideo();
+
+ private:
+ void OnDecodedAudioFrame(std::unique_ptr<media::AudioBus> audio_bus,
+ base::TimeTicks playout_time,
+ bool is_continuous);
+
+ void OnDecodedVideoFrame(scoped_refptr<media::VideoFrame> video_frame,
+ base::TimeTicks playout_time,
+ bool is_continuous);
+
+ scoped_refptr<CastReceiverAudioValve> audio_valve_;
+ blink::VideoCaptureDeliverFrameCB frame_callback_;
+ media::cast::AudioFrameDecodedCallback on_audio_decoded_cb_;
+ media::cast::VideoFrameDecodedCallback on_video_decoded_cb_;
+ std::unique_ptr<media::cast::CastReceiver> cast_receiver_;
+ media::VideoCaptureFormat format_;
+ base::WeakPtrFactory<CastReceiverSessionDelegate> weak_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(CastReceiverSessionDelegate);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_RECEIVER_SESSION_DELEGATE_H_
diff --git a/chromium/chrome/renderer/media/cast_rtp_stream.cc b/chromium/chrome/renderer/media/cast_rtp_stream.cc
new file mode 100644
index 00000000000..4e7daaa3d2d
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_rtp_stream.cc
@@ -0,0 +1,577 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_rtp_stream.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/timer.h"
+#include "base/trace_event/trace_event.h"
+#include "base/values.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/renderer/media/cast_session.h"
+#include "chrome/renderer/media/cast_udp_transport.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/video_encode_accelerator.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_converter.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/limits.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/cast_sender.h"
+#include "media/cast/net/cast_transport_config.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_sink.h"
+#include "third_party/blink/public/platform/web_media_stream_source.h"
+#include "third_party/blink/public/web/modules/mediastream/web_media_stream_utils.h"
+#include "ui/gfx/geometry/size.h"
+
+using media::cast::FrameSenderConfig;
+
+namespace {
+
+// The maximum number of milliseconds that should elapse since the last video
+// frame was received from the video source, before requesting refresh frames.
+const int kRefreshIntervalMilliseconds = 250;
+
+// The maximum number of refresh video frames to request/receive. After this
+// limit (60 * 250ms = 15 seconds), refresh frame requests will stop being made.
+const int kMaxConsecutiveRefreshFrames = 60;
+
+FrameSenderConfig DefaultOpusConfig() {
+ FrameSenderConfig config;
+ config.rtp_payload_type = media::cast::RtpPayloadType::AUDIO_OPUS;
+ config.sender_ssrc = 1;
+ config.receiver_ssrc = 2;
+ config.rtp_timebase = media::cast::kDefaultAudioSamplingRate;
+ config.channels = 2;
+ config.min_bitrate = config.max_bitrate = config.start_bitrate =
+ media::cast::kDefaultAudioEncoderBitrate;
+ config.max_frame_rate = 100; // 10 ms audio frames
+ config.codec = media::cast::CODEC_AUDIO_OPUS;
+ return config;
+}
+
+FrameSenderConfig DefaultVp8Config() {
+ FrameSenderConfig config;
+ config.rtp_payload_type = media::cast::RtpPayloadType::VIDEO_VP8;
+ config.sender_ssrc = 11;
+ config.receiver_ssrc = 12;
+ config.rtp_timebase = media::cast::kVideoFrequency;
+ config.channels = 1;
+ config.max_bitrate = media::cast::kDefaultMaxVideoBitrate;
+ config.min_bitrate = media::cast::kDefaultMinVideoBitrate;
+ config.max_frame_rate = media::cast::kDefaultMaxFrameRate;
+ config.codec = media::cast::CODEC_VIDEO_VP8;
+ return config;
+}
+
+FrameSenderConfig DefaultH264Config() {
+ FrameSenderConfig config;
+ config.rtp_payload_type = media::cast::RtpPayloadType::VIDEO_H264;
+ config.sender_ssrc = 11;
+ config.receiver_ssrc = 12;
+ config.rtp_timebase = media::cast::kVideoFrequency;
+ config.channels = 1;
+ config.max_bitrate = media::cast::kDefaultMaxVideoBitrate;
+ config.min_bitrate = media::cast::kDefaultMinVideoBitrate;
+ config.max_frame_rate = media::cast::kDefaultMaxFrameRate;
+ config.codec = media::cast::CODEC_VIDEO_H264;
+ return config;
+}
+
+FrameSenderConfig DefaultRemotingAudioConfig() {
+ FrameSenderConfig config;
+ config.rtp_payload_type = media::cast::RtpPayloadType::REMOTE_AUDIO;
+ config.sender_ssrc = 3;
+ config.receiver_ssrc = 4;
+ config.codec = media::cast::CODEC_AUDIO_REMOTE;
+ config.rtp_timebase = media::cast::kRemotingRtpTimebase;
+ config.max_bitrate = 1000000;
+ config.min_bitrate = 0;
+ config.channels = 2;
+ config.max_frame_rate = 100; // 10 ms audio frames
+
+ return config;
+}
+
+FrameSenderConfig DefaultRemotingVideoConfig() {
+ FrameSenderConfig config;
+ config.rtp_payload_type = media::cast::RtpPayloadType::REMOTE_VIDEO;
+ config.sender_ssrc = 13;
+ config.receiver_ssrc = 14;
+ config.codec = media::cast::CODEC_VIDEO_REMOTE;
+ config.rtp_timebase = media::cast::kRemotingRtpTimebase;
+ config.max_bitrate = 10000000;
+ config.min_bitrate = 0;
+ config.channels = 1;
+ config.max_frame_rate = media::cast::kDefaultMaxFrameRate;
+ return config;
+}
+
+std::vector<FrameSenderConfig> SupportedAudioConfigs(bool for_remoting_stream) {
+ if (for_remoting_stream)
+ return {DefaultRemotingAudioConfig()};
+ else
+ return {DefaultOpusConfig()};
+}
+
+std::vector<FrameSenderConfig> SupportedVideoConfigs(bool for_remoting_stream) {
+ if (for_remoting_stream)
+ return {DefaultRemotingVideoConfig()};
+
+ std::vector<FrameSenderConfig> supported_configs;
+ // Prefer VP8 over H.264 for hardware encoder.
+ if (CastRtpStream::IsHardwareVP8EncodingSupported())
+ supported_configs.push_back(DefaultVp8Config());
+ if (CastRtpStream::IsHardwareH264EncodingSupported())
+ supported_configs.push_back(DefaultH264Config());
+
+ // Propose the default software VP8 encoder, if no hardware encoders are
+ // available.
+ if (supported_configs.empty())
+ supported_configs.push_back(DefaultVp8Config());
+
+ return supported_configs;
+}
+
+} // namespace
+
+// This class receives MediaStreamTrack events and video frames from a
+// MediaStreamVideoTrack. It also includes a timer to request refresh frames
+// when the capturer halts (e.g., a screen capturer stops delivering frames
+// because the screen is not being updated). When a halt is detected, refresh
+// frames will be requested at regular intervals for a short period of time.
+// This provides the video encoder, downstream, several copies of the last frame
+// so that it may clear up lossy encoding artifacts.
+//
+// Threading: Video frames are received on the IO thread and then
+// forwarded to media::cast::VideoFrameInput. The inner class, Deliverer,
+// handles this. Otherwise, all methods and member variables of the outer class
+// must only be accessed on the render thread.
+class CastVideoSink : public base::SupportsWeakPtr<CastVideoSink>,
+ public blink::WebMediaStreamSink {
+ public:
+ // |track| provides data for this sink.
+ // |error_callback| is called if video formats don't match.
+ CastVideoSink(const blink::WebMediaStreamTrack& track,
+ const CastRtpStream::ErrorCallback& error_callback)
+ : track_(track),
+ deliverer_(new Deliverer(error_callback)),
+ consecutive_refresh_count_(0),
+ expecting_a_refresh_frame_(false),
+ is_connected_to_track_(false) {}
+
+ ~CastVideoSink() override {
+ if (is_connected_to_track_)
+ blink::RemoveSinkFromMediaStreamTrack(track_, this);
+ }
+
+ // Attach this sink to a video track represented by |track_|.
+ // Data received from the track will be submitted to |frame_input|.
+ void AddToTrack(
+ bool is_sink_secure,
+ const scoped_refptr<media::cast::VideoFrameInput>& frame_input) {
+ DCHECK(deliverer_);
+ deliverer_->WillConnectToTrack(AsWeakPtr(), frame_input);
+ refresh_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kRefreshIntervalMilliseconds),
+ base::Bind(&CastVideoSink::OnRefreshTimerFired,
+ base::Unretained(this)));
+ blink::AddSinkToMediaStreamTrack(
+ track_, this, base::BindRepeating(&Deliverer::OnVideoFrame, deliverer_),
+ is_sink_secure);
+ is_connected_to_track_ = true;
+ }
+
+ private:
+ class Deliverer : public base::RefCountedThreadSafe<Deliverer> {
+ public:
+ explicit Deliverer(const CastRtpStream::ErrorCallback& error_callback)
+ : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ error_callback_(error_callback) {}
+
+ void WillConnectToTrack(
+ base::WeakPtr<CastVideoSink> sink,
+ scoped_refptr<media::cast::VideoFrameInput> frame_input) {
+ DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
+ sink_ = sink;
+ frame_input_ = std::move(frame_input);
+ }
+
+ void OnVideoFrame(scoped_refptr<media::VideoFrame> video_frame,
+ base::TimeTicks estimated_capture_time) {
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&CastVideoSink::DidReceiveFrame, sink_));
+
+ const base::TimeTicks timestamp = estimated_capture_time.is_null()
+ ? base::TimeTicks::Now()
+ : estimated_capture_time;
+
+ if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
+ video_frame->format() == media::PIXEL_FORMAT_YV12 ||
+ video_frame->format() == media::PIXEL_FORMAT_I420A)) {
+ error_callback_.Run("Incompatible video frame format.");
+ return;
+ }
+ scoped_refptr<media::VideoFrame> frame = video_frame;
+ // Drop alpha channel since we do not support it yet.
+ if (frame->format() == media::PIXEL_FORMAT_I420A)
+ frame = media::WrapAsI420VideoFrame(std::move(video_frame));
+
+ // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
+ TRACE_EVENT_INSTANT2("cast_perf_test", "ConsumeVideoFrame",
+ TRACE_EVENT_SCOPE_THREAD, "timestamp",
+ (timestamp - base::TimeTicks()).InMicroseconds(),
+ "time_delta", frame->timestamp().InMicroseconds());
+ frame_input_->InsertRawVideoFrame(std::move(frame), timestamp);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Deliverer>;
+ ~Deliverer() {}
+
+ const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+ const CastRtpStream::ErrorCallback error_callback_;
+
+ // These are set on the main thread after construction, and before the first
+ // call to OnVideoFrame() on the IO thread. |sink_| may be passed around on
+ // any thread, but must only be dereferenced on the main renderer thread.
+ base::WeakPtr<CastVideoSink> sink_;
+ scoped_refptr<media::cast::VideoFrameInput> frame_input_;
+
+ DISALLOW_COPY_AND_ASSIGN(Deliverer);
+ };
+
+ private:
+ void OnRefreshTimerFired() {
+ ++consecutive_refresh_count_;
+ if (consecutive_refresh_count_ >= kMaxConsecutiveRefreshFrames)
+ refresh_timer_.Stop(); // Stop timer until receiving a non-refresh frame.
+
+ DVLOG(1) << "CastVideoSink is requesting another refresh frame "
+ "(consecutive count=" << consecutive_refresh_count_ << ").";
+ expecting_a_refresh_frame_ = true;
+ blink::RequestRefreshFrameFromVideoTrack(track_);
+ }
+
+ void DidReceiveFrame() {
+ if (expecting_a_refresh_frame_) {
+ // There is uncertainty as to whether the video frame was in response to a
+ // refresh request. However, if it was not, more video frames will soon
+ // follow, and before the refresh timer can fire again. Thus, the
+ // behavior resulting from this logic will be correct.
+ expecting_a_refresh_frame_ = false;
+ } else {
+ consecutive_refresh_count_ = 0;
+ // The following re-starts the timer, scheduling it to fire at
+ // kRefreshIntervalMilliseconds from now.
+ refresh_timer_.Reset();
+ }
+ }
+
+ const blink::WebMediaStreamTrack track_;
+ const scoped_refptr<Deliverer> deliverer_;
+
+ // Requests refresh frames at a constant rate while the source is paused, up
+ // to a consecutive maximum.
+ base::RepeatingTimer refresh_timer_;
+
+ // Counter for the number of consecutive "refresh frames" requested.
+ int consecutive_refresh_count_;
+
+ // Set to true when a request for a refresh frame has been made. This is
+ // cleared once the next frame is received.
+ bool expecting_a_refresh_frame_;
+
+ bool is_connected_to_track_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastVideoSink);
+};
+
+// Receives audio data from a MediaStreamTrack. Data is submitted to
+// media::cast::FrameInput.
+//
+// Threading: Audio frames are received on the real-time audio thread.
+// Note that RemoveFromAudioTrack() is synchronous and we have
+// gurantee that there will be no more audio data after calling it.
+class CastAudioSink : public base::SupportsWeakPtr<CastAudioSink>,
+ public blink::WebMediaStreamAudioSink,
+ public media::AudioConverter::InputCallback {
+ public:
+ // |track| provides data for this sink.
+ CastAudioSink(const blink::WebMediaStreamTrack& track,
+ int output_channels,
+ int output_sample_rate)
+ : track_(track),
+ output_channels_(output_channels),
+ output_sample_rate_(output_sample_rate),
+ current_input_bus_(nullptr),
+ sample_frames_in_(0),
+ sample_frames_out_(0) {}
+
+ ~CastAudioSink() override {
+ if (frame_input_.get())
+ RemoveFromAudioTrack(this, track_);
+ }
+
+ // Add this sink to the track. Data received from the track will be
+ // submitted to |frame_input|.
+ void AddToTrack(
+ const scoped_refptr<media::cast::AudioFrameInput>& frame_input) {
+ DCHECK(frame_input.get());
+ DCHECK(!frame_input_.get());
+ // This member is written here and then accessed on the IO thread
+ // We will not get data until AddToAudioTrack is called so it is
+ // safe to access this member now.
+ frame_input_ = frame_input;
+ AddToAudioTrack(this, track_);
+ }
+
+ protected:
+ // Called on real-time audio thread.
+ void OnData(const media::AudioBus& input_bus,
+ base::TimeTicks estimated_capture_time) override {
+ DCHECK(input_params_.IsValid());
+ DCHECK_EQ(input_bus.channels(), input_params_.channels());
+ DCHECK_EQ(input_bus.frames(), input_params_.frames_per_buffer());
+ DCHECK(!estimated_capture_time.is_null());
+ DCHECK(converter_.get());
+
+ // Determine the duration of the audio signal enqueued within |converter_|.
+ const base::TimeDelta signal_duration_already_buffered =
+ (sample_frames_in_ * base::TimeDelta::FromSeconds(1) /
+ input_params_.sample_rate()) -
+ (sample_frames_out_ * base::TimeDelta::FromSeconds(1) /
+ output_sample_rate_);
+ DVLOG(2) << "Audio reference time adjustment: -("
+ << signal_duration_already_buffered.InMicroseconds() << " us)";
+ const base::TimeTicks capture_time_of_first_converted_sample =
+ estimated_capture_time - signal_duration_already_buffered;
+
+ // Convert the entire input signal. AudioConverter is efficient in that no
+ // additional copying or conversion will occur if the input signal is in the
+ // same format as the output. Note that, while the number of sample frames
+ // provided as input is always the same, the chunk size (and the size of the
+ // |audio_bus| here) can be variable. This is not an issue since
+ // media::cast::AudioFrameInput can handle variable-sized AudioBuses.
+ std::unique_ptr<media::AudioBus> audio_bus =
+ media::AudioBus::Create(output_channels_, converter_->ChunkSize());
+ // AudioConverter will call ProvideInput() to fetch from |current_data_|.
+ current_input_bus_ = &input_bus;
+ converter_->Convert(audio_bus.get());
+ DCHECK(!current_input_bus_); // ProvideInput() called exactly once?
+
+ sample_frames_in_ += input_params_.frames_per_buffer();
+ sample_frames_out_ += audio_bus->frames();
+
+ frame_input_->InsertAudio(std::move(audio_bus),
+ capture_time_of_first_converted_sample);
+ }
+
+ // Called on real-time audio thread.
+ void OnSetFormat(const media::AudioParameters& params) override {
+ if (input_params_.Equals(params))
+ return;
+ input_params_ = params;
+
+ DVLOG(1) << "Setting up audio resampling: {"
+ << input_params_.channels() << " channels, "
+ << input_params_.sample_rate() << " Hz} --> {"
+ << output_channels_ << " channels, "
+ << output_sample_rate_ << " Hz}";
+ const media::AudioParameters output_params(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::GuessChannelLayout(output_channels_), output_sample_rate_,
+ output_sample_rate_ * input_params_.frames_per_buffer() /
+ input_params_.sample_rate());
+ converter_.reset(
+ new media::AudioConverter(input_params_, output_params, false));
+ converter_->AddInput(this);
+ sample_frames_in_ = 0;
+ sample_frames_out_ = 0;
+ }
+
+ // Called on real-time audio thread.
+ double ProvideInput(media::AudioBus* audio_bus,
+ uint32_t frames_delayed) override {
+ DCHECK(current_input_bus_);
+ current_input_bus_->CopyTo(audio_bus);
+ current_input_bus_ = nullptr;
+ return 1.0;
+ }
+
+ private:
+ const blink::WebMediaStreamTrack track_;
+ const int output_channels_;
+ const int output_sample_rate_;
+
+ // This must be set before the real-time audio thread starts calling OnData(),
+ // and remain unchanged until after the thread will stop calling OnData().
+ scoped_refptr<media::cast::AudioFrameInput> frame_input_;
+
+ // These members are accessed on the real-time audio time only.
+ media::AudioParameters input_params_;
+ std::unique_ptr<media::AudioConverter> converter_;
+ const media::AudioBus* current_input_bus_;
+ int64_t sample_frames_in_;
+ int64_t sample_frames_out_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastAudioSink);
+};
+
+bool CastRtpStream::IsHardwareVP8EncodingSupported() {
+ // Query for hardware VP8 encoder support.
+ const std::vector<media::VideoEncodeAccelerator::SupportedProfile>
+ vea_profiles = content::GetSupportedVideoEncodeAcceleratorProfiles();
+ for (const auto& vea_profile : vea_profiles) {
+ if (vea_profile.profile >= media::VP8PROFILE_MIN &&
+ vea_profile.profile <= media::VP8PROFILE_MAX) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CastRtpStream::IsHardwareH264EncodingSupported() {
+// Query for hardware H.264 encoder support.
+//
+// TODO(miu): Look into why H.264 hardware encoder on MacOS is broken.
+// http://crbug.com/596674
+// TODO(emircan): Look into HW encoder initialization issues on Win.
+// https://crbug.com/636064
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
+ const std::vector<media::VideoEncodeAccelerator::SupportedProfile>
+ vea_profiles = content::GetSupportedVideoEncodeAcceleratorProfiles();
+ for (const auto& vea_profile : vea_profiles) {
+ if (vea_profile.profile >= media::H264PROFILE_MIN &&
+ vea_profile.profile <= media::H264PROFILE_MAX) {
+ return true;
+ }
+ }
+#endif // !defined(OS_MACOSX) && !defined(OS_WIN)
+ return false;
+}
+
+CastRtpStream::CastRtpStream(const blink::WebMediaStreamTrack& track,
+ const scoped_refptr<CastSession>& session)
+ : track_(track),
+ cast_session_(session),
+ is_audio_(track_.Source().GetType() ==
+ blink::WebMediaStreamSource::kTypeAudio) {}
+
+CastRtpStream::CastRtpStream(bool is_audio,
+ const scoped_refptr<CastSession>& session)
+ : cast_session_(session), is_audio_(is_audio) {}
+
+CastRtpStream::~CastRtpStream() {
+ Stop();
+}
+
+std::vector<FrameSenderConfig> CastRtpStream::GetSupportedConfigs() {
+ if (is_audio_)
+ return SupportedAudioConfigs(track_.IsNull());
+ else
+ return SupportedVideoConfigs(track_.IsNull());
+}
+
+void CastRtpStream::Start(int32_t stream_id,
+ const FrameSenderConfig& config,
+ const base::Closure& start_callback,
+ const base::Closure& stop_callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(!start_callback.is_null());
+ DCHECK(!stop_callback.is_null());
+ DCHECK(!error_callback.is_null());
+
+ DVLOG(1) << "CastRtpStream::Start = " << (is_audio_ ? "audio" : "video");
+ stop_callback_ = stop_callback;
+ error_callback_ = error_callback;
+
+ if (track_.IsNull()) {
+ cast_session_->StartRemotingStream(
+ stream_id, config, base::Bind(&CastRtpStream::DidEncounterError,
+ weak_factory_.GetWeakPtr()));
+ } else if (is_audio_) {
+ // In case of error we have to go through DidEncounterError() to stop
+ // the streaming after reporting the error.
+ audio_sink_.reset(
+ new CastAudioSink(track_, config.channels, config.rtp_timebase));
+ cast_session_->StartAudio(
+ config,
+ base::Bind(&CastAudioSink::AddToTrack, audio_sink_->AsWeakPtr()),
+ base::Bind(&CastRtpStream::DidEncounterError,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ // See the code for audio above for explanation of callbacks.
+ video_sink_.reset(new CastVideoSink(
+ track_,
+ media::BindToCurrentLoop(base::Bind(&CastRtpStream::DidEncounterError,
+ weak_factory_.GetWeakPtr()))));
+ cast_session_->StartVideo(
+ config, base::Bind(&CastVideoSink::AddToTrack, video_sink_->AsWeakPtr(),
+ !config.aes_key.empty()),
+ base::Bind(&CastRtpStream::DidEncounterError,
+ weak_factory_.GetWeakPtr()));
+ }
+ start_callback.Run();
+}
+
+void CastRtpStream::Stop() {
+ DVLOG(1) << "CastRtpStream::Stop = " << (is_audio_ ? "audio" : "video");
+ if (stop_callback_.is_null())
+ return; // Already stopped.
+ weak_factory_.InvalidateWeakPtrs();
+ error_callback_.Reset();
+ audio_sink_.reset();
+ video_sink_.reset();
+ std::move(stop_callback_).Run();
+}
+
+void CastRtpStream::ToggleLogging(bool enable) {
+ DVLOG(1) << "CastRtpStream::ToggleLogging(" << enable
+ << ") = " << (is_audio_ ? "audio" : "video");
+ cast_session_->ToggleLogging(is_audio_, enable);
+}
+
+void CastRtpStream::GetRawEvents(
+ const base::Callback<void(std::unique_ptr<base::Value>)>& callback,
+ const std::string& extra_data) {
+ DVLOG(1) << "CastRtpStream::GetRawEvents = "
+ << (is_audio_ ? "audio" : "video");
+ cast_session_->GetEventLogsAndReset(is_audio_, extra_data, callback);
+}
+
+void CastRtpStream::GetStats(
+ const base::Callback<void(std::unique_ptr<base::DictionaryValue>)>&
+ callback) {
+ DVLOG(1) << "CastRtpStream::GetStats = " << (is_audio_ ? "audio" : "video");
+ cast_session_->GetStatsAndReset(is_audio_, callback);
+}
+
+void CastRtpStream::DidEncounterError(const std::string& message) {
+ DCHECK(content::RenderThread::Get());
+ DVLOG(1) << "CastRtpStream::DidEncounterError(" << message
+ << ") = " << (is_audio_ ? "audio" : "video");
+ // Save the WeakPtr first because the error callback might delete this object.
+ base::WeakPtr<CastRtpStream> ptr = weak_factory_.GetWeakPtr();
+ error_callback_.Run(message);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&CastRtpStream::Stop, ptr));
+}
diff --git a/chromium/chrome/renderer/media/cast_rtp_stream.h b/chromium/chrome/renderer/media/cast_rtp_stream.h
new file mode 100644
index 00000000000..7d8426cebb8
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_rtp_stream.h
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_RTP_STREAM_H_
+#define CHROME_RENDERER_MEDIA_CAST_RTP_STREAM_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/constants.h"
+#include "third_party/blink/public/platform/web_media_stream_track.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+class CastAudioSink;
+class CastSession;
+class CastVideoSink;
+
+// This object represents a RTP stream that encodes and optionally
+// encrypt audio or video data from a WebMediaStreamTrack.
+// Note that this object does not actually output packets. It allows
+// configuration of encoding and RTP parameters and control such a logical
+// stream.
+class CastRtpStream {
+ public:
+ typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+ static bool IsHardwareVP8EncodingSupported();
+
+ static bool IsHardwareH264EncodingSupported();
+
+ CastRtpStream(const blink::WebMediaStreamTrack& track,
+ const scoped_refptr<CastSession>& session);
+ CastRtpStream(bool is_audio, const scoped_refptr<CastSession>& session);
+ ~CastRtpStream();
+
+ // Return parameters currently supported by this stream.
+ std::vector<media::cast::FrameSenderConfig> GetSupportedConfigs();
+
+ // Begin encoding of media stream and then submit the encoded streams
+ // to underlying transport.
+ // |stream_id| is the unique ID of this stream.
+ // When the stream is started |start_callback| is called.
+ // When the stream is stopped |stop_callback| is called.
+ // When there is an error |error_callback| is called with a message.
+ void Start(int32_t stream_id,
+ const media::cast::FrameSenderConfig& config,
+ const base::Closure& start_callback,
+ const base::Closure& stop_callback,
+ const ErrorCallback& error_callback);
+
+ // Stop encoding.
+ void Stop();
+
+ // Enables or disables logging for this stream.
+ void ToggleLogging(bool enable);
+
+ // Get serialized raw events for this stream with |extra_data| attached,
+ // and invokes |callback| with the result.
+ void GetRawEvents(
+ const base::Callback<void(std::unique_ptr<base::Value>)>& callback,
+ const std::string& extra_data);
+
+ // Get stats in DictionaryValue format and invokves |callback| with
+ // the result.
+ void GetStats(const base::Callback<
+ void(std::unique_ptr<base::DictionaryValue>)>& callback);
+
+ private:
+ void DidEncounterError(const std::string& message);
+
+ blink::WebMediaStreamTrack track_;
+ const scoped_refptr<CastSession> cast_session_;
+ std::unique_ptr<CastAudioSink> audio_sink_;
+ std::unique_ptr<CastVideoSink> video_sink_;
+ base::Closure stop_callback_;
+ ErrorCallback error_callback_;
+ bool is_audio_;
+
+ base::WeakPtrFactory<CastRtpStream> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(CastRtpStream);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_RTP_STREAM_H_
diff --git a/chromium/chrome/renderer/media/cast_session.cc b/chromium/chrome/renderer/media/cast_session.cc
new file mode 100644
index 00000000000..f683c0f1e6e
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_session.cc
@@ -0,0 +1,136 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_session.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/single_thread_task_runner.h"
+#include "chrome/renderer/media/cast_session_delegate.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/video_encode_accelerator.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/video_frame.h"
+#include "media/cast/cast_sender.h"
+#include "media/cast/logging/logging_defines.h"
+#include "mojo/public/cpp/base/shared_memory_utils.h"
+
+namespace {
+
+void CreateVideoEncodeAccelerator(
+ const media::cast::ReceiveVideoEncodeAcceleratorCallback& callback) {
+ DCHECK(content::RenderThread::Get());
+
+ // Delegate the call to content API on the render thread.
+ content::CreateVideoEncodeAccelerator(callback);
+}
+
+void CreateVideoEncodeMemory(
+ size_t size,
+ const media::cast::ReceiveVideoEncodeMemoryCallback& callback) {
+ DCHECK(content::RenderThread::Get());
+
+ base::UnsafeSharedMemoryRegion shm =
+ mojo::CreateUnsafeSharedMemoryRegion(size);
+ DCHECK(shm.IsValid()) << "Failed to allocate shared memory";
+ callback.Run(std::move(shm));
+}
+
+} // namespace
+
+CastSession::CastSession(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : delegate_(new CastSessionDelegate()),
+ main_thread_task_runner_(std::move(task_runner)),
+ io_task_runner_(content::RenderThread::Get()->GetIOTaskRunner()) {}
+
+CastSession::~CastSession() {
+ // We should always be able to delete the object on the IO thread.
+ CHECK(io_task_runner_->DeleteSoon(FROM_HERE, delegate_.release()));
+}
+
+void CastSession::StartAudio(const media::cast::FrameSenderConfig& config,
+ const AudioFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(content::RenderThread::Get());
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &CastSessionDelegate::StartAudio, base::Unretained(delegate_.get()),
+ config, media::BindToLoop(main_thread_task_runner_, callback),
+ media::BindToLoop(main_thread_task_runner_, error_callback)));
+}
+
+void CastSession::StartVideo(const media::cast::FrameSenderConfig& config,
+ const VideoFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(content::RenderThread::Get());
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &CastSessionDelegate::StartVideo, base::Unretained(delegate_.get()),
+ config, media::BindToLoop(main_thread_task_runner_, callback),
+ media::BindToLoop(main_thread_task_runner_, error_callback),
+ media::BindToLoop(main_thread_task_runner_,
+ base::Bind(&CreateVideoEncodeAccelerator)),
+ media::BindToLoop(main_thread_task_runner_,
+ base::Bind(&CreateVideoEncodeMemory))));
+}
+
+void CastSession::StartRemotingStream(
+ int32_t stream_id,
+ const media::cast::FrameSenderConfig& config,
+ const ErrorCallback& error_callback) {
+ DCHECK(content::RenderThread::Get());
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &CastSessionDelegate::StartRemotingStream,
+ base::Unretained(delegate_.get()), stream_id, config,
+ media::BindToLoop(main_thread_task_runner_, error_callback)));
+}
+
+void CastSession::StartUDP(const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const ErrorCallback& error_callback) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &CastSessionDelegate::StartUDP, base::Unretained(delegate_.get()),
+ net::IPEndPoint(), remote_endpoint, std::move(options),
+ media::BindToLoop(main_thread_task_runner_, error_callback)));
+}
+
+void CastSession::ToggleLogging(bool is_audio, bool enable) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CastSessionDelegate::ToggleLogging,
+ base::Unretained(delegate_.get()), is_audio, enable));
+}
+
+void CastSession::GetEventLogsAndReset(
+ bool is_audio, const std::string& extra_data,
+ const EventLogsCallback& callback) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CastSessionDelegate::GetEventLogsAndReset,
+ base::Unretained(delegate_.get()), is_audio, extra_data,
+ media::BindToLoop(main_thread_task_runner_, callback)));
+}
+
+void CastSession::GetStatsAndReset(bool is_audio,
+ const StatsCallback& callback) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CastSessionDelegate::GetStatsAndReset,
+ base::Unretained(delegate_.get()), is_audio,
+ media::BindToLoop(main_thread_task_runner_, callback)));
+}
diff --git a/chromium/chrome/renderer/media/cast_session.h b/chromium/chrome/renderer/media/cast_session.h
new file mode 100644
index 00000000000..6413a128dad
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_session.h
@@ -0,0 +1,113 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_SESSION_H_
+#define CHROME_RENDERER_MEDIA_CAST_SESSION_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/cast/cast_config.h"
+#include "net/base/ip_endpoint.h"
+
+namespace base {
+class DictionaryValue;
+class SingleThreadTaskRunner;
+class Value;
+} // namespace base
+
+namespace media {
+namespace cast {
+class AudioFrameInput;
+class VideoFrameInput;
+} // namespace cast
+} // namespace media
+
+class CastSessionDelegate;
+
+// This class represents a Cast session and allows the session to be
+// configured on the main thread. Actual work is forwarded to
+// CastSessionDelegate on the IO thread.
+class CastSession : public base::RefCounted<CastSession> {
+ public:
+ using AudioFrameInputAvailableCallback =
+ base::Callback<void(const scoped_refptr<media::cast::AudioFrameInput>&)>;
+ using VideoFrameInputAvailableCallback =
+ base::Callback<void(const scoped_refptr<media::cast::VideoFrameInput>&)>;
+ using SendPacketCallback = base::Callback<void(const std::vector<char>&)>;
+ using EventLogsCallback = base::Callback<void(std::unique_ptr<base::Value>)>;
+ using StatsCallback =
+ base::Callback<void(std::unique_ptr<base::DictionaryValue>)>;
+ using ErrorCallback = base::Callback<void(const std::string&)>;
+
+ explicit CastSession(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Start encoding of audio and video using the provided configuration.
+ //
+ // When Cast sender is started and ready to be used
+ // media::cast::FrameInput will be given through |callback|.
+ // If it encounters an error, |error_callback| will be invoked with the
+ // error message. Both |callback| and |error_callback| will be made on
+ // the main thread.
+ // |StartUDP()| must be called before these methods.
+ void StartAudio(const media::cast::FrameSenderConfig& config,
+ const AudioFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback);
+ void StartVideo(const media::cast::FrameSenderConfig& config,
+ const VideoFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback);
+
+ // Start remoting a stream. |error_callback| will be invoked when any error
+ // occurs. |StartUDP()| must be called before calling this method.
+ void StartRemotingStream(int32_t stream_id,
+ const media::cast::FrameSenderConfig& config,
+ const ErrorCallback& error_callback);
+
+ // This will create the Cast transport and connect to |remote_endpoint|.
+ // |options| is a dictionary which contain optional configuration for the
+ // udp transport.
+ // Must be called before initialization of audio or video.
+ void StartUDP(const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const ErrorCallback& error_callback);
+
+ // Creates or destroys event subscriber for the audio or video stream.
+ // |is_audio|: true if the event subscriber is for audio. Video otherwise.
+ // |enable|: If true, creates an event subscriber. Otherwise destroys
+ // existing subscriber and discards logs.
+ void ToggleLogging(bool is_audio, bool enable);
+
+ // Returns raw event logs in serialized format for either the audio or video
+ // stream since last call and returns result in |callback|. Also attaches
+ // |extra_data| to the log.
+ void GetEventLogsAndReset(bool is_audio,
+ const std::string& extra_data, const EventLogsCallback& callback);
+
+ // Returns stats in a DictionaryValue format for either the audio or video
+ // stream since last call and returns result in |callback|.
+ void GetStatsAndReset(bool is_audio, const StatsCallback& callback);
+
+ private:
+ friend class base::RefCounted<CastSession>;
+ virtual ~CastSession();
+
+ // This member should never be dereferenced on the main thread.
+ // CastSessionDelegate lives only on the IO thread. It is always
+ // safe to post task on the IO thread to access CastSessionDelegate
+ // because it is owned by this object.
+ std::unique_ptr<CastSessionDelegate> delegate_;
+
+ // A main thread task runner that might execute JavaScript.
+ const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
+
+ // Proxy to the IO task runner.
+ const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastSession);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_SESSION_H_
diff --git a/chromium/chrome/renderer/media/cast_session_browsertest.cc b/chromium/chrome/renderer/media/cast_session_browsertest.cc
new file mode 100644
index 00000000000..8c68f62f05e
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_session_browsertest.cc
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_session.h"
+
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/renderer/chrome_content_renderer_client.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+
+typedef ChromeRenderViewTest CastSessionBrowserTest;
+
+// Tests that CastSession is created and destroyed properly inside
+// chrome renderer.
+TEST_F(CastSessionBrowserTest, CreateAndDestroy) {
+ chrome_render_thread_->set_io_task_runner(
+ blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+ ChromeContentRendererClient* client =
+ static_cast<ChromeContentRendererClient*>(content_renderer_client_.get());
+ client->RenderThreadStarted();
+
+ scoped_refptr<CastSession> session(
+ new CastSession(blink::scheduler::GetSingleThreadTaskRunnerForTesting()));
+
+ // Causes CastSession to destruct.
+ session = NULL;
+ base::RunLoop().RunUntilIdle();
+}
diff --git a/chromium/chrome/renderer/media/cast_session_delegate.cc b/chromium/chrome/renderer/media/cast_session_delegate.cc
new file mode 100644
index 00000000000..0e24f6aee0b
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_session_delegate.cc
@@ -0,0 +1,332 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_session_delegate.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/renderer/media/cast_threads.h"
+#include "chrome/renderer/media/cast_transport_ipc.h"
+#include "components/version_info/version_info.h"
+#include "content/public/renderer/render_thread.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/cast_sender.h"
+#include "media/cast/logging/log_serializer.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/logging/proto/raw_events.pb.h"
+#include "media/cast/logging/raw_event_subscriber_bundle.h"
+#include "media/cast/net/cast_transport.h"
+#include "media/cast/net/cast_transport_config.h"
+
+using media::cast::CastEnvironment;
+using media::cast::CastSender;
+using media::cast::FrameSenderConfig;
+
+static base::LazyInstance<CastThreads>::DestructorAtExit g_cast_threads =
+ LAZY_INSTANCE_INITIALIZER;
+
+CastSessionDelegateBase::CastSessionDelegateBase()
+ : io_task_runner_(content::RenderThread::Get()->GetIOTaskRunner()) {
+ DCHECK(io_task_runner_.get());
+#if defined(OS_WIN)
+ // Note that this also increases the accuracy of PostDelayTask,
+ // which is is very helpful to cast.
+ if (!base::Time::ActivateHighResolutionTimer(true)) {
+ LOG(WARNING) << "Failed to activate high resolution timers for cast.";
+ }
+#endif
+}
+
+CastSessionDelegateBase::~CastSessionDelegateBase() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+#if defined(OS_WIN)
+ base::Time::ActivateHighResolutionTimer(false);
+#endif
+}
+
+void CastSessionDelegateBase::StartUDP(
+ const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const ErrorCallback& error_callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ // CastSender uses the renderer's IO thread as the main thread. This reduces
+ // thread hopping for incoming video frames and outgoing network packets.
+ // TODO(hubbe): Create cast environment in ctor instead.
+ cast_environment_ =
+ new CastEnvironment(base::DefaultTickClock::GetInstance(),
+ base::ThreadTaskRunnerHandle::Get(),
+ g_cast_threads.Get().GetAudioEncodeTaskRunner(),
+ g_cast_threads.Get().GetVideoEncodeTaskRunner());
+
+ // Rationale for using unretained: The callback cannot be called after the
+ // destruction of CastTransportIPC, and they both share the same thread.
+ cast_transport_.reset(new CastTransportIPC(
+ local_endpoint, remote_endpoint, std::move(options),
+ base::Bind(&CastSessionDelegateBase::ReceivePacket,
+ base::Unretained(this)),
+ base::Bind(&CastSessionDelegateBase::StatusNotificationCB,
+ base::Unretained(this), error_callback),
+ base::Bind(&media::cast::LogEventDispatcher::DispatchBatchOfEvents,
+ base::Unretained(cast_environment_->logger()))));
+}
+
+void CastSessionDelegateBase::StatusNotificationCB(
+ const ErrorCallback& error_callback,
+ media::cast::CastTransportStatus status) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ std::string error_message;
+
+ switch (status) {
+ case media::cast::TRANSPORT_STREAM_UNINITIALIZED:
+ case media::cast::TRANSPORT_STREAM_INITIALIZED:
+ return; // Not errors, do nothing.
+ case media::cast::TRANSPORT_INVALID_CRYPTO_CONFIG:
+ error_callback.Run("Invalid encrypt/decrypt configuration.");
+ break;
+ case media::cast::TRANSPORT_SOCKET_ERROR:
+ error_callback.Run("Socket error.");
+ break;
+ }
+}
+
+CastSessionDelegate::CastSessionDelegate() {
+ DCHECK(io_task_runner_.get());
+}
+
+CastSessionDelegate::~CastSessionDelegate() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+}
+
+void CastSessionDelegate::StartAudio(
+ const FrameSenderConfig& config,
+ const AudioFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (!cast_transport_ || !cast_sender_) {
+ error_callback.Run("Destination not set.");
+ return;
+ }
+
+ audio_frame_input_available_callback_ = callback;
+ cast_sender_->InitializeAudio(
+ config,
+ base::Bind(&CastSessionDelegate::OnOperationalStatusChange,
+ weak_factory_.GetWeakPtr(), true, error_callback));
+}
+
+void CastSessionDelegate::StartVideo(
+ const FrameSenderConfig& config,
+ const VideoFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback,
+ const media::cast::CreateVideoEncodeAcceleratorCallback& create_vea_cb,
+ const media::cast::CreateVideoEncodeMemoryCallback&
+ create_video_encode_mem_cb) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (!cast_transport_ || !cast_sender_) {
+ error_callback.Run("Destination not set.");
+ return;
+ }
+
+ video_frame_input_available_callback_ = callback;
+
+ cast_sender_->InitializeVideo(
+ config,
+ base::Bind(&CastSessionDelegate::OnOperationalStatusChange,
+ weak_factory_.GetWeakPtr(), false, error_callback),
+ create_vea_cb,
+ create_video_encode_mem_cb);
+}
+
+void CastSessionDelegate::StartRemotingStream(
+ int32_t stream_id,
+ const FrameSenderConfig& config,
+ const ErrorCallback& error_callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (!cast_transport_) {
+ error_callback.Run("Destination not set.");
+ return;
+ }
+
+ media::cast::CastTransportRtpConfig transport_config;
+ transport_config.ssrc = config.sender_ssrc;
+ transport_config.feedback_ssrc = config.receiver_ssrc;
+ transport_config.rtp_payload_type = config.rtp_payload_type;
+ transport_config.rtp_stream_id = stream_id;
+ transport_config.aes_key = config.aes_key;
+ transport_config.aes_iv_mask = config.aes_iv_mask;
+ cast_transport_->InitializeStream(transport_config, nullptr);
+}
+
+void CastSessionDelegate::StartUDP(
+ const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const ErrorCallback& error_callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ CastSessionDelegateBase::StartUDP(local_endpoint, remote_endpoint,
+ std::move(options), error_callback);
+ event_subscribers_.reset(
+ new media::cast::RawEventSubscriberBundle(cast_environment_));
+
+ cast_sender_ = CastSender::Create(cast_environment_, cast_transport_.get());
+}
+
+void CastSessionDelegate::ToggleLogging(bool is_audio, bool enable) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (!event_subscribers_.get())
+ return;
+
+ if (enable)
+ event_subscribers_->AddEventSubscribers(is_audio);
+ else
+ event_subscribers_->RemoveEventSubscribers(is_audio);
+}
+
+void CastSessionDelegate::GetEventLogsAndReset(
+ bool is_audio,
+ const std::string& extra_data,
+ const EventLogsCallback& callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (!event_subscribers_.get()) {
+ callback.Run(std::make_unique<base::Value>(base::Value::Type::BINARY));
+ return;
+ }
+
+ media::cast::EncodingEventSubscriber* subscriber =
+ event_subscribers_->GetEncodingEventSubscriber(is_audio);
+ if (!subscriber) {
+ callback.Run(std::make_unique<base::Value>(base::Value::Type::BINARY));
+ return;
+ }
+
+ media::cast::proto::LogMetadata metadata;
+ media::cast::FrameEventList frame_events;
+ media::cast::PacketEventList packet_events;
+
+ subscriber->GetEventsAndReset(&metadata, &frame_events, &packet_events);
+
+ if (!extra_data.empty())
+ metadata.set_extra_data(extra_data);
+ media::cast::proto::GeneralDescription* gen_desc =
+ metadata.mutable_general_description();
+ gen_desc->set_product(version_info::GetProductName());
+ gen_desc->set_product_version(version_info::GetVersionNumber());
+ gen_desc->set_os(version_info::GetOSType());
+
+ std::unique_ptr<char[]> serialized_log(
+ new char[media::cast::kMaxSerializedBytes]);
+ int output_bytes;
+ bool success = media::cast::SerializeEvents(metadata,
+ frame_events,
+ packet_events,
+ true,
+ media::cast::kMaxSerializedBytes,
+ serialized_log.get(),
+ &output_bytes);
+
+ if (!success) {
+ DVLOG(2) << "Failed to serialize event log.";
+ callback.Run(std::make_unique<base::Value>(base::Value::Type::BINARY));
+ return;
+ }
+
+ DVLOG(2) << "Serialized log length: " << output_bytes;
+
+ auto blob = std::make_unique<base::Value>(std::vector<char>(
+ serialized_log.get(), serialized_log.get() + output_bytes));
+ callback.Run(std::move(blob));
+}
+
+void CastSessionDelegate::GetStatsAndReset(bool is_audio,
+ const StatsCallback& callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (!event_subscribers_.get()) {
+ callback.Run(std::make_unique<base::DictionaryValue>());
+ return;
+ }
+
+ media::cast::StatsEventSubscriber* subscriber =
+ event_subscribers_->GetStatsEventSubscriber(is_audio);
+ if (!subscriber) {
+ callback.Run(std::make_unique<base::DictionaryValue>());
+ return;
+ }
+
+ std::unique_ptr<base::DictionaryValue> stats = subscriber->GetStats();
+ subscriber->Reset();
+
+ callback.Run(std::move(stats));
+}
+
+void CastSessionDelegate::OnOperationalStatusChange(
+ bool is_for_audio,
+ const ErrorCallback& error_callback,
+ media::cast::OperationalStatus status) {
+ DCHECK(cast_sender_);
+
+ switch (status) {
+ case media::cast::STATUS_UNINITIALIZED:
+ case media::cast::STATUS_CODEC_REINIT_PENDING:
+ // Not an error.
+ // TODO(miu): As an optimization, signal the client to pause sending more
+ // frames until the state becomes STATUS_INITIALIZED again.
+ break;
+ case media::cast::STATUS_INITIALIZED:
+ // Once initialized, run the "frame input available" callback to allow the
+ // client to begin sending frames. If STATUS_INITIALIZED is encountered
+ // again, do nothing since this is only an indication that the codec has
+ // successfully re-initialized.
+ if (is_for_audio) {
+ if (!audio_frame_input_available_callback_.is_null()) {
+ std::move(audio_frame_input_available_callback_)
+ .Run(cast_sender_->audio_frame_input());
+ }
+ } else {
+ if (!video_frame_input_available_callback_.is_null()) {
+ std::move(video_frame_input_available_callback_)
+ .Run(cast_sender_->video_frame_input());
+ }
+ }
+ break;
+ case media::cast::STATUS_INVALID_CONFIGURATION:
+ error_callback.Run(base::StringPrintf("Invalid %s configuration.",
+ is_for_audio ? "audio" : "video"));
+ break;
+ case media::cast::STATUS_UNSUPPORTED_CODEC:
+ error_callback.Run(base::StringPrintf("%s codec not supported.",
+ is_for_audio ? "Audio" : "Video"));
+ break;
+ case media::cast::STATUS_CODEC_INIT_FAILED:
+ error_callback.Run(base::StringPrintf("%s codec initialization failed.",
+ is_for_audio ? "Audio" : "Video"));
+ break;
+ case media::cast::STATUS_CODEC_RUNTIME_ERROR:
+ error_callback.Run(base::StringPrintf("%s codec runtime error.",
+ is_for_audio ? "Audio" : "Video"));
+ break;
+ }
+}
+
+void CastSessionDelegate::ReceivePacket(
+ std::unique_ptr<media::cast::Packet> packet) {
+ // Do nothing (frees packet)
+}
diff --git a/chromium/chrome/renderer/media/cast_session_delegate.h b/chromium/chrome/renderer/media/cast_session_delegate.h
new file mode 100644
index 00000000000..eeb09936b9d
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_session_delegate.h
@@ -0,0 +1,155 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_SESSION_DELEGATE_H_
+#define CHROME_RENDERER_MEDIA_CAST_SESSION_DELEGATE_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/default_tick_clock.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/cast_sender.h"
+#include "media/cast/logging/logging_defines.h"
+
+namespace base {
+class DictionaryValue;
+class SingleThreadTaskRunner;
+class Value;
+} // namespace base
+
+namespace media {
+
+namespace cast {
+class CastEnvironment;
+class RawEventSubscriberBundle;
+
+namespace transport {
+class CastTransport;
+} // namespace transport
+} // namespace cast
+} // namespace media
+
+// Breaks out functionality that is common between CastSessionDelegate and
+// CastReceiverSessionDelegate.
+class CastSessionDelegateBase {
+ public:
+ typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+ CastSessionDelegateBase();
+ virtual ~CastSessionDelegateBase();
+
+ // This will start the session by configuring and creating the Cast transport
+ // and the Cast sender.
+ // Must be called before initialization of audio or video.
+ void StartUDP(const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const ErrorCallback& error_callback);
+
+ protected:
+ void StatusNotificationCB(
+ const ErrorCallback& error_callback,
+ media::cast::CastTransportStatus status);
+
+ virtual void ReceivePacket(std::unique_ptr<media::cast::Packet> packet) = 0;
+
+ base::ThreadChecker thread_checker_;
+ scoped_refptr<media::cast::CastEnvironment> cast_environment_;
+ std::unique_ptr<media::cast::CastTransport> cast_transport_;
+
+ // Proxy to the IO message loop.
+ const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ base::WeakPtrFactory<CastSessionDelegateBase> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(CastSessionDelegateBase);
+};
+
+// This class hosts CastSender and connects it to audio/video frame input
+// and network socket.
+// This class is created on the render thread and destroyed on the IO
+// thread. All methods are accessible only on the IO thread.
+class CastSessionDelegate : public CastSessionDelegateBase {
+ public:
+ typedef base::Callback<void(const scoped_refptr<
+ media::cast::AudioFrameInput>&)> AudioFrameInputAvailableCallback;
+ typedef base::Callback<void(const scoped_refptr<
+ media::cast::VideoFrameInput>&)> VideoFrameInputAvailableCallback;
+ typedef base::Callback<void(std::unique_ptr<base::Value>)> EventLogsCallback;
+ typedef base::Callback<void(std::unique_ptr<base::DictionaryValue>)>
+ StatsCallback;
+
+ CastSessionDelegate();
+ ~CastSessionDelegate() override;
+
+ void StartUDP(const net::IPEndPoint& local_endpoint,
+ const net::IPEndPoint& remote_endpoint,
+ std::unique_ptr<base::DictionaryValue> options,
+ const ErrorCallback& error_callback);
+
+ // After calling StartAudio() or StartVideo() encoding of that media will
+ // begin as soon as data is delivered to its sink, if the second method is
+ // called the first media will be restarted. It is strongly recommended not to
+ // deliver any data between calling the two methods.
+ // It's OK to call only one of the two methods.
+ // StartUDP must be called before these methods.
+ void StartAudio(const media::cast::FrameSenderConfig& config,
+ const AudioFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback);
+
+ void StartVideo(
+ const media::cast::FrameSenderConfig& config,
+ const VideoFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback,
+ const media::cast::CreateVideoEncodeAcceleratorCallback& create_vea_cb,
+ const media::cast::CreateVideoEncodeMemoryCallback&
+ create_video_encode_mem_cb);
+
+ // Start remoting session for one stream. After calling this method, a
+ // remoting sender will be ready for sending the demuxed stream. StartUDP()
+ // must be called before calling this method.
+ void StartRemotingStream(int32_t stream_id,
+ const media::cast::FrameSenderConfig& config,
+ const ErrorCallback& error_callback);
+
+ void ToggleLogging(bool is_audio, bool enable);
+ void GetEventLogsAndReset(bool is_audio,
+ const std::string& extra_data, const EventLogsCallback& callback);
+ void GetStatsAndReset(bool is_audio, const StatsCallback& callback);
+
+ protected:
+ // Called to report back operational status changes. The first time this is
+ // called with STATUS_INITIALIZED will result in running the "frame input
+ // available" callback, to indicate the session is ready to accept incoming
+ // audio/video frames. If this is called with an error that has halted the
+ // session, the |error_callback| provided to StartXXX() will be run. This
+ // method may be called multiple times during the session to indicate codec
+ // re-initializations are taking place and/or runtime errors have occurred.
+ void OnOperationalStatusChange(
+ bool is_for_audio,
+ const ErrorCallback& error_callback,
+ media::cast::OperationalStatus result);
+
+ private:
+ void ReceivePacket(std::unique_ptr<media::cast::Packet> packet) override;
+
+ std::unique_ptr<media::cast::CastSender> cast_sender_;
+
+ AudioFrameInputAvailableCallback audio_frame_input_available_callback_;
+ VideoFrameInputAvailableCallback video_frame_input_available_callback_;
+
+ std::unique_ptr<media::cast::RawEventSubscriberBundle> event_subscribers_;
+
+ base::WeakPtrFactory<CastSessionDelegate> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(CastSessionDelegate);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_SESSION_DELEGATE_H_
diff --git a/chromium/chrome/renderer/media/cast_threads.cc b/chromium/chrome/renderer/media/cast_threads.cc
new file mode 100644
index 00000000000..9f3786e1427
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_threads.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_threads.h"
+
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+
+CastThreads::CastThreads()
+ : audio_encode_thread_("CastAudioEncodeThread"),
+ video_encode_thread_("CastVideoEncodeThread") {
+ audio_encode_thread_.Start();
+ video_encode_thread_.Start();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+CastThreads::GetAudioEncodeTaskRunner() {
+ return audio_encode_thread_.task_runner();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+CastThreads::GetVideoEncodeTaskRunner() {
+ return video_encode_thread_.task_runner();
+}
diff --git a/chromium/chrome/renderer/media/cast_threads.h b/chromium/chrome/renderer/media/cast_threads.h
new file mode 100644
index 00000000000..01f05552a0c
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_threads.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Manages threads used by Cast Streaming Extensions API. There is a
+// singleton object of this class in the renderer.
+//
+// There are two threads owned by this class:
+// 1. Audio encode thread.
+// 2. Video encode thread.
+// These two threads are started this object is created.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_THREADS_H_
+#define CHROME_RENDERER_MEDIA_CAST_THREADS_H_
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+
+class CastThreads {
+ public:
+ scoped_refptr<base::SingleThreadTaskRunner> GetAudioEncodeTaskRunner();
+ scoped_refptr<base::SingleThreadTaskRunner> GetVideoEncodeTaskRunner();
+
+ private:
+ friend struct base::LazyInstanceTraitsBase<CastThreads>;
+
+ CastThreads();
+
+ base::Thread audio_encode_thread_;
+ base::Thread video_encode_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastThreads);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_THREADS_H_
diff --git a/chromium/chrome/renderer/media/cast_transport_ipc.cc b/chromium/chrome/renderer/media/cast_transport_ipc.cc
new file mode 100644
index 00000000000..7d9d41df513
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_transport_ipc.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_transport_ipc.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/containers/id_map.h"
+#include "chrome/common/cast_messages.h"
+#include "chrome/renderer/media/cast_ipc_dispatcher.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "media/cast/cast_sender.h"
+
+CastTransportIPC::CastTransportIPC(
+ const net::IPEndPoint& local_end_point,
+ const net::IPEndPoint& remote_end_point,
+ std::unique_ptr<base::DictionaryValue> options,
+ const media::cast::PacketReceiverCallback& packet_callback,
+ const media::cast::CastTransportStatusCallback& status_cb,
+ const media::cast::BulkRawEventsCallback& raw_events_cb)
+ : channel_id_(-1),
+ packet_callback_(packet_callback),
+ status_callback_(status_cb),
+ raw_events_callback_(raw_events_cb) {
+ if (CastIPCDispatcher::Get()) {
+ // TODO(miu): CastIPCDispatcher should be provided as a ctor argument.
+ channel_id_ = CastIPCDispatcher::Get()->AddSender(this);
+ Send(new CastHostMsg_New(channel_id_, local_end_point, remote_end_point,
+ *options));
+ }
+}
+
+CastTransportIPC::~CastTransportIPC() {
+ Send(new CastHostMsg_Delete(channel_id_));
+ if (CastIPCDispatcher::Get()) {
+ CastIPCDispatcher::Get()->RemoveSender(channel_id_);
+ }
+}
+
+void CastTransportIPC::InitializeStream(
+ const media::cast::CastTransportRtpConfig& config,
+ std::unique_ptr<media::cast::RtcpObserver> rtcp_observer) {
+ if (rtcp_observer) {
+ DCHECK(clients_.find(config.ssrc) == clients_.end());
+ clients_[config.ssrc] = std::move(rtcp_observer);
+ }
+ Send(new CastHostMsg_InitializeStream(channel_id_, config));
+}
+
+void CastTransportIPC::InsertFrame(uint32_t ssrc,
+ const media::cast::EncodedFrame& frame) {
+ Send(new CastHostMsg_InsertFrame(channel_id_, ssrc, frame));
+}
+
+void CastTransportIPC::SendSenderReport(
+ uint32_t ssrc,
+ base::TimeTicks current_time,
+ media::cast::RtpTimeTicks current_time_as_rtp_timestamp) {
+ Send(new CastHostMsg_SendSenderReport(channel_id_, ssrc, current_time,
+ current_time_as_rtp_timestamp));
+}
+
+void CastTransportIPC::CancelSendingFrames(
+ uint32_t ssrc,
+ const std::vector<media::cast::FrameId>& frame_ids) {
+ Send(new CastHostMsg_CancelSendingFrames(channel_id_, ssrc, frame_ids));
+}
+
+void CastTransportIPC::ResendFrameForKickstart(uint32_t ssrc,
+ media::cast::FrameId frame_id) {
+ Send(new CastHostMsg_ResendFrameForKickstart(channel_id_, ssrc, frame_id));
+}
+
+void CastTransportIPC::AddValidRtpReceiver(uint32_t rtp_sender_ssrc,
+ uint32_t rtp_receiver_ssrc) {
+ Send(new CastHostMsg_AddValidRtpReceiver(channel_id_, rtp_sender_ssrc,
+ rtp_receiver_ssrc));
+}
+
+void CastTransportIPC::InitializeRtpReceiverRtcpBuilder(
+ uint32_t rtp_receiver_ssrc,
+ const media::cast::RtcpTimeData& time_data) {
+ Send(new CastHostMsg_InitializeRtpReceiverRtcpBuilder(
+ channel_id_, rtp_receiver_ssrc, time_data));
+}
+
+void CastTransportIPC::AddCastFeedback(
+ const media::cast::RtcpCastMessage& cast_message,
+ base::TimeDelta target_delay) {
+ Send(
+ new CastHostMsg_AddCastFeedback(channel_id_, cast_message, target_delay));
+}
+
+void CastTransportIPC::AddPli(const media::cast::RtcpPliMessage& pli_message) {
+ Send(new CastHostMsg_AddPli(channel_id_, pli_message));
+}
+
+void CastTransportIPC::AddRtcpEvents(
+ const media::cast::ReceiverRtcpEventSubscriber::RtcpEvents& rtcp_events) {
+ Send(new CastHostMsg_AddRtcpEvents(channel_id_, rtcp_events));
+}
+
+void CastTransportIPC::AddRtpReceiverReport(
+ const media::cast::RtcpReportBlock& rtp_receiver_report_block) {
+ Send(new CastHostMsg_AddRtpReceiverReport(channel_id_,
+ rtp_receiver_report_block));
+}
+
+void CastTransportIPC::SendRtcpFromRtpReceiver() {
+ Send(new CastHostMsg_SendRtcpFromRtpReceiver(channel_id_));
+}
+
+void CastTransportIPC::OnNotifyStatusChange(
+ media::cast::CastTransportStatus status) {
+ status_callback_.Run(status);
+}
+
+void CastTransportIPC::OnRawEvents(
+ const std::vector<media::cast::PacketEvent>& packet_events,
+ const std::vector<media::cast::FrameEvent>& frame_events) {
+ // Note: Casting away const to avoid having to copy all the data elements. As
+ // the only consumer of this data in the IPC message, mutating the inputs
+ // should be acceptable. Just nod and blame the interface we were given here.
+ std::unique_ptr<std::vector<media::cast::FrameEvent>> taken_frame_events(
+ new std::vector<media::cast::FrameEvent>());
+ taken_frame_events->swap(
+ const_cast<std::vector<media::cast::FrameEvent>&>(frame_events));
+ std::unique_ptr<std::vector<media::cast::PacketEvent>> taken_packet_events(
+ new std::vector<media::cast::PacketEvent>());
+ taken_packet_events->swap(
+ const_cast<std::vector<media::cast::PacketEvent>&>(packet_events));
+ raw_events_callback_.Run(std::move(taken_frame_events),
+ std::move(taken_packet_events));
+}
+
+void CastTransportIPC::OnRtt(uint32_t rtp_sender_ssrc, base::TimeDelta rtt) {
+ auto it = clients_.find(rtp_sender_ssrc);
+ if (it == clients_.end()) {
+ LOG(ERROR) << "Received RTT report for unknown SSRC: " << rtp_sender_ssrc;
+ return;
+ }
+ it->second->OnReceivedRtt(rtt);
+}
+
+void CastTransportIPC::OnRtcpCastMessage(
+ uint32_t rtp_sender_ssrc,
+ const media::cast::RtcpCastMessage& cast_message) {
+ auto it = clients_.find(rtp_sender_ssrc);
+ if (it == clients_.end()) {
+ LOG(ERROR) << "Received cast message for unknown SSRC: " << rtp_sender_ssrc;
+ return;
+ }
+ it->second->OnReceivedCastMessage(cast_message);
+}
+
+void CastTransportIPC::OnReceivedPli(uint32_t rtp_sender_ssrc) {
+ auto it = clients_.find(rtp_sender_ssrc);
+ if (it == clients_.end()) {
+ LOG(ERROR) << "Received picture loss indicator for unknown SSRC: "
+ << rtp_sender_ssrc;
+ return;
+ }
+ it->second->OnReceivedPli();
+}
+
+void CastTransportIPC::OnReceivedPacket(const media::cast::Packet& packet) {
+ if (!packet_callback_.is_null()) {
+ // TODO(hubbe): Perhaps an non-ownership-transferring cb here?
+ std::unique_ptr<media::cast::Packet> packet_copy(
+ new media::cast::Packet(packet));
+ packet_callback_.Run(std::move(packet_copy));
+ } else {
+ DVLOG(1) << "CastIPCDispatcher::OnReceivedPacket no packet callback yet.";
+ }
+}
+
+void CastTransportIPC::Send(IPC::Message* message) {
+ if (CastIPCDispatcher::Get() && channel_id_ != -1) {
+ CastIPCDispatcher::Get()->Send(message);
+ } else {
+ delete message;
+ }
+}
diff --git a/chromium/chrome/renderer/media/cast_transport_ipc.h b/chromium/chrome/renderer/media/cast_transport_ipc.h
new file mode 100644
index 00000000000..2cf54cf7921
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_transport_ipc.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_TRANSPORT_IPC_H_
+#define CHROME_RENDERER_MEDIA_CAST_TRANSPORT_IPC_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/net/cast_transport.h"
+
+// This implementation of the CastTransport interface communicates with the
+// browser process over IPC and relays all calls to/from the cast transport to
+// the browser process. The primary reason for this arrangement is to give the
+// renderer less direct control over the UDP sockets.
+class CastTransportIPC : public media::cast::CastTransport {
+ public:
+ CastTransportIPC(const net::IPEndPoint& local_end_point,
+ const net::IPEndPoint& remote_end_point,
+ std::unique_ptr<base::DictionaryValue> options,
+ const media::cast::PacketReceiverCallback& packet_callback,
+ const media::cast::CastTransportStatusCallback& status_cb,
+ const media::cast::BulkRawEventsCallback& raw_events_cb);
+
+ ~CastTransportIPC() override;
+
+ // media::cast::CastTransport implementation.
+ void InitializeStream(
+ const media::cast::CastTransportRtpConfig& config,
+ std::unique_ptr<media::cast::RtcpObserver> rtcp_observer) override;
+ void InsertFrame(uint32_t ssrc,
+ const media::cast::EncodedFrame& frame) override;
+ void SendSenderReport(
+ uint32_t ssrc,
+ base::TimeTicks current_time,
+ media::cast::RtpTimeTicks current_time_as_rtp_timestamp) override;
+ void CancelSendingFrames(
+ uint32_t ssrc,
+ const std::vector<media::cast::FrameId>& frame_ids) override;
+ void ResendFrameForKickstart(uint32_t ssrc,
+ media::cast::FrameId frame_id) override;
+ void AddValidRtpReceiver(uint32_t rtp_sender_ssrc,
+ uint32_t rtp_receiver_ssrc) override;
+ void InitializeRtpReceiverRtcpBuilder(
+ uint32_t rtp_receiver_ssrc,
+ const media::cast::RtcpTimeData& time_data) override;
+ void AddCastFeedback(const media::cast::RtcpCastMessage& cast_message,
+ base::TimeDelta target_delay) override;
+ void AddPli(const media::cast::RtcpPliMessage& pli_message) override;
+ void AddRtcpEvents(const media::cast::ReceiverRtcpEventSubscriber::RtcpEvents&
+ rtcp_events) override;
+ void AddRtpReceiverReport(
+ const media::cast::RtcpReportBlock& rtp_receiver_report_block) override;
+ void SendRtcpFromRtpReceiver() override;
+ void SetOptions(const base::DictionaryValue& options) final {}
+ void OnNotifyStatusChange(media::cast::CastTransportStatus status);
+ void OnRawEvents(const std::vector<media::cast::PacketEvent>& packet_events,
+ const std::vector<media::cast::FrameEvent>& frame_events);
+ void OnRtt(uint32_t rtp_sender_ssrc, base::TimeDelta rtt);
+ void OnRtcpCastMessage(uint32_t rtp_sender_ssrc,
+ const media::cast::RtcpCastMessage& cast_message);
+ void OnReceivedPli(uint32_t rtp_sender_ssrc);
+ void OnReceivedPacket(const media::cast::Packet& packet);
+
+ private:
+ void Send(IPC::Message* message);
+
+ int32_t channel_id_;
+ media::cast::PacketReceiverCallback packet_callback_;
+ media::cast::CastTransportStatusCallback status_callback_;
+ media::cast::BulkRawEventsCallback raw_events_callback_;
+ using ClientMap =
+ std::map<uint32_t, std::unique_ptr<media::cast::RtcpObserver>>;
+ ClientMap clients_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastTransportIPC);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_TRANSPORT_IPC_H_
diff --git a/chromium/chrome/renderer/media/cast_udp_transport.cc b/chromium/chrome/renderer/media/cast_udp_transport.cc
new file mode 100644
index 00000000000..a8302a8a0fa
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_udp_transport.cc
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/cast_udp_transport.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/values.h"
+#include "chrome/renderer/media/cast_session.h"
+
+CastUdpTransport::CastUdpTransport(const scoped_refptr<CastSession>& session)
+ : cast_session_(session), options_(new base::DictionaryValue) {}
+
+CastUdpTransport::~CastUdpTransport() {
+}
+
+void CastUdpTransport::SetDestination(
+ const net::IPEndPoint& remote_address,
+ const CastSessionDelegate::ErrorCallback& error_callback) {
+ DVLOG(1) << "CastUdpTransport::SetDestination = "
+ << remote_address.ToString();
+ remote_address_ = remote_address;
+ cast_session_->StartUDP(
+ remote_address, base::WrapUnique(options_->DeepCopy()), error_callback);
+}
+
+void CastUdpTransport::SetOptions(
+ std::unique_ptr<base::DictionaryValue> options) {
+ options_ = std::move(options);
+}
diff --git a/chromium/chrome/renderer/media/cast_udp_transport.h b/chromium/chrome/renderer/media/cast_udp_transport.h
new file mode 100644
index 00000000000..726edbe5c67
--- /dev/null
+++ b/chromium/chrome/renderer/media/cast_udp_transport.h
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CAST_UDP_TRANSPORT_H_
+#define CHROME_RENDERER_MEDIA_CAST_UDP_TRANSPORT_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/renderer/media/cast_session_delegate.h"
+#include "net/base/ip_endpoint.h"
+
+namespace base {
+class DictionaryValue;
+} // namespace base
+
+class CastSession;
+
+// This class represents the transport mechanism used by Cast RTP streams
+// to connect to a remote client. It specifies the destination address
+// and network protocol used to send Cast RTP streams.
+class CastUdpTransport {
+ public:
+ explicit CastUdpTransport(const scoped_refptr<CastSession>& session);
+ virtual ~CastUdpTransport();
+
+ // Specify the remote IP address and port.
+ void SetDestination(const net::IPEndPoint& remote_address,
+ const CastSessionDelegate::ErrorCallback& error_callback);
+
+ // Set options.
+ void SetOptions(std::unique_ptr<base::DictionaryValue> options);
+
+ private:
+ const scoped_refptr<CastSession> cast_session_;
+ net::IPEndPoint remote_address_;
+ std::unique_ptr<base::DictionaryValue> options_;
+ base::WeakPtrFactory<CastUdpTransport> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(CastUdpTransport);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CAST_UDP_TRANSPORT_H_
diff --git a/chromium/chrome/renderer/media/chrome_key_systems.cc b/chromium/chrome/renderer/media/chrome_key_systems.cc
new file mode 100644
index 00000000000..b1c800648c7
--- /dev/null
+++ b/chromium/chrome/renderer/media/chrome_key_systems.cc
@@ -0,0 +1,335 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/chrome_key_systems.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "components/cdm/renderer/external_clear_key_key_system_properties.h"
+#include "components/cdm/renderer/widevine_key_system_properties.h"
+#include "content/public/renderer/render_thread.h"
+#include "media/base/decrypt_config.h"
+#include "media/base/eme_constants.h"
+#include "media/base/key_system_properties.h"
+#include "media/media_buildflags.h"
+#include "third_party/widevine/cdm/buildflags.h"
+
+#if defined(OS_ANDROID)
+#include "components/cdm/renderer/android_key_systems.h"
+#endif
+
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+#include "base/feature_list.h"
+#include "content/public/renderer/key_system_support.h"
+#include "media/base/media_switches.h"
+#include "media/base/video_codecs.h"
+#if BUILDFLAG(ENABLE_WIDEVINE)
+#include "third_party/widevine/cdm/widevine_cdm_common.h"
+// TODO(crbug.com/663554): Needed for WIDEVINE_CDM_MIN_GLIBC_VERSION.
+// component updated CDM on all desktop platforms and remove this.
+#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
+// The following must be after widevine_cdm_version.h.
+#if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION)
+#include <gnu/libc-version.h>
+#include "base/version.h"
+#endif // defined(WIDEVINE_CDM_MIN_GLIBC_VERSION)
+#endif // BUILDFLAG(ENABLE_WIDEVINE)
+#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
+
+using media::EmeFeatureSupport;
+using media::EmeSessionTypeSupport;
+using media::KeySystemProperties;
+using media::SupportedCodecs;
+
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+
+// External Clear Key (used for testing).
+static void AddExternalClearKey(
+ std::vector<std::unique_ptr<KeySystemProperties>>* concrete_key_systems) {
+ // TODO(xhwang): Move these into an array so we can use a for loop to add
+ // supported key systems below.
+ static const char kExternalClearKeyKeySystem[] =
+ "org.chromium.externalclearkey";
+ static const char kExternalClearKeyDecryptOnlyKeySystem[] =
+ "org.chromium.externalclearkey.decryptonly";
+ static const char kExternalClearKeyMessageTypeTestKeySystem[] =
+ "org.chromium.externalclearkey.messagetypetest";
+ static const char kExternalClearKeyFileIOTestKeySystem[] =
+ "org.chromium.externalclearkey.fileiotest";
+ static const char kExternalClearKeyOutputProtectionTestKeySystem[] =
+ "org.chromium.externalclearkey.outputprotectiontest";
+ static const char kExternalClearKeyPlatformVerificationTestKeySystem[] =
+ "org.chromium.externalclearkey.platformverificationtest";
+ static const char kExternalClearKeyInitializeFailKeySystem[] =
+ "org.chromium.externalclearkey.initializefail";
+ static const char kExternalClearKeyCrashKeySystem[] =
+ "org.chromium.externalclearkey.crash";
+ static const char kExternalClearKeyVerifyCdmHostTestKeySystem[] =
+ "org.chromium.externalclearkey.verifycdmhosttest";
+ static const char kExternalClearKeyStorageIdTestKeySystem[] =
+ "org.chromium.externalclearkey.storageidtest";
+ static const char kExternalClearKeyDifferentGuidTestKeySystem[] =
+ "org.chromium.externalclearkey.differentguid";
+ static const char kExternalClearKeyCdmProxyKeySystem[] =
+ "org.chromium.externalclearkey.cdmproxy";
+
+ // TODO(xhwang): Actually use |capability| to determine capabilities.
+ media::mojom::KeySystemCapabilityPtr capability;
+ if (!content::IsKeySystemSupported(kExternalClearKeyKeySystem, &capability)) {
+ DVLOG(1) << "External Clear Key not supported";
+ return;
+ }
+
+ concrete_key_systems->emplace_back(
+ new cdm::ExternalClearKeyProperties(kExternalClearKeyKeySystem));
+
+ // Add support of decrypt-only mode in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyDecryptOnlyKeySystem));
+
+ // A key system that triggers various types of messages in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyMessageTypeTestKeySystem));
+
+ // A key system that triggers the FileIO test in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyFileIOTestKeySystem));
+
+ // A key system that triggers the output protection test in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyOutputProtectionTestKeySystem));
+
+ // A key system that triggers the platform verification test in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyPlatformVerificationTestKeySystem));
+
+ // A key system that Chrome thinks is supported by ClearKeyCdm, but actually
+ // will be refused by ClearKeyCdm. This is to test the CDM initialization
+ // failure case.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyInitializeFailKeySystem));
+
+ // A key system that triggers a crash in ClearKeyCdm.
+ concrete_key_systems->emplace_back(
+ new cdm::ExternalClearKeyProperties(kExternalClearKeyCrashKeySystem));
+
+ // A key system that triggers the verify host files test in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyVerifyCdmHostTestKeySystem));
+
+ // A key system that fetches the Storage ID in ClearKeyCdm.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyStorageIdTestKeySystem));
+
+ // A key system that is registered with a different CDM GUID.
+ concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties(
+ kExternalClearKeyDifferentGuidTestKeySystem));
+
+ // A key system that requires the use of CdmProxy.
+ concrete_key_systems->emplace_back(
+ new cdm::ExternalClearKeyProperties(kExternalClearKeyCdmProxyKeySystem));
+}
+
+#if BUILDFLAG(ENABLE_WIDEVINE)
+static SupportedCodecs GetSupportedCodecs(
+ const std::vector<media::VideoCodec>& supported_video_codecs,
+ bool supports_vp9_profile2,
+ bool is_secure) {
+ SupportedCodecs supported_codecs = media::EME_CODEC_NONE;
+
+ // Audio codecs are always supported because the CDM only does decrypt-only
+ // for audio. The only exception is when |is_secure| is true and there's no
+ // secure video decoder available, which is a signal that secure hardware
+ // decryption is not available either.
+ // TODO(sandersd): Distinguish these from those that are directly supported,
+ // as those may offer a higher level of protection.
+ if (!supported_video_codecs.empty() || !is_secure) {
+ supported_codecs |= media::EME_CODEC_OPUS;
+ supported_codecs |= media::EME_CODEC_VORBIS;
+ supported_codecs |= media::EME_CODEC_FLAC;
+#if BUILDFLAG(USE_PROPRIETARY_CODECS)
+ supported_codecs |= media::EME_CODEC_AAC;
+#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
+ }
+
+ // Video codecs are determined by what was registered for the CDM.
+ for (const auto& codec : supported_video_codecs) {
+ switch (codec) {
+ case media::VideoCodec::kCodecVP8:
+ supported_codecs |= media::EME_CODEC_VP8;
+ break;
+ case media::VideoCodec::kCodecVP9:
+ supported_codecs |= media::EME_CODEC_VP9_PROFILE0;
+ if (supports_vp9_profile2)
+ supported_codecs |= media::EME_CODEC_VP9_PROFILE2;
+ break;
+ case media::VideoCodec::kCodecAV1:
+ supported_codecs |= media::EME_CODEC_AV1;
+ break;
+#if BUILDFLAG(USE_PROPRIETARY_CODECS)
+ case media::VideoCodec::kCodecH264:
+ supported_codecs |= media::EME_CODEC_AVC1;
+ break;
+#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
+ default:
+ DVLOG(1) << "Unexpected supported codec: " << GetCodecName(codec);
+ break;
+ }
+ }
+
+ return supported_codecs;
+}
+
+// Returns persistent-license session support.
+static EmeSessionTypeSupport GetPersistentLicenseSupport(
+ bool supported_by_the_cdm) {
+ // Do not support persistent-license if the process cannot persist data.
+ // TODO(crbug.com/457487): Have a better plan on this. See bug for details.
+ if (ChromeRenderThreadObserver::is_incognito_process()) {
+ DVLOG(2) << __func__ << ": Not supported in incognito process.";
+ return EmeSessionTypeSupport::NOT_SUPPORTED;
+ }
+
+ if (!supported_by_the_cdm) {
+ DVLOG(2) << __func__ << ": Not supported by the CDM.";
+ return EmeSessionTypeSupport::NOT_SUPPORTED;
+ }
+
+// On ChromeOS, platform verification is similar to CDM host verification.
+#if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) || defined(OS_CHROMEOS)
+ bool cdm_host_verification_potentially_supported = true;
+#else
+ bool cdm_host_verification_potentially_supported = false;
+#endif
+
+ // If we are sure CDM host verification is NOT supported, we should not
+ // support persistent-license.
+ if (!cdm_host_verification_potentially_supported) {
+ DVLOG(2) << __func__ << ": Not supported without CDM host verification.";
+ return EmeSessionTypeSupport::NOT_SUPPORTED;
+ }
+
+#if defined(OS_CHROMEOS)
+ // On ChromeOS, platform verification (similar to CDM host verification)
+ // requires identifier to be allowed.
+ // TODO(jrummell): Currently the ChromeOS CDM does not require storage ID
+ // to support persistent license. Update this logic when the new CDM requires
+ // storage ID.
+ return EmeSessionTypeSupport::SUPPORTED_WITH_IDENTIFIER;
+#elif BUILDFLAG(ENABLE_CDM_STORAGE_ID)
+ // On other platforms, we require storage ID to support persistent license.
+ return EmeSessionTypeSupport::SUPPORTED;
+#else
+ // Storage ID not implemented, so no support for persistent license.
+ DVLOG(2) << __func__ << ": Not supported without CDM storage ID.";
+ return EmeSessionTypeSupport::NOT_SUPPORTED;
+#endif // defined(OS_CHROMEOS)
+}
+
+static void AddWidevine(
+ std::vector<std::unique_ptr<KeySystemProperties>>* concrete_key_systems) {
+#if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION)
+ base::Version glibc_version(gnu_get_libc_version());
+ DCHECK(glibc_version.IsValid());
+ if (glibc_version < base::Version(WIDEVINE_CDM_MIN_GLIBC_VERSION))
+ return;
+#endif // defined(WIDEVINE_CDM_MIN_GLIBC_VERSION)
+
+ media::mojom::KeySystemCapabilityPtr capability;
+ if (!content::IsKeySystemSupported(kWidevineKeySystem, &capability)) {
+ DVLOG(1) << "Widevine CDM is not currently available.";
+ return;
+ }
+
+ // Codecs and encryption schemes.
+ auto codecs = GetSupportedCodecs(capability->video_codecs,
+ capability->supports_vp9_profile2,
+ /*is_secure=*/false);
+ const auto& encryption_schemes = capability->encryption_schemes;
+ // TODO(xhwang): Investigate whether hardware VP9 profile 2 is supported.
+ auto hw_secure_codecs = GetSupportedCodecs(capability->hw_secure_video_codecs,
+ /*supports_vp9_profile2=*/false,
+ /*is_secure=*/true);
+ const auto& hw_secure_encryption_schemes =
+ capability->hw_secure_encryption_schemes;
+
+ // Robustness.
+ using Robustness = cdm::WidevineKeySystemProperties::Robustness;
+ auto max_audio_robustness = Robustness::SW_SECURE_CRYPTO;
+ auto max_video_robustness = Robustness::SW_SECURE_DECODE;
+
+#if defined(OS_CHROMEOS)
+ // On ChromeOS, we support HW_SECURE_ALL even without hardware secure codecs.
+ // See WidevineKeySystemProperties::GetRobustnessConfigRule().
+ max_audio_robustness = Robustness::HW_SECURE_ALL;
+ max_video_robustness = Robustness::HW_SECURE_ALL;
+#else
+ if (base::FeatureList::IsEnabled(media::kHardwareSecureDecryption)) {
+ max_audio_robustness = Robustness::HW_SECURE_CRYPTO;
+ max_video_robustness = Robustness::HW_SECURE_ALL;
+ }
+#endif
+
+ // Session types.
+ bool cdm_supports_temporary_session = base::Contains(
+ capability->session_types, media::CdmSessionType::kTemporary);
+ if (!cdm_supports_temporary_session) {
+ DVLOG(1) << "Temporary session must be supported.";
+ return;
+ }
+
+ bool cdm_supports_persistent_license = base::Contains(
+ capability->session_types, media::CdmSessionType::kPersistentLicense);
+ auto persistent_license_support =
+ GetPersistentLicenseSupport(cdm_supports_persistent_license);
+
+ // TODO(xhwang): Check more conditions as needed.
+ auto persistent_usage_record_support =
+ base::Contains(capability->session_types,
+ media::CdmSessionType::kPersistentUsageRecord)
+ ? EmeSessionTypeSupport::SUPPORTED
+ : EmeSessionTypeSupport::NOT_SUPPORTED;
+
+ // Others.
+ auto persistent_state_support = EmeFeatureSupport::REQUESTABLE;
+ auto distinctive_identifier_support = EmeFeatureSupport::NOT_SUPPORTED;
+#if defined(OS_CHROMEOS)
+ distinctive_identifier_support = EmeFeatureSupport::REQUESTABLE;
+#endif
+
+ concrete_key_systems->emplace_back(new cdm::WidevineKeySystemProperties(
+ codecs, encryption_schemes, hw_secure_codecs,
+ hw_secure_encryption_schemes, max_audio_robustness, max_video_robustness,
+ persistent_license_support, persistent_usage_record_support,
+ persistent_state_support, distinctive_identifier_support));
+}
+#endif // BUILDFLAG(ENABLE_WIDEVINE)
+#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
+
+void AddChromeKeySystems(
+ std::vector<std::unique_ptr<KeySystemProperties>>* key_systems_properties) {
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+ if (base::FeatureList::IsEnabled(media::kExternalClearKeyForTesting))
+ AddExternalClearKey(key_systems_properties);
+
+#if BUILDFLAG(ENABLE_WIDEVINE)
+ AddWidevine(key_systems_properties);
+#endif // BUILDFLAG(ENABLE_WIDEVINE)
+
+#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
+
+#if defined(OS_ANDROID)
+ cdm::AddAndroidWidevine(key_systems_properties);
+#endif // defined(OS_ANDROID)
+}
diff --git a/chromium/chrome/renderer/media/chrome_key_systems.h b/chromium/chrome/renderer/media/chrome_key_systems.h
new file mode 100644
index 00000000000..3e82ee70e2b
--- /dev/null
+++ b/chromium/chrome/renderer/media/chrome_key_systems.h
@@ -0,0 +1,20 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_H_
+#define CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_H_
+
+#include <memory>
+#include <vector>
+
+namespace media {
+class KeySystemProperties;
+}
+
+// Register the key systems supported by populating |key_systems_properties|.
+void AddChromeKeySystems(
+ std::vector<std::unique_ptr<media::KeySystemProperties>>*
+ key_systems_properties);
+
+#endif // CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_H_
diff --git a/chromium/chrome/renderer/media/chrome_key_systems_provider.cc b/chromium/chrome/renderer/media/chrome_key_systems_provider.cc
new file mode 100644
index 00000000000..e1ba75626fe
--- /dev/null
+++ b/chromium/chrome/renderer/media/chrome_key_systems_provider.cc
@@ -0,0 +1,82 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/chrome_key_systems_provider.h"
+
+#include "base/time/default_tick_clock.h"
+#include "chrome/renderer/media/chrome_key_systems.h"
+#include "third_party/widevine/cdm/buildflags.h"
+
+#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)
+#include "third_party/widevine/cdm/widevine_cdm_common.h"
+#endif
+
+ChromeKeySystemsProvider::ChromeKeySystemsProvider()
+ : has_updated_(false),
+ is_update_needed_(true),
+ tick_clock_(base::DefaultTickClock::GetInstance()) {}
+
+ChromeKeySystemsProvider::~ChromeKeySystemsProvider() {}
+
+void ChromeKeySystemsProvider::AddSupportedKeySystems(
+ std::vector<std::unique_ptr<media::KeySystemProperties>>* key_systems) {
+ DCHECK(key_systems);
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!test_provider_.is_null()) {
+ test_provider_.Run(key_systems);
+ } else {
+ AddChromeKeySystems(key_systems);
+ }
+
+ has_updated_ = true;
+ last_update_time_ticks_ = tick_clock_->NowTicks();
+
+// Check whether all potentially supported key systems are supported. If so,
+// no need to update again.
+#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)
+ for (const auto& properties : *key_systems) {
+ if (properties->GetKeySystemName() == kWidevineKeySystem) {
+ is_update_needed_ = false;
+ }
+ }
+#else
+ is_update_needed_ = false;
+#endif
+}
+
+bool ChromeKeySystemsProvider::IsKeySystemsUpdateNeeded() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Always needs update if we have never updated, regardless the
+ // |last_update_time_ticks_|'s initial value.
+ if (!has_updated_) {
+ DCHECK(is_update_needed_);
+ return true;
+ }
+
+ if (!is_update_needed_)
+ return false;
+
+ // The update could be expensive. For example, it could involve a sync IPC to
+ // the browser process. Use a minimum update interval to avoid unnecessarily
+ // frequent update.
+ static const int kMinUpdateIntervalInMilliseconds = 1000;
+ if ((tick_clock_->NowTicks() - last_update_time_ticks_).InMilliseconds() <
+ kMinUpdateIntervalInMilliseconds) {
+ return false;
+ }
+
+ return true;
+}
+
+void ChromeKeySystemsProvider::SetTickClockForTesting(
+ const base::TickClock* tick_clock) {
+ tick_clock_ = tick_clock;
+}
+
+void ChromeKeySystemsProvider::SetProviderDelegateForTesting(
+ const KeySystemsProviderDelegate& test_provider) {
+ test_provider_ = test_provider;
+}
diff --git a/chromium/chrome/renderer/media/chrome_key_systems_provider.h b/chromium/chrome/renderer/media/chrome_key_systems_provider.h
new file mode 100644
index 00000000000..48b45e5b3a0
--- /dev/null
+++ b/chromium/chrome/renderer/media/chrome_key_systems_provider.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_PROVIDER_H_
+#define CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_PROVIDER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "media/base/key_system_properties.h"
+
+typedef std::vector<std::unique_ptr<media::KeySystemProperties>>
+ KeySystemPropertiesVector;
+typedef base::Callback<void(KeySystemPropertiesVector*)>
+ KeySystemsProviderDelegate;
+
+class ChromeKeySystemsProvider {
+ public:
+ ChromeKeySystemsProvider();
+ ~ChromeKeySystemsProvider();
+
+ // Adds properties for supported key systems.
+ void AddSupportedKeySystems(KeySystemPropertiesVector* key_systems);
+
+ // Returns whether client key systems properties should be updated.
+ // TODO(chcunningham): Refactor this to a proper change "observer" API that is
+ // less fragile (don't assume AddSupportedKeySystems has just one caller).
+ bool IsKeySystemsUpdateNeeded();
+
+ void SetTickClockForTesting(const base::TickClock* tick_clock);
+
+ void SetProviderDelegateForTesting(
+ const KeySystemsProviderDelegate& test_provider);
+
+ private:
+ // Whether AddSupportedKeySystems() has ever been called.
+ bool has_updated_;
+
+ // Whether a future update is needed. For example, when some potentially
+ // supported key systems are NOT supported yet. This could happen when the
+ // required component for a key system is not yet available.
+ bool is_update_needed_;
+
+ // Throttle how often we signal an update is needed to avoid unnecessary high
+ // frequency of expensive IPC calls.
+ base::TimeTicks last_update_time_ticks_;
+ const base::TickClock* tick_clock_;
+
+ // Ensure all methods are called from the same (Main) thread.
+ base::ThreadChecker thread_checker_;
+
+ // For unit tests to inject their own key systems. Will bypass adding default
+ // Chrome key systems when set.
+ KeySystemsProviderDelegate test_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeKeySystemsProvider);
+};
+
+#endif // CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_PROVIDER_H_
diff --git a/chromium/chrome/renderer/media/chrome_key_systems_provider_unittest.cc b/chromium/chrome/renderer/media/chrome_key_systems_provider_unittest.cc
new file mode 100644
index 00000000000..a57a4ab2cbc
--- /dev/null
+++ b/chromium/chrome/renderer/media/chrome_key_systems_provider_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/chrome_key_systems_provider.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/widevine/cdm/buildflags.h"
+#include "third_party/widevine/cdm/widevine_cdm_common.h"
+
+namespace {
+
+class TestKeySystemProperties : public media::KeySystemProperties {
+ public:
+ explicit TestKeySystemProperties(const std::string& key_system_name)
+ : key_system_name_(key_system_name) {}
+
+ std::string GetKeySystemName() const override { return key_system_name_; }
+ bool IsSupportedInitDataType(
+ media::EmeInitDataType init_data_type) const override {
+ return false;
+ }
+
+ media::EmeConfigRule GetEncryptionSchemeConfigRule(
+ media::EncryptionMode encryption_scheme) const override {
+ return media::EmeConfigRule::NOT_SUPPORTED;
+ }
+
+ media::SupportedCodecs GetSupportedCodecs() const override {
+ return media::EME_CODEC_NONE;
+ }
+
+ media::EmeConfigRule GetRobustnessConfigRule(
+ media::EmeMediaType media_type,
+ const std::string& requested_robustness) const override {
+ return requested_robustness.empty() ? media::EmeConfigRule::SUPPORTED
+ : media::EmeConfigRule::NOT_SUPPORTED;
+ }
+
+ media::EmeSessionTypeSupport GetPersistentLicenseSessionSupport()
+ const override {
+ return media::EmeSessionTypeSupport::NOT_SUPPORTED;
+ }
+
+ media::EmeSessionTypeSupport GetPersistentUsageRecordSessionSupport()
+ const override {
+ return media::EmeSessionTypeSupport::NOT_SUPPORTED;
+ }
+
+ media::EmeFeatureSupport GetPersistentStateSupport() const override {
+ return media::EmeFeatureSupport::NOT_SUPPORTED;
+ }
+
+ media::EmeFeatureSupport GetDistinctiveIdentifierSupport() const override {
+ return media::EmeFeatureSupport::NOT_SUPPORTED;
+ }
+
+ private:
+ const std::string key_system_name_;
+};
+
+class TestKeySystemsProviderDelegate {
+ public:
+ TestKeySystemsProviderDelegate() : include_widevine_(false) {}
+
+ void AddTestKeySystems(
+ std::vector<std::unique_ptr<media::KeySystemProperties>>* key_systems) {
+ key_systems->emplace_back(
+ new TestKeySystemProperties("com.example.foobar"));
+
+ if (include_widevine_) {
+#if BUILDFLAG(ENABLE_WIDEVINE)
+ key_systems->emplace_back(
+ new TestKeySystemProperties(kWidevineKeySystem));
+#else
+ // Tests should only attempt to include Widevine when it is available.
+ NOTREACHED();
+#endif
+ }
+ }
+
+ void set_include_widevine(bool include_widevine) {
+ include_widevine_ = include_widevine;
+ }
+
+ private:
+ bool include_widevine_;
+};
+
+} // namespace
+
+TEST(ChromeKeySystemsProviderTest, IsKeySystemsUpdateNeeded) {
+ ChromeKeySystemsProvider key_systems_provider;
+
+ base::SimpleTestTickClock tick_clock;
+ key_systems_provider.SetTickClockForTesting(&tick_clock);
+
+ std::unique_ptr<TestKeySystemsProviderDelegate> provider_delegate(
+ new TestKeySystemsProviderDelegate());
+ key_systems_provider.SetProviderDelegateForTesting(
+ base::Bind(&TestKeySystemsProviderDelegate::AddTestKeySystems,
+ base::Unretained(provider_delegate.get())));
+
+ // IsKeySystemsUpdateNeeded() always returns true after construction.
+ EXPECT_TRUE(key_systems_provider.IsKeySystemsUpdateNeeded());
+
+ std::vector<std::unique_ptr<media::KeySystemProperties>> key_systems;
+ key_systems_provider.AddSupportedKeySystems(&key_systems);
+
+ // No update needed immediately after AddSupportedKeySystems() call.
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+
+ // Widevine not initially provided.
+ EXPECT_EQ(key_systems.size(), 1U);
+ EXPECT_EQ(key_systems[0]->GetKeySystemName(), "com.example.foobar");
+
+ // This is timing related. The update interval for Widevine is 1000 ms.
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+ tick_clock.Advance(base::TimeDelta::FromMilliseconds(990));
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+ tick_clock.Advance(base::TimeDelta::FromMilliseconds(10));
+
+#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)
+ // Require update once enough time has passed for builds that install Widevine
+ // as a component.
+ EXPECT_TRUE(key_systems_provider.IsKeySystemsUpdateNeeded());
+
+ // Now add Widevine.
+ provider_delegate->set_include_widevine(true);
+ key_systems.clear();
+ key_systems_provider.AddSupportedKeySystems(&key_systems);
+
+ // Widevine should now be among the list.
+ bool found_widevine = false;
+ for (const auto& key_system_properties : key_systems) {
+ if (key_system_properties->GetKeySystemName() == kWidevineKeySystem) {
+ found_widevine = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found_widevine);
+
+ // Update not needed now, nor later because Widevine has been described.
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+ tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+ tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+#else
+ // No update needed for builds that either don't offer Widevine or do so
+ // as part of Chrome rather than component installer.
+ EXPECT_FALSE(key_systems_provider.IsKeySystemsUpdateNeeded());
+#endif // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)
+}
diff --git a/chromium/chrome/renderer/media/flash_embed_rewrite.cc b/chromium/chrome/renderer/media/flash_embed_rewrite.cc
new file mode 100644
index 00000000000..b74b5d1ea1a
--- /dev/null
+++ b/chromium/chrome/renderer/media/flash_embed_rewrite.cc
@@ -0,0 +1,77 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/flash_embed_rewrite.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "url/gurl.h"
+
+GURL FlashEmbedRewrite::RewriteFlashEmbedURL(const GURL& url) {
+ DCHECK(url.is_valid());
+
+ if (url.DomainIs("youtube.com") || url.DomainIs("youtube-nocookie.com"))
+ return RewriteYouTubeFlashEmbedURL(url);
+
+ if (url.DomainIs("dailymotion.com"))
+ return RewriteDailymotionFlashEmbedURL(url);
+
+ return GURL();
+}
+
+GURL FlashEmbedRewrite::RewriteYouTubeFlashEmbedURL(const GURL& url) {
+ // YouTube URLs are of the form of youtube.com/v/VIDEO_ID. So, we check to see
+ // if the given URL does follow that format.
+ if (url.path().find("/v/") != 0)
+ return GURL();
+
+ std::string url_str = url.spec();
+
+ // If the website is using an invalid YouTube URL, we will try and
+ // fix the URL by ensuring that if there are multiple parameters,
+ // the parameter string begins with a "?" and then follows with a "&"
+ // for each subsequent parameter. We do this because the Flash video player
+ // has some URL correction capabilities so we don't want this move to HTML5
+ // to break webpages that used to work.
+ size_t index = url_str.find_first_of("&?");
+ bool invalid_url = index != std::string::npos && url_str.at(index) == '&';
+
+ if (invalid_url) {
+ // ? should appear first before all parameters.
+ url_str.replace(index, 1, "?");
+
+ // Replace all instances of ? (after the first) with &.
+ for (size_t pos = index + 1;
+ (pos = url_str.find("?", pos)) != std::string::npos; pos += 1) {
+ url_str.replace(pos, 1, "&");
+ }
+ }
+
+ GURL corrected_url = GURL(url_str);
+
+ // Change the path to use the YouTube HTML5 API.
+ std::string path = corrected_url.path();
+ path.replace(path.find("/v/"), 3, "/embed/");
+
+ url::Replacements<char> r;
+ r.SetPath(path.c_str(), url::Component(0, path.length()));
+
+ return corrected_url.ReplaceComponents(r);
+}
+
+GURL FlashEmbedRewrite::RewriteDailymotionFlashEmbedURL(const GURL& url) {
+ // Dailymotion flash embeds are of the form of either:
+ // - /swf/
+ // - /swf/video/
+ if (url.path().find("/swf/") != 0)
+ return GURL();
+
+ std::string path = url.path();
+ int replace_length = path.find("/swf/video/") == 0 ? 11 : 5;
+ path.replace(0, replace_length, "/embed/video/");
+
+ url::Replacements<char> r;
+ r.SetPath(path.c_str(), url::Component(0, path.length()));
+
+ return url.ReplaceComponents(r);
+}
diff --git a/chromium/chrome/renderer/media/flash_embed_rewrite.h b/chromium/chrome/renderer/media/flash_embed_rewrite.h
new file mode 100644
index 00000000000..35b1f002469
--- /dev/null
+++ b/chromium/chrome/renderer/media/flash_embed_rewrite.h
@@ -0,0 +1,23 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_FLASH_EMBED_REWRITE_H_
+#define CHROME_RENDERER_MEDIA_FLASH_EMBED_REWRITE_H_
+
+class GURL;
+
+class FlashEmbedRewrite {
+ public:
+ // Entry point that will then call a private website-specific method.
+ static GURL RewriteFlashEmbedURL(const GURL&);
+
+ private:
+ // YouTube specific method.
+ static GURL RewriteYouTubeFlashEmbedURL(const GURL&);
+
+ // Dailymotion specific method.
+ static GURL RewriteDailymotionFlashEmbedURL(const GURL&);
+};
+
+#endif // CHROME_RENDERER_MEDIA_FLASH_EMBED_REWRITE_H_
diff --git a/chromium/chrome/renderer/media/flash_embed_rewrite_unittest.cc b/chromium/chrome/renderer/media/flash_embed_rewrite_unittest.cc
new file mode 100644
index 00000000000..48ef65a18fa
--- /dev/null
+++ b/chromium/chrome/renderer/media/flash_embed_rewrite_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/flash_embed_rewrite.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+TEST(FlashEmbedRewriteTest, YouTubeRewriteEmbed) {
+ struct TestData {
+ std::string original;
+ std::string expected;
+ } test_data[] = {
+ // { original, expected }
+ {"http://youtube.com", ""},
+ {"http://www.youtube.com", ""},
+ {"https://www.youtube.com", ""},
+ {"http://www.foo.youtube.com", ""},
+ {"https://www.foo.youtube.com", ""},
+ // Non-YouTube domains shouldn't be modified
+ {"http://www.plus.google.com", ""},
+ // URL isn't using Flash
+ {"http://www.youtube.com/embed/deadbeef", ""},
+ // URL isn't using Flash, no www
+ {"http://youtube.com/embed/deadbeef", ""},
+ // URL isn't using Flash, invalid parameter construct
+ {"http://www.youtube.com/embed/deadbeef&start=4", ""},
+ // URL is using Flash, no www
+ {"http://youtube.com/v/deadbeef", "http://youtube.com/embed/deadbeef"},
+ // URL is using Flash, is valid, https
+ {"https://www.youtube.com/v/deadbeef",
+ "https://www.youtube.com/embed/deadbeef"},
+ // URL is using Flash, is valid, http
+ {"http://www.youtube.com/v/deadbeef",
+ "http://www.youtube.com/embed/deadbeef"},
+ // URL is using Flash, valid
+ {"https://www.foo.youtube.com/v/deadbeef",
+ "https://www.foo.youtube.com/embed/deadbeef"},
+ // URL is using Flash, is valid, has one parameter
+ {"http://www.youtube.com/v/deadbeef?start=4",
+ "http://www.youtube.com/embed/deadbeef?start=4"},
+ // URL is using Flash, is valid, has multiple parameters
+ {"http://www.youtube.com/v/deadbeef?start=4&fs=1",
+ "http://www.youtube.com/embed/deadbeef?start=4&fs=1"},
+ // URL is using Flash, invalid parameter construct, has one parameter
+ {"http://www.youtube.com/v/deadbeef&start=4",
+ "http://www.youtube.com/embed/deadbeef?start=4"},
+ // URL is using Flash, invalid parameter construct, has multiple
+ // parameters
+ {"http://www.youtube.com/v/deadbeef&start=4&fs=1?foo=bar",
+ "http://www.youtube.com/embed/deadbeef?start=4&fs=1&foo=bar"},
+ // URL is using Flash, invalid parameter construct, has multiple
+ // parameters
+ {"http://www.youtube.com/v/deadbeef&start=4&fs=1",
+ "http://www.youtube.com/embed/deadbeef?start=4&fs=1"},
+ // Invalid parameter construct
+ {"http://www.youtube.com/abcd/v/deadbeef", ""},
+ // Invalid parameter construct
+ {"http://www.youtube.com/v/abcd/", "http://www.youtube.com/embed/abcd/"},
+ // Invalid parameter construct
+ {"http://www.youtube.com/v/123/", "http://www.youtube.com/embed/123/"},
+ // youtube-nocookie.com
+ {"http://www.youtube-nocookie.com/v/123/",
+ "http://www.youtube-nocookie.com/embed/123/"},
+ // youtube-nocookie.com, isn't using flash
+ {"http://www.youtube-nocookie.com/embed/123/", ""},
+ // youtube-nocookie.com, has one parameter
+ {"http://www.youtube-nocookie.com/v/123?start=foo",
+ "http://www.youtube-nocookie.com/embed/123?start=foo"},
+ // youtube-nocookie.com, has multiple parameters
+ {"http://www.youtube-nocookie.com/v/123?start=foo&bar=baz",
+ "http://www.youtube-nocookie.com/embed/123?start=foo&bar=baz"},
+ // youtube-nocookie.com, invalid parameter construct, has one parameter
+ {"http://www.youtube-nocookie.com/v/123&start=foo",
+ "http://www.youtube-nocookie.com/embed/123?start=foo"},
+ // youtube-nocookie.com, invalid parameter construct, has multiple
+ // parameters
+ {"http://www.youtube-nocookie.com/v/123&start=foo&bar=baz",
+ "http://www.youtube-nocookie.com/embed/123?start=foo&bar=baz"},
+ // youtube-nocookie.com, https
+ {"https://www.youtube-nocookie.com/v/123/",
+ "https://www.youtube-nocookie.com/embed/123/"},
+ // URL isn't using Flash, has JS API enabled
+ {"http://www.youtube.com/embed/deadbeef?enablejsapi=1", ""},
+ // URL is using Flash, has JS API enabled
+ {"http://www.youtube.com/v/deadbeef?enablejsapi=1",
+ "http://www.youtube.com/embed/deadbeef?enablejsapi=1"},
+ // youtube-nocookie.com, has JS API enabled
+ {"http://www.youtube-nocookie.com/v/123?enablejsapi=1",
+ "http://www.youtube-nocookie.com/embed/123?enablejsapi=1"},
+ // ... with multiple parameters.
+ {"http://www.youtube.com/v/deadbeef?enablejsapi=1&foo=2",
+ "http://www.youtube.com/embed/deadbeef?enablejsapi=1&foo=2"},
+ // URL is using Flash, has JS API enabled, invalid parameter construct
+ {"http://www.youtube.com/v/deadbeef&enablejsapi=1",
+ "http://www.youtube.com/embed/deadbeef?enablejsapi=1"},
+ // ... with multiple parameters.
+ {"http://www.youtube.com/v/deadbeef&enablejsapi=1&foo=2",
+ "http://www.youtube.com/embed/deadbeef?enablejsapi=1&foo=2"},
+ // URL is using Flash, has JS API enabled, invalid parameter construct,
+ // has multiple parameters
+ {"http://www.youtube.com/v/deadbeef&start=4&enablejsapi=1",
+ "http://www.youtube.com/embed/deadbeef?start=4&enablejsapi=1"},
+ };
+
+ for (const auto& data : test_data) {
+ EXPECT_EQ(GURL(data.expected),
+ FlashEmbedRewrite::RewriteFlashEmbedURL(GURL(data.original)));
+ }
+}
+
+TEST(FlashEmbedRewriteTest, DailymotionRewriteEmbed) {
+ struct TestData {
+ std::string original;
+ std::string expected;
+ } test_data[] = {
+ // { original, expected }
+ {"http://dailymotion.com", ""},
+ {"http://www.dailymotion.com", ""},
+ {"https://www.dailymotion.com", ""},
+ {"http://www.foo.dailymotion.com", ""},
+ {"https://www.foo.dailymotion.com", ""},
+ // URL isn't using Flash
+ {"http://www.dailymotion.com/embed/video/deadbeef", ""},
+ // URL isn't using Flash, no www
+ {"http://dailymotion.com/embed/video/deadbeef", ""},
+ // URL isn't using Flash, invalid parameter construct
+ {"http://www.dailymotion.com/embed/video/deadbeef&start=4", ""},
+ // URL is using Flash, no www
+ {"http://dailymotion.com/swf/deadbeef",
+ "http://dailymotion.com/embed/video/deadbeef"},
+ // URL is using Flash, is valid, https
+ {"https://www.dailymotion.com/swf/deadbeef",
+ "https://www.dailymotion.com/embed/video/deadbeef"},
+ // URL is using Flash, is valid, http
+ {"http://www.dailymotion.com/swf/deadbeef",
+ "http://www.dailymotion.com/embed/video/deadbeef"},
+ // URL is using Flash, valid
+ {"https://www.foo.dailymotion.com/swf/deadbeef",
+ "https://www.foo.dailymotion.com/embed/video/deadbeef"},
+ // URL is using Flash, is valid, has one parameter
+ {"http://www.dailymotion.com/swf/deadbeef?start=4",
+ "http://www.dailymotion.com/embed/video/deadbeef?start=4"},
+ // URL is using Flash, is valid, has multiple parameters
+ {"http://www.dailymotion.com/swf/deadbeef?start=4&fs=1",
+ "http://www.dailymotion.com/embed/video/deadbeef?start=4&fs=1"},
+ // URL is using Flash, invalid parameter construct, has one parameter
+ {"http://www.dailymotion.com/swf/deadbeef&start=4",
+ "http://www.dailymotion.com/embed/video/deadbeef&start=4"},
+ // Invalid URL.
+ {"http://www.dailymotion.com/abcd/swf/deadbeef", ""},
+ // Uses /swf/video/
+ {"http://www.dailymotion.com/swf/video/deadbeef",
+ "http://www.dailymotion.com/embed/video/deadbeef"}};
+
+ for (const auto& data : test_data) {
+ EXPECT_EQ(GURL(data.expected),
+ FlashEmbedRewrite::RewriteFlashEmbedURL(GURL(data.original)));
+ }
+}
diff --git a/chromium/chrome/renderer/media/webrtc_logging_agent_impl.cc b/chromium/chrome/renderer/media/webrtc_logging_agent_impl.cc
new file mode 100644
index 00000000000..b8b24d4ebb9
--- /dev/null
+++ b/chromium/chrome/renderer/media/webrtc_logging_agent_impl.cc
@@ -0,0 +1,134 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/media/webrtc_logging_agent_impl.h"
+
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
+
+namespace chrome {
+namespace {
+
+constexpr base::TimeDelta kMinTimeSinceLastLogBufferSend =
+ base::TimeDelta::FromMilliseconds(100);
+constexpr base::TimeDelta kSendLogBufferDelay =
+ base::TimeDelta::FromMilliseconds(200);
+
+// There can be only one registered WebRtcLogMessageDelegate, and so this class
+// abstracts away that detail, so that we can set callbacks more than once. It
+// also abstracts away the detail of what thread the LogMessage call runs on.
+class WebRtcLogMessageDelegateImpl : public blink::WebRtcLogMessageDelegate {
+ public:
+ static WebRtcLogMessageDelegateImpl* GetInstance() {
+ static base::NoDestructor<WebRtcLogMessageDelegateImpl> instance;
+ return instance.get();
+ }
+
+ void Start(
+ base::RepeatingCallback<void(mojom::WebRtcLoggingMessagePtr)> callback) {
+ auto task_runner = base::SequencedTaskRunnerHandle::Get();
+ {
+ base::AutoLock locked(lock_);
+ task_runner_ = task_runner;
+ callback_ = std::move(callback);
+ }
+ blink::InitWebRtcLogging();
+ }
+
+ void Stop() {
+ {
+ base::AutoLock locked(lock_);
+ task_runner_ = nullptr;
+ callback_.Reset();
+ }
+ }
+
+ // blink::WebRtcLogMessageDelegate methods:
+ void LogMessage(const std::string& message) override {
+ // Called from a random thread.
+ auto data = mojom::WebRtcLoggingMessage::New(base::Time::Now(), message);
+ {
+ base::AutoLock locked(lock_);
+ if (callback_)
+ task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(callback_, std::move(data)));
+ }
+ }
+
+ WebRtcLogMessageDelegateImpl() { blink::InitWebRtcLoggingDelegate(this); }
+
+ private:
+ ~WebRtcLogMessageDelegateImpl() override = default;
+
+ base::Lock lock_;
+ base::RepeatingCallback<void(mojom::WebRtcLoggingMessagePtr)> callback_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+};
+
+} // namespace
+
+WebRtcLoggingAgentImpl::WebRtcLoggingAgentImpl() = default;
+WebRtcLoggingAgentImpl::~WebRtcLoggingAgentImpl() = default;
+
+void WebRtcLoggingAgentImpl::AddReceiver(
+ mojo::PendingReceiver<mojom::WebRtcLoggingAgent> receiver) {
+ self_receiver_set_.Add(this, std::move(receiver));
+}
+
+void WebRtcLoggingAgentImpl::Start(
+ mojo::PendingRemote<mojom::WebRtcLoggingClient> pending_client) {
+ // We only support one client at a time. OK to drop any existing client.
+ client_.Bind(std::move(pending_client));
+
+ WebRtcLogMessageDelegateImpl::GetInstance()->Start(base::BindRepeating(
+ &WebRtcLoggingAgentImpl::OnNewMessage, weak_factory_.GetWeakPtr()));
+}
+
+void WebRtcLoggingAgentImpl::Stop() {
+ if (!log_buffer_.empty())
+ SendLogBuffer();
+ WebRtcLogMessageDelegateImpl::GetInstance()->Stop();
+ if (client_) {
+ client_->OnStopped();
+ client_.reset();
+ }
+}
+
+void WebRtcLoggingAgentImpl::OnNewMessage(
+ mojom::WebRtcLoggingMessagePtr message) {
+ // We may have already been asked to stop.
+ if (!client_)
+ return;
+
+ log_buffer_.emplace_back(std::move(message));
+ if (log_buffer_.size() > 1) {
+ // A delayed task has already been posted for sending the buffer contents.
+ return;
+ }
+
+ if ((base::TimeTicks::Now() - last_log_buffer_send_) >
+ kMinTimeSinceLastLogBufferSend) {
+ SendLogBuffer();
+ } else {
+ base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLoggingAgentImpl::SendLogBuffer,
+ weak_factory_.GetWeakPtr()),
+ kSendLogBufferDelay);
+ }
+}
+
+void WebRtcLoggingAgentImpl::SendLogBuffer() {
+ last_log_buffer_send_ = base::TimeTicks::Now();
+ if (client_) {
+ client_->OnAddMessages(std::move(log_buffer_));
+ } else {
+ log_buffer_.clear();
+ }
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/renderer/media/webrtc_logging_agent_impl.h b/chromium/chrome/renderer/media/webrtc_logging_agent_impl.h
new file mode 100644
index 00000000000..d5698c20f30
--- /dev/null
+++ b/chromium/chrome/renderer/media/webrtc_logging_agent_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MEDIA_WEBRTC_LOGGING_AGENT_IMPL_H_
+#define CHROME_RENDERER_MEDIA_WEBRTC_LOGGING_AGENT_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/common/media/webrtc_logging.mojom.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+
+namespace chrome {
+
+class WebRtcLoggingAgentImpl : public mojom::WebRtcLoggingAgent {
+ public:
+ WebRtcLoggingAgentImpl();
+ ~WebRtcLoggingAgentImpl() override;
+
+ void AddReceiver(mojo::PendingReceiver<mojom::WebRtcLoggingAgent> receiver);
+
+ // mojom::WebRtcLoggingAgent methods:
+ void Start(
+ mojo::PendingRemote<mojom::WebRtcLoggingClient> pending_client) override;
+ void Stop() override;
+
+ private:
+ void OnNewMessage(mojom::WebRtcLoggingMessagePtr message);
+ void SendLogBuffer();
+
+ mojo::ReceiverSet<mojom::WebRtcLoggingAgent> self_receiver_set_;
+ mojo::Remote<mojom::WebRtcLoggingClient> client_;
+ std::vector<mojom::WebRtcLoggingMessagePtr> log_buffer_;
+ base::TimeTicks last_log_buffer_send_;
+
+ base::WeakPtrFactory<WebRtcLoggingAgentImpl> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLoggingAgentImpl);
+};
+
+} // namespace chrome
+
+#endif // CHROME_RENDERER_MEDIA_WEBRTC_LOGGING_AGENT_IMPL_H_
diff --git a/chromium/chrome/renderer/media/webrtc_logging_agent_impl_unittest.cc b/chromium/chrome/renderer/media/webrtc_logging_agent_impl_unittest.cc
new file mode 100644
index 00000000000..72f8b2410aa
--- /dev/null
+++ b/chromium/chrome/renderer/media/webrtc_logging_agent_impl_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "chrome/renderer/media/webrtc_logging_agent_impl.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
+
+namespace chrome {
+namespace {
+
+class WebRtcLoggingClientRecorder : public mojom::WebRtcLoggingClient {
+ public:
+ struct Log {
+ std::string buffer;
+ int on_stopped_count = 0;
+ };
+
+ explicit WebRtcLoggingClientRecorder(Log* log) : log_(log) {}
+ ~WebRtcLoggingClientRecorder() override = default;
+
+ // mojom::WebRtcLoggingClient methods:
+ void OnAddMessages(
+ std::vector<mojom::WebRtcLoggingMessagePtr> messages) override {
+ for (auto& message : messages) {
+ log_->buffer.append(message->data);
+ log_->buffer.append("\n");
+ }
+ }
+ void OnStopped() override { log_->on_stopped_count++; }
+
+ private:
+ Log* const log_;
+};
+
+} // namespace
+
+TEST(WebRtcLoggingAgentImplTest, Basic) {
+ constexpr char kTestString[] = "abcdefghijklmnopqrstuvwxyz";
+
+ base::test::TaskEnvironment task_environment;
+
+ mojo::UniqueReceiverSet<mojom::WebRtcLoggingClient> client_set;
+
+ WebRtcLoggingAgentImpl agent;
+ WebRtcLoggingClientRecorder::Log log;
+
+ // Start agent.
+ {
+ mojo::PendingRemote<mojom::WebRtcLoggingClient> client;
+ client_set.Add(std::make_unique<WebRtcLoggingClientRecorder>(&log),
+ client.InitWithNewPipeAndPassReceiver());
+ agent.Start(std::move(client));
+ }
+
+ base::RunLoop().RunUntilIdle();
+
+ // These log messages should be added to the log buffer.
+ blink::WebRtcLogMessage(kTestString);
+ blink::WebRtcLogMessage(kTestString);
+
+ base::RunLoop().RunUntilIdle();
+
+ // Stop logging messages.
+ agent.Stop();
+
+ base::RunLoop().RunUntilIdle();
+
+ // This log message should not be added to the log buffer.
+ blink::WebRtcLogMessage(kTestString);
+
+ base::RunLoop().RunUntilIdle();
+
+ // Size is calculated as (sizeof(kTestString) - 1 for terminating null
+ // + 1 for eol added for each log message in LogMessage) * 2.
+ constexpr uint32_t kExpectedSize = sizeof(kTestString) * 2;
+ EXPECT_EQ(kExpectedSize, log.buffer.size());
+
+ std::string ref_output = kTestString;
+ ref_output.append("\n");
+ ref_output.append(kTestString);
+ ref_output.append("\n");
+ EXPECT_STREQ(ref_output.c_str(), log.buffer.c_str());
+
+ EXPECT_EQ(1, log.on_stopped_count);
+}
+
+} // namespace chrome
diff --git a/chromium/chrome/renderer/net/DEPS b/chromium/chrome/renderer/net/DEPS
new file mode 100644
index 00000000000..61b7ce098a6
--- /dev/null
+++ b/chromium/chrome/renderer/net/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+components/error_page/renderer",
+ "+components/offline_pages/core",
+ "+components/security_interstitials/content/renderer",
+ "+components/security_interstitials/core",
+ "+components/security_interstitials/core/common/mojom",
+ "+gin",
+]
diff --git a/chromium/chrome/renderer/net/OWNERS b/chromium/chrome/renderer/net/OWNERS
new file mode 100644
index 00000000000..9deb60fd506
--- /dev/null
+++ b/chromium/chrome/renderer/net/OWNERS
@@ -0,0 +1,11 @@
+# Preferred net owner.
+mmenke@chromium.org
+
+# Offline owners.
+chili@chromium.org
+jianli@chromium.org
+
+file://net/OWNERS
+
+# COMPONENT: Internals>Network
+# TEAM: net-dev@chromium.org
diff --git a/chromium/chrome/renderer/net/available_offline_content_helper.cc b/chromium/chrome/renderer/net/available_offline_content_helper.cc
new file mode 100644
index 00000000000..a4c7a103533
--- /dev/null
+++ b/chromium/chrome/renderer/net/available_offline_content_helper.cc
@@ -0,0 +1,196 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net/available_offline_content_helper.h"
+
+#include <utility>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_value_converter.h"
+#include "base/json/json_writer.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/available_offline_content.mojom.h"
+#include "components/error_page/common/net_error_info.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/renderer/render_thread.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+#include "third_party/blink/public/platform/platform.h"
+
+namespace {
+
+using chrome::mojom::AvailableOfflineContentPtr;
+using chrome::mojom::AvailableContentType;
+
+// Converts a string to base-64 data. This is done for security purposes, to
+// avoid potential XSS. Note that when this value is decoded in javascript, we
+// want to use the atob() function, but that function only handles latin-1
+// characters. Additionally, javascript needs UTF16 strings. So we instead
+// encode to UTF16, and then store that data as base64.
+std::string ConvertToUTF16Base64(const std::string& text) {
+ base::string16 text_utf16 = base::UTF8ToUTF16(text);
+ std::string utf16_bytes;
+ for (base::char16 c : text_utf16) {
+ utf16_bytes.push_back(static_cast<char>(c >> 8));
+ utf16_bytes.push_back(static_cast<char>(c & 0xff));
+ }
+ std::string encoded;
+ base::Base64Encode(utf16_bytes, &encoded);
+ return encoded;
+}
+
+base::Value AvailableContentToValue(const AvailableOfflineContentPtr& content) {
+ // All pieces of text content downloaded from the web will be base64 encoded
+ // to lessen security risks when this dictionary is passed as a string to
+ // |ExecuteJavaScript|.
+ std::string base64_encoded;
+ base::Value value(base::Value::Type::DICTIONARY);
+ value.SetKey("ID", base::Value(content->id));
+ value.SetKey("name_space", base::Value(content->name_space));
+ value.SetKey("title_base64",
+ base::Value(ConvertToUTF16Base64(content->title)));
+ value.SetKey("snippet_base64",
+ base::Value(ConvertToUTF16Base64(content->snippet)));
+ value.SetKey("date_modified", base::Value(content->date_modified));
+ value.SetKey("attribution_base64",
+ base::Value(ConvertToUTF16Base64(content->attribution)));
+ value.SetKey("thumbnail_data_uri",
+ base::Value(content->thumbnail_data_uri.spec()));
+ value.SetKey("favicon_data_uri",
+ base::Value(content->favicon_data_uri.spec()));
+ value.SetKey("content_type",
+ base::Value(static_cast<int>(content->content_type)));
+ return value;
+}
+
+base::Value AvailableContentListToValue(
+ const std::vector<AvailableOfflineContentPtr>& content_list) {
+ base::Value value(base::Value::Type::LIST);
+ for (const auto& content : content_list) {
+ value.Append(AvailableContentToValue(content));
+ }
+ return value;
+}
+
+void RecordSuggestionPresented(
+ const std::vector<AvailableOfflineContentPtr>& suggestions) {
+ for (const AvailableOfflineContentPtr& item : suggestions) {
+ UMA_HISTOGRAM_ENUMERATION("Net.ErrorPageCounts.SuggestionPresented",
+ item->content_type);
+ }
+}
+
+AvailableOfflineContentHelper::Binder& GetBinderOverride() {
+ static base::NoDestructor<AvailableOfflineContentHelper::Binder> binder;
+ return *binder;
+}
+
+} // namespace
+
+AvailableOfflineContentHelper::AvailableOfflineContentHelper() = default;
+AvailableOfflineContentHelper::~AvailableOfflineContentHelper() = default;
+
+void AvailableOfflineContentHelper::Reset() {
+ provider_.reset();
+}
+
+void AvailableOfflineContentHelper::FetchAvailableContent(
+ AvailableContentCallback callback) {
+ if (!BindProvider()) {
+ std::move(callback).Run(true, {});
+ return;
+ }
+ provider_->List(
+ base::BindOnce(&AvailableOfflineContentHelper::AvailableContentReceived,
+ base::Unretained(this), std::move(callback)));
+}
+
+bool AvailableOfflineContentHelper::BindProvider() {
+ if (provider_)
+ return true;
+
+ auto receiver = provider_.BindNewPipeAndPassReceiver();
+ const auto& binder_override = GetBinderOverride();
+ if (binder_override) {
+ binder_override.Run(std::move(receiver));
+ return true;
+ }
+
+ blink::Platform::Current()->GetBrowserInterfaceBrokerProxy()->GetInterface(
+ std::move(receiver));
+ return true;
+}
+
+// static
+void AvailableOfflineContentHelper::OverrideBinderForTesting(Binder binder) {
+ GetBinderOverride() = std::move(binder);
+}
+
+void AvailableOfflineContentHelper::LaunchItem(const std::string& id,
+ const std::string& name_space) {
+ if (!BindProvider())
+ return;
+
+ for (const AvailableOfflineContentPtr& item : fetched_content_) {
+ if (item->id == id && item->name_space == name_space) {
+ UMA_HISTOGRAM_ENUMERATION("Net.ErrorPageCounts.SuggestionClicked",
+ item->content_type);
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTION_CLICKED);
+ provider_->LaunchItem(id, name_space);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+void AvailableOfflineContentHelper::LaunchDownloadsPage() {
+ if (!BindProvider())
+ return;
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_DOWNLOADS_PAGE_CLICKED);
+ provider_->LaunchDownloadsPage(has_prefetched_content_);
+}
+
+void AvailableOfflineContentHelper::ListVisibilityChanged(bool is_visible) {
+ if (!BindProvider())
+ return;
+ provider_->ListVisibilityChanged(is_visible);
+}
+
+void AvailableOfflineContentHelper::AvailableContentReceived(
+ AvailableContentCallback callback,
+ bool list_visible_by_prefs,
+ std::vector<AvailableOfflineContentPtr> content) {
+ has_prefetched_content_ = false;
+ fetched_content_ = std::move(content);
+ std::string json;
+
+ if (!fetched_content_.empty()) {
+ // As prefetched content has the highest priority if at least one piece is
+ // available it will be the at the first position on the list.
+ has_prefetched_content_ = fetched_content_.front()->content_type ==
+ AvailableContentType::kPrefetchedPage;
+
+ RecordSuggestionPresented(fetched_content_);
+ if (list_visible_by_prefs)
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN);
+ else
+ RecordEvent(
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED);
+ base::JSONWriter::Write(AvailableContentListToValue(fetched_content_),
+ &json);
+ }
+ std::move(callback).Run(list_visible_by_prefs, json);
+ // We don't need to retain the visuals here, so free up some memory.
+ for (const AvailableOfflineContentPtr& item : fetched_content_) {
+ item->thumbnail_data_uri = GURL();
+ item->favicon_data_uri = GURL();
+ }
+}
+
diff --git a/chromium/chrome/renderer/net/available_offline_content_helper.h b/chromium/chrome/renderer/net/available_offline_content_helper.h
new file mode 100644
index 00000000000..e07c0fb38d6
--- /dev/null
+++ b/chromium/chrome/renderer/net/available_offline_content_helper.h
@@ -0,0 +1,70 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_NET_AVAILABLE_OFFLINE_CONTENT_HELPER_H_
+#define CHROME_RENDERER_NET_AVAILABLE_OFFLINE_CONTENT_HELPER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "chrome/common/available_offline_content.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+// Notice: this file is only included on OS_ANDROID.
+
+// Wraps calls from the renderer thread to the AvailableOfflineContentProvider,
+// and records related UMA.
+class AvailableOfflineContentHelper {
+ public:
+ using AvailableContentCallback =
+ base::OnceCallback<void(bool list_visible_by_prefs,
+ const std::string& offline_content_json)>;
+
+ AvailableOfflineContentHelper();
+ ~AvailableOfflineContentHelper();
+
+ // Fetch available offline content and return a JSON representation.
+ // Calls callback once with the return value. An empty string
+ // is returned if no offline content is available.
+ // Note: A call to Reset, or deletion of this object will prevent the callback
+ // from running.
+ void FetchAvailableContent(AvailableContentCallback callback);
+
+ // These methods just forward to the AvailableOfflineContentProvider.
+ void LaunchItem(const std::string& id, const std::string& name_space);
+ void LaunchDownloadsPage();
+ void ListVisibilityChanged(bool is_visible);
+
+ // Abort previous requests and free the mojo connection.
+ void Reset();
+
+ using Binder = base::RepeatingCallback<void(
+ mojo::PendingReceiver<chrome::mojom::AvailableOfflineContentProvider>)>;
+ static void OverrideBinderForTesting(Binder binder);
+
+ private:
+ void AvailableContentReceived(
+ AvailableContentCallback callback,
+ bool list_visible_by_prefs,
+ std::vector<chrome::mojom::AvailableOfflineContentPtr> content);
+
+ // Binds |provider_| if necessary. Returns true if the provider is bound.
+ bool BindProvider();
+
+ mojo::Remote<chrome::mojom::AvailableOfflineContentProvider> provider_;
+ // This is the result of the last FetchAvailableContent call. It is retained
+ // only so that metrics can be recorded properly on call to LaunchItem().
+ std::vector<chrome::mojom::AvailableOfflineContentPtr> fetched_content_;
+
+ // Records if the last received content message indicated that prefetched
+ // articles are available or not.
+ bool has_prefetched_content_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(AvailableOfflineContentHelper);
+};
+
+#endif // CHROME_RENDERER_NET_AVAILABLE_OFFLINE_CONTENT_HELPER_H_
diff --git a/chromium/chrome/renderer/net/net_error_helper.cc b/chromium/chrome/renderer/net/net_error_helper.cc
new file mode 100644
index 00000000000..8d7b09590a3
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_helper.cc
@@ -0,0 +1,621 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net/net_error_helper.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/i18n/rtl.h"
+#include "base/json/json_writer.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "components/error_page/common/error.h"
+#include "components/error_page/common/error_page_params.h"
+#include "components/error_page/common/localized_error.h"
+#include "components/error_page/common/net_error_info.h"
+#include "components/grit/components_resources.h"
+#include "components/offline_pages/core/offline_page_feature.h"
+#include "components/security_interstitials/content/renderer/security_interstitial_page_controller.h"
+#include "components/security_interstitials/core/common/mojom/interstitial_commands.mojom.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/content_renderer_client.h"
+#include "content/public/renderer/document_state.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+#include "net/base/net_errors.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
+#include "third_party/blink/public/platform/web_data.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_response.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_document_loader.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_history_item.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/webui/jstemplate_builder.h"
+#include "url/gurl.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/common/offline_page_auto_fetcher.mojom.h"
+#endif
+
+using base::JSONWriter;
+using content::DocumentState;
+using content::RenderFrame;
+using content::RenderFrameObserver;
+using content::RenderThread;
+using content::kUnreachableWebDataURL;
+using error_page::DnsProbeStatus;
+using error_page::DnsProbeStatusToString;
+using error_page::ErrorPageParams;
+using error_page::LocalizedError;
+
+namespace {
+
+// Number of seconds to wait for the navigation correction service to return
+// suggestions. If it takes too long, just use the local error page.
+const int kNavigationCorrectionFetchTimeoutSec = 3;
+
+NetErrorHelperCore::PageType GetLoadingPageType(const GURL& url) {
+ if (!url.is_valid() || url.spec() != kUnreachableWebDataURL)
+ return NetErrorHelperCore::NON_ERROR_PAGE;
+ return NetErrorHelperCore::ERROR_PAGE;
+}
+
+NetErrorHelperCore::FrameType GetFrameType(RenderFrame* render_frame) {
+ if (render_frame->IsMainFrame())
+ return NetErrorHelperCore::MAIN_FRAME;
+ return NetErrorHelperCore::SUB_FRAME;
+}
+
+#if defined(OS_ANDROID)
+bool IsOfflineContentOnNetErrorFeatureEnabled() {
+ return true;
+}
+#else // OS_ANDROID
+bool IsOfflineContentOnNetErrorFeatureEnabled() {
+ return false;
+}
+#endif // OS_ANDROID
+
+#if defined(OS_ANDROID)
+bool IsAutoFetchFeatureEnabled() {
+ return base::FeatureList::IsEnabled(features::kAutoFetchOnNetErrorPage);
+}
+#else // OS_ANDROID
+bool IsAutoFetchFeatureEnabled() {
+ return false;
+}
+#endif // OS_ANDROID
+
+const net::NetworkTrafficAnnotationTag& GetNetworkTrafficAnnotationTag() {
+ static const net::NetworkTrafficAnnotationTag network_traffic_annotation_tag =
+ net::DefineNetworkTrafficAnnotation("net_error_helper", R"(
+ semantics {
+ sender: "NetErrorHelper"
+ description:
+ "Chrome asks Link Doctor service when a navigating page returns an "
+ "error to investigate details about what is wrong."
+ trigger:
+ "When Chrome navigates to a page, and the page returns an error."
+ data:
+ "Failed page information including the URL will be sent to the service."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "You can enable or disable this feature via 'Use a web service to help "
+ "resolve navigation errors' in Chrome's settings under Advanced. The "
+ "feature is enabled by default."
+ chrome_policy {
+ AlternateErrorPagesEnabled {
+ policy_options {mode: MANDATORY}
+ AlternateErrorPagesEnabled: false
+ }
+ }
+ })");
+ return network_traffic_annotation_tag;
+}
+
+} // namespace
+
+NetErrorHelper::NetErrorHelper(RenderFrame* render_frame)
+ : RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<NetErrorHelper>(render_frame) {
+ RenderThread::Get()->AddObserver(this);
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ bool auto_reload_enabled =
+ command_line->HasSwitch(switches::kEnableAutoReload);
+ // TODO(mmenke): Consider only creating a NetErrorHelperCore for main frames.
+ // subframes don't need any of the NetErrorHelperCore's extra logic.
+ core_.reset(new NetErrorHelperCore(this,
+ auto_reload_enabled,
+ !render_frame->IsHidden()));
+
+ render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
+ base::Bind(&NetErrorHelper::OnNetworkDiagnosticsClientRequest,
+ base::Unretained(this)));
+ render_frame->GetAssociatedInterfaceRegistry()->AddInterface(base::Bind(
+ &NetErrorHelper::OnNavigationCorrectorRequest, base::Unretained(this)));
+}
+
+NetErrorHelper::~NetErrorHelper() {
+ RenderThread::Get()->RemoveObserver(this);
+}
+
+void NetErrorHelper::ButtonPressed(NetErrorHelperCore::Button button) {
+ core_->ExecuteButtonPress(button);
+}
+
+void NetErrorHelper::TrackClick(int tracking_id) {
+ core_->TrackClick(tracking_id);
+}
+
+void NetErrorHelper::LaunchOfflineItem(const std::string& id,
+ const std::string& name_space) {
+ core_->LaunchOfflineItem(id, name_space);
+}
+
+void NetErrorHelper::LaunchDownloadsPage() {
+ core_->LaunchDownloadsPage();
+}
+
+void NetErrorHelper::SavePageForLater() {
+ core_->SavePageForLater();
+}
+
+void NetErrorHelper::CancelSavePage() {
+ core_->CancelSavePage();
+}
+
+void NetErrorHelper::ListVisibilityChanged(bool is_visible) {
+ core_->ListVisibilityChanged(is_visible);
+}
+
+content::RenderFrame* NetErrorHelper::GetRenderFrame() {
+ return render_frame();
+}
+
+void NetErrorHelper::SendCommand(
+ security_interstitials::SecurityInterstitialCommand command) {
+ mojo::AssociatedRemote<security_interstitials::mojom::InterstitialCommands>
+ interface;
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&interface);
+ switch (command) {
+ case security_interstitials::CMD_DONT_PROCEED: {
+ interface->DontProceed();
+ break;
+ }
+ case security_interstitials::CMD_PROCEED: {
+ interface->Proceed();
+ break;
+ }
+ case security_interstitials::CMD_SHOW_MORE_SECTION: {
+ interface->ShowMoreSection();
+ break;
+ }
+ case security_interstitials::CMD_OPEN_HELP_CENTER: {
+ interface->OpenHelpCenter();
+ break;
+ }
+ case security_interstitials::CMD_OPEN_DIAGNOSTIC: {
+ interface->OpenDiagnostic();
+ break;
+ }
+ case security_interstitials::CMD_RELOAD: {
+ interface->Reload();
+ break;
+ }
+ case security_interstitials::CMD_OPEN_DATE_SETTINGS: {
+ interface->OpenDateSettings();
+ break;
+ }
+ case security_interstitials::CMD_OPEN_LOGIN: {
+ interface->OpenLogin();
+ break;
+ }
+ case security_interstitials::CMD_DO_REPORT: {
+ interface->DoReport();
+ break;
+ }
+ case security_interstitials::CMD_DONT_REPORT: {
+ interface->DontReport();
+ break;
+ }
+ case security_interstitials::CMD_OPEN_REPORTING_PRIVACY: {
+ interface->OpenReportingPrivacy();
+ break;
+ }
+ case security_interstitials::CMD_OPEN_WHITEPAPER: {
+ interface->OpenWhitepaper();
+ break;
+ }
+ case security_interstitials::CMD_REPORT_PHISHING_ERROR: {
+ interface->ReportPhishingError();
+ break;
+ }
+ default: {
+ // Other values in the enum are only used by tests so this
+ // method should not be called with them.
+ NOTREACHED();
+ }
+ }
+}
+
+void NetErrorHelper::DidStartNavigation(
+ const GURL& url,
+ base::Optional<blink::WebNavigationType> navigation_type) {
+ core_->OnStartLoad(GetFrameType(render_frame()), GetLoadingPageType(url));
+}
+
+void NetErrorHelper::DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) {
+ // If this is a "same-document" navigation, it's not a real navigation. There
+ // wasn't a start event for it, either, so just ignore it.
+ if (is_same_document_navigation)
+ return;
+
+ // Invalidate weak pointers from old error page controllers. If loading a new
+ // error page, the controller has not yet been attached, so this won't affect
+ // it.
+ weak_controller_delegate_factory_.InvalidateWeakPtrs();
+ weak_security_interstitial_controller_delegate_factory_.InvalidateWeakPtrs();
+
+ core_->OnCommitLoad(GetFrameType(render_frame()),
+ render_frame()->GetWebFrame()->GetDocument().Url());
+}
+
+void NetErrorHelper::DidFinishLoad() {
+ core_->OnFinishLoad(GetFrameType(render_frame()));
+}
+
+void NetErrorHelper::OnStop() {
+ core_->OnStop();
+}
+
+void NetErrorHelper::WasShown() {
+ core_->OnWasShown();
+}
+
+void NetErrorHelper::WasHidden() {
+ core_->OnWasHidden();
+}
+
+void NetErrorHelper::OnDestruct() {
+ delete this;
+}
+
+void NetErrorHelper::NetworkStateChanged(bool enabled) {
+ core_->NetworkStateChanged(enabled);
+}
+
+void NetErrorHelper::PrepareErrorPage(const error_page::Error& error,
+ bool is_failed_post,
+ std::string* error_html) {
+ core_->PrepareErrorPage(GetFrameType(render_frame()), error, is_failed_post,
+ error_html);
+}
+
+bool NetErrorHelper::ShouldSuppressErrorPage(const GURL& url) {
+ return core_->ShouldSuppressErrorPage(GetFrameType(render_frame()), url);
+}
+
+std::unique_ptr<network::ResourceRequest> NetErrorHelper::CreatePostRequest(
+ const GURL& url) const {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+ resource_request->method = "POST";
+ resource_request->fetch_request_context_type =
+ static_cast<int>(blink::mojom::RequestContextType::INTERNAL);
+ resource_request->resource_type =
+ static_cast<int>(content::ResourceType::kSubResource);
+
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ resource_request->site_for_cookies = frame->GetDocument().SiteForCookies();
+ // The security origin of the error page should exist and be opaque.
+ DCHECK(!frame->GetDocument().GetSecurityOrigin().IsNull());
+ DCHECK(frame->GetDocument().GetSecurityOrigin().IsOpaque());
+ // All requests coming from a renderer process have to use |request_initiator|
+ // that matches the |request_initiator_site_lock| set by the browser when
+ // creating URLLoaderFactory exposed to the renderer.
+ blink::WebSecurityOrigin origin = frame->GetDocument().GetSecurityOrigin();
+ resource_request->request_initiator = static_cast<url::Origin>(origin);
+ // Since the page is trying to fetch cross-origin resources (which would
+ // be protected by CORB in no-cors mode), we need to ask for CORS. See also
+ // https://crbug.com/932542.
+ resource_request->mode = network::mojom::RequestMode::kCors;
+ resource_request->headers.SetHeader(net::HttpRequestHeaders::kOrigin,
+ origin.ToString().Ascii());
+ return resource_request;
+}
+
+chrome::mojom::NetworkDiagnostics*
+NetErrorHelper::GetRemoteNetworkDiagnostics() {
+ if (!remote_network_diagnostics_) {
+ render_frame()->GetRemoteAssociatedInterfaces()
+ ->GetInterface(&remote_network_diagnostics_);
+ }
+ return remote_network_diagnostics_.get();
+}
+
+chrome::mojom::NetworkEasterEgg* NetErrorHelper::GetRemoteNetworkEasterEgg() {
+ if (!remote_network_easter_egg_) {
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
+ &remote_network_easter_egg_);
+ }
+ return remote_network_easter_egg_.get();
+}
+
+LocalizedError::PageState NetErrorHelper::GenerateLocalizedErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_show_network_diagnostics_dialog,
+ std::unique_ptr<ErrorPageParams> params,
+ std::string* error_html) const {
+ error_html->clear();
+
+ int resource_id = IDR_NET_ERROR_HTML;
+ std::string extracted_string =
+ ui::ResourceBundle::GetSharedInstance().DecompressDataResource(
+ resource_id);
+ base::StringPiece template_html(extracted_string.data(),
+ extracted_string.size());
+
+ LocalizedError::PageState page_state = LocalizedError::GetPageState(
+ error.reason(), error.domain(), error.url(), is_failed_post,
+ error.stale_copy_in_cache(), can_show_network_diagnostics_dialog,
+ ChromeRenderThreadObserver::is_incognito_process(),
+ IsOfflineContentOnNetErrorFeatureEnabled(), IsAutoFetchFeatureEnabled(),
+ RenderThread::Get()->GetLocale(), std::move(params));
+ DCHECK(!template_html.empty()) << "unable to load template.";
+ // "t" is the id of the template's root node.
+ *error_html =
+ webui::GetTemplatesHtml(template_html, &page_state.strings, "t");
+ return page_state;
+}
+
+void NetErrorHelper::LoadErrorPage(const std::string& html,
+ const GURL& failed_url) {
+ render_frame()->LoadHTMLString(html, GURL(kUnreachableWebDataURL), "UTF-8",
+ failed_url, true /* replace_current_item */);
+}
+
+void NetErrorHelper::EnablePageHelperFunctions() {
+ security_interstitials::SecurityInterstitialPageController::Install(
+ render_frame(),
+ weak_security_interstitial_controller_delegate_factory_.GetWeakPtr());
+ NetErrorPageController::Install(
+ render_frame(), weak_controller_delegate_factory_.GetWeakPtr());
+}
+
+LocalizedError::PageState NetErrorHelper::UpdateErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_show_network_diagnostics_dialog) {
+ LocalizedError::PageState page_state = LocalizedError::GetPageState(
+ error.reason(), error.domain(), error.url(), is_failed_post,
+ error.stale_copy_in_cache(), can_show_network_diagnostics_dialog,
+ ChromeRenderThreadObserver::is_incognito_process(),
+ IsOfflineContentOnNetErrorFeatureEnabled(), IsAutoFetchFeatureEnabled(),
+ RenderThread::Get()->GetLocale(), std::unique_ptr<ErrorPageParams>());
+
+ std::string json;
+ JSONWriter::Write(page_state.strings, &json);
+
+ std::string js = "if (window.updateForDnsProbe) "
+ "updateForDnsProbe(" + json + ");";
+ base::string16 js16;
+ if (base::UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
+ render_frame()->ExecuteJavaScript(js16);
+ } else {
+ NOTREACHED();
+ }
+ return page_state;
+}
+
+void NetErrorHelper::InitializeErrorPageEasterEggHighScore(int high_score) {
+ std::string js = base::StringPrintf(
+ "if (window.initializeEasterEggHighScore) "
+ "initializeEasterEggHighScore(%i);",
+ high_score);
+ base::string16 js16;
+ if (!base::UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
+ NOTREACHED();
+ return;
+ }
+
+ render_frame()->ExecuteJavaScript(js16);
+}
+
+void NetErrorHelper::RequestEasterEggHighScore() {
+ GetRemoteNetworkEasterEgg()->GetHighScore(base::BindOnce(
+ [](NetErrorHelper* helper, uint32_t high_score) {
+ helper->core_->OnEasterEggHighScoreReceived(high_score);
+ },
+ base::Unretained(this)));
+}
+
+void NetErrorHelper::UpdateEasterEggHighScore(int high_score) {
+ GetRemoteNetworkEasterEgg()->UpdateHighScore(high_score);
+}
+
+void NetErrorHelper::ResetEasterEggHighScore() {
+ GetRemoteNetworkEasterEgg()->ResetHighScore();
+}
+
+void NetErrorHelper::FetchNavigationCorrections(
+ const GURL& navigation_correction_url,
+ const std::string& navigation_correction_request_body) {
+ DCHECK(!correction_loader_.get());
+
+ std::unique_ptr<network::ResourceRequest> resource_request =
+ CreatePostRequest(navigation_correction_url);
+
+ correction_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), GetNetworkTrafficAnnotationTag());
+ correction_loader_->AttachStringForUpload(navigation_correction_request_body,
+ "application/json");
+ correction_loader_->DownloadToString(
+ render_frame()->GetURLLoaderFactory().get(),
+ base::BindOnce(&NetErrorHelper::OnNavigationCorrectionsFetched,
+ base::Unretained(this)),
+ network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
+ correction_loader_->SetTimeoutDuration(
+ base::TimeDelta::FromSeconds(kNavigationCorrectionFetchTimeoutSec));
+}
+
+void NetErrorHelper::CancelFetchNavigationCorrections() {
+ correction_loader_.reset();
+}
+
+void NetErrorHelper::SendTrackingRequest(
+ const GURL& tracking_url,
+ const std::string& tracking_request_body) {
+ // If there's already a pending tracking request, this will cancel it.
+ std::unique_ptr<network::ResourceRequest> resource_request =
+ CreatePostRequest(tracking_url);
+
+ tracking_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), GetNetworkTrafficAnnotationTag());
+ tracking_loader_->AttachStringForUpload(tracking_request_body,
+ "application/json");
+ tracking_loader_->DownloadToString(
+ render_frame()->GetURLLoaderFactory().get(),
+ base::BindOnce(&NetErrorHelper::OnTrackingRequestComplete,
+ base::Unretained(this)),
+ network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
+}
+
+void NetErrorHelper::ReloadFrame() {
+ render_frame()->GetWebFrame()->StartReload(blink::WebFrameLoadType::kReload);
+}
+
+void NetErrorHelper::DiagnoseError(const GURL& page_url) {
+ GetRemoteNetworkDiagnostics()->RunNetworkDiagnostics(page_url);
+}
+
+void NetErrorHelper::DownloadPageLater() {
+#if defined(OS_ANDROID)
+ render_frame()->Send(new ChromeViewHostMsg_DownloadPageLater(
+ render_frame()->GetRoutingID()));
+#endif // defined(OS_ANDROID)
+}
+
+void NetErrorHelper::SetIsShowingDownloadButton(bool show) {
+#if defined(OS_ANDROID)
+ render_frame()->Send(
+ new ChromeViewHostMsg_SetIsShowingDownloadButtonInErrorPage(
+ render_frame()->GetRoutingID(), show));
+#endif // defined(OS_ANDROID)
+}
+
+void NetErrorHelper::OfflineContentAvailable(
+ bool list_visible_by_prefs,
+ const std::string& offline_content_json) {
+#if defined(OS_ANDROID)
+ if (!offline_content_json.empty()) {
+ std::string isShownParam(list_visible_by_prefs ? "true" : "false");
+ render_frame()->ExecuteJavaScript(base::UTF8ToUTF16(
+ base::StrCat({"offlineContentAvailable(", isShownParam, ", ",
+ offline_content_json, ");"})));
+ }
+#endif
+}
+
+#if defined(OS_ANDROID)
+void NetErrorHelper::SetAutoFetchState(
+ chrome::mojom::OfflinePageAutoFetcherScheduleResult result) {
+ const char* scheduled = "false";
+ const char* can_schedule = "false";
+ switch (result) {
+ case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kAlreadyScheduled:
+ case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kScheduled:
+ scheduled = "true";
+ can_schedule = "true";
+ break;
+ case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kOtherError:
+ break;
+ case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kNotEnoughQuota:
+ can_schedule = "true";
+ break;
+ }
+ render_frame()->ExecuteJavaScript(base::UTF8ToUTF16(base::StrCat(
+ {"setAutoFetchState(", scheduled, ", ", can_schedule, ");"})));
+}
+#endif // defined(OS_ANDROID)
+
+void NetErrorHelper::DNSProbeStatus(int32_t status_num) {
+ DCHECK(status_num >= 0 && status_num < error_page::DNS_PROBE_MAX);
+
+ DVLOG(1) << "Received status " << DnsProbeStatusToString(status_num);
+
+ core_->OnNetErrorInfo(static_cast<DnsProbeStatus>(status_num));
+}
+
+void NetErrorHelper::SetNavigationCorrectionInfo(
+ const GURL& navigation_correction_url,
+ const std::string& language,
+ const std::string& country_code,
+ const std::string& api_key,
+ const GURL& search_url) {
+ core_->OnSetNavigationCorrectionInfo(navigation_correction_url, language,
+ country_code, api_key, search_url);
+}
+
+void NetErrorHelper::OnNavigationCorrectionsFetched(
+ std::unique_ptr<std::string> response_body) {
+ bool success = response_body.get() != nullptr;
+ correction_loader_.reset();
+ core_->OnNavigationCorrectionsFetched(success ? *response_body : "",
+ base::i18n::IsRTL());
+}
+
+void NetErrorHelper::OnTrackingRequestComplete(
+ std::unique_ptr<std::string> response_body) {
+ tracking_loader_.reset();
+}
+
+void NetErrorHelper::OnNetworkDiagnosticsClientRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::NetworkDiagnosticsClient>
+ receiver) {
+ network_diagnostics_client_receivers_.Add(this, std::move(receiver));
+}
+
+void NetErrorHelper::OnNavigationCorrectorRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::NavigationCorrector>
+ receiver) {
+ navigation_corrector_receivers_.Add(this, std::move(receiver));
+}
+
+void NetErrorHelper::SetCanShowNetworkDiagnosticsDialog(bool can_show) {
+ core_->OnSetCanShowNetworkDiagnosticsDialog(can_show);
+}
diff --git a/chromium/chrome/renderer/net/net_error_helper.h b/chromium/chrome/renderer/net/net_error_helper.h
new file mode 100644
index 00000000000..eda01c4d0dd
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_helper.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_NET_NET_ERROR_HELPER_H_
+#define CHROME_RENDERER_NET_NET_ERROR_HELPER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "build/build_config.h"
+#include "chrome/common/navigation_corrector.mojom.h"
+#include "chrome/common/network_diagnostics.mojom.h"
+#include "chrome/common/network_easter_egg.mojom.h"
+#include "chrome/renderer/net/net_error_helper_core.h"
+#include "chrome/renderer/net/net_error_page_controller.h"
+#include "components/error_page/common/localized_error.h"
+#include "components/error_page/common/net_error_info.h"
+#include "components/security_interstitials/content/renderer/security_interstitial_page_controller.h"
+#include "components/security_interstitials/core/controller_client.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "net/base/net_errors.h"
+
+class GURL;
+
+namespace error_page {
+class Error;
+struct ErrorPageParams;
+}
+
+namespace network {
+class SimpleURLLoader;
+}
+
+// Listens for NetErrorInfo messages from the NetErrorTabHelper on the
+// browser side and updates the error page with more details (currently, just
+// DNS probe results) if/when available.
+// TODO(crbug.com/578770): Should this class be moved into the error_page
+// component?
+class NetErrorHelper
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<NetErrorHelper>,
+ public content::RenderThreadObserver,
+ public NetErrorHelperCore::Delegate,
+ public NetErrorPageController::Delegate,
+ public security_interstitials::SecurityInterstitialPageController::
+ Delegate,
+ public chrome::mojom::NetworkDiagnosticsClient,
+ public chrome::mojom::NavigationCorrector {
+ public:
+ explicit NetErrorHelper(content::RenderFrame* render_frame);
+ ~NetErrorHelper() override;
+
+ // NetErrorPageController::Delegate implementation
+ void ButtonPressed(NetErrorHelperCore::Button button) override;
+ void TrackClick(int tracking_id) override;
+ void LaunchOfflineItem(const std::string& id,
+ const std::string& name_space) override;
+ void LaunchDownloadsPage() override;
+ void SavePageForLater() override;
+ void CancelSavePage() override;
+ void ListVisibilityChanged(bool is_visible) override;
+ void UpdateEasterEggHighScore(int high_score) override;
+ void ResetEasterEggHighScore() override;
+
+ // security_interstitials::SecurityInterstitialPageController::Delegate
+ // implementation
+ void SendCommand(
+ security_interstitials::SecurityInterstitialCommand command) override;
+
+ // RenderFrameObserver implementation.
+ void DidStartNavigation(
+ const GURL& url,
+ base::Optional<blink::WebNavigationType> navigation_type) override;
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override;
+ void DidFinishLoad() override;
+ void OnStop() override;
+ void WasShown() override;
+ void WasHidden() override;
+ void OnDestruct() override;
+
+ // RenderThreadObserver implementation.
+ void NetworkStateChanged(bool online) override;
+
+ // Sets values in |pending_error_page_info_|. If |error_html| is not null, it
+ // initializes |error_html| with the HTML of an error page in response to
+ // |error|. Updates internals state with the assumption the page will be
+ // loaded immediately.
+ void PrepareErrorPage(const error_page::Error& error,
+ bool is_failed_post,
+ std::string* error_html);
+
+ // Returns whether a load for |url| in the |frame| the NetErrorHelper is
+ // attached to should have its error page suppressed.
+ bool ShouldSuppressErrorPage(const GURL& url);
+
+ private:
+ // Returns ResourceRequest filled with |url|. It has request_initiator from
+ // the frame origin and origin header with "null" for a unique origin.
+ std::unique_ptr<network::ResourceRequest> CreatePostRequest(
+ const GURL& url) const;
+ chrome::mojom::NetworkDiagnostics* GetRemoteNetworkDiagnostics();
+ chrome::mojom::NetworkEasterEgg* GetRemoteNetworkEasterEgg();
+
+ // NetErrorHelperCore::Delegate implementation:
+ error_page::LocalizedError::PageState GenerateLocalizedErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_use_local_diagnostics_service,
+ std::unique_ptr<error_page::ErrorPageParams> params,
+ std::string* html) const override;
+ void LoadErrorPage(const std::string& html, const GURL& failed_url) override;
+
+ void EnablePageHelperFunctions() override;
+ error_page::LocalizedError::PageState UpdateErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_use_local_diagnostics_service) override;
+ void InitializeErrorPageEasterEggHighScore(int high_score) override;
+ void RequestEasterEggHighScore() override;
+ void FetchNavigationCorrections(
+ const GURL& navigation_correction_url,
+ const std::string& navigation_correction_request_body) override;
+ void CancelFetchNavigationCorrections() override;
+ void SendTrackingRequest(const GURL& tracking_url,
+ const std::string& tracking_request_body) override;
+ void ReloadFrame() override;
+ void DiagnoseError(const GURL& page_url) override;
+ void DownloadPageLater() override;
+ void SetIsShowingDownloadButton(bool show) override;
+ void OfflineContentAvailable(
+ bool list_visible_by_prefs,
+ const std::string& offline_content_json) override;
+ content::RenderFrame* GetRenderFrame() override;
+
+#if defined(OS_ANDROID)
+ void SetAutoFetchState(
+ chrome::mojom::OfflinePageAutoFetcherScheduleResult state) override;
+#endif
+
+ void OnSetNavigationCorrectionInfo(const GURL& navigation_correction_url,
+ const std::string& language,
+ const std::string& country_code,
+ const std::string& api_key,
+ const GURL& search_url);
+
+ void OnNavigationCorrectionsFetched(
+ std::unique_ptr<std::string> response_body);
+
+ void OnTrackingRequestComplete(std::unique_ptr<std::string> response_body);
+
+ void OnNetworkDiagnosticsClientRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::NetworkDiagnosticsClient>
+ receiver);
+ void OnNavigationCorrectorRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::NavigationCorrector>
+ receiver);
+
+ // chrome::mojom::NetworkDiagnosticsClient:
+ void SetCanShowNetworkDiagnosticsDialog(bool can_show) override;
+ void DNSProbeStatus(int32_t) override;
+
+ // chrome::mojom::NavigationCorrector:
+ void SetNavigationCorrectionInfo(const GURL& navigation_correction_url,
+ const std::string& language,
+ const std::string& country_code,
+ const std::string& api_key,
+ const GURL& search_url) override;
+
+ std::unique_ptr<network::SimpleURLLoader> correction_loader_;
+ std::unique_ptr<network::SimpleURLLoader> tracking_loader_;
+
+ std::unique_ptr<NetErrorHelperCore> core_;
+
+ mojo::AssociatedReceiverSet<chrome::mojom::NetworkDiagnosticsClient>
+ network_diagnostics_client_receivers_;
+ mojo::AssociatedRemote<chrome::mojom::NetworkDiagnostics>
+ remote_network_diagnostics_;
+ mojo::AssociatedReceiverSet<chrome::mojom::NavigationCorrector>
+ navigation_corrector_receivers_;
+ mojo::AssociatedRemote<chrome::mojom::NetworkEasterEgg>
+ remote_network_easter_egg_;
+
+ // Weak factories for vending weak pointers to PageControllers. Weak
+ // pointers are invalidated on each commit, to prevent getting messages from
+ // Controllers used for the previous commit that haven't yet been cleaned up.
+ base::WeakPtrFactory<NetErrorPageController::Delegate>
+ weak_controller_delegate_factory_{this};
+
+ base::WeakPtrFactory<
+ security_interstitials::SecurityInterstitialPageController::Delegate>
+ weak_security_interstitial_controller_delegate_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(NetErrorHelper);
+};
+
+#endif // CHROME_RENDERER_NET_NET_ERROR_HELPER_H_
diff --git a/chromium/chrome/renderer/net/net_error_helper_core.cc b/chromium/chrome/renderer/net/net_error_helper_core.cc
new file mode 100644
index 00000000000..572506e7155
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_helper_core.cc
@@ -0,0 +1,1067 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net/net_error_helper_core.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/i18n/rtl.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_value_converter.h"
+#include "base/json/json_writer.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_features.h"
+#include "components/error_page/common/error_page_params.h"
+#include "components/error_page/common/localized_error.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/url_formatter.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_thread.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace {
+
+// |NetErrorNavigationCorrectionTypes| enum id for Web search query.
+// Other correction types uses the |kCorrectionResourceTable| array order.
+const int kWebSearchQueryUMAId = 100;
+
+// Number of URL correction suggestions to display.
+const int kMaxUrlCorrectionsToDisplay = 1;
+
+struct CorrectionTypeToResourceTable {
+ int resource_id;
+ const char* correction_type;
+};
+
+// Note: Ordering should be the same as |NetErrorNavigationCorrectionTypes| enum
+// in histograms.xml.
+const CorrectionTypeToResourceTable kCorrectionResourceTable[] = {
+ {IDS_ERRORPAGES_SUGGESTION_VISIT_GOOGLE_CACHE, "cachedPage"},
+ // "reloadPage" is has special handling.
+ {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "urlCorrection"},
+ {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "siteDomain"},
+ {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "host"},
+ {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "sitemap"},
+ {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "pathParentFolder"},
+ // "siteSearchQuery" is not yet supported.
+ // TODO(mmenke): Figure out what format "siteSearchQuery" uses for its
+ // suggestions.
+ // "webSearchQuery" has special handling.
+ {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "contentOverlap"},
+ {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "emphasizedUrlCorrection"},
+};
+
+struct NavigationCorrection {
+ NavigationCorrection() : is_porn(false), is_soft_porn(false) {}
+
+ static void RegisterJSONConverter(
+ base::JSONValueConverter<NavigationCorrection>* converter) {
+ converter->RegisterStringField("correctionType",
+ &NavigationCorrection::correction_type);
+ converter->RegisterStringField("urlCorrection",
+ &NavigationCorrection::url_correction);
+ converter->RegisterStringField("clickType",
+ &NavigationCorrection::click_type);
+ converter->RegisterStringField("clickData",
+ &NavigationCorrection::click_data);
+ converter->RegisterBoolField("isPorn", &NavigationCorrection::is_porn);
+ converter->RegisterBoolField("isSoftPorn",
+ &NavigationCorrection::is_soft_porn);
+ }
+
+ std::string correction_type;
+ std::string url_correction;
+ std::string click_type;
+ std::string click_data;
+ bool is_porn;
+ bool is_soft_porn;
+};
+
+struct NavigationCorrectionResponse {
+ std::string event_id;
+ std::string fingerprint;
+ std::vector<std::unique_ptr<NavigationCorrection>> corrections;
+
+ static void RegisterJSONConverter(
+ base::JSONValueConverter<NavigationCorrectionResponse>* converter) {
+ converter->RegisterStringField("result.eventId",
+ &NavigationCorrectionResponse::event_id);
+ converter->RegisterStringField("result.fingerprint",
+ &NavigationCorrectionResponse::fingerprint);
+ converter->RegisterRepeatedMessage(
+ "result.UrlCorrections", &NavigationCorrectionResponse::corrections);
+ }
+};
+
+base::TimeDelta GetAutoReloadTime(size_t reload_count) {
+ static const int kDelaysMs[] = {0, 5000, 30000, 60000,
+ 300000, 600000, 1800000};
+ if (reload_count >= base::size(kDelaysMs))
+ reload_count = base::size(kDelaysMs) - 1;
+ return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
+}
+
+// Returns whether |error| is a DNS-related error (and therefore whether
+// the tab helper should start a DNS probe after receiving it).
+bool IsNetDnsError(const error_page::Error& error) {
+ return error.domain() == error_page::Error::kNetErrorDomain &&
+ net::IsDnsError(error.reason());
+}
+
+GURL SanitizeURL(const GURL& url) {
+ GURL::Replacements remove_params;
+ remove_params.ClearUsername();
+ remove_params.ClearPassword();
+ remove_params.ClearQuery();
+ remove_params.ClearRef();
+ return url.ReplaceComponents(remove_params);
+}
+
+// Sanitizes and formats a URL for upload to the error correction service.
+std::string PrepareUrlForUpload(const GURL& url) {
+ // TODO(yuusuke): Change to url_formatter::FormatUrl when Link Doctor becomes
+ // unicode-capable.
+ std::string spec_to_send = SanitizeURL(url).spec();
+
+ // Notify navigation correction service of the url truncation by sending of
+ // "?" at the end.
+ if (url.has_query())
+ spec_to_send.append("?");
+ return spec_to_send;
+}
+
+// Given an Error, returns true if the FixURL service should be used
+// for that error. Also sets |error_param| to the string that should be sent to
+// the FixURL service to identify the error type.
+bool ShouldUseFixUrlServiceForError(const error_page::Error& error,
+ std::string* error_param) {
+ error_param->clear();
+
+ // Don't use the correction service for HTTPS (for privacy reasons).
+ GURL unreachable_url(error.url());
+ if (GURL(unreachable_url).SchemeIsCryptographic())
+ return false;
+
+ const auto& domain = error.domain();
+ if (domain == error_page::Error::kHttpErrorDomain && error.reason() == 404) {
+ *error_param = "http404";
+ return true;
+ }
+ if (IsNetDnsError(error)) {
+ *error_param = "dnserror";
+ return true;
+ }
+ if (domain == error_page::Error::kNetErrorDomain &&
+ (error.reason() == net::ERR_CONNECTION_FAILED ||
+ error.reason() == net::ERR_CONNECTION_REFUSED ||
+ error.reason() == net::ERR_ADDRESS_UNREACHABLE ||
+ error.reason() == net::ERR_CONNECTION_TIMED_OUT)) {
+ *error_param = "connectionFailure";
+ return true;
+ }
+ return false;
+}
+
+// Creates a request body for use with the fixurl service. Sets parameters
+// shared by all types of requests to the service. |correction_params| must
+// contain the parameters specific to the actual request type.
+std::string CreateRequestBody(
+ const std::string& method,
+ const std::string& error_param,
+ const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
+ std::unique_ptr<base::DictionaryValue> params_dict) {
+ // Set params common to all request types.
+ params_dict->SetString("key", correction_params.api_key);
+ params_dict->SetString("clientName", "chrome");
+ params_dict->SetString("error", error_param);
+
+ if (!correction_params.language.empty())
+ params_dict->SetString("language", correction_params.language);
+
+ if (!correction_params.country_code.empty())
+ params_dict->SetString("originCountry", correction_params.country_code);
+
+ base::DictionaryValue request_dict;
+ request_dict.SetString("method", method);
+ request_dict.SetString("apiVersion", "v1");
+ request_dict.Set("params", std::move(params_dict));
+
+ std::string request_body;
+ bool success = base::JSONWriter::Write(request_dict, &request_body);
+ DCHECK(success);
+ return request_body;
+}
+
+// If URL correction information should be retrieved remotely for a main frame
+// load that failed with |error|, returns true and sets
+// |correction_request_body| to be the body for the correction request.
+std::string CreateFixUrlRequestBody(
+ const error_page::Error& error,
+ const NetErrorHelperCore::NavigationCorrectionParams& correction_params) {
+ std::string error_param;
+ bool result = ShouldUseFixUrlServiceForError(error, &error_param);
+ DCHECK(result);
+
+ // TODO(mmenke): Investigate open sourcing the relevant protocol buffers and
+ // using those directly instead.
+ std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
+ params->SetString("urlQuery", PrepareUrlForUpload(error.url()));
+ return CreateRequestBody("linkdoctor.fixurl.fixurl", error_param,
+ correction_params, std::move(params));
+}
+
+std::string CreateClickTrackingUrlRequestBody(
+ const error_page::Error& error,
+ const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
+ const NavigationCorrectionResponse& response,
+ const NavigationCorrection& correction) {
+ std::string error_param;
+ bool result = ShouldUseFixUrlServiceForError(error, &error_param);
+ DCHECK(result);
+
+ std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
+
+ params->SetString("originalUrlQuery", PrepareUrlForUpload(error.url()));
+
+ params->SetString("clickedUrlCorrection", correction.url_correction);
+ params->SetString("clickType", correction.click_type);
+ params->SetString("clickData", correction.click_data);
+
+ params->SetString("eventId", response.event_id);
+ params->SetString("fingerprint", response.fingerprint);
+
+ return CreateRequestBody("linkdoctor.fixurl.clicktracking", error_param,
+ correction_params, std::move(params));
+}
+
+base::string16 FormatURLForDisplay(const GURL& url, bool is_rtl) {
+ // Translate punycode into UTF8, unescape UTF8 URLs.
+ base::string16 url_for_display(url_formatter::FormatUrl(
+ url, url_formatter::kFormatUrlOmitNothing, net::UnescapeRule::NORMAL,
+ nullptr, nullptr, nullptr));
+ // URLs are always LTR.
+ if (is_rtl)
+ base::i18n::WrapStringWithLTRFormatting(&url_for_display);
+ return url_for_display;
+}
+
+std::unique_ptr<NavigationCorrectionResponse> ParseNavigationCorrectionResponse(
+ const std::string raw_response) {
+ // TODO(mmenke): Open source related protocol buffers and use them directly.
+ std::unique_ptr<base::Value> parsed =
+ base::JSONReader::ReadDeprecated(raw_response);
+ std::unique_ptr<NavigationCorrectionResponse> response(
+ new NavigationCorrectionResponse());
+ base::JSONValueConverter<NavigationCorrectionResponse> converter;
+ if (!parsed || !converter.Convert(*parsed, response.get()))
+ response.reset();
+ return response;
+}
+
+void LogCorrectionTypeShown(int type_id) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.ErrorPageCounts.NavigationCorrectionLinksShown", type_id,
+ kWebSearchQueryUMAId + 1);
+}
+
+std::unique_ptr<error_page::ErrorPageParams> CreateErrorPageParams(
+ const NavigationCorrectionResponse& response,
+ const error_page::Error& error,
+ const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
+ bool is_rtl) {
+ // Version of URL for display in suggestions. It has to be sanitized first
+ // because any received suggestions will be relative to the sanitized URL.
+ base::string16 original_url_for_display =
+ FormatURLForDisplay(SanitizeURL(GURL(error.url())), is_rtl);
+
+ std::unique_ptr<error_page::ErrorPageParams> params(
+ new error_page::ErrorPageParams());
+ params->override_suggestions.reset(new base::ListValue());
+ std::unique_ptr<base::ListValue> parsed_corrections(new base::ListValue());
+ for (auto it = response.corrections.begin(); it != response.corrections.end();
+ ++it) {
+ // Doesn't seem like a good idea to show these.
+ if ((*it)->is_porn || (*it)->is_soft_porn)
+ continue;
+
+ int tracking_id = it - response.corrections.begin();
+
+ if ((*it)->correction_type == "reloadPage") {
+ params->suggest_reload = true;
+ params->reload_tracking_id = tracking_id;
+ continue;
+ }
+
+ if ((*it)->correction_type == "webSearchQuery") {
+ // If there are multiple searches suggested, use the first suggestion.
+ if (params->search_terms.empty()) {
+ params->search_url = correction_params.search_url;
+ params->search_terms = (*it)->url_correction;
+ params->search_tracking_id = tracking_id;
+ LogCorrectionTypeShown(kWebSearchQueryUMAId);
+ }
+ continue;
+ }
+
+ // Allow reload page and web search query to be empty strings, but not
+ // links.
+ if ((*it)->url_correction.empty() ||
+ (params->override_suggestions->GetSize() >=
+ kMaxUrlCorrectionsToDisplay)) {
+ continue;
+ }
+
+ size_t correction_index;
+ for (correction_index = 0;
+ correction_index < base::size(kCorrectionResourceTable);
+ ++correction_index) {
+ if ((*it)->correction_type !=
+ kCorrectionResourceTable[correction_index].correction_type) {
+ continue;
+ }
+ std::unique_ptr<base::DictionaryValue> suggest(
+ new base::DictionaryValue());
+ suggest->SetString(
+ "summary",
+ l10n_util::GetStringUTF16(
+ kCorrectionResourceTable[correction_index].resource_id));
+ suggest->SetString("urlCorrection", (*it)->url_correction);
+ suggest->SetString(
+ "urlCorrectionForDisplay",
+ FormatURLForDisplay(GURL((*it)->url_correction), is_rtl));
+ suggest->SetString("originalUrlForDisplay", original_url_for_display);
+ suggest->SetInteger("trackingId", tracking_id);
+ suggest->SetInteger("type", static_cast<int>(correction_index));
+
+ params->override_suggestions->Append(std::move(suggest));
+ LogCorrectionTypeShown(static_cast<int>(correction_index));
+ break;
+ }
+ }
+
+ if (params->override_suggestions->empty() && !params->search_url.is_valid())
+ params.reset();
+ return params;
+}
+
+// Tracks navigation correction service usage in UMA to enable more in depth
+// analysis.
+void TrackClickUMA(std::string type_id) {
+ // Web search suggestion isn't in |kCorrectionResourceTable| array.
+ if (type_id == "webSearchQuery") {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.ErrorPageCounts.NavigationCorrectionLinksUsed",
+ kWebSearchQueryUMAId, kWebSearchQueryUMAId + 1);
+ return;
+ }
+
+ size_t correction_index;
+ for (correction_index = 0;
+ correction_index < base::size(kCorrectionResourceTable);
+ ++correction_index) {
+ if (kCorrectionResourceTable[correction_index].correction_type == type_id) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.ErrorPageCounts.NavigationCorrectionLinksUsed",
+ static_cast<int>(correction_index), kWebSearchQueryUMAId + 1);
+ break;
+ }
+ }
+}
+
+} // namespace
+
+struct NetErrorHelperCore::ErrorPageInfo {
+ ErrorPageInfo(error_page::Error error, bool was_failed_post)
+ : error(error),
+ was_failed_post(was_failed_post),
+ needs_dns_updates(false),
+ needs_load_navigation_corrections(false),
+ is_finished_loading(false),
+ auto_reload_triggered(false) {}
+
+ // Information about the failed page load.
+ error_page::Error error;
+ bool was_failed_post;
+
+ // Information about the status of the error page.
+
+ // True if a page is a DNS error page and has not yet received a final DNS
+ // probe status.
+ bool needs_dns_updates;
+ bool dns_probe_complete = false;
+
+ // True if a blank page was loaded, and navigation corrections need to be
+ // loaded to generate the real error page.
+ bool needs_load_navigation_corrections;
+
+ // Navigation correction service paramers, which will be used in response to
+ // certain types of network errors. They are all stored here in case they
+ // change over the course of displaying the error page.
+ std::unique_ptr<NetErrorHelperCore::NavigationCorrectionParams>
+ navigation_correction_params;
+
+ std::unique_ptr<NavigationCorrectionResponse> navigation_correction_response;
+
+ // All the navigation corrections that have been clicked, for tracking
+ // purposes.
+ std::set<int> clicked_corrections;
+
+ // True if a page has completed loading, at which point it can receive
+ // updates.
+ bool is_finished_loading;
+
+ // True if the auto-reload timer has fired and a reload is or has been in
+ // flight.
+ bool auto_reload_triggered;
+
+ error_page::LocalizedError::PageState page_state;
+};
+
+NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {}
+
+NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams(
+ const NavigationCorrectionParams& other) = default;
+
+NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {}
+
+bool NetErrorHelperCore::IsReloadableError(
+ const NetErrorHelperCore::ErrorPageInfo& info) {
+ GURL url = info.error.url();
+ return info.error.domain() == error_page::Error::kNetErrorDomain &&
+ info.error.reason() != net::ERR_ABORTED &&
+ // For now, net::ERR_UNKNOWN_URL_SCHEME is only being displayed on
+ // Chrome for Android.
+ info.error.reason() != net::ERR_UNKNOWN_URL_SCHEME &&
+ // Do not trigger if the server rejects a client certificate.
+ // https://crbug.com/431387
+ !net::IsClientCertificateError(info.error.reason()) &&
+ // Some servers reject client certificates with a generic
+ // handshake_failure alert.
+ // https://crbug.com/431387
+ info.error.reason() != net::ERR_SSL_PROTOCOL_ERROR &&
+ // Do not trigger for blacklisted URLs.
+ // https://crbug.com/803839
+ info.error.reason() != net::ERR_BLOCKED_BY_ADMINISTRATOR &&
+ // Do not trigger for requests that were blocked by the browser itself.
+ info.error.reason() != net::ERR_BLOCKED_BY_CLIENT &&
+ !info.was_failed_post &&
+ // Do not trigger for this error code because it is used by Chrome
+ // while an auth prompt is being displayed.
+ info.error.reason() != net::ERR_INVALID_AUTH_CREDENTIALS &&
+ // Don't auto-reload non-http/https schemas.
+ // https://crbug.com/471713
+ url.SchemeIsHTTPOrHTTPS();
+}
+
+NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate,
+ bool auto_reload_enabled,
+ bool is_visible)
+ : delegate_(delegate),
+ last_probe_status_(error_page::DNS_PROBE_POSSIBLE),
+ can_show_network_diagnostics_dialog_(false),
+ auto_reload_enabled_(auto_reload_enabled),
+ auto_reload_timer_(new base::OneShotTimer()),
+ auto_reload_paused_(false),
+ auto_reload_in_flight_(false),
+ uncommitted_load_started_(false),
+ online_(content::RenderThread::Get()->IsOnline()),
+ visible_(is_visible),
+ auto_reload_count_(0),
+ navigation_from_button_(NO_BUTTON)
+#if defined(OS_ANDROID)
+ ,
+ page_auto_fetcher_helper_(
+ std::make_unique<PageAutoFetcherHelper>(delegate->GetRenderFrame()))
+#endif
+{
+}
+
+NetErrorHelperCore::~NetErrorHelperCore() = default;
+
+void NetErrorHelperCore::CancelPendingFetches() {
+ // Cancel loading the alternate error page, and prevent any pending error page
+ // load from starting a new error page load. Swapping in the error page when
+ // it's finished loading could abort the navigation, otherwise.
+ if (committed_error_page_info_)
+ committed_error_page_info_->needs_load_navigation_corrections = false;
+ if (pending_error_page_info_)
+ pending_error_page_info_->needs_load_navigation_corrections = false;
+ delegate_->CancelFetchNavigationCorrections();
+ auto_reload_timer_->Stop();
+ auto_reload_paused_ = false;
+}
+
+void NetErrorHelperCore::OnStop() {
+ CancelPendingFetches();
+ uncommitted_load_started_ = false;
+ auto_reload_count_ = 0;
+ auto_reload_in_flight_ = false;
+}
+
+void NetErrorHelperCore::OnWasShown() {
+ visible_ = true;
+ if (auto_reload_paused_)
+ MaybeStartAutoReloadTimer();
+}
+
+void NetErrorHelperCore::OnWasHidden() {
+ visible_ = false;
+ PauseAutoReloadTimer();
+}
+
+void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
+ if (frame_type != MAIN_FRAME)
+ return;
+
+ uncommitted_load_started_ = true;
+
+ // If there's no pending error page information associated with the page load,
+ // or the new page is not an error page, then reset pending error page state.
+ if (!pending_error_page_info_ || page_type != ERROR_PAGE) {
+ CancelPendingFetches();
+ } else {
+ // Halt auto-reload if it's currently scheduled. OnFinishLoad will trigger
+ // auto-reload if appropriate.
+ PauseAutoReloadTimer();
+ }
+}
+
+void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) {
+ if (frame_type != MAIN_FRAME)
+ return;
+
+ // If a page is committing, either it's an error page and autoreload will be
+ // started again below, or it's a success page and we need to clear autoreload
+ // state.
+ auto_reload_in_flight_ = false;
+
+ // uncommitted_load_started_ could already be false, since RenderFrameImpl
+ // calls OnCommitLoad once for each in-page navigation (like a fragment
+ // change) with no corresponding OnStartLoad.
+ uncommitted_load_started_ = false;
+
+#if defined(OS_ANDROID)
+ // Don't need this state. It will be refreshed if another error page is
+ // loaded.
+ available_content_helper_.Reset();
+ page_auto_fetcher_helper_->OnCommitLoad();
+#endif
+
+ // Track if an error occurred due to a page button press.
+ // This isn't perfect; if (for instance), the server is slow responding
+ // to a request generated from the page reload button, and the user hits
+ // the browser reload button, this code will still believe the
+ // result is from the page reload button.
+ if (committed_error_page_info_ && pending_error_page_info_ &&
+ navigation_from_button_ != NO_BUTTON &&
+ committed_error_page_info_->error.url() ==
+ pending_error_page_info_->error.url()) {
+ DCHECK(navigation_from_button_ == RELOAD_BUTTON);
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR);
+ }
+ navigation_from_button_ = NO_BUTTON;
+
+ committed_error_page_info_ = std::move(pending_error_page_info_);
+}
+
+void NetErrorHelperCore::ErrorPageLoadedWithFinalErrorCode() {
+ ErrorPageInfo* page_info = committed_error_page_info_.get();
+ DCHECK(page_info);
+ error_page::Error updated_error = GetUpdatedError(*page_info);
+
+ if (page_info->page_state.is_offline_error)
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_ERROR_SHOWN);
+
+#if defined(OS_ANDROID)
+ // The fetch functions shouldn't be triggered multiple times per page load.
+ if (page_info->page_state.offline_content_feature_enabled) {
+ available_content_helper_.FetchAvailableContent(base::BindOnce(
+ &Delegate::OfflineContentAvailable, base::Unretained(delegate_)));
+ }
+
+ // |TrySchedule()| shouldn't be called more than once per page.
+ if (page_info->page_state.auto_fetch_allowed) {
+ page_auto_fetcher_helper_->TrySchedule(
+ false, base::BindOnce(&Delegate::SetAutoFetchState,
+ base::Unretained(delegate_)));
+ }
+#endif // defined(OS_ANDROID)
+
+ if (page_info->page_state.download_button_shown)
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_SHOWN);
+
+ if (page_info->page_state.reload_button_shown)
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN);
+
+ delegate_->SetIsShowingDownloadButton(
+ page_info->page_state.download_button_shown);
+}
+
+void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
+ if (frame_type != MAIN_FRAME)
+ return;
+
+ if (!committed_error_page_info_) {
+ auto_reload_count_ = 0;
+ return;
+ }
+ committed_error_page_info_->is_finished_loading = true;
+
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_SHOWN);
+ if (committed_error_page_info_->page_state.show_cached_copy_button_shown) {
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_SHOWN);
+ }
+
+ delegate_->SetIsShowingDownloadButton(
+ committed_error_page_info_->page_state.download_button_shown);
+
+ delegate_->EnablePageHelperFunctions();
+
+ if (committed_error_page_info_->needs_load_navigation_corrections) {
+ // If there is another pending error page load, |fix_url| should have been
+ // cleared.
+ DCHECK(!pending_error_page_info_);
+ DCHECK(!committed_error_page_info_->needs_dns_updates);
+ delegate_->FetchNavigationCorrections(
+ committed_error_page_info_->navigation_correction_params->url,
+ CreateFixUrlRequestBody(
+ committed_error_page_info_->error,
+ *committed_error_page_info_->navigation_correction_params));
+ } else if (auto_reload_enabled_ &&
+ IsReloadableError(*committed_error_page_info_)) {
+ MaybeStartAutoReloadTimer();
+ }
+
+ DVLOG(1) << "Error page finished loading; sending saved status.";
+ if (committed_error_page_info_->needs_dns_updates) {
+ if (last_probe_status_ != error_page::DNS_PROBE_POSSIBLE)
+ UpdateErrorPage();
+ } else {
+ ErrorPageLoadedWithFinalErrorCode();
+ }
+}
+
+void NetErrorHelperCore::PrepareErrorPage(FrameType frame_type,
+ const error_page::Error& error,
+ bool is_failed_post,
+ std::string* error_html) {
+ if (frame_type == MAIN_FRAME) {
+ // If navigation corrections were needed before, that should have been
+ // cancelled earlier by starting a new page load (Which has now failed).
+ DCHECK(!committed_error_page_info_ ||
+ !committed_error_page_info_->needs_load_navigation_corrections);
+
+ pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
+ pending_error_page_info_->navigation_correction_params.reset(
+ new NavigationCorrectionParams(navigation_correction_params_));
+ PrepareErrorPageForMainFrame(pending_error_page_info_.get(), error_html);
+ } else {
+ if (error_html) {
+ delegate_->GenerateLocalizedErrorPage(
+ error, is_failed_post,
+ false /* No diagnostics dialogs allowed for subframes. */, nullptr,
+ error_html);
+ }
+ }
+}
+
+void NetErrorHelperCore::OnNetErrorInfo(error_page::DnsProbeStatus status) {
+ DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, status);
+
+ last_probe_status_ = status;
+
+ if (!committed_error_page_info_ ||
+ !committed_error_page_info_->needs_dns_updates ||
+ !committed_error_page_info_->is_finished_loading) {
+ return;
+ }
+
+ UpdateErrorPage();
+}
+
+void NetErrorHelperCore::OnSetCanShowNetworkDiagnosticsDialog(
+ bool can_show_network_diagnostics_dialog) {
+ can_show_network_diagnostics_dialog_ = can_show_network_diagnostics_dialog;
+}
+
+void NetErrorHelperCore::OnSetNavigationCorrectionInfo(
+ const GURL& navigation_correction_url,
+ const std::string& language,
+ const std::string& country_code,
+ const std::string& api_key,
+ const GURL& search_url) {
+ navigation_correction_params_.url = navigation_correction_url;
+ navigation_correction_params_.language = language;
+ navigation_correction_params_.country_code = country_code;
+ navigation_correction_params_.api_key = api_key;
+ navigation_correction_params_.search_url = search_url;
+}
+
+void NetErrorHelperCore::OnEasterEggHighScoreReceived(int high_score) {
+ if (!committed_error_page_info_ ||
+ !committed_error_page_info_->is_finished_loading) {
+ return;
+ }
+
+ delegate_->InitializeErrorPageEasterEggHighScore(high_score);
+}
+
+void NetErrorHelperCore::PrepareErrorPageForMainFrame(
+ ErrorPageInfo* pending_error_page_info,
+ std::string* error_html) {
+ std::string error_param;
+ error_page::Error error = pending_error_page_info->error;
+
+ if (pending_error_page_info->navigation_correction_params &&
+ pending_error_page_info->navigation_correction_params->url.is_valid() &&
+ ShouldUseFixUrlServiceForError(error, &error_param)) {
+ pending_error_page_info->needs_load_navigation_corrections = true;
+ return;
+ }
+
+ if (IsNetDnsError(pending_error_page_info->error)) {
+ // The last probe status needs to be reset if this is a DNS error. This
+ // means that if a DNS error page is committed but has not yet finished
+ // loading, a DNS probe status scheduled to be sent to it may be thrown
+ // out, but since the new error page should trigger a new DNS probe, it
+ // will just get the results for the next page load.
+ last_probe_status_ = error_page::DNS_PROBE_POSSIBLE;
+ pending_error_page_info->needs_dns_updates = true;
+ error = GetUpdatedError(*pending_error_page_info);
+ }
+ if (error_html) {
+ pending_error_page_info->page_state = delegate_->GenerateLocalizedErrorPage(
+ error, pending_error_page_info->was_failed_post,
+ can_show_network_diagnostics_dialog_, nullptr, error_html);
+ }
+}
+
+void NetErrorHelperCore::UpdateErrorPage() {
+ DCHECK(committed_error_page_info_->needs_dns_updates);
+ DCHECK(committed_error_page_info_->is_finished_loading);
+ DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, last_probe_status_);
+
+ UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
+ last_probe_status_, error_page::DNS_PROBE_MAX);
+ // Every status other than error_page::DNS_PROBE_POSSIBLE and
+ // error_page::DNS_PROBE_STARTED is a final status code. Once one is reached,
+ // the page does not need further updates.
+ if (last_probe_status_ != error_page::DNS_PROBE_STARTED) {
+ committed_error_page_info_->needs_dns_updates = false;
+ committed_error_page_info_->dns_probe_complete = true;
+ }
+
+ error_page::LocalizedError::PageState new_state =
+ delegate_->UpdateErrorPage(GetUpdatedError(*committed_error_page_info_),
+ committed_error_page_info_->was_failed_post,
+ can_show_network_diagnostics_dialog_);
+
+ // This button can't be changed by a DNS error update, so there's no code
+ // to update the related UMA in ErrorPageLoadedWithFinalErrorCode(). Instead,
+ // verify there's no change in this button's state.
+ DCHECK_EQ(
+ committed_error_page_info_->page_state.show_cached_copy_button_shown,
+ new_state.show_cached_copy_button_shown);
+
+ committed_error_page_info_->page_state = std::move(new_state);
+ if (!committed_error_page_info_->needs_dns_updates)
+ ErrorPageLoadedWithFinalErrorCode();
+}
+
+void NetErrorHelperCore::OnNavigationCorrectionsFetched(
+ const std::string& corrections,
+ bool is_rtl) {
+ // Loading suggestions only starts when a blank error page finishes loading,
+ // and is cancelled with a new load.
+ DCHECK(!pending_error_page_info_);
+ DCHECK(committed_error_page_info_->is_finished_loading);
+ DCHECK(committed_error_page_info_->needs_load_navigation_corrections);
+ DCHECK(committed_error_page_info_->navigation_correction_params);
+
+ pending_error_page_info_.reset(
+ new ErrorPageInfo(committed_error_page_info_->error,
+ committed_error_page_info_->was_failed_post));
+ pending_error_page_info_->navigation_correction_response =
+ ParseNavigationCorrectionResponse(corrections);
+
+ std::string error_html;
+ if (pending_error_page_info_->navigation_correction_response) {
+ // Copy navigation correction parameters used for the request, so tracking
+ // requests can still be sent if the configuration changes.
+ pending_error_page_info_->navigation_correction_params.reset(
+ new NavigationCorrectionParams(
+ *committed_error_page_info_->navigation_correction_params));
+ std::unique_ptr<error_page::ErrorPageParams> params = CreateErrorPageParams(
+ *pending_error_page_info_->navigation_correction_response,
+ pending_error_page_info_->error,
+ *pending_error_page_info_->navigation_correction_params, is_rtl);
+ pending_error_page_info_->page_state =
+ delegate_->GenerateLocalizedErrorPage(
+ pending_error_page_info_->error,
+ pending_error_page_info_->was_failed_post,
+ can_show_network_diagnostics_dialog_, std::move(params),
+ &error_html);
+ } else {
+ // Since |navigation_correction_params| in |pending_error_page_info_| is
+ // NULL, this won't trigger another attempt to load corrections.
+ PrepareErrorPageForMainFrame(pending_error_page_info_.get(), &error_html);
+ }
+
+ // TODO(mmenke): Once the new API is in place, look into replacing this
+ // double page load by just updating the error page, like DNS
+ // probes do.
+ delegate_->LoadErrorPage(error_html, pending_error_page_info_->error.url());
+}
+
+error_page::Error NetErrorHelperCore::GetUpdatedError(
+ const ErrorPageInfo& error_info) const {
+ // If a probe didn't run or wasn't conclusive, restore the original error.
+ const bool dns_probe_used =
+ error_info.needs_dns_updates || error_info.dns_probe_complete;
+ if (!dns_probe_used || last_probe_status_ == error_page::DNS_PROBE_NOT_RUN ||
+ last_probe_status_ == error_page::DNS_PROBE_FINISHED_INCONCLUSIVE) {
+ return error_info.error;
+ }
+
+ return error_page::Error::DnsProbeError(
+ error_info.error.url(), last_probe_status_,
+ error_info.error.stale_copy_in_cache());
+}
+
+void NetErrorHelperCore::Reload() {
+ if (!committed_error_page_info_)
+ return;
+ delegate_->ReloadFrame();
+}
+
+bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
+ // Automation tools expect to be in control of reloads.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAutomation)) {
+ return false;
+ }
+
+ if (!committed_error_page_info_ ||
+ !committed_error_page_info_->is_finished_loading ||
+ pending_error_page_info_ || uncommitted_load_started_) {
+ return false;
+ }
+
+ StartAutoReloadTimer();
+ return true;
+}
+
+void NetErrorHelperCore::StartAutoReloadTimer() {
+ DCHECK(committed_error_page_info_);
+ DCHECK(IsReloadableError(*committed_error_page_info_));
+
+ committed_error_page_info_->auto_reload_triggered = true;
+
+ if (!online_ || !visible_) {
+ auto_reload_paused_ = true;
+ return;
+ }
+
+ auto_reload_paused_ = false;
+ base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
+ auto_reload_timer_->Stop();
+ auto_reload_timer_->Start(
+ FROM_HERE, delay,
+ base::Bind(&NetErrorHelperCore::AutoReloadTimerFired,
+ base::Unretained(this)));
+}
+
+void NetErrorHelperCore::AutoReloadTimerFired() {
+ // AutoReloadTimerFired only runs if:
+ // 1. StartAutoReloadTimer was previously called, which requires that
+ // committed_error_page_info_ is populated;
+ // 2. No other page load has started since (1), since OnStartLoad stops the
+ // auto-reload timer.
+ DCHECK(committed_error_page_info_);
+
+ auto_reload_count_++;
+ auto_reload_in_flight_ = true;
+ Reload();
+}
+
+void NetErrorHelperCore::PauseAutoReloadTimer() {
+ if (!auto_reload_timer_->IsRunning())
+ return;
+ DCHECK(committed_error_page_info_);
+ DCHECK(!auto_reload_paused_);
+ DCHECK(committed_error_page_info_->auto_reload_triggered);
+ auto_reload_timer_->Stop();
+ auto_reload_paused_ = true;
+}
+
+void NetErrorHelperCore::NetworkStateChanged(bool online) {
+ bool was_online = online_;
+ online_ = online;
+ if (!was_online && online) {
+ // Transitioning offline -> online
+ if (auto_reload_paused_)
+ MaybeStartAutoReloadTimer();
+ } else if (was_online && !online) {
+ // Transitioning online -> offline
+ if (auto_reload_timer_->IsRunning())
+ auto_reload_count_ = 0;
+ PauseAutoReloadTimer();
+ }
+}
+
+bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
+ const GURL& url) {
+ // Don't suppress child frame errors.
+ if (frame_type != MAIN_FRAME)
+ return false;
+
+ // If there's no auto reload attempt in flight, this error page didn't come
+ // from auto reload, so don't suppress it.
+ if (!auto_reload_in_flight_)
+ return false;
+
+ uncommitted_load_started_ = false;
+ // This serves to terminate the auto-reload in flight attempt. If
+ // ShouldSuppressErrorPage is called, the auto-reload yielded an error, which
+ // means the request was already sent.
+ auto_reload_in_flight_ = false;
+ MaybeStartAutoReloadTimer();
+ return true;
+}
+
+#if defined(OS_ANDROID)
+void NetErrorHelperCore::SetPageAutoFetcherHelperForTesting(
+ std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper) {
+ page_auto_fetcher_helper_ = std::move(page_auto_fetcher_helper);
+}
+#endif
+
+void NetErrorHelperCore::ExecuteButtonPress(Button button) {
+ // If there's no committed error page, should not be invoked.
+ DCHECK(committed_error_page_info_);
+
+ switch (button) {
+ case RELOAD_BUTTON:
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED);
+ navigation_from_button_ = RELOAD_BUTTON;
+ Reload();
+ return;
+ case MORE_BUTTON:
+ // Visual effects on page are handled in Javascript code.
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED);
+ return;
+ case EASTER_EGG:
+ RecordEvent(error_page::NETWORK_ERROR_EASTER_EGG_ACTIVATED);
+ delegate_->RequestEasterEggHighScore();
+ return;
+ case SHOW_CACHED_COPY_BUTTON:
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_CLICKED);
+ return;
+ case DIAGNOSE_ERROR:
+ RecordEvent(error_page::NETWORK_ERROR_DIAGNOSE_BUTTON_CLICKED);
+ delegate_->DiagnoseError(committed_error_page_info_->error.url());
+ return;
+ case DOWNLOAD_BUTTON:
+ RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_CLICKED);
+ delegate_->DownloadPageLater();
+ return;
+ case NO_BUTTON:
+ NOTREACHED();
+ return;
+ }
+}
+
+void NetErrorHelperCore::TrackClick(int tracking_id) {
+ // It's technically possible for |navigation_correction_params| to be NULL but
+ // for |navigation_correction_response| not to be NULL, if the paramters
+ // changed between loading the original error page and loading the error page
+ if (!committed_error_page_info_ ||
+ !committed_error_page_info_->navigation_correction_response) {
+ return;
+ }
+
+ NavigationCorrectionResponse* response =
+ committed_error_page_info_->navigation_correction_response.get();
+
+ // |tracking_id| is less than 0 when the error page was not generated by the
+ // navigation correction service. |tracking_id| should never be greater than
+ // the array size, but best to be safe, since it contains data from a remote
+ // site, though none of that data should make it into Javascript callbacks.
+ if (tracking_id < 0 ||
+ static_cast<size_t>(tracking_id) >= response->corrections.size()) {
+ return;
+ }
+
+ // Only report a clicked link once.
+ if (committed_error_page_info_->clicked_corrections.count(tracking_id))
+ return;
+
+ TrackClickUMA(response->corrections[tracking_id]->correction_type);
+
+ committed_error_page_info_->clicked_corrections.insert(tracking_id);
+ std::string request_body = CreateClickTrackingUrlRequestBody(
+ committed_error_page_info_->error,
+ *committed_error_page_info_->navigation_correction_params, *response,
+ *response->corrections[tracking_id]);
+ delegate_->SendTrackingRequest(
+ committed_error_page_info_->navigation_correction_params->url,
+ request_body);
+}
+
+void NetErrorHelperCore::LaunchOfflineItem(const std::string& id,
+ const std::string& name_space) {
+#if defined(OS_ANDROID)
+ available_content_helper_.LaunchItem(id, name_space);
+#endif
+}
+
+void NetErrorHelperCore::LaunchDownloadsPage() {
+#if defined(OS_ANDROID)
+ available_content_helper_.LaunchDownloadsPage();
+#endif
+}
+
+void NetErrorHelperCore::SavePageForLater() {
+#if defined(OS_ANDROID)
+ page_auto_fetcher_helper_->TrySchedule(
+ /*user_requested=*/true, base::BindOnce(&Delegate::SetAutoFetchState,
+ base::Unretained(delegate_)));
+#endif
+}
+
+void NetErrorHelperCore::CancelSavePage() {
+#if defined(OS_ANDROID)
+ page_auto_fetcher_helper_->CancelSchedule();
+#endif
+}
+
+void NetErrorHelperCore::ListVisibilityChanged(bool is_visible) {
+#if defined(OS_ANDROID)
+ available_content_helper_.ListVisibilityChanged(is_visible);
+#endif
+}
diff --git a/chromium/chrome/renderer/net/net_error_helper_core.h b/chromium/chrome/renderer/net/net_error_helper_core.h
new file mode 100644
index 00000000000..bdd2f4a2489
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_helper_core.h
@@ -0,0 +1,346 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_NET_NET_ERROR_HELPER_CORE_H_
+#define CHROME_RENDERER_NET_NET_ERROR_HELPER_CORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
+#include "components/error_page/common/error.h"
+#include "components/error_page/common/localized_error.h"
+#include "components/error_page/common/net_error_info.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/renderer/net/available_offline_content_helper.h"
+#include "chrome/renderer/net/page_auto_fetcher_helper_android.h"
+#endif
+
+namespace content {
+class RenderFrame;
+}
+namespace error_page {
+struct ErrorPageParams;
+}
+
+// Class that contains the logic for how the NetErrorHelper. This allows for
+// testing the logic without a RenderView or WebFrame, which are difficult to
+// mock, and for testing races which are impossible to reliably reproduce
+// with real RenderViews or WebFrames.
+class NetErrorHelperCore {
+ public:
+ enum FrameType {
+ MAIN_FRAME,
+ SUB_FRAME,
+ };
+
+ enum PageType {
+ NON_ERROR_PAGE,
+ ERROR_PAGE,
+ };
+
+ enum Button {
+ NO_BUTTON,
+ RELOAD_BUTTON,
+ MORE_BUTTON,
+ EASTER_EGG,
+ SHOW_CACHED_COPY_BUTTON, // "Google cached copy" button label experiment.
+ DIAGNOSE_ERROR,
+ DOWNLOAD_BUTTON, // "Download page later" experiment.
+ };
+
+ // The Delegate handles all interaction with the RenderView, WebFrame, and
+ // the network, as well as the generation of error pages.
+ class Delegate {
+ public:
+ // Generates an error page's HTML for the given error.
+ virtual error_page::LocalizedError::PageState GenerateLocalizedErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_show_network_diagnostics_dialog,
+ std::unique_ptr<error_page::ErrorPageParams> params,
+ std::string* html) const = 0;
+
+ // Loads the given HTML in the frame for use as an error page.
+ virtual void LoadErrorPage(const std::string& html,
+ const GURL& failed_url) = 0;
+
+ // Create extra Javascript bindings in the error page. Will only be invoked
+ // after an error page has finished loading.
+ virtual void EnablePageHelperFunctions() = 0;
+
+ // Updates the currently displayed error page with a new error code. The
+ // currently displayed error page must have finished loading, and must have
+ // been generated by a call to GenerateLocalizedErrorPage.
+ virtual error_page::LocalizedError::PageState UpdateErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_show_network_diagnostics_dialog) = 0;
+
+ // Tell the currently displayed error page about the user's current easter
+ // egg game high score (from the user's synced preferences). The currently
+ // displayed error page must have finished loading, and must have been
+ // generated by a call to GenerateLocalizedErrorPage.
+ virtual void InitializeErrorPageEasterEggHighScore(int high_score) = 0;
+
+ // Request the current easter egg high score stored in the user's synced
+ // preferences from the browser. The delegate should call
+ // OnEasterEggHighScoreReceived() with the response.
+ virtual void RequestEasterEggHighScore() = 0;
+
+ // Fetches an error page and calls into OnErrorPageFetched when done. Any
+ // previous fetch must either be canceled or finished before calling. Can't
+ // be called synchronously after a previous fetch completes.
+ virtual void FetchNavigationCorrections(
+ const GURL& navigation_correction_url,
+ const std::string& navigation_correction_request_body) = 0;
+
+ // Cancels fetching navigation corrections. Does nothing if no fetch is
+ // ongoing.
+ virtual void CancelFetchNavigationCorrections() = 0;
+
+ // Sends an HTTP request used to track which link on the page was clicked to
+ // the navigation correction service.
+ virtual void SendTrackingRequest(
+ const GURL& tracking_url,
+ const std::string& tracking_request_body) = 0;
+
+ // Starts a reload of the observed frame.
+ virtual void ReloadFrame() = 0;
+
+ // Run the platform diagnostics too for the specified URL.
+ virtual void DiagnoseError(const GURL& page_url) = 0;
+
+ // Schedule to download the page at a later time.
+ virtual void DownloadPageLater() = 0;
+
+ // Inform that download button is being shown in the error page.
+ virtual void SetIsShowingDownloadButton(bool show) = 0;
+
+ // Signals that offline content is available.
+ virtual void OfflineContentAvailable(
+ bool list_visible_by_prefs,
+ const std::string& offline_content_json) = 0;
+
+ // Returns the render frame associated with NetErrorHelper.
+ virtual content::RenderFrame* GetRenderFrame() = 0;
+
+#if defined(OS_ANDROID)
+ // Called after an attempt to automatically schedule a background fetch for
+ // a page with a network error.
+ virtual void SetAutoFetchState(
+ chrome::mojom::OfflinePageAutoFetcherScheduleResult result) = 0;
+#endif
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ struct NavigationCorrectionParams {
+ NavigationCorrectionParams();
+ NavigationCorrectionParams(const NavigationCorrectionParams& other);
+ ~NavigationCorrectionParams();
+
+ // URL used both for getting the suggestions and tracking clicks.
+ GURL url;
+
+ std::string language;
+ std::string country_code;
+ std::string api_key;
+ GURL search_url;
+ };
+
+ NetErrorHelperCore(Delegate* delegate,
+ bool auto_reload_enabled,
+ bool is_visible);
+ ~NetErrorHelperCore();
+
+ // Sets values in |pending_error_page_info_|. If |error_html| is not null, it
+ // initializes |error_html| with the HTML of an error page in response to
+ // |error|. Updates internals state with the assumption the page will be
+ // loaded immediately.
+ void PrepareErrorPage(FrameType frame_type,
+ const error_page::Error& error,
+ bool is_failed_post,
+ std::string* error_html);
+
+ // These methods handle tracking the actual state of the page.
+ void OnStartLoad(FrameType frame_type, PageType page_type);
+ void OnCommitLoad(FrameType frame_type, const GURL& url);
+ void OnFinishLoad(FrameType frame_type);
+ void OnStop();
+ void OnWasShown();
+ void OnWasHidden();
+
+ void CancelPendingFetches();
+
+ // Called when an error page have has been retrieved over the network. |html|
+ // must be an empty string on error.
+ void OnNavigationCorrectionsFetched(const std::string& corrections,
+ bool is_rtl);
+
+ // Notifies |this| that network error information from the browser process
+ // has been received.
+ void OnNetErrorInfo(error_page::DnsProbeStatus status);
+
+ // Notifies |this| if it can use a local error diagnostics service through its
+ // delegate.
+ void OnSetCanShowNetworkDiagnosticsDialog(
+ bool can_show_network_diagnostics_dialog);
+
+ void OnSetNavigationCorrectionInfo(const GURL& navigation_correction_url,
+ const std::string& language,
+ const std::string& country_code,
+ const std::string& api_key,
+ const GURL& search_url);
+
+ // Notifies |this| about the current high score that's saved in the user's
+ // synced preferences.
+ void OnEasterEggHighScoreReceived(int high_score);
+
+ // Notifies |this| that the network's online status changed.
+ // Handler for NetworkStateChanged notification from the browser process. If
+ // the network state changes to online, this method is responsible for
+ // starting the auto-reload process.
+ //
+ // Warning: if there are many tabs sitting at an error page, this handler will
+ // be run at the same time for each of their top-level renderframes, which can
+ // cause many requests to be started at the same time. There's no current
+ // protection against this kind of "reload storm".
+ //
+ // TODO(rdsmith): prevent the reload storm.
+ void NetworkStateChanged(bool online);
+
+ int auto_reload_count() const { return auto_reload_count_; }
+
+ bool ShouldSuppressErrorPage(FrameType frame_type, const GURL& url);
+
+ void set_timer_for_testing(std::unique_ptr<base::OneShotTimer> timer) {
+ auto_reload_timer_ = std::move(timer);
+ }
+
+#if defined(OS_ANDROID)
+ void SetPageAutoFetcherHelperForTesting(
+ std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper);
+#endif
+
+ // Execute the effect of pressing the specified button.
+ // Note that the visual effects of the 'MORE' button are taken
+ // care of in JavaScript.
+ void ExecuteButtonPress(Button button);
+
+ // Reports to the correction service that the link with the given tracking
+ // ID was clicked. Only pages generated with information from the service
+ // have links with tracking IDs. Duplicate requests from the same page with
+ // the same tracking ID are ignored.
+ void TrackClick(int tracking_id);
+
+ // Opens a suggested offline item.
+ void LaunchOfflineItem(const std::string& id, const std::string& name_space);
+
+ // Shows all available offline content.
+ void LaunchDownloadsPage();
+
+ void CancelSavePage();
+ void SavePageForLater();
+
+ // Signals the user changed the visibility of the offline content list in the
+ // dino page.
+ void ListVisibilityChanged(bool is_visible);
+
+ private:
+ struct ErrorPageInfo;
+
+ // Sets values in |pending_error_page_info| for a main frame error page. If
+ // |error_html| is not null, it also fetches the string containing the error
+ // page HTML, and sets error_html to it. Depending on
+ // |pending_error_page_info|, may use the navigation correction service, or
+ // show a DNS probe error page. May modify |pending_error_page_info|.
+ void PrepareErrorPageForMainFrame(ErrorPageInfo* pending_error_page_info,
+ std::string* error_html);
+
+ // Updates the currently displayed error page with a new error based on the
+ // most recently received DNS probe result. The page must have finished
+ // loading before this is called.
+ void UpdateErrorPage();
+
+ // Called after the error page is loaded and is showing the final error code.
+ // This is either called on page load, or after a DNS probe finishes.
+ void ErrorPageLoadedWithFinalErrorCode();
+
+ error_page::Error GetUpdatedError(const ErrorPageInfo& error_info) const;
+
+ void Reload();
+ bool MaybeStartAutoReloadTimer();
+ void StartAutoReloadTimer();
+ void AutoReloadTimerFired();
+ void PauseAutoReloadTimer();
+
+ static bool IsReloadableError(const ErrorPageInfo& info);
+
+ Delegate* delegate_;
+
+ // The last DnsProbeStatus received from the browser.
+ error_page::DnsProbeStatus last_probe_status_;
+
+ // Information for the provisional / "pre-provisional" error page. NULL when
+ // there's no page pending, or the pending page is not an error page.
+ std::unique_ptr<ErrorPageInfo> pending_error_page_info_;
+
+ // Information for the committed error page. NULL when the committed page is
+ // not an error page.
+ std::unique_ptr<ErrorPageInfo> committed_error_page_info_;
+
+ bool can_show_network_diagnostics_dialog_;
+
+ NavigationCorrectionParams navigation_correction_params_;
+
+ // True if auto-reload is enabled at all.
+ const bool auto_reload_enabled_;
+
+ // Timer used to wait for auto-reload attempts.
+ std::unique_ptr<base::OneShotTimer> auto_reload_timer_;
+
+ // True if the auto-reload timer would be running but is waiting for an
+ // offline->online network transition.
+ bool auto_reload_paused_;
+
+ // Whether an auto-reload-initiated Reload() attempt is in flight.
+ bool auto_reload_in_flight_;
+
+ // True if there is an uncommitted-but-started load, error page or not. This
+ // is used to inhibit starting auto-reload when an error page finishes, in
+ // case this happens:
+ // Error page starts
+ // Error page commits
+ // Non-error page starts
+ // Error page finishes
+ bool uncommitted_load_started_;
+
+ // Is the browser online?
+ bool online_;
+
+ // Is the RenderFrame this object is observing visible?
+ bool visible_;
+
+ int auto_reload_count_;
+
+ // This value is set only when a navigation has been initiated from
+ // the error page. It is used to detect when such navigations result
+ // in errors.
+ Button navigation_from_button_;
+
+#if defined(OS_ANDROID)
+ AvailableOfflineContentHelper available_content_helper_;
+ std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper_;
+#endif
+};
+
+#endif // CHROME_RENDERER_NET_NET_ERROR_HELPER_CORE_H_
diff --git a/chromium/chrome/renderer/net/net_error_helper_core_unittest.cc b/chromium/chrome/renderer/net/net_error_helper_core_unittest.cc
new file mode 100644
index 00000000000..437e13e5e05
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -0,0 +1,2784 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net/net_error_helper_core.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "base/timer/mock_timer.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/available_offline_content.mojom.h"
+#include "chrome/renderer/net/available_offline_content_helper.h"
+#include "components/error_page/common/error.h"
+#include "components/error_page/common/error_page_params.h"
+#include "components/error_page/common/net_error_info.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/mock_render_thread.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "net/base/net_errors.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/common/offline_page_auto_fetcher.mojom.h"
+#endif
+
+namespace {
+
+const char kFailedUrl[] = "http://failed/";
+const char kFailedHttpsUrl[] = "https://failed/";
+
+const char kNavigationCorrectionUrl[] = "http://navigation.corrections/";
+const char kLanguage[] = "en";
+const char kCountry[] = "us";
+const char kApiKey[] = "api_key";
+const char kSearchUrl[] = "http://www.google.com/search";
+
+const char kSuggestedSearchTerms[] = "Happy Goats";
+const char kNavigationCorrectionEventId[] = "#007";
+const char kNavigationCorrectionFingerprint[] = "RandumStuff";
+
+struct NavigationCorrection {
+ const char* correction_type;
+ const char* url_correction;
+ const char* click_type;
+ const char* click_data;
+ bool is_porn;
+ bool is_soft_porn;
+
+ std::unique_ptr<base::DictionaryValue> ToValue() const {
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetString("correctionType", correction_type);
+ dict->SetString("urlCorrection", url_correction);
+ dict->SetString("clickType", click_type);
+ dict->SetString("clickData", click_data);
+ dict->SetBoolean("isPorn", is_porn);
+ dict->SetBoolean("isSoftPorn", is_soft_porn);
+ return dict;
+ }
+};
+
+const NavigationCorrection kDefaultCorrections[] = {
+ {"reloadPage", kFailedUrl, "rld", "data1", false, false},
+ {"urlCorrection", "http://somewhere_else/", "btn", "data2", false, false},
+ {"contentOverlap", "http://pony_island/", "btn", "data3", false, false},
+
+ // Porn should be ignored.
+ {"emphasizedUrlCorrection", "http://porn/", "btn", "data4", true, false},
+ {"sitemap", "http://more_porn/", "btn", "data5", false, true},
+
+ {"webSearchQuery", kSuggestedSearchTerms, "frm", "data6", false, false},
+};
+
+std::string SuggestionsToResponse(const NavigationCorrection* corrections,
+ int num_corrections) {
+ auto url_corrections = std::make_unique<base::ListValue>();
+ for (int i = 0; i < num_corrections; ++i)
+ url_corrections->Append(corrections[i].ToValue());
+
+ base::DictionaryValue response;
+ response.Set("result.UrlCorrections", std::move(url_corrections));
+ response.SetString("result.eventId", kNavigationCorrectionEventId);
+ response.SetString("result.fingerprint", kNavigationCorrectionFingerprint);
+
+ std::string json;
+ base::JSONWriter::Write(response, &json);
+ return json;
+}
+
+testing::AssertionResult StringValueEquals(const base::DictionaryValue& dict,
+ const std::string& key,
+ const std::string& expected_value) {
+ std::string actual_value;
+ if (!dict.GetString(key, &actual_value))
+ return testing::AssertionFailure() << key << " not found.";
+ if (actual_value != expected_value) {
+ return testing::AssertionFailure()
+ << "actual: " << actual_value << "\n expected: " << expected_value;
+ }
+ return testing::AssertionSuccess();
+}
+
+// Creates a string from an error that is used as a mock locally generated
+// error page for that error.
+std::string ErrorToString(const error_page::Error& error, bool is_failed_post) {
+ std::ostringstream ss;
+ ss << "(" << error.url() << ", " << error.domain() << ", " << error.reason()
+ << ", " << (is_failed_post ? "POST" : "NOT POST") << ")";
+ return ss.str();
+}
+
+error_page::Error ProbeError(error_page::DnsProbeStatus status) {
+ return error_page::Error::DnsProbeError(GURL(kFailedUrl), status, false);
+}
+
+error_page::Error ProbeErrorForURL(error_page::DnsProbeStatus status,
+ const GURL& url) {
+ return error_page::Error::DnsProbeError(url, status, false);
+}
+
+error_page::Error NetErrorForURL(net::Error net_error, const GURL& url) {
+ return error_page::Error::NetError(url, net_error, false);
+}
+
+error_page::Error NetError(net::Error net_error) {
+ return error_page::Error::NetError(GURL(kFailedUrl), net_error, false);
+}
+
+// Convenience functions that create an error string for a non-POST request.
+
+std::string ProbeErrorString(error_page::DnsProbeStatus status) {
+ return ErrorToString(ProbeError(status), false);
+}
+
+std::string NetErrorStringForURL(net::Error net_error, const GURL& url) {
+ return ErrorToString(NetErrorForURL(net_error, url), false);
+}
+
+std::string NetErrorString(net::Error net_error) {
+ return ErrorToString(NetError(net_error), false);
+}
+
+class NetErrorHelperCoreTest : public testing::Test,
+ public NetErrorHelperCore::Delegate {
+ public:
+ NetErrorHelperCoreTest()
+ : timer_(NULL),
+ update_count_(0),
+ error_html_update_count_(0),
+ reload_count_(0),
+ diagnose_error_count_(0),
+ download_count_(0),
+ list_visible_by_prefs_(true),
+ enable_page_helper_functions_count_(0),
+ default_url_(GURL(kFailedUrl)),
+ error_url_(GURL(content::kUnreachableWebDataURL)),
+ tracking_request_count_(0) {
+ SetUpCore(false, true);
+ }
+
+ ~NetErrorHelperCoreTest() override {
+ // No test finishes while an error page is being fetched.
+ EXPECT_FALSE(is_url_being_fetched());
+ }
+
+ void SetUpCore(bool auto_reload_enabled,
+ bool visible) {
+ // The old value of timer_, if any, will be freed by the old core_ being
+ // destructed, since core_ takes ownership of the timer.
+ timer_ = new base::MockOneShotTimer();
+ core_.reset(new NetErrorHelperCore(this, auto_reload_enabled, visible));
+ core_->set_timer_for_testing(base::WrapUnique(timer_));
+ }
+
+ NetErrorHelperCore* core() { return core_.get(); }
+
+ const GURL& url_being_fetched() const { return url_being_fetched_; }
+ bool is_url_being_fetched() const { return !url_being_fetched_.is_empty(); }
+
+ int reload_count() const { return reload_count_; }
+
+ int diagnose_error_count() const { return diagnose_error_count_; }
+
+ const GURL& diagnose_error_url() const { return diagnose_error_url_; }
+
+ int download_count() const { return download_count_; }
+
+ const GURL& default_url() const { return default_url_; }
+
+ const GURL& error_url() const { return error_url_; }
+
+ int enable_page_helper_functions_count() const {
+ return enable_page_helper_functions_count_;
+ }
+
+ const std::string& last_update_string() const { return last_update_string_; }
+ int update_count() const { return update_count_; }
+
+ const std::string& last_error_html() const { return last_error_html_; }
+ int error_html_update_count() const { return error_html_update_count_; }
+
+ bool last_can_show_network_diagnostics_dialog() const {
+ return last_can_show_network_diagnostics_dialog_;
+ }
+
+ const error_page::ErrorPageParams* last_error_page_params() const {
+ return last_error_page_params_.get();
+ }
+
+ const GURL& last_tracking_url() const { return last_tracking_url_; }
+ const std::string& last_tracking_request_body() const {
+ return last_tracking_request_body_;
+ }
+ int tracking_request_count() const { return tracking_request_count_; }
+
+ void set_offline_content_feature_enabled(
+ bool offline_content_feature_enabled) {
+ offline_content_feature_enabled_ = offline_content_feature_enabled;
+ }
+
+ bool list_visible_by_prefs() const { return list_visible_by_prefs_; }
+
+ void set_auto_fetch_allowed(bool allowed) { auto_fetch_allowed_ = allowed; }
+
+ void set_is_offline_error(bool is_offline_error) {
+ is_offline_error_ = is_offline_error;
+ }
+
+ const std::string& offline_content_json() const {
+ return offline_content_json_;
+ }
+
+ const std::string& offline_content_summary_json() const {
+ return offline_content_summary_json_;
+ }
+
+#if defined(OS_ANDROID)
+ // State of auto fetch, as reported to Delegate. Unset if SetAutoFetchState
+ // was not called.
+ base::Optional<chrome::mojom::OfflinePageAutoFetcherScheduleResult>
+ auto_fetch_state() const {
+ return auto_fetch_state_;
+ }
+#endif
+
+ base::MockOneShotTimer* timer() { return timer_; }
+
+ base::test::TaskEnvironment* task_environment() { return &task_environment_; }
+ content::MockRenderThread* render_thread() { return &render_thread_; }
+
+ void NavigationCorrectionsLoadSuccess(const NavigationCorrection* corrections,
+ int num_corrections) {
+ NavigationCorrectionsLoadFinished(
+ SuggestionsToResponse(corrections, num_corrections));
+ }
+
+ void NavigationCorrectionsLoadFailure() {
+ NavigationCorrectionsLoadFinished("");
+ }
+
+ void NavigationCorrectionsLoadFinished(const std::string& result) {
+ url_being_fetched_ = GURL();
+ core()->OnNavigationCorrectionsFetched(result, false);
+ }
+
+ void DoErrorLoadOfURL(net::Error error, const GURL& url) {
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorForURL(error, url),
+ false /* is_failed_post */, &html);
+ EXPECT_FALSE(html.empty());
+ EXPECT_EQ(NetErrorStringForURL(error, url), html);
+
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ }
+
+ void DoErrorLoad(net::Error error) {
+ DoErrorLoadOfURL(error, GURL(kFailedUrl));
+ }
+
+ void DoSuccessLoad() {
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ }
+
+ void DoDnsProbe(error_page::DnsProbeStatus final_status) {
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(final_status);
+ }
+
+ void EnableNavigationCorrections() {
+ SetNavigationCorrectionURL(GURL(kNavigationCorrectionUrl));
+ }
+
+ void DisableNavigationCorrections() { SetNavigationCorrectionURL(GURL()); }
+
+ void ExpectDefaultNavigationCorrections() const {
+ // Checks that the last error page params correspond to kDefaultSuggestions.
+ ASSERT_TRUE(last_error_page_params());
+ EXPECT_TRUE(last_error_page_params()->suggest_reload);
+ EXPECT_EQ(1u, last_error_page_params()->override_suggestions->GetSize());
+ EXPECT_EQ(GURL(kSearchUrl), last_error_page_params()->search_url);
+ EXPECT_EQ(kSuggestedSearchTerms, last_error_page_params()->search_terms);
+ }
+
+ private:
+ void SetNavigationCorrectionURL(const GURL& navigation_correction_url) {
+ core()->OnSetNavigationCorrectionInfo(navigation_correction_url, kLanguage,
+ kCountry, kApiKey, GURL(kSearchUrl));
+ }
+ error_page::LocalizedError::PageState GetPageState() const {
+ error_page::LocalizedError::PageState result;
+ result.auto_fetch_allowed = auto_fetch_allowed_;
+ result.offline_content_feature_enabled = offline_content_feature_enabled_;
+ result.is_offline_error = is_offline_error_;
+ return result;
+ }
+
+ // NetErrorHelperCore::Delegate implementation:
+ error_page::LocalizedError::PageState GenerateLocalizedErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_show_network_diagnostics_dialog,
+ std::unique_ptr<error_page::ErrorPageParams> params,
+ std::string* html) const override {
+ last_can_show_network_diagnostics_dialog_ =
+ can_show_network_diagnostics_dialog;
+ last_error_page_params_ = std::move(params);
+ *html = ErrorToString(error, is_failed_post);
+
+ return GetPageState();
+ }
+
+ void LoadErrorPage(const std::string& html, const GURL& failed_url) override {
+ error_html_update_count_++;
+ last_error_html_ = html;
+ }
+
+ void EnablePageHelperFunctions() override {
+ enable_page_helper_functions_count_++;
+ }
+
+ error_page::LocalizedError::PageState UpdateErrorPage(
+ const error_page::Error& error,
+ bool is_failed_post,
+ bool can_show_network_diagnostics_dialog) override {
+ update_count_++;
+ last_can_show_network_diagnostics_dialog_ =
+ can_show_network_diagnostics_dialog;
+ last_error_page_params_.reset(nullptr);
+ last_error_html_ = ErrorToString(error, is_failed_post);
+
+ return GetPageState();
+ }
+
+ void InitializeErrorPageEasterEggHighScore(int high_score) override {}
+ void RequestEasterEggHighScore() override {}
+
+ void FetchNavigationCorrections(
+ const GURL& navigation_correction_url,
+ const std::string& navigation_correction_request_body) override {
+ EXPECT_TRUE(url_being_fetched_.is_empty());
+ EXPECT_TRUE(request_body_.empty());
+ EXPECT_EQ(GURL(kNavigationCorrectionUrl), navigation_correction_url);
+
+ url_being_fetched_ = navigation_correction_url;
+ request_body_ = navigation_correction_request_body;
+
+ // Check the body of the request.
+
+ base::JSONReader reader;
+ std::unique_ptr<base::Value> parsed_body(
+ reader.ReadDeprecated(navigation_correction_request_body));
+ ASSERT_TRUE(parsed_body);
+ base::DictionaryValue* dict = NULL;
+ ASSERT_TRUE(parsed_body->GetAsDictionary(&dict));
+
+ EXPECT_TRUE(StringValueEquals(*dict, "params.urlQuery", kFailedUrl));
+ EXPECT_TRUE(StringValueEquals(*dict, "params.language", kLanguage));
+ EXPECT_TRUE(StringValueEquals(*dict, "params.originCountry", kCountry));
+ EXPECT_TRUE(StringValueEquals(*dict, "params.key", kApiKey));
+ }
+
+ void CancelFetchNavigationCorrections() override {
+ url_being_fetched_ = GURL();
+ request_body_.clear();
+ }
+
+ void ReloadFrame() override { reload_count_++; }
+
+ void DiagnoseError(const GURL& page_url) override {
+ diagnose_error_count_++;
+ diagnose_error_url_ = page_url;
+ }
+
+ void DownloadPageLater() override { download_count_++; }
+
+ void SetIsShowingDownloadButton(bool show) override {}
+
+ void OfflineContentAvailable(
+ bool list_visible_by_prefs,
+ const std::string& offline_content_json) override {
+ list_visible_by_prefs_ = list_visible_by_prefs;
+ offline_content_json_ = offline_content_json;
+ }
+
+#if defined(OS_ANDROID)
+ void SetAutoFetchState(
+ chrome::mojom::OfflinePageAutoFetcherScheduleResult result) override {
+ auto_fetch_state_ = result;
+ }
+#endif
+
+ void SendTrackingRequest(const GURL& tracking_url,
+ const std::string& tracking_request_body) override {
+ last_tracking_url_ = tracking_url;
+ last_tracking_request_body_ = tracking_request_body;
+ tracking_request_count_++;
+
+ // Check the body of the request.
+
+ base::JSONReader reader;
+ std::unique_ptr<base::Value> parsed_body(
+ reader.ReadDeprecated(tracking_request_body));
+ ASSERT_TRUE(parsed_body);
+ base::DictionaryValue* dict = NULL;
+ ASSERT_TRUE(parsed_body->GetAsDictionary(&dict));
+
+ EXPECT_TRUE(
+ StringValueEquals(*dict, "params.originalUrlQuery", kFailedUrl));
+ EXPECT_TRUE(StringValueEquals(*dict, "params.language", kLanguage));
+ EXPECT_TRUE(StringValueEquals(*dict, "params.originCountry", kCountry));
+ EXPECT_TRUE(StringValueEquals(*dict, "params.key", kApiKey));
+ }
+
+ content::RenderFrame* GetRenderFrame() override { return nullptr; }
+
+ base::MockOneShotTimer* timer_;
+
+ base::test::TaskEnvironment task_environment_;
+ content::MockRenderThread render_thread_;
+
+ std::unique_ptr<NetErrorHelperCore> core_;
+
+ GURL url_being_fetched_;
+ std::string request_body_;
+
+ // Contains the information passed to the last call to UpdateErrorPage, as a
+ // string.
+ std::string last_update_string_;
+ // Number of times |last_update_string_| has been changed.
+ int update_count_;
+
+ // Contains the HTML set by the last call to LoadErrorPageInMainFrame.
+ std::string last_error_html_;
+ // Number of times |last_error_html_| has been changed.
+ int error_html_update_count_;
+
+ // Values passed in to the last call of GenerateLocalizedErrorPage or
+ // UpdateErrorPage. Mutable because GenerateLocalizedErrorPage is const.
+ mutable bool last_can_show_network_diagnostics_dialog_;
+ mutable std::unique_ptr<error_page::ErrorPageParams> last_error_page_params_;
+
+ int reload_count_;
+ int diagnose_error_count_;
+ GURL diagnose_error_url_;
+ int download_count_;
+ bool list_visible_by_prefs_;
+ std::string offline_content_json_;
+ std::string offline_content_summary_json_;
+#if defined(OS_ANDROID)
+ base::Optional<chrome::mojom::OfflinePageAutoFetcherScheduleResult>
+ auto_fetch_state_;
+#endif
+ bool offline_content_feature_enabled_ = false;
+ bool is_offline_error_ = false;
+ bool auto_fetch_allowed_ = false;
+
+ int enable_page_helper_functions_count_;
+
+ const GURL default_url_;
+ const GURL error_url_;
+
+ GURL last_tracking_url_;
+ std::string last_tracking_request_body_;
+ int tracking_request_count_;
+};
+
+//------------------------------------------------------------------------------
+// Basic tests that don't update the error page for probes or load navigation
+// corrections.
+//------------------------------------------------------------------------------
+
+TEST_F(NetErrorHelperCoreTest, Null) {}
+
+TEST_F(NetErrorHelperCoreTest, SuccessfulPageLoad) {
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, SuccessfulPageLoadWithNavigationCorrections) {
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsError) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page.
+ EXPECT_FALSE(html.empty());
+ EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html);
+
+ // Error page loads.
+ EXPECT_EQ(0, enable_page_helper_functions_count());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+ EXPECT_EQ(1, enable_page_helper_functions_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsErrorWithCorrections) {
+ EnableNavigationCorrections();
+
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page.
+ EXPECT_FALSE(html.empty());
+ EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Much like above tests, but with a bunch of spurious DNS status messages that
+// should have no effect.
+TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsErrorSpuriousStatus) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ // Should have returned a local error page.
+ EXPECT_FALSE(html.empty());
+ EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html);
+
+ // Error page loads.
+
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, SubFrameDnsError) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page.
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, SubFrameDnsErrorWithCorrections) {
+ EnableNavigationCorrections();
+
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page.
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Much like above tests, but with a bunch of spurious DNS status messages that
+// should have no effect.
+TEST_F(NetErrorHelperCoreTest, SubFrameDnsErrorSpuriousStatus) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ // Should have returned a local error page.
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html);
+
+ // Error page loads.
+
+ core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+//------------------------------------------------------------------------------
+// Tests for updating the error page in response to DNS probe results. None
+// of these have navigation corrections enabled.
+//------------------------------------------------------------------------------
+
+// Test case where the error page finishes loading before receiving any DNS
+// probe messages.
+TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbe) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // Any other probe updates should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Same as above, but the probe is not run.
+TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNotRun) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+
+ // When the not run status arrives, the page should revert to the normal dns
+ // error page.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_NOT_RUN);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+
+ // Any other probe updates should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Same as above, but the probe result is inconclusive.
+TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeInconclusive) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ // When the inconclusive status arrives, the page should revert to the normal
+ // dns error page.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+
+ // Any other probe updates should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Same as above, but the probe result is no internet.
+TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNoInternet) {
+ base::HistogramTester histogram_tester_;
+
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ // The final status arrives, and should display the offline error page.
+ set_is_offline_error(true);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NO_INTERNET),
+ last_error_html());
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts", error_page::NETWORK_ERROR_PAGE_OFFLINE_ERROR_SHOWN,
+ 1);
+
+ // Any other probe updates should be ignored.
+ set_is_offline_error(false);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+
+ // Perform a second error page load, and confirm that the previous load
+ // doesn't affect the result.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+ set_is_offline_error(true);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NO_INTERNET),
+ last_error_html());
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts", error_page::NETWORK_ERROR_PAGE_OFFLINE_ERROR_SHOWN,
+ 2);
+}
+
+// Same as above, but the probe result is bad config.
+TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeBadConfig) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ // When the inconclusive status arrives, the page should revert to the normal
+ // dns error page.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_BAD_CONFIG),
+ last_error_html());
+
+ // Any other probe updates should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Test case where the error page finishes loading after receiving the start
+// DNS probe message.
+TEST_F(NetErrorHelperCoreTest, FinishedAfterStartProbe) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+
+ // Nothing should be done when a probe status comes in before loading
+ // finishes.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(0, update_count());
+
+ // When loading finishes, however, the buffered probe status should be sent
+ // to the page.
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ // Should update the page again when the probe result comes in.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // Any other probe updates should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_NOT_RUN);
+ EXPECT_EQ(2, update_count());
+}
+
+// Test case where the error page finishes loading before receiving any DNS
+// probe messages and the request is a POST.
+TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbePost) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ true /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ErrorToString(ProbeError(error_page::DNS_PROBE_POSSIBLE), true),
+ html);
+
+ // Error page loads.
+ EXPECT_EQ(0, enable_page_helper_functions_count());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(1, enable_page_helper_functions_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ErrorToString(ProbeError(error_page::DNS_PROBE_STARTED), true),
+ last_error_html());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(
+ ErrorToString(ProbeError(error_page::DNS_PROBE_FINISHED_NXDOMAIN), true),
+ last_error_html());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Test case where the probe finishes before the page is committed.
+TEST_F(NetErrorHelperCoreTest, ProbeFinishesEarly) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+
+ // Nothing should be done when the probe statuses come in before loading
+ // finishes.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ EXPECT_EQ(0, update_count());
+
+ // When loading finishes, however, the buffered probe status should be sent
+ // to the page.
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // Any other probe updates should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(1, update_count());
+}
+
+// Test case where one error page loads completely before a new navigation
+// results in another error page. Probes are run for both pages.
+TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbes) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Probe results come in.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // The process starts again.
+
+ // Normal page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(2, update_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(3, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ // The probe returns a different result this time.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_EQ(4, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NO_INTERNET),
+ last_error_html());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Test case where one error page loads completely before a new navigation
+// results in another error page. Probe results for the first probe are only
+// received after the second load starts, but before it commits.
+TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbesAfterSecondStarts) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // The process starts again.
+
+ // Normal page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page starts to load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+
+ // Probe results come in, and the first page is updated.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // Second page finishes loading, and is updated using the same probe result.
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(3, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // Other probe results should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_EQ(3, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// Same as above, but a new page is loaded before the error page commits.
+TEST_F(NetErrorHelperCoreTest, ErrorPageLoadInterrupted) {
+ // Original page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and an error page is requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ // Probe statuses come in, but should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+
+ // A new navigation begins while the error page is loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // And fails.
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ // Should have returned a local error page indicating a probe may run.
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
+
+ // Error page finishes loading.
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Probe results come in.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NO_INTERNET),
+ last_error_html());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+//------------------------------------------------------------------------------
+// Navigation correction tests.
+//------------------------------------------------------------------------------
+
+// Check that corrections are not used for HTTPS URLs.
+TEST_F(NetErrorHelperCoreTest, NoCorrectionsForHttps) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // The HTTPS page fails to load.
+ std::string html;
+ auto error =
+ NetErrorForURL(net::ERR_NAME_NOT_RESOLVED, GURL(kFailedHttpsUrl));
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME, error,
+ false /* is_failed_post */, &html);
+
+ auto probe_error =
+ ProbeErrorForURL(error_page::DNS_PROBE_POSSIBLE, GURL(kFailedHttpsUrl));
+ EXPECT_EQ(ErrorToString(probe_error, false), html);
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // The blank page loads, no error page is loaded.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Page is updated in response to DNS probes as normal.
+ EXPECT_EQ(0, update_count());
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_FALSE(last_error_page_params());
+ auto final_probe_error = ProbeErrorForURL(
+ error_page::DNS_PROBE_FINISHED_NXDOMAIN, GURL(kFailedHttpsUrl));
+ EXPECT_EQ(ErrorToString(final_probe_error, false), last_error_html());
+}
+
+// The blank page loads, then the navigation corrections request succeeds and is
+// loaded. Then the probe results come in.
+TEST_F(NetErrorHelperCoreTest, CorrectionsReceivedBeforeProbe) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+
+ // Corrections retrieval starts when the error page finishes loading.
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are retrieved.
+ NavigationCorrectionsLoadSuccess(kDefaultCorrections,
+ base::size(kDefaultCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ ExpectDefaultNavigationCorrections();
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Any probe statuses should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(1, error_html_update_count());
+}
+
+// The blank page finishes loading, then probe results come in, and then
+// the navigation corrections request succeeds.
+TEST_F(NetErrorHelperCoreTest, CorrectionsRetrievedAfterProbes) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Probe statuses should be ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are retrieved.
+ EXPECT_TRUE(is_url_being_fetched());
+ NavigationCorrectionsLoadSuccess(kDefaultCorrections,
+ base::size(kDefaultCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ ExpectDefaultNavigationCorrections();
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(0, update_count());
+}
+
+// The corrections request fails and then the error page loads for an error that
+// does not trigger DNS probes.
+TEST_F(NetErrorHelperCoreTest, CorrectionsFailLoadNoProbes) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_FAILED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Corrections request fails, final error page is shown.
+ EXPECT_TRUE(is_url_being_fetched());
+ NavigationCorrectionsLoadFailure();
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(last_error_html(), NetErrorString(net::ERR_CONNECTION_FAILED));
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // If probe statuses come in last from another page load, they should be
+ // ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(1, error_html_update_count());
+}
+
+// The corrections request fails and then the error page loads before probe
+// results are received.
+TEST_F(NetErrorHelperCoreTest, CorrectionsFailLoadBeforeProbe) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Corrections request fails, probe pending page shown.
+ EXPECT_TRUE(is_url_being_fetched());
+ NavigationCorrectionsLoadFailure();
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(last_error_html(),
+ ProbeErrorString(error_page::DNS_PROBE_POSSIBLE));
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+
+ // Probe page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Probe statuses comes in, and page is updated.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+
+ // The commit results in sending a second probe status, which is ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(1, error_html_update_count());
+}
+
+// The corrections request fails after receiving probe results.
+TEST_F(NetErrorHelperCoreTest, CorrectionsFailAfterProbe) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Results come in, but end up being ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+
+ // Corrections request fails, probe pending page shown.
+ EXPECT_TRUE(is_url_being_fetched());
+ NavigationCorrectionsLoadFailure();
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(last_error_html(),
+ ProbeErrorString(error_page::DNS_PROBE_POSSIBLE));
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+
+ // Probe page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Probe statuses comes in, and page is updated.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(1, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+ EXPECT_EQ(1, error_html_update_count());
+}
+
+// An error page load that would normally load correction is interrupted
+// by a new navigation before the blank page commits.
+TEST_F(NetErrorHelperCoreTest, CorrectionsInterruptedBeforeCommit) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page starts loading.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+
+ // A new page load starts.
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // A new page load interrupts the original load.
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// An error page load that would normally load corrections is interrupted
+// by a new navigation before the blank page finishes loading.
+TEST_F(NetErrorHelperCoreTest, CorrectionsInterruptedBeforeLoad) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page starts loading and is committed.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+
+ // A new page load interrupts the original load.
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(0, error_html_update_count());
+}
+
+// The corrections request is cancelled due to a new navigation. The new
+// navigation fails and then loads corrections successfully.
+TEST_F(NetErrorHelperCoreTest, CorrectionsInterrupted) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+
+ // Results come in, but end up being ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+
+ // A new load appears!
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // It fails, and corrections are requested again once a blank page is loaded.
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ EXPECT_FALSE(is_url_being_fetched());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+
+ // Corrections request succeeds.
+ NavigationCorrectionsLoadSuccess(kDefaultCorrections,
+ base::size(kDefaultCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ ExpectDefaultNavigationCorrections();
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Probe statuses come in, and are ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+}
+
+// The corrections request is cancelled due to call to Stop(). The cross
+// process navigation is cancelled, and then a new load fails and tries to load
+// corrections again, unsuccessfully.
+TEST_F(NetErrorHelperCoreTest, CorrectionsStopped) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ EXPECT_TRUE(is_url_being_fetched());
+ core()->OnStop();
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Results come in, but end up being ignored.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, update_count());
+
+ // Cross process navigation must have been cancelled, and a new load appears!
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested again.
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads again.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+
+ // Corrections request fails, probe pending page shown.
+ NavigationCorrectionsLoadFailure();
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(last_error_html(),
+ ProbeErrorString(error_page::DNS_PROBE_POSSIBLE));
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Probe page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Probe statuses comes in, and page is updated.
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_STARTED), last_error_html());
+ EXPECT_EQ(1, update_count());
+
+ core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(2, update_count());
+ EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_FINISHED_NXDOMAIN),
+ last_error_html());
+ EXPECT_EQ(1, error_html_update_count());
+}
+
+// Check the case corrections are disabled while the blank page (Loaded
+// before the corrections page) is being loaded.
+TEST_F(NetErrorHelperCoreTest, CorrectionsDisabledBeforeFetch) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ // Corrections is disabled.
+ DisableNavigationCorrections();
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are retrieved.
+ NavigationCorrectionsLoadSuccess(kDefaultCorrections,
+ base::size(kDefaultCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ EXPECT_FALSE(is_url_being_fetched());
+ ExpectDefaultNavigationCorrections();
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(0, update_count());
+}
+
+// Check the case corrections is disabled while fetching the corrections for
+// a failed page load.
+TEST_F(NetErrorHelperCoreTest, CorrectionsDisabledDuringFetch) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are disabled.
+ DisableNavigationCorrections();
+
+ // Corrections are retrieved.
+ NavigationCorrectionsLoadSuccess(kDefaultCorrections,
+ base::size(kDefaultCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ EXPECT_FALSE(is_url_being_fetched());
+ ExpectDefaultNavigationCorrections();
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(0, update_count());
+}
+
+// Checks corrections are is used when there are no search suggestions.
+TEST_F(NetErrorHelperCoreTest, CorrectionsWithoutSearch) {
+ const NavigationCorrection kCorrections[] = {
+ {"urlCorrection", "http://somewhere_else/", "btn", "data", false, false},
+ };
+
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are retrieved.
+ NavigationCorrectionsLoadSuccess(kCorrections, base::size(kCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Check params.
+ ASSERT_TRUE(last_error_page_params());
+ EXPECT_FALSE(last_error_page_params()->suggest_reload);
+ EXPECT_EQ(1u, last_error_page_params()->override_suggestions->GetSize());
+ EXPECT_FALSE(last_error_page_params()->search_url.is_valid());
+ EXPECT_EQ("", last_error_page_params()->search_terms);
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(0, update_count());
+}
+
+// Checks corrections are used when there are only search suggestions.
+TEST_F(NetErrorHelperCoreTest, CorrectionsOnlySearchSuggestion) {
+ const NavigationCorrection kCorrections[] = {
+ {"webSearchQuery", kSuggestedSearchTerms, "frm", "data", false, false},
+ };
+
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are retrieved.
+ NavigationCorrectionsLoadSuccess(kCorrections, base::size(kCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Check params.
+ ASSERT_TRUE(last_error_page_params());
+ EXPECT_FALSE(last_error_page_params()->suggest_reload);
+ EXPECT_EQ(0u, last_error_page_params()->override_suggestions->GetSize());
+ EXPECT_EQ(GURL(kSearchUrl), last_error_page_params()->search_url);
+ EXPECT_EQ(kSuggestedSearchTerms, last_error_page_params()->search_terms);
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(0, update_count());
+}
+
+// The correction service returns a non-JSON result.
+TEST_F(NetErrorHelperCoreTest, CorrectionServiceReturnsNonJsonResult) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_FAILED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Corrections request fails, final error page is shown.
+ EXPECT_TRUE(is_url_being_fetched());
+ NavigationCorrectionsLoadFinished("Weird Response");
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(last_error_html(), NetErrorString(net::ERR_CONNECTION_FAILED));
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+}
+
+// The correction service returns a JSON result that isn't a valid list of
+// corrections.
+TEST_F(NetErrorHelperCoreTest, CorrectionServiceReturnsInvalidJsonResult) {
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails, and corrections are requested.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_FAILED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ // Corrections request fails, final error page is shown.
+ EXPECT_TRUE(is_url_being_fetched());
+ NavigationCorrectionsLoadFinished("{\"result\": 42}");
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(last_error_html(), NetErrorString(net::ERR_CONNECTION_FAILED));
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_EQ(0, update_count());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Error page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+}
+
+TEST_F(NetErrorHelperCoreTest, CorrectionClickTracking) {
+ // Go through the standard navigation correction steps.
+
+ // Original page starts loading.
+ EnableNavigationCorrections();
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+
+ // It fails.
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_NAME_NOT_RESOLVED),
+ false /* is_failed_post */, &html);
+ EXPECT_TRUE(html.empty());
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // The blank page loads.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+
+ // Corrections retrieval starts when the error page finishes loading.
+ EXPECT_FALSE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(is_url_being_fetched());
+ EXPECT_FALSE(last_error_page_params());
+
+ // Corrections are retrieved.
+ NavigationCorrectionsLoadSuccess(kDefaultCorrections,
+ base::size(kDefaultCorrections));
+ EXPECT_EQ(1, error_html_update_count());
+ EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html());
+ ExpectDefaultNavigationCorrections();
+ EXPECT_FALSE(is_url_being_fetched());
+
+ // Corrections load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+
+ EXPECT_EQ(0, tracking_request_count());
+
+ // Invalid clicks should be ignored.
+ core()->TrackClick(-1);
+ core()->TrackClick(base::size(kDefaultCorrections));
+ EXPECT_EQ(0, tracking_request_count());
+
+ for (size_t i = 0; i < base::size(kDefaultCorrections); ++i) {
+ // Skip links that do not appear in the page.
+ if (kDefaultCorrections[i].is_porn || kDefaultCorrections[i].is_soft_porn)
+ continue;
+
+ int old_tracking_request_count = tracking_request_count();
+ core()->TrackClick(i);
+ EXPECT_EQ(old_tracking_request_count + 1, tracking_request_count());
+ EXPECT_EQ(GURL(kNavigationCorrectionUrl), last_tracking_url());
+
+ // Make sure all expected strings appear in output.
+ EXPECT_TRUE(last_tracking_request_body().find(
+ kDefaultCorrections[i].url_correction));
+ EXPECT_TRUE(
+ last_tracking_request_body().find(kDefaultCorrections[i].click_data));
+ EXPECT_TRUE(
+ last_tracking_request_body().find(kDefaultCorrections[i].click_type));
+ EXPECT_TRUE(
+ last_tracking_request_body().find(kNavigationCorrectionEventId));
+ EXPECT_TRUE(
+ last_tracking_request_body().find(kNavigationCorrectionFingerprint));
+ }
+
+ // Make sure duplicate tracking requests are ignored.
+ for (size_t i = 0; i < base::size(kDefaultCorrections); ++i) {
+ // Skip links that do not appear in the page.
+ if (kDefaultCorrections[i].is_porn || kDefaultCorrections[i].is_soft_porn)
+ continue;
+
+ int old_tracking_request_count = tracking_request_count();
+ core()->TrackClick(i);
+ EXPECT_EQ(old_tracking_request_count, tracking_request_count());
+ }
+
+ EXPECT_EQ(0, update_count());
+ EXPECT_EQ(1, error_html_update_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, AutoReloadDisabled) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+
+ EXPECT_FALSE(timer()->IsRunning());
+ EXPECT_EQ(0, reload_count());
+}
+
+class NetErrorHelperCoreAutoReloadTest : public NetErrorHelperCoreTest {
+ public:
+ void SetUp() override {
+ NetErrorHelperCoreTest::SetUp();
+ SetUpCore(true, true);
+ }
+};
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, Succeeds) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+
+ EXPECT_TRUE(timer()->IsRunning());
+ EXPECT_EQ(0, reload_count());
+
+ timer()->Fire();
+ EXPECT_FALSE(timer()->IsRunning());
+ EXPECT_EQ(1, reload_count());
+
+ DoSuccessLoad();
+
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, Retries) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+
+ EXPECT_TRUE(timer()->IsRunning());
+ base::TimeDelta first_delay = timer()->GetCurrentDelay();
+ EXPECT_EQ(0, reload_count());
+
+ timer()->Fire();
+ EXPECT_FALSE(timer()->IsRunning());
+ EXPECT_EQ(1, reload_count());
+
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+
+ EXPECT_TRUE(timer()->IsRunning());
+ EXPECT_GT(timer()->GetCurrentDelay(), first_delay);
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, StopsTimerOnStop) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(timer()->IsRunning());
+ core()->OnStop();
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, StopsLoadingOnStop) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_EQ(0, core()->auto_reload_count());
+ timer()->Fire();
+ EXPECT_EQ(1, core()->auto_reload_count());
+ EXPECT_EQ(1, reload_count());
+ core()->OnStop();
+ EXPECT_FALSE(timer()->IsRunning());
+ EXPECT_EQ(0, core()->auto_reload_count());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, StopsOnOtherLoadStart) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(timer()->IsRunning());
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ EXPECT_FALSE(timer()->IsRunning());
+ EXPECT_EQ(0, core()->auto_reload_count());
+}
+
+// This is a regression test for http://crbug.com/881208.
+TEST_F(NetErrorHelperCoreAutoReloadTest, StopsOnErrorLoadCommit) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(timer()->IsRunning());
+
+ // Simulate manually reloading the error page while the timer is still
+ // running.
+ std::string html;
+ core()->PrepareErrorPage(
+ NetErrorHelperCore::MAIN_FRAME,
+ NetErrorForURL(net::ERR_CONNECTION_RESET, GURL(kFailedUrl)),
+ false /* is_failed_post */, &html);
+
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, ResetsCountOnSuccess) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ base::TimeDelta delay = timer()->GetCurrentDelay();
+ EXPECT_EQ(0, core()->auto_reload_count());
+ timer()->Fire();
+ EXPECT_EQ(1, core()->auto_reload_count());
+ EXPECT_EQ(1, reload_count());
+ DoSuccessLoad();
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_EQ(0, core()->auto_reload_count());
+ EXPECT_EQ(timer()->GetCurrentDelay(), delay);
+ timer()->Fire();
+ EXPECT_EQ(1, core()->auto_reload_count());
+ EXPECT_EQ(2, reload_count());
+ DoSuccessLoad();
+ EXPECT_EQ(0, core()->auto_reload_count());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, RestartsOnOnline) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ base::TimeDelta delay = timer()->GetCurrentDelay();
+ timer()->Fire();
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(timer()->IsRunning());
+ EXPECT_NE(delay, timer()->GetCurrentDelay());
+ core()->NetworkStateChanged(false);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_TRUE(timer()->IsRunning());
+ EXPECT_EQ(delay, timer()->GetCurrentDelay());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotStartOnOnline) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ timer()->Fire();
+ DoSuccessLoad();
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotStartOffline) {
+ core()->NetworkStateChanged(false);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotRestartOnOnlineAfterStop) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ timer()->Fire();
+ core()->OnStop();
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, WithDnsProbes) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ DoDnsProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+ timer()->Fire();
+ EXPECT_EQ(1, reload_count());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, ExponentialBackoffLevelsOff) {
+ base::TimeDelta previous = base::TimeDelta::FromMilliseconds(0);
+ const int kMaxTries = 50;
+ int tries = 0;
+ for (tries = 0; tries < kMaxTries; tries++) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(timer()->IsRunning());
+ if (previous == timer()->GetCurrentDelay())
+ break;
+ previous = timer()->GetCurrentDelay();
+ timer()->Fire();
+ }
+
+ EXPECT_LT(tries, kMaxTries);
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, SlowError) {
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ EXPECT_FALSE(timer()->IsRunning());
+ // Start a new non-error page load.
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ EXPECT_FALSE(timer()->IsRunning());
+ // Finish the error page load.
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, OnlineSlowError) {
+ core()->NetworkStateChanged(false);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(false);
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, OnlinePendingError) {
+ core()->NetworkStateChanged(false);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(false);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, OnlinePartialErrorReplacement) {
+ core()->NetworkStateChanged(false);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ std::string html;
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
+ core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::NON_ERROR_PAGE);
+ core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ NetError(net::ERR_CONNECTION_RESET),
+ false /* is_failed_post */, &html);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, ShouldSuppressNonReloadableErrorPage) {
+ DoErrorLoad(net::ERR_ABORTED);
+ EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ GURL(kFailedUrl)));
+
+ DoErrorLoad(net::ERR_UNKNOWN_URL_SCHEME);
+ EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ GURL(kFailedUrl)));
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotReload) {
+ DoErrorLoad(net::ERR_ABORTED);
+ EXPECT_FALSE(timer()->IsRunning());
+
+ DoErrorLoad(net::ERR_UNKNOWN_URL_SCHEME);
+ EXPECT_FALSE(timer()->IsRunning());
+
+ DoErrorLoad(net::ERR_SSL_PROTOCOL_ERROR);
+ EXPECT_FALSE(timer()->IsRunning());
+
+ DoErrorLoad(net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ EXPECT_FALSE(timer()->IsRunning());
+
+ DoErrorLoad(net::ERR_BAD_SSL_CLIENT_AUTH_CERT);
+ EXPECT_FALSE(timer()->IsRunning());
+
+ DoErrorLoadOfURL(net::ERR_ACCESS_DENIED, GURL("data://some-data-here"));
+ EXPECT_FALSE(timer()->IsRunning());
+
+ DoErrorLoadOfURL(net::ERR_ACCESS_DENIED, GURL("chrome-extension://foo"));
+ EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, ShouldSuppressErrorPage) {
+ // Set up the environment to test ShouldSuppressErrorPage: auto-reload is
+ // enabled, an error page is loaded, and the auto-reload callback is running.
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ timer()->Fire();
+
+ // Sub-frame load.
+ EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::SUB_FRAME,
+ GURL(kFailedUrl)));
+ EXPECT_TRUE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ GURL(kFailedUrl)));
+ // No auto-reload attempt in flight.
+ EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ GURL(kFailedUrl)));
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, HiddenAndShown) {
+ SetUpCore(true, true);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(timer()->IsRunning());
+ core()->OnWasHidden();
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->OnWasShown();
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, HiddenWhileOnline) {
+ SetUpCore(true, true);
+ core()->NetworkStateChanged(false);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->OnWasHidden();
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(false);
+ core()->OnWasShown();
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_TRUE(timer()->IsRunning());
+ core()->NetworkStateChanged(false);
+ core()->OnWasHidden();
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->NetworkStateChanged(true);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->OnWasShown();
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, ShownWhileNotReloading) {
+ SetUpCore(true, false);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_FALSE(timer()->IsRunning());
+ core()->OnWasShown();
+ EXPECT_TRUE(timer()->IsRunning());
+}
+
+TEST_F(NetErrorHelperCoreAutoReloadTest, ManualReloadShowsError) {
+ SetUpCore(true, true);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
+ NetErrorHelperCore::ERROR_PAGE);
+ EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
+ GURL(kFailedUrl)));
+}
+
+TEST_F(NetErrorHelperCoreTest, ExplicitReloadSucceeds) {
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_EQ(0, reload_count());
+ core()->ExecuteButtonPress(NetErrorHelperCore::RELOAD_BUTTON);
+ EXPECT_EQ(1, reload_count());
+}
+
+TEST_F(NetErrorHelperCoreTest, CanNotShowNetworkDiagnostics) {
+ core()->OnSetCanShowNetworkDiagnosticsDialog(false);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_FALSE(last_can_show_network_diagnostics_dialog());
+}
+
+TEST_F(NetErrorHelperCoreTest, CanShowNetworkDiagnostics) {
+ core()->OnSetCanShowNetworkDiagnosticsDialog(true);
+ DoErrorLoad(net::ERR_CONNECTION_RESET);
+ EXPECT_TRUE(last_can_show_network_diagnostics_dialog());
+
+ core()->ExecuteButtonPress(NetErrorHelperCore::DIAGNOSE_ERROR);
+ EXPECT_EQ(1, diagnose_error_count());
+ EXPECT_EQ(GURL(kFailedUrl), diagnose_error_url());
+}
+
+#if defined(OS_ANDROID)
+TEST_F(NetErrorHelperCoreTest, Download) {
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ EXPECT_EQ(0, download_count());
+ core()->ExecuteButtonPress(NetErrorHelperCore::DOWNLOAD_BUTTON);
+ EXPECT_EQ(1, download_count());
+}
+
+const char kThumbnailDataURI[] = "data:image/png;base64,abc";
+const char kFaviconDataURI[] = "data:image/png;base64,def";
+
+// Creates a couple of fake AvailableOfflineContent instances.
+std::vector<chrome::mojom::AvailableOfflineContentPtr>
+GetFakeAvailableContent() {
+ std::vector<chrome::mojom::AvailableOfflineContentPtr> content;
+ content.push_back(chrome::mojom::AvailableOfflineContent::New(
+ "ID", "name_space", "title", "snippet", "date_modified", "attribution",
+ GURL(kThumbnailDataURI), GURL(kFaviconDataURI),
+ chrome::mojom::AvailableContentType::kPrefetchedPage));
+ content.push_back(chrome::mojom::AvailableOfflineContent::New(
+ "ID2", "name_space2", "title2", "snippet2", "date_modified2",
+ "attribution2", GURL(kThumbnailDataURI), GURL(kFaviconDataURI),
+ chrome::mojom::AvailableContentType::kOtherPage));
+ return content;
+}
+
+// Builds the expected JSON representation of the AvailableOfflineContent
+// instances returned by |GetFakeAvailableContent|.
+const std::string GetExpectedAvailableContentAsJson() {
+ // About the below data:
+ // * |content_type| is an AvailableContentType enum value where
+ // 0 = kPrefetchedPage and 3=kOtherPage.
+ // * The base64 encoded values represent the encoded versions of the
+ // respective entries returned by |GetFakeAvailableContent|.
+ std::string want_json = R"([
+ {
+ "ID": "ID",
+ "attribution_base64": "AGEAdAB0AHIAaQBiAHUAdABpAG8Abg==",
+ "content_type": 0,
+ "date_modified": "date_modified",
+ "favicon_data_uri": "data:image/png;base64,def",
+ "name_space": "name_space",
+ "snippet_base64": "AHMAbgBpAHAAcABlAHQ=",
+ "thumbnail_data_uri": "data:image/png;base64,abc",
+ "title_base64": "AHQAaQB0AGwAZQ=="
+ },
+ {
+ "ID": "ID2",
+ "attribution_base64": "AGEAdAB0AHIAaQBiAHUAdABpAG8AbgAy",
+ "content_type": 3,
+ "date_modified": "date_modified2",
+ "favicon_data_uri": "data:image/png;base64,def",
+ "name_space": "name_space2",
+ "snippet_base64": "AHMAbgBpAHAAcABlAHQAMg==",
+ "thumbnail_data_uri": "data:image/png;base64,abc",
+ "title_base64": "AHQAaQB0AGwAZQAy"
+ }
+ ])";
+ base::ReplaceChars(want_json, base::kWhitespaceASCII, "", &want_json);
+ return want_json;
+}
+
+class FakeAvailableOfflineContentProvider
+ : public chrome::mojom::AvailableOfflineContentProvider {
+ public:
+ FakeAvailableOfflineContentProvider() = default;
+
+ void List(ListCallback callback) override {
+ if (return_content_) {
+ std::move(callback).Run(list_visible_by_prefs_,
+ GetFakeAvailableContent());
+ } else {
+ std::move(callback).Run(list_visible_by_prefs_, {});
+ }
+ }
+
+ MOCK_METHOD2(LaunchItem,
+ void(const std::string& item_ID, const std::string& name_space));
+ MOCK_METHOD1(LaunchDownloadsPage, void(bool open_prefetched_articles_tab));
+ MOCK_METHOD1(ListVisibilityChanged, void(bool is_visible));
+
+ void AddBinding(
+ mojo::PendingReceiver<chrome::mojom::AvailableOfflineContentProvider>
+ receiver) {
+ receivers_.Add(this, std::move(receiver));
+ }
+
+ void set_return_content(bool return_content) {
+ return_content_ = return_content;
+ }
+
+ void set_list_visible_by_prefs(bool list_visible_by_prefs) {
+ list_visible_by_prefs_ = list_visible_by_prefs;
+ }
+
+ private:
+ bool return_content_ = true;
+ bool list_visible_by_prefs_ = true;
+ mojo::ReceiverSet<chrome::mojom::AvailableOfflineContentProvider> receivers_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeAvailableOfflineContentProvider);
+};
+
+// Provides set up for testing the 'available offline content' feature.
+class NetErrorHelperCoreAvailableOfflineContentTest
+ : public NetErrorHelperCoreTest {
+ public:
+ void SetUp() override {
+ NetErrorHelperCoreTest::SetUp();
+ AvailableOfflineContentHelper::OverrideBinderForTesting(
+ base::BindRepeating(&FakeAvailableOfflineContentProvider::AddBinding,
+ base::Unretained(&fake_provider_)));
+ }
+
+ void TearDown() override {
+ AvailableOfflineContentHelper::OverrideBinderForTesting(
+ base::NullCallback());
+ }
+
+ protected:
+ FakeAvailableOfflineContentProvider fake_provider_;
+ base::HistogramTester histogram_tester_;
+};
+
+TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, ListAvailableContent) {
+ set_offline_content_feature_enabled(true);
+ fake_provider_.set_return_content(true);
+
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ task_environment()->RunUntilIdle();
+ EXPECT_TRUE(list_visible_by_prefs());
+ EXPECT_EQ(GetExpectedAvailableContentAsJson(), offline_content_json());
+
+ histogram_tester_.ExpectTotalCount("Net.ErrorPageCounts.SuggestionPresented",
+ 2);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts.SuggestionPresented",
+ chrome::mojom::AvailableContentType::kPrefetchedPage, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts.SuggestionPresented",
+ chrome::mojom::AvailableContentType::kOtherPage, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 0);
+
+ core()->LaunchOfflineItem("ID", "name_space");
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts.SuggestionPresented",
+ chrome::mojom::AvailableContentType::kPrefetchedPage, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTION_CLICKED, 1);
+
+ core()->LaunchDownloadsPage();
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_DOWNLOADS_PAGE_CLICKED, 1);
+}
+
+TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, ListHiddenByPrefs) {
+ set_offline_content_feature_enabled(true);
+ fake_provider_.set_return_content(true);
+ fake_provider_.set_list_visible_by_prefs(false);
+
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ task_environment()->RunUntilIdle();
+ EXPECT_FALSE(list_visible_by_prefs());
+ EXPECT_EQ(GetExpectedAvailableContentAsJson(), offline_content_json());
+
+ histogram_tester_.ExpectTotalCount("Net.ErrorPageCounts.SuggestionPresented",
+ 2);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts.SuggestionPresented",
+ chrome::mojom::AvailableContentType::kPrefetchedPage, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts.SuggestionPresented",
+ chrome::mojom::AvailableContentType::kOtherPage, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 0);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 1);
+
+ core()->LaunchOfflineItem("ID", "name_space");
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts.SuggestionPresented",
+ chrome::mojom::AvailableContentType::kPrefetchedPage, 1);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTION_CLICKED, 1);
+
+ core()->LaunchDownloadsPage();
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_DOWNLOADS_PAGE_CLICKED, 1);
+}
+
+TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, ListNoAvailableContent) {
+ set_offline_content_feature_enabled(true);
+ fake_provider_.set_return_content(false);
+
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ task_environment()->RunUntilIdle();
+
+ EXPECT_TRUE(list_visible_by_prefs());
+ EXPECT_EQ("", offline_content_json());
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 0);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 0);
+}
+
+TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, NotAllowed) {
+ set_offline_content_feature_enabled(false);
+ fake_provider_.set_return_content(true);
+
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ task_environment()->RunUntilIdle();
+
+ EXPECT_TRUE(list_visible_by_prefs());
+ EXPECT_EQ("", offline_content_json());
+ histogram_tester_.ExpectTotalCount("Net.ErrorPageCounts.SuggestionPresented",
+ 0);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 0);
+ histogram_tester_.ExpectBucketCount(
+ "Net.ErrorPageCounts",
+ error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 0);
+}
+
+class FakeOfflinePageAutoFetcher
+ : public chrome::mojom::OfflinePageAutoFetcher {
+ public:
+ FakeOfflinePageAutoFetcher() = default;
+
+ struct TryScheduleParameters {
+ bool user_requested;
+ TryScheduleCallback callback;
+ };
+
+ void TrySchedule(bool user_requested, TryScheduleCallback callback) override {
+ try_schedule_calls_.push_back({user_requested, std::move(callback)});
+ }
+
+ void CancelSchedule() override { cancel_calls_++; }
+
+ void AddReceiver(
+ mojo::PendingReceiver<chrome::mojom::OfflinePageAutoFetcher> receiver) {
+ receivers_.Add(this, std::move(receiver));
+ }
+
+ int cancel_calls() const { return cancel_calls_; }
+ std::vector<TryScheduleParameters> take_try_schedule_calls() {
+ std::vector<TryScheduleParameters> result = std::move(try_schedule_calls_);
+ try_schedule_calls_.clear();
+ return result;
+ }
+
+ private:
+ mojo::ReceiverSet<chrome::mojom::OfflinePageAutoFetcher> receivers_;
+ int cancel_calls_ = 0;
+ std::vector<TryScheduleParameters> try_schedule_calls_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeOfflinePageAutoFetcher);
+};
+// This uses the real implementation of PageAutoFetcherHelper, but with a
+// substituted fetcher.
+class TestPageAutoFetcherHelper : public PageAutoFetcherHelper {
+ public:
+ explicit TestPageAutoFetcherHelper(
+ base::RepeatingCallback<
+ mojo::PendingRemote<chrome::mojom::OfflinePageAutoFetcher>()> binder)
+ : PageAutoFetcherHelper(nullptr), binder_(binder) {}
+ bool Bind() override {
+ if (!fetcher_)
+ fetcher_.Bind(binder_.Run());
+ return true;
+ }
+
+ private:
+ base::RepeatingCallback<
+ mojo::PendingRemote<chrome::mojom::OfflinePageAutoFetcher>()>
+ binder_;
+};
+
+// Provides set up for testing the 'auto fetch on dino' feature.
+class NetErrorHelperCoreAutoFetchTest : public NetErrorHelperCoreTest {
+ public:
+ void SetUp() override {
+ NetErrorHelperCoreTest::SetUp();
+ auto binder = base::BindLambdaForTesting([&]() {
+ mojo::PendingRemote<chrome::mojom::OfflinePageAutoFetcher> fetcher_remote;
+ fake_fetcher_.AddReceiver(
+ fetcher_remote.InitWithNewPipeAndPassReceiver());
+ return fetcher_remote;
+ });
+
+ core()->SetPageAutoFetcherHelperForTesting(
+ std::make_unique<TestPageAutoFetcherHelper>(binder));
+ }
+
+ void TearDown() override {
+ AvailableOfflineContentHelper::OverrideBinderForTesting(
+ base::NullCallback());
+ }
+
+ protected:
+ FakeOfflinePageAutoFetcher fake_fetcher_;
+};
+
+TEST_F(NetErrorHelperCoreAutoFetchTest, NotAllowed) {
+ set_auto_fetch_allowed(false);
+
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ task_environment()->RunUntilIdle();
+
+ // When auto fetch is not allowed, OfflinePageAutoFetcher is not called.
+ std::vector<FakeOfflinePageAutoFetcher::TryScheduleParameters> calls =
+ fake_fetcher_.take_try_schedule_calls();
+ EXPECT_EQ(0ul, calls.size());
+}
+
+TEST_F(NetErrorHelperCoreAutoFetchTest, AutoFetchTriggered) {
+ set_auto_fetch_allowed(true);
+
+ DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
+ task_environment()->RunUntilIdle();
+
+ // Auto fetch is allowed, so OfflinePageAutoFetcher is called once.
+ std::vector<FakeOfflinePageAutoFetcher::TryScheduleParameters> calls =
+ fake_fetcher_.take_try_schedule_calls();
+ EXPECT_EQ(1ul, calls.size());
+
+ // Finalize the call to TrySchedule, and verify the delegate is called.
+ std::move(calls[0].callback)
+ .Run(chrome::mojom::OfflinePageAutoFetcherScheduleResult::kScheduled);
+ task_environment()->RunUntilIdle();
+
+ EXPECT_EQ(chrome::mojom::OfflinePageAutoFetcherScheduleResult::kScheduled,
+ auto_fetch_state());
+}
+
+#endif // defined(OS_ANDROID)
+
+} // namespace
diff --git a/chromium/chrome/renderer/net/net_error_page_controller.cc b/chromium/chrome/renderer/net/net_error_page_controller.cc
new file mode 100644
index 00000000000..161ee674854
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_page_controller.cc
@@ -0,0 +1,162 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net/net_error_page_controller.h"
+
+#include "base/strings/string_piece.h"
+#include "content/public/renderer/render_frame.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+gin::WrapperInfo NetErrorPageController::kWrapperInfo = {
+ gin::kEmbedderNativeGin};
+
+NetErrorPageController::Delegate::Delegate() {}
+NetErrorPageController::Delegate::~Delegate() {}
+
+// static
+void NetErrorPageController::Install(content::RenderFrame* render_frame,
+ base::WeakPtr<Delegate> delegate) {
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ render_frame->GetWebFrame()->MainWorldScriptContext();
+ if (context.IsEmpty())
+ return;
+
+ v8::Context::Scope context_scope(context);
+
+ gin::Handle<NetErrorPageController> controller = gin::CreateHandle(
+ isolate, new NetErrorPageController(delegate));
+ if (controller.IsEmpty())
+ return;
+
+ v8::Local<v8::Object> global = context->Global();
+ global
+ ->Set(context, gin::StringToV8(isolate, "errorPageController"),
+ controller.ToV8())
+ .ToChecked();
+}
+
+bool NetErrorPageController::DownloadButtonClick() {
+ return ButtonClick(NetErrorHelperCore::DOWNLOAD_BUTTON);
+}
+
+bool NetErrorPageController::ReloadButtonClick() {
+ return ButtonClick(NetErrorHelperCore::RELOAD_BUTTON);
+}
+
+bool NetErrorPageController::DetailsButtonClick() {
+ return ButtonClick(NetErrorHelperCore::MORE_BUTTON);
+}
+
+bool NetErrorPageController::TrackEasterEgg() {
+ return ButtonClick(NetErrorHelperCore::EASTER_EGG);
+}
+
+bool NetErrorPageController::UpdateEasterEggHighScore(int high_score) {
+ if (delegate_)
+ delegate_->UpdateEasterEggHighScore(high_score);
+ return true;
+}
+
+bool NetErrorPageController::ResetEasterEggHighScore() {
+ if (delegate_)
+ delegate_->ResetEasterEggHighScore();
+ return true;
+}
+
+bool NetErrorPageController::DiagnoseErrorsButtonClick() {
+ return ButtonClick(NetErrorHelperCore::DIAGNOSE_ERROR);
+}
+
+bool NetErrorPageController::TrackCachedCopyButtonClick() {
+ return ButtonClick(NetErrorHelperCore::SHOW_CACHED_COPY_BUTTON);
+}
+
+bool NetErrorPageController::TrackClick(const gin::Arguments& args) {
+ if (args.PeekNext().IsEmpty() || !args.PeekNext()->IsInt32())
+ return false;
+
+ if (delegate_) {
+ delegate_->TrackClick(args.PeekNext()
+ ->Int32Value(args.GetHolderCreationContext())
+ .FromMaybe(0));
+ }
+ return true;
+}
+
+bool NetErrorPageController::ButtonClick(NetErrorHelperCore::Button button) {
+ if (delegate_)
+ delegate_->ButtonPressed(button);
+
+ return true;
+}
+
+void NetErrorPageController::LaunchOfflineItem(gin::Arguments* args) {
+ if (!delegate_)
+ return;
+ std::string id;
+ std::string name_space;
+ if (args->GetNext(&id) && args->GetNext(&name_space))
+ delegate_->LaunchOfflineItem(id, name_space);
+}
+
+void NetErrorPageController::LaunchDownloadsPage() {
+ if (delegate_)
+ delegate_->LaunchDownloadsPage();
+}
+
+void NetErrorPageController::SavePageForLater() {
+ if (delegate_)
+ delegate_->SavePageForLater();
+}
+
+void NetErrorPageController::CancelSavePage() {
+ if (delegate_)
+ delegate_->CancelSavePage();
+}
+
+void NetErrorPageController::ListVisibilityChanged(bool is_visible) {
+ if (delegate_)
+ delegate_->ListVisibilityChanged(is_visible);
+}
+
+NetErrorPageController::NetErrorPageController(base::WeakPtr<Delegate> delegate)
+ : delegate_(delegate) {
+}
+
+NetErrorPageController::~NetErrorPageController() {}
+
+gin::ObjectTemplateBuilder NetErrorPageController::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return gin::Wrappable<NetErrorPageController>::GetObjectTemplateBuilder(
+ isolate)
+ .SetMethod("downloadButtonClick",
+ &NetErrorPageController::DownloadButtonClick)
+ .SetMethod("reloadButtonClick",
+ &NetErrorPageController::ReloadButtonClick)
+ .SetMethod("detailsButtonClick",
+ &NetErrorPageController::DetailsButtonClick)
+ .SetMethod("diagnoseErrorsButtonClick",
+ &NetErrorPageController::DiagnoseErrorsButtonClick)
+ .SetMethod("trackClick", &NetErrorPageController::TrackClick)
+ .SetMethod("trackEasterEgg", &NetErrorPageController::TrackEasterEgg)
+ .SetMethod("updateEasterEggHighScore",
+ &NetErrorPageController::UpdateEasterEggHighScore)
+ .SetMethod("resetEasterEggHighScore",
+ &NetErrorPageController::ResetEasterEggHighScore)
+ .SetMethod("trackCachedCopyButtonClick",
+ &NetErrorPageController::TrackCachedCopyButtonClick)
+ .SetMethod("launchOfflineItem",
+ &NetErrorPageController::LaunchOfflineItem)
+ .SetMethod("launchDownloadsPage",
+ &NetErrorPageController::LaunchDownloadsPage)
+ .SetMethod("savePageForLater", &NetErrorPageController::SavePageForLater)
+ .SetMethod("cancelSavePage", &NetErrorPageController::CancelSavePage)
+ .SetMethod("listVisibilityChanged",
+ &NetErrorPageController::ListVisibilityChanged);
+}
diff --git a/chromium/chrome/renderer/net/net_error_page_controller.h b/chromium/chrome/renderer/net/net_error_page_controller.h
new file mode 100644
index 00000000000..8e97ffb1ed3
--- /dev/null
+++ b/chromium/chrome/renderer/net/net_error_page_controller.h
@@ -0,0 +1,126 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_NET_NET_ERROR_PAGE_CONTROLLER_H_
+#define CHROME_RENDERER_NET_NET_ERROR_PAGE_CONTROLLER_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/renderer/net/net_error_helper_core.h"
+#include "gin/arguments.h"
+#include "gin/wrappable.h"
+
+namespace content {
+class RenderFrame;
+}
+
+// This class makes various helper functions available to the
+// error page loaded by NetErrorHelper. It is bound to the JavaScript
+// window.errorPageController object.
+class NetErrorPageController : public gin::Wrappable<NetErrorPageController> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ // Interface used to notify creator of user actions invoked on the error page.
+ class Delegate {
+ public:
+ // Button press notification from error page.
+ virtual void ButtonPressed(NetErrorHelperCore::Button button) = 0;
+
+ // Called when a link with the given tracking ID is pressed.
+ virtual void TrackClick(int tracking_id) = 0;
+
+ // Called to open suggested offline content when it is pressed.
+ virtual void LaunchOfflineItem(const std::string& id,
+ const std::string& name_space) = 0;
+
+ // Called to show all available offline content.
+ virtual void LaunchDownloadsPage() = 0;
+
+ // Schedules a request to save the page later. This is different from the
+ // download button in that the page is only saved temporarily. This is used
+ // only for the auto-fetch-on-net-error-page feature.
+ virtual void SavePageForLater() = 0;
+
+ // Cancels the request to save the page later. This cancels a previous call
+ // to |SavePageForLater|, or the automatic request made when loading the
+ // error page. This is used only for the auto-fetch-on-net-error-page
+ // feature.
+ virtual void CancelSavePage() = 0;
+
+ // Called to signal the user tapped the button to change the visibility of
+ // the offline content list.
+ virtual void ListVisibilityChanged(bool is_visible) = 0;
+
+ // Save a new high score for the easer egg game in the user's synced
+ // preferences.
+ virtual void UpdateEasterEggHighScore(int high_score) = 0;
+
+ // Clear any high score for the easer egg game saved in the user's synced
+ // preferences.
+ virtual void ResetEasterEggHighScore() = 0;
+
+ protected:
+ Delegate();
+ virtual ~Delegate();
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // Will invoke methods on |delegate| in response to user actions taken on the
+ // error page. May call delegate methods even after the page has been
+ // navigated away from, so it is recommended consumers make sure the weak
+ // pointers are destroyed in response to navigations.
+ static void Install(content::RenderFrame* render_frame,
+ base::WeakPtr<Delegate> delegate);
+
+ private:
+ explicit NetErrorPageController(base::WeakPtr<Delegate> delegate);
+ ~NetErrorPageController() override;
+
+ void ErrorPageLoadedOrUpdated();
+
+ // Execute a button click to download page later.
+ bool DownloadButtonClick();
+
+ // Execute a "Reload" button click.
+ bool ReloadButtonClick();
+
+ // Execute a "Details" button click.
+ bool DetailsButtonClick();
+
+ // Track easter egg plays and high scores.
+ bool TrackEasterEgg();
+ bool UpdateEasterEggHighScore(int high_score);
+ bool ResetEasterEggHighScore();
+
+ // Execute a "Diagnose Errors" button click.
+ bool DiagnoseErrorsButtonClick();
+
+ // Track "Show cached copy" button clicks.
+ bool TrackCachedCopyButtonClick();
+
+ // Track a click when the page has suggestions from the navigation correction
+ // service.
+ bool TrackClick(const gin::Arguments& args);
+
+ // Used internally by other button click methods.
+ bool ButtonClick(NetErrorHelperCore::Button button);
+
+ void LaunchOfflineItem(gin::Arguments* args);
+ void LaunchDownloadsPage();
+ void SavePageForLater();
+ void CancelSavePage();
+ void ListVisibilityChanged(bool is_visible);
+
+ // gin::WrappableBase
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override;
+
+ base::WeakPtr<Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetErrorPageController);
+};
+
+#endif // CHROME_RENDERER_NET_NET_ERROR_PAGE_CONTROLLER_H_
diff --git a/chromium/chrome/renderer/net/page_auto_fetcher_helper_android.cc b/chromium/chrome/renderer/net/page_auto_fetcher_helper_android.cc
new file mode 100644
index 00000000000..655069f5f91
--- /dev/null
+++ b/chromium/chrome/renderer/net/page_auto_fetcher_helper_android.cc
@@ -0,0 +1,59 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net/page_auto_fetcher_helper_android.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+
+PageAutoFetcherHelper::PageAutoFetcherHelper(content::RenderFrame* render_frame)
+ : render_frame_(render_frame) {}
+PageAutoFetcherHelper::~PageAutoFetcherHelper() = default;
+
+void PageAutoFetcherHelper::OnCommitLoad() {
+ // Make sure we don't try to re-use the same mojo interface for more than one
+ // page. Otherwise, the browser side will use the old page's URL.
+ fetcher_.reset();
+}
+
+void PageAutoFetcherHelper::TrySchedule(
+ bool user_requested,
+ base::OnceCallback<void(FetcherScheduleResult)> complete_callback) {
+ if (!Bind()) {
+ std::move(complete_callback).Run(FetcherScheduleResult::kOtherError);
+ return;
+ }
+
+ fetcher_->TrySchedule(
+ user_requested,
+ base::BindOnce(&PageAutoFetcherHelper::TryScheduleComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ std::move(complete_callback)));
+}
+
+void PageAutoFetcherHelper::TryScheduleComplete(
+ base::OnceCallback<void(FetcherScheduleResult)> complete_callback,
+ FetcherScheduleResult result) {
+ std::move(complete_callback).Run(result);
+}
+
+void PageAutoFetcherHelper::CancelSchedule() {
+ if (Bind()) {
+ fetcher_->CancelSchedule();
+ }
+}
+
+bool PageAutoFetcherHelper::Bind() {
+ if (fetcher_)
+ return true;
+ render_frame_->GetRemoteInterfaces()->GetInterface(
+ fetcher_.BindNewPipeAndPassReceiver());
+ return fetcher_.is_bound();
+}
diff --git a/chromium/chrome/renderer/net/page_auto_fetcher_helper_android.h b/chromium/chrome/renderer/net/page_auto_fetcher_helper_android.h
new file mode 100644
index 00000000000..fdb9554b818
--- /dev/null
+++ b/chromium/chrome/renderer/net/page_auto_fetcher_helper_android.h
@@ -0,0 +1,50 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_NET_PAGE_AUTO_FETCHER_HELPER_ANDROID_H_
+#define CHROME_RENDERER_NET_PAGE_AUTO_FETCHER_HELPER_ANDROID_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/common/offline_page_auto_fetcher.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace content {
+class RenderFrame;
+}
+
+// Wraps calls from the renderer thread to the PageAutoFetcher, and records
+// related UMA.
+class PageAutoFetcherHelper {
+ public:
+ using FetcherScheduleResult =
+ chrome::mojom::OfflinePageAutoFetcherScheduleResult;
+ explicit PageAutoFetcherHelper(content::RenderFrame* render_frame);
+ virtual ~PageAutoFetcherHelper();
+ // Should be called for each page load.
+ void OnCommitLoad();
+ void TrySchedule(
+ bool user_requested,
+ base::OnceCallback<void(FetcherScheduleResult)> complete_callback);
+ void CancelSchedule();
+
+ protected:
+ void TryScheduleComplete(
+ base::OnceCallback<void(FetcherScheduleResult)> complete_callback,
+ FetcherScheduleResult result);
+
+ // Binds |fetcher_| if necessary. Returns true if the fetcher_ is bound.
+ // Virtual for testing only.
+ virtual bool Bind();
+
+ content::RenderFrame* render_frame_;
+ mojo::Remote<chrome::mojom::OfflinePageAutoFetcher> fetcher_;
+
+ base::WeakPtrFactory<PageAutoFetcherHelper> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PageAutoFetcherHelper);
+};
+
+#endif // CHROME_RENDERER_NET_PAGE_AUTO_FETCHER_HELPER_ANDROID_H_
diff --git a/chromium/chrome/renderer/net_benchmarking_extension.cc b/chromium/chrome/renderer/net_benchmarking_extension.cc
new file mode 100644
index 00000000000..3677aee5231
--- /dev/null
+++ b/chromium/chrome/renderer/net_benchmarking_extension.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/net_benchmarking_extension.h"
+
+#include "base/no_destructor.h"
+#include "chrome/common/net_benchmarking.mojom.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/renderer/render_thread.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/platform/web_cache.h"
+#include "v8/include/v8.h"
+
+using blink::WebCache;
+
+const char kNetBenchmarkingExtensionName[] = "v8/NetBenchmarking";
+
+namespace extensions_v8 {
+
+class NetBenchmarkingWrapper : public v8::Extension {
+ public:
+ NetBenchmarkingWrapper() :
+ v8::Extension(kNetBenchmarkingExtensionName,
+ "if (typeof(chrome) == 'undefined') {"
+ " chrome = {};"
+ "};"
+ "if (typeof(chrome.benchmarking) == 'undefined') {"
+ " chrome.benchmarking = {};"
+ "};"
+ "chrome.benchmarking.clearCache = function() {"
+ " native function ClearCache();"
+ " ClearCache();"
+ "};"
+ "chrome.benchmarking.clearHostResolverCache = function() {"
+ " native function ClearHostResolverCache();"
+ " ClearHostResolverCache();"
+ "};"
+ "chrome.benchmarking.clearPredictorCache = function() {"
+ " native function ClearPredictorCache();"
+ " ClearPredictorCache();"
+ "};"
+ "chrome.benchmarking.closeConnections = function() {"
+ " native function CloseConnections();"
+ " CloseConnections();"
+ "};"
+ ) {}
+
+ v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::String> name) override {
+ if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "ClearCache",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, ClearCache);
+ } else if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "ClearHostResolverCache",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, ClearHostResolverCache);
+ } else if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "ClearPredictorCache",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, ClearPredictorCache);
+ } else if (name->StringEquals(
+ v8::String::NewFromUtf8(isolate, "CloseConnections",
+ v8::NewStringType::kInternalized)
+ .ToLocalChecked())) {
+ return v8::FunctionTemplate::New(isolate, CloseConnections);
+ }
+
+ return v8::Local<v8::FunctionTemplate>();
+ }
+
+ static chrome::mojom::NetBenchmarking& GetNetBenchmarking() {
+ static base::NoDestructor<mojo::Remote<chrome::mojom::NetBenchmarking>>
+ net_benchmarking(ConnectToBrowser());
+ return **net_benchmarking;
+ }
+
+ static mojo::Remote<chrome::mojom::NetBenchmarking> ConnectToBrowser() {
+ mojo::Remote<chrome::mojom::NetBenchmarking> net_benchmarking;
+ content::RenderThread::Get()->BindHostReceiver(
+ net_benchmarking.BindNewPipeAndPassReceiver());
+ return net_benchmarking;
+ }
+
+ static void ClearCache(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetNetBenchmarking().ClearCache();
+ WebCache::Clear();
+ }
+
+ static void ClearHostResolverCache(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetNetBenchmarking().ClearHostResolverCache();
+ }
+
+ static void ClearPredictorCache(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetNetBenchmarking().ClearPredictorCache();
+ }
+
+ static void CloseConnections(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetNetBenchmarking().CloseCurrentConnections();
+ }
+};
+
+std::unique_ptr<v8::Extension> NetBenchmarkingExtension::Get() {
+ return std::make_unique<NetBenchmarkingWrapper>();
+}
+
+} // namespace extensions_v8
diff --git a/chromium/chrome/renderer/net_benchmarking_extension.h b/chromium/chrome/renderer/net_benchmarking_extension.h
new file mode 100644
index 00000000000..7501bab6100
--- /dev/null
+++ b/chromium/chrome/renderer/net_benchmarking_extension.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_NET_BENCHMARKING_EXTENSION_H_
+#define CHROME_RENDERER_NET_BENCHMARKING_EXTENSION_H_
+
+#include <memory>
+
+namespace v8 {
+class Extension;
+}
+
+namespace extensions_v8 {
+
+class NetBenchmarkingExtension {
+ public:
+ static std::unique_ptr<v8::Extension> Get();
+};
+
+} // namespace extensions_v8
+
+#endif // CHROME_RENDERER_NET_BENCHMARKING_EXTENSION_H_
diff --git a/chromium/chrome/renderer/performance_manager/OWNERS b/chromium/chrome/renderer/performance_manager/OWNERS
new file mode 100644
index 00000000000..0a5168e1964
--- /dev/null
+++ b/chromium/chrome/renderer/performance_manager/OWNERS
@@ -0,0 +1 @@
+file://chrome/browser/performance_manager/OWNERS
diff --git a/chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.cc b/chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.cc
new file mode 100644
index 00000000000..77c60805204
--- /dev/null
+++ b/chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.cc
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.h"
+
+#include "base/allocator/allocator_extension.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+
+namespace performance_manager {
+namespace mechanism {
+
+namespace {
+constexpr char kMaxTotalThreadCacheBytesKey[] =
+ "tcmalloc.max_total_thread_cache_bytes";
+} // namespace
+
+TcmallocTunablesImpl::TcmallocTunablesImpl() = default;
+TcmallocTunablesImpl::~TcmallocTunablesImpl() = default;
+
+// Static
+void TcmallocTunablesImpl::Create(
+ mojo::PendingReceiver<tcmalloc::mojom::TcmallocTunables> receiver) {
+ mojo::MakeSelfOwnedReceiver(std::make_unique<TcmallocTunablesImpl>(),
+ std::move(receiver));
+}
+
+void TcmallocTunablesImpl::SetMaxTotalThreadCacheBytes(uint32_t size_bytes) {
+ bool res = base::allocator::SetNumericProperty(kMaxTotalThreadCacheBytesKey,
+ size_bytes);
+ LOG_IF(ERROR, !res) << "Unable to SetNumericProperty("
+ << kMaxTotalThreadCacheBytesKey << ") to " << size_bytes;
+}
+
+} // namespace mechanism
+} // namespace performance_manager
diff --git a/chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.h b/chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.h
new file mode 100644
index 00000000000..4936f70c168
--- /dev/null
+++ b/chromium/chrome/renderer/performance_manager/mechanisms/tcmalloc_tunables_impl.h
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PERFORMANCE_MANAGER_MECHANISMS_TCMALLOC_TUNABLES_IMPL_H_
+#define CHROME_RENDERER_PERFORMANCE_MANAGER_MECHANISMS_TCMALLOC_TUNABLES_IMPL_H_
+
+#include "base/macros.h"
+#include "chrome/common/performance_manager/mojom/tcmalloc.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace performance_manager {
+namespace mechanism {
+
+class TcmallocTunablesImpl : public tcmalloc::mojom::TcmallocTunables {
+ public:
+ ~TcmallocTunablesImpl() override;
+ TcmallocTunablesImpl();
+
+ static void Create(
+ mojo::PendingReceiver<tcmalloc::mojom::TcmallocTunables> receiver);
+
+ protected:
+ // TcmallocTunables impl:
+ void SetMaxTotalThreadCacheBytes(uint32_t size_bytes) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TcmallocTunablesImpl);
+};
+
+} // namespace mechanism
+} // namespace performance_manager
+
+#endif // CHROME_RENDERER_PERFORMANCE_MANAGER_MECHANISMS_TCMALLOC_TUNABLES_IMPL_H_
diff --git a/chromium/chrome/renderer/plugins/DEPS b/chromium/chrome/renderer/plugins/DEPS
new file mode 100644
index 00000000000..39298501524
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+gin",
+ "+third_party/widevine",
+]
diff --git a/chromium/chrome/renderer/plugins/OWNERS b/chromium/chrome/renderer/plugins/OWNERS
new file mode 100644
index 00000000000..f8cf45de415
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/OWNERS
@@ -0,0 +1,2 @@
+tommycli@chromium.org
+# COMPONENT: Internals>Plugins
diff --git a/chromium/chrome/renderer/plugins/chrome_plugin_placeholder.cc b/chromium/chrome/renderer/plugins/chrome_plugin_placeholder.cc
new file mode 100644
index 00000000000..205c7398f82
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/chrome_plugin_placeholder.cc
@@ -0,0 +1,399 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/plugins/chrome_plugin_placeholder.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/prerender_messages.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/grit/renderer_resources.h"
+#include "chrome/renderer/chrome_content_renderer_client.h"
+#include "chrome/renderer/content_settings_observer.h"
+#include "chrome/renderer/custom_menu_commands.h"
+#include "chrome/renderer/plugins/plugin_preroller.h"
+#include "chrome/renderer/plugins/plugin_uma.h"
+#include "components/strings/grit/components_strings.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/context_menu_params.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "gin/object_template_builder.h"
+#include "ipc/ipc_sync_channel.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/common/page/page_zoom.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_input_event.h"
+#include "third_party/blink/public/platform/web_mouse_event.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_plugin_container.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/webui/jstemplate_builder.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/origin.h"
+#include "url/url_util.h"
+
+using base::UserMetricsAction;
+using content::RenderThread;
+using content::RenderView;
+
+namespace {
+const ChromePluginPlaceholder* g_last_active_menu = nullptr;
+} // namespace
+
+gin::WrapperInfo ChromePluginPlaceholder::kWrapperInfo = {
+ gin::kEmbedderNativeGin};
+
+ChromePluginPlaceholder::ChromePluginPlaceholder(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const std::string& html_data,
+ const base::string16& title)
+ : plugins::LoadablePluginPlaceholder(render_frame, params, html_data),
+ status_(chrome::mojom::PluginStatus::kAllowed),
+ title_(title),
+ context_menu_request_id_(0) {
+ RenderThread::Get()->AddObserver(this);
+}
+
+ChromePluginPlaceholder::~ChromePluginPlaceholder() {
+ RenderThread::Get()->RemoveObserver(this);
+ if (context_menu_request_id_ && render_frame())
+ render_frame()->CancelContextMenu(context_menu_request_id_);
+}
+
+mojo::PendingRemote<chrome::mojom::PluginRenderer>
+ChromePluginPlaceholder::BindPluginRenderer() {
+ return plugin_renderer_receiver_.BindNewPipeAndPassRemote();
+}
+
+// TODO(bauerb): Move this method to NonLoadablePluginPlaceholder?
+// static
+ChromePluginPlaceholder* ChromePluginPlaceholder::CreateLoadableMissingPlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params) {
+ const base::StringPiece template_html(
+ ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_BLOCKED_PLUGIN_HTML));
+
+ base::DictionaryValue values;
+ values.SetString("name", "");
+ values.SetString("message",
+ l10n_util::GetStringUTF8(IDS_PLUGIN_NOT_SUPPORTED));
+
+ std::string html_data = webui::GetI18nTemplateHtml(template_html, &values);
+
+ // Will destroy itself when its WebViewPlugin is going away.
+ return new ChromePluginPlaceholder(render_frame, params, html_data,
+ params.mime_type.Utf16());
+}
+
+// static
+ChromePluginPlaceholder* ChromePluginPlaceholder::CreateBlockedPlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const content::WebPluginInfo& info,
+ const std::string& identifier,
+ const base::string16& name,
+ int template_id,
+ const base::string16& message,
+ const PowerSaverInfo& power_saver_info) {
+ base::DictionaryValue values;
+ values.SetString("message", message);
+ values.SetString("name", name);
+ values.SetString("hide", l10n_util::GetStringUTF8(IDS_PLUGIN_HIDE));
+ values.SetString(
+ "pluginType",
+ render_frame->IsMainFrame() &&
+ render_frame->GetWebFrame()->GetDocument().IsPluginDocument()
+ ? "document"
+ : "embedded");
+
+ if (!power_saver_info.poster_attribute.empty()) {
+ values.SetString("poster", power_saver_info.poster_attribute);
+ values.SetString("baseurl", power_saver_info.base_url.spec());
+
+ if (!power_saver_info.custom_poster_size.IsEmpty()) {
+ float zoom_factor = blink::PageZoomLevelToZoomFactor(
+ render_frame->GetWebFrame()->View()->ZoomLevel());
+ int width =
+ roundf(power_saver_info.custom_poster_size.width() / zoom_factor);
+ int height =
+ roundf(power_saver_info.custom_poster_size.height() / zoom_factor);
+ values.SetString("visibleWidth", base::NumberToString(width) + "px");
+ values.SetString("visibleHeight", base::NumberToString(height) + "px");
+ } else {
+ // Need to populate these to please $i18n{...} replacement mechanism.
+ // 'undefined' is used on purpose as an invalid value for width and
+ // height, which is ignored by CSS.
+ values.SetString("visibleWidth", "undefined");
+ values.SetString("visibleHeight", "undefined");
+ }
+ }
+
+ const base::StringPiece template_html(
+ ui::ResourceBundle::GetSharedInstance().GetRawDataResource(template_id));
+
+ DCHECK(!template_html.empty()) << "unable to load template. ID: "
+ << template_id;
+ std::string html_data = webui::GetI18nTemplateHtml(template_html, &values);
+
+ // |blocked_plugin| will destroy itself when its WebViewPlugin is going away.
+ ChromePluginPlaceholder* blocked_plugin =
+ new ChromePluginPlaceholder(render_frame, params, html_data, name);
+
+ if (!power_saver_info.poster_attribute.empty())
+ blocked_plugin->BlockForPowerSaverPoster();
+ blocked_plugin->SetPluginInfo(info);
+ blocked_plugin->SetIdentifier(identifier);
+
+ blocked_plugin->set_power_saver_enabled(power_saver_info.power_saver_enabled);
+ blocked_plugin->set_blocked_for_background_tab(
+ power_saver_info.blocked_for_background_tab);
+
+ return blocked_plugin;
+}
+
+void ChromePluginPlaceholder::SetStatus(chrome::mojom::PluginStatus status) {
+ status_ = status;
+}
+
+bool ChromePluginPlaceholder::OnMessageReceived(const IPC::Message& message) {
+ // We don't swallow these messages because multiple blocked plugins and other
+ // objects have an interest in them.
+ IPC_BEGIN_MESSAGE_MAP(ChromePluginPlaceholder, message)
+ IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetPrerenderMode)
+ IPC_MESSAGE_HANDLER(ChromeViewMsg_LoadBlockedPlugins, OnLoadBlockedPlugins)
+ IPC_END_MESSAGE_MAP()
+
+ return false;
+}
+
+void ChromePluginPlaceholder::ShowPermissionBubbleCallback() {
+ mojo::AssociatedRemote<chrome::mojom::PluginHost> plugin_host;
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
+ plugin_host.BindNewEndpointAndPassReceiver());
+ plugin_host->ShowFlashPermissionBubble();
+}
+
+void ChromePluginPlaceholder::FinishedDownloading() {
+ SetMessage(l10n_util::GetStringFUTF16(IDS_PLUGIN_UPDATING, plugin_name_));
+}
+
+void ChromePluginPlaceholder::UpdateDownloading() {
+ SetMessage(l10n_util::GetStringFUTF16(IDS_PLUGIN_DOWNLOADING, plugin_name_));
+}
+
+void ChromePluginPlaceholder::UpdateSuccess() {
+ PluginListChanged();
+}
+
+void ChromePluginPlaceholder::UpdateFailure() {
+ SetMessage(l10n_util::GetStringFUTF16(IDS_PLUGIN_DOWNLOAD_ERROR_SHORT,
+ plugin_name_));
+}
+
+void ChromePluginPlaceholder::OnSetPrerenderMode(
+ prerender::PrerenderMode mode,
+ const std::string& histogram_prefix) {
+ OnSetIsPrerendering(mode != prerender::NO_PRERENDER);
+}
+
+void ChromePluginPlaceholder::PluginListChanged() {
+ if (!render_frame() || !plugin())
+ return;
+
+ chrome::mojom::PluginInfoPtr plugin_info = chrome::mojom::PluginInfo::New();
+ std::string mime_type(GetPluginParams().mime_type.Utf8());
+
+ ChromeContentRendererClient::GetPluginInfoHost()->GetPluginInfo(
+ routing_id(), GURL(GetPluginParams().url),
+ render_frame()->GetWebFrame()->Top()->GetSecurityOrigin(), mime_type,
+ &plugin_info);
+ if (plugin_info->status == status_)
+ return;
+ blink::WebPlugin* new_plugin = ChromeContentRendererClient::CreatePlugin(
+ render_frame(), GetPluginParams(), *plugin_info);
+ ReplacePlugin(new_plugin);
+ if (!new_plugin) {
+ PluginUMAReporter::GetInstance()->ReportPluginMissing(
+ GetPluginParams().mime_type.Utf8(), GURL(GetPluginParams().url));
+ }
+}
+
+void ChromePluginPlaceholder::OnMenuAction(int request_id, unsigned action) {
+ DCHECK_EQ(context_menu_request_id_, request_id);
+ if (g_last_active_menu != this)
+ return;
+ switch (action) {
+ case MENU_COMMAND_PLUGIN_RUN: {
+ RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Menu"));
+ MarkPluginEssential(
+ content::PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_CLICK);
+ LoadPlugin();
+ break;
+ }
+ case MENU_COMMAND_PLUGIN_HIDE: {
+ RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Menu"));
+ HidePlugin();
+ break;
+ }
+ case MENU_COMMAND_ENABLE_FLASH: {
+ ShowPermissionBubbleCallback();
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+void ChromePluginPlaceholder::OnMenuClosed(int request_id) {
+ DCHECK_EQ(context_menu_request_id_, request_id);
+ context_menu_request_id_ = 0;
+}
+
+v8::Local<v8::Value> ChromePluginPlaceholder::GetV8Handle(
+ v8::Isolate* isolate) {
+ return gin::CreateHandle(isolate, this).ToV8();
+}
+
+void ChromePluginPlaceholder::ShowContextMenu(
+ const blink::WebMouseEvent& event) {
+ if (context_menu_request_id_)
+ return; // Don't allow nested context menu requests.
+ if (!render_frame())
+ return;
+
+ content::ContextMenuParams params;
+
+ if (!title_.empty()) {
+ content::MenuItem name_item;
+ name_item.label = title_;
+ params.custom_items.push_back(name_item);
+
+ content::MenuItem separator_item;
+ separator_item.type = content::MenuItem::SEPARATOR;
+ params.custom_items.push_back(separator_item);
+ }
+
+ bool flash_hidden =
+ status_ == chrome::mojom::PluginStatus::kFlashHiddenPreferHtml;
+ if (!GetPluginInfo().path.value().empty() && !flash_hidden) {
+ content::MenuItem run_item;
+ run_item.action = MENU_COMMAND_PLUGIN_RUN;
+ // Disable this menu item if the plugin is blocked by policy.
+ run_item.enabled = LoadingAllowed();
+ run_item.label = l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PLUGIN_RUN);
+ params.custom_items.push_back(run_item);
+ }
+
+ if (flash_hidden) {
+ content::MenuItem enable_flash_item;
+ enable_flash_item.action = MENU_COMMAND_ENABLE_FLASH;
+ enable_flash_item.enabled = true;
+ enable_flash_item.label =
+ l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_ENABLE_FLASH);
+ params.custom_items.push_back(enable_flash_item);
+ }
+
+ content::MenuItem hide_item;
+ hide_item.action = MENU_COMMAND_PLUGIN_HIDE;
+ bool is_main_frame_plugin_document =
+ render_frame()->IsMainFrame() &&
+ render_frame()->GetWebFrame()->GetDocument().IsPluginDocument();
+ hide_item.enabled = !is_main_frame_plugin_document;
+ hide_item.label = l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PLUGIN_HIDE);
+ params.custom_items.push_back(hide_item);
+
+ blink::WebPoint point(event.PositionInWidget().x, event.PositionInWidget().y);
+ if (plugin() && plugin()->Container())
+ point = plugin()->Container()->LocalToRootFramePoint(point);
+
+ params.x = point.x;
+ params.y = point.y;
+
+ context_menu_request_id_ = render_frame()->ShowContextMenu(this, params);
+ g_last_active_menu = this;
+}
+
+blink::WebPlugin* ChromePluginPlaceholder::CreatePlugin() {
+ std::unique_ptr<content::PluginInstanceThrottler> throttler;
+ // If the plugin has already been marked essential in its placeholder form,
+ // we shouldn't create a new throttler and start the process all over again.
+ if (power_saver_enabled()) {
+ throttler = content::PluginInstanceThrottler::Create(
+ heuristic_run_before_ ? content::RenderFrame::DONT_RECORD_DECISION
+ : content::RenderFrame::RECORD_DECISION);
+ // PluginPreroller manages its own lifetime.
+ new PluginPreroller(render_frame(), GetPluginParams(), GetPluginInfo(),
+ GetIdentifier(), title_,
+ l10n_util::GetStringFUTF16(IDS_PLUGIN_BLOCKED, title_),
+ throttler.get());
+ }
+ return render_frame()->CreatePlugin(GetPluginInfo(), GetPluginParams(),
+ std::move(throttler));
+}
+
+void ChromePluginPlaceholder::OnBlockedContent(
+ content::RenderFrame::PeripheralContentStatus status,
+ bool is_same_origin) {
+ DCHECK(render_frame());
+
+ if (status ==
+ content::RenderFrame::PeripheralContentStatus::CONTENT_STATUS_TINY) {
+ ContentSettingsObserver::Get(render_frame())
+ ->DidBlockContentType(CONTENT_SETTINGS_TYPE_PLUGINS, title_);
+ }
+
+ std::string message = base::StringPrintf(
+ is_same_origin ? "Same-origin plugin content from %s must have a visible "
+ "size larger than 6 x 6 pixels, or it will be blocked. "
+ "Invisible content is always blocked."
+ : "Cross-origin plugin content from %s must have a "
+ "visible size larger than 400 x 300 pixels, or it will "
+ "be blocked. Invisible content is always blocked.",
+ GetPluginParams().url.GetString().Utf8().c_str());
+ render_frame()->AddMessageToConsole(blink::mojom::ConsoleMessageLevel::kInfo,
+ message);
+}
+
+gin::ObjectTemplateBuilder ChromePluginPlaceholder::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ gin::ObjectTemplateBuilder builder =
+ gin::Wrappable<ChromePluginPlaceholder>::GetObjectTemplateBuilder(isolate)
+ .SetMethod<void (ChromePluginPlaceholder::*)()>(
+ "hide", &ChromePluginPlaceholder::HideCallback)
+ .SetMethod<void (ChromePluginPlaceholder::*)()>(
+ "load", &ChromePluginPlaceholder::LoadCallback)
+ .SetMethod<void (ChromePluginPlaceholder::*)()>(
+ "didFinishLoading",
+ &ChromePluginPlaceholder::DidFinishLoadingCallback)
+ .SetMethod("showPermissionBubble",
+ &ChromePluginPlaceholder::ShowPermissionBubbleCallback);
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnablePluginPlaceholderTesting)) {
+ builder.SetMethod<void (ChromePluginPlaceholder::*)()>(
+ "notifyPlaceholderReadyForTesting",
+ &ChromePluginPlaceholder::NotifyPlaceholderReadyForTestingCallback);
+ }
+
+ return builder;
+}
diff --git a/chromium/chrome/renderer/plugins/chrome_plugin_placeholder.h b/chromium/chrome/renderer/plugins/chrome_plugin_placeholder.h
new file mode 100644
index 00000000000..738a37a2679
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/chrome_plugin_placeholder.h
@@ -0,0 +1,105 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PLUGINS_CHROME_PLUGIN_PLACEHOLDER_H_
+#define CHROME_RENDERER_PLUGINS_CHROME_PLUGIN_PLACEHOLDER_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/plugin.mojom.h"
+#include "chrome/common/prerender_types.h"
+#include "chrome/renderer/plugins/power_saver_info.h"
+#include "components/plugins/renderer/loadable_plugin_placeholder.h"
+#include "content/public/renderer/context_menu_client.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+class ChromePluginPlaceholder final
+ : public plugins::LoadablePluginPlaceholder,
+ public content::RenderThreadObserver,
+ public content::ContextMenuClient,
+ public chrome::mojom::PluginRenderer,
+ public gin::Wrappable<ChromePluginPlaceholder> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ static ChromePluginPlaceholder* CreateBlockedPlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const content::WebPluginInfo& info,
+ const std::string& identifier,
+ const base::string16& name,
+ int resource_id,
+ const base::string16& message,
+ const PowerSaverInfo& power_saver_info);
+
+ // Creates a new WebViewPlugin with a MissingPlugin as a delegate.
+ static ChromePluginPlaceholder* CreateLoadableMissingPlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params);
+
+ void SetStatus(chrome::mojom::PluginStatus status);
+
+ mojo::PendingRemote<chrome::mojom::PluginRenderer> BindPluginRenderer();
+
+ private:
+ ChromePluginPlaceholder(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const std::string& html_data,
+ const base::string16& title);
+ ~ChromePluginPlaceholder() override;
+
+ // content::LoadablePluginPlaceholder overrides.
+ blink::WebPlugin* CreatePlugin() override;
+ void OnBlockedContent(content::RenderFrame::PeripheralContentStatus status,
+ bool is_same_origin) override;
+
+ // gin::Wrappable (via PluginPlaceholder) method
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) final;
+
+ // content::RenderViewObserver (via PluginPlaceholder) override:
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ // WebViewPlugin::Delegate (via PluginPlaceholder) methods:
+ v8::Local<v8::Value> GetV8Handle(v8::Isolate* isolate) override;
+ void ShowContextMenu(const blink::WebMouseEvent&) override;
+
+ // content::RenderThreadObserver methods:
+ void PluginListChanged() override;
+
+ // content::ContextMenuClient methods:
+ void OnMenuAction(int request_id, unsigned action) override;
+ void OnMenuClosed(int request_id) override;
+
+ // Show the Plugins permission bubble.
+ void ShowPermissionBubbleCallback();
+
+ // chrome::mojom::PluginRenderer methods.
+ void FinishedDownloading() override;
+ void UpdateDownloading() override;
+ void UpdateSuccess() override;
+ void UpdateFailure() override;
+
+ // IPC message handlers:
+ void OnSetPrerenderMode(prerender::PrerenderMode mode,
+ const std::string& histogram_prefix);
+
+ chrome::mojom::PluginStatus status_;
+
+ base::string16 title_;
+
+ int context_menu_request_id_; // Nonzero when request pending.
+ base::string16 plugin_name_;
+
+ mojo::Receiver<chrome::mojom::PluginRenderer> plugin_renderer_receiver_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(ChromePluginPlaceholder);
+};
+
+#endif // CHROME_RENDERER_PLUGINS_CHROME_PLUGIN_PLACEHOLDER_H_
diff --git a/chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc b/chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc
new file mode 100644
index 00000000000..d4e3c3b13f8
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc
@@ -0,0 +1,64 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/plugins/non_loadable_plugin_placeholder.h"
+
+#include "base/files/file_path.h"
+#include "base/values.h"
+#include "chrome/common/plugin.mojom.h"
+#include "chrome/grit/renderer_resources.h"
+#include "components/plugins/renderer/plugin_placeholder.h"
+#include "components/strings/grit/components_strings.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/strings/grit/blink_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/webui/jstemplate_builder.h"
+
+// static
+plugins::PluginPlaceholder*
+NonLoadablePluginPlaceholder::CreateNotSupportedPlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params) {
+ const base::StringPiece template_html(
+ ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_BLOCKED_PLUGIN_HTML));
+
+ base::DictionaryValue values;
+ values.SetString("name", "");
+ values.SetString("message",
+ l10n_util::GetStringUTF8(IDS_PLUGIN_NOT_SUPPORTED));
+
+ std::string html_data = webui::GetI18nTemplateHtml(template_html, &values);
+
+ // PluginPlaceholder will destroy itself when its WebViewPlugin is going away.
+ return new plugins::PluginPlaceholder(render_frame, params, html_data);
+}
+
+// static
+plugins::PluginPlaceholder* NonLoadablePluginPlaceholder::CreateErrorPlugin(
+ content::RenderFrame* render_frame,
+ const base::FilePath& file_path) {
+ base::DictionaryValue values;
+ values.SetString("name", "");
+ values.SetString("message",
+ l10n_util::GetStringUTF8(IDS_PLUGIN_INITIALIZATION_ERROR));
+
+ const base::StringPiece template_html(
+ ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_BLOCKED_PLUGIN_HTML));
+ std::string html_data = webui::GetI18nTemplateHtml(template_html, &values);
+
+ blink::WebPluginParams params;
+ // PluginPlaceholder will destroy itself when its WebViewPlugin is going away.
+ plugins::PluginPlaceholder* plugin =
+ new plugins::PluginPlaceholder(render_frame, params, html_data);
+
+ mojo::AssociatedRemote<chrome::mojom::PluginHost> plugin_host;
+ render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&plugin_host);
+ plugin_host->CouldNotLoadPlugin(file_path);
+
+ return plugin;
+}
diff --git a/chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.h b/chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.h
new file mode 100644
index 00000000000..1b2c6d13dee
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/non_loadable_plugin_placeholder.h
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PLUGINS_NON_LOADABLE_PLUGIN_PLACEHOLDER_H_
+#define CHROME_RENDERER_PLUGINS_NON_LOADABLE_PLUGIN_PLACEHOLDER_H_
+
+#include "base/macros.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace blink {
+struct WebPluginParams;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace plugins {
+class PluginPlaceholder;
+}
+
+class NonLoadablePluginPlaceholder {
+ public:
+ // Creates a non-loadable plugin placeholder for platforms without plugins.
+ static plugins::PluginPlaceholder* CreateNotSupportedPlugin(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params);
+
+ static plugins::PluginPlaceholder* CreateErrorPlugin(
+ content::RenderFrame* render_frame,
+ const base::FilePath& file_path);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(NonLoadablePluginPlaceholder);
+};
+
+#endif // CHROME_RENDERER_PLUGINS_NON_LOADABLE_PLUGIN_PLACEHOLDER_H_
diff --git a/chromium/chrome/renderer/plugins/pdf_plugin_placeholder.cc b/chromium/chrome/renderer/plugins/pdf_plugin_placeholder.cc
new file mode 100644
index 00000000000..1a506d03253
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/pdf_plugin_placeholder.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/plugins/pdf_plugin_placeholder.h"
+
+#include "base/command_line.h"
+#include "chrome/common/pdf_util.h"
+#include "chrome/common/render_messages.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/renderer/render_thread.h"
+#include "gin/object_template_builder.h"
+
+gin::WrapperInfo PDFPluginPlaceholder::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+// static
+PDFPluginPlaceholder* PDFPluginPlaceholder::CreatePDFPlaceholder(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params) {
+ std::string html_data = GetPDFPlaceholderHTML(params.url);
+ return new PDFPluginPlaceholder(render_frame, params, html_data);
+}
+
+PDFPluginPlaceholder::PDFPluginPlaceholder(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const std::string& html_data)
+ : plugins::PluginPlaceholderBase(render_frame, params, html_data) {}
+
+PDFPluginPlaceholder::~PDFPluginPlaceholder() {}
+
+v8::Local<v8::Value> PDFPluginPlaceholder::GetV8Handle(v8::Isolate* isolate) {
+ return gin::CreateHandle(isolate, this).ToV8();
+}
+
+gin::ObjectTemplateBuilder PDFPluginPlaceholder::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ gin::ObjectTemplateBuilder builder =
+ gin::Wrappable<PDFPluginPlaceholder>::GetObjectTemplateBuilder(isolate)
+ .SetMethod<void (PDFPluginPlaceholder::*)()>(
+ "openPDF", &PDFPluginPlaceholder::OpenPDFCallback);
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnablePluginPlaceholderTesting)) {
+ builder.SetMethod<void (PDFPluginPlaceholder::*)()>(
+ "notifyPlaceholderReadyForTesting",
+ &PDFPluginPlaceholder::NotifyPlaceholderReadyForTestingCallback);
+ }
+
+ return builder;
+}
+
+void PDFPluginPlaceholder::OpenPDFCallback() {
+ ReportPDFLoadStatus(PDFLoadStatus::kViewPdfClickedInPdfPluginPlaceholder);
+ content::RenderThread::Get()->Send(
+ new ChromeViewHostMsg_OpenPDF(routing_id(), GetPluginParams().url));
+}
diff --git a/chromium/chrome/renderer/plugins/pdf_plugin_placeholder.h b/chromium/chrome/renderer/plugins/pdf_plugin_placeholder.h
new file mode 100644
index 00000000000..987f1f9edca
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/pdf_plugin_placeholder.h
@@ -0,0 +1,40 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PLUGINS_PDF_PLUGIN_PLACEHOLDER_H_
+#define CHROME_RENDERER_PLUGINS_PDF_PLUGIN_PLACEHOLDER_H_
+
+#include "components/plugins/renderer/plugin_placeholder.h"
+
+// Placeholder that allows users to click to download a PDF for when
+// plugins are disabled and the PDF fails to load.
+// TODO(amberwon): Flesh out the class more to download an embedded PDF when the
+// PDF plugin is disabled or unavailable.
+class PDFPluginPlaceholder : public plugins::PluginPlaceholderBase,
+ public gin::Wrappable<PDFPluginPlaceholder> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ // Returned placeholder is owned by the associated plugin, which can be
+ // retrieved with PluginPlaceholderBase::plugin().
+ static PDFPluginPlaceholder* CreatePDFPlaceholder(
+ content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params);
+
+ private:
+ PDFPluginPlaceholder(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const std::string& html_data);
+ ~PDFPluginPlaceholder() final;
+
+ // WebViewPlugin::Delegate methods:
+ v8::Local<v8::Value> GetV8Handle(v8::Isolate* isolate) final;
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) final;
+
+ void OpenPDFCallback();
+};
+
+#endif // CHROME_RENDERER_PLUGINS_PDF_PLUGIN_PLACEHOLDER_H_
diff --git a/chromium/chrome/renderer/plugins/plugin_preroller.cc b/chromium/chrome/renderer/plugins/plugin_preroller.cc
new file mode 100644
index 00000000000..d6c3da86edc
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/plugin_preroller.cc
@@ -0,0 +1,92 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/plugins/plugin_preroller.h"
+
+#include "base/base64.h"
+#include "chrome/grit/renderer_resources.h"
+#include "chrome/renderer/plugins/chrome_plugin_placeholder.h"
+#include "chrome/renderer/plugins/power_saver_info.h"
+#include "third_party/blink/public/platform/web_rect.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_plugin.h"
+#include "third_party/blink/public/web/web_plugin_container.h"
+#include "ui/gfx/codec/png_codec.h"
+
+PluginPreroller::PluginPreroller(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const content::WebPluginInfo& info,
+ const std::string& identifier,
+ const base::string16& name,
+ const base::string16& message,
+ content::PluginInstanceThrottler* throttler)
+ : RenderFrameObserver(render_frame),
+ params_(params),
+ info_(info),
+ identifier_(identifier),
+ name_(name),
+ message_(message),
+ throttler_(throttler) {
+ DCHECK(throttler);
+ throttler_->AddObserver(this);
+}
+
+PluginPreroller::~PluginPreroller() {
+ if (throttler_)
+ throttler_->RemoveObserver(this);
+}
+
+void PluginPreroller::OnKeyframeExtracted(const SkBitmap* bitmap) {
+ std::vector<unsigned char> png_data;
+ if (!gfx::PNGCodec::EncodeBGRASkBitmap(*bitmap, false, &png_data)) {
+ DLOG(ERROR) << "Provided keyframe could not be encoded as PNG.";
+ return;
+ }
+
+ base::StringPiece png_as_string(reinterpret_cast<char*>(&png_data[0]),
+ png_data.size());
+
+ std::string data_url_header = "data:image/png;base64,";
+ std::string data_url_body;
+ base::Base64Encode(png_as_string, &data_url_body);
+ keyframe_data_url_ = GURL(data_url_header + data_url_body);
+}
+
+void PluginPreroller::OnThrottleStateChange() {
+ if (!throttler_->IsThrottled())
+ return;
+
+ PowerSaverInfo power_saver_info;
+ power_saver_info.power_saver_enabled = true;
+ power_saver_info.poster_attribute = keyframe_data_url_.spec();
+ power_saver_info.custom_poster_size = throttler_->GetSize();
+
+ ChromePluginPlaceholder* placeholder =
+ ChromePluginPlaceholder::CreateBlockedPlugin(
+ render_frame(), params_, info_, identifier_, name_,
+ IDR_PLUGIN_POSTER_HTML, message_, power_saver_info);
+ placeholder->SetPremadePlugin(throttler_);
+ placeholder->AllowLoading();
+
+ blink::WebPluginContainer* container =
+ throttler_->GetWebPlugin()->Container();
+ container->SetPlugin(placeholder->plugin());
+
+ bool success = placeholder->plugin()->Initialize(container);
+ DCHECK(success);
+
+ container->Invalidate();
+ container->ReportGeometry();
+
+ delete this;
+}
+
+void PluginPreroller::OnThrottlerDestroyed() {
+ throttler_ = nullptr;
+ delete this;
+}
+
+void PluginPreroller::OnDestruct() {
+ delete this;
+}
diff --git a/chromium/chrome/renderer/plugins/plugin_preroller.h b/chromium/chrome/renderer/plugins/plugin_preroller.h
new file mode 100644
index 00000000000..0f812302711
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/plugin_preroller.h
@@ -0,0 +1,57 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PLUGINS_PLUGIN_PREROLLER_H_
+#define CHROME_RENDERER_PLUGINS_PLUGIN_PREROLLER_H_
+
+#include "base/macros.h"
+#include "content/public/common/webplugininfo.h"
+#include "content/public/renderer/plugin_instance_throttler.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#include "url/gurl.h"
+
+class SkBitmap;
+
+// This class manages a plugin briefly for the purposes of keyframe extraction.
+// Once a keyframe has been extracted, this class will replace the plugin with
+// a ChromePluginPlaceholder. The actual plugin will continue to live in a
+// throttled state. This class manages its own lifetime.
+class PluginPreroller : public content::PluginInstanceThrottler::Observer,
+ public content::RenderFrameObserver {
+ public:
+ // Does not take ownership of |render_frame|, |plugin|, or |throttler|.
+ PluginPreroller(content::RenderFrame* render_frame,
+ const blink::WebPluginParams& params,
+ const content::WebPluginInfo& info,
+ const std::string& identifier,
+ const base::string16& name,
+ const base::string16& message,
+ content::PluginInstanceThrottler* throttler);
+
+ ~PluginPreroller() override;
+
+ private:
+ // content::PluginInstanceThrottler::Observer methods:
+ void OnKeyframeExtracted(const SkBitmap* bitmap) override;
+ void OnThrottleStateChange() override;
+ void OnThrottlerDestroyed() override;
+
+ // content::RenderFrameObserver implementation.
+ void OnDestruct() override;
+
+ blink::WebPluginParams params_;
+ content::WebPluginInfo info_;
+ std::string identifier_;
+ base::string16 name_;
+ base::string16 message_;
+
+ content::PluginInstanceThrottler* throttler_;
+
+ GURL keyframe_data_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginPreroller);
+};
+
+#endif // CHROME_RENDERER_PLUGINS_PLUGIN_PREROLLER_H_
diff --git a/chromium/chrome/renderer/plugins/plugin_uma.cc b/chromium/chrome/renderer/plugins/plugin_uma.cc
new file mode 100644
index 00000000000..201e41e501c
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/plugin_uma.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/plugins/plugin_uma.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "content/public/common/content_constants.h"
+
+namespace {
+
+// String we will use to convert mime type to plugin type.
+const char kWindowsMediaPlayerType[] = "application/x-mplayer2";
+const char kSilverlightTypePrefix[] = "application/x-silverlight";
+const char kRealPlayerTypePrefix[] = "audio/x-pn-realaudio";
+const char kJavaTypeSubstring[] = "application/x-java-applet";
+const char kQuickTimeType[] = "video/quicktime";
+
+// Arrays containing file extensions connected with specific plugins.
+// Note: THE ARRAYS MUST BE SORTED BECAUSE BINARY SEARCH IS USED ON THEM!
+const char* const kWindowsMediaPlayerExtensions[] = {".asx"};
+
+const char* const kRealPlayerExtensions[] = {".ra", ".ram", ".rm",
+ ".rmm", ".rmp", ".rpm"};
+
+const char* const kQuickTimeExtensions[] = {".moov", ".mov", ".qif",
+ ".qt", ".qti", ".qtif"};
+
+const char* const kShockwaveFlashExtensions[] = {".spl", ".swf"};
+
+} // namespace.
+
+class UMASenderImpl : public PluginUMAReporter::UMASender {
+ void SendPluginUMA(PluginUMAReporter::ReportType report_type,
+ PluginUMAReporter::PluginType plugin_type) override;
+};
+
+void UMASenderImpl::SendPluginUMA(PluginUMAReporter::ReportType report_type,
+ PluginUMAReporter::PluginType plugin_type) {
+ // UMA_HISTOGRAM_ENUMERATION requires constant histogram name. Use string
+ // constants explicitly instead of trying to use variables for names.
+ switch (report_type) {
+ case PluginUMAReporter::MISSING_PLUGIN:
+ UMA_HISTOGRAM_ENUMERATION("Plugin.MissingPlugins",
+ plugin_type,
+ PluginUMAReporter::PLUGIN_TYPE_MAX);
+ break;
+ case PluginUMAReporter::DISABLED_PLUGIN:
+ UMA_HISTOGRAM_ENUMERATION("Plugin.DisabledPlugins",
+ plugin_type,
+ PluginUMAReporter::PLUGIN_TYPE_MAX);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+// static.
+PluginUMAReporter* PluginUMAReporter::GetInstance() {
+ return base::Singleton<PluginUMAReporter>::get();
+}
+
+void PluginUMAReporter::ReportPluginMissing(const std::string& plugin_mime_type,
+ const GURL& plugin_src) {
+ report_sender_->SendPluginUMA(MISSING_PLUGIN,
+ GetPluginType(plugin_mime_type, plugin_src));
+}
+
+void PluginUMAReporter::ReportPluginDisabled(
+ const std::string& plugin_mime_type,
+ const GURL& plugin_src) {
+ report_sender_->SendPluginUMA(DISABLED_PLUGIN,
+ GetPluginType(plugin_mime_type, plugin_src));
+}
+
+PluginUMAReporter::PluginUMAReporter() : report_sender_(new UMASenderImpl()) {}
+
+PluginUMAReporter::~PluginUMAReporter() {}
+
+// static.
+bool PluginUMAReporter::CompareCStrings(const char* first, const char* second) {
+ return strcmp(first, second) < 0;
+}
+
+bool PluginUMAReporter::CStringArrayContainsCString(const char* const* array,
+ size_t array_size,
+ const char* str) {
+ return std::binary_search(array, array + array_size, str, CompareCStrings);
+}
+
+void PluginUMAReporter::ExtractFileExtension(const GURL& src,
+ std::string* extension) {
+ std::string extension_file_path(src.ExtractFileName());
+ if (extension_file_path.empty())
+ extension_file_path = src.host();
+
+ size_t last_dot = extension_file_path.find_last_of('.');
+ if (last_dot != std::string::npos) {
+ *extension = extension_file_path.substr(last_dot);
+ } else {
+ extension->clear();
+ }
+
+ *extension = base::ToLowerASCII(*extension);
+}
+
+PluginUMAReporter::PluginType PluginUMAReporter::GetPluginType(
+ const std::string& plugin_mime_type,
+ const GURL& plugin_src) {
+ // If we know plugin's mime type, we use it to determine plugin's type. Else,
+ // we try to determine plugin type using plugin source's extension.
+ if (!plugin_mime_type.empty())
+ return MimeTypeToPluginType(base::ToLowerASCII(plugin_mime_type));
+
+ return SrcToPluginType(plugin_src);
+}
+
+PluginUMAReporter::PluginType PluginUMAReporter::SrcToPluginType(
+ const GURL& src) {
+ std::string file_extension;
+ ExtractFileExtension(src, &file_extension);
+ if (CStringArrayContainsCString(kWindowsMediaPlayerExtensions,
+ base::size(kWindowsMediaPlayerExtensions),
+ file_extension.c_str())) {
+ return WINDOWS_MEDIA_PLAYER;
+ }
+
+ if (CStringArrayContainsCString(kQuickTimeExtensions,
+ base::size(kQuickTimeExtensions),
+ file_extension.c_str())) {
+ return QUICKTIME;
+ }
+
+ if (CStringArrayContainsCString(kRealPlayerExtensions,
+ base::size(kRealPlayerExtensions),
+ file_extension.c_str())) {
+ return REALPLAYER;
+ }
+
+ if (CStringArrayContainsCString(kShockwaveFlashExtensions,
+ base::size(kShockwaveFlashExtensions),
+ file_extension.c_str())) {
+ return SHOCKWAVE_FLASH;
+ }
+
+ return UNSUPPORTED_EXTENSION;
+}
+
+PluginUMAReporter::PluginType PluginUMAReporter::MimeTypeToPluginType(
+ const std::string& mime_type) {
+ if (mime_type == kWindowsMediaPlayerType)
+ return WINDOWS_MEDIA_PLAYER;
+
+ size_t prefix_length = strlen(kSilverlightTypePrefix);
+ if (strncmp(mime_type.c_str(), kSilverlightTypePrefix, prefix_length) == 0)
+ return SILVERLIGHT;
+
+ prefix_length = strlen(kRealPlayerTypePrefix);
+ if (strncmp(mime_type.c_str(), kRealPlayerTypePrefix, prefix_length) == 0)
+ return REALPLAYER;
+
+ if (strstr(mime_type.c_str(), kJavaTypeSubstring))
+ return JAVA;
+
+ if (mime_type == kQuickTimeType)
+ return QUICKTIME;
+
+ if (mime_type == content::kBrowserPluginMimeType)
+ return BROWSER_PLUGIN;
+
+ if (mime_type == content::kFlashPluginSwfMimeType ||
+ mime_type == content::kFlashPluginSplMimeType) {
+ return SHOCKWAVE_FLASH;
+ }
+
+ return UNSUPPORTED_MIMETYPE;
+}
diff --git a/chromium/chrome/renderer/plugins/plugin_uma.h b/chromium/chrome/renderer/plugins/plugin_uma.h
new file mode 100644
index 00000000000..d21498ad194
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/plugin_uma.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PLUGINS_PLUGIN_UMA_H_
+#define CHROME_RENDERER_PLUGINS_PLUGIN_UMA_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "url/gurl.h"
+
+// Used to send UMA data about missing plugins to UMA histogram server. Method
+// ReportPluginMissing should be called whenever plugin that is not available or
+// enabled is called. We try to determine plugin's type by requested mime type,
+// or, if mime type is unknown, by plugin's src url.
+class PluginUMAReporter {
+ public:
+ enum ReportType {
+ MISSING_PLUGIN,
+ DISABLED_PLUGIN,
+ };
+
+ // Make sure the enum list in tools/histogram/histograms.xml is updated with
+ // any change in this list.
+ enum PluginType {
+ WINDOWS_MEDIA_PLAYER = 0,
+ SILVERLIGHT = 1,
+ REALPLAYER = 2,
+ JAVA = 3,
+ QUICKTIME = 4,
+ OTHER = 5, // This is obsolete and replaced by UNSUPPORTED_* types.
+ UNSUPPORTED_MIMETYPE,
+ UNSUPPORTED_EXTENSION,
+ // NOTE: Add new unsupported types only immediately above this line.
+ BROWSER_PLUGIN = 10,
+ SHOCKWAVE_FLASH,
+ WIDEVINE_CDM = 12, // Obsolete March 2018
+ // NOTE: Add new plugin types only immediately above this line.
+ PLUGIN_TYPE_MAX
+ };
+
+ // Sends UMA data, i.e. plugin's type.
+ class UMASender {
+ public:
+ virtual ~UMASender() {}
+ virtual void SendPluginUMA(ReportType report_type,
+ PluginType plugin_type) = 0;
+ };
+
+ // Returns singleton instance.
+ static PluginUMAReporter* GetInstance();
+
+ void ReportPluginMissing(const std::string& plugin_mime_type,
+ const GURL& plugin_src);
+
+ void ReportPluginDisabled(const std::string& plugin_mime_type,
+ const GURL& plugin_src);
+
+ private:
+ friend struct base::DefaultSingletonTraits<PluginUMAReporter>;
+ friend class PluginUMATest;
+
+ PluginUMAReporter();
+ ~PluginUMAReporter();
+
+ static bool CompareCStrings(const char* first, const char* second);
+ bool CStringArrayContainsCString(const char* const* array,
+ size_t array_size,
+ const char* str);
+ // Extracts file extension from url.
+ void ExtractFileExtension(const GURL& src, std::string* extension);
+
+ PluginType GetPluginType(const std::string& plugin_mime_type,
+ const GURL& plugin_src);
+
+ // Converts plugin's src to plugin type.
+ PluginType SrcToPluginType(const GURL& src);
+ // Converts plugin's mime type to plugin type.
+ PluginType MimeTypeToPluginType(const std::string& mime_type);
+
+ std::unique_ptr<UMASender> report_sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginUMAReporter);
+};
+
+#endif // CHROME_RENDERER_PLUGINS_PLUGIN_UMA_H_
diff --git a/chromium/chrome/renderer/plugins/plugin_uma_unittest.cc b/chromium/chrome/renderer/plugins/plugin_uma_unittest.cc
new file mode 100644
index 00000000000..33882b5cd0f
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/plugin_uma_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "chrome/renderer/plugins/plugin_uma.h"
+#include "media/media_buildflags.h"
+#include "ppapi/buildflags/buildflags.h"
+
+class PluginUMATest : public testing::Test {
+ public:
+ static void ExpectPluginType(
+ PluginUMAReporter::PluginType expected_plugin_type,
+ const std::string& plugin_mime_type,
+ const GURL& plugin_src) {
+ EXPECT_EQ(expected_plugin_type,
+ PluginUMAReporter::GetInstance()->GetPluginType(plugin_mime_type,
+ plugin_src));
+ }
+};
+
+TEST_F(PluginUMATest, WindowsMediaPlayer) {
+ ExpectPluginType(PluginUMAReporter::WINDOWS_MEDIA_PLAYER,
+ "application/x-mplayer2",
+ GURL("file://some_file.mov"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "application/x-mplayer2-some_sufix",
+ GURL("file://some_file.mov"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "some-prefix-application/x-mplayer2",
+ GURL("file://some_file.mov"));
+}
+
+TEST_F(PluginUMATest, Silverlight) {
+ ExpectPluginType(PluginUMAReporter::SILVERLIGHT,
+ "application/x-silverlight",
+ GURL("aaaa"));
+ ExpectPluginType(PluginUMAReporter::SILVERLIGHT,
+ "application/x-silverlight-some-sufix",
+ GURL("aaaa"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "some-prefix-application/x-silverlight",
+ GURL("aaaa"));
+}
+
+TEST_F(PluginUMATest, RealPlayer) {
+ ExpectPluginType(
+ PluginUMAReporter::REALPLAYER, "audio/x-pn-realaudio", GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::REALPLAYER,
+ "audio/x-pn-realaudio-some-sufix",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "some-prefix-audio/x-pn-realaudio",
+ GURL("some url"));
+}
+
+TEST_F(PluginUMATest, Java) {
+ ExpectPluginType(
+ PluginUMAReporter::JAVA, "application/x-java-applet", GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::JAVA,
+ "application/x-java-applet-some-sufix",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::JAVA,
+ "some-prefix-application/x-java-applet-sufix",
+ GURL("some url"));
+}
+
+TEST_F(PluginUMATest, QuickTime) {
+ ExpectPluginType(
+ PluginUMAReporter::QUICKTIME, "video/quicktime", GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "video/quicktime-sufix",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "prefix-video/quicktime",
+ GURL("some url"));
+}
+
+TEST_F(PluginUMATest, BrowserPlugin) {
+ ExpectPluginType(PluginUMAReporter::BROWSER_PLUGIN,
+ "application/browser-plugin",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "application/browser-plugin-sufix",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "prefix-application/browser-plugin",
+ GURL("some url"));
+}
+
+TEST_F(PluginUMATest, ShockwaveFlash) {
+ ExpectPluginType(PluginUMAReporter::SHOCKWAVE_FLASH,
+ "application/x-shockwave-flash",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::SHOCKWAVE_FLASH,
+ "application/futuresplash",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "application/x-futuresplash",
+ GURL("some url"));
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "application/shockwave-flash",
+ GURL("some url"));
+}
+
+TEST_F(PluginUMATest, BySrcExtension) {
+ ExpectPluginType(
+ PluginUMAReporter::QUICKTIME, std::string(), GURL("file://file.mov"));
+
+ // When plugin's mime type is given, we don't check extension.
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_MIMETYPE,
+ "unknown-plugin",
+ GURL("http://file.mov"));
+
+ ExpectPluginType(PluginUMAReporter::WINDOWS_MEDIA_PLAYER,
+ std::string(),
+ GURL("file://file.asx"));
+ ExpectPluginType(
+ PluginUMAReporter::REALPLAYER, std::string(), GURL("file://file.rm"));
+ ExpectPluginType(PluginUMAReporter::QUICKTIME,
+ std::string(),
+ GURL("http://aaa/file.mov?x=aaaa&y=b#c"));
+ ExpectPluginType(PluginUMAReporter::QUICKTIME,
+ std::string(),
+ GURL("http://file.mov?x=aaaa&y=b#c"));
+ ExpectPluginType(PluginUMAReporter::SHOCKWAVE_FLASH,
+ std::string(),
+ GURL("http://file.swf?x=aaaa&y=b#c"));
+ ExpectPluginType(PluginUMAReporter::SHOCKWAVE_FLASH,
+ std::string(),
+ GURL("http://file.spl?x=aaaa&y=b#c"));
+
+ ExpectPluginType(PluginUMAReporter::UNSUPPORTED_EXTENSION,
+ std::string(),
+ GURL("http://file.unknown_extension"));
+ ExpectPluginType(
+ PluginUMAReporter::UNSUPPORTED_EXTENSION, std::string(), GURL("http://"));
+ ExpectPluginType(
+ PluginUMAReporter::UNSUPPORTED_EXTENSION, std::string(), GURL("mov"));
+}
+
+TEST_F(PluginUMATest, CaseSensitivity) {
+ ExpectPluginType(
+ PluginUMAReporter::QUICKTIME, "video/QUICKTIME", GURL("http://file.aaa"));
+ ExpectPluginType(
+ PluginUMAReporter::QUICKTIME, std::string(), GURL("http://file.MoV"));
+}
diff --git a/chromium/chrome/renderer/plugins/power_saver_info.cc b/chromium/chrome/renderer/plugins/power_saver_info.cc
new file mode 100644
index 00000000000..799c8f2d10c
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/power_saver_info.cc
@@ -0,0 +1,75 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/renderer/plugins/power_saver_info.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/webplugininfo.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#include "url/origin.h"
+
+namespace {
+
+std::string GetPluginInstancePosterAttribute(
+ const blink::WebPluginParams& params) {
+ DCHECK_EQ(params.attribute_names.size(), params.attribute_values.size());
+
+ for (size_t i = 0; i < params.attribute_names.size(); ++i) {
+ if (params.attribute_names[i].Utf8() == "poster" &&
+ !params.attribute_values[i].IsEmpty()) {
+ return params.attribute_values[i].Utf8();
+ }
+ }
+ return std::string();
+}
+
+bool GetPluginPowerSaverEnabled(bool power_saver_setting_on, bool is_flash) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ std::string override_for_testing = command_line->GetSwitchValueASCII(
+ switches::kOverridePluginPowerSaverForTesting);
+
+ // This feature has only been tested thoroughly with Flash thus far. It is
+ // also enabled for the Power Saver test plugin for browser tests.
+ if (override_for_testing == "always")
+ return true;
+ else if (override_for_testing == "never")
+ return false;
+ else
+ return power_saver_setting_on && is_flash;
+}
+
+} // namespace
+
+PowerSaverInfo::PowerSaverInfo()
+ : power_saver_enabled(false), blocked_for_background_tab(false) {}
+
+PowerSaverInfo::PowerSaverInfo(const PowerSaverInfo& other) = default;
+
+PowerSaverInfo PowerSaverInfo::Get(content::RenderFrame* render_frame,
+ bool power_saver_setting_on,
+ const blink::WebPluginParams& params,
+ const content::WebPluginInfo& plugin_info,
+ const GURL& document_url) {
+ bool is_flash =
+ plugin_info.name == base::ASCIIToUTF16(content::kFlashPluginName);
+
+ PowerSaverInfo info;
+ info.power_saver_enabled =
+ GetPluginPowerSaverEnabled(power_saver_setting_on, is_flash);
+
+ if (info.power_saver_enabled) {
+ info.blocked_for_background_tab = render_frame->IsHidden();
+ info.poster_attribute = GetPluginInstancePosterAttribute(params);
+ info.base_url = document_url;
+ }
+
+ return info;
+}
diff --git a/chromium/chrome/renderer/plugins/power_saver_info.h b/chromium/chrome/renderer/plugins/power_saver_info.h
new file mode 100644
index 00000000000..ec1c5defcb4
--- /dev/null
+++ b/chromium/chrome/renderer/plugins/power_saver_info.h
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PLUGINS_POWER_SAVER_INFO_H_
+#define CHROME_RENDERER_PLUGINS_POWER_SAVER_INFO_H_
+
+#include <string>
+
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+
+namespace blink {
+struct WebPluginParams;
+}
+
+namespace content {
+class RenderFrame;
+struct WebPluginInfo;
+}
+
+// This contains information specifying the plugin's Power Saver behavior.
+// The default constructor has Plugin Power Saver disabled.
+struct PowerSaverInfo {
+ PowerSaverInfo();
+ PowerSaverInfo(const PowerSaverInfo& other);
+
+ // Determines the PowerSaverInfo using the peripheral content heuristic.
+ static PowerSaverInfo Get(content::RenderFrame* render_frame,
+ bool power_saver_setting_on,
+ const blink::WebPluginParams& params,
+ const content::WebPluginInfo& plugin_info,
+ const GURL& document_url);
+
+ // Whether power saver should be enabled.
+ bool power_saver_enabled;
+
+ // Whether the plugin should be deferred because it is in a background tab.
+ bool blocked_for_background_tab;
+
+ // The poster image specified in image 'srcset' attribute format.
+ std::string poster_attribute;
+
+ // Used to resolve relative paths in |poster_attribute|.
+ GURL base_url;
+
+ // Specify this to provide partially obscured plugins a centered poster image.
+ gfx::Size custom_poster_size;
+};
+
+#endif // CHROME_RENDERER_PLUGINS_POWER_SAVER_INFO_H_
diff --git a/chromium/chrome/renderer/prerender/OWNERS b/chromium/chrome/renderer/prerender/OWNERS
new file mode 100644
index 00000000000..c71af0e773c
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/OWNERS
@@ -0,0 +1,3 @@
+file://chrome/browser/prerender/OWNERS
+
+# COMPONENT: Internals>Preload
diff --git a/chromium/chrome/renderer/prerender/prerender_dispatcher.cc b/chromium/chrome/renderer/prerender/prerender_dispatcher.cc
new file mode 100644
index 00000000000..edf31ee8009
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_dispatcher.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/prerender/prerender_dispatcher.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "chrome/common/prerender_messages.h"
+#include "chrome/common/prerender_types.h"
+#include "chrome/renderer/prerender/prerender_extra_data.h"
+#include "content/public/common/referrer.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_prerendering_support.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace prerender {
+
+using blink::WebPrerender;
+using blink::WebPrerenderingSupport;
+
+PrerenderDispatcher::PrerenderDispatcher()
+ : process_start_time_(base::TimeTicks::Now()) {
+ WebPrerenderingSupport::Initialize(this);
+}
+
+PrerenderDispatcher::~PrerenderDispatcher() {
+ WebPrerenderingSupport::Shutdown();
+}
+
+bool PrerenderDispatcher::IsPrerenderURL(const GURL& url) const {
+ return running_prerender_urls_.count(url) >= 1;
+}
+
+void PrerenderDispatcher::IncrementPrefetchCount() {
+ prefetch_count_++;
+}
+
+void PrerenderDispatcher::DecrementPrefetchCount() {
+ if (!--prefetch_count_ && prefetch_finished_) {
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ "Prerender.NoStatePrefetchRendererLifetimeExtension",
+ base::TimeTicks::Now() - prefetch_parsed_time_);
+ content::RenderThread::Get()->Send(new PrerenderHostMsg_PrefetchFinished());
+ }
+}
+
+void PrerenderDispatcher::PrerenderStart(int prerender_id) {
+ auto it = prerenders_.find(prerender_id);
+ if (it == prerenders_.end())
+ return;
+
+ WebPrerender& prerender = it->second;
+
+ // The prerender should only be null in unit tests.
+ if (prerender.IsNull())
+ return;
+
+ prerender.DidStartPrerender();
+}
+
+void PrerenderDispatcher::PrerenderStopLoading(int prerender_id) {
+ auto it = prerenders_.find(prerender_id);
+ if (it == prerenders_.end())
+ return;
+
+ WebPrerender& prerender = it->second;
+ DCHECK(!prerender.IsNull())
+ << "OnPrerenderStopLoading shouldn't be called from a unit test, the only"
+ << "context in which a WebPrerender in the dispatcher can be null.";
+
+ prerender.DidSendLoadForPrerender();
+}
+
+void PrerenderDispatcher::PrerenderDomContentLoaded(int prerender_id) {
+ auto it = prerenders_.find(prerender_id);
+ if (it == prerenders_.end())
+ return;
+
+ WebPrerender& prerender = it->second;
+ DCHECK(!prerender.IsNull())
+ << "OnPrerenderDomContentLoaded shouldn't be called from a unit test,"
+ << " the only context in which a WebPrerender in the dispatcher can be"
+ << " null.";
+
+ prerender.DidSendDOMContentLoadedForPrerender();
+}
+
+void PrerenderDispatcher::PrerenderAddAlias(const GURL& alias) {
+ running_prerender_urls_.insert(alias);
+}
+
+void PrerenderDispatcher::PrerenderRemoveAliases(
+ const std::vector<GURL>& aliases) {
+ for (size_t i = 0; i < aliases.size(); ++i) {
+ auto it = running_prerender_urls_.find(aliases[i]);
+ if (it != running_prerender_urls_.end()) {
+ running_prerender_urls_.erase(it);
+ }
+ }
+}
+
+void PrerenderDispatcher::PrerenderStop(int prerender_id) {
+ auto it = prerenders_.find(prerender_id);
+ if (it == prerenders_.end())
+ return;
+ WebPrerender& prerender = it->second;
+
+ // The prerender should only be null in unit tests.
+ if (!prerender.IsNull())
+ prerender.DidStopPrerender();
+
+ // TODO(cbentzel): We'd also want to send the map of active prerenders when
+ // creating a new render process, so the Add/Remove go relative to that.
+ // This may not be that big of a deal in practice, since the newly created tab
+ // is unlikely to go to the prerendered page.
+ prerenders_.erase(prerender_id);
+}
+
+void PrerenderDispatcher::OnPrerenderDispatcherRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::PrerenderDispatcher>
+ receiver) {
+ receivers_.Add(this, std::move(receiver));
+}
+
+void PrerenderDispatcher::RegisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) {
+ associated_interfaces->AddInterface(
+ base::Bind(&PrerenderDispatcher::OnPrerenderDispatcherRequest,
+ base::Unretained(this)));
+}
+
+void PrerenderDispatcher::UnregisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) {
+ associated_interfaces->RemoveInterface(
+ chrome::mojom::PrerenderDispatcher::Name_);
+}
+
+void PrerenderDispatcher::Add(const WebPrerender& prerender) {
+ const PrerenderExtraData& extra_data =
+ PrerenderExtraData::FromPrerender(prerender);
+ if (prerenders_.count(extra_data.prerender_id()) != 0) {
+ // TODO(gavinp): Determine why these apparently duplicate adds occur.
+ return;
+ }
+
+ prerenders_[extra_data.prerender_id()] = prerender;
+
+ PrerenderAttributes attributes;
+ attributes.url = GURL(prerender.Url());
+ attributes.rel_types = prerender.RelTypes();
+
+ content::RenderThread::Get()->Send(new PrerenderHostMsg_AddLinkRelPrerender(
+ extra_data.prerender_id(), attributes,
+ content::Referrer::SanitizeForRequest(
+ GURL(prerender.Url()),
+ content::Referrer(blink::WebStringToGURL(prerender.GetReferrer()),
+ prerender.GetReferrerPolicy())),
+ prerender.SecurityOrigin(), extra_data.size(),
+ extra_data.render_view_route_id()));
+}
+
+void PrerenderDispatcher::Cancel(const WebPrerender& prerender) {
+ const PrerenderExtraData& extra_data =
+ PrerenderExtraData::FromPrerender(prerender);
+ content::RenderThread::Get()->Send(
+ new PrerenderHostMsg_CancelLinkRelPrerender(extra_data.prerender_id()));
+ // The browser will not send an OnPrerenderStop (the prerender may have even
+ // been canceled before it was started), so release it to avoid a
+ // leak. Moreover, if it did, the PrerenderClient in Blink will have been
+ // detached already.
+ prerenders_.erase(extra_data.prerender_id());
+}
+
+void PrerenderDispatcher::Abandon(const WebPrerender& prerender) {
+ const PrerenderExtraData& extra_data =
+ PrerenderExtraData::FromPrerender(prerender);
+ content::RenderThread::Get()->Send(
+ new PrerenderHostMsg_AbandonLinkRelPrerender(extra_data.prerender_id()));
+ // The browser will not send an OnPrerenderStop (the prerender may have even
+ // been canceled before it was started), so release it to avoid a
+ // leak. Moreover, if it did, the PrerenderClient in Blink will have been
+ // detached already.
+ prerenders_.erase(extra_data.prerender_id());
+}
+
+void PrerenderDispatcher::PrefetchFinished() {
+ prefetch_parsed_time_ = base::TimeTicks::Now();
+ if (prefetch_count_) {
+ prefetch_finished_ = true;
+ } else {
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ "Prerender.NoStatePrefetchRendererParseTime",
+ prefetch_parsed_time_ - process_start_time_);
+ content::RenderThread::Get()->Send(new PrerenderHostMsg_PrefetchFinished());
+ }
+}
+
+} // namespace prerender
diff --git a/chromium/chrome/renderer/prerender/prerender_dispatcher.h b/chromium/chrome/renderer/prerender/prerender_dispatcher.h
new file mode 100644
index 00000000000..ecbcadc6896
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_dispatcher.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PRERENDER_PRERENDER_DISPATCHER_H_
+#define CHROME_RENDERER_PRERENDER_PRERENDER_DISPATCHER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "chrome/common/prerender.mojom.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/platform/web_prerender.h"
+#include "third_party/blink/public/platform/web_prerendering_support.h"
+
+class GURL;
+
+namespace prerender {
+
+// There is one PrerenderDispatcher per render process. It keeps track of which
+// prerenders were launched from this renderer, and ensures prerender navigation
+// is triggered on navigation to those. It implements the prerendering interface
+// supplied to WebKit.
+class PrerenderDispatcher : public content::RenderThreadObserver,
+ public blink::WebPrerenderingSupport,
+ public chrome::mojom::PrerenderDispatcher {
+ public:
+ PrerenderDispatcher();
+ ~PrerenderDispatcher() override;
+
+ bool IsPrerenderURL(const GURL& url) const;
+
+ void IncrementPrefetchCount();
+ void DecrementPrefetchCount();
+
+ private:
+ friend class PrerenderDispatcherTest;
+
+ // chrome::mojom::PrerenderDispatcher:
+ void PrerenderStart(int prerender_id) override;
+ void PrerenderStopLoading(int prerender_id) override;
+ void PrerenderDomContentLoaded(int prerender_id) override;
+ void PrerenderAddAlias(const GURL& alias) override;
+ void PrerenderRemoveAliases(const std::vector<GURL>& aliases) override;
+ void PrerenderStop(int prerender_id) override;
+
+ void OnPrerenderDispatcherRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::PrerenderDispatcher>
+ receiver);
+
+ // From RenderThreadObserver:
+ void RegisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) override;
+ void UnregisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) override;
+
+ // From WebPrerenderingSupport:
+ void Add(const blink::WebPrerender& prerender) override;
+ void Cancel(const blink::WebPrerender& prerender) override;
+ void Abandon(const blink::WebPrerender& prerender) override;
+ void PrefetchFinished() override;
+
+ // From WebKit, prerender elements launched by renderers in our process.
+ std::map<int, blink::WebPrerender> prerenders_;
+
+ // From the browser process, which prerenders are running, indexed by URL.
+ // Updated by the browser processes as aliases are discovered.
+ std::multiset<GURL> running_prerender_urls_;
+
+ int prefetch_count_ = 0;
+ bool prefetch_finished_ = false;
+ base::TimeTicks process_start_time_;
+ base::TimeTicks prefetch_parsed_time_;
+
+ mojo::AssociatedReceiverSet<chrome::mojom::PrerenderDispatcher> receivers_;
+};
+
+} // namespace prerender
+
+#endif // CHROME_RENDERER_PRERENDER_PRERENDER_DISPATCHER_H_
diff --git a/chromium/chrome/renderer/prerender/prerender_dispatcher_unittest.cc b/chromium/chrome/renderer/prerender/prerender_dispatcher_unittest.cc
new file mode 100644
index 00000000000..52e86b7a1f3
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_dispatcher_unittest.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/prerender/prerender_dispatcher.h"
+
+#include <map>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace prerender {
+
+namespace {
+
+int g_next_prerender_id = 0;
+
+} // namespace
+
+using blink::WebPrerender;
+
+// Since we can't mock out blink::WebPrerender in chrome, this test can't test
+// signalling to or from the WebKit side. Instead, it checks only that the
+// messages received from the browser generate consistant state in the
+// PrerenderDispatcher. Since prerenders couldn't even start or stop without the
+// WebKit signalling, we can expect PrerenderBrowserTest to provide adequate
+// coverage of this.
+class PrerenderDispatcherTest : public testing::Test {
+ public:
+ PrerenderDispatcherTest() {}
+
+ bool IsPrerenderURL(const GURL& url) const {
+ return prerender_dispatcher_.IsPrerenderURL(url);
+ }
+
+ const std::map<int, WebPrerender>& prerenders() const {
+ return prerender_dispatcher_.prerenders_;
+ }
+
+ int StartPrerender(const GURL& url) {
+ DCHECK_EQ(0u, prerender_dispatcher_.prerenders_.count(g_next_prerender_id));
+ prerender_dispatcher_.prerenders_[g_next_prerender_id] = WebPrerender();
+
+ prerender_dispatcher_.PrerenderStart(g_next_prerender_id);
+ prerender_dispatcher_.PrerenderAddAlias(url);
+ return g_next_prerender_id++;
+ }
+
+ void AddAliasToPrerender(const GURL& url) {
+ prerender_dispatcher_.PrerenderAddAlias(url);
+ }
+
+ void RemoveAliasFromPrerender(const GURL& url) {
+ std::vector<GURL> urls;
+ urls.push_back(url);
+ prerender_dispatcher_.PrerenderRemoveAliases(urls);
+ }
+
+ void StopPrerender(int prerender_id) {
+ prerender_dispatcher_.PrerenderStop(prerender_id);
+ }
+
+ int GetCountForURL(const GURL& url) const {
+ return prerender_dispatcher_.running_prerender_urls_.count(url);
+ }
+
+ private:
+ PrerenderDispatcher prerender_dispatcher_;
+ DISALLOW_COPY_AND_ASSIGN(PrerenderDispatcherTest);
+};
+
+TEST_F(PrerenderDispatcherTest, PrerenderDispatcherEmpty) {
+ EXPECT_TRUE(prerenders().empty());
+}
+
+TEST_F(PrerenderDispatcherTest, PrerenderDispatcherSingleAdd) {
+ GURL foo_url = GURL("http://foo.com");
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ StartPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(1, GetCountForURL(foo_url));
+}
+
+TEST_F(PrerenderDispatcherTest, PrerenderDispatcherMultipleAdd) {
+ GURL foo_url = GURL("http://foo.com");
+ GURL bar_url = GURL("http://bar.com");
+
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ EXPECT_FALSE(IsPrerenderURL(bar_url));
+ StartPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_FALSE(IsPrerenderURL(bar_url));
+
+ AddAliasToPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_FALSE(IsPrerenderURL(bar_url));
+ EXPECT_EQ(2, GetCountForURL(foo_url));
+
+ StartPrerender(bar_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_TRUE(IsPrerenderURL(bar_url));
+ EXPECT_EQ(2, GetCountForURL(foo_url));
+ EXPECT_EQ(1, GetCountForURL(bar_url));
+}
+
+TEST_F(PrerenderDispatcherTest, PrerenderDispatcherSingleRemove) {
+ GURL foo_url = GURL("http://foo.com");
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ int foo_id = StartPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ StopPrerender(foo_id);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(1, GetCountForURL(foo_url));
+ RemoveAliasFromPrerender(foo_url);
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(0, GetCountForURL(foo_url));
+}
+
+TEST_F(PrerenderDispatcherTest, PrerenderDispatcherTooManyRemoves) {
+ GURL foo_url = GURL("http://foo.com");
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ int foo_id = StartPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ StopPrerender(foo_id);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(1, GetCountForURL(foo_url));
+ RemoveAliasFromPrerender(foo_url);
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(0, GetCountForURL(foo_url));
+ RemoveAliasFromPrerender(foo_url);
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(0, GetCountForURL(foo_url));
+}
+
+TEST_F(PrerenderDispatcherTest, PrerenderDispatcherMultipleRemoves) {
+ GURL foo_url = GURL("http://foo.com");
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ int foo_id = StartPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ AddAliasToPrerender(foo_url);
+ StopPrerender(foo_id);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(2, GetCountForURL(foo_url));
+ RemoveAliasFromPrerender(foo_url);
+ EXPECT_TRUE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(1, GetCountForURL(foo_url));
+ RemoveAliasFromPrerender(foo_url);
+ EXPECT_FALSE(IsPrerenderURL(foo_url));
+ EXPECT_EQ(0, GetCountForURL(foo_url));
+}
+
+} // end namespace prerender
diff --git a/chromium/chrome/renderer/prerender/prerender_extra_data.cc b/chromium/chrome/renderer/prerender/prerender_extra_data.cc
new file mode 100644
index 00000000000..2ca2123934e
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_extra_data.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/prerender/prerender_extra_data.h"
+
+#include "base/logging.h"
+
+namespace prerender {
+
+PrerenderExtraData::PrerenderExtraData(
+ int prerender_id,
+ int render_view_route_id,
+ const gfx::Size& size)
+ : prerender_id_(prerender_id),
+ render_view_route_id_(render_view_route_id),
+ size_(size) {
+}
+
+PrerenderExtraData::~PrerenderExtraData() {
+}
+
+// static
+const PrerenderExtraData& PrerenderExtraData::FromPrerender(
+ const blink::WebPrerender& prerender) {
+ DCHECK(prerender.GetExtraData());
+ return static_cast<const PrerenderExtraData&>(*prerender.GetExtraData());
+}
+
+} // namespace prerender
+
diff --git a/chromium/chrome/renderer/prerender/prerender_extra_data.h b/chromium/chrome/renderer/prerender/prerender_extra_data.h
new file mode 100644
index 00000000000..3d28066cb78
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_extra_data.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PRERENDER_PRERENDER_EXTRA_DATA_H_
+#define CHROME_RENDERER_PRERENDER_PRERENDER_EXTRA_DATA_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "third_party/blink/public/platform/web_prerender.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace prerender {
+
+class PrerenderExtraData : public blink::WebPrerender::ExtraData {
+ public:
+ PrerenderExtraData(int prerender_id,
+ int render_view_route_id,
+ const gfx::Size& size);
+ ~PrerenderExtraData() override;
+
+ int prerender_id() const { return prerender_id_; }
+ int render_view_route_id() const { return render_view_route_id_; }
+ const gfx::Size& size() const { return size_; }
+
+ static const PrerenderExtraData& FromPrerender(
+ const blink::WebPrerender& prerender);
+
+ private:
+ const int prerender_id_;
+ const int render_view_route_id_;
+ const gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrerenderExtraData);
+};
+
+} // namespace prerender
+
+#endif // CHROME_RENDERER_PRERENDER_PRERENDER_EXTRA_DATA_H_
+
diff --git a/chromium/chrome/renderer/prerender/prerender_helper.cc b/chromium/chrome/renderer/prerender/prerender_helper.cc
new file mode 100644
index 00000000000..23780b57966
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_helper.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/prerender/prerender_helper.h"
+
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "chrome/common/prerender_messages.h"
+#include "chrome/common/prerender_url_loader_throttle.h"
+#include "content/public/renderer/document_state.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+
+namespace prerender {
+
+PrerenderHelper::PrerenderHelper(content::RenderFrame* render_frame,
+ PrerenderMode prerender_mode,
+ const std::string& histogram_prefix)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<PrerenderHelper>(render_frame),
+ prerender_mode_(prerender_mode),
+ histogram_prefix_(histogram_prefix) {
+ DCHECK_NE(prerender_mode_, NO_PRERENDER);
+}
+
+PrerenderHelper::~PrerenderHelper() = default;
+
+void PrerenderHelper::AddThrottle(
+ const base::WeakPtr<PrerenderURLLoaderThrottle>& throttle) {
+ throttles_.push_back(throttle);
+}
+
+// static.
+bool PrerenderHelper::IsPrerendering(const content::RenderFrame* render_frame) {
+ return PrerenderHelper::GetPrerenderMode(render_frame) != NO_PRERENDER;
+}
+
+// static.
+PrerenderMode PrerenderHelper::GetPrerenderMode(
+ const content::RenderFrame* render_frame) {
+ PrerenderHelper* helper = PrerenderHelper::Get(render_frame);
+ if (!helper)
+ return NO_PRERENDER;
+
+ DCHECK_NE(helper->prerender_mode_, NO_PRERENDER);
+ return helper->prerender_mode_;
+}
+
+bool PrerenderHelper::OnMessageReceived(
+ const IPC::Message& message) {
+ IPC_BEGIN_MESSAGE_MAP(PrerenderHelper, message)
+ IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetIsPrerendering)
+ IPC_END_MESSAGE_MAP()
+ // Return false on PrerenderMsg_SetIsPrerendering so other observers can see
+ // the message.
+ return false;
+}
+
+void PrerenderHelper::OnSetIsPrerendering(PrerenderMode mode,
+ const std::string& histogram_prefix) {
+ // Immediately after construction, |this| may receive the message that
+ // triggered its creation. If so, ignore it.
+ if (mode != prerender::NO_PRERENDER)
+ return;
+
+ auto throttles = std::move(throttles_);
+
+ // |this| must be deleted so PrerenderHelper::IsPrerendering returns false
+ // when the visibility state is updated, so the visibility state string will
+ // not be "prerendered".
+ delete this;
+
+ for (auto resource : throttles) {
+ if (resource)
+ resource->PrerenderUsed();
+ }
+}
+
+void PrerenderHelper::OnDestruct() {
+ delete this;
+}
+
+} // namespace prerender
diff --git a/chromium/chrome/renderer/prerender/prerender_helper.h b/chromium/chrome/renderer/prerender/prerender_helper.h
new file mode 100644
index 00000000000..0e1dcaf5830
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerender_helper.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PRERENDER_PRERENDER_HELPER_H_
+#define CHROME_RENDERER_PRERENDER_PRERENDER_HELPER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "chrome/common/prerender_types.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+
+namespace prerender {
+class PrerenderURLLoaderThrottle;
+
+// Helper class to track whether its RenderFrame is currently being prerendered.
+// Created when prerendering starts and deleted as soon as it stops.
+class PrerenderHelper
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<PrerenderHelper> {
+ public:
+ PrerenderHelper(content::RenderFrame* render_frame,
+ PrerenderMode prerender_mode,
+ const std::string& histogram_prefix);
+
+ ~PrerenderHelper() override;
+
+ // Called when a PrerenderURLLoaderThrottle is created for a resource for
+ // this frame.
+ void AddThrottle(const base::WeakPtr<PrerenderURLLoaderThrottle>& throttle);
+
+ PrerenderMode prerender_mode() const { return prerender_mode_; }
+ std::string histogram_prefix() const { return histogram_prefix_; }
+
+ // Returns true if |render_view| is currently prerendering.
+ static bool IsPrerendering(const content::RenderFrame* render_frame);
+
+ static PrerenderMode GetPrerenderMode(
+ const content::RenderFrame* render_frame);
+
+ private:
+ // RenderFrameObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void OnDestruct() override;
+
+ void OnSetIsPrerendering(PrerenderMode mode,
+ const std::string& histogram_prefix);
+
+ PrerenderMode prerender_mode_;
+ std::string histogram_prefix_;
+
+ // Pending requests for this frame..
+ std::vector<base::WeakPtr<PrerenderURLLoaderThrottle>> throttles_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrerenderHelper);
+};
+
+} // namespace prerender
+
+#endif // CHROME_RENDERER_PRERENDER_PRERENDER_HELPER_H_
diff --git a/chromium/chrome/renderer/prerender/prerenderer_client.cc b/chromium/chrome/renderer/prerender/prerenderer_client.cc
new file mode 100644
index 00000000000..36fb2257de4
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerenderer_client.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/prerender/prerenderer_client.h"
+
+#include "base/logging.h"
+#include "chrome/renderer/prerender/prerender_extra_data.h"
+#include "chrome/renderer/prerender/prerender_helper.h"
+#include "content/public/renderer/render_view.h"
+#include "third_party/blink/public/web/web_view.h"
+
+namespace {
+static int s_last_prerender_id = 0;
+}
+
+namespace prerender {
+
+PrerendererClient::PrerendererClient(content::RenderView* render_view)
+ : content::RenderViewObserver(render_view) {
+ DCHECK(render_view);
+ DVLOG(5) << "PrerendererClient::PrerendererClient()";
+ render_view->GetWebView()->SetPrerendererClient(this);
+}
+
+PrerendererClient::~PrerendererClient() {
+}
+
+void PrerendererClient::WillAddPrerender(blink::WebPrerender* prerender) {
+ DVLOG(3) << "PrerendererClient::willAddPrerender url = "
+ << prerender->Url().GetString().Utf8();
+ prerender->SetExtraData(
+ new PrerenderExtraData(++s_last_prerender_id, routing_id(),
+ render_view()->GetWebView()->GetSize()));
+}
+
+bool PrerendererClient::IsPrefetchOnly() {
+ return PrerenderHelper::GetPrerenderMode(
+ render_view()->GetMainRenderFrame()) == PREFETCH_ONLY;
+}
+
+void PrerendererClient::OnDestruct() {
+ delete this;
+}
+
+} // namespace prerender
diff --git a/chromium/chrome/renderer/prerender/prerenderer_client.h b/chromium/chrome/renderer/prerender/prerenderer_client.h
new file mode 100644
index 00000000000..90b629032c2
--- /dev/null
+++ b/chromium/chrome/renderer/prerender/prerenderer_client.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PRERENDER_PRERENDERER_CLIENT_H_
+#define CHROME_RENDERER_PRERENDER_PRERENDERER_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "content/public/renderer/render_view_observer.h"
+#include "third_party/blink/public/web/web_prerenderer_client.h"
+
+namespace prerender {
+
+class PrerendererClient : public content::RenderViewObserver,
+ public blink::WebPrerendererClient {
+ public:
+ explicit PrerendererClient(content::RenderView* render_view);
+
+ private:
+ ~PrerendererClient() override;
+
+ // RenderViewObserver implementation.
+ void OnDestruct() override;
+
+ // Implements blink::WebPrerendererClient
+ void WillAddPrerender(blink::WebPrerender* prerender) override;
+ bool IsPrefetchOnly() override;
+};
+
+} // namespace prerender
+
+#endif // CHROME_RENDERER_PRERENDER_PRERENDERER_CLIENT_H_
+
diff --git a/chromium/chrome/renderer/previews/OWNERS b/chromium/chrome/renderer/previews/OWNERS
new file mode 100644
index 00000000000..75baba8b67f
--- /dev/null
+++ b/chromium/chrome/renderer/previews/OWNERS
@@ -0,0 +1,3 @@
+file://components/data_reduction_proxy/OWNERS
+
+# COMPONENT: Blink>Previews \ No newline at end of file
diff --git a/chromium/chrome/renderer/previews/resource_loading_hints_agent.cc b/chromium/chrome/renderer/previews/resource_loading_hints_agent.cc
new file mode 100644
index 00000000000..e17ce0cb48c
--- /dev/null
+++ b/chromium/chrome/renderer/previews/resource_loading_hints_agent.cc
@@ -0,0 +1,103 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/previews/resource_loading_hints_agent.h"
+
+#include <vector>
+
+#include "base/metrics/histogram_macros.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/platform/web_loading_hints_provider.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_document_loader.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace previews {
+
+namespace {
+
+const blink::WebVector<blink::WebString> convert_to_web_vector(
+ const std::vector<std::string>& subresource_patterns_to_block) {
+ blink::WebVector<blink::WebString> web_vector(
+ subresource_patterns_to_block.size());
+ for (const std::string& element : subresource_patterns_to_block) {
+ web_vector.emplace_back(blink::WebString::FromASCII(element));
+ }
+ return web_vector;
+}
+
+} // namespace
+
+ResourceLoadingHintsAgent::ResourceLoadingHintsAgent(
+ blink::AssociatedInterfaceRegistry* associated_interfaces,
+ content::RenderFrame* render_frame)
+ : content::RenderFrameObserver(render_frame) {
+ DCHECK(render_frame);
+ DCHECK(IsMainFrame());
+
+ associated_interfaces->AddInterface(base::BindRepeating(
+ &ResourceLoadingHintsAgent::SetReceiver, base::Unretained(this)));
+}
+
+GURL ResourceLoadingHintsAgent::GetDocumentURL() const {
+ return render_frame()->GetWebFrame()->GetDocument().Url();
+}
+
+void ResourceLoadingHintsAgent::DidCreateNewDocument() {
+ DCHECK(IsMainFrame());
+ if (!GetDocumentURL().SchemeIsHTTPOrHTTPS())
+ return;
+ if (subresource_patterns_to_block_.empty())
+ return;
+
+ blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+ DCHECK(web_frame);
+
+ std::unique_ptr<blink::WebLoadingHintsProvider> loading_hints =
+ std::make_unique<blink::WebLoadingHintsProvider>(
+ ukm_source_id_.value(),
+ convert_to_web_vector(subresource_patterns_to_block_));
+
+ web_frame->GetDocumentLoader()->SetLoadingHintsProvider(
+ std::move(loading_hints));
+ // Once the hints are sent to the document loader, clear the local copy to
+ // prevent accidental reuse.
+ subresource_patterns_to_block_.clear();
+}
+
+void ResourceLoadingHintsAgent::OnDestruct() {
+ delete this;
+}
+
+ResourceLoadingHintsAgent::~ResourceLoadingHintsAgent() = default;
+
+void ResourceLoadingHintsAgent::SetReceiver(
+ mojo::PendingAssociatedReceiver<
+ blink::mojom::PreviewsResourceLoadingHintsReceiver> receiver) {
+ receiver_.Bind(std::move(receiver));
+}
+
+bool ResourceLoadingHintsAgent::IsMainFrame() const {
+ return render_frame()->IsMainFrame();
+}
+
+void ResourceLoadingHintsAgent::SetResourceLoadingHints(
+ blink::mojom::PreviewsResourceLoadingHintsPtr resource_loading_hints) {
+ DCHECK(IsMainFrame());
+
+ UMA_HISTOGRAM_COUNTS_100(
+ "ResourceLoadingHints.CountBlockedSubresourcePatterns",
+ resource_loading_hints->subresources_to_block.size());
+
+ ukm_source_id_ = resource_loading_hints->ukm_source_id;
+
+ for (const auto& subresource :
+ resource_loading_hints->subresources_to_block) {
+ subresource_patterns_to_block_.push_back(subresource);
+ }
+}
+
+} // namespace previews
diff --git a/chromium/chrome/renderer/previews/resource_loading_hints_agent.h b/chromium/chrome/renderer/previews/resource_loading_hints_agent.h
new file mode 100644
index 00000000000..a219957cc1b
--- /dev/null
+++ b/chromium/chrome/renderer/previews/resource_loading_hints_agent.h
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PREVIEWS_RESOURCE_LOADING_HINTS_AGENT_H_
+#define CHROME_RENDERER_PREVIEWS_RESOURCE_LOADING_HINTS_AGENT_H_
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/mojom/loader/previews_resource_loading_hints.mojom-blink.h"
+#include "third_party/blink/public/mojom/loader/previews_resource_loading_hints.mojom.h"
+#include "url/gurl.h"
+
+namespace previews {
+
+// The renderer-side agent of ResourceLoadingHintsAgent. There is one instance
+// per main frame, responsible for sending the loading hints from browser to
+// the document loader.
+class ResourceLoadingHintsAgent
+ : public content::RenderFrameObserver,
+ public blink::mojom::PreviewsResourceLoadingHintsReceiver,
+ public base::SupportsWeakPtr<ResourceLoadingHintsAgent> {
+ public:
+ ResourceLoadingHintsAgent(
+ blink::AssociatedInterfaceRegistry* associated_interfaces,
+ content::RenderFrame* render_frame);
+ ~ResourceLoadingHintsAgent() override;
+
+ private:
+ // content::RenderFrameObserver:
+ void DidCreateNewDocument() override;
+ void OnDestruct() override;
+
+ GURL GetDocumentURL() const;
+
+ // blink::mojom::PreviewsResourceLoadingHintsReceiver:
+ void SetResourceLoadingHints(blink::mojom::PreviewsResourceLoadingHintsPtr
+ resource_loading_hints) override;
+
+ void SetReceiver(
+ mojo::PendingAssociatedReceiver<
+ blink::mojom::PreviewsResourceLoadingHintsReceiver> receiver);
+
+ bool IsMainFrame() const;
+
+ std::vector<std::string> subresource_patterns_to_block_;
+ base::Optional<int64_t> ukm_source_id_;
+
+ mojo::AssociatedReceiver<blink::mojom::PreviewsResourceLoadingHintsReceiver>
+ receiver_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceLoadingHintsAgent);
+};
+
+} // namespace previews
+
+#endif // CHROME_RENDERER_PREVIEWS_RESOURCE_LOADING_HINTS_AGENT_H_
diff --git a/chromium/chrome/renderer/printing/OWNERS b/chromium/chrome/renderer/printing/OWNERS
new file mode 100644
index 00000000000..d50bb849014
--- /dev/null
+++ b/chromium/chrome/renderer/printing/OWNERS
@@ -0,0 +1,3 @@
+file://printing/OWNERS
+
+# COMPONENT: Internals>Printing
diff --git a/chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.cc b/chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.cc
new file mode 100644
index 00000000000..69cde8a84ba
--- /dev/null
+++ b/chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/printing/chrome_print_render_frame_helper_delegate.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/strings/string_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/prerender.mojom.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/renderer/prerender/prerender_helper.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/buildflags/buildflags.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/renderer/guest_view/mime_handler_view/post_message_support.h"
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+ChromePrintRenderFrameHelperDelegate::ChromePrintRenderFrameHelperDelegate() =
+ default;
+
+ChromePrintRenderFrameHelperDelegate::~ChromePrintRenderFrameHelperDelegate() =
+ default;
+
+bool ChromePrintRenderFrameHelperDelegate::CancelPrerender(
+ content::RenderFrame* render_frame) {
+ if (!prerender::PrerenderHelper::IsPrerendering(render_frame))
+ return false;
+
+ mojo::Remote<chrome::mojom::PrerenderCanceler> canceler;
+ render_frame->GetRemoteInterfaces()->GetInterface(
+ canceler.BindNewPipeAndPassReceiver());
+ canceler->CancelPrerenderForPrinting();
+ return true;
+}
+
+// Return the PDF object element if |frame| is the out of process PDF extension.
+blink::WebElement ChromePrintRenderFrameHelperDelegate::GetPdfElement(
+ blink::WebLocalFrame* frame) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ GURL url = frame->GetDocument().Url();
+ bool inside_print_preview = url.GetOrigin() == chrome::kChromeUIPrintURL;
+ bool inside_pdf_extension =
+ url.SchemeIs(extensions::kExtensionScheme) &&
+ url.host_piece() == extension_misc::kPdfExtensionId;
+ if (inside_print_preview || inside_pdf_extension) {
+ // <object> with id="plugin" is created in
+ // chrome/browser/resources/pdf/pdf_viewer.js.
+ auto plugin_element = frame->GetDocument().GetElementById("plugin");
+ if (!plugin_element.IsNull()) {
+ return plugin_element;
+ }
+ NOTREACHED();
+ }
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+ return blink::WebElement();
+}
+
+bool ChromePrintRenderFrameHelperDelegate::IsPrintPreviewEnabled() {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ return !command_line->HasSwitch(switches::kDisablePrintPreview);
+}
+
+bool ChromePrintRenderFrameHelperDelegate::OverridePrint(
+ blink::WebLocalFrame* frame) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ auto* post_message_support =
+ extensions::PostMessageSupport::FromWebLocalFrame(frame);
+ if (post_message_support) {
+ // This message is handled in chrome/browser/resources/pdf/pdf_viewer.js and
+ // instructs the PDF plugin to print. This is to make window.print() on a
+ // PDF plugin document correctly print the PDF. See
+ // https://crbug.com/448720.
+ base::DictionaryValue message;
+ message.SetString("type", "print");
+ post_message_support->PostMessageFromValue(message);
+ return true;
+ }
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+ return false;
+}
diff --git a/chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.h b/chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.h
new file mode 100644
index 00000000000..581d9f5d6b2
--- /dev/null
+++ b/chromium/chrome/renderer/printing/chrome_print_render_frame_helper_delegate.h
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_PRINTING_CHROME_PRINT_RENDER_FRAME_HELPER_DELEGATE_H_
+#define CHROME_RENDERER_PRINTING_CHROME_PRINT_RENDER_FRAME_HELPER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "components/printing/renderer/print_render_frame_helper.h"
+
+class ChromePrintRenderFrameHelperDelegate
+ : public printing::PrintRenderFrameHelper::Delegate {
+ public:
+ ChromePrintRenderFrameHelperDelegate();
+ ~ChromePrintRenderFrameHelperDelegate() override;
+
+ private:
+ // printing::PrintRenderFrameHelper::Delegate:
+ bool CancelPrerender(content::RenderFrame* render_frame) override;
+ blink::WebElement GetPdfElement(blink::WebLocalFrame* frame) override;
+ bool IsPrintPreviewEnabled() override;
+ bool OverridePrint(blink::WebLocalFrame* frame) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromePrintRenderFrameHelperDelegate);
+};
+
+#endif // CHROME_RENDERER_PRINTING_CHROME_PRINT_RENDER_FRAME_HELPER_DELEGATE_H_
diff --git a/chromium/chrome/renderer/safe_browsing/DEPS b/chromium/chrome/renderer/safe_browsing/DEPS
new file mode 100644
index 00000000000..5f594216755
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/DEPS
@@ -0,0 +1,28 @@
+include_rules = [
+ "+components/safe_browsing/common",
+ "+components/safe_browsing/proto/csd.pb.h",
+ "+components/safe_browsing/renderer",
+ "+components/safe_browsing/features.h",
+ "+third_party/smhasher",
+]
+
+# These are browser tests which run in single process mode, and so access both
+# code from the browser and the renderer.
+specific_include_rules = {
+ "phishing_classifier_browsertest\.cc": [
+ "+chrome/browser/ui/browser.h",
+ "+chrome/browser/ui/tabs/tab_strip_model.h",
+ "+content/public/browser",
+ ],
+ "phishing_classifier_delegate_browsertest\.cc": [
+ "+chrome/browser/ui/browser.h",
+ "+chrome/browser/ui/browser_commands.h",
+ "+chrome/browser/ui/tabs/tab_strip_model.h",
+ "+content/public/browser",
+ ],
+ "phishing_dom_feature_extractor_browsertest\.cc": [
+ "+chrome/browser/ui/browser.h",
+ "+chrome/browser/ui/tabs/tab_strip_model.h",
+ "+content/public/browser",
+ ],
+}
diff --git a/chromium/chrome/renderer/safe_browsing/OWNERS b/chromium/chrome/renderer/safe_browsing/OWNERS
new file mode 100644
index 00000000000..fede9e2915a
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/OWNERS
@@ -0,0 +1,5 @@
+drubery@chromium.org
+nparker@chromium.org
+vakh@chromium.org
+
+# COMPONENT: Services>Safebrowsing
diff --git a/chromium/chrome/renderer/safe_browsing/feature_extractor_clock.cc b/chromium/chrome/renderer/safe_browsing/feature_extractor_clock.cc
new file mode 100644
index 00000000000..45fbd4d4437
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/feature_extractor_clock.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
+
+namespace safe_browsing {
+
+FeatureExtractorClock::~FeatureExtractorClock() {}
+
+base::TimeTicks FeatureExtractorClock::Now() {
+ return base::TimeTicks::Now();
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/feature_extractor_clock.h b/chromium/chrome/renderer/safe_browsing/feature_extractor_clock.h
new file mode 100644
index 00000000000..930c1a4b7ab
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/feature_extractor_clock.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A simple abstraction for getting the current time during feature extraction.
+// This can be mocked out for testing.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_FEATURE_EXTRACTOR_CLOCK_H_
+#define CHROME_RENDERER_SAFE_BROWSING_FEATURE_EXTRACTOR_CLOCK_H_
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace safe_browsing {
+
+class FeatureExtractorClock {
+ public:
+ FeatureExtractorClock() {}
+ virtual ~FeatureExtractorClock();
+
+ // Returns the current time. May be mocked for testing.
+ virtual base::TimeTicks Now();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FeatureExtractorClock);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_FEATURE_EXTRACTOR_CLOCK_H_
diff --git a/chromium/chrome/renderer/safe_browsing/features.cc b/chromium/chrome/renderer/safe_browsing/features.cc
new file mode 100644
index 00000000000..a40462cf960
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/features.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/features.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace safe_browsing {
+
+const size_t FeatureMap::kMaxFeatureMapSize = 10000;
+
+FeatureMap::FeatureMap() {}
+FeatureMap::~FeatureMap() {}
+
+bool FeatureMap::AddBooleanFeature(const std::string& name) {
+ return AddRealFeature(name, 1.0);
+}
+
+bool FeatureMap::AddRealFeature(const std::string& name, double value) {
+ if (features_.size() >= kMaxFeatureMapSize) {
+ // If we hit this case, it indicates that either kMaxFeatureMapSize is
+ // too small, or there is a bug causing too many features to be added.
+ // In this case, we'll log to a histogram so we can see that this is
+ // happening, and make phishing classification fail silently.
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.TooManyFeatures", 1);
+ return false;
+ }
+ // We only expect features in the range [0.0, 1.0], so fail if the feature is
+ // outside this range.
+ if (value < 0.0 || value > 1.0) {
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.IllegalFeatureValue", 1);
+ return false;
+ }
+
+ features_[name] = value;
+ return true;
+}
+
+void FeatureMap::Clear() {
+ features_.clear();
+}
+
+namespace features {
+// URL host features
+const char kUrlHostIsIpAddress[] = "UrlHostIsIpAddress";
+const char kUrlTldToken[] = "UrlTld=";
+const char kUrlDomainToken[] = "UrlDomain=";
+const char kUrlOtherHostToken[] = "UrlOtherHostToken=";
+
+// URL host aggregate features
+const char kUrlNumOtherHostTokensGTOne[] = "UrlNumOtherHostTokens>1";
+const char kUrlNumOtherHostTokensGTThree[] = "UrlNumOtherHostTokens>3";
+
+// URL path features
+const char kUrlPathToken[] = "UrlPathToken=";
+
+// DOM HTML form features
+const char kPageHasForms[] = "PageHasForms";
+const char kPageActionOtherDomainFreq[] = "PageActionOtherDomainFreq";
+const char kPageActionURL[] = "PageActionURL=";
+const char kPageHasTextInputs[] = "PageHasTextInputs";
+const char kPageHasPswdInputs[] = "PageHasPswdInputs";
+const char kPageHasRadioInputs[] = "PageHasRadioInputs";
+const char kPageHasCheckInputs[] = "PageHasCheckInputs";
+
+// DOM HTML link features
+const char kPageExternalLinksFreq[] = "PageExternalLinksFreq";
+const char kPageLinkDomain[] = "PageLinkDomain=";
+const char kPageSecureLinksFreq[] = "PageSecureLinksFreq";
+
+// DOM HTML script features
+const char kPageNumScriptTagsGTOne[] = "PageNumScriptTags>1";
+const char kPageNumScriptTagsGTSix[] = "PageNumScriptTags>6";
+
+// Other DOM HTML features
+const char kPageImgOtherDomainFreq[] = "PageImgOtherDomainFreq";
+
+// Page term features
+const char kPageTerm[] = "PageTerm=";
+
+} // namespace features
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/features.h b/chromium/chrome/renderer/safe_browsing/features.h
new file mode 100644
index 00000000000..e6f21dfac3a
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/features.h
@@ -0,0 +1,181 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Common types and constants for extracting and evaluating features in the
+// client-side phishing detection model. A feature is simply a string and an
+// associated floating-point value between 0 and 1. The phishing
+// classification model contains rules which give an appropriate weight to each
+// feature or combination of features. These values can then be summed to
+// compute a final phishiness score.
+//
+// Some features are boolean features. If these features are set, they always
+// have a value of 0.0 or 1.0. In practice, the features are only set if the
+// value is true (1.0).
+//
+// We also use token features. These features have a unique name that is
+// constructed from the URL or page contents that we are classifying, for
+// example, "UrlDomain=chromium". These features are also always set to 1.0
+// if they are present.
+//
+// The intermediate storage of the features for a URL is a FeatureMap, which is
+// just a thin wrapper around a map of feature name to value. The entire set
+// of features for a URL is extracted before we do any scoring.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_FEATURES_H_
+#define CHROME_RENDERER_SAFE_BROWSING_FEATURES_H_
+
+#include <stddef.h>
+#include <string>
+#include <unordered_map>
+
+#include "base/macros.h"
+
+namespace safe_browsing {
+
+// Container for a map of features to values, which enforces behavior
+// such as a maximum number of features in the map.
+class FeatureMap {
+ public:
+ FeatureMap();
+ ~FeatureMap();
+
+ // Adds a boolean feature to a FeatureMap with a value of 1.0.
+ // Returns true on success, or false if the feature map exceeds
+ // kMaxFeatureMapSize.
+ bool AddBooleanFeature(const std::string& name);
+
+ // Adds a real-valued feature to a FeatureMap with the given value.
+ // Values must always be in the range [0.0, 1.0]. Returns true on
+ // success, or false if the feature map exceeds kMaxFeatureMapSize
+ // or the value is outside of the allowed range.
+ bool AddRealFeature(const std::string& name, double value);
+
+ // Provides read-only access to the current set of features.
+ const std::unordered_map<std::string, double>& features() const {
+ return features_;
+ }
+
+ // Clears the set of features in the map.
+ void Clear();
+
+ // This is an upper bound on the number of features that will be extracted.
+ // We should never hit this cap; it is intended as a sanity check to prevent
+ // the FeatureMap from growing too large.
+ static const size_t kMaxFeatureMapSize;
+
+ private:
+ std::unordered_map<std::string, double> features_;
+
+ DISALLOW_COPY_AND_ASSIGN(FeatureMap);
+};
+
+namespace features {
+// Constants for the various feature names that we use.
+//
+// IMPORTANT: when adding new features, you must update kAllowedFeatures in
+// chrome/browser/safe_browsing/client_side_detection_service.cc if the feature
+// should be sent in sanitized pingbacks.
+
+////////////////////////////////////////////////////
+// URL host features
+////////////////////////////////////////////////////
+
+// Set if the URL's hostname is an IP address.
+extern const char kUrlHostIsIpAddress[];
+// Token feature containing the portion of the hostname controlled by a
+// registrar, for example "com" or "co.uk".
+extern const char kUrlTldToken[];
+// Token feature containing the first host component below the registrar.
+// For example, in "www.google.com", the domain would be "google".
+extern const char kUrlDomainToken[];
+// Token feature containing each host component below the domain.
+// For example, in "www.host.example.com", both "www" and "host" would be
+// "other host tokens".
+extern const char kUrlOtherHostToken[];
+
+////////////////////////////////////////////////////
+// Aggregate features for URL host tokens
+////////////////////////////////////////////////////
+
+// Set if the number of "other" host tokens for a URL is greater than one.
+// Longer hostnames, regardless of the specific tokens, can be a signal that
+// the URL is phishy.
+extern const char kUrlNumOtherHostTokensGTOne[];
+// Set if the number of "other" host tokens for a URL is greater than three.
+extern const char kUrlNumOtherHostTokensGTThree[];
+
+////////////////////////////////////////////////////
+// URL path token features
+////////////////////////////////////////////////////
+
+// Token feature containing each alphanumeric string in the path that is at
+// least 3 characters long. For example, "/abc/d/efg" would have 2 path
+// token features, "abc" and "efg". Query parameters are not included.
+extern const char kUrlPathToken[];
+
+////////////////////////////////////////////////////
+// DOM HTML form features
+////////////////////////////////////////////////////
+
+// Set if the page has any <form> elements.
+extern const char kPageHasForms[];
+// The fraction of form elements whose |action| attribute points to a
+// URL on a different domain from the document URL.
+extern const char kPageActionOtherDomainFreq[];
+// Token feature containing each URL that an |action| attribute
+// points to.
+extern const char kPageActionURL[];
+// Set if the page has any <input type="text"> elements
+// (includes inputs with missing or unknown types).
+extern const char kPageHasTextInputs[];
+// Set if the page has any <input type="password"> elements.
+extern const char kPageHasPswdInputs[];
+// Set if the page has any <input type="radio"> elements.
+extern const char kPageHasRadioInputs[];
+// Set if the page has any <input type="checkbox"> elements.
+extern const char kPageHasCheckInputs[];
+
+////////////////////////////////////////////////////
+// DOM HTML link features
+////////////////////////////////////////////////////
+
+// The fraction of links in the page which point to a domain other than the
+// domain of the document. See "URL host features" above for a discussion
+// of how the doamin is computed.
+extern const char kPageExternalLinksFreq[];
+// Token feature containing each external domain that is linked to.
+extern const char kPageLinkDomain[];
+// Fraction of links in the page that use https.
+extern const char kPageSecureLinksFreq[];
+
+////////////////////////////////////////////////////
+// DOM HTML script features
+////////////////////////////////////////////////////
+
+// Set if the number of <script> elements in the page is greater than 1.
+extern const char kPageNumScriptTagsGTOne[];
+// Set if the number of <script> elements in the page is greater than 6.
+extern const char kPageNumScriptTagsGTSix[];
+
+////////////////////////////////////////////////////
+// Other DOM HTML features
+////////////////////////////////////////////////////
+
+// The fraction of images whose src attribute points to an external domain.
+extern const char kPageImgOtherDomainFreq[];
+
+////////////////////////////////////////////////////
+// Page term features
+////////////////////////////////////////////////////
+
+// Token feature for a term (whitespace-delimited) on a page. Terms can be
+// single words or multi-word n-grams. Rather than adding this feature for
+// every possible token on a page, only the terms that are mentioned in the
+// classification model are added.
+extern const char kPageTerm[];
+
+} // namespace features
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_FEATURES_H_
diff --git a/chromium/chrome/renderer/safe_browsing/features_unittest.cc b/chromium/chrome/renderer/safe_browsing/features_unittest.cc
new file mode 100644
index 00000000000..0ba14e71f7c
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/features_unittest.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/features.h"
+
+#include <stddef.h>
+
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+TEST(PhishingFeaturesTest, TooManyFeatures) {
+ FeatureMap features;
+ for (size_t i = 0; i < FeatureMap::kMaxFeatureMapSize; ++i) {
+ EXPECT_TRUE(features.AddBooleanFeature(
+ base::StringPrintf("Feature%" PRIuS, i)));
+ }
+ EXPECT_EQ(FeatureMap::kMaxFeatureMapSize, features.features().size());
+
+ // Attempting to add more features should fail.
+ for (size_t i = 0; i < 3; ++i) {
+ EXPECT_FALSE(features.AddBooleanFeature(
+ base::StringPrintf("Extra%" PRIuS, i)));
+ }
+ EXPECT_EQ(FeatureMap::kMaxFeatureMapSize, features.features().size());
+}
+
+TEST(PhishingFeaturesTest, IllegalFeatureValue) {
+ FeatureMap features;
+ EXPECT_FALSE(features.AddRealFeature("toosmall", -0.1));
+ EXPECT_TRUE(features.AddRealFeature("zero", 0.0));
+ EXPECT_TRUE(features.AddRealFeature("pointfive", 0.5));
+ EXPECT_TRUE(features.AddRealFeature("one", 1.0));
+ EXPECT_FALSE(features.AddRealFeature("toolarge", 1.1));
+
+ FeatureMap expected_features;
+ expected_features.AddRealFeature("zero", 0.0);
+ expected_features.AddRealFeature("pointfive", 0.5);
+ expected_features.AddRealFeature("one", 1.0);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.cc b/chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.cc
new file mode 100644
index 00000000000..665baf0b5c3
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.cc
@@ -0,0 +1,14 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/mock_feature_extractor_clock.h"
+
+namespace safe_browsing {
+
+MockFeatureExtractorClock::MockFeatureExtractorClock()
+ : FeatureExtractorClock() {}
+
+MockFeatureExtractorClock::~MockFeatureExtractorClock() {}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.h b/chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.h
new file mode 100644
index 00000000000..7012ee81090
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/mock_feature_extractor_clock.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A mock implementation of FeatureExtractorClock for testing.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_MOCK_FEATURE_EXTRACTOR_CLOCK_H_
+#define CHROME_RENDERER_SAFE_BROWSING_MOCK_FEATURE_EXTRACTOR_CLOCK_H_
+
+#include "base/macros.h"
+#include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace safe_browsing {
+
+class MockFeatureExtractorClock : public FeatureExtractorClock {
+ public:
+ MockFeatureExtractorClock();
+ ~MockFeatureExtractorClock() override;
+
+ MOCK_METHOD0(Now, base::TimeTicks());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockFeatureExtractorClock);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_MOCK_FEATURE_EXTRACTOR_CLOCK_H_
diff --git a/chromium/chrome/renderer/safe_browsing/murmurhash3_util.cc b/chromium/chrome/renderer/safe_browsing/murmurhash3_util.cc
new file mode 100644
index 00000000000..4134203e0ed
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/murmurhash3_util.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/murmurhash3_util.h"
+#include "third_party/smhasher/src/MurmurHash3.h"
+
+namespace safe_browsing {
+
+uint32_t MurmurHash3String(const std::string& str, uint32_t seed) {
+ uint32_t output;
+ MurmurHash3_x86_32(str.data(), str.size(), seed, &output);
+ return output;
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/murmurhash3_util.h b/chromium/chrome/renderer/safe_browsing/murmurhash3_util.h
new file mode 100644
index 00000000000..8a4b1d092e1
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/murmurhash3_util.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_MURMURHASH3_UTIL_H_
+#define CHROME_RENDERER_SAFE_BROWSING_MURMURHASH3_UTIL_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace safe_browsing {
+
+// Runs the 32-bit murmurhash3 function on the given string and returns the
+// output as a uint32_t.
+uint32_t MurmurHash3String(const std::string& str, uint32_t seed);
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_MURMURHASH3_UTIL_H_
diff --git a/chromium/chrome/renderer/safe_browsing/murmurhash3_util_unittest.cc b/chromium/chrome/renderer/safe_browsing/murmurhash3_util_unittest.cc
new file mode 100644
index 00000000000..f550041e414
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/murmurhash3_util_unittest.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Unit test to verify basic operation of murmurhash3.
+
+#include "chrome/renderer/safe_browsing/murmurhash3_util.h"
+
+#include <string>
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+TEST(MurmurHash3UtilTest, MurmurHash3String) {
+ EXPECT_EQ(893017187U, MurmurHash3String("abcd", 1234U));
+ EXPECT_EQ(3322282861U, MurmurHash3String("abcde", 56789U));
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_classifier.cc b/chromium/chrome/renderer/safe_browsing/phishing_classifier.cc
new file mode 100644
index 00000000000..ee16a74f908
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_classifier.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_classifier.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h"
+#include "chrome/renderer/safe_browsing/phishing_term_feature_extractor.h"
+#include "chrome/renderer/safe_browsing/phishing_url_feature_extractor.h"
+#include "chrome/renderer/safe_browsing/scorer.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "content/public/renderer/render_frame.h"
+#include "crypto/sha2.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_document_loader.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "url/gurl.h"
+
+namespace safe_browsing {
+
+const float PhishingClassifier::kInvalidScore = -1.0;
+const float PhishingClassifier::kPhishyThreshold = 0.5;
+
+PhishingClassifier::PhishingClassifier(content::RenderFrame* render_frame,
+ FeatureExtractorClock* clock)
+ : render_frame_(render_frame), scorer_(nullptr), clock_(clock) {
+ Clear();
+}
+
+PhishingClassifier::~PhishingClassifier() {
+ // The RenderView should have called CancelPendingClassification() before
+ // we are destroyed.
+ CheckNoPendingClassification();
+}
+
+void PhishingClassifier::set_phishing_scorer(const Scorer* scorer) {
+ CheckNoPendingClassification();
+ scorer_ = scorer;
+ if (scorer_) {
+ url_extractor_.reset(new PhishingUrlFeatureExtractor);
+ dom_extractor_.reset(new PhishingDOMFeatureExtractor(clock_.get()));
+ term_extractor_.reset(new PhishingTermFeatureExtractor(
+ &scorer_->page_terms(),
+ &scorer_->page_words(),
+ scorer_->max_words_per_term(),
+ scorer_->murmurhash3_seed(),
+ scorer_->max_shingles_per_page(),
+ scorer_->shingle_size(),
+ clock_.get()));
+ } else {
+ // We're disabling client-side phishing detection, so tear down all
+ // of the relevant objects.
+ url_extractor_.reset();
+ dom_extractor_.reset();
+ term_extractor_.reset();
+ }
+}
+
+bool PhishingClassifier::is_ready() const {
+ return scorer_ != NULL;
+}
+
+void PhishingClassifier::BeginClassification(const base::string16* page_text,
+ DoneCallback done_callback) {
+ DCHECK(is_ready());
+
+ // The RenderView should have called CancelPendingClassification() before
+ // starting a new classification, so DCHECK this.
+ CheckNoPendingClassification();
+ // However, in an opt build, we will go ahead and clean up the pending
+ // classification so that we can start in a known state.
+ CancelPendingClassification();
+
+ page_text_ = page_text;
+ done_callback_ = std::move(done_callback);
+
+ // For consistency, we always want to invoke the DoneCallback
+ // asynchronously, rather than directly from this method. To ensure that
+ // this is the case, post a task to begin feature extraction on the next
+ // iteration of the message loop.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PhishingClassifier::BeginFeatureExtraction,
+ weak_factory_.GetWeakPtr()));
+}
+
+void PhishingClassifier::BeginFeatureExtraction() {
+ blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
+
+ // Check whether the URL is one that we should classify.
+ // Currently, we only classify http/https URLs that are GET requests.
+ GURL url(frame->GetDocument().Url());
+ if (!url.SchemeIsHTTPOrHTTPS()) {
+ RunFailureCallback();
+ return;
+ }
+
+ blink::WebDocumentLoader* document_loader = frame->GetDocumentLoader();
+ if (!document_loader || document_loader->HttpMethod().Ascii() != "GET") {
+ RunFailureCallback();
+ return;
+ }
+
+ features_.reset(new FeatureMap);
+ if (!url_extractor_->ExtractFeatures(url, features_.get())) {
+ RunFailureCallback();
+ return;
+ }
+
+ // DOM feature extraction can take awhile, so it runs asynchronously
+ // in several chunks of work and invokes the callback when finished.
+ dom_extractor_->ExtractFeatures(
+ frame->GetDocument(), features_.get(),
+ base::BindOnce(&PhishingClassifier::DOMExtractionFinished,
+ base::Unretained(this)));
+}
+
+void PhishingClassifier::CancelPendingClassification() {
+ // Note that cancelling the feature extractors is simply a no-op if they
+ // were not running.
+ DCHECK(is_ready());
+ dom_extractor_->CancelPendingExtraction();
+ term_extractor_->CancelPendingExtraction();
+ weak_factory_.InvalidateWeakPtrs();
+ Clear();
+}
+
+void PhishingClassifier::DOMExtractionFinished(bool success) {
+ shingle_hashes_.reset(new std::set<uint32_t>);
+ if (success) {
+ // Term feature extraction can take awhile, so it runs asynchronously
+ // in several chunks of work and invokes the callback when finished.
+ term_extractor_->ExtractFeatures(
+ page_text_, features_.get(), shingle_hashes_.get(),
+ base::BindOnce(&PhishingClassifier::TermExtractionFinished,
+ base::Unretained(this)));
+ } else {
+ RunFailureCallback();
+ }
+}
+
+void PhishingClassifier::TermExtractionFinished(bool success) {
+ if (success) {
+ blink::WebLocalFrame* main_frame = render_frame_->GetWebFrame();
+
+ // Hash all of the features so that they match the model, then compute
+ // the score.
+ FeatureMap hashed_features;
+ ClientPhishingRequest verdict;
+ verdict.set_model_version(scorer_->model_version());
+ verdict.set_url(main_frame->GetDocument().Url().GetString().Utf8());
+ for (const auto& it : features_->features()) {
+ bool result = hashed_features.AddRealFeature(
+ crypto::SHA256HashString(it.first), it.second);
+ DCHECK(result);
+ ClientPhishingRequest::Feature* feature = verdict.add_feature_map();
+ feature->set_name(it.first);
+ feature->set_value(it.second);
+ }
+ for (const auto& it : *shingle_hashes_) {
+ verdict.add_shingle_hashes(it);
+ }
+ float score = static_cast<float>(scorer_->ComputeScore(hashed_features));
+ verdict.set_client_score(score);
+ verdict.set_is_phishing(score >= kPhishyThreshold);
+ RunCallback(verdict);
+ } else {
+ RunFailureCallback();
+ }
+}
+
+void PhishingClassifier::CheckNoPendingClassification() {
+ DCHECK(done_callback_.is_null());
+ DCHECK(!page_text_);
+ if (!done_callback_.is_null() || page_text_) {
+ LOG(ERROR) << "Classification in progress, missing call to "
+ << "CancelPendingClassification";
+ }
+}
+
+void PhishingClassifier::RunCallback(const ClientPhishingRequest& verdict) {
+ std::move(done_callback_).Run(verdict);
+ Clear();
+}
+
+void PhishingClassifier::RunFailureCallback() {
+ ClientPhishingRequest verdict;
+ // In this case we're not guaranteed to have a valid URL. Just set it
+ // to the empty string to make sure we have a valid protocol buffer.
+ verdict.set_url("");
+ verdict.set_client_score(kInvalidScore);
+ verdict.set_is_phishing(false);
+ RunCallback(verdict);
+}
+
+void PhishingClassifier::Clear() {
+ page_text_ = NULL;
+ done_callback_.Reset();
+ features_.reset(NULL);
+ shingle_hashes_.reset(NULL);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_classifier.h b/chromium/chrome/renderer/safe_browsing/phishing_classifier.h
new file mode 100644
index 00000000000..9528c434cce
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_classifier.h
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This class handles the process of extracting all of the features from a
+// page and computing a phishyness score. The basic steps are:
+// - Run each feature extractor over the page, building up a FeatureMap of
+// feature -> value.
+// - SHA-256 hash all of the feature names in the map so that they match the
+// supplied model.
+// - Hand the hashed map off to a Scorer, which computes the probability that
+// the page is phishy.
+// - If the page is phishy, run the supplied callback.
+//
+// For more details, see phishing_*_feature_extractor.h, scorer.h, and
+// client_model.proto.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_PHISHING_CLASSIFIER_H_
+#define CHROME_RENDERER_SAFE_BROWSING_PHISHING_CLASSIFIER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace safe_browsing {
+class ClientPhishingRequest;
+class FeatureExtractorClock;
+class FeatureMap;
+class PhishingDOMFeatureExtractor;
+class PhishingTermFeatureExtractor;
+class PhishingUrlFeatureExtractor;
+class Scorer;
+
+class PhishingClassifier {
+ public:
+ // Callback to be run when phishing classification finishes. The verdict
+ // is a ClientPhishingRequest which contains the verdict computed by the
+ // classifier as well as the extracted features. If the verdict.is_phishing()
+ // is true, the page is considered phishy by the client-side model,
+ // and the browser should ping back to get a final verdict. The
+ // verdict.client_score() is set to kInvalidScore if classification failed.
+ typedef base::OnceCallback<void(const ClientPhishingRequest& /* verdict */)>
+ DoneCallback;
+
+ static const float kInvalidScore;
+
+ // Creates a new PhishingClassifier object that will operate on
+ // |render_view|. |clock| is used to time feature extractor operations, and
+ // the PhishingClassifier takes ownership of this object. Note that the
+ // classifier will not be 'ready' until set_phishing_scorer() is called.
+ PhishingClassifier(content::RenderFrame* render_frame,
+ FeatureExtractorClock* clock);
+ virtual ~PhishingClassifier();
+
+ // Sets a scorer for the classifier to use in computing the phishiness score.
+ // This must live at least as long as the PhishingClassifier. The caller is
+ // expected to cancel any pending classification before setting a phishing
+ // scorer.
+ void set_phishing_scorer(const Scorer* scorer);
+
+ // Returns true if the classifier is ready to classify pages, i.e. it
+ // has had a scorer set via set_phishing_scorer().
+ bool is_ready() const;
+
+ // Called by the RenderView when a page has finished loading. This begins
+ // the feature extraction and scoring process. |page_text| should contain
+ // the plain text of a web page, including any subframes, as returned by
+ // RenderView::CaptureText(). |page_text| is owned by the caller, and must
+ // not be destroyed until either |done_callback| is run or
+ // CancelPendingClassification() is called.
+ //
+ // To avoid blocking the render thread for too long, phishing classification
+ // may run in several chunks of work, posting a task to the current
+ // MessageLoop to continue processing. Once the scoring process is complete,
+ // |done_callback| is run on the current thread. PhishingClassifier takes
+ // ownership of the callback.
+ //
+ // It is an error to call BeginClassification if the classifier is not yet
+ // ready.
+ virtual void BeginClassification(const base::string16* page_text,
+ DoneCallback callback);
+
+ // Called by the RenderView (on the render thread) when a page is unloading
+ // or the RenderView is being destroyed. This cancels any extraction that
+ // is in progress. It is an error to call CancelPendingClassification if
+ // the classifier is not yet ready.
+ virtual void CancelPendingClassification();
+
+ private:
+ // Any score equal to or above this value is considered phishy.
+ static const float kPhishyThreshold;
+
+ // Begins the feature extraction process, by extracting URL features and
+ // beginning DOM feature extraction.
+ void BeginFeatureExtraction();
+
+ // Callback to be run when DOM feature extraction is complete.
+ // If it was successful, begins term feature extraction, otherwise
+ // runs the DoneCallback with a non-phishy verdict.
+ void DOMExtractionFinished(bool success);
+
+ // Callback to be run when term feature extraction is complete.
+ // If it was successful, computes a score and runs the DoneCallback.
+ // If extraction was unsuccessful, runs the DoneCallback with a
+ // non-phishy verdict.
+ void TermExtractionFinished(bool success);
+
+ // Helper to verify that there is no pending phishing classification. Dies
+ // in debug builds if the state is not as expected. This is a no-op in
+ // release builds.
+ void CheckNoPendingClassification();
+
+ // Helper method to run the DoneCallback and clear the state.
+ void RunCallback(const ClientPhishingRequest& verdict);
+
+ // Helper to run the DoneCallback when feature extraction has failed.
+ // This always signals a non-phishy verdict for the page, with kInvalidScore.
+ void RunFailureCallback();
+
+ // Clears the current state of the PhishingClassifier.
+ void Clear();
+
+ content::RenderFrame* render_frame_; // owns us
+ const Scorer* scorer_; // owned by the caller
+ std::unique_ptr<FeatureExtractorClock> clock_;
+ std::unique_ptr<PhishingUrlFeatureExtractor> url_extractor_;
+ std::unique_ptr<PhishingDOMFeatureExtractor> dom_extractor_;
+ std::unique_ptr<PhishingTermFeatureExtractor> term_extractor_;
+
+ // State for any in-progress extraction.
+ std::unique_ptr<FeatureMap> features_;
+ std::unique_ptr<std::set<uint32_t>> shingle_hashes_;
+ const base::string16* page_text_; // owned by the caller
+ DoneCallback done_callback_;
+
+ // Used in scheduling BeginFeatureExtraction tasks.
+ // These pointers are invalidated if classification is cancelled.
+ base::WeakPtrFactory<PhishingClassifier> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PhishingClassifier);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_PHISHING_CLASSIFIER_H_
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc b/chromium/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
new file mode 100644
index 00000000000..f9eb25ab600
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_classifier.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/common/safe_browsing/client_model.pb.h"
+#include "chrome/renderer/chrome_content_renderer_client.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/mock_feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/murmurhash3_util.h"
+#include "chrome/renderer/safe_browsing/scorer.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "crypto/sha2.h"
+#include "net/dns/mock_host_resolver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "url/gurl.h"
+
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::Not;
+using ::testing::Pair;
+
+namespace safe_browsing {
+
+class TestChromeContentRendererClient : public ChromeContentRendererClient {
+ public:
+ TestChromeContentRendererClient() {}
+ ~TestChromeContentRendererClient() override {}
+ // Since visited_link_slave_ in ChromeContentRenderClient never get initiated,
+ // overrides VisitedLinkedHash() function to prevent crashing.
+ uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override {
+ return 0;
+ }
+};
+
+class PhishingClassifierTest : public ChromeRenderViewTest {
+ protected:
+ PhishingClassifierTest()
+ : url_tld_token_net_(features::kUrlTldToken + std::string("net")),
+ page_link_domain_phishing_(features::kPageLinkDomain +
+ std::string("phishing.com")),
+ page_term_login_(features::kPageTerm + std::string("login")),
+ page_text_(base::ASCIIToUTF16("login")),
+ phishy_score_(PhishingClassifier::kInvalidScore) {}
+
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+ PrepareModel();
+ SetUpClassifier();
+ }
+
+ void PrepareModel() {
+ // Construct a model to test with. We include one feature from each of
+ // the feature extractors, which allows us to verify that they all ran.
+ ClientSideModel model;
+
+ model.add_hashes(crypto::SHA256HashString(url_tld_token_net_));
+ model.add_hashes(crypto::SHA256HashString(page_link_domain_phishing_));
+ model.add_hashes(crypto::SHA256HashString(page_term_login_));
+ model.add_hashes(crypto::SHA256HashString("login"));
+ model.add_hashes(crypto::SHA256HashString(features::kUrlTldToken +
+ std::string("net")));
+ model.add_hashes(crypto::SHA256HashString(features::kPageLinkDomain +
+ std::string("phishing.com")));
+ model.add_hashes(crypto::SHA256HashString(features::kPageTerm +
+ std::string("login")));
+ model.add_hashes(crypto::SHA256HashString("login"));
+
+ // Add a default rule with a non-phishy weight.
+ ClientSideModel::Rule* rule = model.add_rule();
+ rule->set_weight(-1.0);
+
+ // To give a phishy score, the total weight needs to be >= 0
+ // (0.5 when converted to a probability). This will only happen
+ // if all of the listed features are present.
+ rule = model.add_rule();
+ rule->add_feature(0);
+ rule->add_feature(1);
+ rule->add_feature(2);
+ rule->set_weight(1.0);
+
+ model.add_page_term(3);
+ model.set_murmur_hash_seed(2777808611U);
+ model.add_page_word(MurmurHash3String("login", model.murmur_hash_seed()));
+ model.set_max_words_per_term(1);
+ model.set_max_shingles_per_page(100);
+ model.set_shingle_size(3);
+
+ clock_ = new MockFeatureExtractorClock;
+ scorer_.reset(Scorer::Create(model.SerializeAsString()));
+ ASSERT_TRUE(scorer_.get());
+
+ // These tests don't exercise the extraction timing.
+ EXPECT_CALL(*clock_, Now())
+ .WillRepeatedly(::testing::Return(base::TimeTicks::Now()));
+ }
+
+ void SetUpClassifier() {
+ classifier_.reset(
+ new PhishingClassifier(view_->GetMainRenderFrame(), clock_));
+ // No scorer yet, so the classifier is not ready.
+ ASSERT_FALSE(classifier_->is_ready());
+
+ // Now set the scorer.
+ classifier_->set_phishing_scorer(scorer_.get());
+ ASSERT_TRUE(classifier_->is_ready());
+ }
+
+ // Helper method to start phishing classification.
+ void RunPhishingClassifier(const base::string16* page_text) {
+ feature_map_.Clear();
+
+ classifier_->BeginClassification(
+ page_text,
+ base::BindOnce(&PhishingClassifierTest::ClassificationFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Completion callback for classification.
+ void ClassificationFinished(const ClientPhishingRequest& verdict) {
+ phishy_score_ = verdict.client_score();
+ for (int i = 0; i < verdict.feature_map_size(); ++i) {
+ feature_map_.AddRealFeature(verdict.feature_map(i).name(),
+ verdict.feature_map(i).value());
+ }
+ is_phishing_ = verdict.is_phishing();
+ }
+
+ void LoadHtml(const GURL& url, const std::string& content) {
+ LoadHTMLWithUrlOverride(content.c_str(), url.spec().c_str());
+ }
+
+ content::ContentRendererClient* CreateContentRendererClient() override {
+ ChromeContentRendererClient* client = new TestChromeContentRendererClient();
+ InitChromeContentRendererClient(client);
+ return client;
+ }
+
+ std::string response_content_;
+ std::unique_ptr<Scorer> scorer_;
+ std::unique_ptr<PhishingClassifier> classifier_;
+ MockFeatureExtractorClock* clock_; // Owned by classifier_.
+
+ // Features that are in the model.
+ const std::string url_tld_token_net_;
+ const std::string page_link_domain_phishing_;
+ const std::string page_term_login_;
+ base::string16 page_text_;
+
+ // Outputs of phishing classifier.
+ FeatureMap feature_map_;
+ float phishy_score_;
+ bool is_phishing_;
+};
+
+TEST_F(PhishingClassifierTest, TestClassificationOfPhishingDotComHttp) {
+ LoadHtml(
+ GURL("http://host.net"),
+ "<html><body><a href=\"http://phishing.com/\">login</a></body></html>");
+ RunPhishingClassifier(&page_text_);
+
+ // Note: features.features() might contain other features that simply aren't
+ // in the model.
+ EXPECT_THAT(feature_map_.features(),
+ AllOf(Contains(Pair(url_tld_token_net_, 1.0)),
+ Contains(Pair(page_link_domain_phishing_, 1.0)),
+ Contains(Pair(page_term_login_, 1.0))));
+ EXPECT_FLOAT_EQ(0.5, phishy_score_);
+ EXPECT_TRUE(is_phishing_);
+}
+
+TEST_F(PhishingClassifierTest, TestClassificationOfPhishingDotComHttps) {
+ // Host the target page on HTTPS.
+ LoadHtml(
+ GURL("https://host.net"),
+ "<html><body><a href=\"http://phishing.com/\">login</a></body></html>");
+ RunPhishingClassifier(&page_text_);
+
+ // Note: features.features() might contain other features that simply aren't
+ // in the model.
+ EXPECT_THAT(feature_map_.features(),
+ AllOf(Contains(Pair(url_tld_token_net_, 1.0)),
+ Contains(Pair(page_link_domain_phishing_, 1.0)),
+ Contains(Pair(page_term_login_, 1.0))));
+ EXPECT_FLOAT_EQ(0.5, phishy_score_);
+ EXPECT_TRUE(is_phishing_);
+}
+
+TEST_F(PhishingClassifierTest, TestClassificationOfSafeDotComHttp) {
+ // Change the link domain to something non-phishy.
+ LoadHtml(GURL("http://host.net"),
+ "<html><body><a href=\"http://safe.com/\">login</a></body></html>");
+ RunPhishingClassifier(&page_text_);
+
+ EXPECT_THAT(feature_map_.features(),
+ AllOf(Contains(Pair(url_tld_token_net_, 1.0)),
+ Contains(Pair(page_term_login_, 1.0))));
+ EXPECT_THAT(feature_map_.features(),
+ Not(Contains(Pair(page_link_domain_phishing_, 1.0))));
+ EXPECT_GE(phishy_score_, 0.0);
+ EXPECT_LT(phishy_score_, 0.5);
+ EXPECT_FALSE(is_phishing_);
+}
+
+TEST_F(PhishingClassifierTest, TestClassificationOfSafeDotComHttps) {
+ // Host target page in HTTPS and change the link domain to something
+ // non-phishy.
+ LoadHtml(GURL("https://host.net"),
+ "<html><body><a href=\"http://safe.com/\">login</a></body></html>");
+ RunPhishingClassifier(&page_text_);
+
+ EXPECT_THAT(feature_map_.features(),
+ AllOf(Contains(Pair(url_tld_token_net_, 1.0)),
+ Contains(Pair(page_term_login_, 1.0))));
+ EXPECT_THAT(feature_map_.features(),
+ Not(Contains(Pair(page_link_domain_phishing_, 1.0))));
+ EXPECT_GE(phishy_score_, 0.0);
+ EXPECT_LT(phishy_score_, 0.5);
+ EXPECT_FALSE(is_phishing_);
+}
+
+TEST_F(PhishingClassifierTest, TestClassificationWhenNoTld) {
+ // Extraction should fail for this case since there is no TLD.
+ LoadHtml(GURL("http://localhost"), "<html><body>content</body></html>");
+ RunPhishingClassifier(&page_text_);
+
+ EXPECT_EQ(0U, feature_map_.features().size());
+ EXPECT_EQ(PhishingClassifier::kInvalidScore, phishy_score_);
+ EXPECT_FALSE(is_phishing_);
+}
+
+TEST_F(PhishingClassifierTest, TestClassificationWhenSchemeNotSupported) {
+ // Extraction should also fail for this case because the URL is not http or
+ // https.
+ LoadHtml(GURL("file://host.net"), "<html><body>content</body></html>");
+ RunPhishingClassifier(&page_text_);
+
+ EXPECT_EQ(0U, feature_map_.features().size());
+ EXPECT_EQ(PhishingClassifier::kInvalidScore, phishy_score_);
+ EXPECT_FALSE(is_phishing_);
+}
+
+TEST_F(PhishingClassifierTest, DisableDetection) {
+ EXPECT_TRUE(classifier_->is_ready());
+ // Set a NULL scorer, which turns detection back off.
+ classifier_->set_phishing_scorer(NULL);
+ EXPECT_FALSE(classifier_->is_ready());
+}
+
+// TODO(jialiul): Add test to verify that classification only starts on GET
+// method. It seems there is no easy way to simulate a HTTP POST in
+// ChromeRenderViewTest.
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc b/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
new file mode 100644
index 00000000000..73d6b08f92d
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
@@ -0,0 +1,307 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
+
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/debug/stack_trace.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/phishing_classifier.h"
+#include "chrome/renderer/safe_browsing/scorer.h"
+#include "components/safe_browsing/common/safe_browsing.mojom-forward.h"
+#include "components/safe_browsing/common/safe_browsing.mojom-shared.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "content/public/renderer/document_state.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+
+using content::DocumentState;
+using content::RenderThread;
+
+namespace safe_browsing {
+
+namespace {
+
+GURL StripRef(const GURL& url) {
+ GURL::Replacements replacements;
+ replacements.ClearRef();
+ return url.ReplaceComponents(replacements);
+}
+
+std::set<PhishingClassifierDelegate*>& PhishingClassifierDelegates() {
+ static base::NoDestructor<std::set<PhishingClassifierDelegate*>> s;
+ return *s;
+}
+
+base::LazyInstance<std::unique_ptr<const safe_browsing::Scorer>>::
+ DestructorAtExit g_phishing_scorer = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+void PhishingClassifierFilter::Create(
+ mojo::PendingReceiver<mojom::PhishingModelSetter> receiver) {
+ mojo::MakeSelfOwnedReceiver(std::make_unique<PhishingClassifierFilter>(),
+ std::move(receiver));
+}
+
+PhishingClassifierFilter::PhishingClassifierFilter() {}
+
+PhishingClassifierFilter::~PhishingClassifierFilter() {}
+
+void PhishingClassifierFilter::SetPhishingModel(const std::string& model) {
+ safe_browsing::Scorer* scorer = NULL;
+ // An empty model string means we should disable client-side phishing
+ // detection.
+ if (!model.empty()) {
+ scorer = safe_browsing::Scorer::Create(model);
+ if (!scorer) {
+ DLOG(ERROR) << "Unable to create a PhishingScorer - corrupt model?";
+ return;
+ }
+ }
+ for (auto* delegate : PhishingClassifierDelegates())
+ delegate->SetPhishingScorer(scorer);
+ g_phishing_scorer.Get().reset(scorer);
+}
+
+// static
+PhishingClassifierDelegate* PhishingClassifierDelegate::Create(
+ content::RenderFrame* render_frame,
+ PhishingClassifier* classifier) {
+ // Private constructor and public static Create() method to facilitate
+ // stubbing out this class for binary-size reduction purposes.
+ return new PhishingClassifierDelegate(render_frame, classifier);
+}
+
+PhishingClassifierDelegate::PhishingClassifierDelegate(
+ content::RenderFrame* render_frame,
+ PhishingClassifier* classifier)
+ : content::RenderFrameObserver(render_frame),
+ last_main_frame_transition_(ui::PAGE_TRANSITION_LINK),
+ have_page_text_(false),
+ is_classifying_(false) {
+ PhishingClassifierDelegates().insert(this);
+ if (!classifier) {
+ classifier =
+ new PhishingClassifier(render_frame, new FeatureExtractorClock());
+ }
+
+ classifier_.reset(classifier);
+
+ if (g_phishing_scorer.Get().get())
+ SetPhishingScorer(g_phishing_scorer.Get().get());
+
+ registry_.AddInterface(
+ base::BindRepeating(&PhishingClassifierDelegate::PhishingDetectorReceiver,
+ base::Unretained(this)));
+}
+
+PhishingClassifierDelegate::~PhishingClassifierDelegate() {
+ CancelPendingClassification(SHUTDOWN);
+ PhishingClassifierDelegates().erase(this);
+}
+
+void PhishingClassifierDelegate::SetPhishingScorer(
+ const safe_browsing::Scorer* scorer) {
+ if (is_classifying_) {
+ // If there is a classification going on right now it means we're
+ // actually replacing an existing scorer with a new model. In
+ // this case we simply cancel the current classification.
+ // TODO(noelutz): if this happens too frequently we could also
+ // replace the old scorer with the new one once classification is done
+ // but this would complicate the code somewhat.
+ CancelPendingClassification(NEW_PHISHING_SCORER);
+ }
+ classifier_->set_phishing_scorer(scorer);
+}
+
+void PhishingClassifierDelegate::PhishingDetectorReceiver(
+ mojo::PendingReceiver<mojom::PhishingDetector> receiver) {
+ phishing_detector_receivers_.Add(this, std::move(receiver));
+}
+
+void PhishingClassifierDelegate::OnInterfaceRequestForFrame(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle* interface_pipe) {
+ registry_.TryBindInterface(interface_name, interface_pipe);
+}
+
+void PhishingClassifierDelegate::StartPhishingDetection(
+ const GURL& url,
+ StartPhishingDetectionCallback callback) {
+ if (!callback_.is_null())
+ std::move(callback_).Run(mojom::PhishingDetectorResult::CANCELLED, "");
+
+ last_url_received_from_browser_ = StripRef(url);
+ callback_ = std::move(callback);
+ // Start classifying the current page if all conditions are met.
+ // See MaybeStartClassification() for details.
+ MaybeStartClassification();
+}
+
+void PhishingClassifierDelegate::DidCommitProvisionalLoad(
+ bool is_same_document_navigation,
+ ui::PageTransition transition) {
+ blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+ // A new page is starting to load, so cancel classificaiton.
+ //
+ // TODO(bryner): We shouldn't need to cancel classification if the navigation
+ // is within the same document. However, if we let classification continue in
+ // this case, we need to properly deal with the fact that PageCaptured will
+ // be called again for the same-document navigation. We need to be sure not
+ // to swap out the page text while the term feature extractor is still
+ // running.
+ CancelPendingClassification(is_same_document_navigation ? NAVIGATE_WITHIN_PAGE
+ : NAVIGATE_AWAY);
+ if (frame->Parent())
+ return;
+
+ last_main_frame_transition_ = transition;
+}
+
+void PhishingClassifierDelegate::PageCaptured(base::string16* page_text,
+ bool preliminary_capture) {
+ if (preliminary_capture) {
+ return;
+ }
+ // Make sure there's no classification in progress. We don't want to swap
+ // out the page text string from underneath the term feature extractor.
+ //
+ // Note: Currently, if the url hasn't changed, we won't restart
+ // classification in this case. We may want to adjust this.
+ CancelPendingClassification(PAGE_RECAPTURED);
+ last_finished_load_url_ = render_frame()->GetWebFrame()->GetDocument().Url();
+ classifier_page_text_.swap(*page_text);
+ have_page_text_ = true;
+
+ GURL stripped_last_load_url(StripRef(last_finished_load_url_));
+ if (stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) {
+ DVLOG(2) << "Toplevel URL is unchanged, not starting classification.";
+ return;
+ }
+
+ UMA_HISTOGRAM_BOOLEAN(
+ "SBClientPhishing.PageCapturedMatchesBrowserURL",
+ (last_url_received_from_browser_ == stripped_last_load_url));
+
+ MaybeStartClassification();
+}
+
+void PhishingClassifierDelegate::CancelPendingClassification(
+ CancelClassificationReason reason) {
+ if (is_classifying_) {
+ UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.CancelClassificationReason",
+ reason,
+ CANCEL_CLASSIFICATION_MAX);
+ is_classifying_ = false;
+ }
+ if (classifier_->is_ready()) {
+ classifier_->CancelPendingClassification();
+ }
+ classifier_page_text_.clear();
+ have_page_text_ = false;
+}
+
+void PhishingClassifierDelegate::ClassificationDone(
+ const ClientPhishingRequest& verdict) {
+ DVLOG(2) << "Phishy verdict = " << verdict.is_phishing()
+ << " score = " << verdict.client_score();
+ if (callback_.is_null())
+ return;
+
+ if (verdict.client_score() != PhishingClassifier::kInvalidScore) {
+ DCHECK_EQ(last_url_sent_to_classifier_.spec(), verdict.url());
+ std::move(callback_).Run(mojom::PhishingDetectorResult::SUCCESS,
+ verdict.SerializeAsString());
+ } else {
+ std::move(callback_).Run(mojom::PhishingDetectorResult::INVALID_SCORE,
+ verdict.SerializeAsString());
+ }
+}
+
+void PhishingClassifierDelegate::MaybeStartClassification() {
+ // We can begin phishing classification when the following conditions are
+ // met:
+ // 1. A Scorer has been created
+ // 2. The browser has sent a StartPhishingDetection message for the current
+ // toplevel URL.
+ // 3. The page has finished loading and the page text has been extracted.
+ // 4. The load is a new navigation (not a session history navigation).
+ // 5. The toplevel URL has not already been classified.
+ //
+ // Note that if we determine that this particular navigation should not be
+ // classified at all (as opposed to deferring it until we get an IPC or the
+ // load completes), we discard the page text since it won't be needed.
+ if (!classifier_->is_ready()) {
+ DVLOG(2) << "Not starting classification, no Scorer created.";
+ // Keep classifier_page_text_, in case a Scorer is set later.
+ if (!callback_.is_null())
+ std::move(callback_).Run(
+ mojom::PhishingDetectorResult::CLASSIFIER_NOT_READY, "");
+ return;
+ }
+
+ if (last_main_frame_transition_ & ui::PAGE_TRANSITION_FORWARD_BACK) {
+ // Skip loads from session history navigation. However, update the
+ // last URL sent to the classifier, so that we'll properly detect
+ // same-document navigations.
+ DVLOG(2) << "Not starting classification for back/forward navigation";
+ last_url_sent_to_classifier_ = last_finished_load_url_;
+ classifier_page_text_.clear(); // we won't need this.
+ have_page_text_ = false;
+ if (!callback_.is_null())
+ std::move(callback_).Run(
+ mojom::PhishingDetectorResult::FORWARD_BACK_TRANSITION, "");
+ return;
+ }
+
+ GURL stripped_last_load_url(StripRef(last_finished_load_url_));
+ if (!have_page_text_) {
+ DVLOG(2) << "Not starting classification, there is no page text ready.";
+ return;
+ }
+
+ if (last_url_received_from_browser_ != stripped_last_load_url) {
+ // The browser has not yet confirmed that this URL should be classified,
+ // so defer classification for now. Note: the ref does not affect
+ // any of the browser's preclassification checks, so we don't require it
+ // to match.
+ DVLOG(2) << "Not starting classification, last url from browser is "
+ << last_url_received_from_browser_ << ", last finished load is "
+ << last_finished_load_url_;
+ // Keep classifier_page_text_, in case the browser notifies us later that
+ // we should classify the URL.
+ return;
+ }
+
+ DVLOG(2) << "Starting classification for " << last_finished_load_url_;
+ last_url_sent_to_classifier_ = last_finished_load_url_;
+ is_classifying_ = true;
+ classifier_->BeginClassification(
+ &classifier_page_text_,
+ base::BindOnce(&PhishingClassifierDelegate::ClassificationDone,
+ base::Unretained(this)));
+}
+
+void PhishingClassifierDelegate::OnDestruct() {
+ delete this;
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.h b/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.h
new file mode 100644
index 00000000000..91755fe80c3
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This class is used by the RenderView to interact with a PhishingClassifier.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_PHISHING_CLASSIFIER_DELEGATE_H_
+#define CHROME_RENDERER_SAFE_BROWSING_PHISHING_CLASSIFIER_DELEGATE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "components/safe_browsing/common/safe_browsing.mojom.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+namespace safe_browsing {
+class ClientPhishingRequest;
+class PhishingClassifier;
+class Scorer;
+
+class PhishingClassifierFilter : public mojom::PhishingModelSetter {
+ public:
+ PhishingClassifierFilter();
+ ~PhishingClassifierFilter() override;
+
+ static void Create(
+ mojo::PendingReceiver<mojom::PhishingModelSetter> receiver);
+
+ private:
+ // mojom::PhishingModelSetter
+ void SetPhishingModel(const std::string& model) override;
+
+ DISALLOW_COPY_AND_ASSIGN(PhishingClassifierFilter);
+};
+
+class PhishingClassifierDelegate : public content::RenderFrameObserver,
+ public mojom::PhishingDetector {
+ public:
+ // The RenderFrame owns us. This object takes ownership of the classifier.
+ // Note that if classifier is null, a default instance of PhishingClassifier
+ // will be used.
+ static PhishingClassifierDelegate* Create(content::RenderFrame* render_frame,
+ PhishingClassifier* classifier);
+ ~PhishingClassifierDelegate() override;
+
+ // Called by the RenderFrame once there is a phishing scorer available.
+ // The scorer is passed on to the classifier.
+ void SetPhishingScorer(const safe_browsing::Scorer* scorer);
+
+ // Called by the RenderFrame once a page has finished loading. Updates the
+ // last-loaded URL and page text, then starts classification if all other
+ // conditions are met (see MaybeStartClassification for details).
+ // We ignore preliminary captures, since these happen before the page has
+ // finished loading.
+ void PageCaptured(base::string16* page_text, bool preliminary_capture);
+
+ // RenderFrameObserver implementation, public for testing.
+
+ // Called by the RenderFrame when a page has started loading in the given
+ // WebFrame. Typically, this will cause any pending classification to be
+ // cancelled. However, if the navigation is within the same page, we
+ // continue running the current classification.
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override;
+
+ private:
+ friend class PhishingClassifierDelegateTest;
+
+ PhishingClassifierDelegate(content::RenderFrame* render_frame,
+ PhishingClassifier* classifier);
+
+ enum CancelClassificationReason {
+ NAVIGATE_AWAY,
+ NAVIGATE_WITHIN_PAGE,
+ PAGE_RECAPTURED,
+ SHUTDOWN,
+ NEW_PHISHING_SCORER,
+ CANCEL_CLASSIFICATION_MAX // Always add new values before this one.
+ };
+
+ void PhishingDetectorReceiver(
+ mojo::PendingReceiver<mojom::PhishingDetector> receiver);
+
+ // Cancels any pending classification and frees the page text.
+ void CancelPendingClassification(CancelClassificationReason reason);
+
+ void OnDestruct() override;
+
+ void OnInterfaceRequestForFrame(
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle* interface_pipe) override;
+
+ // mojom::PhishingDetector
+ // Called by the RenderFrame when it receives a StartPhishingDetection IPC
+ // from the browser. This signals that it is ok to begin classification
+ // for the given toplevel URL. If the URL has been fully loaded into the
+ // RenderFrame and a Scorer has been set, this will begin classification,
+ // otherwise classification will be deferred until these conditions are met.
+ void StartPhishingDetection(const GURL& url,
+ StartPhishingDetectionCallback callback) override;
+
+ // Called when classification for the current page finishes.
+ void ClassificationDone(const ClientPhishingRequest& verdict);
+
+ // Shared code to begin classification if all conditions are met.
+ void MaybeStartClassification();
+
+ // The PhishingClassifier to use for the RenderFrame. This is created once
+ // a scorer is made available via SetPhishingScorer().
+ std::unique_ptr<PhishingClassifier> classifier_;
+
+ // The last URL that the browser instructed us to classify,
+ // with the ref stripped.
+ GURL last_url_received_from_browser_;
+
+ // The last top-level URL that has finished loading in the RenderFrame.
+ // This corresponds to the text in classifier_page_text_.
+ GURL last_finished_load_url_;
+
+ // The transition type for the last load in the main frame. We use this
+ // to exclude back/forward loads from classification. Note that this is
+ // set in DidCommitProvisionalLoad(); the transition is reset after this
+ // call in the RenderFrame, so we need to save off the value.
+ ui::PageTransition last_main_frame_transition_;
+
+ // The URL of the last load that we actually started classification on.
+ // This is used to suppress phishing classification on subframe navigation
+ // and back and forward navigations in history.
+ GURL last_url_sent_to_classifier_;
+
+ // The page text that will be analyzed by the phishing classifier. This is
+ // set by OnNavigate and cleared when the classifier finishes. Note that if
+ // there is no Scorer yet when OnNavigate is called, or the browser has not
+ // instructed us to classify the page, the page text will be cached until
+ // these conditions are met.
+ base::string16 classifier_page_text_;
+
+ // Tracks whether we have stored anything in classifier_page_text_ for the
+ // most recent load. We use this to distinguish empty text from cases where
+ // PageCaptured has not been called.
+ bool have_page_text_;
+
+ // Set to true if the classifier is currently running.
+ bool is_classifying_;
+
+ // The callback from the most recent call to StartPhishingDetection.
+ StartPhishingDetectionCallback callback_;
+
+ mojo::ReceiverSet<mojom::PhishingDetector> phishing_detector_receivers_;
+
+ service_manager::BinderRegistry registry_;
+
+ DISALLOW_COPY_AND_ASSIGN(PhishingClassifierDelegate);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_PHISHING_CLASSIFIER_DELEGATE_H_
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
new file mode 100644
index 00000000000..ff04757c66f
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -0,0 +1,477 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/phishing_classifier.h"
+#include "chrome/renderer/safe_browsing/scorer.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "chrome/test/base/chrome_unit_test_suite.h"
+#include "components/safe_browsing/common/safe_browsing.mojom-shared.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "url/gurl.h"
+
+using base::ASCIIToUTF16;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Mock;
+using ::testing::Pointee;
+using ::testing::StrictMock;
+
+namespace safe_browsing {
+
+namespace {
+
+class MockPhishingClassifier : public PhishingClassifier {
+ public:
+ explicit MockPhishingClassifier(content::RenderFrame* render_frame)
+ : PhishingClassifier(render_frame, NULL /* clock */) {}
+
+ ~MockPhishingClassifier() override {}
+
+ MOCK_METHOD2(BeginClassification, void(const base::string16*, DoneCallback));
+ MOCK_METHOD0(CancelPendingClassification, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockPhishingClassifier);
+};
+
+class MockScorer : public Scorer {
+ public:
+ MockScorer() : Scorer() {}
+ ~MockScorer() override {}
+
+ MOCK_CONST_METHOD1(ComputeScore, double(const FeatureMap&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockScorer);
+};
+} // namespace
+
+class PhishingClassifierDelegateTest : public ChromeRenderViewTest {
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+
+ content::RenderFrame* render_frame = view_->GetMainRenderFrame();
+ classifier_ = new StrictMock<MockPhishingClassifier>(render_frame);
+ delegate_ = PhishingClassifierDelegate::Create(render_frame, classifier_);
+ }
+
+ // Runs the ClassificationDone callback, then verify if message sent
+ // by FakeRenderThread is correct.
+ void RunAndVerifyClassificationDone(const ClientPhishingRequest& verdict) {
+ delegate_->ClassificationDone(verdict);
+ }
+
+ void OnStartPhishingDetection(const GURL& url) {
+ delegate_->StartPhishingDetection(
+ url, base::BindOnce(&PhishingClassifierDelegateTest::VerifyRequestProto,
+ base::Unretained(this)));
+ }
+
+ void SimulateRedirection(const GURL& redir_url) {
+ delegate_->last_url_received_from_browser_ = redir_url;
+ }
+
+ void SimulatePageTrantitionForwardOrBack(const char* html, const char* url) {
+ LoadHTMLWithUrlOverride(html, url);
+ delegate_->last_main_frame_transition_ = ui::PAGE_TRANSITION_FORWARD_BACK;
+ }
+
+ void VerifyRequestProto(mojom::PhishingDetectorResult result,
+ const std::string& request_proto) {
+ if (result != mojom::PhishingDetectorResult::SUCCESS)
+ return;
+
+ ClientPhishingRequest verdict;
+ ASSERT_TRUE(verdict.ParseFromString(request_proto));
+ EXPECT_EQ("http://host.test/", verdict.url());
+ EXPECT_EQ(0.8f, verdict.client_score());
+ EXPECT_FALSE(verdict.is_phishing());
+ }
+
+ StrictMock<MockPhishingClassifier>* classifier_; // Owned by |delegate_|.
+ PhishingClassifierDelegate* delegate_; // Owned by the RenderFrame.
+};
+
+TEST_F(PhishingClassifierDelegateTest, Navigation) {
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ ASSERT_TRUE(classifier_->is_ready());
+
+ // Test an initial load. We expect classification to happen normally.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ std::string html = "<html><body>dummy</body></html>";
+ GURL url("http://host.test/index.html");
+ LoadHTMLWithUrlOverride(html.c_str(), url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ OnStartPhishingDetection(url);
+ base::string16 page_text = ASCIIToUTF16("dummy");
+ {
+ InSequence s;
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ }
+
+ // Reloading the same page will trigger a new classification.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ LoadHTMLWithUrlOverride(html.c_str(), url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ OnStartPhishingDetection(url);
+ page_text = ASCIIToUTF16("dummy");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ page_text = ASCIIToUTF16("dummy");
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ OnStartPhishingDetection(url);
+ page_text = ASCIIToUTF16("dummy");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // Same document navigation works similarly to a subframe navigation, but see
+ // the TODO in PhishingClassifierDelegate::DidCommitProvisionalLoad.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ OnSameDocumentNavigation(GetMainFrame(), true);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ OnStartPhishingDetection(url);
+ page_text = ASCIIToUTF16("dummy");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // Now load a new toplevel page, which should trigger another classification.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL new_url("http://host2.com");
+ LoadHTMLWithUrlOverride("dummy2", new_url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ page_text = ASCIIToUTF16("dummy2");
+ OnStartPhishingDetection(new_url);
+ {
+ InSequence s;
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ }
+
+ // No classification should happen on back/forward navigation.
+ // Note: in practice, the browser will not send a StartPhishingDetection IPC
+ // in this case. However, we want to make sure that the delegate behaves
+ // correctly regardless.
+ EXPECT_CALL(*classifier_, CancelPendingClassification()).Times(1);
+ // Simulate a go back navigation, i.e. back to http://host.test/index.html.
+ SimulatePageTrantitionForwardOrBack(html.c_str(), url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ page_text = ASCIIToUTF16("dummy");
+ OnStartPhishingDetection(new_url);
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ // Simulate a go forward navigation, i.e. forward to http://host.test
+ SimulatePageTrantitionForwardOrBack("dummy2", new_url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ page_text = ASCIIToUTF16("dummy2");
+ OnStartPhishingDetection(url);
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // Now go back again and navigate to a different place within
+ // the same page. No classification should happen.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ // Simulate a go back again to http://host.test/index.html
+ SimulatePageTrantitionForwardOrBack(html.c_str(), url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ page_text = ASCIIToUTF16("dummy");
+ OnStartPhishingDetection(url);
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ // Same document navigation.
+ OnSameDocumentNavigation(GetMainFrame(), true);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ OnStartPhishingDetection(url);
+ page_text = ASCIIToUTF16("dummy");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+TEST_F(PhishingClassifierDelegateTest, NoScorer) {
+ // For this test, we'll create the delegate with no scorer available yet.
+ ASSERT_FALSE(classifier_->is_ready());
+
+ // Queue up a pending classification, cancel it, then queue up another one.
+ GURL url("http://host.test");
+ base::string16 page_text = ASCIIToUTF16("dummy");
+ LoadHTMLWithUrlOverride("dummy", url.spec().c_str());
+ OnStartPhishingDetection(url);
+ delegate_->PageCaptured(&page_text, false);
+
+ GURL url2("http://host2.com");
+ page_text = ASCIIToUTF16("dummy");
+ LoadHTMLWithUrlOverride("dummy", url2.spec().c_str());
+ OnStartPhishingDetection(url2);
+ delegate_->PageCaptured(&page_text, false);
+
+ // Now set a scorer, which should cause a classifier to be created,
+ // but no classification will start.
+ page_text = ASCIIToUTF16("dummy");
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // Manually start a classification.
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ OnStartPhishingDetection(url2);
+
+ // If we set a new scorer while a classification is going on the
+ // classification should be cancelled.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->SetPhishingScorer(&scorer);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+TEST_F(PhishingClassifierDelegateTest, NoScorer_Ref) {
+ // Similar to the last test, but navigates within the page before
+ // setting the scorer.
+ ASSERT_FALSE(classifier_->is_ready());
+
+ // Queue up a pending classification, cancel it, then queue up another one.
+ GURL url("http://host.test");
+ base::string16 page_text = ASCIIToUTF16("dummy");
+ LoadHTMLWithUrlOverride("dummy", url.spec().c_str());
+ OnStartPhishingDetection(url);
+ delegate_->PageCaptured(&page_text, false);
+
+ page_text = ASCIIToUTF16("dummy");
+ OnStartPhishingDetection(url);
+ delegate_->PageCaptured(&page_text, false);
+
+ // Now set a scorer, which should cause a classifier to be created,
+ // but no classification will start.
+ page_text = ASCIIToUTF16("dummy");
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // Manually start a classification.
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ OnStartPhishingDetection(url);
+
+ // If we set a new scorer while a classification is going on the
+ // classification should be cancelled.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->SetPhishingScorer(&scorer);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+TEST_F(PhishingClassifierDelegateTest, NoStartPhishingDetection) {
+ // Tests the behavior when OnStartPhishingDetection has not yet been called
+ // when the page load finishes.
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ ASSERT_TRUE(classifier_->is_ready());
+
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url("http://host.test");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ base::string16 page_text = ASCIIToUTF16("phish");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ // Now simulate the StartPhishingDetection IPC. We expect classification
+ // to begin.
+ page_text = ASCIIToUTF16("phish");
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ OnStartPhishingDetection(url);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // Now try again, but this time we will navigate the page away before
+ // the IPC is sent.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url2("http://host2.com");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url2.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ page_text = ASCIIToUTF16("phish");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url3("http://host3.com");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url3.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ OnStartPhishingDetection(url);
+
+ // In this test, the original page is a redirect, which we do not get a
+ // StartPhishingDetection IPC for. We simulate the redirection event to
+ // load a new page while reusing the original session history entry, and
+ // check that classification begins correctly for the landing page.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url4("http://host4.com");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url4.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ page_text = ASCIIToUTF16("abc");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+
+ GURL redir_url("http://host4.com/redir");
+ LoadHTMLWithUrlOverride("123", redir_url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ OnStartPhishingDetection(url4);
+ page_text = ASCIIToUTF16("123");
+ {
+ InSequence s;
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ SimulateRedirection(redir_url);
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ }
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+TEST_F(PhishingClassifierDelegateTest, IgnorePreliminaryCapture) {
+ // Tests that preliminary PageCaptured notifications are ignored.
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ ASSERT_TRUE(classifier_->is_ready());
+
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url("http://host.test");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ OnStartPhishingDetection(url);
+ base::string16 page_text = ASCIIToUTF16("phish");
+ delegate_->PageCaptured(&page_text, true);
+
+ // Once the non-preliminary capture happens, classification should begin.
+ page_text = ASCIIToUTF16("phish");
+ {
+ InSequence s;
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ }
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+TEST_F(PhishingClassifierDelegateTest, DuplicatePageCapture) {
+ // Tests that a second PageCaptured notification causes classification to
+ // be cancelled.
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ ASSERT_TRUE(classifier_->is_ready());
+
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url("http://host.test");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ OnStartPhishingDetection(url);
+ base::string16 page_text = ASCIIToUTF16("phish");
+ {
+ InSequence s;
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ }
+
+ page_text = ASCIIToUTF16("phish");
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+TEST_F(PhishingClassifierDelegateTest, PhishingDetectionDone) {
+ // Tests that a SafeBrowsingHostMsg_PhishingDetectionDone IPC is
+ // sent to the browser whenever we finish classification.
+ MockScorer scorer;
+ delegate_->SetPhishingScorer(&scorer);
+ ASSERT_TRUE(classifier_->is_ready());
+
+ // Start by loading a page to populate the delegate's state.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ GURL url("http://host.test");
+ LoadHTMLWithUrlOverride("<html><body>phish</body></html>",
+ url.spec().c_str());
+ Mock::VerifyAndClearExpectations(classifier_);
+ base::string16 page_text = ASCIIToUTF16("phish");
+ OnStartPhishingDetection(url);
+ {
+ InSequence s;
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+ EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
+ delegate_->PageCaptured(&page_text, false);
+ Mock::VerifyAndClearExpectations(classifier_);
+ }
+
+ // Now run the callback to simulate the classifier finishing.
+ ClientPhishingRequest verdict;
+ verdict.set_url(url.spec());
+ verdict.set_client_score(0.8f);
+ verdict.set_is_phishing(false); // Send IPC even if site is not phishing.
+ RunAndVerifyClassificationDone(verdict);
+
+ // The delegate will cancel pending classification on destruction.
+ EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc b/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
new file mode 100644
index 00000000000..f1a9fb37d82
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
@@ -0,0 +1,504 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "content/public/renderer/render_view.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_element_collection.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+
+namespace safe_browsing {
+
+// This time should be short enough that it doesn't noticeably disrupt the
+// user's interaction with the page.
+const int PhishingDOMFeatureExtractor::kMaxTimePerChunkMs = 10;
+
+// Experimenting shows that we get a reasonable gain in performance by
+// increasing this up to around 10, but there's not much benefit in
+// increasing it past that.
+const int PhishingDOMFeatureExtractor::kClockCheckGranularity = 10;
+
+// This should be longer than we expect feature extraction to take on any
+// actual phishing page.
+const int PhishingDOMFeatureExtractor::kMaxTotalTimeMs = 500;
+
+// Intermediate state used for computing features. See features.h for
+// descriptions of the DOM features that are computed.
+struct PhishingDOMFeatureExtractor::PageFeatureState {
+ // Link related features
+ int external_links;
+ std::unordered_set<std::string> external_domains;
+ int secure_links;
+ int total_links;
+
+ // Form related features
+ int num_forms;
+ int num_text_inputs;
+ int num_pswd_inputs;
+ int num_radio_inputs;
+ int num_check_inputs;
+ int action_other_domain;
+ int total_actions;
+ std::unordered_set<std::string> page_action_urls;
+
+ // Image related features
+ int img_other_domain;
+ int total_imgs;
+
+ // How many script tags
+ int num_script_tags;
+
+ // The time at which we started feature extraction for the current page.
+ base::TimeTicks start_time;
+
+ // The number of iterations we've done for the current extraction.
+ int num_iterations;
+
+ explicit PageFeatureState(base::TimeTicks start_time_ticks)
+ : external_links(0),
+ secure_links(0),
+ total_links(0),
+ num_forms(0),
+ num_text_inputs(0),
+ num_pswd_inputs(0),
+ num_radio_inputs(0),
+ num_check_inputs(0),
+ action_other_domain(0),
+ total_actions(0),
+ img_other_domain(0),
+ total_imgs(0),
+ num_script_tags(0),
+ start_time(start_time_ticks),
+ num_iterations(0) {}
+
+ ~PageFeatureState() {}
+};
+
+// Per-frame state
+struct PhishingDOMFeatureExtractor::FrameData {
+ // This is our reference to document.all, which is an iterator over all
+ // of the elements in the document. It keeps track of our current position.
+ blink::WebElementCollection elements;
+ // The domain of the document URL, stored here so that we don't need to
+ // recompute it every time it's needed.
+ std::string domain;
+};
+
+PhishingDOMFeatureExtractor::PhishingDOMFeatureExtractor(
+ FeatureExtractorClock* clock)
+ : clock_(clock) {
+ Clear();
+}
+
+PhishingDOMFeatureExtractor::~PhishingDOMFeatureExtractor() {
+ // The RenderView should have called CancelPendingExtraction() before
+ // we are destroyed.
+ CheckNoPendingExtraction();
+}
+
+void PhishingDOMFeatureExtractor::ExtractFeatures(blink::WebDocument document,
+ FeatureMap* features,
+ DoneCallback done_callback) {
+ // The RenderView should have called CancelPendingExtraction() before
+ // starting a new extraction, so DCHECK this.
+ CheckNoPendingExtraction();
+ // However, in an opt build, we will go ahead and clean up the pending
+ // extraction so that we can start in a known state.
+ CancelPendingExtraction();
+
+ features_ = features;
+ done_callback_ = std::move(done_callback);
+
+ page_feature_state_.reset(new PageFeatureState(clock_->Now()));
+ cur_document_ = document;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PhishingDOMFeatureExtractor::ExtractFeaturesWithTimeout,
+ weak_factory_.GetWeakPtr()));
+}
+
+void PhishingDOMFeatureExtractor::CancelPendingExtraction() {
+ // Cancel any pending callbacks, and clear our state.
+ weak_factory_.InvalidateWeakPtrs();
+ Clear();
+}
+
+void PhishingDOMFeatureExtractor::ExtractFeaturesWithTimeout() {
+ DCHECK(page_feature_state_.get());
+ ++page_feature_state_->num_iterations;
+ base::TimeTicks current_chunk_start_time = clock_->Now();
+
+ if (cur_document_.IsNull()) {
+ // This will only happen if we weren't able to get the document for the
+ // main frame. We'll treat this as an extraction failure.
+ RunCallback(false);
+ return;
+ }
+
+ int num_elements = 0;
+ for (; !cur_document_.IsNull(); cur_document_ = GetNextDocument()) {
+ blink::WebElement cur_element;
+ if (cur_frame_data_.get()) {
+ // We're resuming traversal of a frame, so just advance to the next
+ // element.
+ cur_element = cur_frame_data_->elements.NextItem();
+ // When we resume the traversal, the first call to nextItem() potentially
+ // has to walk through the document again from the beginning, if it was
+ // modified between our chunks of work. Log how long this takes, so we
+ // can tell if it's too slow.
+ UMA_HISTOGRAM_TIMES("SBClientPhishing.DOMFeatureResumeTime",
+ clock_->Now() - current_chunk_start_time);
+ } else {
+ // We just moved to a new frame, so update our frame state
+ // and advance to the first element.
+ ResetFrameData();
+ cur_element = cur_frame_data_->elements.FirstItem();
+ }
+
+ for (; !cur_element.IsNull();
+ cur_element = cur_frame_data_->elements.NextItem()) {
+ if (cur_element.HasHTMLTagName("a")) {
+ HandleLink(cur_element);
+ } else if (cur_element.HasHTMLTagName("form")) {
+ HandleForm(cur_element);
+ } else if (cur_element.HasHTMLTagName("img")) {
+ HandleImage(cur_element);
+ } else if (cur_element.HasHTMLTagName("input")) {
+ HandleInput(cur_element);
+ } else if (cur_element.HasHTMLTagName("script")) {
+ HandleScript(cur_element);
+ }
+
+ if (++num_elements >= kClockCheckGranularity) {
+ num_elements = 0;
+ base::TimeTicks now = clock_->Now();
+ if (now - page_feature_state_->start_time >=
+ base::TimeDelta::FromMilliseconds(kMaxTotalTimeMs)) {
+ DLOG(ERROR) << "Feature extraction took too long, giving up";
+ // We expect this to happen infrequently, so record when it does.
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.DOMFeatureTimeout", 1);
+ RunCallback(false);
+ return;
+ }
+ base::TimeDelta chunk_elapsed = now - current_chunk_start_time;
+ if (chunk_elapsed >=
+ base::TimeDelta::FromMilliseconds(kMaxTimePerChunkMs)) {
+ // The time limit for the current chunk is up, so post a task to
+ // continue extraction.
+ //
+ // Record how much time we actually spent on the chunk. If this is
+ // much higher than kMaxTimePerChunkMs, we may need to adjust the
+ // clock granularity.
+ UMA_HISTOGRAM_TIMES("SBClientPhishing.DOMFeatureChunkTime",
+ chunk_elapsed);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &PhishingDOMFeatureExtractor::ExtractFeaturesWithTimeout,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+ // Otherwise, continue.
+ }
+ }
+
+ // We're done with this frame, recalculate the FrameData when we
+ // advance to the next frame.
+ cur_frame_data_.reset();
+ }
+
+ InsertFeatures();
+ RunCallback(true);
+}
+
+void PhishingDOMFeatureExtractor::HandleLink(
+ const blink::WebElement& element) {
+ // Count the number of times we link to a different host.
+ if (!element.HasAttribute("href")) {
+ DVLOG(1) << "Skipping anchor tag with no href";
+ return;
+ }
+
+ // Retrieve the link and resolve the link in case it's relative.
+ blink::WebURL full_url = CompleteURL(element, element.GetAttribute("href"));
+
+ std::string domain;
+ bool is_external = IsExternalDomain(full_url, &domain);
+ if (domain.empty()) {
+ DVLOG(1) << "Could not extract domain from link: " << full_url;
+ return;
+ }
+
+ if (is_external) {
+ ++page_feature_state_->external_links;
+
+ // Record each unique domain that we link to.
+ page_feature_state_->external_domains.insert(domain);
+ }
+
+ // Check how many are https links.
+ if (GURL(full_url).SchemeIs("https")) {
+ ++page_feature_state_->secure_links;
+ }
+
+ ++page_feature_state_->total_links;
+}
+
+void PhishingDOMFeatureExtractor::HandleForm(
+ const blink::WebElement& element) {
+ // Increment the number of forms on this page.
+ ++page_feature_state_->num_forms;
+
+ // Record whether the action points to a different domain.
+ if (!element.HasAttribute("action")) {
+ return;
+ }
+
+ blink::WebURL full_url = CompleteURL(element, element.GetAttribute("action"));
+
+ page_feature_state_->page_action_urls.insert(full_url.GetString().Utf8());
+
+ std::string domain;
+ bool is_external = IsExternalDomain(full_url, &domain);
+ if (domain.empty()) {
+ DVLOG(1) << "Could not extract domain from form action: " << full_url;
+ return;
+ }
+
+ if (is_external) {
+ ++page_feature_state_->action_other_domain;
+ }
+ ++page_feature_state_->total_actions;
+}
+
+void PhishingDOMFeatureExtractor::HandleImage(
+ const blink::WebElement& element) {
+ if (!element.HasAttribute("src")) {
+ DVLOG(1) << "Skipping img tag with no src";
+ }
+
+ // Record whether the image points to a different domain.
+ blink::WebURL full_url = CompleteURL(element, element.GetAttribute("src"));
+ std::string domain;
+ bool is_external = IsExternalDomain(full_url, &domain);
+ if (domain.empty()) {
+ DVLOG(1) << "Could not extract domain from image src: " << full_url;
+ return;
+ }
+
+ if (is_external) {
+ ++page_feature_state_->img_other_domain;
+ }
+ ++page_feature_state_->total_imgs;
+}
+
+void PhishingDOMFeatureExtractor::HandleInput(
+ const blink::WebElement& element) {
+ // The HTML spec says that if the type is unspecified, it defaults to text.
+ // In addition, any unrecognized type will be treated as a text input.
+ //
+ // Note that we use the attribute value rather than
+ // WebFormControlElement::formControlType() for consistency with the
+ // way the phishing classification model is created.
+ std::string type = base::ToLowerASCII(element.GetAttribute("type").Utf8());
+ if (type == "password") {
+ ++page_feature_state_->num_pswd_inputs;
+ } else if (type == "radio") {
+ ++page_feature_state_->num_radio_inputs;
+ } else if (type == "checkbox") {
+ ++page_feature_state_->num_check_inputs;
+ } else if (type != "submit" && type != "reset" && type != "file" &&
+ type != "hidden" && type != "image" && type != "button") {
+ // Note that there are a number of new input types in HTML5 that are not
+ // handled above. For now, we will consider these as text inputs since
+ // they could be used to capture user input.
+ ++page_feature_state_->num_text_inputs;
+ }
+}
+
+void PhishingDOMFeatureExtractor::HandleScript(
+ const blink::WebElement& element) {
+ ++page_feature_state_->num_script_tags;
+}
+
+void PhishingDOMFeatureExtractor::CheckNoPendingExtraction() {
+ DCHECK(done_callback_.is_null());
+ DCHECK(!cur_frame_data_.get());
+ DCHECK(cur_document_.IsNull());
+ if (!done_callback_.is_null() || cur_frame_data_.get() ||
+ !cur_document_.IsNull()) {
+ LOG(ERROR) << "Extraction in progress, missing call to "
+ << "CancelPendingExtraction";
+ }
+}
+
+void PhishingDOMFeatureExtractor::RunCallback(bool success) {
+ // Record some timing stats that we can use to evaluate feature extraction
+ // performance. These include both successful and failed extractions.
+ DCHECK(page_feature_state_.get());
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.DOMFeatureIterations",
+ page_feature_state_->num_iterations);
+ UMA_HISTOGRAM_TIMES("SBClientPhishing.DOMFeatureTotalTime",
+ clock_->Now() - page_feature_state_->start_time);
+
+ DCHECK(!done_callback_.is_null());
+ std::move(done_callback_).Run(success);
+ Clear();
+}
+
+void PhishingDOMFeatureExtractor::Clear() {
+ features_ = NULL;
+ done_callback_.Reset();
+ cur_frame_data_.reset(NULL);
+ cur_document_.Reset();
+}
+
+void PhishingDOMFeatureExtractor::ResetFrameData() {
+ DCHECK(!cur_document_.IsNull());
+ DCHECK(!cur_frame_data_.get());
+
+ cur_frame_data_.reset(new FrameData());
+ cur_frame_data_->elements = cur_document_.All();
+ cur_frame_data_->domain =
+ net::registry_controlled_domains::GetDomainAndRegistry(
+ cur_document_.Url(),
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+blink::WebDocument PhishingDOMFeatureExtractor::GetNextDocument() {
+ DCHECK(!cur_document_.IsNull());
+ blink::WebFrame* frame = cur_document_.GetFrame();
+ // Advance to the next frame that contains a document, with no wrapping.
+ if (frame) {
+ for (frame = frame->TraverseNext(); frame; frame = frame->TraverseNext()) {
+ // TODO(dcheng): Verify if the WebDocument::IsNull check is really needed.
+ if (frame->IsWebLocalFrame() &&
+ !frame->ToWebLocalFrame()->GetDocument().IsNull()) {
+ return frame->ToWebLocalFrame()->GetDocument();
+ }
+ }
+ } else {
+ // Keep track of how often frame traversal got "stuck" due to the
+ // current subdocument getting removed from the frame tree.
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.DOMFeatureFrameRemoved", 1);
+ }
+ return blink::WebDocument();
+}
+
+bool PhishingDOMFeatureExtractor::IsExternalDomain(const GURL& url,
+ std::string* domain) const {
+ DCHECK(domain);
+ DCHECK(cur_frame_data_.get());
+
+ if (cur_frame_data_->domain.empty()) {
+ return false;
+ }
+
+ // TODO(bryner): Ensure that the url encoding is consistent with the features
+ // in the model.
+ if (url.HostIsIPAddress()) {
+ domain->assign(url.host());
+ } else {
+ domain->assign(net::registry_controlled_domains::GetDomainAndRegistry(
+ url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES));
+ }
+
+ return !domain->empty() && *domain != cur_frame_data_->domain;
+}
+
+blink::WebURL PhishingDOMFeatureExtractor::CompleteURL(
+ const blink::WebElement& element,
+ const blink::WebString& partial_url) {
+ return element.GetDocument().CompleteURL(partial_url);
+}
+
+void PhishingDOMFeatureExtractor::InsertFeatures() {
+ DCHECK(page_feature_state_.get());
+
+ if (page_feature_state_->total_links > 0) {
+ // Add a feature for the fraction of times the page links to an external
+ // domain vs. an internal domain.
+ double link_freq = static_cast<double>(
+ page_feature_state_->external_links) /
+ page_feature_state_->total_links;
+ features_->AddRealFeature(features::kPageExternalLinksFreq, link_freq);
+
+ // Add a feature for each unique domain that we're linking to
+ for (const auto& domain : page_feature_state_->external_domains) {
+ features_->AddBooleanFeature(features::kPageLinkDomain + domain);
+ }
+
+ // Fraction of links that use https.
+ double secure_freq = static_cast<double>(
+ page_feature_state_->secure_links) / page_feature_state_->total_links;
+ features_->AddRealFeature(features::kPageSecureLinksFreq, secure_freq);
+ }
+
+ // Record whether forms appear and whether various form elements appear.
+ if (page_feature_state_->num_forms > 0) {
+ features_->AddBooleanFeature(features::kPageHasForms);
+ }
+ if (page_feature_state_->num_text_inputs > 0) {
+ features_->AddBooleanFeature(features::kPageHasTextInputs);
+ }
+ if (page_feature_state_->num_pswd_inputs > 0) {
+ features_->AddBooleanFeature(features::kPageHasPswdInputs);
+ }
+ if (page_feature_state_->num_radio_inputs > 0) {
+ features_->AddBooleanFeature(features::kPageHasRadioInputs);
+ }
+ if (page_feature_state_->num_check_inputs > 0) {
+ features_->AddBooleanFeature(features::kPageHasCheckInputs);
+ }
+
+ // Record fraction of form actions that point to a different domain.
+ if (page_feature_state_->total_actions > 0) {
+ double action_freq = static_cast<double>(
+ page_feature_state_->action_other_domain) /
+ page_feature_state_->total_actions;
+ features_->AddRealFeature(features::kPageActionOtherDomainFreq,
+ action_freq);
+ }
+
+ // Add a feature for each unique external action url.
+ for (const auto& url : page_feature_state_->page_action_urls) {
+ features_->AddBooleanFeature(features::kPageActionURL + url);
+ }
+
+ // Record how many image src attributes point to a different domain.
+ if (page_feature_state_->total_imgs > 0) {
+ double img_freq = static_cast<double>(
+ page_feature_state_->img_other_domain) /
+ page_feature_state_->total_imgs;
+ features_->AddRealFeature(features::kPageImgOtherDomainFreq, img_freq);
+ }
+
+ // Record number of script tags (discretized for numerical stability.)
+ if (page_feature_state_->num_script_tags > 1) {
+ features_->AddBooleanFeature(features::kPageNumScriptTagsGTOne);
+ if (page_feature_state_->num_script_tags > 6) {
+ features_->AddBooleanFeature(features::kPageNumScriptTagsGTSix);
+ }
+ }
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h b/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h
new file mode 100644
index 00000000000..6f4e2c1503c
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PhishingDOMFeatureExtractor handles computing DOM-based features for the
+// client-side phishing detection model. These include the presence of various
+// types of elements, ratios of external and secure links, and tokens for
+// external domains linked to.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_PHISHING_DOM_FEATURE_EXTRACTOR_H_
+#define CHROME_RENDERER_SAFE_BROWSING_PHISHING_DOM_FEATURE_EXTRACTOR_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "third_party/blink/public/web/web_document.h"
+
+class GURL;
+
+namespace blink {
+class WebElement;
+}
+
+namespace safe_browsing {
+class FeatureExtractorClock;
+class FeatureMap;
+
+class PhishingDOMFeatureExtractor {
+ public:
+ // Callback to be run when feature extraction finishes. The callback
+ // argument is true if extraction was successful, false otherwise.
+ typedef base::OnceCallback<void(bool)> DoneCallback;
+
+ // Creates a PhishingDOMFeatureExtractor instance.
+ // |clock| is used for timing feature extractor operations, and may be
+ // mocked for testing. The caller maintains ownership of the clock.
+ explicit PhishingDOMFeatureExtractor(FeatureExtractorClock* clock);
+ virtual ~PhishingDOMFeatureExtractor();
+
+ // Begins extracting features into the given FeatureMap for the page.
+ // To avoid blocking the render thread for too long, the feature extractor
+ // may run in several chunks of work, posting a task to the current
+ // MessageLoop to continue processing. Once feature extraction is complete,
+ // |done_callback| is run on the current thread. PhishingDOMFeatureExtractor
+ // takes ownership of the callback.
+ void ExtractFeatures(blink::WebDocument document,
+ FeatureMap* features,
+ DoneCallback done_callback);
+
+ // Cancels any pending feature extraction. The DoneCallback will not be run.
+ // Must be called if there is a feature extraction in progress when the page
+ // is unloaded or the PhishingDOMFeatureExtractor is destroyed.
+ void CancelPendingExtraction();
+
+ private:
+ struct FrameData;
+ struct PageFeatureState;
+
+ // The maximum amount of wall time that we will spend on a single extraction
+ // iteration before pausing to let other MessageLoop tasks run.
+ static const int kMaxTimePerChunkMs;
+
+ // The number of elements that we will process before checking to see whether
+ // kMaxTimePerChunkMs has elapsed. Since checking the current time can be
+ // slow, we don't do this on every element processed.
+ static const int kClockCheckGranularity;
+
+ // The maximum total amount of time that the feature extractor will run
+ // before giving up on the current page.
+ static const int kMaxTotalTimeMs;
+
+ // Does the actual work of ExtractFeatures. ExtractFeaturesWithTimeout runs
+ // until a predefined maximum amount of time has elapsed, then posts a task
+ // to the current MessageLoop to continue extraction. When extraction
+ // finishes, calls RunCallback().
+ void ExtractFeaturesWithTimeout();
+
+ // Handlers for the various HTML elements that we compute features for.
+ // Since some of the features (such as ratios) cannot be computed until
+ // feature extraction is finished, these handlers do not add to the feature
+ // map directly. Instead, they update the values in the PageFeatureState.
+ void HandleLink(const blink::WebElement& element);
+ void HandleForm(const blink::WebElement& element);
+ void HandleImage(const blink::WebElement& element);
+ void HandleInput(const blink::WebElement& element);
+ void HandleScript(const blink::WebElement& element);
+
+ // Helper to verify that there is no pending feature extraction. Dies in
+ // debug builds if the state is not as expected. This is a no-op in release
+ // builds.
+ void CheckNoPendingExtraction();
+
+ // Runs |done_callback_| and then clears all internal state.
+ void RunCallback(bool success);
+
+ // Clears all internal feature extraction state.
+ void Clear();
+
+ // Called after advancing |cur_document_| to update the state in
+ // |cur_frame_data_|.
+ void ResetFrameData();
+
+ // Returns the next document in frame-traversal order from cur_document_.
+ // If there are no more documents, returns a null WebDocument.
+ blink::WebDocument GetNextDocument();
+
+ // Given a URL, checks whether the domain is different from the domain of
+ // the current frame's URL. If so, stores the domain in |domain| and returns
+ // true, otherwise returns false.
+ virtual bool IsExternalDomain(const GURL& url, std::string* domain) const;
+
+ // Given a partial URL, extend it to a full url based on the current frame's
+ // URL.
+ virtual blink::WebURL CompleteURL(const blink::WebElement& element,
+ const blink::WebString& partial_url);
+
+ // Called once all frames have been processed to compute features from the
+ // PageFeatureState and add them to |features_|. See features.h for a
+ // description of which features are computed.
+ void InsertFeatures();
+
+
+ // Non-owned pointer to our clock.
+ FeatureExtractorClock* clock_;
+
+ // The output parameters from the most recent call to ExtractFeatures().
+ FeatureMap* features_; // The caller keeps ownership of this.
+ DoneCallback done_callback_;
+
+ // The current (sub-)document that we are processing. May be a null document
+ // (isNull()) if we are not currently extracting features.
+ blink::WebDocument cur_document_;
+
+ // Stores extra state for |cur_document_| that will be persisted until we
+ // advance to the next frame.
+ std::unique_ptr<FrameData> cur_frame_data_;
+
+ // Stores the intermediate data used to create features. This data is
+ // accumulated across all frames in the RenderView.
+ std::unique_ptr<PageFeatureState> page_feature_state_;
+
+ // Used in scheduling ExtractFeaturesWithTimeout tasks.
+ // These pointers are invalidated if extraction is cancelled.
+ base::WeakPtrFactory<PhishingDOMFeatureExtractor> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PhishingDOMFeatureExtractor);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_PHISHING_DOM_FEATURE_EXTRACTOR_H_
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc b/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
new file mode 100644
index 00000000000..abc6437ed04
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
@@ -0,0 +1,578 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h"
+
+#include <memory>
+#include <unordered_map>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "chrome/renderer/chrome_content_renderer_client.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/mock_feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/test/test_utils.h"
+#include "net/base/escape.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_runtime_features.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "ui/native_theme/native_theme_features.h"
+
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Return;
+using blink::WebRuntimeFeatures;
+
+namespace safe_browsing {
+
+// TestPhishingDOMFeatureExtractor has nearly identical behavior as
+// PhishingDOMFeatureExtractor, except the IsExternalDomain() and
+// CompleteURL() functions. This is to work around the fact that
+// ChromeRenderViewTest object does not know where the html content is hosted.
+class TestPhishingDOMFeatureExtractor : public PhishingDOMFeatureExtractor {
+ public:
+ explicit TestPhishingDOMFeatureExtractor(FeatureExtractorClock* clock)
+ : PhishingDOMFeatureExtractor(clock) {}
+
+ void SetDocumentDomain(std::string domain) { base_domain_ = domain; }
+
+ void SetURLToFrameDomainCheckingMap(
+ const std::unordered_map<std::string, std::string>& checking_map) {
+ url_to_frame_domain_map_ = checking_map;
+ }
+
+ void Reset() {
+ base_domain_.clear();
+ url_to_frame_domain_map_.clear();
+ }
+
+ private:
+ // LoadHTML() function in RenderViewTest only loads html as data,
+ // thus cur_frame_data_->domain is empty. Therefore, in base class
+ // PhishingDOMFeatureExtractor::IsExternalDomain() will always return false.
+ // Overriding IsExternalDomain(..) to work around this problem.
+ bool IsExternalDomain(const GURL& url, std::string* domain) const override {
+ DCHECK(domain);
+ *domain = net::registry_controlled_domains::GetDomainAndRegistry(
+ url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if (domain->empty())
+ *domain = url.spec();
+ // If this html only has one frame, use base_domain_ to determine if url
+ // is external.
+ if (!base_domain_.empty()) {
+ return !url.DomainIs(base_domain_);
+ } else {
+ // html contains multiple frames, need to check against
+ // its corresponding frame's domain.
+ auto it = url_to_frame_domain_map_.find(url.spec());
+ if (it != url_to_frame_domain_map_.end()) {
+ const std::string document_domain = it->second;
+ return !url.DomainIs(document_domain);
+ }
+ NOTREACHED() << "Testing input setup is incorrect. "
+ "Please check url_to_frame_domain_map_ setup.";
+ return true;
+ }
+ }
+
+ // For similar reason as above, PhishingDOMFeatureExtractor::CompeteURL(..)
+ // always returns empty WebURL. Overriding this CompeteURL(..) to work around
+ // this issue.
+ blink::WebURL CompleteURL(const blink::WebElement& element,
+ const blink::WebString& partial_url) override {
+ GURL parsed_url = blink::WebStringToGURL(partial_url);
+ GURL full_url;
+ if (parsed_url.has_scheme()) {
+ // This is already a complete URL.
+ full_url = parsed_url;
+ } else if (!base_domain_.empty()) {
+ // This is a partial URL and only one frame in testing html.
+ full_url = GURL("http://" + base_domain_).Resolve(partial_url.Utf8());
+ } else {
+ auto it = url_to_frame_domain_map_.find(partial_url.Utf8());
+ if (it != url_to_frame_domain_map_.end()) {
+ const std::string frame_domain = it->second;
+ full_url = GURL("http://" + it->second).Resolve(partial_url.Utf8());
+ url_to_frame_domain_map_[full_url.spec()] = it->second;
+ } else {
+ NOTREACHED() << "Testing input setup is incorrect. "
+ "Please check url_to_frame_domain_map_ setup.";
+ }
+ }
+ return blink::WebURL(full_url);
+ }
+
+ // If there is only main frame, we use base_domain_ to track where
+ // the html content is hosted.
+ std::string base_domain_;
+
+ // If html contains multiple frame/iframe, we track domain of each frame by
+ // using this map, where keys are the urls mentioned in the html content,
+ // values are the domains of the corresponding frames.
+ std::unordered_map<std::string, std::string> url_to_frame_domain_map_;
+};
+
+class TestChromeContentRendererClient : public ChromeContentRendererClient {
+ public:
+ TestChromeContentRendererClient() {}
+ ~TestChromeContentRendererClient() override {}
+ // Since visited_link_slave_ in ChromeContentRenderClient never get initiated,
+ // overrides VisitedLinkedHash() function to prevent crashing.
+ uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override {
+ return 0;
+ }
+};
+
+class PhishingDOMFeatureExtractorTest : public ChromeRenderViewTest {
+ public:
+ PhishingDOMFeatureExtractorTest()
+ : success_(false), message_loop_(new content::MessageLoopRunner) {}
+
+ bool GetSuccess() { return success_; }
+ void ResetTest() {
+ success_ = false;
+ message_loop_ = new content::MessageLoopRunner;
+ extractor_->Reset();
+ }
+
+ void ExtractFeaturesAcrossFrames(
+ const std::string& html_content,
+ FeatureMap* features,
+ const std::unordered_map<std::string, std::string>&
+ url_frame_domain_map) {
+ extractor_->SetURLToFrameDomainCheckingMap(url_frame_domain_map);
+ LoadHTML(html_content.c_str());
+
+ extractor_->ExtractFeatures(
+ GetMainFrame()->GetDocument(), features,
+ base::BindOnce(&PhishingDOMFeatureExtractorTest::AnotherExtractionDone,
+ weak_factory_.GetWeakPtr()));
+ message_loop_->Run();
+ }
+
+ void ExtractFeatures(const std::string& document_domain,
+ const std::string& html_content,
+ FeatureMap* features) {
+ extractor_->SetDocumentDomain(document_domain);
+ LoadHTML(html_content.c_str());
+
+ extractor_->ExtractFeatures(
+ GetMainFrame()->GetDocument(), features,
+ base::BindOnce(&PhishingDOMFeatureExtractorTest::AnotherExtractionDone,
+ weak_factory_.GetWeakPtr()));
+ message_loop_->Run();
+ }
+
+ // Helper for the SubframeRemoval test that posts a message to remove
+ // the iframe "frame1" from the document.
+ void ScheduleRemoveIframe() {
+ GetMainFrame()
+ ->GetTaskRunner(blink::TaskType::kInternalTest)
+ ->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PhishingDOMFeatureExtractorTest::RemoveIframe,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+ WebRuntimeFeatures::EnableOverlayScrollbars(
+ ui::IsOverlayScrollbarEnabled());
+ extractor_.reset(new TestPhishingDOMFeatureExtractor(&clock_));
+ }
+
+ void TearDown() override {
+ extractor_.reset(nullptr);
+ ChromeRenderViewTest::TearDown();
+ }
+
+ content::ContentRendererClient* CreateContentRendererClient() override {
+ ChromeContentRendererClient* client = new TestChromeContentRendererClient();
+ InitChromeContentRendererClient(client);
+ return client;
+ }
+
+ void AnotherExtractionDone(bool success) {
+ success_ = success;
+ message_loop_->QuitClosure().Run();
+ }
+
+ // Does the actual work of removing the iframe "frame1" from the document.
+ void RemoveIframe() {
+ blink::WebLocalFrame* main_frame = GetMainFrame();
+ ASSERT_TRUE(main_frame);
+ main_frame->ExecuteScript(blink::WebString(
+ "document.body.removeChild(document.getElementById('frame1'));"));
+ }
+
+ MockFeatureExtractorClock clock_;
+ bool success_;
+ std::unique_ptr<TestPhishingDOMFeatureExtractor> extractor_;
+ scoped_refptr<content::MessageLoopRunner> message_loop_;
+ base::WeakPtrFactory<PhishingDOMFeatureExtractorTest> weak_factory_{this};
+};
+
+TEST_F(PhishingDOMFeatureExtractorTest, FormFeatures) {
+ // This test doesn't exercise the extraction timing.
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::TimeTicks::Now()));
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageHasForms);
+ expected_features.AddRealFeature(features::kPageActionOtherDomainFreq, 0.25);
+ expected_features.AddBooleanFeature(features::kPageHasTextInputs);
+ expected_features.AddBooleanFeature(features::kPageHasCheckInputs);
+ expected_features.AddBooleanFeature(features::kPageActionURL +
+ std::string("http://cgi.host.com/submit"));
+ expected_features.AddBooleanFeature(features::kPageActionURL +
+ std::string("http://other.com/"));
+
+ GURL url("http://host.com/query");
+ expected_features.AddBooleanFeature(features::kPageActionURL + url.spec());
+
+ FeatureMap features;
+ EXPECT_FALSE(GetSuccess());
+
+ ExtractFeatures(
+ "host.com",
+ "<html><head><body>"
+ "<form action=\"query\"><input type=text><input type=checkbox></form>"
+ "<form action=\"http://cgi.host.com/submit\"></form>"
+ "<form action=\"http://other.com/\"></form>"
+ "<form action=\"query\"></form>"
+ "<form></form></body></html>",
+ &features);
+ EXPECT_TRUE(GetSuccess());
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageHasRadioInputs);
+ expected_features.AddBooleanFeature(features::kPageHasPswdInputs);
+
+ features.Clear();
+ ResetTest();
+ EXPECT_FALSE(GetSuccess());
+ ExtractFeatures("host.com",
+ "<html><head><body>"
+ "<input type=\"radio\"><input type=password></body></html>",
+ &features);
+ EXPECT_TRUE(GetSuccess());
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageHasTextInputs);
+
+ features.Clear();
+ ResetTest();
+ EXPECT_FALSE(GetSuccess());
+ ExtractFeatures("host.com", "<html><head><body><input></body></html>",
+ &features);
+ EXPECT_TRUE(GetSuccess());
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageHasTextInputs);
+
+ features.Clear();
+ ResetTest();
+ EXPECT_FALSE(GetSuccess());
+ ExtractFeatures("host.com",
+ "<html><head><body><input type=\"invalid\"></body></html>",
+ &features);
+ EXPECT_TRUE(GetSuccess());
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+TEST_F(PhishingDOMFeatureExtractorTest, LinkFeatures) {
+ // This test doesn't exercise the extraction timing.
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::TimeTicks::Now()));
+
+ FeatureMap expected_features;
+ expected_features.AddRealFeature(features::kPageExternalLinksFreq, 0.5);
+ expected_features.AddRealFeature(features::kPageSecureLinksFreq, 0.0);
+ expected_features.AddBooleanFeature(features::kPageLinkDomain +
+ std::string("chromium.org"));
+
+ FeatureMap features;
+ ExtractFeatures("host.com",
+ "<html><head><body>"
+ "<a href=\"http://www2.host.com/abc\">link</a>"
+ "<a name=page_anchor></a>"
+ "<a href=\"http://www.chromium.org/\">chromium</a>"
+ "</body></html>",
+ &features);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ expected_features.Clear();
+ expected_features.AddRealFeature(features::kPageExternalLinksFreq, 0.25);
+ expected_features.AddRealFeature(features::kPageSecureLinksFreq, 0.25);
+ expected_features.AddBooleanFeature(features::kPageLinkDomain +
+ std::string("chromium.org"));
+ features.Clear();
+ ResetTest();
+ ExtractFeatures("host.com",
+ "<html><head><body>"
+ "<a href=\"login\">this is not secure</a>"
+ "<a href=\"http://host.com\">not secure</a>"
+ "<a href=\"http://chromium.org/\">also not secure</a>"
+ "<a href=\"https://www2.host.com/login\"> this secure</a>"
+ "</body></html>",
+ &features);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+TEST_F(PhishingDOMFeatureExtractorTest, ScriptAndImageFeatures) {
+ // This test doesn't exercise the extraction timing.
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::TimeTicks::Now()));
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageNumScriptTagsGTOne);
+
+ FeatureMap features;
+ ExtractFeatures(
+ "host.com",
+ "<html><head><script></script><script></script></head></html>",
+ &features);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageNumScriptTagsGTOne);
+ expected_features.AddBooleanFeature(features::kPageNumScriptTagsGTSix);
+ expected_features.AddRealFeature(features::kPageImgOtherDomainFreq, 0.5);
+
+ features.Clear();
+ ResetTest();
+ std::string html(
+ "<html><head><script></script><script></script><script></script>"
+ "<script></script><script></script><script></script><script></script>"
+ "</head><body>"
+ "<img src=\"file:///C:/other.png\">"
+ "<img src=\"img/header.png\">"
+ "</body></html>");
+ ExtractFeatures("host.com", html, &features);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+// A page with nested iframes.
+// html
+// iframe2 / \ iframe1
+// \ iframe3
+TEST_F(PhishingDOMFeatureExtractorTest, SubFrames) {
+ // This test doesn't exercise the extraction timing.
+ // Test that features are aggregated across all frames.
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::TimeTicks::Now()));
+
+ const char urlprefix[] = "data:text/html;charset=utf-8,";
+ std::unordered_map<std::string, std::string> url_iframe_map;
+ std::string iframe1_nested_html(
+ "<html><body><input type=password>"
+ "<a href=\"https://host3.com/submit\">link</a>"
+ "<a href=\"relative\">link</a>"
+ "</body></html>");
+ GURL iframe1_nested_url(urlprefix + iframe1_nested_html);
+ // iframe1_nested is on host1.com.
+ url_iframe_map["https://host3.com/submit"] = "host1.com";
+ url_iframe_map["relative"] = "host1.com";
+
+ std::string iframe1_html(
+ "<html><head><script></script><body>"
+ "<form action=\"http://host3.com/home\"><input type=checkbox></form>"
+ "<form action=\"http://host1.com/submit\"></form>"
+ "<a href=\"http://www.host1.com/reset\">link</a>"
+ "<iframe src=\"" +
+ net::EscapeForHTML(iframe1_nested_url.spec()) +
+ "\"></iframe></head></html>");
+ GURL iframe1_url(urlprefix + iframe1_html);
+ // iframe1 is on host1.com too.
+ url_iframe_map["http://host3.com/home"] = "host1.com";
+ url_iframe_map["http://host1.com/submit"] = "host1.com";
+ url_iframe_map["http://www.host1.com/reset"] = "host1.com";
+
+ std::string iframe2_html(
+ "<html><head><script></script><body>"
+ "<img src=\"file:///C:/other.html\">"
+ "</body></html>");
+ GURL iframe2_url(urlprefix + iframe2_html);
+ // iframe2 is on host2.com
+ url_iframe_map["file:///C:/other.html"] = "host2.com";
+
+ std::string html(
+ "<html><body><input type=text>"
+ "<a href=\"info.html\">link</a>"
+ "<iframe src=\"" +
+ net::EscapeForHTML(iframe1_url.spec()) + "\"></iframe><iframe src=\"" +
+ net::EscapeForHTML(iframe2_url.spec()) + "\"></iframe></body></html>");
+ // The entire html is hosted on host.com
+ url_iframe_map["info.html"] = "host.com";
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageHasForms);
+ // Form action domains are compared to the URL of the document they're in,
+ // not the URL of the toplevel page. So http://host1.com/ has two form
+ // actions, one of which is external.
+ expected_features.AddRealFeature(features::kPageActionOtherDomainFreq, 0.5);
+ expected_features.AddBooleanFeature(features::kPageHasTextInputs);
+ expected_features.AddBooleanFeature(features::kPageHasPswdInputs);
+ expected_features.AddBooleanFeature(features::kPageHasCheckInputs);
+ expected_features.AddRealFeature(features::kPageExternalLinksFreq, 0.25);
+ expected_features.AddBooleanFeature(features::kPageLinkDomain +
+ std::string("host3.com"));
+ expected_features.AddRealFeature(features::kPageSecureLinksFreq, 0.25);
+ expected_features.AddBooleanFeature(features::kPageNumScriptTagsGTOne);
+ expected_features.AddRealFeature(features::kPageImgOtherDomainFreq, 1.0);
+ expected_features.AddBooleanFeature(features::kPageActionURL +
+ std::string("http://host1.com/submit"));
+ expected_features.AddBooleanFeature(features::kPageActionURL +
+ std::string("http://host3.com/home"));
+
+ FeatureMap features;
+ ExtractFeaturesAcrossFrames(html, &features, url_iframe_map);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+TEST_F(PhishingDOMFeatureExtractorTest, Continuation) {
+ // For this test, we'll cause the feature extraction to run multiple
+ // iterations by incrementing the clock.
+
+ // This page has a total of 50 elements. For the external forms feature to
+ // be computed correctly, the extractor has to examine the whole document.
+ // Note: the empty HEAD is important -- WebKit will synthesize a HEAD if
+ // there isn't one present, which can be confusing for the element counts.
+ std::string html =
+ "<html><head></head><body>"
+ "<form action=\"ondomain\"></form>";
+ for (int i = 0; i < 45; ++i) {
+ html.append("<p>");
+ }
+ html.append("<form action=\"http://host2.com/\"></form></body></html>");
+
+ // Advance the clock 6 ms every 10 elements processed, 10 ms between chunks.
+ // Note that this assumes kClockCheckGranularity = 10 and
+ // kMaxTimePerChunkMs = 10.
+ base::TimeTicks now = base::TimeTicks::Now();
+ EXPECT_CALL(clock_, Now())
+ // Time check at the start of extraction.
+ .WillOnce(Return(now))
+ // Time check at the start of the first chunk of work.
+ .WillOnce(Return(now))
+ // Time check after the first 10 elements.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(6)))
+ // Time check after the next 10 elements. This is over the chunk
+ // time limit, so a continuation task will be posted.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(12)))
+ // Time check at the start of the second chunk of work.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(22)))
+ // Time check after resuming iteration for the second chunk.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(24)))
+ // Time check after the next 10 elements.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(30)))
+ // Time check after the next 10 elements. This will trigger another
+ // continuation task.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(36)))
+ // Time check at the start of the third chunk of work.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(46)))
+ // Time check after resuming iteration for the third chunk.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(48)))
+ // Time check after the last 10 elements.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(54)))
+ // A final time check for the histograms.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(56)));
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageHasForms);
+ expected_features.AddRealFeature(features::kPageActionOtherDomainFreq, 0.5);
+ expected_features.AddBooleanFeature(features::kPageActionURL +
+ std::string("http://host.com/ondomain"));
+ expected_features.AddBooleanFeature(features::kPageActionURL +
+ std::string("http://host2.com/"));
+
+ FeatureMap features;
+ ExtractFeatures("host.com", html, &features);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ // Make sure none of the mock expectations carry over to the next test.
+ ::testing::Mock::VerifyAndClearExpectations(&clock_);
+
+ // Now repeat the test with the same page, but advance the clock faster so
+ // that the extraction time exceeds the maximum total time for the feature
+ // extractor. Extraction should fail. Note that this assumes
+ // kMaxTotalTimeMs = 500.
+ EXPECT_CALL(clock_, Now())
+ // Time check at the start of extraction.
+ .WillOnce(Return(now))
+ // Time check at the start of the first chunk of work.
+ .WillOnce(Return(now))
+ // Time check after the first 10 elements.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(300)))
+ // Time check at the start of the second chunk of work.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(350)))
+ // Time check after resuming iteration for the second chunk.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(360)))
+ // Time check after the next 10 elements. This is over the limit.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(600)))
+ // A final time check for the histograms.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(620)));
+
+ features.Clear();
+ ResetTest();
+ ExtractFeatures("host.com", html, &features);
+ EXPECT_FALSE(GetSuccess());
+}
+
+TEST_F(PhishingDOMFeatureExtractorTest, SubframeRemoval) {
+ // In this test, we'll advance the feature extractor so that it is positioned
+ // inside an iframe, and have it pause due to exceeding the chunk time limit.
+ // Then, prior to continuation, the iframe is removed from the document.
+ // As currently implemented, this should finish extraction from the removed
+ // iframe document.
+ const char urlprefix[] = "data:text/html;charset=utf-8,";
+ std::string iframe1_html(
+ "<html><body><p><p><p><input type=password></body></html>");
+ GURL iframe1_url(urlprefix + iframe1_html);
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ EXPECT_CALL(clock_, Now())
+ // Time check at the start of extraction.
+ .WillOnce(Return(now))
+ // Time check at the start of the first chunk of work.
+ .WillOnce(Return(now))
+ // Time check after the first 10 elements. Enough time has passed
+ // to stop extraction. Schedule the iframe removal to happen as soon as
+ // the feature extractor returns control to the message loop.
+ .WillOnce(DoAll(
+ Invoke(this, &PhishingDOMFeatureExtractorTest::ScheduleRemoveIframe),
+ Return(now + base::TimeDelta::FromMilliseconds(21))))
+ // Time check at the start of the second chunk of work.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(25)))
+ // Time check after resuming iteration for the second chunk.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(27)))
+ // A final time check for the histograms.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(33)));
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageHasForms);
+ expected_features.AddBooleanFeature(features::kPageHasPswdInputs);
+
+ FeatureMap features;
+ std::string html(
+ "<html><head></head><body>"
+ "<iframe src=\"" +
+ net::EscapeForHTML(iframe1_url.spec()) +
+ "\" id=\"frame1\"></iframe>"
+ "<form></form></body></html>");
+ ExtractFeatures("host.com", html, &features);
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc b/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
new file mode 100644
index 00000000000..2bea4a2ea43
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
@@ -0,0 +1,295 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_term_feature_extractor.h"
+
+#include <list>
+#include <map>
+#include <memory>
+#include <unordered_set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/i18n/break_iterator.h"
+#include "base/i18n/case_conversion.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/murmurhash3_util.h"
+#include "crypto/sha2.h"
+
+namespace safe_browsing {
+
+// This time should be short enough that it doesn't noticeably disrupt the
+// user's interaction with the page.
+const int PhishingTermFeatureExtractor::kMaxTimePerChunkMs = 10;
+
+// Experimenting shows that we get a reasonable gain in performance by
+// increasing this up to around 10, but there's not much benefit in
+// increasing it past that.
+const int PhishingTermFeatureExtractor::kClockCheckGranularity = 5;
+
+// This should be longer than we expect feature extraction to take on any
+// actual phishing page.
+const int PhishingTermFeatureExtractor::kMaxTotalTimeMs = 500;
+
+// All of the state pertaining to the current feature extraction.
+struct PhishingTermFeatureExtractor::ExtractionState {
+ // Stores up to max_words_per_term_ previous words separated by spaces.
+ std::string previous_words;
+
+ // Stores the current shingle after a new word is processed and added in.
+ std::string current_shingle;
+
+ // Stores the sizes of the words in current_shingle. Note: the size includes
+ // the space after each word. In other words, the sum of all sizes in this
+ // list is equal to the length of current_shingle.
+ std::list<size_t> shingle_word_sizes;
+
+ // Stores the sizes of the words in previous_words. Note: the size includes
+ // the space after each word. In other words, the sum of all sizes in this
+ // list is equal to the length of previous_words.
+ std::list<size_t> previous_word_sizes;
+
+ // An iterator for word breaking.
+ std::unique_ptr<base::i18n::BreakIterator> iterator;
+
+ // The time at which we started feature extraction for the current page.
+ base::TimeTicks start_time;
+
+ // The number of iterations we've done for the current extraction.
+ int num_iterations;
+
+ ExtractionState(const base::string16& text, base::TimeTicks start_time_ticks)
+ : start_time(start_time_ticks),
+ num_iterations(0) {
+ std::unique_ptr<base::i18n::BreakIterator> i(new base::i18n::BreakIterator(
+ text, base::i18n::BreakIterator::BREAK_WORD));
+
+ if (i->Init()) {
+ iterator = std::move(i);
+ } else {
+ DLOG(ERROR) << "failed to open iterator";
+ }
+ }
+};
+
+PhishingTermFeatureExtractor::PhishingTermFeatureExtractor(
+ const std::unordered_set<std::string>* page_term_hashes,
+ const std::unordered_set<uint32_t>* page_word_hashes,
+ size_t max_words_per_term,
+ uint32_t murmurhash3_seed,
+ size_t max_shingles_per_page,
+ size_t shingle_size,
+ FeatureExtractorClock* clock)
+ : page_term_hashes_(page_term_hashes),
+ page_word_hashes_(page_word_hashes),
+ max_words_per_term_(max_words_per_term),
+ murmurhash3_seed_(murmurhash3_seed),
+ max_shingles_per_page_(max_shingles_per_page),
+ shingle_size_(shingle_size),
+ clock_(clock) {
+ Clear();
+}
+
+PhishingTermFeatureExtractor::~PhishingTermFeatureExtractor() {
+ // The RenderView should have called CancelPendingExtraction() before
+ // we are destroyed.
+ CheckNoPendingExtraction();
+}
+
+void PhishingTermFeatureExtractor::ExtractFeatures(
+ const base::string16* page_text,
+ FeatureMap* features,
+ std::set<uint32_t>* shingle_hashes,
+ DoneCallback done_callback) {
+ // The RenderView should have called CancelPendingExtraction() before
+ // starting a new extraction, so DCHECK this.
+ CheckNoPendingExtraction();
+ // However, in an opt build, we will go ahead and clean up the pending
+ // extraction so that we can start in a known state.
+ CancelPendingExtraction();
+
+ page_text_ = page_text;
+ features_ = features;
+ shingle_hashes_ = shingle_hashes, done_callback_ = std::move(done_callback);
+
+ state_.reset(new ExtractionState(*page_text_, clock_->Now()));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PhishingTermFeatureExtractor::ExtractFeaturesWithTimeout,
+ weak_factory_.GetWeakPtr()));
+}
+
+void PhishingTermFeatureExtractor::CancelPendingExtraction() {
+ // Cancel any pending callbacks, and clear our state.
+ weak_factory_.InvalidateWeakPtrs();
+ Clear();
+}
+
+void PhishingTermFeatureExtractor::ExtractFeaturesWithTimeout() {
+ DCHECK(state_.get());
+ ++state_->num_iterations;
+ base::TimeTicks current_chunk_start_time = clock_->Now();
+
+ if (!state_->iterator.get()) {
+ // We failed to initialize the break iterator, so stop now.
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.TermFeatureBreakIterError", 1);
+ RunCallback(false);
+ return;
+ }
+
+ int num_words = 0;
+ while (state_->iterator->Advance()) {
+ if (state_->iterator->IsWord()) {
+ const size_t start = state_->iterator->prev();
+ const size_t length = state_->iterator->pos() - start;
+ HandleWord(base::StringPiece16(page_text_->data() + start, length));
+ ++num_words;
+ }
+
+ if (num_words >= kClockCheckGranularity) {
+ num_words = 0;
+ base::TimeTicks now = clock_->Now();
+ if (now - state_->start_time >=
+ base::TimeDelta::FromMilliseconds(kMaxTotalTimeMs)) {
+ DLOG(ERROR) << "Feature extraction took too long, giving up";
+ // We expect this to happen infrequently, so record when it does.
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.TermFeatureTimeout", 1);
+ RunCallback(false);
+ return;
+ }
+ base::TimeDelta chunk_elapsed = now - current_chunk_start_time;
+ if (chunk_elapsed >=
+ base::TimeDelta::FromMilliseconds(kMaxTimePerChunkMs)) {
+ // The time limit for the current chunk is up, so post a task to
+ // continue extraction.
+ //
+ // Record how much time we actually spent on the chunk. If this is
+ // much higher than kMaxTimePerChunkMs, we may need to adjust the
+ // clock granularity.
+ UMA_HISTOGRAM_TIMES("SBClientPhishing.TermFeatureChunkTime",
+ chunk_elapsed);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &PhishingTermFeatureExtractor::ExtractFeaturesWithTimeout,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+ // Otherwise, continue.
+ }
+ }
+ RunCallback(true);
+}
+
+void PhishingTermFeatureExtractor::HandleWord(
+ const base::StringPiece16& word) {
+ // First, extract shingle hashes.
+ const std::string& word_lower = base::UTF16ToUTF8(base::i18n::ToLower(word));
+ state_->current_shingle.append(word_lower + " ");
+ state_->shingle_word_sizes.push_back(word_lower.size() + 1);
+ if (state_->shingle_word_sizes.size() == shingle_size_) {
+ shingle_hashes_->insert(
+ MurmurHash3String(state_->current_shingle, murmurhash3_seed_));
+ state_->current_shingle.erase(0, state_->shingle_word_sizes.front());
+ state_->shingle_word_sizes.pop_front();
+ }
+ // Check if the size of shingle hashes is over the limit.
+ if (shingle_hashes_->size() > max_shingles_per_page_) {
+ // Pop the largest one.
+ auto it = shingle_hashes_->end();
+ shingle_hashes_->erase(--it);
+ }
+
+ // Next, extract page terms.
+ uint32_t word_hash = MurmurHash3String(word_lower, murmurhash3_seed_);
+
+ // Quick out if the word is not part of any term, which is the common case.
+ if (page_word_hashes_->find(word_hash) == page_word_hashes_->end()) {
+ // Word doesn't exist in our terms so we can clear the n-gram state.
+ state_->previous_words.clear();
+ state_->previous_word_sizes.clear();
+ return;
+ }
+
+ // Find all of the n-grams that we need to check and compute their SHA-256
+ // hashes.
+ std::map<std::string /* hash */, std::string /* plaintext */>
+ hashes_to_check;
+ hashes_to_check[crypto::SHA256HashString(word_lower)] = word_lower;
+
+ // Combine the new word with the previous words to find additional n-grams.
+ // Note that we don't yet add the new word length to previous_word_sizes,
+ // since we don't want to compute the hash for the word by itself again.
+ //
+ state_->previous_words.append(word_lower);
+ std::string current_term = state_->previous_words;
+ for (auto it = state_->previous_word_sizes.begin();
+ it != state_->previous_word_sizes.end(); ++it) {
+ hashes_to_check[crypto::SHA256HashString(current_term)] = current_term;
+ current_term.erase(0, *it);
+ }
+
+ // Add features for any hashes that match page_term_hashes_.
+ for (auto it = hashes_to_check.begin(); it != hashes_to_check.end(); ++it) {
+ if (page_term_hashes_->find(it->first) != page_term_hashes_->end()) {
+ features_->AddBooleanFeature(features::kPageTerm + it->second);
+ }
+ }
+
+ // Now that we have handled the current word, we have to add a space at the
+ // end of it, and add the new word's size (including the space) to
+ // previous_word_sizes. Note: it's possible that the document language
+ // doesn't use ASCII spaces to separate words. That's fine though, we just
+ // need to be consistent with how the model is generated.
+ state_->previous_words.append(" ");
+ state_->previous_word_sizes.push_back(word_lower.size() + 1);
+
+ // Cap the number of previous words.
+ if (state_->previous_word_sizes.size() >= max_words_per_term_) {
+ state_->previous_words.erase(0, state_->previous_word_sizes.front());
+ state_->previous_word_sizes.pop_front();
+ }
+}
+
+void PhishingTermFeatureExtractor::CheckNoPendingExtraction() {
+ DCHECK(done_callback_.is_null());
+ DCHECK(!state_.get());
+ if (!done_callback_.is_null() || state_.get()) {
+ LOG(ERROR) << "Extraction in progress, missing call to "
+ << "CancelPendingExtraction";
+ }
+}
+
+void PhishingTermFeatureExtractor::RunCallback(bool success) {
+ // Record some timing stats that we can use to evaluate feature extraction
+ // performance. These include both successful and failed extractions.
+ DCHECK(state_.get());
+ UMA_HISTOGRAM_COUNTS_1M("SBClientPhishing.TermFeatureIterations",
+ state_->num_iterations);
+ UMA_HISTOGRAM_TIMES("SBClientPhishing.TermFeatureTotalTime",
+ clock_->Now() - state_->start_time);
+
+ DCHECK(!done_callback_.is_null());
+ std::move(done_callback_).Run(success);
+ Clear();
+}
+
+void PhishingTermFeatureExtractor::Clear() {
+ page_text_ = NULL;
+ features_ = NULL;
+ shingle_hashes_ = NULL;
+ done_callback_.Reset();
+ state_.reset(NULL);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h b/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h
new file mode 100644
index 00000000000..d0cc48d7033
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PhishingTermFeatureExtractor handles computing term features from the text
+// of a web page for the client-side phishing detection model. To do this, it
+// takes a list of terms that appear in the model, and scans through the page
+// text looking for them. Any terms that appear will cause a corresponding
+// features::kPageTerm feature to be added to the FeatureMap.
+//
+// To make it harder for a phisher to enumerate all of the relevant terms in
+// the model, the terms are provided as SHA-256 hashes, rather than plain text.
+//
+// There is one PhishingTermFeatureExtractor per RenderView.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_PHISHING_TERM_FEATURE_EXTRACTOR_H_
+#define CHROME_RENDERER_SAFE_BROWSING_PHISHING_TERM_FEATURE_EXTRACTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_set>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+
+namespace safe_browsing {
+class FeatureExtractorClock;
+class FeatureMap;
+
+class PhishingTermFeatureExtractor {
+ public:
+ // Callback to be run when feature extraction finishes. The callback
+ // argument is true if extraction was successful, false otherwise.
+ typedef base::OnceCallback<void(bool)> DoneCallback;
+
+ // Creates a PhishingTermFeatureExtractor which will extract features for
+ // all of the terms whose SHA-256 hashes are in |page_term_hashes|. These
+ // terms may be multi-word n-grams, with at most |max_words_per_term| words.
+ //
+ // |page_word_hashes| contains the murmur3 hashes for all of the individual
+ // words that make up the terms. Both sets of strings are UTF-8 encoded and
+ // lowercased prior to hashing. The caller owns both sets of strings, and
+ // must ensure that they are valid until the PhishingTermFeatureExtractor is
+ // destroyed.
+ //
+ // In addition to extracting page terms, we will also extract text shingling
+ // sketch, which consists of hashes of N-gram-words (referred to as shingles)
+ // in the page. |shingle_size| defines N, and |max_shingles_per_page| defines
+ // the maximum number of unique shingle hashes we extracted per page.
+ //
+ // |clock| is used for timing feature extractor operations, and may be mocked
+ // for testing. The caller keeps ownership of the clock.
+ PhishingTermFeatureExtractor(
+ const std::unordered_set<std::string>* page_term_hashes,
+ const std::unordered_set<uint32_t>* page_word_hashes,
+ size_t max_words_per_term,
+ uint32_t murmurhash3_seed,
+ size_t max_shingles_per_page,
+ size_t shingle_size,
+ FeatureExtractorClock* clock);
+ ~PhishingTermFeatureExtractor();
+
+ // Begins extracting features from |page_text| into the given FeatureMap.
+ // |page_text| should contain the plain text of a web page, including any
+ // subframes, as returned by RenderView::CaptureText().
+ //
+ // To avoid blocking the render thread for too long, the feature extractor
+ // may run in several chunks of work, posting a task to the current
+ // MessageLoop to continue processing. Once feature extraction is complete,
+ // |done_callback| is run on the current thread.
+ // PhishingTermFeatureExtractor takes ownership of the callback.
+ //
+ // |page_text|, |features|, and |shingle_hashes| are owned by the caller,
+ // and must not be destroyed until either |done_callback| is run or
+ // CancelPendingExtraction() is called.
+ void ExtractFeatures(const base::string16* page_text,
+ FeatureMap* features,
+ std::set<uint32_t>* shingle_hashes,
+ DoneCallback done_callback);
+
+ // Cancels any pending feature extraction. The DoneCallback will not be run.
+ // Must be called if there is a feature extraction in progress when the page
+ // is unloaded or the PhishingTermFeatureExtractor is destroyed.
+ void CancelPendingExtraction();
+
+ private:
+ struct ExtractionState;
+
+ // The maximum amount of wall time that we will spend on a single extraction
+ // iteration before pausing to let other MessageLoop tasks run.
+ static const int kMaxTimePerChunkMs;
+
+ // The number of words that we will process before checking to see whether
+ // kMaxTimePerChunkMs has elapsed. Since checking the current time can be
+ // slow, we don't do this on every word processed.
+ static const int kClockCheckGranularity;
+
+ // The maximum total amount of time that the feature extractor will run
+ // before giving up on the current page.
+ static const int kMaxTotalTimeMs;
+
+ // Does the actual work of ExtractFeatures. ExtractFeaturesWithTimeout runs
+ // until a predefined maximum amount of time has elapsed, then posts a task
+ // to the current MessageLoop to continue extraction. When extraction
+ // finishes, calls RunCallback().
+ void ExtractFeaturesWithTimeout();
+
+ // Handles a single word in the page text.
+ void HandleWord(const base::StringPiece16& word);
+
+ // Helper to verify that there is no pending feature extraction. Dies in
+ // debug builds if the state is not as expected. This is a no-op in release
+ // builds.
+ void CheckNoPendingExtraction();
+
+ // Runs |done_callback_| and then clears all internal state.
+ void RunCallback(bool success);
+
+ // Clears all internal feature extraction state.
+ void Clear();
+
+ // All of the term hashes that we are looking for in the page.
+ const std::unordered_set<std::string>* page_term_hashes_;
+
+ // Murmur3 hashes of all the individual words in page_term_hashes_. If
+ // page_term_hashes_ included (hashed) "one" and "one two", page_word_hashes_
+ // would contain (hashed) "one" and "two". We do this so that we can have a
+ // quick out in the common case that the current word we are processing
+ // doesn't contain any part of one of our terms.
+ const std::unordered_set<uint32_t>* page_word_hashes_;
+
+ // The maximum number of words in an n-gram.
+ const size_t max_words_per_term_;
+
+ // The seed for murmurhash3.
+ const uint32_t murmurhash3_seed_;
+
+ // The maximum number of unique shingle hashes we extract in a page.
+ const size_t max_shingles_per_page_;
+
+ // The number of words in a shingle.
+ const size_t shingle_size_;
+
+ // Non-owned pointer to our clock.
+ FeatureExtractorClock* clock_;
+
+ // The output parameters from the most recent call to ExtractFeatures().
+ const base::string16* page_text_; // The caller keeps ownership of this.
+ FeatureMap* features_; // The caller keeps ownership of this.
+ std::set<uint32_t>* shingle_hashes_;
+ DoneCallback done_callback_;
+
+ // Stores the current state of term extraction from |page_text_|.
+ std::unique_ptr<ExtractionState> state_;
+
+ // Used in scheduling ExtractFeaturesWithTimeout tasks.
+ // These pointers are invalidated if extraction is cancelled.
+ base::WeakPtrFactory<PhishingTermFeatureExtractor> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PhishingTermFeatureExtractor);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_PHISHING_TERM_FEATURE_EXTRACTOR_H_
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc b/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
new file mode 100644
index 00000000000..c1b38303cbd
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
@@ -0,0 +1,468 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_term_feature_extractor.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string16.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/mock_feature_extractor_clock.h"
+#include "chrome/renderer/safe_browsing/murmurhash3_util.h"
+#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "crypto/sha2.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ASCIIToUTF16;
+using ::testing::Return;
+
+static const uint32_t kMurmurHash3Seed = 2777808611U;
+
+namespace safe_browsing {
+
+class PhishingTermFeatureExtractorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ std::unordered_set<std::string> terms;
+ terms.insert("one");
+ terms.insert("one one");
+ terms.insert("two");
+ terms.insert("multi word test");
+ terms.insert("capitalization");
+ terms.insert("space");
+ terms.insert("separator");
+ terms.insert("punctuation");
+ // Chinese (translation of "hello")
+ terms.insert("\xe4\xbd\xa0\xe5\xa5\xbd");
+ // Chinese (translation of "goodbye")
+ terms.insert("\xe5\x86\x8d\xe8\xa7\x81");
+
+ for (auto it = terms.begin(); it != terms.end(); ++it) {
+ term_hashes_.insert(crypto::SHA256HashString(*it));
+ }
+
+ std::unordered_set<std::string> words;
+ words.insert("one");
+ words.insert("two");
+ words.insert("multi");
+ words.insert("word");
+ words.insert("test");
+ words.insert("capitalization");
+ words.insert("space");
+ words.insert("separator");
+ words.insert("punctuation");
+ words.insert("\xe4\xbd\xa0\xe5\xa5\xbd");
+ words.insert("\xe5\x86\x8d\xe8\xa7\x81");
+
+ for (auto it = words.begin(); it != words.end(); ++it) {
+ word_hashes_.insert(MurmurHash3String(*it, kMurmurHash3Seed));
+ }
+
+ ResetExtractor(3 /* max shingles per page */);
+ }
+
+ void ResetExtractor(size_t max_shingles_per_page) {
+ extractor_.reset(new PhishingTermFeatureExtractor(
+ &term_hashes_,
+ &word_hashes_,
+ 3 /* max_words_per_term */,
+ kMurmurHash3Seed,
+ max_shingles_per_page,
+ 4 /* shingle_size */,
+ &clock_));
+ }
+
+ // Runs the TermFeatureExtractor on |page_text|, waiting for the
+ // completion callback. Returns the success boolean from the callback.
+ bool ExtractFeatures(const base::string16* page_text,
+ FeatureMap* features,
+ std::set<uint32_t>* shingle_hashes) {
+ success_ = false;
+ extractor_->ExtractFeatures(
+ page_text, features, shingle_hashes,
+ base::BindOnce(&PhishingTermFeatureExtractorTest::ExtractionDone,
+ base::Unretained(this)));
+ active_run_loop_ = std::make_unique<base::RunLoop>();
+ active_run_loop_->Run();
+ return success_;
+ }
+
+ void PartialExtractFeatures(const base::string16* page_text,
+ FeatureMap* features,
+ std::set<uint32_t>* shingle_hashes) {
+ extractor_->ExtractFeatures(
+ page_text, features, shingle_hashes,
+ base::BindOnce(&PhishingTermFeatureExtractorTest::ExtractionDone,
+ base::Unretained(this)));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PhishingTermFeatureExtractorTest::QuitExtraction,
+ base::Unretained(this)));
+ active_run_loop_ = std::make_unique<base::RunLoop>();
+ active_run_loop_->RunUntilIdle();
+ }
+
+ // Completion callback for feature extraction.
+ void ExtractionDone(bool success) {
+ success_ = success;
+ active_run_loop_->QuitWhenIdle();
+ }
+
+ void QuitExtraction() {
+ extractor_->CancelPendingExtraction();
+ active_run_loop_->QuitWhenIdle();
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<base::RunLoop> active_run_loop_;
+ MockFeatureExtractorClock clock_;
+ std::unique_ptr<PhishingTermFeatureExtractor> extractor_;
+ std::unordered_set<std::string> term_hashes_;
+ std::unordered_set<uint32_t> word_hashes_;
+ bool success_; // holds the success value from ExtractFeatures
+};
+
+TEST_F(PhishingTermFeatureExtractorTest, ExtractFeatures) {
+ // This test doesn't exercise the extraction timing.
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::TimeTicks::Now()));
+
+ base::string16 page_text = ASCIIToUTF16("blah");
+ FeatureMap expected_features; // initially empty
+ std::set<uint32_t> expected_shingle_hashes;
+
+ FeatureMap features;
+ std::set<uint32_t> shingle_hashes;
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+ page_text = ASCIIToUTF16("one one");
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("one"));
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("one one"));
+ expected_shingle_hashes.clear();
+
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+ page_text = ASCIIToUTF16("bla bla multi word test bla");
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("multi word test"));
+ expected_shingle_hashes.clear();
+ expected_shingle_hashes.insert(MurmurHash3String("bla bla multi word ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("bla multi word test ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("multi word test bla ",
+ kMurmurHash3Seed));
+
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+ // This text has all of the words for one of the terms, but they are
+ // not in the correct order.
+ page_text = ASCIIToUTF16("bla bla test word multi bla");
+ expected_features.Clear();
+ expected_shingle_hashes.clear();
+ expected_shingle_hashes.insert(MurmurHash3String("bla bla test word ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("bla test word multi ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("test word multi bla ",
+ kMurmurHash3Seed));
+
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+ // Test various separators.
+ page_text = ASCIIToUTF16("Capitalization plus non-space\n"
+ "separator... punctuation!");
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("capitalization"));
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("space"));
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("separator"));
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("punctuation"));
+ expected_shingle_hashes.clear();
+ expected_shingle_hashes.insert(
+ MurmurHash3String("capitalization plus non space ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("plus non space separator ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("non space separator punctuation ", kMurmurHash3Seed));
+
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+ // Test a page with too many words and we should only 3 minimum hashes.
+ page_text = ASCIIToUTF16("This page has way too many words.");
+ expected_features.Clear();
+ expected_shingle_hashes.clear();
+ expected_shingle_hashes.insert(MurmurHash3String("this page has way ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("page has way too ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("has way too many ",
+ kMurmurHash3Seed));
+ expected_shingle_hashes.insert(MurmurHash3String("way too many words ",
+ kMurmurHash3Seed));
+ auto it = expected_shingle_hashes.end();
+ expected_shingle_hashes.erase(--it);
+
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+ // Test with empty page text.
+ page_text = base::string16();
+ expected_features.Clear();
+ expected_shingle_hashes.clear();
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+
+#if !defined(OS_ANDROID)
+ // The test code is disabled due to http://crbug.com/392234
+ // The client-side detection feature is not enabled on Android yet.
+ // If we decided to enable the feature, we need to fix the bug first.
+
+ // Chinese translation of the phrase "hello goodbye hello goodbye". This tests
+ // that we can correctly separate terms in languages that don't use spaces.
+ page_text =
+ base::UTF8ToUTF16("\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x86\x8d\xe8\xa7\x81"
+ "\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x86\x8d\xe8\xa7\x81");
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(
+ features::kPageTerm + std::string("\xe4\xbd\xa0\xe5\xa5\xbd"));
+ expected_features.AddBooleanFeature(
+ features::kPageTerm + std::string("\xe5\x86\x8d\xe8\xa7\x81"));
+ expected_shingle_hashes.clear();
+ expected_shingle_hashes.insert(MurmurHash3String(
+ "\xe4\xbd\xa0\xe5\xa5\xbd \xe5\x86\x8d\xe8\xa7\x81 "
+ "\xe4\xbd\xa0\xe5\xa5\xbd \xe5\x86\x8d\xe8\xa7\x81 ", kMurmurHash3Seed));
+
+ features.Clear();
+ shingle_hashes.clear();
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+#endif
+}
+
+TEST_F(PhishingTermFeatureExtractorTest, Continuation) {
+ // For this test, we'll cause the feature extraction to run multiple
+ // iterations by incrementing the clock.
+ ResetExtractor(200 /* max shingles per page */);
+
+ // This page has a total of 30 words. For the features to be computed
+ // correctly, the extractor has to process the entire string of text.
+ base::string16 page_text(ASCIIToUTF16("one "));
+ for (int i = 0; i < 28; ++i) {
+ page_text.append(ASCIIToUTF16(base::StringPrintf("%d ", i)));
+ }
+ page_text.append(ASCIIToUTF16("two"));
+
+ // Advance the clock 3 ms every 5 words processed, 10 ms between chunks.
+ // Note that this assumes kClockCheckGranularity = 5 and
+ // kMaxTimePerChunkMs = 10.
+ base::TimeTicks now = base::TimeTicks::Now();
+ EXPECT_CALL(clock_, Now())
+ // Time check at the start of extraction.
+ .WillOnce(Return(now))
+ // Time check at the start of the first chunk of work.
+ .WillOnce(Return(now))
+ // Time check after the first 5 words.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(3)))
+ // Time check after the next 5 words.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(6)))
+ // Time check after the next 5 words.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(9)))
+ // Time check after the next 5 words. This is over the chunk
+ // time limit, so a continuation task will be posted.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(12)))
+ // Time check at the start of the second chunk of work.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(22)))
+ // Time check after the next 5 words.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(25)))
+ // Time check after the next 5 words.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(28)))
+ // A final check for the histograms.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(30)));
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("one"));
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("two"));
+ std::set<uint32_t> expected_shingle_hashes;
+ expected_shingle_hashes.insert(
+ MurmurHash3String("one 0 1 2 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("0 1 2 3 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("1 2 3 4 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("2 3 4 5 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("3 4 5 6 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("4 5 6 7 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("5 6 7 8 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("6 7 8 9 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("7 8 9 10 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("8 9 10 11 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("9 10 11 12 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("10 11 12 13 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("11 12 13 14 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("12 13 14 15 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("13 14 15 16 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("14 15 16 17 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("15 16 17 18 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("16 17 18 19 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("17 18 19 20 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("18 19 20 21 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("19 20 21 22 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("20 21 22 23 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("21 22 23 24 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("22 23 24 25 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("23 24 25 26 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("24 25 26 27 ", kMurmurHash3Seed));
+ expected_shingle_hashes.insert(
+ MurmurHash3String("25 26 27 two ", kMurmurHash3Seed));
+
+ FeatureMap features;
+ std::set<uint32_t> shingle_hashes;
+ ASSERT_TRUE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+ EXPECT_THAT(expected_shingle_hashes, testing::ContainerEq(shingle_hashes));
+ // Make sure none of the mock expectations carry over to the next test.
+ ::testing::Mock::VerifyAndClearExpectations(&clock_);
+
+ // Now repeat the test with the same text, but advance the clock faster so
+ // that the extraction time exceeds the maximum total time for the feature
+ // extractor. Extraction should fail. Note that this assumes
+ // kMaxTotalTimeMs = 500.
+ EXPECT_CALL(clock_, Now())
+ // Time check at the start of extraction.
+ .WillOnce(Return(now))
+ // Time check at the start of the first chunk of work.
+ .WillOnce(Return(now))
+ // Time check after the first 5 words,
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(300)))
+ // Time check at the start of the second chunk of work.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(350)))
+ // Time check after the next 5 words. This is over the limit.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(600)))
+ // A final time check for the histograms.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(620)));
+
+ features.Clear();
+ shingle_hashes.clear();
+ EXPECT_FALSE(ExtractFeatures(&page_text, &features, &shingle_hashes));
+}
+
+TEST_F(PhishingTermFeatureExtractorTest, PartialExtractionTest) {
+ std::unique_ptr<base::string16> page_text(
+ new base::string16(ASCIIToUTF16("one ")));
+ for (int i = 0; i < 28; ++i) {
+ page_text->append(ASCIIToUTF16(base::StringPrintf("%d ", i)));
+ }
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ EXPECT_CALL(clock_, Now())
+ // Time check at the start of extraction.
+ .WillOnce(Return(now))
+ // Time check at the start of the first chunk of work.
+ .WillOnce(Return(now))
+ // Time check after the first 5 words.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(7)))
+ // Time check after the next 5 words. This should be greater than
+ // kMaxTimePerChunkMs so that we stop and schedule extraction for later.
+ .WillOnce(Return(now + base::TimeDelta::FromMilliseconds(14)));
+
+ FeatureMap features;
+ std::set<uint32_t> shingle_hashes;
+ // Extract first 10 words then stop.
+ PartialExtractFeatures(page_text.get(), &features, &shingle_hashes);
+
+ page_text.reset(new base::string16());
+ for (int i = 30; i < 58; ++i) {
+ page_text->append(ASCIIToUTF16(base::StringPrintf("%d ", i)));
+ }
+ page_text->append(ASCIIToUTF16("multi word test "));
+ features.Clear();
+ shingle_hashes.clear();
+
+ // This part doesn't exercise the extraction timing.
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::TimeTicks::Now()));
+
+ // Now extract normally and make sure nothing breaks.
+ EXPECT_TRUE(ExtractFeatures(page_text.get(), &features, &shingle_hashes));
+
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kPageTerm +
+ std::string("multi word test"));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc b/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc
new file mode 100644
index 00000000000..931c5203edd
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_url_feature_extractor.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/gurl.h"
+
+namespace safe_browsing {
+
+PhishingUrlFeatureExtractor::PhishingUrlFeatureExtractor() {}
+
+PhishingUrlFeatureExtractor::~PhishingUrlFeatureExtractor() {}
+
+bool PhishingUrlFeatureExtractor::ExtractFeatures(const GURL& url,
+ FeatureMap* features) {
+ base::ElapsedTimer timer;
+ if (url.HostIsIPAddress()) {
+ if (!features->AddBooleanFeature(features::kUrlHostIsIpAddress))
+ return false;
+ } else {
+ // Remove any leading/trailing dots.
+ std::string host;
+ base::TrimString(url.host(), ".", &host);
+
+ // TODO(bryner): Ensure that the url encoding is consistent with
+ // the features in the model.
+
+ // Disallow unknown registries so that we don't classify
+ // partial hostnames (e.g. "www.subdomain").
+ size_t registry_length =
+ net::registry_controlled_domains::GetCanonicalHostRegistryLength(
+ host, net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+
+ if (registry_length == 0 || registry_length == std::string::npos) {
+ DVLOG(1) << "Could not find TLD for host: " << host;
+ return false;
+ }
+ DCHECK_LT(registry_length, host.size()) << "Non-zero registry length, but "
+ "host is only a TLD: " << host;
+ size_t tld_start = host.size() - registry_length;
+ if (!features->AddBooleanFeature(features::kUrlTldToken +
+ host.substr(tld_start)))
+ return false;
+
+ // Pull off the TLD and the preceeding dot.
+ host.erase(tld_start - 1);
+ std::vector<std::string> host_tokens = base::SplitString(
+ host, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (host_tokens.empty()) {
+ DVLOG(1) << "Could not find domain for host: " << host;
+ return false;
+ }
+ if (!features->AddBooleanFeature(features::kUrlDomainToken +
+ host_tokens.back()))
+ return false;
+ host_tokens.pop_back();
+
+ // Now we're just left with the "other" host tokens.
+ for (auto it = host_tokens.begin(); it != host_tokens.end(); ++it) {
+ if (!features->AddBooleanFeature(features::kUrlOtherHostToken + *it))
+ return false;
+ }
+
+ if (host_tokens.size() > 1) {
+ if (!features->AddBooleanFeature(features::kUrlNumOtherHostTokensGTOne))
+ return false;
+ if (host_tokens.size() > 3) {
+ if (!features->AddBooleanFeature(
+ features::kUrlNumOtherHostTokensGTThree))
+ return false;
+ }
+ }
+ }
+
+ std::vector<std::string> long_tokens;
+ SplitStringIntoLongAlphanumTokens(url.path(), &long_tokens);
+ for (const std::string& token : long_tokens) {
+ if (!features->AddBooleanFeature(features::kUrlPathToken + token))
+ return false;
+ }
+
+ UMA_HISTOGRAM_TIMES("SBClientPhishing.URLFeatureTime", timer.Elapsed());
+ return true;
+}
+
+// static
+void PhishingUrlFeatureExtractor::SplitStringIntoLongAlphanumTokens(
+ const std::string& full,
+ std::vector<std::string>* tokens) {
+ // Split on common non-alphanumerics.
+ // TODO(bryner): Split on all(?) non-alphanumerics and handle %XX properly.
+ static const char kTokenSeparators[] = ".,\\/_-|=%:!&";
+ for (const base::StringPiece& token :
+ base::SplitStringPiece(full, kTokenSeparators, base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY)) {
+ // Copy over only the splits that are 3 or more chars long.
+ // TODO(bryner): Determine a meaningful min size.
+ if (token.length() >= kMinPathComponentLength)
+ tokens->push_back(token.as_string());
+ }
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.h b/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.h
new file mode 100644
index 00000000000..09460a66a44
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PhishingUrlFeatureExtractor handles computing URL-based features for
+// the client-side phishing detection model. These include tokens in the
+// host and path, features pertaining to host length, and IP addresses.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_PHISHING_URL_FEATURE_EXTRACTOR_H_
+#define CHROME_RENDERER_SAFE_BROWSING_PHISHING_URL_FEATURE_EXTRACTOR_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+class GURL;
+
+namespace safe_browsing {
+class FeatureMap;
+
+class PhishingUrlFeatureExtractor {
+ public:
+ PhishingUrlFeatureExtractor();
+ ~PhishingUrlFeatureExtractor();
+
+ // Extracts features for |url| into the given feature map.
+ // Returns true on success.
+ bool ExtractFeatures(const GURL& url, FeatureMap* features);
+
+ private:
+ friend class PhishingUrlFeatureExtractorTest;
+
+ static const size_t kMinPathComponentLength = 3;
+
+ // Given a string, finds all substrings of consecutive alphanumeric
+ // characters of length >= kMinPathComponentLength and inserts them into
+ // tokens.
+ static void SplitStringIntoLongAlphanumTokens(
+ const std::string& full,
+ std::vector<std::string>* tokens);
+
+ DISALLOW_COPY_AND_ASSIGN(PhishingUrlFeatureExtractor);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_PHISHING_URL_FEATURE_EXTRACTOR_H_
diff --git a/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc b/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc
new file mode 100644
index 00000000000..e5412a7bd4e
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/phishing_url_feature_extractor.h"
+
+#include <string>
+#include <vector>
+#include "chrome/renderer/safe_browsing/features.h"
+#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::testing::ElementsAre;
+
+namespace safe_browsing {
+
+class PhishingUrlFeatureExtractorTest : public ::testing::Test {
+ protected:
+ PhishingUrlFeatureExtractor extractor_;
+
+ void SplitStringIntoLongAlphanumTokens(const std::string& full,
+ std::vector<std::string>* tokens) {
+ PhishingUrlFeatureExtractor::SplitStringIntoLongAlphanumTokens(full,
+ tokens);
+ }
+};
+
+TEST_F(PhishingUrlFeatureExtractorTest, ExtractFeatures) {
+ std::string url = "http://123.0.0.1/mydocuments/a.file.html";
+ FeatureMap expected_features;
+ expected_features.AddBooleanFeature(features::kUrlHostIsIpAddress);
+ expected_features.AddBooleanFeature(features::kUrlPathToken +
+ std::string("mydocuments"));
+ expected_features.AddBooleanFeature(features::kUrlPathToken +
+ std::string("file"));
+ expected_features.AddBooleanFeature(features::kUrlPathToken +
+ std::string("html"));
+
+ FeatureMap features;
+ ASSERT_TRUE(extractor_.ExtractFeatures(GURL(url), &features));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ url = "http://www.www.cnn.co.uk/sports/sports/index.html?shouldnotappear";
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kUrlTldToken +
+ std::string("co.uk"));
+ expected_features.AddBooleanFeature(features::kUrlDomainToken +
+ std::string("cnn"));
+ expected_features.AddBooleanFeature(features::kUrlOtherHostToken +
+ std::string("www"));
+ expected_features.AddBooleanFeature(features::kUrlNumOtherHostTokensGTOne);
+ expected_features.AddBooleanFeature(features::kUrlPathToken +
+ std::string("sports"));
+ expected_features.AddBooleanFeature(features::kUrlPathToken +
+ std::string("index"));
+ expected_features.AddBooleanFeature(features::kUrlPathToken +
+ std::string("html"));
+
+ features.Clear();
+ ASSERT_TRUE(extractor_.ExtractFeatures(GURL(url), &features));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ url = "http://justadomain.com/";
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kUrlTldToken +
+ std::string("com"));
+ expected_features.AddBooleanFeature(features::kUrlDomainToken +
+ std::string("justadomain"));
+
+ features.Clear();
+ ASSERT_TRUE(extractor_.ExtractFeatures(GURL(url), &features));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ url = "http://witharef.com/#abc";
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kUrlTldToken +
+ std::string("com"));
+ expected_features.AddBooleanFeature(features::kUrlDomainToken +
+ std::string("witharef"));
+
+ features.Clear();
+ ASSERT_TRUE(extractor_.ExtractFeatures(GURL(url), &features));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ url = "http://...www..lotsodots....com./";
+ expected_features.Clear();
+ expected_features.AddBooleanFeature(features::kUrlTldToken +
+ std::string("com"));
+ expected_features.AddBooleanFeature(features::kUrlDomainToken +
+ std::string("lotsodots"));
+ expected_features.AddBooleanFeature(features::kUrlOtherHostToken +
+ std::string("www"));
+
+ features.Clear();
+ ASSERT_TRUE(extractor_.ExtractFeatures(GURL(url), &features));
+ ExpectFeatureMapsAreEqual(features, expected_features);
+
+ url = "http://unrecognized.tld/";
+ EXPECT_FALSE(extractor_.ExtractFeatures(GURL(url), &features));
+
+ url = "http://com/123";
+ EXPECT_FALSE(extractor_.ExtractFeatures(GURL(url), &features));
+
+ url = "http://.co.uk/";
+ EXPECT_FALSE(extractor_.ExtractFeatures(GURL(url), &features));
+
+ url = "file:///nohost.txt";
+ EXPECT_FALSE(extractor_.ExtractFeatures(GURL(url), &features));
+
+ url = "not:valid:at:all";
+ EXPECT_FALSE(extractor_.ExtractFeatures(GURL(url), &features));
+}
+
+TEST_F(PhishingUrlFeatureExtractorTest, SplitStringIntoLongAlphanumTokens) {
+ std::string full = "This.is/a_pretty\\unusual-!path,indeed";
+ std::vector<std::string> long_tokens;
+ SplitStringIntoLongAlphanumTokens(full, &long_tokens);
+ EXPECT_THAT(long_tokens,
+ ElementsAre("This", "pretty", "unusual", "path", "indeed"));
+
+ long_tokens.clear();
+ full = "...i-am_re/al&ly\\b,r,o|k=e:n///up%20";
+ SplitStringIntoLongAlphanumTokens(full, &long_tokens);
+ EXPECT_THAT(long_tokens, ElementsAre());
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/scorer.cc b/chromium/chrome/renderer/safe_browsing/scorer.cc
new file mode 100644
index 00000000000..23ceefb610b
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/scorer.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/scorer.h"
+
+#include <math.h>
+
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_piece.h"
+#include "chrome/common/safe_browsing/client_model.pb.h"
+#include "chrome/renderer/safe_browsing/features.h"
+
+namespace {
+// Enum used to keep stats about the status of the Scorer creation.
+enum ScorerCreationStatus {
+ SCORER_SUCCESS,
+ SCORER_FAIL_MODEL_OPEN_FAIL, // Not used anymore
+ SCORER_FAIL_MODEL_FILE_EMPTY, // Not used anymore
+ SCORER_FAIL_MODEL_FILE_TOO_LARGE, // Not used anymore
+ SCORER_FAIL_MODEL_PARSE_ERROR,
+ SCORER_FAIL_MODEL_MISSING_FIELDS,
+ SCORER_STATUS_MAX // Always add new values before this one.
+};
+
+void RecordScorerCreationStatus(ScorerCreationStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ScorerCreationStatus",
+ status,
+ SCORER_STATUS_MAX);
+}
+} // namespace
+
+namespace safe_browsing {
+
+// Helper function which converts log odds to a probability in the range
+// [0.0,1.0].
+static double LogOdds2Prob(double log_odds) {
+ // 709 = floor(1023*ln(2)). 2**1023 is the largest finite double.
+ // Small log odds aren't a problem. as the odds will be 0. It's only
+ // when we get +infinity for the odds, that odds/(odds+1) would be NaN.
+ if (log_odds >= 709) {
+ return 1.0;
+ }
+ double odds = exp(log_odds);
+ return odds/(odds+1.0);
+}
+
+Scorer::Scorer() {}
+Scorer::~Scorer() {}
+
+/* static */
+Scorer* Scorer::Create(const base::StringPiece& model_str) {
+ std::unique_ptr<Scorer> scorer(new Scorer());
+ ClientSideModel& model = scorer->model_;
+ if (!model.ParseFromArray(model_str.data(), model_str.size())) {
+ DLOG(ERROR) << "Unable to parse phishing model. This Scorer object is "
+ << "invalid.";
+ RecordScorerCreationStatus(SCORER_FAIL_MODEL_PARSE_ERROR);
+ return NULL;
+ } else if (!model.IsInitialized()) {
+ DLOG(ERROR) << "Unable to parse phishing model. The model is missing "
+ << "some required fields. Maybe the .proto file changed?";
+ RecordScorerCreationStatus(SCORER_FAIL_MODEL_MISSING_FIELDS);
+ return NULL;
+ }
+ RecordScorerCreationStatus(SCORER_SUCCESS);
+ for (int i = 0; i < model.page_term_size(); ++i) {
+ scorer->page_terms_.insert(model.hashes(model.page_term(i)));
+ }
+ for (int i = 0; i < model.page_word_size(); ++i) {
+ scorer->page_words_.insert(model.page_word(i));
+ }
+ return scorer.release();
+}
+
+double Scorer::ComputeScore(const FeatureMap& features) const {
+ double logodds = 0.0;
+ for (int i = 0; i < model_.rule_size(); ++i) {
+ logodds += ComputeRuleScore(model_.rule(i), features);
+ }
+ return LogOdds2Prob(logodds);
+}
+
+int Scorer::model_version() const {
+ return model_.version();
+}
+
+const std::unordered_set<std::string>& Scorer::page_terms() const {
+ return page_terms_;
+}
+
+const std::unordered_set<uint32_t>& Scorer::page_words() const {
+ return page_words_;
+}
+
+size_t Scorer::max_words_per_term() const {
+ return model_.max_words_per_term();
+}
+
+uint32_t Scorer::murmurhash3_seed() const {
+ return model_.murmur_hash_seed();
+}
+
+size_t Scorer::max_shingles_per_page() const {
+ return model_.max_shingles_per_page();
+}
+
+size_t Scorer::shingle_size() const {
+ return model_.shingle_size();
+}
+
+double Scorer::ComputeRuleScore(const ClientSideModel::Rule& rule,
+ const FeatureMap& features) const {
+ const std::unordered_map<std::string, double>& feature_map =
+ features.features();
+ double rule_score = 1.0;
+ for (int i = 0; i < rule.feature_size(); ++i) {
+ const auto it = feature_map.find(model_.hashes(rule.feature(i)));
+ if (it == feature_map.end() || it->second == 0.0) {
+ // If the feature of the rule does not exist in the given feature map the
+ // feature weight is considered to be zero. If the feature weight is zero
+ // we leave early since we know that the rule score will be zero.
+ return 0.0;
+ }
+ rule_score *= it->second;
+ }
+ return rule_score * rule.weight();
+}
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/scorer.h b/chromium/chrome/renderer/safe_browsing/scorer.h
new file mode 100644
index 00000000000..c1a8991dbd1
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/scorer.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This class loads a client-side model and lets you compute a phishing score
+// for a set of previously extracted features. The phishing score corresponds
+// to the probability that the features are indicative of a phishing site.
+//
+// For more details on how the score is actually computed for a given model
+// and a given set of features read the comments in client_model.proto file.
+//
+// See features.h for a list of features that are currently used.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_SCORER_H_
+#define CHROME_RENDERER_SAFE_BROWSING_SCORER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <unordered_set>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "chrome/common/safe_browsing/client_model.pb.h"
+
+namespace safe_browsing {
+class FeatureMap;
+
+// Scorer methods are virtual to simplify mocking of this class.
+class Scorer {
+ public:
+ virtual ~Scorer();
+
+ // Factory method which creates a new Scorer object by parsing the given
+ // model. If parsing fails this method returns NULL.
+ static Scorer* Create(const base::StringPiece& model_str);
+
+ // This method computes the probability that the given features are indicative
+ // of phishing. It returns a score value that falls in the range [0.0,1.0]
+ // (range is inclusive on both ends).
+ virtual double ComputeScore(const FeatureMap& features) const;
+
+ // Returns the version number of the loaded client model.
+ int model_version() const;
+
+ // -- Accessors used by the page feature extractor ---------------------------
+
+ // Returns a set of hashed page terms that appear in the model in binary
+ // format.
+ const std::unordered_set<std::string>& page_terms() const;
+
+ // Returns a set of hashed page words that appear in the model in binary
+ // format.
+ const std::unordered_set<uint32_t>& page_words() const;
+
+ // Return the maximum number of words per term for the loaded model.
+ size_t max_words_per_term() const;
+
+ // Returns the murmurhash3 seed for the loaded model.
+ uint32_t murmurhash3_seed() const;
+
+ // Return the maximum number of unique shingle hashes per page.
+ size_t max_shingles_per_page() const;
+
+ // Return the number of words in a shingle.
+ size_t shingle_size() const;
+
+ protected:
+ // Most clients should use the factory method. This constructor is public
+ // to allow for mock implementations.
+ Scorer();
+
+ private:
+ friend class PhishingScorerTest;
+
+ // Computes the score for a given rule and feature map. The score is computed
+ // by multiplying the rule weight with the product of feature weights for the
+ // given rule. The feature weights are stored in the feature map. If a
+ // particular feature does not exist in the feature map we set its weight to
+ // zero.
+ double ComputeRuleScore(const ClientSideModel::Rule& rule,
+ const FeatureMap& features) const;
+
+ ClientSideModel model_;
+ std::unordered_set<std::string> page_terms_;
+ std::unordered_set<uint32_t> page_words_;
+
+ DISALLOW_COPY_AND_ASSIGN(Scorer);
+};
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_SCORER_H_
diff --git a/chromium/chrome/renderer/safe_browsing/scorer_unittest.cc b/chromium/chrome/renderer/safe_browsing/scorer_unittest.cc
new file mode 100644
index 00000000000..6f9094b7dd1
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/scorer_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/scorer.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <unordered_set>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/threading/thread.h"
+#include "chrome/common/safe_browsing/client_model.pb.h"
+#include "chrome/renderer/safe_browsing/features.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+class PhishingScorerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Setup a simple model. Note that the scorer does not care about
+ // how features are encoded so we use readable strings here to make
+ // the test simpler to follow.
+ model_.Clear();
+ model_.add_hashes("feature1");
+ model_.add_hashes("feature2");
+ model_.add_hashes("feature3");
+ model_.add_hashes("token one");
+ model_.add_hashes("token two");
+
+ ClientSideModel::Rule* rule;
+ rule = model_.add_rule();
+ rule->set_weight(0.5);
+
+ rule = model_.add_rule();
+ rule->add_feature(0); // feature1
+ rule->set_weight(2.0);
+
+ rule = model_.add_rule();
+ rule->add_feature(0); // feature1
+ rule->add_feature(1); // feature2
+ rule->set_weight(3.0);
+
+ model_.add_page_term(3); // token one
+ model_.add_page_term(4); // token two
+
+ // These will be murmur3 hashes, but for this test it's not necessary
+ // that the hashes correspond to actual words.
+ model_.add_page_word(1000U);
+ model_.add_page_word(2000U);
+ model_.add_page_word(3000U);
+
+ model_.set_max_words_per_term(2);
+ model_.set_murmur_hash_seed(12345U);
+ model_.set_max_shingles_per_page(10);
+ model_.set_shingle_size(3);
+ }
+
+ ClientSideModel model_;
+};
+
+TEST_F(PhishingScorerTest, HasValidModel) {
+ std::unique_ptr<Scorer> scorer;
+ scorer.reset(Scorer::Create(model_.SerializeAsString()));
+ EXPECT_TRUE(scorer.get() != NULL);
+
+ // Invalid model string.
+ scorer.reset(Scorer::Create("bogus string"));
+ EXPECT_FALSE(scorer.get());
+
+ // Mode is missing a required field.
+ model_.clear_max_words_per_term();
+ scorer.reset(Scorer::Create(model_.SerializePartialAsString()));
+ EXPECT_FALSE(scorer.get());
+}
+
+TEST_F(PhishingScorerTest, PageTerms) {
+ std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+ ASSERT_TRUE(scorer.get());
+
+ // Use std::vector instead of std::unordered_set for comparison.
+ // On Android, EXPECT_THAT(..., ContainerEq(...)) doesn't support
+ // std::hash_set, but std::vector works fine.
+ std::vector<std::string> expected_page_terms;
+ expected_page_terms.push_back("token one");
+ expected_page_terms.push_back("token two");
+ std::sort(expected_page_terms.begin(), expected_page_terms.end());
+
+ std::unordered_set<std::string> page_terms = scorer->page_terms();
+ std::vector<std::string> page_terms_v(page_terms.begin(), page_terms.end());
+ std::sort(page_terms_v.begin(), page_terms_v.end());
+
+ EXPECT_THAT(page_terms_v, ::testing::ContainerEq(expected_page_terms));
+}
+
+TEST_F(PhishingScorerTest, PageWords) {
+ std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+ ASSERT_TRUE(scorer.get());
+ std::vector<uint32_t> expected_page_words;
+ expected_page_words.push_back(1000U);
+ expected_page_words.push_back(2000U);
+ expected_page_words.push_back(3000U);
+ std::sort(expected_page_words.begin(), expected_page_words.end());
+
+ std::unordered_set<uint32_t> page_words = scorer->page_words();
+ std::vector<uint32_t> page_words_v(page_words.begin(), page_words.end());
+ std::sort(page_words_v.begin(), page_words_v.end());
+
+ EXPECT_THAT(page_words_v, ::testing::ContainerEq(expected_page_words));
+
+ EXPECT_EQ(2U, scorer->max_words_per_term());
+ EXPECT_EQ(12345U, scorer->murmurhash3_seed());
+ EXPECT_EQ(10U, scorer->max_shingles_per_page());
+ EXPECT_EQ(3U, scorer->shingle_size());
+}
+
+TEST_F(PhishingScorerTest, ComputeScore) {
+ std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+ ASSERT_TRUE(scorer.get());
+
+ // An empty feature map should match the empty rule.
+ FeatureMap features;
+ // The expected logodds is 0.5 (empty rule) => p = exp(0.5) / (exp(0.5) + 1)
+ // => 0.62245933120185459
+ EXPECT_DOUBLE_EQ(0.62245933120185459, scorer->ComputeScore(features));
+ // Same if the feature does not match any rule.
+ EXPECT_TRUE(features.AddBooleanFeature("not existing feature"));
+ EXPECT_DOUBLE_EQ(0.62245933120185459, scorer->ComputeScore(features));
+
+ // Feature 1 matches which means that the logodds will be:
+ // 0.5 (empty rule) + 2.0 (rule weight) * 0.15 (feature weight) = 0.8
+ // => p = 0.6899744811276125
+ EXPECT_TRUE(features.AddRealFeature("feature1", 0.15));
+ EXPECT_DOUBLE_EQ(0.6899744811276125, scorer->ComputeScore(features));
+
+ // Now, both feature 1 and feature 2 match. Expected logodds:
+ // 0.5 (empty rule) + 2.0 (rule weight) * 0.15 (feature weight) +
+ // 3.0 (rule weight) * 0.15 (feature1 weight) * 1.0 (feature2) weight = 9.8
+ // => p = 0.99999627336071584
+ EXPECT_TRUE(features.AddBooleanFeature("feature2"));
+ EXPECT_DOUBLE_EQ(0.77729986117469119, scorer->ComputeScore(features));
+}
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/test_utils.cc b/chromium/chrome/renderer/safe_browsing/test_utils.cc
new file mode 100644
index 00000000000..b3d8f9b5074
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/test_utils.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/safe_browsing/test_utils.h"
+
+#include <map>
+#include <string>
+
+#include "chrome/renderer/safe_browsing/features.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace safe_browsing {
+
+void ExpectFeatureMapsAreEqual(const FeatureMap& first,
+ const FeatureMap& second) {
+ std::map<std::string, double> sorted_first(first.features().begin(),
+ first.features().end());
+ std::map<std::string, double> sorted_second(second.features().begin(),
+ second.features().end());
+ EXPECT_THAT(sorted_first, testing::ContainerEq(sorted_second));
+}
+
+} // namespace safe_browsing
diff --git a/chromium/chrome/renderer/safe_browsing/test_utils.h b/chromium/chrome/renderer/safe_browsing/test_utils.h
new file mode 100644
index 00000000000..cbe2061c161
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/test_utils.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SAFE_BROWSING_TEST_UTILS_H_
+#define CHROME_RENDERER_SAFE_BROWSING_TEST_UTILS_H_
+
+namespace safe_browsing {
+class FeatureMap;
+
+// Compares two FeatureMap objects using gMock. Always use this instead of
+// operator== or ContainerEq, since hash_map's equality operator may return
+// false if the elements were inserted in different orders.
+void ExpectFeatureMapsAreEqual(const FeatureMap& first,
+ const FeatureMap& second);
+
+} // namespace safe_browsing
+
+#endif // CHROME_RENDERER_SAFE_BROWSING_TEST_UTILS_H_
diff --git a/chromium/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc b/chromium/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
new file mode 100644
index 00000000000..f8a0ae5b140
--- /dev/null
+++ b/chromium/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
@@ -0,0 +1,461 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/safe_browsing/renderer/threat_dom_details.h"
+
+#include <memory>
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/safe_browsing/common/safe_browsing.mojom.h"
+#include "components/safe_browsing/features.h"
+#include "components/variations/variations_associated_data.h"
+#include "content/public/renderer/render_view.h"
+#include "net/base/escape.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/platform/web_runtime_features.h"
+#include "ui/native_theme/native_theme_features.h"
+
+namespace {
+
+std::unique_ptr<base::test::ScopedFeatureList> SetupTagAndAttributeFeature() {
+ std::map<std::string, std::string> feature_params;
+ feature_params[std::string(safe_browsing::kTagAndAttributeParamName)] =
+ "div,foo,div,baz,div,attr2,div,attr3,div,longattr4,div,attr5,div,attr6";
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_list(
+ new base::test::ScopedFeatureList);
+ scoped_list->InitWithFeaturesAndParameters(
+ {{safe_browsing::kThreatDomDetailsTagAndAttributeFeature,
+ feature_params}},
+ {});
+ return scoped_list;
+}
+
+} // namespace
+
+using ThreatDOMDetailsTest = ChromeRenderViewTest;
+
+using testing::ElementsAre;
+
+TEST_F(ThreatDOMDetailsTest, Everything) {
+ blink::WebRuntimeFeatures::EnableOverlayScrollbars(
+ ui::IsOverlayScrollbarEnabled());
+ // Configure a field trial to collect divs with attribute foo.
+ std::unique_ptr<base::test::ScopedFeatureList> feature_list =
+ SetupTagAndAttributeFeature();
+ std::unique_ptr<safe_browsing::ThreatDOMDetails> details(
+ safe_browsing::ThreatDOMDetails::Create(view_->GetMainRenderFrame(),
+ registry_.get()));
+ // Lower kMaxNodes and kMaxAttributes for the test. Loading 500 subframes in a
+ // debug build takes a while.
+ safe_browsing::ThreatDOMDetails::kMaxNodes = 50;
+ safe_browsing::ThreatDOMDetails::kMaxAttributes = 5;
+
+ const char kUrlPrefix[] = "data:text/html;charset=utf-8,";
+ {
+ // A page with an internal script
+ std::string html = "<html><head><script></script></head></html>";
+ LoadHTML(html.c_str());
+ base::HistogramTester histograms;
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ ASSERT_EQ(1u, params.size());
+ auto* param = params[0].get();
+ EXPECT_EQ(GURL(kUrlPrefix + net::EscapeQueryParamValue(html, false)),
+ param->url);
+ EXPECT_EQ(0, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ }
+
+ {
+ // A page with 2 external scripts.
+ // Note: This part of the test causes 2 leaks: LEAK: 5 WebCoreNode
+ // LEAK: 2 CachedResource.
+ GURL script1_url("data:text/javascript;charset=utf-8,var a=1;");
+ GURL script2_url("data:text/javascript;charset=utf-8,var b=2;");
+ std::string html = "<html><head><script src=\"" + script1_url.spec() +
+ "\"></script><script src=\"" + script2_url.spec() +
+ "\"></script></head></html>";
+ GURL url(kUrlPrefix + net::EscapeQueryParamValue(html, false));
+
+ LoadHTML(html.c_str());
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ ASSERT_EQ(3u, params.size());
+ auto* param = params[0].get();
+ EXPECT_EQ(script1_url, param->url);
+ EXPECT_EQ("SCRIPT", param->tag_name);
+ EXPECT_EQ(1, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+
+ param = params[1].get();
+ EXPECT_EQ(script2_url, param->url);
+ EXPECT_EQ("SCRIPT", param->tag_name);
+ EXPECT_EQ(2, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+
+ param = params[2].get();
+ EXPECT_EQ(url, param->url);
+ EXPECT_EQ(0, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ }
+
+ {
+ // A page with some divs containing an iframe which itself contains an
+ // iframe. Tag "img foo" exists to ensure we honour both the tag name and
+ // the attribute name when deciding which elements to collect.
+ // html
+ // \ div foo
+ // \ img foo, div bar
+ // \ div baz, iframe1
+ // \ iframe2
+ // Since ThreatDOMDetails is a RenderFrameObserver, it will only
+ // extract resources from the frame it assigned to (in this case,
+ // the main frame). Extracting resources from all frames within a
+ // page is covered in SafeBrowsingBlockingPageBrowserTest.
+ // In this example, ExtractResources() will still touch iframe1
+ // since it is the direct child of the main frame, but it would not
+ // go inside of iframe1.
+ // We configure the test to collect divs with attribute foo and baz, but not
+ // divs with attribute bar. So div foo will be collected and contain iframe1
+ // and div baz as children.
+ std::string iframe2_html = "<html><body>iframe2</body></html>";
+ GURL iframe2_url(kUrlPrefix + iframe2_html);
+ std::string iframe1_html = "<iframe src=\"" +
+ net::EscapeForHTML(iframe2_url.spec()) +
+ "\"></iframe>";
+ GURL iframe1_url(kUrlPrefix + iframe1_html);
+ std::string html =
+ "<html><head><div foo=1 foo2=2><img foo=1><div bar=1><div baz=1></div>"
+ "<iframe src=\"" +
+ net::EscapeForHTML(iframe1_url.spec()) +
+ "\"></iframe></div></div></head></html>";
+ GURL url(kUrlPrefix + net::EscapeQueryParamValue(html, false));
+
+ LoadHTML(html.c_str());
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ ASSERT_EQ(4u, params.size());
+
+ auto* param = params[0].get();
+ EXPECT_TRUE(param->url.is_empty());
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("DIV", param->tag_name);
+ // The children field contains URLs, but this mapping is not currently
+ // maintained among the interior nodes. The summary node is the parent of
+ // all elements in the frame.
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(1, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_THAT(param->child_node_ids, ElementsAre(2, 3));
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("foo", param->attributes[0]->name);
+ EXPECT_EQ("1", param->attributes[0]->value);
+
+ param = params[1].get();
+ EXPECT_TRUE(param->url.is_empty());
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("DIV", param->tag_name);
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(2, param->node_id);
+ EXPECT_EQ(1, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("baz", param->attributes[0]->name);
+ EXPECT_EQ("1", param->attributes[0]->value);
+
+ param = params[2].get();
+ EXPECT_EQ(iframe1_url, param->url);
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("IFRAME", param->tag_name);
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(3, param->node_id);
+ EXPECT_EQ(1, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_TRUE(param->attributes.empty());
+
+ param = params[3].get();
+ EXPECT_EQ(url, param->url);
+ EXPECT_EQ(GURL(), param->parent);
+ EXPECT_THAT(param->children, ElementsAre(iframe1_url));
+ EXPECT_EQ(0, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ }
+
+ {
+ // Test >50 subframes.
+ std::string html;
+ for (int i = 0; i < 55; ++i) {
+ // The iframe contents is just a number.
+ GURL iframe_url(base::StringPrintf("%s%d", kUrlPrefix, i));
+ html += "<iframe src=\"" + net::EscapeForHTML(iframe_url.spec()) +
+ "\"></iframe>";
+ }
+ GURL url(kUrlPrefix + html);
+
+ LoadHTML(html.c_str());
+ base::HistogramTester histograms;
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ ASSERT_EQ(51u, params.size());
+
+ // The element nodes should all have node IDs.
+ for (size_t i = 0; i < params.size() - 1; ++i) {
+ auto& param = *params[i];
+ const int expected_id = i + 1;
+ EXPECT_EQ(expected_id, param.node_id);
+ EXPECT_EQ(0, param.parent_node_id);
+ EXPECT_TRUE(param.child_node_ids.empty());
+ }
+ }
+
+ {
+ // A page with >50 scripts, to verify kMaxNodes.
+ std::string html;
+ for (int i = 0; i < 55; ++i) {
+ // The iframe contents is just a number.
+ GURL script_url(base::StringPrintf("%s%d", kUrlPrefix, i));
+ html += "<script src=\"" + net::EscapeForHTML(script_url.spec()) +
+ "\"></script>";
+ }
+ GURL url(kUrlPrefix + html);
+
+ LoadHTML(html.c_str());
+ base::HistogramTester histograms;
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ ASSERT_EQ(51u, params.size());
+
+ // The element nodes should all have node IDs.
+ for (size_t i = 0; i < params.size() - 1; ++i) {
+ auto& param = *params[i];
+ const int expected_id = i + 1;
+ EXPECT_EQ(expected_id, param.node_id);
+ EXPECT_EQ(0, param.parent_node_id);
+ EXPECT_TRUE(param.child_node_ids.empty());
+ }
+ }
+
+ {
+ // Check the limit on the number of attributes collected and their lengths.
+ safe_browsing::ThreatDOMDetails::kMaxAttributeStringLength = 5;
+ std::string html =
+ "<html><head><div foo=1 attr2=2 attr3=3 longattr4=4 attr5=longvalue5 "
+ "attr6=6></div></head></html>";
+ LoadHTML(html.c_str());
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ GURL url = GURL(kUrlPrefix + net::EscapeQueryParamValue(html, false));
+ ASSERT_EQ(2u, params.size());
+ auto* param = params[0].get();
+ EXPECT_TRUE(param->url.is_empty());
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("DIV", param->tag_name);
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(1, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_EQ(5u, param->attributes.size());
+ EXPECT_EQ("foo", param->attributes[0]->name);
+ EXPECT_EQ("1", param->attributes[0]->value);
+ EXPECT_EQ("attr2", param->attributes[1]->name);
+ EXPECT_EQ("2", param->attributes[1]->value);
+ EXPECT_EQ("attr3", param->attributes[2]->name);
+ EXPECT_EQ("3", param->attributes[2]->value);
+ EXPECT_EQ("longattr4", param->attributes[3]->name);
+ EXPECT_EQ("4", param->attributes[3]->value);
+ EXPECT_EQ("attr5", param->attributes[4]->name);
+ EXPECT_EQ("lo...", param->attributes[4]->value);
+ param = params[1].get();
+ EXPECT_EQ(url, param->url);
+ EXPECT_EQ(0, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ }
+}
+
+TEST_F(ThreatDOMDetailsTest, DefaultTagAndAttributesList) {
+ // Verify that the default tag and attribute list is initialized and used
+ // when the Finch feature (ThreatDomDetailsTagAttributes) is disabled.
+ blink::WebRuntimeFeatures::EnableOverlayScrollbars(
+ ui::IsOverlayScrollbarEnabled());
+ std::unique_ptr<base::test::ScopedFeatureList> feature_list(
+ new base::test::ScopedFeatureList);
+ feature_list->InitAndDisableFeature(
+ safe_browsing::kThreatDomDetailsTagAndAttributeFeature);
+ std::unique_ptr<safe_browsing::ThreatDOMDetails> details(
+ safe_browsing::ThreatDOMDetails::Create(view_->GetMainRenderFrame(),
+ registry_.get()));
+ const char kUrlPrefix[] = "data:text/html;charset=utf-8,";
+
+ // A page with some divs containing an iframe which itself contains an
+ // iframe. Tag "img foo" exists to ensure we honour both the tag name and
+ // the attribute name when deciding which elements to collect.
+ // html
+ // \ div[data-google-query-id=foo]
+ // \ div[id=bar]
+ // \ iframe[id=baz]
+ // \ iframe2
+ // Since ThreatDOMDetails is a RenderFrameObserver, it will only
+ // extract resources from the frame it assigned to (in this case,
+ // the main frame). Extracting resources from all frames within a
+ // page is covered in SafeBrowsingBlockingPageBrowserTest.
+ // In this example, ExtractResources() will still touch iframe[baz]
+ // since it is the direct child of the main frame, but it would not
+ // go inside of the iframe.
+ std::string iframe2_html = "<html><body>iframe2</body></html>";
+ GURL iframe2_url(kUrlPrefix + iframe2_html);
+ std::string html =
+ "<html><head><div data-google-query-id=foo><div id=bar>"
+ "<iframe id=baz><iframe src=\"" +
+ net::EscapeForHTML(iframe2_url.spec()) +
+ "\"></iframe></iframe></div></div></head></html>";
+ GURL url(kUrlPrefix + net::EscapeQueryParamValue(html, false));
+
+ LoadHTML(html.c_str());
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+ details->ExtractResources(&params);
+ ASSERT_EQ(4u, params.size());
+
+ auto* param = params[0].get();
+ EXPECT_TRUE(param->url.is_empty());
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("DIV", param->tag_name);
+ // The children field contains URLs, but this mapping is not currently
+ // maintained among the interior nodes. The summary node (last in the list) is
+ // the parent of all elements in the frame.
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(1, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_THAT(param->child_node_ids, ElementsAre(2));
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("data-google-query-id", param->attributes[0]->name);
+ EXPECT_EQ("foo", param->attributes[0]->value);
+
+ param = params[1].get();
+ EXPECT_EQ("id", param->attributes[0]->name);
+ EXPECT_EQ("bar", param->attributes[0]->value);
+ EXPECT_TRUE(param->url.is_empty());
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("DIV", param->tag_name);
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(2, param->node_id);
+ EXPECT_EQ(1, param->parent_node_id);
+ EXPECT_THAT(param->child_node_ids, ElementsAre(3));
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("id", param->attributes[0]->name);
+ EXPECT_EQ("bar", param->attributes[0]->value);
+
+ param = params[2].get();
+ EXPECT_TRUE(param->url.is_empty());
+ EXPECT_EQ(url, param->parent);
+ EXPECT_EQ("IFRAME", param->tag_name);
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(3, param->node_id);
+ EXPECT_EQ(2, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("id", param->attributes[0]->name);
+ EXPECT_EQ("baz", param->attributes[0]->value);
+
+ param = params[3].get();
+ EXPECT_EQ(url, param->url);
+ EXPECT_EQ(GURL(), param->parent);
+ EXPECT_TRUE(param->children.empty());
+ EXPECT_EQ(0, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+}
+
+TEST_F(ThreatDOMDetailsTest, CheckTagAndAttributeListIsSorted) {
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_list(
+ new base::test::ScopedFeatureList);
+ scoped_list->InitAndEnableFeature(
+ safe_browsing::kCaptureInlineJavascriptForGoogleAds);
+
+ std::unique_ptr<safe_browsing::ThreatDOMDetails> details(
+ safe_browsing::ThreatDOMDetails::Create(view_->GetMainRenderFrame(),
+ registry_.get()));
+ std::vector<safe_browsing::TagAndAttributesItem> tag_and_attr_list =
+ details->GetTagAndAttributesListForTest();
+ bool is_sorted;
+ std::vector<std::string> tag_names;
+ for (auto item : tag_and_attr_list) {
+ tag_names.push_back(item.tag_name);
+ // Check that list of attributes is sorted.
+ is_sorted = std::is_sorted(item.attributes.begin(), item.attributes.end());
+ EXPECT_TRUE(is_sorted);
+ }
+ // Check that the tags are sorted.
+ is_sorted = std::is_sorted(tag_names.begin(), tag_names.end());
+ EXPECT_TRUE(is_sorted);
+}
+
+TEST_F(ThreatDOMDetailsTest, CaptureInnerHtmlContent) {
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_list(
+ new base::test::ScopedFeatureList);
+ scoped_list->InitAndEnableFeature(
+ safe_browsing::kCaptureInlineJavascriptForGoogleAds);
+ std::unique_ptr<safe_browsing::ThreatDOMDetails> details(
+ safe_browsing::ThreatDOMDetails::Create(view_->GetMainRenderFrame(),
+ registry_.get()));
+
+ const char kUrlPrefix[] = "data:text/html;charset=utf-8,";
+ {
+ // A page with a html element without an onclick element, html element with
+ // an onclick element, an internal script. Html elements without onclick
+ // elements should not be recorded in ThreatDomDetails.
+ std::string html =
+ "<html><head><a></a><a onclick=\"var y = 2;\"></a><img onclick=\"var z "
+ "= 3;\"></img><script>var x = 1;</script></head></html>";
+ LoadHTML(html.c_str());
+ base::HistogramTester histograms;
+ std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
+
+ details->ExtractResources(&params);
+ ASSERT_EQ(4u, params.size());
+ auto* param = params[0].get();
+ EXPECT_EQ(GURL(), param->url);
+ EXPECT_EQ("A", param->tag_name);
+ EXPECT_EQ(1, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("onclick", param->attributes[0]->name);
+ EXPECT_EQ("var y = 2;", param->attributes[0]->value);
+
+ param = params[1].get();
+ EXPECT_EQ(GURL(), param->url);
+ EXPECT_EQ("IMG", param->tag_name);
+ EXPECT_EQ(2, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_EQ(1u, param->attributes.size());
+ EXPECT_EQ("onclick", param->attributes[0]->name);
+ EXPECT_EQ("var z = 3;", param->attributes[0]->value);
+
+ param = params[2].get();
+ EXPECT_EQ(GURL(), param->url);
+ EXPECT_EQ("SCRIPT", param->tag_name);
+ EXPECT_EQ(3, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ EXPECT_EQ("var x = 1;", param->inner_html);
+
+ param = params[3].get();
+ EXPECT_EQ(GURL(kUrlPrefix + net::EscapeQueryParamValue(html, false)),
+ param->url);
+ EXPECT_EQ(0, param->node_id);
+ EXPECT_EQ(0, param->parent_node_id);
+ EXPECT_TRUE(param->child_node_ids.empty());
+ }
+}
diff --git a/chromium/chrome/renderer/sandbox_status_extension_android.cc b/chromium/chrome/renderer/sandbox_status_extension_android.cc
new file mode 100644
index 00000000000..8076cd912b1
--- /dev/null
+++ b/chromium/chrome/renderer/sandbox_status_extension_android.cc
@@ -0,0 +1,165 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/sandbox_status_extension_android.h"
+
+#include <utility>
+
+#include "base/android/build_info.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/task/post_task.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/renderer/chrome_object_extensions_utils.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/seccomp_sandbox_status_android.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "gin/arguments.h"
+#include "gin/function_template.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "v8/include/v8.h"
+
+SandboxStatusExtension::SandboxStatusExtension(content::RenderFrame* frame)
+ : content::RenderFrameObserver(frame) {
+ // Don't do anything else for subframes.
+ if (!frame->IsMainFrame())
+ return;
+ frame->GetAssociatedInterfaceRegistry()->AddInterface(
+ base::Bind(&SandboxStatusExtension::OnSandboxStatusExtensionRequest,
+ base::RetainedRef(this)));
+}
+
+SandboxStatusExtension::~SandboxStatusExtension() {}
+
+// static
+void SandboxStatusExtension::Create(content::RenderFrame* frame) {
+ auto* extension = new SandboxStatusExtension(frame);
+ extension->AddRef(); // Balanced in OnDestruct().
+}
+
+void SandboxStatusExtension::OnDestruct() {
+ // This object is ref-counted, since a callback could still be in-flight.
+ Release();
+}
+
+void SandboxStatusExtension::DidClearWindowObject() {
+ Install();
+}
+
+void SandboxStatusExtension::AddSandboxStatusExtension() {
+ should_install_ = true;
+}
+
+void SandboxStatusExtension::OnSandboxStatusExtensionRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::SandboxStatusExtension>
+ receiver) {
+ receiver_.Bind(std::move(receiver));
+}
+
+void SandboxStatusExtension::Install() {
+ if (!should_install_)
+ return;
+
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ render_frame()->GetWebFrame()->MainWorldScriptContext();
+ if (context.IsEmpty())
+ return;
+
+ v8::Context::Scope context_scope(context);
+
+ v8::Local<v8::Object> chrome =
+ content::GetOrCreateChromeObject(isolate, context);
+ v8::Local<v8::Function> function;
+ bool success =
+ gin::CreateFunctionTemplate(
+ isolate, base::Bind(&SandboxStatusExtension::GetSandboxStatus, this))
+ ->GetFunction(context)
+ .ToLocal(&function);
+ if (success) {
+ success = chrome
+ ->Set(context,
+ gin::StringToSymbol(isolate, "getAndroidSandboxStatus"),
+ function)
+ .IsJust();
+ }
+ DCHECK(success);
+}
+
+void SandboxStatusExtension::GetSandboxStatus(gin::Arguments* args) {
+ if (!render_frame())
+ return;
+
+ if (render_frame()->GetWebFrame()->GetSecurityOrigin().Host() !=
+ chrome::kChromeUISandboxHost) {
+ args->ThrowTypeError("Not allowed on this origin");
+ return;
+ }
+
+ v8::HandleScope handle_scope(args->isolate());
+
+ v8::Local<v8::Function> callback;
+ if (!args->GetNext(&callback)) {
+ args->ThrowError();
+ return;
+ }
+
+ auto global_callback =
+ std::make_unique<v8::Global<v8::Function>>(args->isolate(), callback);
+
+ base::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::ThreadPool(), base::MayBlock()},
+ base::Bind(&SandboxStatusExtension::ReadSandboxStatus, this),
+ base::Bind(&SandboxStatusExtension::RunCallback, this,
+ base::Passed(&global_callback)));
+}
+
+std::unique_ptr<base::Value> SandboxStatusExtension::ReadSandboxStatus() {
+ std::string secontext;
+ base::FilePath path(FILE_PATH_LITERAL("/proc/self/attr/current"));
+ base::ReadFileToString(path, &secontext);
+
+ std::string proc_status;
+ path = base::FilePath(FILE_PATH_LITERAL("/proc/self/status"));
+ base::ReadFileToString(path, &proc_status);
+
+ auto status = std::make_unique<base::DictionaryValue>();
+ status->SetInteger("uid", getuid());
+ status->SetInteger("pid", getpid());
+ status->SetString("secontext", secontext);
+ status->SetInteger("seccompStatus",
+ static_cast<int>(content::GetSeccompSandboxStatus()));
+ status->SetString("procStatus", proc_status);
+ status->SetString(
+ "androidBuildId",
+ base::android::BuildInfo::GetInstance()->android_build_id());
+
+ return std::move(status);
+}
+
+void SandboxStatusExtension::RunCallback(
+ std::unique_ptr<v8::Global<v8::Function>> callback,
+ std::unique_ptr<base::Value> status) {
+ if (!render_frame())
+ return;
+
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ render_frame()->GetWebFrame()->MainWorldScriptContext();
+ v8::Context::Scope context_scope(context);
+ v8::Local<v8::Function> callback_local =
+ v8::Local<v8::Function>::New(isolate, *callback);
+
+ v8::Local<v8::Value> argv[] = {
+ content::V8ValueConverter::Create()->ToV8Value(status.get(), context)};
+ render_frame()->GetWebFrame()->CallFunctionEvenIfScriptDisabled(
+ callback_local, v8::Object::New(isolate), 1, argv);
+}
diff --git a/chromium/chrome/renderer/sandbox_status_extension_android.h b/chromium/chrome/renderer/sandbox_status_extension_android.h
new file mode 100644
index 00000000000..0b352ecdbfc
--- /dev/null
+++ b/chromium/chrome/renderer/sandbox_status_extension_android.h
@@ -0,0 +1,78 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SANDBOX_STATUS_EXTENSION_ANDROID_H_
+#define CHROME_RENDERER_SANDBOX_STATUS_EXTENSION_ANDROID_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "chrome/common/sandbox_status_extension_android.mojom.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "v8/include/v8.h"
+
+namespace gin {
+class Arguments;
+}
+
+// On Android, this class adds a function chrome.getAndroidSandboxStatus()
+// to the chrome://sandbox/ WebUI page. This is done only after the browser
+// SandboxInternalsUI sends an IPC mesage blessing this RenderFrame.
+class SandboxStatusExtension
+ : public base::RefCountedThreadSafe<SandboxStatusExtension>,
+ public content::RenderFrameObserver,
+ public chrome::mojom::SandboxStatusExtension {
+ public:
+ // Creates a new SandboxStatusExtension for the |frame|.
+ static void Create(content::RenderFrame* frame);
+
+ // content::RenderFrameObserver:
+ void OnDestruct() override;
+ void DidClearWindowObject() override;
+
+ protected:
+ friend class RefCountedThreadSafe<SandboxStatusExtension>;
+ ~SandboxStatusExtension() override;
+
+ private:
+ explicit SandboxStatusExtension(content::RenderFrame* frame);
+
+ // chrome::mojom::SandboxStatusExtension
+ void AddSandboxStatusExtension() override;
+
+ void OnSandboxStatusExtensionRequest(
+ mojo::PendingAssociatedReceiver<chrome::mojom::SandboxStatusExtension>
+ receiver);
+
+ // Installs the JavaScript function into the scripting context, if
+ // should_install_ is true.
+ void Install();
+
+ // Native implementation of chrome.getAndroidSandboxStatus.
+ void GetSandboxStatus(gin::Arguments* args);
+
+ // Called on the blocking pool, this gets the sandbox status of the current
+ // renderer process and returns a status object as a base::Value.
+ std::unique_ptr<base::Value> ReadSandboxStatus();
+
+ // Runs the callback argument provided to GetSandboxStatus() with the status
+ // object computed by ReadSandboxStatus(). This is called back on the thread
+ // on which GetSandboxStatus() was called originally.
+ void RunCallback(std::unique_ptr<v8::Global<v8::Function>> callback,
+ std::unique_ptr<base::Value> status);
+
+ // Set to true by AddSandboxStatusExtension().
+ bool should_install_ = false;
+
+ mojo::AssociatedReceiver<chrome::mojom::SandboxStatusExtension> receiver_{
+ this};
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxStatusExtension);
+};
+
+#endif // CHROME_RENDERER_SANDBOX_STATUS_EXTENSION_ANDROID_H_
diff --git a/chromium/chrome/renderer/searchbox/DEPS b/chromium/chrome/renderer/searchbox/DEPS
new file mode 100644
index 00000000000..03093492dc7
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+components/favicon_base",
+ "+components/ntp_tiles",
+]
diff --git a/chromium/chrome/renderer/searchbox/OWNERS b/chromium/chrome/renderer/searchbox/OWNERS
new file mode 100644
index 00000000000..fa6ab8f1fb5
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/OWNERS
@@ -0,0 +1,4 @@
+file://chrome/browser/search/OWNERS
+
+dcblack@chromium.org
+# COMPONENT: UI>Browser>NewTabPage
diff --git a/chromium/chrome/renderer/searchbox/search_bouncer.cc b/chromium/chrome/renderer/searchbox/search_bouncer.cc
new file mode 100644
index 00000000000..126a99f82f8
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/search_bouncer.cc
@@ -0,0 +1,56 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/searchbox/search_bouncer.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+
+namespace {
+base::LazyInstance<SearchBouncer>::Leaky g_search_bouncer =
+ LAZY_INSTANCE_INITIALIZER;
+
+GURL RemoveQueryAndRef(const GURL& url) {
+ url::Replacements<char> replacements;
+ replacements.ClearQuery();
+ replacements.ClearRef();
+ return url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+SearchBouncer::SearchBouncer() = default;
+
+SearchBouncer::~SearchBouncer() = default;
+
+// static
+SearchBouncer* SearchBouncer::GetInstance() {
+ return g_search_bouncer.Pointer();
+}
+
+void SearchBouncer::RegisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) {
+ // Note: Unretained is safe here because this class is a leaky LazyInstance.
+ // For the same reason, UnregisterMojoInterfaces isn't required.
+ associated_interfaces->AddInterface(base::Bind(
+ &SearchBouncer::BindSearchBouncerReceiver, base::Unretained(this)));
+}
+
+bool SearchBouncer::IsNewTabPage(const GURL& url) const {
+ GURL url_no_query_or_ref = RemoveQueryAndRef(url);
+ return url_no_query_or_ref.is_valid() &&
+ url_no_query_or_ref == new_tab_page_url_;
+}
+
+void SearchBouncer::SetNewTabPageURL(const GURL& new_tab_page_url) {
+ new_tab_page_url_ = new_tab_page_url;
+}
+
+void SearchBouncer::BindSearchBouncerReceiver(
+ mojo::PendingAssociatedReceiver<chrome::mojom::SearchBouncer> receiver) {
+ search_bouncer_receiver_.Bind(std::move(receiver));
+}
diff --git a/chromium/chrome/renderer/searchbox/search_bouncer.h b/chromium/chrome/renderer/searchbox/search_bouncer.h
new file mode 100644
index 00000000000..929a85fb38f
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/search_bouncer.h
@@ -0,0 +1,48 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SEARCHBOX_SEARCH_BOUNCER_H_
+#define CHROME_RENDERER_SEARCHBOX_SEARCH_BOUNCER_H_
+
+#include "base/macros.h"
+#include "chrome/common/search.mojom.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "url/gurl.h"
+
+// SearchBouncer tracks a set of URLs which should be transferred back to the
+// browser process for potential reassignment to an Instant renderer process.
+class SearchBouncer : public content::RenderThreadObserver,
+ public chrome::mojom::SearchBouncer {
+ public:
+ SearchBouncer();
+ ~SearchBouncer() override;
+
+ static SearchBouncer* GetInstance();
+
+ // RenderThreadObserver:
+ void RegisterMojoInterfaces(
+ blink::AssociatedInterfaceRegistry* associated_interfaces) override;
+
+ // Returns whether |url| is a valid Instant new tab page URL.
+ bool IsNewTabPage(const GURL& url) const;
+
+ // chrome::mojom::SearchBouncer:
+ void SetNewTabPageURL(const GURL& new_tab_page_url) override;
+
+ private:
+ void BindSearchBouncerReceiver(
+ mojo::PendingAssociatedReceiver<chrome::mojom::SearchBouncer> receiver);
+
+ GURL new_tab_page_url_;
+
+ mojo::AssociatedReceiver<chrome::mojom::SearchBouncer>
+ search_bouncer_receiver_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(SearchBouncer);
+};
+
+#endif // CHROME_RENDERER_SEARCHBOX_SEARCH_BOUNCER_H_
diff --git a/chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc b/chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc
new file mode 100644
index 00000000000..0d18a93e2a7
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/searchbox/search_bouncer.h"
+
+#include <vector>
+
+#include "chrome/common/url_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+class SearchBouncerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ bouncer_.SetNewTabPageURL(GURL("http://example.com/newtab"));
+ }
+
+ SearchBouncer bouncer_;
+};
+
+TEST_F(SearchBouncerTest, IsNewTabPage) {
+ EXPECT_FALSE(bouncer_.IsNewTabPage(GURL("http://example.com/foo")));
+ EXPECT_TRUE(bouncer_.IsNewTabPage(GURL("http://example.com/newtab")));
+ EXPECT_TRUE(bouncer_.IsNewTabPage(GURL("http://example.com/newtab?q=foo")));
+ EXPECT_TRUE(bouncer_.IsNewTabPage(GURL("http://example.com/newtab#q=foo")));
+ EXPECT_TRUE(
+ bouncer_.IsNewTabPage(GURL("http://example.com/newtab#q=foo?q=foo")));
+}
diff --git a/chromium/chrome/renderer/searchbox/searchbox.cc b/chromium/chrome/renderer/searchbox/searchbox.cc
new file mode 100644
index 00000000000..ebb238442cf
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/searchbox.cc
@@ -0,0 +1,596 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/searchbox/searchbox.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "chrome/common/search.mojom.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/renderer/searchbox/searchbox_extension.h"
+#include "components/favicon_base/favicon_types.h"
+#include "components/favicon_base/favicon_url_parser.h"
+#include "components/url_formatter/url_fixer.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_performance.h"
+
+namespace {
+
+// The size of the InstantMostVisitedItem cache.
+const size_t kMaxInstantMostVisitedItemCacheSize = 100;
+
+// Returns true if items stored in |old_item_id_pairs| and |new_items| are
+// equal.
+bool AreMostVisitedItemsEqual(
+ const std::vector<InstantMostVisitedItemIDPair>& old_item_id_pairs,
+ const std::vector<InstantMostVisitedItem>& new_items) {
+ if (old_item_id_pairs.size() != new_items.size())
+ return false;
+
+ for (size_t i = 0; i < new_items.size(); ++i) {
+ if (new_items[i].url != old_item_id_pairs[i].second.url ||
+ new_items[i].title != old_item_id_pairs[i].second.title ||
+ new_items[i].source != old_item_id_pairs[i].second.source) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Helper for SearchBox::GenerateImageURLFromTransientURL().
+class SearchBoxIconURLHelper: public SearchBox::IconURLHelper {
+ public:
+ explicit SearchBoxIconURLHelper(const SearchBox* search_box);
+ ~SearchBoxIconURLHelper() override;
+ int GetViewID() const override;
+ std::string GetURLStringFromRestrictedID(InstantRestrictedID rid) const
+ override;
+
+ private:
+ const SearchBox* search_box_;
+};
+
+SearchBoxIconURLHelper::SearchBoxIconURLHelper(const SearchBox* search_box)
+ : search_box_(search_box) {
+}
+
+SearchBoxIconURLHelper::~SearchBoxIconURLHelper() {
+}
+
+int SearchBoxIconURLHelper::GetViewID() const {
+ return search_box_->render_frame()->GetRenderView()->GetRoutingID();
+}
+
+std::string SearchBoxIconURLHelper::GetURLStringFromRestrictedID(
+ InstantRestrictedID rid) const {
+ InstantMostVisitedItem item;
+ if (!search_box_->GetMostVisitedItemWithID(rid, &item))
+ return std::string();
+
+ return item.url.spec();
+}
+
+} // namespace
+
+namespace internal { // for testing
+
+// Parses "<view_id>/<restricted_id>". If successful, assigns
+// |*view_id| := "<view_id>", |*rid| := "<restricted_id>", and returns true.
+bool ParseViewIdAndRestrictedId(const std::string& id_part,
+ int* view_id_out,
+ InstantRestrictedID* rid_out) {
+ DCHECK(view_id_out);
+ DCHECK(rid_out);
+ // Check that the path is of Most visited item ID form.
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ id_part, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (tokens.size() != 2)
+ return false;
+
+ int view_id;
+ InstantRestrictedID rid;
+ if (!base::StringToInt(tokens[0], &view_id) || view_id < 0 ||
+ !base::StringToInt(tokens[1], &rid) || rid < 0)
+ return false;
+
+ *view_id_out = view_id;
+ *rid_out = rid;
+ return true;
+}
+
+// Takes a favicon |url| that looks like:
+//
+// chrome-search://favicon/<view_id>/<restricted_id>
+// chrome-search://favicon/<parameters>/<view_id>/<restricted_id>
+//
+// If successful, assigns |*param_part| := "" or "<parameters>/" (note trailing
+// slash), |*view_id| := "<view_id>", |*rid| := "rid", and returns true.
+bool ParseIconRestrictedUrl(const GURL& url,
+ std::string* param_part,
+ int* view_id,
+ InstantRestrictedID* rid) {
+ DCHECK(param_part);
+ DCHECK(view_id);
+ DCHECK(rid);
+ // Strip leading slash.
+ std::string raw_path = url.path();
+ DCHECK_GT(raw_path.length(), (size_t) 0);
+ DCHECK_EQ(raw_path[0], '/');
+ raw_path = raw_path.substr(1);
+
+ // Get the starting index of the page URL.
+ chrome::ParsedFaviconPath parsed;
+ if (!chrome::ParseFaviconPath(
+ raw_path, chrome::FaviconUrlFormat::kFaviconLegacy, &parsed)) {
+ return false;
+ }
+ int path_index = parsed.path_index;
+
+ std::string id_part = raw_path.substr(path_index);
+ if (!ParseViewIdAndRestrictedId(id_part, view_id, rid))
+ return false;
+
+ *param_part = raw_path.substr(0, path_index);
+ return true;
+}
+
+void TranslateIconRestrictedUrl(const GURL& transient_url,
+ const SearchBox::IconURLHelper& helper,
+ GURL* url) {
+ std::string params;
+ int view_id = -1;
+ InstantRestrictedID rid = -1;
+
+ if (!internal::ParseIconRestrictedUrl(transient_url, &params, &view_id,
+ &rid) ||
+ view_id != helper.GetViewID()) {
+ *url = GURL(base::StringPrintf("chrome-search://%s/",
+ chrome::kChromeUIFaviconHost));
+ } else {
+ std::string item_url = helper.GetURLStringFromRestrictedID(rid);
+ *url = GURL(base::StringPrintf("chrome-search://%s/%s%s",
+ chrome::kChromeUIFaviconHost, params.c_str(),
+ item_url.c_str()));
+ }
+}
+
+std::string FixupAndValidateUrl(const std::string& url) {
+ GURL gurl = url_formatter::FixupURL(url, /*desired_tld=*/std::string());
+ if (!gurl.is_valid())
+ return std::string();
+
+ // Unless "http" was specified, replaces FixupURL's default "http" with
+ // "https".
+ if (url.find(std::string("http://")) == std::string::npos &&
+ gurl.SchemeIs(url::kHttpScheme)) {
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr(url::kHttpsScheme);
+ gurl = gurl.ReplaceComponents(replacements);
+ }
+
+ return gurl.spec();
+}
+
+} // namespace internal
+
+SearchBox::IconURLHelper::IconURLHelper() = default;
+
+SearchBox::IconURLHelper::~IconURLHelper() = default;
+
+SearchBox::SearchBox(content::RenderFrame* render_frame)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<SearchBox>(render_frame),
+ binding_(this),
+ can_run_js_in_renderframe_(false),
+ page_seq_no_(0),
+ is_focused_(false),
+ is_input_in_progress_(false),
+ is_key_capture_enabled_(false),
+ most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize),
+ has_received_most_visited_(false) {
+ // Connect to the embedded search interface in the browser.
+ mojo::AssociatedRemote<chrome::mojom::EmbeddedSearchConnector> connector;
+ render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&connector);
+ chrome::mojom::EmbeddedSearchClientAssociatedPtrInfo embedded_search_client;
+ binding_.Bind(mojo::MakeRequest(&embedded_search_client));
+ connector->Connect(mojo::MakeRequest(&embedded_search_service_),
+ std::move(embedded_search_client));
+}
+
+SearchBox::~SearchBox() = default;
+
+void SearchBox::LogEvent(NTPLoggingEventType event) {
+ base::Time navigation_start = base::Time::FromDoubleT(
+ render_frame()->GetWebFrame()->Performance().NavigationStart());
+ base::Time now = base::Time::Now();
+ base::TimeDelta delta = now - navigation_start;
+ embedded_search_service_->LogEvent(page_seq_no_, event, delta);
+}
+
+void SearchBox::LogSuggestionEventWithValue(
+ NTPSuggestionsLoggingEventType event,
+ int data) {
+ base::Time navigation_start = base::Time::FromDoubleT(
+ render_frame()->GetWebFrame()->Performance().NavigationStart());
+ base::Time now = base::Time::Now();
+ base::TimeDelta delta = now - navigation_start;
+ embedded_search_service_->LogSuggestionEventWithValue(page_seq_no_, event,
+ data, delta);
+}
+
+void SearchBox::LogMostVisitedImpression(
+ const ntp_tiles::NTPTileImpression& impression) {
+ embedded_search_service_->LogMostVisitedImpression(page_seq_no_, impression);
+}
+
+void SearchBox::LogMostVisitedNavigation(
+ const ntp_tiles::NTPTileImpression& impression) {
+ embedded_search_service_->LogMostVisitedNavigation(page_seq_no_, impression);
+}
+
+void SearchBox::DeleteMostVisitedItem(
+ InstantRestrictedID most_visited_item_id) {
+ GURL url = GetURLForMostVisitedItem(most_visited_item_id);
+ if (!url.is_valid())
+ return;
+ embedded_search_service_->DeleteMostVisitedItem(page_seq_no_, url);
+}
+
+void SearchBox::GenerateImageURLFromTransientURL(const GURL& transient_url,
+ GURL* url) const {
+ SearchBoxIconURLHelper helper(this);
+ internal::TranslateIconRestrictedUrl(transient_url, helper, url);
+}
+
+void SearchBox::GetMostVisitedItems(
+ std::vector<InstantMostVisitedItemIDPair>* items) const {
+ most_visited_items_cache_.GetCurrentItems(items);
+}
+
+bool SearchBox::AreMostVisitedItemsAvailable() const {
+ return has_received_most_visited_;
+}
+
+bool SearchBox::GetMostVisitedItemWithID(
+ InstantRestrictedID most_visited_item_id,
+ InstantMostVisitedItem* item) const {
+ return most_visited_items_cache_.GetItemWithRestrictedID(most_visited_item_id,
+ item);
+}
+
+const ThemeBackgroundInfo* SearchBox::GetThemeBackgroundInfo() const {
+ return base::OptionalOrNullptr(theme_info_);
+}
+
+void SearchBox::Paste(const base::string16& text) {
+ embedded_search_service_->PasteAndOpenDropdown(page_seq_no_, text);
+}
+
+void SearchBox::StartCapturingKeyStrokes() {
+ embedded_search_service_->FocusOmnibox(page_seq_no_, OMNIBOX_FOCUS_INVISIBLE);
+}
+
+void SearchBox::StopCapturingKeyStrokes() {
+ embedded_search_service_->FocusOmnibox(page_seq_no_, OMNIBOX_FOCUS_NONE);
+}
+
+void SearchBox::UndoAllMostVisitedDeletions() {
+ embedded_search_service_->UndoAllMostVisitedDeletions(page_seq_no_);
+}
+
+void SearchBox::UndoMostVisitedDeletion(
+ InstantRestrictedID most_visited_item_id) {
+ GURL url = GetURLForMostVisitedItem(most_visited_item_id);
+ if (!url.is_valid())
+ return;
+ embedded_search_service_->UndoMostVisitedDeletion(page_seq_no_, url);
+}
+
+bool SearchBox::IsCustomLinks() const {
+ return most_visited_info_.items_are_custom_links;
+}
+
+bool SearchBox::IsUsingMostVisited() const {
+ return most_visited_info_.use_most_visited;
+}
+
+bool SearchBox::AreShortcutsVisible() const {
+ return most_visited_info_.is_visible;
+}
+
+void SearchBox::AddCustomLink(const GURL& url, const std::string& title) {
+ if (!url.is_valid()) {
+ AddCustomLinkResult(false);
+ return;
+ }
+ embedded_search_service_->AddCustomLink(
+ page_seq_no_, url, title,
+ base::BindOnce(&SearchBox::AddCustomLinkResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SearchBox::UpdateCustomLink(InstantRestrictedID link_id,
+ const GURL& new_url,
+ const std::string& new_title) {
+ GURL url = GetURLForMostVisitedItem(link_id);
+ if (!url.is_valid()) {
+ UpdateCustomLinkResult(false);
+ return;
+ }
+ embedded_search_service_->UpdateCustomLink(
+ page_seq_no_, url, new_url, new_title,
+ base::BindOnce(&SearchBox::UpdateCustomLinkResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SearchBox::ReorderCustomLink(InstantRestrictedID link_id, int new_pos) {
+ GURL url = GetURLForMostVisitedItem(link_id);
+ if (!url.is_valid())
+ return;
+ embedded_search_service_->ReorderCustomLink(page_seq_no_, url, new_pos);
+}
+
+void SearchBox::DeleteCustomLink(InstantRestrictedID most_visited_item_id) {
+ GURL url = GetURLForMostVisitedItem(most_visited_item_id);
+ if (!url.is_valid()) {
+ DeleteCustomLinkResult(false);
+ return;
+ }
+ embedded_search_service_->DeleteCustomLink(
+ page_seq_no_, url,
+ base::BindOnce(&SearchBox::DeleteCustomLinkResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SearchBox::UndoCustomLinkAction() {
+ embedded_search_service_->UndoCustomLinkAction(page_seq_no_);
+}
+
+void SearchBox::ResetCustomLinks() {
+ embedded_search_service_->ResetCustomLinks(page_seq_no_);
+}
+
+void SearchBox::ToggleMostVisitedOrCustomLinks() {
+ embedded_search_service_->ToggleMostVisitedOrCustomLinks(page_seq_no_);
+}
+
+void SearchBox::ToggleShortcutsVisibility(bool do_notify) {
+ embedded_search_service_->ToggleShortcutsVisibility(page_seq_no_, do_notify);
+}
+
+std::string SearchBox::FixupAndValidateUrl(const std::string& url) const {
+ return internal::FixupAndValidateUrl(url);
+}
+
+void SearchBox::SetCustomBackgroundInfo(const GURL& background_url,
+ const std::string& attribution_line_1,
+ const std::string& attribution_line_2,
+ const GURL& action_url,
+ const std::string& collection_id) {
+ embedded_search_service_->SetCustomBackgroundInfo(
+ background_url, attribution_line_1, attribution_line_2, action_url,
+ collection_id);
+}
+
+void SearchBox::SelectLocalBackgroundImage() {
+ embedded_search_service_->SelectLocalBackgroundImage();
+}
+
+void SearchBox::BlocklistSearchSuggestion(int task_version, long task_id) {
+ embedded_search_service_->BlocklistSearchSuggestion(task_version, task_id);
+}
+
+void SearchBox::BlocklistSearchSuggestionWithHash(
+ int task_version,
+ long task_id,
+ const std::vector<uint8_t>& hash) {
+ embedded_search_service_->BlocklistSearchSuggestionWithHash(task_version,
+ task_id, hash);
+}
+
+void SearchBox::SearchSuggestionSelected(int task_version,
+ long task_id,
+ const std::vector<uint8_t>& hash) {
+ embedded_search_service_->SearchSuggestionSelected(task_version, task_id,
+ hash);
+}
+
+void SearchBox::OptOutOfSearchSuggestions() {
+ embedded_search_service_->OptOutOfSearchSuggestions();
+}
+
+void SearchBox::ApplyDefaultTheme() {
+ embedded_search_service_->ApplyDefaultTheme();
+}
+
+void SearchBox::ApplyAutogeneratedTheme(SkColor color) {
+ embedded_search_service_->ApplyAutogeneratedTheme(color);
+}
+
+void SearchBox::RevertThemeChanges() {
+ embedded_search_service_->RevertThemeChanges();
+}
+
+void SearchBox::ConfirmThemeChanges() {
+ embedded_search_service_->ConfirmThemeChanges();
+}
+
+void SearchBox::QueryAutocomplete(const base::string16& input) {
+ embedded_search_service_->QueryAutocomplete(
+ input, base::BindOnce(&SearchBox::QueryAutocompleteResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SearchBox::DeleteAutocompleteMatch(uint8_t line) {
+ embedded_search_service_->DeleteAutocompleteMatch(
+ line, base::BindOnce(&SearchBox::OnDeleteAutocompleteMatch,
+ base::Unretained(this)));
+}
+
+void SearchBox::StopAutocomplete(bool clear_result) {
+ embedded_search_service_->StopAutocomplete(clear_result);
+}
+
+void SearchBox::BlocklistPromo(const std::string& promo_id) {
+ embedded_search_service_->BlocklistPromo(promo_id);
+}
+
+void SearchBox::QueryAutocompleteResult(
+ chrome::mojom::AutocompleteResultPtr result) {
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchQueryAutocompleteResult(
+ render_frame()->GetWebFrame(), std::move(result));
+ }
+}
+
+void SearchBox::OnDeleteAutocompleteMatch(
+ chrome::mojom::DeleteAutocompleteMatchResultPtr result) {
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchDeleteAutocompleteMatchResult(
+ render_frame()->GetWebFrame(), std::move(result));
+ }
+}
+
+void SearchBox::SetPageSequenceNumber(int page_seq_no) {
+ page_seq_no_ = page_seq_no;
+}
+
+void SearchBox::FocusChanged(OmniboxFocusState new_focus_state,
+ OmniboxFocusChangeReason reason) {
+ bool key_capture_enabled = new_focus_state == OMNIBOX_FOCUS_INVISIBLE;
+ if (key_capture_enabled != is_key_capture_enabled_) {
+ // Tell the page if the key capture mode changed unless the focus state
+ // changed because of TYPING. This is because in that case, the browser
+ // hasn't really stopped capturing key strokes.
+ //
+ // (More practically, if we don't do this check, the page would receive
+ // onkeycapturechange before the corresponding onchange, and the page would
+ // have no way of telling whether the keycapturechange happened because of
+ // some actual user action or just because they started typing.)
+ if (reason != OMNIBOX_FOCUS_CHANGE_TYPING) {
+ is_key_capture_enabled_ = key_capture_enabled;
+ DVLOG(1) << render_frame() << " KeyCaptureChange";
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchKeyCaptureChange(
+ render_frame()->GetWebFrame());
+ }
+ }
+ }
+ bool is_focused = new_focus_state == OMNIBOX_FOCUS_VISIBLE;
+ if (is_focused != is_focused_) {
+ is_focused_ = is_focused;
+ DVLOG(1) << render_frame() << " FocusChange";
+ if (can_run_js_in_renderframe_)
+ SearchBoxExtension::DispatchFocusChange(render_frame()->GetWebFrame());
+ }
+}
+
+void SearchBox::AddCustomLinkResult(bool success) {
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchAddCustomLinkResult(
+ render_frame()->GetWebFrame(), success);
+ }
+}
+
+void SearchBox::UpdateCustomLinkResult(bool success) {
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchUpdateCustomLinkResult(
+ render_frame()->GetWebFrame(), success);
+ }
+}
+
+void SearchBox::DeleteCustomLinkResult(bool success) {
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchDeleteCustomLinkResult(
+ render_frame()->GetWebFrame(), success);
+ }
+}
+
+void SearchBox::MostVisitedInfoChanged(
+ const InstantMostVisitedInfo& most_visited_info) {
+ has_received_most_visited_ = true;
+ most_visited_info_.items_are_custom_links =
+ most_visited_info.items_are_custom_links;
+
+ std::vector<InstantMostVisitedItemIDPair> last_known_items;
+ GetMostVisitedItems(&last_known_items);
+
+ if (AreMostVisitedItemsEqual(last_known_items, most_visited_info.items) &&
+ most_visited_info_.use_most_visited ==
+ most_visited_info.use_most_visited &&
+ most_visited_info_.is_visible == most_visited_info.is_visible) {
+ return; // Do not send duplicate onmostvisitedchange events.
+ }
+
+ most_visited_info_.use_most_visited = most_visited_info.use_most_visited;
+ most_visited_info_.is_visible = most_visited_info.is_visible;
+
+ most_visited_items_cache_.AddItems(most_visited_info.items);
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchMostVisitedChanged(
+ render_frame()->GetWebFrame());
+ }
+}
+
+void SearchBox::SetInputInProgress(bool is_input_in_progress) {
+ if (is_input_in_progress_ == is_input_in_progress)
+ return;
+
+ is_input_in_progress_ = is_input_in_progress;
+ DVLOG(1) << render_frame() << " SetInputInProgress";
+ if (can_run_js_in_renderframe_) {
+ if (is_input_in_progress_)
+ SearchBoxExtension::DispatchInputStart(render_frame()->GetWebFrame());
+ else
+ SearchBoxExtension::DispatchInputCancel(render_frame()->GetWebFrame());
+ }
+}
+
+void SearchBox::ThemeChanged(const ThemeBackgroundInfo& theme_info) {
+ // Do not send duplicate notifications.
+ if (theme_info_ == theme_info)
+ return;
+
+ theme_info_ = theme_info;
+ if (can_run_js_in_renderframe_)
+ SearchBoxExtension::DispatchThemeChange(render_frame()->GetWebFrame());
+}
+
+void SearchBox::LocalBackgroundSelected() {
+ if (can_run_js_in_renderframe_) {
+ SearchBoxExtension::DispatchLocalBackgroundSelected(
+ render_frame()->GetWebFrame());
+ }
+}
+
+GURL SearchBox::GetURLForMostVisitedItem(InstantRestrictedID item_id) const {
+ InstantMostVisitedItem item;
+ return GetMostVisitedItemWithID(item_id, &item) ? item.url : GURL();
+}
+
+void SearchBox::DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) {
+ can_run_js_in_renderframe_ = true;
+}
+
+void SearchBox::OnDestruct() {
+ delete this;
+}
diff --git a/chromium/chrome/renderer/searchbox/searchbox.h b/chromium/chrome/renderer/searchbox/searchbox.h
new file mode 100644
index 00000000000..c7755aab6aa
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/searchbox.h
@@ -0,0 +1,274 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SEARCHBOX_SEARCHBOX_H_
+#define CHROME_RENDERER_SEARCHBOX_SEARCHBOX_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "chrome/common/search.mojom.h"
+#include "chrome/common/search/instant_types.h"
+#include "chrome/common/search/ntp_logging_events.h"
+#include "chrome/renderer/instant_restricted_id_cache.h"
+#include "components/ntp_tiles/ntp_tile_impression.h"
+#include "components/omnibox/common/omnibox_focus_state.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "url/gurl.h"
+
+// The renderer-side implementation of the embeddedSearch API (see
+// https://www.chromium.org/embeddedsearch).
+class SearchBox : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<SearchBox>,
+ public chrome::mojom::EmbeddedSearchClient {
+ public:
+ // Helper class for GenerateImageURLFromTransientURL() to adapt SearchBox's
+ // instance, thereby allow mocking for unit tests.
+ class IconURLHelper {
+ public:
+ IconURLHelper();
+ virtual ~IconURLHelper();
+ // Retruns view id for validating icon URL.
+ virtual int GetViewID() const = 0;
+ // Returns the page URL string for |rid|, or empty string for invalid |rid|.
+ virtual std::string GetURLStringFromRestrictedID(InstantRestrictedID rid)
+ const = 0;
+ };
+
+ explicit SearchBox(content::RenderFrame* render_frame);
+ ~SearchBox() override;
+
+ // Sends LogEvent to the browser.
+ void LogEvent(NTPLoggingEventType event);
+
+ // Sends LogSuggestionEventWithValue to the browser.
+ void LogSuggestionEventWithValue(NTPSuggestionsLoggingEventType event,
+ int data);
+
+ // Sends LogMostVisitedImpression to the browser.
+ void LogMostVisitedImpression(const ntp_tiles::NTPTileImpression& impression);
+
+ // Sends LogMostVisitedNavigation to the browser.
+ void LogMostVisitedNavigation(const ntp_tiles::NTPTileImpression& impression);
+
+ // Sends DeleteMostVisitedItem to the browser.
+ void DeleteMostVisitedItem(InstantRestrictedID most_visited_item_id);
+
+ // Generates the image URL of the most visited item favicon specified by
+ // |transient_url|. If |transient_url| is valid, |url| is set with a
+ // translated URL. Otherwise, |url| is set the the default favicon
+ // ("chrome-search://favicon/").
+ //
+ // Valid forms of |transient_url|:
+ // chrome-search://favicon/<view_id>/<restricted_id>
+ // chrome-search://favicon/<favicon_parameters>/<view_id>/<restricted_id>
+ //
+ // We do this to prevent search providers from abusing image URLs and deduce
+ // whether the user has visited a particular page. For example, if
+ // "chrome-search://favicon/http://www.secretsite.com" is accessible, then
+ // the search provider can use its return code to determine whether the user
+ // has visited "http://www.secretsite.com". Therefore we require search
+ // providers to specify URL by "<view_id>/<restricted_id>". We then translate
+ // this to the original |url|, and pass the request to the proper endpoint.
+ void GenerateImageURLFromTransientURL(const GURL& transient_url,
+ GURL* url) const;
+
+ // Returns the latest most visited items sent by the browser.
+ void GetMostVisitedItems(
+ std::vector<InstantMostVisitedItemIDPair>* items) const;
+
+ bool AreMostVisitedItemsAvailable() const;
+
+ // If the |most_visited_item_id| is found in the cache, sets |item| to it
+ // and returns true.
+ bool GetMostVisitedItemWithID(InstantRestrictedID most_visited_item_id,
+ InstantMostVisitedItem* item) const;
+
+ // Sends PasteAndOpenDropdown to the browser.
+ void Paste(const base::string16& text);
+
+ // Will return null if the theme info hasn't been set yet.
+ const ThemeBackgroundInfo* GetThemeBackgroundInfo() const;
+
+ // Sends FocusOmnibox(OMNIBOX_FOCUS_INVISIBLE) to the browser.
+ void StartCapturingKeyStrokes();
+
+ // Sends FocusOmnibox(OMNIBOX_FOCUS_NONE) to the browser.
+ void StopCapturingKeyStrokes();
+
+ // Sends UndoAllMostVisitedDeletions to the browser.
+ void UndoAllMostVisitedDeletions();
+
+ // Sends UndoMostVisitedDeletion to the browser.
+ void UndoMostVisitedDeletion(InstantRestrictedID most_visited_item_id);
+
+ // Returns true if the most visited items are custom links.
+ bool IsCustomLinks() const;
+
+ // Returns true if most visited is enabled instead of custom links.
+ bool IsUsingMostVisited() const;
+
+ // Returns true if the shortcuts are visible and not hidden by the user.
+ bool AreShortcutsVisible() const;
+
+ // Sends AddCustomLink to the browser.
+ void AddCustomLink(const GURL& url, const std::string& title);
+
+ // Sends UpdateCustomLink to the browser.
+ void UpdateCustomLink(InstantRestrictedID link_id,
+ const GURL& new_url,
+ const std::string& new_title);
+
+ // Sends ReorderCustomLink to the browser.
+ void ReorderCustomLink(InstantRestrictedID link_id, int new_pos);
+
+ // Sends DeleteCustomLink to the browser.
+ void DeleteCustomLink(InstantRestrictedID most_visited_item_id);
+
+ // Sends UndoCustomLinkAction to the browser.
+ void UndoCustomLinkAction();
+
+ // Sends ResetCustomLinks to the browser.
+ void ResetCustomLinks();
+
+ // Sends ToggleMostVisitedOrCustomLinks to the browser.
+ void ToggleMostVisitedOrCustomLinks();
+
+ // Sends ToggleShortcutsVisibility to the browser.
+ void ToggleShortcutsVisibility(bool do_notify);
+
+ // Attempts to fix obviously invalid URLs. Uses the "https" scheme unless
+ // otherwise specified. Returns the fixed URL if valid, otherwise returns an
+ // empty string.
+ std::string FixupAndValidateUrl(const std::string& url) const;
+
+ // Updates the NTP custom background preferences, sometimes this includes
+ // image attributions.
+ void SetCustomBackgroundInfo(const GURL& background_url,
+ const std::string& attribution_line_1,
+ const std::string& attribution_line_2,
+ const GURL& action_url,
+ const std::string& collection_id);
+
+ // Let the user select a local file for the NTP background.
+ void SelectLocalBackgroundImage();
+
+ // Add a search suggestion task id to the blocklist.
+ void BlocklistSearchSuggestion(int task_version, long task_id);
+
+ // Add a search suggestion task id and hash to the blocklist.
+ void BlocklistSearchSuggestionWithHash(int task_version,
+ long task_id,
+ const std::vector<uint8_t>& hash);
+
+ // A suggestion collected, issue a new request with the suggestion
+ // temporarily added to the blocklist.
+ void SearchSuggestionSelected(int task_version,
+ long task_id,
+ const std::vector<uint8_t>& hash);
+
+ // Opts the user out of receiving search suggestions.
+ void OptOutOfSearchSuggestions();
+
+ // Applies the default theme.
+ void ApplyDefaultTheme();
+
+ // Applies autogenerated theme for the given color.
+ void ApplyAutogeneratedTheme(SkColor color);
+
+ // Reverts applied theme changes.
+ void RevertThemeChanges();
+
+ // Confirms applied theme changes.
+ void ConfirmThemeChanges();
+
+ // Queries the autocomplete backend for realbox results for |input| as a
+ // search term. Handled by |QueryAutocompleteResult|.
+ void QueryAutocomplete(const base::string16& input);
+
+ // Deletes |AutocompleteMatch| by index of the result.
+ void DeleteAutocompleteMatch(uint8_t line);
+
+ // Cancels the current autocomplete query. Clears the result set if
+ // |clear_result| is true.
+ void StopAutocomplete(bool clear_result);
+
+ // Called when a user dismisses a promo.
+ void BlocklistPromo(const std::string& promo_id);
+
+ bool is_focused() const { return is_focused_; }
+ bool is_input_in_progress() const { return is_input_in_progress_; }
+ bool is_key_capture_enabled() const { return is_key_capture_enabled_; }
+
+ private:
+ // Overridden from content::RenderFrameObserver:
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override;
+ void OnDestruct() override;
+
+ // Overridden from chrome::mojom::EmbeddedSearchClient:
+ void SetPageSequenceNumber(int page_seq_no) override;
+ void FocusChanged(OmniboxFocusState new_focus_state,
+ OmniboxFocusChangeReason reason) override;
+ void MostVisitedInfoChanged(
+ const InstantMostVisitedInfo& most_visited_info) override;
+ void SetInputInProgress(bool input_in_progress) override;
+ void ThemeChanged(const ThemeBackgroundInfo& theme_info) override;
+ void LocalBackgroundSelected() override;
+
+ void AddCustomLinkResult(bool success);
+ void UpdateCustomLinkResult(bool success);
+ void DeleteCustomLinkResult(bool success);
+
+ // Returns the URL of the Most Visited item specified by the |item_id|.
+ GURL GetURLForMostVisitedItem(InstantRestrictedID item_id) const;
+
+ // Asynchronous callback for autocomplete query results. Sends to renderer.
+ void QueryAutocompleteResult(chrome::mojom::AutocompleteResultPtr result);
+
+ // Asynchronous callback for results of attempting to delete an autocomplete
+ // result.
+ void OnDeleteAutocompleteMatch(
+ chrome::mojom::DeleteAutocompleteMatchResultPtr result);
+
+ // The connection to the EmbeddedSearch service in the browser process.
+ chrome::mojom::EmbeddedSearchAssociatedPtr embedded_search_service_;
+ mojo::AssociatedBinding<chrome::mojom::EmbeddedSearchClient> binding_;
+
+ // Whether it's legal to execute JavaScript in |render_frame()|.
+ // This class may want to execute JS in response to IPCs (via the
+ // SearchBoxExtension::Dispatch* methods). However, for cross-process
+ // navigations, a "provisional frame" is created at first, and it's illegal
+ // to execute any JS in it before it is actually swapped in, i.e. before the
+ // navigation has committed. So this only gets set to true in
+ // RenderFrameObserver::DidCommitProvisionalLoad. See crbug.com/765101.
+ // Note: If crbug.com/794942 ever gets resolved, then it might be possible to
+ // move the mojo connection code from the ctor to DidCommitProvisionalLoad and
+ // avoid this bool.
+ bool can_run_js_in_renderframe_;
+
+ // The Instant state.
+ int page_seq_no_;
+ bool is_focused_;
+ bool is_input_in_progress_;
+ bool is_key_capture_enabled_;
+ InstantRestrictedIDCache<InstantMostVisitedItem> most_visited_items_cache_;
+ // Use |most_visited_items_cache_| instead of |most_visited_info_.items| when
+ // comparing most visited items.
+ InstantMostVisitedInfo most_visited_info_;
+ bool has_received_most_visited_;
+ base::Optional<ThemeBackgroundInfo> theme_info_;
+
+ base::WeakPtrFactory<SearchBox> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(SearchBox);
+};
+
+#endif // CHROME_RENDERER_SEARCHBOX_SEARCHBOX_H_
diff --git a/chromium/chrome/renderer/searchbox/searchbox_extension.cc b/chromium/chrome/renderer/searchbox/searchbox_extension.cc
new file mode 100644
index 00000000000..5412c5566fd
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/searchbox_extension.cc
@@ -0,0 +1,1469 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/searchbox/searchbox_extension.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/i18n/rtl.h"
+#include "base/json/json_writer.h"
+#include "base/json/string_escape.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chrome/common/search/generated_colors_info.h"
+#include "chrome/common/search/instant_types.h"
+#include "chrome/common/search/ntp_logging_events.h"
+#include "chrome/common/search/selected_colors_info.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/renderer_resources.h"
+#include "chrome/renderer/searchbox/searchbox.h"
+#include "components/crx_file/id_util.h"
+#include "components/ntp_tiles/features.h"
+#include "components/ntp_tiles/ntp_tile_impression.h"
+#include "components/ntp_tiles/tile_source.h"
+#include "components/ntp_tiles/tile_visual_type.h"
+#include "content/public/renderer/chrome_object_extensions_utils.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "gin/data_object_builder.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "gin/wrappable.h"
+#include "third_party/blink/public/common/page/page_zoom.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/window_open_disposition.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/gfx/text_elider.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+#include "v8/include/v8.h"
+
+namespace {
+
+const char kCSSBackgroundImageFormat[] = "-webkit-image-set("
+ "url(chrome-search://theme/IDR_THEME_NTP_BACKGROUND?%s) 1x, "
+ "url(chrome-search://theme/IDR_THEME_NTP_BACKGROUND@2x?%s) 2x)";
+
+const char kCSSBackgroundPositionCenter[] = "center";
+const char kCSSBackgroundPositionLeft[] = "left";
+const char kCSSBackgroundPositionTop[] = "top";
+const char kCSSBackgroundPositionRight[] = "right";
+const char kCSSBackgroundPositionBottom[] = "bottom";
+
+const char kCSSBackgroundRepeatNo[] = "no-repeat";
+const char kCSSBackgroundRepeatX[] = "repeat-x";
+const char kCSSBackgroundRepeatY[] = "repeat-y";
+const char kCSSBackgroundRepeat[] = "repeat";
+
+const char kThemeAttributionFormat[] = "-webkit-image-set("
+ "url(chrome-search://theme/IDR_THEME_NTP_ATTRIBUTION?%s) 1x, "
+ "url(chrome-search://theme/IDR_THEME_NTP_ATTRIBUTION@2x?%s) 2x)";
+
+const char kLTRHtmlTextDirection[] = "ltr";
+const char kRTLHtmlTextDirection[] = "rtl";
+
+// Max character limit for custom link titles.
+const size_t kMaxCustomLinkTitleLength = 150;
+
+void Dispatch(blink::WebLocalFrame* frame, const blink::WebString& script) {
+ if (!frame)
+ return;
+ frame->ExecuteScript(blink::WebScriptSource(script));
+}
+
+// Populates a Javascript MostVisitedItem object for returning from
+// newTabPage.mostVisited. This does not include private data such as "url" or
+// "title".
+v8::Local<v8::Object> GenerateMostVisitedItem(
+ v8::Isolate* isolate,
+ float device_pixel_ratio,
+ int render_view_id,
+ InstantRestrictedID restricted_id) {
+ return gin::DataObjectBuilder(isolate)
+ .Set("rid", restricted_id)
+ .Set("faviconUrl", base::StringPrintf(
+ "chrome-search://favicon/size/16@%fx/%d/%d",
+ device_pixel_ratio, render_view_id, restricted_id))
+ .Build();
+}
+
+// Populates a Javascript MostVisitedItem object appropriate for returning from
+// newTabPage.getMostVisitedItemData.
+// NOTE: Includes private data such as "url", "title", and "domain", so this
+// should not be returned to the host page (via newTabPage.mostVisited). It is
+// only accessible to most-visited iframes via getMostVisitedItemData.
+v8::Local<v8::Object> GenerateMostVisitedItemData(
+ v8::Isolate* isolate,
+ int render_view_id,
+ InstantRestrictedID restricted_id,
+ const InstantMostVisitedItem& mv_item) {
+ // We set the "dir" attribute of the title, so that in RTL locales, a LTR
+ // title is rendered left-to-right and truncated from the right. For
+ // example, the title of http://msdn.microsoft.com/en-us/default.aspx is
+ // "MSDN: Microsoft developer network". In RTL locales, in the New Tab
+ // page, if the "dir" of this title is not specified, it takes Chrome UI's
+ // directionality. So the title will be truncated as "soft developer
+ // network". Setting the "dir" attribute as "ltr" renders the truncated
+ // title as "MSDN: Microsoft D...". As another example, the title of
+ // http://yahoo.com is "Yahoo!". In RTL locales, in the New Tab page, the
+ // title will be rendered as "!Yahoo" if its "dir" attribute is not set to
+ // "ltr".
+ const char* direction;
+ if (base::i18n::GetFirstStrongCharacterDirection(mv_item.title) ==
+ base::i18n::RIGHT_TO_LEFT) {
+ direction = kRTLHtmlTextDirection;
+ } else {
+ direction = kLTRHtmlTextDirection;
+ }
+
+ std::string title = base::UTF16ToUTF8(mv_item.title);
+ if (title.empty())
+ title = mv_item.url.spec();
+
+ gin::DataObjectBuilder builder(isolate);
+ builder.Set("renderViewId", render_view_id)
+ .Set("rid", restricted_id)
+ .Set("tileTitleSource", static_cast<int>(mv_item.title_source))
+ .Set("tileSource", static_cast<int>(mv_item.source))
+ .Set("title", title)
+ .Set("domain", mv_item.url.host())
+ .Set("direction", base::StringPiece(direction))
+ .Set("url", mv_item.url.spec())
+ .Set("dataGenerationTime",
+ mv_item.data_generation_time.is_null()
+ ? v8::Local<v8::Value>(v8::Null(isolate))
+ : v8::Date::New(isolate->GetCurrentContext(),
+ mv_item.data_generation_time.ToJsTime())
+ .ToLocalChecked());
+
+ // If the suggestion already has a favicon, we populate the element with it.
+ if (!mv_item.favicon.spec().empty())
+ builder.Set("faviconUrl", mv_item.favicon.spec());
+
+ return builder.Build();
+}
+
+base::Time ConvertDateValueToTime(v8::Value* value) {
+ DCHECK(value);
+ if (value->IsNull() || !value->IsDate())
+ return base::Time();
+
+ return base::Time::FromJsTime(v8::Date::Cast(value)->ValueOf());
+}
+
+base::Optional<int> CoerceToInt(v8::Isolate* isolate, v8::Value* value) {
+ DCHECK(value);
+ v8::MaybeLocal<v8::Int32> maybe_int =
+ value->ToInt32(isolate->GetCurrentContext());
+ if (maybe_int.IsEmpty())
+ return base::nullopt;
+ return maybe_int.ToLocalChecked()->Value();
+}
+
+// Returns an array with the RGBA color components.
+v8::Local<v8::Value> SkColorToArray(v8::Isolate* isolate,
+ const SkColor& color) {
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ v8::Local<v8::Array> color_array = v8::Array::New(isolate, 4);
+ color_array
+ ->CreateDataProperty(context, 0,
+ v8::Int32::New(isolate, SkColorGetR(color)))
+ .Check();
+ color_array
+ ->CreateDataProperty(context, 1,
+ v8::Int32::New(isolate, SkColorGetG(color)))
+ .Check();
+ color_array
+ ->CreateDataProperty(context, 2,
+ v8::Int32::New(isolate, SkColorGetB(color)))
+ .Check();
+ color_array
+ ->CreateDataProperty(context, 3,
+ v8::Int32::New(isolate, SkColorGetA(color)))
+ .Check();
+ return color_array;
+}
+
+// Converts given array to SkColor and returns whether the conversion is
+// successful.
+bool ArrayToSkColor(v8::Isolate* isolate,
+ v8::Local<v8::Array> color,
+ SkColor* color_result) {
+ if (color->Length() != 4)
+ return false;
+
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ v8::Local<v8::Value> r_value;
+ v8::Local<v8::Value> g_value;
+ v8::Local<v8::Value> b_value;
+ v8::Local<v8::Value> a_value;
+
+ if (!color->Get(context, 0).ToLocal(&r_value) ||
+ !color->Get(context, 1).ToLocal(&g_value) ||
+ !color->Get(context, 2).ToLocal(&b_value) ||
+ !color->Get(context, 3).ToLocal(&a_value))
+ return false;
+
+ base::Optional<int> r = CoerceToInt(isolate, *r_value);
+ base::Optional<int> g = CoerceToInt(isolate, *g_value);
+ base::Optional<int> b = CoerceToInt(isolate, *b_value);
+ base::Optional<int> a = CoerceToInt(isolate, *a_value);
+
+ if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
+ return false;
+
+ if (*a > 255 || *r > 255 || *g > 255 || *b > 255)
+ return false;
+
+ *color_result = SkColorSetARGB(*a, *r, *g, *b);
+ return true;
+}
+
+v8::Local<v8::Object> GenerateThemeBackgroundInfo(
+ v8::Isolate* isolate,
+ const ThemeBackgroundInfo& theme_info) {
+ gin::DataObjectBuilder builder(isolate);
+
+ // True if the theme is the system default and no custom theme has been
+ // applied.
+ // Value is always valid.
+ builder.Set("usingDefaultTheme", theme_info.using_default_theme);
+
+ // Theme color for background as an array with the RGBA components in order.
+ // Value is always valid.
+ builder.Set("backgroundColorRgba",
+ SkColorToArray(isolate, theme_info.background_color));
+
+ // Theme color for text as an array with the RGBA components in order.
+ // Value is always valid.
+ builder.Set("textColorRgba", SkColorToArray(isolate, theme_info.text_color));
+
+ // Theme color for light text as an array with the RGBA components in order.
+ // Value is always valid.
+ builder.Set("textColorLightRgba",
+ SkColorToArray(isolate, theme_info.text_color_light));
+
+ // The theme alternate logo value indicates same color when TRUE and a
+ // colorful one when FALSE.
+ builder.Set("alternateLogo", theme_info.logo_alternate);
+
+ // The theme background image url is of format kCSSBackgroundImageFormat
+ // where both instances of "%s" are replaced with the id that identifies the
+ // theme.
+ // This is the CSS "background-image" format.
+ // Value is only valid if there's a custom theme background image.
+ if (theme_info.has_theme_image) {
+ builder.Set("imageUrl", base::StringPrintf(kCSSBackgroundImageFormat,
+ theme_info.theme_id.c_str(),
+ theme_info.theme_id.c_str()));
+
+ // The theme background image horizontal alignment is one of "left",
+ // "right", "center".
+ // This is the horizontal component of the CSS "background-position" format.
+ // Value is only valid if |imageUrl| is not empty.
+ std::string alignment = kCSSBackgroundPositionCenter;
+ if (theme_info.image_horizontal_alignment ==
+ THEME_BKGRND_IMAGE_ALIGN_LEFT) {
+ alignment = kCSSBackgroundPositionLeft;
+ } else if (theme_info.image_horizontal_alignment ==
+ THEME_BKGRND_IMAGE_ALIGN_RIGHT) {
+ alignment = kCSSBackgroundPositionRight;
+ }
+ builder.Set("imageHorizontalAlignment", alignment);
+
+ // The theme background image vertical alignment is one of "top", "bottom",
+ // "center".
+ // This is the vertical component of the CSS "background-position" format.
+ // Value is only valid if |image_url| is not empty.
+ if (theme_info.image_vertical_alignment == THEME_BKGRND_IMAGE_ALIGN_TOP) {
+ alignment = kCSSBackgroundPositionTop;
+ } else if (theme_info.image_vertical_alignment ==
+ THEME_BKGRND_IMAGE_ALIGN_BOTTOM) {
+ alignment = kCSSBackgroundPositionBottom;
+ } else {
+ alignment = kCSSBackgroundPositionCenter;
+ }
+ builder.Set("imageVerticalAlignment", alignment);
+
+ // The tiling of the theme background image is one of "no-repeat",
+ // "repeat-x", "repeat-y", "repeat".
+ // This is the CSS "background-repeat" format.
+ // Value is only valid if |image_url| is not empty.
+ std::string tiling = kCSSBackgroundRepeatNo;
+ switch (theme_info.image_tiling) {
+ case THEME_BKGRND_IMAGE_NO_REPEAT:
+ tiling = kCSSBackgroundRepeatNo;
+ break;
+ case THEME_BKGRND_IMAGE_REPEAT_X:
+ tiling = kCSSBackgroundRepeatX;
+ break;
+ case THEME_BKGRND_IMAGE_REPEAT_Y:
+ tiling = kCSSBackgroundRepeatY;
+ break;
+ case THEME_BKGRND_IMAGE_REPEAT:
+ tiling = kCSSBackgroundRepeat;
+ break;
+ }
+ builder.Set("imageTiling", tiling);
+
+ // The attribution URL is only valid if the theme has attribution logo.
+ if (theme_info.has_attribution) {
+ builder.Set("attributionUrl",
+ base::StringPrintf(kThemeAttributionFormat,
+ theme_info.theme_id.c_str(),
+ theme_info.theme_id.c_str()));
+ }
+ }
+
+ builder.Set("themeId", theme_info.theme_id);
+ builder.Set("themeName", theme_info.theme_name);
+
+ builder.Set("customBackgroundConfigured",
+ !theme_info.custom_background_url.is_empty());
+
+ // If a custom background has been set provide the relevant information to the
+ // page.
+ if (!theme_info.custom_background_url.is_empty()) {
+ builder.Set("imageUrl", theme_info.custom_background_url.spec());
+ builder.Set("attributionActionUrl",
+ theme_info.custom_background_attribution_action_url.spec());
+ builder.Set("attribution1",
+ theme_info.custom_background_attribution_line_1);
+ builder.Set("attribution2",
+ theme_info.custom_background_attribution_line_2);
+ builder.Set("collectionId", theme_info.collection_id);
+ // Clear the theme attribution url, as it shouldn't be shown when
+ // a custom background is set.
+ builder.Set("attributionUrl", std::string());
+ }
+
+ // Set fields for themeing NTP elements.
+ builder.Set("isNtpBackgroundDark",
+ !color_utils::IsDark(theme_info.text_color));
+ builder.Set("useTitleContainer", theme_info.has_theme_image);
+
+ // TODO(gayane): Rename icon color to shortcut color in JS for consitancy.
+ builder.Set("iconBackgroundColor",
+ SkColorToArray(isolate, theme_info.shortcut_color));
+ builder.Set("useWhiteAddIcon",
+ color_utils::IsDark(theme_info.shortcut_color));
+
+ builder.Set("logoColor", SkColorToArray(isolate, theme_info.logo_color));
+
+ builder.Set("colorId", theme_info.color_id);
+ if (theme_info.color_id != -1) {
+ builder.Set("colorDark", SkColorToArray(isolate, theme_info.color_dark));
+ builder.Set("colorLight", SkColorToArray(isolate, theme_info.color_light));
+ builder.Set("colorPicked",
+ SkColorToArray(isolate, theme_info.color_picked));
+ }
+
+ return builder.Build();
+}
+
+content::RenderFrame* GetMainRenderFrameForCurrentContext() {
+ blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForCurrentContext();
+ if (!frame)
+ return nullptr;
+ content::RenderFrame* main_frame =
+ content::RenderFrame::FromWebFrame(frame->LocalRoot());
+ if (!main_frame || !main_frame->IsMainFrame())
+ return nullptr;
+ return main_frame;
+}
+
+SearchBox* GetSearchBoxForCurrentContext() {
+ content::RenderFrame* main_frame = GetMainRenderFrameForCurrentContext();
+ if (!main_frame)
+ return nullptr;
+ return SearchBox::Get(main_frame);
+}
+
+base::Value CreateAutocompleteMatches(
+ const std::vector<chrome::mojom::AutocompleteMatchPtr>& matches) {
+ base::Value list(base::Value::Type::LIST);
+ for (const chrome::mojom::AutocompleteMatchPtr& match : matches) {
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetBoolKey("allowedToBeDefaultMatch",
+ match->allowed_to_be_default_match);
+ dict.SetStringKey("contents", match->contents);
+ base::Value contents_class(base::Value::Type::LIST);
+ for (const auto& classification : match->contents_class) {
+ base::Value entry(base::Value::Type::DICTIONARY);
+ entry.SetIntKey("offset", classification->offset);
+ entry.SetIntKey("style", classification->style);
+ contents_class.Append(std::move(entry));
+ }
+ dict.SetKey("contentsClass", std::move(contents_class));
+ dict.SetStringKey("description", match->description);
+ base::Value description_class(base::Value::Type::LIST);
+ for (const auto& classification : match->description_class) {
+ base::Value entry(base::Value::Type::DICTIONARY);
+ entry.SetIntKey("offset", classification->offset);
+ entry.SetIntKey("style", classification->style);
+ description_class.Append(std::move(entry));
+ }
+ dict.SetKey("descriptionClass", std::move(description_class));
+ dict.SetStringKey("destinationUrl", match->destination_url);
+ dict.SetStringKey("inlineAutocompletion", match->inline_autocompletion);
+ dict.SetBoolKey("isSearchType", match->is_search_type);
+ dict.SetStringKey("fillIntoEdit", match->fill_into_edit);
+ dict.SetBoolKey("swapContentsAndDescription",
+ match->swap_contents_and_description);
+ dict.SetStringKey("type", match->type);
+ dict.SetBoolKey("supportsDeletion", match->supports_deletion);
+ list.Append(std::move(dict));
+ }
+ return list;
+}
+
+static const char kDispatchFocusChangedScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.searchBox &&"
+ " window.chrome.embeddedSearch.searchBox.onfocuschange &&"
+ " typeof window.chrome.embeddedSearch.searchBox.onfocuschange =="
+ " 'function') {"
+ " window.chrome.embeddedSearch.searchBox.onfocuschange();"
+ " true;"
+ "}";
+
+static const char kDispatchAddCustomLinkResult[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.onaddcustomlinkdone &&"
+ " typeof window.chrome.embeddedSearch.newTabPage"
+ " .onaddcustomlinkdone === 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.onaddcustomlinkdone(%s);"
+ " true;"
+ "}";
+
+static const char kDispatchUpdateCustomLinkResult[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.onupdatecustomlinkdone &&"
+ " typeof window.chrome.embeddedSearch.newTabPage"
+ " .onupdatecustomlinkdone === 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.onupdatecustomlinkdone(%s);"
+ " true;"
+ "}";
+
+static const char kDispatchQueryAutocompleteResult[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.searchBox &&"
+ " window.chrome.embeddedSearch.searchBox.onqueryautocompletedone &&"
+ " typeof window.chrome.embeddedSearch.searchBox"
+ " .onqueryautocompletedone === 'function') {"
+ " window.chrome.embeddedSearch.searchBox.onqueryautocompletedone(%s);"
+ " true;"
+ "}";
+
+static const char kDispatchDeleteAutocompleteMatchResult[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.searchBox &&"
+ " window.chrome.embeddedSearch.searchBox.ondeleteautocompletematch &&"
+ " typeof window.chrome.embeddedSearch.searchBox"
+ " .ondeleteautocompletematch === 'function') {"
+ " window.chrome.embeddedSearch.searchBox.ondeleteautocompletematch(%s);"
+ " true;"
+ "}";
+
+static const char kDispatchDeleteCustomLinkResult[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.ondeletecustomlinkdone &&"
+ " typeof window.chrome.embeddedSearch.newTabPage"
+ " .ondeletecustomlinkdone === 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.ondeletecustomlinkdone(%s);"
+ " true;"
+ "}";
+
+static const char kDispatchInputCancelScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.oninputcancel &&"
+ " typeof window.chrome.embeddedSearch.newTabPage.oninputcancel =="
+ " 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.oninputcancel();"
+ " true;"
+ "}";
+
+static const char kDispatchInputStartScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.oninputstart &&"
+ " typeof window.chrome.embeddedSearch.newTabPage.oninputstart =="
+ " 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.oninputstart();"
+ " true;"
+ "}";
+
+static const char kDispatchKeyCaptureChangeScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.searchBox &&"
+ " window.chrome.embeddedSearch.searchBox.onkeycapturechange &&"
+ " typeof window.chrome.embeddedSearch.searchBox.onkeycapturechange =="
+ " 'function') {"
+ " window.chrome.embeddedSearch.searchBox.onkeycapturechange();"
+ " true;"
+ "}";
+
+static const char kDispatchMostVisitedChangedScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.onmostvisitedchange &&"
+ " typeof window.chrome.embeddedSearch.newTabPage.onmostvisitedchange =="
+ " 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.onmostvisitedchange();"
+ " true;"
+ "}";
+
+static const char kDispatchThemeChangeEventScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.onthemechange &&"
+ " typeof window.chrome.embeddedSearch.newTabPage.onthemechange =="
+ " 'function') {"
+ " window.chrome.embeddedSearch.newTabPage.onthemechange();"
+ " true;"
+ "}";
+
+static const char kDispatchLocalBackgroundSelectedScript[] =
+ "if (window.chrome &&"
+ " window.chrome.embeddedSearch &&"
+ " window.chrome.embeddedSearch.newTabPage &&"
+ " window.chrome.embeddedSearch.newTabPage.onlocalbackgroundselected &&"
+ " typeof "
+ "window.chrome.embeddedSearch.newTabPage.onlocalbackgroundselected =="
+ " 'function') {"
+ " "
+ "window.chrome.embeddedSearch.newTabPage."
+ "onlocalbackgroundselected();"
+ " true;"
+ "}";
+
+// ----------------------------------------------------------------------------
+
+class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ SearchBoxBindings();
+ ~SearchBoxBindings() override;
+
+ private:
+ // gin::Wrappable.
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) final;
+
+ // Handlers for JS properties.
+ static bool IsFocused();
+ static bool IsKeyCaptureEnabled();
+
+ // Handlers for JS functions.
+ static void DeleteAutocompleteMatch(int line);
+ static void Paste(const std::string& text);
+ static void QueryAutocomplete(const base::string16& input);
+ static void StopAutocomplete(bool clear_result);
+ static void StartCapturingKeyStrokes();
+ static void StopCapturingKeyStrokes();
+
+ DISALLOW_COPY_AND_ASSIGN(SearchBoxBindings);
+};
+
+gin::WrapperInfo SearchBoxBindings::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+SearchBoxBindings::SearchBoxBindings() = default;
+
+SearchBoxBindings::~SearchBoxBindings() = default;
+
+gin::ObjectTemplateBuilder SearchBoxBindings::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return gin::Wrappable<SearchBoxBindings>::GetObjectTemplateBuilder(isolate)
+ .SetProperty("rtl", &base::i18n::IsRTL)
+ .SetProperty("isFocused", &SearchBoxBindings::IsFocused)
+ .SetProperty("isKeyCaptureEnabled",
+ &SearchBoxBindings::IsKeyCaptureEnabled)
+ .SetMethod("deleteAutocompleteMatch",
+ &SearchBoxBindings::DeleteAutocompleteMatch)
+ .SetMethod("paste", &SearchBoxBindings::Paste)
+ .SetMethod("queryAutocomplete", &SearchBoxBindings::QueryAutocomplete)
+ .SetMethod("stopAutocomplete", &SearchBoxBindings::StopAutocomplete)
+ .SetMethod("startCapturingKeyStrokes",
+ &SearchBoxBindings::StartCapturingKeyStrokes)
+ .SetMethod("stopCapturingKeyStrokes",
+ &SearchBoxBindings::StopCapturingKeyStrokes);
+}
+
+// static
+bool SearchBoxBindings::IsFocused() {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return false;
+ return search_box->is_focused();
+}
+
+// static
+bool SearchBoxBindings::IsKeyCaptureEnabled() {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return false;
+ return search_box->is_key_capture_enabled();
+}
+
+// static
+void SearchBoxBindings::DeleteAutocompleteMatch(int line) {
+ DCHECK_GE(line, 0);
+ DCHECK_LE(line, 255);
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->DeleteAutocompleteMatch(line);
+}
+
+// static
+void SearchBoxBindings::Paste(const std::string& text) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->Paste(base::UTF8ToUTF16(text));
+}
+
+// static
+void SearchBoxBindings::QueryAutocomplete(const base::string16& input) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->QueryAutocomplete(input);
+}
+
+// static
+void SearchBoxBindings::StopAutocomplete(bool clear_result) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->StopAutocomplete(clear_result);
+}
+
+// static
+void SearchBoxBindings::StartCapturingKeyStrokes() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->StartCapturingKeyStrokes();
+}
+
+// static
+void SearchBoxBindings::StopCapturingKeyStrokes() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->StopCapturingKeyStrokes();
+}
+
+class NewTabPageBindings : public gin::Wrappable<NewTabPageBindings> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ NewTabPageBindings();
+ ~NewTabPageBindings() override;
+
+ private:
+ // gin::Wrappable.
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) final;
+
+ static bool HasOrigin(const GURL& origin);
+
+ // Handlers for JS properties.
+ static bool IsInputInProgress();
+ static v8::Local<v8::Value> GetMostVisited(v8::Isolate* isolate);
+ static bool GetMostVisitedAvailable(v8::Isolate* isolate);
+ static v8::Local<v8::Value> GetThemeBackgroundInfo(v8::Isolate* isolate);
+ static bool GetIsCustomLinks();
+ static bool GetIsUsingMostVisited();
+ static bool GetAreShortcutsVisible();
+
+ // Handlers for JS functions visible to all NTPs.
+ static void DeleteMostVisitedItem(v8::Isolate* isolate,
+ v8::Local<v8::Value> rid);
+ static void UndoAllMostVisitedDeletions();
+ static void UndoMostVisitedDeletion(v8::Isolate* isolate,
+ v8::Local<v8::Value> rid);
+
+ // Handlers for JS functions visible only to the most visited iframe, the edit
+ // custom links iframe, and/or the local NTP.
+ static v8::Local<v8::Value> GetMostVisitedItemData(v8::Isolate* isolate,
+ int rid);
+ static void UpdateCustomLink(int rid,
+ const std::string& url,
+ const std::string& title);
+ static void ReorderCustomLink(int rid, int new_pos);
+ static void UndoCustomLinkAction();
+ static void ResetCustomLinks();
+ static void ToggleMostVisitedOrCustomLinks();
+ static void ToggleShortcutsVisibility(bool do_notify);
+ static std::string FixupAndValidateUrl(const std::string& url);
+ static void LogEvent(int event);
+ static void LogSuggestionEventWithValue(int event, int data);
+ static void LogMostVisitedImpression(
+ int position,
+ int tile_title_source,
+ int tile_source,
+ int tile_type,
+ v8::Local<v8::Value> data_generation_time);
+ static void LogMostVisitedNavigation(
+ int position,
+ int tile_title_source,
+ int tile_source,
+ int tile_type,
+ v8::Local<v8::Value> data_generation_time);
+ static void ResetCustomBackgroundInfo();
+ static void SetCustomBackgroundInfo(const std::string& background_url,
+ const std::string& attribution_line_1,
+ const std::string& attribution_line_2,
+ const std::string& attributionActionUrl,
+ const std::string& collection_id);
+ static void SelectLocalBackgroundImage();
+ static void BlocklistSearchSuggestion(int task_version, int task_id);
+ static void BlocklistSearchSuggestionWithHash(int task_version,
+ int task_id,
+ const std::string& hash);
+ static void SearchSuggestionSelected(int task_version,
+ int task_id,
+ const std::string& hash);
+ static void OptOutOfSearchSuggestions();
+ static void UseDefaultTheme();
+ static void ApplyDefaultTheme();
+ static void ApplyAutogeneratedTheme(v8::Isolate* isolate,
+ int id,
+ v8::Local<v8::Value> color);
+ static void RevertThemeChanges();
+ static void ConfirmThemeChanges();
+ static void BlocklistPromo(const std::string& promo_id);
+ static v8::Local<v8::Value> GetColorsInfo(v8::Isolate* isolate);
+
+ DISALLOW_COPY_AND_ASSIGN(NewTabPageBindings);
+};
+
+gin::WrapperInfo NewTabPageBindings::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+NewTabPageBindings::NewTabPageBindings() = default;
+
+NewTabPageBindings::~NewTabPageBindings() = default;
+
+gin::ObjectTemplateBuilder NewTabPageBindings::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return gin::Wrappable<NewTabPageBindings>::GetObjectTemplateBuilder(isolate)
+ .SetProperty("isInputInProgress", &NewTabPageBindings::IsInputInProgress)
+ .SetProperty("mostVisited", &NewTabPageBindings::GetMostVisited)
+ .SetProperty("mostVisitedAvailable",
+ &NewTabPageBindings::GetMostVisitedAvailable)
+ .SetProperty("themeBackgroundInfo",
+ &NewTabPageBindings::GetThemeBackgroundInfo)
+ .SetProperty("isCustomLinks", &NewTabPageBindings::GetIsCustomLinks)
+ .SetProperty("isUsingMostVisited",
+ &NewTabPageBindings::GetIsUsingMostVisited)
+ .SetProperty("areShortcutsVisible",
+ &NewTabPageBindings::GetAreShortcutsVisible)
+ .SetMethod("deleteMostVisitedItem",
+ &NewTabPageBindings::DeleteMostVisitedItem)
+ .SetMethod("undoAllMostVisitedDeletions",
+ &NewTabPageBindings::UndoAllMostVisitedDeletions)
+ .SetMethod("undoMostVisitedDeletion",
+ &NewTabPageBindings::UndoMostVisitedDeletion)
+ .SetMethod("getMostVisitedItemData",
+ &NewTabPageBindings::GetMostVisitedItemData)
+ .SetMethod("updateCustomLink", &NewTabPageBindings::UpdateCustomLink)
+ .SetMethod("reorderCustomLink", &NewTabPageBindings::ReorderCustomLink)
+ .SetMethod("undoCustomLinkAction",
+ &NewTabPageBindings::UndoCustomLinkAction)
+ .SetMethod("resetCustomLinks", &NewTabPageBindings::ResetCustomLinks)
+ .SetMethod("toggleMostVisitedOrCustomLinks",
+ &NewTabPageBindings::ToggleMostVisitedOrCustomLinks)
+ .SetMethod("toggleShortcutsVisibility",
+ &NewTabPageBindings::ToggleShortcutsVisibility)
+ .SetMethod("fixupAndValidateUrl",
+ &NewTabPageBindings::FixupAndValidateUrl)
+ .SetMethod("logEvent", &NewTabPageBindings::LogEvent)
+ .SetMethod("logSuggestionEventWithValue",
+ &NewTabPageBindings::LogSuggestionEventWithValue)
+ .SetMethod("logMostVisitedImpression",
+ &NewTabPageBindings::LogMostVisitedImpression)
+ .SetMethod("logMostVisitedNavigation",
+ &NewTabPageBindings::LogMostVisitedNavigation)
+ .SetMethod("resetBackgroundInfo",
+ &NewTabPageBindings::ResetCustomBackgroundInfo)
+ .SetMethod("setBackgroundInfo",
+ &NewTabPageBindings::SetCustomBackgroundInfo)
+ .SetMethod("selectLocalBackgroundImage",
+ &NewTabPageBindings::SelectLocalBackgroundImage)
+ .SetMethod("blacklistSearchSuggestion",
+ &NewTabPageBindings::BlocklistSearchSuggestion)
+ .SetMethod("blacklistSearchSuggestionWithHash",
+ &NewTabPageBindings::BlocklistSearchSuggestionWithHash)
+ .SetMethod("searchSuggestionSelected",
+ &NewTabPageBindings::SearchSuggestionSelected)
+ .SetMethod("optOutOfSearchSuggestions",
+ &NewTabPageBindings::OptOutOfSearchSuggestions)
+ .SetMethod("useDefaultTheme", &NewTabPageBindings::UseDefaultTheme)
+ .SetMethod("applyDefaultTheme", &NewTabPageBindings::ApplyDefaultTheme)
+ .SetMethod("applyAutogeneratedTheme",
+ &NewTabPageBindings::ApplyAutogeneratedTheme)
+ .SetMethod("revertThemeChanges", &NewTabPageBindings::RevertThemeChanges)
+ .SetMethod("confirmThemeChanges",
+ &NewTabPageBindings::ConfirmThemeChanges)
+ .SetMethod("getColorsInfo", &NewTabPageBindings::GetColorsInfo)
+ .SetMethod("blocklistPromo", &NewTabPageBindings::BlocklistPromo);
+}
+
+// static
+bool NewTabPageBindings::HasOrigin(const GURL& origin) {
+ blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForCurrentContext();
+ if (!frame)
+ return false;
+ GURL url(frame->GetDocument().Url());
+ return url.GetOrigin() == origin.GetOrigin();
+}
+
+// static
+bool NewTabPageBindings::IsInputInProgress() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return false;
+ return search_box->is_input_in_progress();
+}
+
+// static
+v8::Local<v8::Value> NewTabPageBindings::GetMostVisited(v8::Isolate* isolate) {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return v8::Null(isolate);
+
+ content::RenderFrame* render_frame = GetMainRenderFrameForCurrentContext();
+ content::RenderView* render_view = render_frame->GetRenderView();
+
+ // This corresponds to "window.devicePixelRatio" in JavaScript.
+ float zoom_factor =
+ blink::PageZoomLevelToZoomFactor(render_view->GetZoomLevel());
+ float device_pixel_ratio = render_frame->GetDeviceScaleFactor() * zoom_factor;
+
+ int render_view_id = render_view->GetRoutingID();
+
+ std::vector<InstantMostVisitedItemIDPair> instant_mv_items;
+ search_box->GetMostVisitedItems(&instant_mv_items);
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ v8::Local<v8::Object> v8_mv_items =
+ v8::Array::New(isolate, instant_mv_items.size());
+ for (size_t i = 0; i < instant_mv_items.size(); ++i) {
+ InstantRestrictedID rid = instant_mv_items[i].first;
+ v8_mv_items
+ ->CreateDataProperty(
+ context, i,
+ GenerateMostVisitedItem(isolate, device_pixel_ratio, render_view_id,
+ rid))
+ .Check();
+ }
+ return v8_mv_items;
+}
+
+// static
+bool NewTabPageBindings::GetMostVisitedAvailable(v8::Isolate* isolate) {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return false;
+
+ return search_box->AreMostVisitedItemsAvailable();
+}
+
+// static
+v8::Local<v8::Value> NewTabPageBindings::GetThemeBackgroundInfo(
+ v8::Isolate* isolate) {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return v8::Null(isolate);
+ const ThemeBackgroundInfo* theme_info = search_box->GetThemeBackgroundInfo();
+ if (!theme_info)
+ return v8::Null(isolate);
+ return GenerateThemeBackgroundInfo(isolate, *theme_info);
+}
+
+// static
+bool NewTabPageBindings::GetIsCustomLinks() {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return false;
+
+ return search_box->IsCustomLinks();
+}
+
+// static
+bool NewTabPageBindings::GetIsUsingMostVisited() {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !(HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)) ||
+ HasOrigin(GURL(chrome::kChromeSearchLocalNtpUrl)))) {
+ return false;
+ }
+
+ return search_box->IsUsingMostVisited();
+}
+
+// static
+bool NewTabPageBindings::GetAreShortcutsVisible() {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !(HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)) ||
+ HasOrigin(GURL(chrome::kChromeSearchLocalNtpUrl)))) {
+ return true;
+ }
+
+ return search_box->AreShortcutsVisible();
+}
+
+// static
+void NewTabPageBindings::DeleteMostVisitedItem(v8::Isolate* isolate,
+ v8::Local<v8::Value> rid_value) {
+ // Manually convert to integer, so that the string "\"1\"" is also accepted.
+ base::Optional<int> rid = CoerceToInt(isolate, *rid_value);
+ if (!rid.has_value())
+ return;
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+
+ // Treat the Most Visited item as a custom link if called from the Most
+ // Visited or edit custom link iframes. This will initialize custom links if
+ // they have not already been initialized.
+ if (HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) {
+ search_box->DeleteCustomLink(*rid);
+ search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_REMOVE);
+ } else {
+ search_box->DeleteMostVisitedItem(*rid);
+ }
+}
+
+// static
+void NewTabPageBindings::UndoAllMostVisitedDeletions() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->UndoAllMostVisitedDeletions();
+}
+
+// static
+void NewTabPageBindings::UndoMostVisitedDeletion(
+ v8::Isolate* isolate,
+ v8::Local<v8::Value> rid_value) {
+ // Manually convert to integer, so that the string "\"1\"" is also accepted.
+ base::Optional<int> rid = CoerceToInt(isolate, *rid_value);
+ if (!rid.has_value())
+ return;
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+
+ search_box->UndoMostVisitedDeletion(*rid);
+}
+
+// static
+v8::Local<v8::Value> NewTabPageBindings::GetMostVisitedItemData(
+ v8::Isolate* isolate,
+ int rid) {
+ const SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return v8::Null(isolate);
+
+ InstantMostVisitedItem item;
+ if (!search_box->GetMostVisitedItemWithID(rid, &item))
+ return v8::Null(isolate);
+
+ int render_view_id =
+ GetMainRenderFrameForCurrentContext()->GetRenderView()->GetRoutingID();
+ return GenerateMostVisitedItemData(isolate, render_view_id, rid, item);
+}
+
+// static
+void NewTabPageBindings::UpdateCustomLink(int rid,
+ const std::string& url,
+ const std::string& title) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return;
+
+ // Limit the title to |kMaxCustomLinkTitleLength| characters. If truncated,
+ // adds an ellipsis.
+ base::string16 truncated_title =
+ gfx::TruncateString(base::UTF8ToUTF16(title), kMaxCustomLinkTitleLength,
+ gfx::CHARACTER_BREAK);
+
+ const GURL gurl(url);
+ // If rid is -1, adds a new link. Otherwise, updates the existing link
+ // indicated by the rid (empty fields will passed as empty strings). This will
+ // initialize custom links if they have not already been initialized.
+ if (rid == -1) {
+ if (!gurl.is_valid() || truncated_title.empty())
+ return;
+ search_box->AddCustomLink(gurl, base::UTF16ToUTF8(truncated_title));
+ search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_ADD);
+ } else {
+ // Check that the URL, if provided, is valid.
+ if (!url.empty() && !gurl.is_valid())
+ return;
+ search_box->UpdateCustomLink(rid, gurl, base::UTF16ToUTF8(truncated_title));
+ search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_UPDATE);
+ }
+}
+
+// static
+void NewTabPageBindings::ReorderCustomLink(int rid, int new_pos) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return;
+ search_box->ReorderCustomLink(rid, new_pos);
+}
+
+// static
+void NewTabPageBindings::UndoCustomLinkAction() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->UndoCustomLinkAction();
+ search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_UNDO);
+}
+
+// static
+void NewTabPageBindings::ResetCustomLinks() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->ResetCustomLinks();
+ search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_RESTORE_ALL);
+}
+
+// static
+void NewTabPageBindings::ToggleMostVisitedOrCustomLinks() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->ToggleMostVisitedOrCustomLinks();
+ search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_TOGGLE_TYPE);
+}
+
+// static
+void NewTabPageBindings::ToggleShortcutsVisibility(bool do_notify) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->ToggleShortcutsVisibility(do_notify);
+ search_box->LogEvent(
+ NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_TOGGLE_VISIBILITY);
+}
+
+// static
+std::string NewTabPageBindings::FixupAndValidateUrl(const std::string& url) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return std::string();
+ return search_box->FixupAndValidateUrl(url);
+}
+
+// static
+void NewTabPageBindings::LogEvent(int event) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box) {
+ return;
+ }
+ if (event <= NTP_EVENT_TYPE_LAST)
+ search_box->LogEvent(static_cast<NTPLoggingEventType>(event));
+}
+
+// static
+void NewTabPageBindings::LogSuggestionEventWithValue(int event, int data) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box) {
+ return;
+ }
+ if (event <= static_cast<int>(NTPSuggestionsLoggingEventType::kMaxValue)) {
+ search_box->LogSuggestionEventWithValue(
+ static_cast<NTPSuggestionsLoggingEventType>(event), data);
+ }
+}
+
+// static
+void NewTabPageBindings::LogMostVisitedImpression(
+ int position,
+ int tile_title_source,
+ int tile_source,
+ int tile_type,
+ v8::Local<v8::Value> data_generation_time) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return;
+
+ if (tile_title_source <= static_cast<int>(ntp_tiles::TileTitleSource::LAST) &&
+ tile_source <= static_cast<int>(ntp_tiles::TileSource::LAST) &&
+ tile_type <= ntp_tiles::TileVisualType::TILE_TYPE_MAX) {
+ const ntp_tiles::NTPTileImpression impression(
+ position, static_cast<ntp_tiles::TileSource>(tile_source),
+ static_cast<ntp_tiles::TileTitleSource>(tile_title_source),
+ static_cast<ntp_tiles::TileVisualType>(tile_type),
+ favicon_base::IconType::kInvalid,
+ ConvertDateValueToTime(*data_generation_time),
+ /*url_for_rappor=*/GURL());
+ search_box->LogMostVisitedImpression(impression);
+ }
+}
+
+// static
+void NewTabPageBindings::LogMostVisitedNavigation(
+ int position,
+ int tile_title_source,
+ int tile_source,
+ int tile_type,
+ v8::Local<v8::Value> data_generation_time) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)))
+ return;
+
+ if (tile_title_source <= static_cast<int>(ntp_tiles::TileTitleSource::LAST) &&
+ tile_source <= static_cast<int>(ntp_tiles::TileSource::LAST) &&
+ tile_type <= ntp_tiles::TileVisualType::TILE_TYPE_MAX) {
+ const ntp_tiles::NTPTileImpression impression(
+ position, static_cast<ntp_tiles::TileSource>(tile_source),
+ static_cast<ntp_tiles::TileTitleSource>(tile_title_source),
+ static_cast<ntp_tiles::TileVisualType>(tile_type),
+ favicon_base::IconType::kInvalid,
+ ConvertDateValueToTime(*data_generation_time),
+ /*url_for_rappor=*/GURL());
+ search_box->LogMostVisitedNavigation(impression);
+ }
+}
+
+// static
+void NewTabPageBindings::ResetCustomBackgroundInfo() {
+ SetCustomBackgroundInfo(std::string(), std::string(), std::string(),
+ std::string(), std::string());
+}
+
+// static
+void NewTabPageBindings::SetCustomBackgroundInfo(
+ const std::string& background_url,
+ const std::string& attribution_line_1,
+ const std::string& attribution_line_2,
+ const std::string& attribution_action_url,
+ const std::string& collection_id) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ search_box->SetCustomBackgroundInfo(
+ GURL(background_url), attribution_line_1, attribution_line_2,
+ GURL(attribution_action_url), collection_id);
+ // Captures different events that occur when a background selection is made
+ // and 'Done' is clicked on the dialog.
+ if (!collection_id.empty()) {
+ search_box->LogEvent(
+ NTPLoggingEventType::NTP_BACKGROUND_DAILY_REFRESH_ENABLED);
+ } else if (background_url.empty()) {
+ search_box->LogEvent(
+ NTPLoggingEventType::NTP_CUSTOMIZE_RESTORE_BACKGROUND_CLICKED);
+ search_box->LogEvent(NTPLoggingEventType::NTP_BACKGROUND_IMAGE_RESET);
+ } else {
+ search_box->LogEvent(
+ NTPLoggingEventType::NTP_CUSTOMIZE_CHROME_BACKGROUND_DONE);
+ search_box->LogEvent(NTPLoggingEventType::NTP_BACKGROUND_IMAGE_SET);
+ }
+}
+
+// static
+void NewTabPageBindings::SelectLocalBackgroundImage() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ search_box->SelectLocalBackgroundImage();
+}
+
+// static
+void NewTabPageBindings::BlocklistSearchSuggestion(const int task_version,
+ const int task_id) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->BlocklistSearchSuggestion(task_version, task_id);
+}
+
+// static
+void NewTabPageBindings::BlocklistSearchSuggestionWithHash(
+ int task_version,
+ int task_id,
+ const std::string& hash) {
+ if (hash.length() != 4) {
+ return;
+ }
+
+ std::vector<uint8_t> data(hash.begin(), hash.end());
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->BlocklistSearchSuggestionWithHash(task_version, task_id, data);
+}
+
+// static
+void NewTabPageBindings::SearchSuggestionSelected(int task_version,
+ int task_id,
+ const std::string& hash) {
+ if (hash.length() > 4) {
+ return;
+ }
+
+ std::vector<uint8_t> data(hash.begin(), hash.end());
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->SearchSuggestionSelected(task_version, task_id, data);
+}
+
+// static
+void NewTabPageBindings::OptOutOfSearchSuggestions() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->OptOutOfSearchSuggestions();
+}
+
+// static
+void NewTabPageBindings::ApplyDefaultTheme() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->ApplyDefaultTheme();
+ content::RenderThread::Get()->RecordAction(
+ base::UserMetricsAction("ChromeColors_DefaultApplied"));
+}
+
+// static
+void NewTabPageBindings::UseDefaultTheme() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->ApplyDefaultTheme();
+ search_box->ConfirmThemeChanges();
+ content::RenderThread::Get()->RecordAction(
+ base::UserMetricsAction("ChromeColors_ThemeUninstalled"));
+}
+
+// static
+void NewTabPageBindings::ApplyAutogeneratedTheme(v8::Isolate* isolate,
+ int id,
+ v8::Local<v8::Value> value) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box || !value->IsArray())
+ return;
+ SkColor color;
+ if (ArrayToSkColor(isolate, value.As<v8::Array>(), &color)) {
+ search_box->ApplyAutogeneratedTheme(color);
+ content::RenderThread::Get()->RecordAction(
+ base::UserMetricsAction("ChromeColors_ColorApplied"));
+ if (id > 0 && id < static_cast<int>(chrome_colors::kNumColorsInfo)) {
+ UMA_HISTOGRAM_ENUMERATION("ChromeColors.AppliedColor", id,
+ chrome_colors::kNumColorsInfo);
+ }
+ }
+}
+
+// static
+void NewTabPageBindings::RevertThemeChanges() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->RevertThemeChanges();
+}
+
+// static
+void NewTabPageBindings::ConfirmThemeChanges() {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->ConfirmThemeChanges();
+}
+
+v8::Local<v8::Value> NewTabPageBindings::GetColorsInfo(v8::Isolate* isolate) {
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ v8::Local<v8::Object> v8_colors =
+ v8::Array::New(isolate, chrome_colors::kNumColorsInfo);
+ int i = 0;
+ for (chrome_colors::ColorInfo color_info :
+ chrome_colors::kGeneratedColorsInfo) {
+ v8::Local<v8::Object> v8_color_info =
+ gin::DataObjectBuilder(isolate)
+ .Set("id", color_info.id)
+ .Set("color", SkColorToArray(isolate, color_info.color))
+ .Set("label", l10n_util::GetStringUTF16(color_info.label_id))
+ .Set("icon", std::string(color_info.icon_data))
+ .Build();
+ v8_colors->CreateDataProperty(context, i++, v8_color_info).Check();
+ }
+ return v8_colors;
+}
+
+void NewTabPageBindings::BlocklistPromo(const std::string& promo_id) {
+ SearchBox* search_box = GetSearchBoxForCurrentContext();
+ if (!search_box)
+ return;
+ search_box->BlocklistPromo(promo_id);
+}
+
+} // namespace
+
+// static
+void SearchBoxExtension::Install(blink::WebLocalFrame* frame) {
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = frame->MainWorldScriptContext();
+ if (context.IsEmpty())
+ return;
+
+ v8::Context::Scope context_scope(context);
+
+ gin::Handle<SearchBoxBindings> searchbox_controller =
+ gin::CreateHandle(isolate, new SearchBoxBindings());
+ if (searchbox_controller.IsEmpty())
+ return;
+
+ gin::Handle<NewTabPageBindings> newtabpage_controller =
+ gin::CreateHandle(isolate, new NewTabPageBindings());
+ if (newtabpage_controller.IsEmpty())
+ return;
+
+ v8::Local<v8::Object> chrome =
+ content::GetOrCreateChromeObject(isolate, context);
+ v8::Local<v8::Object> embedded_search = v8::Object::New(isolate);
+ embedded_search
+ ->Set(context, gin::StringToV8(isolate, "searchBox"),
+ searchbox_controller.ToV8())
+ .ToChecked();
+ embedded_search
+ ->Set(context, gin::StringToV8(isolate, "newTabPage"),
+ newtabpage_controller.ToV8())
+ .ToChecked();
+ chrome
+ ->Set(context, gin::StringToSymbol(isolate, "embeddedSearch"),
+ embedded_search)
+ .ToChecked();
+}
+
+// static
+void SearchBoxExtension::DispatchFocusChange(blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchFocusChangedScript);
+}
+
+// static
+void SearchBoxExtension::DispatchAddCustomLinkResult(
+ blink::WebLocalFrame* frame,
+ bool success) {
+ blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf(
+ kDispatchAddCustomLinkResult, success ? "true" : "false")));
+ Dispatch(frame, script);
+}
+
+// static
+void SearchBoxExtension::DispatchUpdateCustomLinkResult(
+ blink::WebLocalFrame* frame,
+ bool success) {
+ blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf(
+ kDispatchUpdateCustomLinkResult, success ? "true" : "false")));
+ Dispatch(frame, script);
+}
+
+// static
+void SearchBoxExtension::DispatchDeleteCustomLinkResult(
+ blink::WebLocalFrame* frame,
+ bool success) {
+ blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf(
+ kDispatchDeleteCustomLinkResult, success ? "true" : "false")));
+ Dispatch(frame, script);
+}
+
+void SearchBoxExtension::DispatchQueryAutocompleteResult(
+ blink::WebLocalFrame* frame,
+ chrome::mojom::AutocompleteResultPtr result) {
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetStringKey("input", result->input);
+ dict.SetDoubleKey("status", static_cast<double>(result->status));
+ dict.SetKey("matches", CreateAutocompleteMatches(result->matches));
+
+ std::string json;
+ base::JSONWriter::Write(dict, &json);
+ Dispatch(frame, blink::WebString::FromUTF8(base::StringPrintf(
+ kDispatchQueryAutocompleteResult, json.c_str())));
+}
+
+void SearchBoxExtension::DispatchDeleteAutocompleteMatchResult(
+ blink::WebLocalFrame* frame,
+ chrome::mojom::DeleteAutocompleteMatchResultPtr result) {
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetBoolKey("success", result->success);
+ dict.SetKey("matches", CreateAutocompleteMatches(result->matches));
+
+ std::string json;
+ base::JSONWriter::Write(dict, &json);
+ Dispatch(frame, blink::WebString::FromUTF8(base::StringPrintf(
+ kDispatchDeleteAutocompleteMatchResult, json.c_str())));
+}
+
+// static
+void SearchBoxExtension::DispatchInputCancel(blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchInputCancelScript);
+}
+
+// static
+void SearchBoxExtension::DispatchInputStart(blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchInputStartScript);
+}
+
+// static
+void SearchBoxExtension::DispatchKeyCaptureChange(blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchKeyCaptureChangeScript);
+}
+
+// static
+void SearchBoxExtension::DispatchMostVisitedChanged(
+ blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchMostVisitedChangedScript);
+}
+
+// static
+void SearchBoxExtension::DispatchThemeChange(blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchThemeChangeEventScript);
+}
+
+// static
+void SearchBoxExtension::DispatchLocalBackgroundSelected(
+ blink::WebLocalFrame* frame) {
+ Dispatch(frame, kDispatchLocalBackgroundSelectedScript);
+}
diff --git a/chromium/chrome/renderer/searchbox/searchbox_extension.h b/chromium/chrome/renderer/searchbox/searchbox_extension.h
new file mode 100644
index 00000000000..5d2052e21ec
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/searchbox_extension.h
@@ -0,0 +1,57 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SEARCHBOX_SEARCHBOX_EXTENSION_H_
+#define CHROME_RENDERER_SEARCHBOX_SEARCHBOX_EXTENSION_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "chrome/common/search.mojom.h"
+#include "ui/gfx/color_palette.h"
+
+namespace blink {
+class WebLocalFrame;
+}
+
+constexpr SkColor kNTPLightLogoColor = SkColorSetRGB(238, 238, 238);
+constexpr SkColor kNTPLightIconColor = gfx::kGoogleGrey100;
+constexpr SkColor kNTPDarkIconColor = gfx::kGoogleGrey900;
+
+// Javascript bindings for the chrome.embeddedSearch APIs. See
+// https://www.chromium.org/embeddedsearch.
+class SearchBoxExtension {
+ public:
+ static void Install(blink::WebLocalFrame* frame);
+
+ // Helpers to dispatch Javascript events.
+ static void DispatchChromeIdentityCheckResult(blink::WebLocalFrame* frame,
+ const base::string16& identity,
+ bool identity_match);
+ static void DispatchFocusChange(blink::WebLocalFrame* frame);
+ static void DispatchAddCustomLinkResult(blink::WebLocalFrame* frame,
+ bool success);
+ static void DispatchUpdateCustomLinkResult(blink::WebLocalFrame* frame,
+ bool success);
+ static void DispatchDeleteCustomLinkResult(blink::WebLocalFrame* frame,
+ bool success);
+ static void DispatchQueryAutocompleteResult(
+ blink::WebLocalFrame* frame,
+ chrome::mojom::AutocompleteResultPtr result);
+ static void DispatchDeleteAutocompleteMatchResult(
+ blink::WebLocalFrame* frame,
+ chrome::mojom::DeleteAutocompleteMatchResultPtr result);
+ static void DispatchInputCancel(blink::WebLocalFrame* frame);
+ static void DispatchInputStart(blink::WebLocalFrame* frame);
+ static void DispatchKeyCaptureChange(blink::WebLocalFrame* frame);
+ static void DispatchMostVisitedChanged(blink::WebLocalFrame* frame);
+ static void DispatchThemeChange(blink::WebLocalFrame* frame);
+ static void DispatchLocalBackgroundSelected(blink::WebLocalFrame* frame);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(SearchBoxExtension);
+};
+
+#endif // CHROME_RENDERER_SEARCHBOX_SEARCHBOX_EXTENSION_H_
diff --git a/chromium/chrome/renderer/searchbox/searchbox_unittest.cc b/chromium/chrome/renderer/searchbox/searchbox_unittest.cc
new file mode 100644
index 00000000000..747fe743cc5
--- /dev/null
+++ b/chromium/chrome/renderer/searchbox/searchbox_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/searchbox/searchbox.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+
+#include "base/stl_util.h"
+#include "chrome/common/search/instant_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char* kUrlString1 = "http://www.google.com";
+const char* kUrlString2 = "http://www.chromium.org/path/q=3#r=4";
+const char* kUrlString3 = "http://www.youtube.com:8080/hosps";
+
+// Mock helper to test internal::TranslateIconRestrictedUrl().
+class MockIconURLHelper: public SearchBox::IconURLHelper {
+ public:
+ MockIconURLHelper();
+ ~MockIconURLHelper() override;
+ int GetViewID() const override;
+ std::string GetURLStringFromRestrictedID(InstantRestrictedID rid) const
+ override;
+
+ private:
+ std::map<InstantRestrictedID, std::string> rid_to_url_string_;
+};
+
+MockIconURLHelper::MockIconURLHelper() {
+ rid_to_url_string_[1] = kUrlString1;
+ rid_to_url_string_[2] = kUrlString2;
+ rid_to_url_string_[3] = kUrlString3;
+}
+
+MockIconURLHelper::~MockIconURLHelper() {
+}
+
+int MockIconURLHelper::GetViewID() const {
+ return 137;
+}
+
+std::string MockIconURLHelper::GetURLStringFromRestrictedID(
+ InstantRestrictedID rid) const {
+ auto it = rid_to_url_string_.find(rid);
+ return it == rid_to_url_string_.end() ? std::string() : it->second;
+}
+
+} // namespace
+
+namespace internal {
+
+// Defined in searchbox.cc
+bool ParseViewIdAndRestrictedId(const std::string& id_part,
+ int* view_id_out,
+ InstantRestrictedID* rid_out);
+
+// Defined in searchbox.cc
+bool ParseIconRestrictedUrl(const GURL& url,
+ std::string* param_part,
+ int* view_id,
+ InstantRestrictedID* rid);
+
+// Defined in searchbox.cc
+void TranslateIconRestrictedUrl(const GURL& transient_url,
+ const SearchBox::IconURLHelper& helper,
+ GURL* url);
+
+// Defined in searchbox.cc
+std::string FixupAndValidateUrl(const std::string& url);
+
+TEST(SearchBoxUtilTest, ParseViewIdAndRestrictedIdSuccess) {
+ int view_id = -1;
+ InstantRestrictedID rid = -1;
+
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("2/3", &view_id, &rid));
+ EXPECT_EQ(2, view_id);
+ EXPECT_EQ(3, rid);
+
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("0/0", &view_id, &rid));
+ EXPECT_EQ(0, view_id);
+ EXPECT_EQ(0, rid);
+
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("1048576/314", &view_id, &rid));
+ EXPECT_EQ(1048576, view_id);
+ EXPECT_EQ(314, rid);
+
+ // Odd but not fatal.
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("00/09", &view_id, &rid));
+ EXPECT_EQ(0, view_id);
+ EXPECT_EQ(9, rid);
+
+ // Tolerates multiple, leading, and trailing "/".
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("2////3", &view_id, &rid));
+ EXPECT_EQ(2, view_id);
+ EXPECT_EQ(3, rid);
+
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("5/6/", &view_id, &rid));
+ EXPECT_EQ(5, view_id);
+ EXPECT_EQ(6, rid);
+
+ EXPECT_TRUE(ParseViewIdAndRestrictedId("/7/8", &view_id, &rid));
+ EXPECT_EQ(7, view_id);
+ EXPECT_EQ(8, rid);
+}
+
+TEST(SearchBoxUtilTest, ParseViewIdAndRestrictedIdFailure) {
+ const char* test_cases[] = {
+ "",
+ " ",
+ "/",
+ "2/",
+ "/3",
+ "2a/3",
+ "2/3a",
+ " 2/3",
+ "2/ 3",
+ "2 /3 ",
+ "23",
+ "2,3",
+ "-2/3",
+ "2/-3",
+ "2/3/1",
+ "blahblah",
+ "0xA/0x10",
+ };
+ for (size_t i = 0; i < base::size(test_cases); ++i) {
+ int view_id = -1;
+ InstantRestrictedID rid = -1;
+ EXPECT_FALSE(ParseViewIdAndRestrictedId(test_cases[i], &view_id, &rid))
+ << " for test_cases[" << i << "]";
+ EXPECT_EQ(-1, view_id);
+ EXPECT_EQ(-1, rid);
+ }
+}
+
+TEST(SearchBoxUtilTest, ParseIconRestrictedUrlFaviconSuccess) {
+ struct {
+ const char* transient_url_str;
+ const char* expected_param_part;
+ int expected_view_id;
+ InstantRestrictedID expected_rid;
+ } test_cases[] = {
+ {"chrome-search://favicon/1/2", "", 1, 2},
+ {"chrome-search://favicon/size/16@2x/3/4", "size/16@2x/", 3, 4},
+ {"chrome-search://favicon/iconurl/9/10", "iconurl/", 9, 10},
+ };
+ for (size_t i = 0; i < base::size(test_cases); ++i) {
+ std::string param_part = "(unwritten)";
+ int view_id = -1;
+ InstantRestrictedID rid = -1;
+ EXPECT_TRUE(ParseIconRestrictedUrl(GURL(test_cases[i].transient_url_str),
+ &param_part, &view_id, &rid))
+ << " for test_cases[" << i << "]";
+ EXPECT_EQ(test_cases[i].expected_param_part, param_part)
+ << " for test_cases[" << i << "]";
+ EXPECT_EQ(test_cases[i].expected_view_id, view_id)
+ << " for test_cases[" << i << "]";
+ EXPECT_EQ(test_cases[i].expected_rid, rid)
+ << " for test_cases[" << i << "]";
+ }
+}
+
+TEST(SearchBoxUtilTest, ParseIconRestrictedUrlFailure) {
+ struct {
+ const char* transient_url_str;
+ } test_cases[] = {
+ {"chrome-search://favicon/"},
+ {"chrome-search://favicon/3/"},
+ {"chrome-search://favicon/size/3/4"},
+ {"chrome-search://favicon/largest/http://www.google.com"},
+ {"chrome-search://favicon/size/16@2x/-1/10"},
+ };
+ for (size_t i = 0; i < base::size(test_cases); ++i) {
+ std::string param_part = "(unwritten)";
+ int view_id = -1;
+ InstantRestrictedID rid = -1;
+ EXPECT_FALSE(ParseIconRestrictedUrl(GURL(test_cases[i].transient_url_str),
+ &param_part, &view_id, &rid))
+ << " for test_cases[" << i << "]";
+ EXPECT_EQ("(unwritten)", param_part);
+ EXPECT_EQ(-1, view_id);
+ EXPECT_EQ(-1, rid);
+ }
+}
+
+TEST(SearchBoxUtilTest, TranslateIconRestrictedUrlSuccess) {
+ struct {
+ const char* transient_url_str;
+ std::string expected_url_str;
+ } test_cases[] = {
+ {"chrome-search://favicon/137/1",
+ std::string("chrome-search://favicon/") + kUrlString1},
+ {"chrome-search://favicon/", "chrome-search://favicon/"},
+ {"chrome-search://favicon/314", "chrome-search://favicon/"},
+ {"chrome-search://favicon/314/1", "chrome-search://favicon/"},
+ {"chrome-search://favicon/137/255", "chrome-search://favicon/"},
+ {"chrome-search://favicon/-3/-1", "chrome-search://favicon/"},
+ {"chrome-search://favicon/invalidstuff", "chrome-search://favicon/"},
+ {"chrome-search://favicon/size/16@2x/http://www.google.com",
+ "chrome-search://favicon/"},
+ };
+
+ MockIconURLHelper helper;
+ for (size_t i = 0; i < base::size(test_cases); ++i) {
+ GURL url;
+ GURL transient_url(test_cases[i].transient_url_str);
+ TranslateIconRestrictedUrl(transient_url, helper, &url);
+ EXPECT_EQ(GURL(test_cases[i].expected_url_str), url)
+ << " for test_cases[" << i << "]";
+ }
+}
+
+TEST(SearchBoxUtilTest, FixupAndValidateUrlReturnsEmptyIfInvalid) {
+ struct TestCase {
+ const char* url;
+ bool is_valid;
+ } test_cases[] = {
+ {" ", false},
+ {"^&*@)^)", false},
+ {"foo", true},
+ {"http://foo", true},
+ {"\thttp://foo", true},
+ {" http://foo", true},
+ {"https://foo", true},
+ {"foo.com", true},
+ {"http://foo.com", true},
+ {"https://foo.com", true},
+ {"blob://foo", true},
+
+ };
+
+ for (const TestCase& test_case : test_cases) {
+ const std::string& url = FixupAndValidateUrl(test_case.url);
+ EXPECT_EQ(!url.empty(), test_case.is_valid)
+ << " for test_case '" << test_case.url << "'";
+ }
+}
+
+TEST(SearchBoxUtilTest, FixupAndValidateUrlDefaultsToHttps) {
+ struct TestCase {
+ const char* url;
+ const char* expected_scheme;
+ } test_cases[] = {
+ // No scheme.
+ {"foo.com", url::kHttpsScheme},
+ // With "http".
+ {"http://foo.com", url::kHttpScheme},
+ // With "http" and whitespace.
+ {"\thttp://foo", url::kHttpScheme},
+ {" http://foo", url::kHttpScheme},
+ // With "https".
+ {"https://foo.com", url::kHttpsScheme},
+ // Non "http"/"https".
+ {"blob://foo", url::kBlobScheme},
+ };
+
+ for (const TestCase& test_case : test_cases) {
+ const GURL url(FixupAndValidateUrl(test_case.url));
+ EXPECT_TRUE(url.SchemeIs(test_case.expected_scheme))
+ << " for test case '" << test_case.url << "'";
+ }
+}
+
+} // namespace internal
diff --git a/chromium/chrome/renderer/subresource_redirect/DEPS b/chromium/chrome/renderer/subresource_redirect/DEPS
new file mode 100644
index 00000000000..a7c8042ecab
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/base32",
+]
diff --git a/chromium/chrome/renderer/subresource_redirect/OWNERS b/chromium/chrome/renderer/subresource_redirect/OWNERS
new file mode 100644
index 00000000000..b3958dcfa85
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/OWNERS
@@ -0,0 +1,4 @@
+tbansal@chromium.org
+robertogden@chromium.org
+
+# COMPONENT: Internals>Network>DataProxy \ No newline at end of file
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.cc
new file mode 100644
index 00000000000..fca2f593fcc
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.cc
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_experiments.h"
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/strings/string_split.h"
+#include "chrome/common/chrome_features.h"
+
+namespace subresource_redirect {
+
+bool ShouldIncludeMediaSuffix(const GURL& url) {
+ if (!base::FeatureList::IsEnabled(
+ features::kSubresourceRedirectIncludedMediaSuffixes))
+ return true;
+
+ std::vector<std::string> suffixes = {".jpg", ".jpeg", ".png", ".svg",
+ ".webp"};
+
+ std::string csv = base::GetFieldTrialParamValueByFeature(
+ features::kSubresourceRedirectIncludedMediaSuffixes,
+ "included_path_suffixes");
+ if (csv != "") {
+ suffixes = base::SplitString(csv, ",", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ }
+
+ for (const std::string& suffix : suffixes) {
+ if (base::EndsWith(url.path(), suffix,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.h b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.h
new file mode 100644
index 00000000000..5b13de439e8
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments.h
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_EXPERIMENTS_H_
+#define CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_EXPERIMENTS_H_
+
+#include "url/gurl.h"
+
+namespace subresource_redirect {
+
+// Returns true if the given url matches an included media suffix.
+bool ShouldIncludeMediaSuffix(const GURL& url);
+
+} // namespace subresource_redirect
+
+#endif // CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_EXPERIMENTS_H_
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments_unittest.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments_unittest.cc
new file mode 100644
index 00000000000..99c66e146a5
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_experiments_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_experiments.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/common/chrome_features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace subresource_redirect {
+
+namespace {
+
+TEST(SubresourceRedirectExperimentsTest, TestDefaultShouldIncludeMediaSuffix) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ features::kSubresourceRedirectIncludedMediaSuffixes);
+
+ EXPECT_FALSE(ShouldIncludeMediaSuffix(GURL("http://chromium.org/path/")));
+
+ std::vector<std::string> default_suffixes = {".jpg", ".jpeg", ".png", ".svg",
+ ".webp"};
+ for (const std::string& suffix : default_suffixes) {
+ GURL url("http://chromium.org/image" + suffix);
+ EXPECT_TRUE(ShouldIncludeMediaSuffix(url));
+ }
+}
+
+TEST(SubresourceRedirectExperimentsTest, TestShouldIncludeMediaSuffix) {
+ struct TestCase {
+ std::string msg;
+ bool enable_feature;
+ std::string varaiation_value;
+ std::vector<std::string> urls;
+ bool want_return;
+ };
+ const TestCase kTestCases[]{
+ {
+ .msg = "Feature disabled, should always return true",
+ .enable_feature = false,
+ .varaiation_value = "",
+ .urls = {"http://chromium.org/image.jpg"},
+ .want_return = true,
+ },
+ {
+ .msg = "Default values are overridden by variations",
+ .enable_feature = true,
+ .varaiation_value = ".html",
+ .urls = {"http://chromium.org/image.jpeg",
+ "http://chromium.org/image.png",
+ "http://chromium.org/image.jpg",
+ "http://chromium.org/image.svg"},
+ .want_return = false,
+ },
+ {
+ .msg = "Variation value whitespace should be trimmed",
+ .enable_feature = true,
+ .varaiation_value = " .svg , \t .png\n",
+ .urls = {"http://chromium.org/image.svg",
+ "http://chromium.org/image.png"},
+ .want_return = true,
+ },
+ {
+ .msg = "Variation value empty values should be excluded",
+ .enable_feature = true,
+ .varaiation_value = ".svg,,.png,",
+ .urls = {"http://chromium.org/image.svg",
+ "http://chromium.org/image.png"},
+ .want_return = true,
+ },
+ {
+ .msg = "URLs should be compared case insensitive",
+ .enable_feature = true,
+ .varaiation_value = ".svg,.png,",
+ .urls = {"http://chromium.org/image.SvG",
+ "http://chromium.org/image.PNG"},
+ .want_return = true,
+ },
+ {
+ .msg = "Query params and fragments don't matter",
+ .enable_feature = true,
+ .varaiation_value = ".svg,.png,",
+ .urls = {"http://chromium.org/image.svg?hello=world",
+ "http://chromium.org/image.png#test"},
+ .want_return = true,
+ },
+ {
+ .msg = "Query params and fragments shouldn't be considered",
+ .enable_feature = true,
+ .varaiation_value = ".svg,.png,",
+ .urls = {"http://chromium.org/?image=image.svg",
+ "http://chromium.org/#image.png"},
+ .want_return = false,
+ },
+ };
+ for (const TestCase& test_case : kTestCases) {
+ SCOPED_TRACE(test_case.msg);
+
+ base::test::ScopedFeatureList scoped_feature_list;
+ if (test_case.enable_feature) {
+ scoped_feature_list.InitAndEnableFeatureWithParameters(
+ features::kSubresourceRedirectIncludedMediaSuffixes,
+ {{"included_path_suffixes", test_case.varaiation_value}});
+ } else {
+ scoped_feature_list.InitAndDisableFeature(
+ features::kSubresourceRedirectIncludedMediaSuffixes);
+ }
+
+ for (const std::string& url : test_case.urls) {
+ EXPECT_EQ(test_case.want_return, ShouldIncludeMediaSuffix(GURL(url)));
+ }
+ }
+}
+
+} // namespace
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.cc
new file mode 100644
index 00000000000..a88bd5ceaad
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
+
+#include "base/command_line.h"
+#include "content/public/common/content_switches.h"
+
+namespace subresource_redirect {
+
+bool ShouldForceEnableSubresourceRedirect() {
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableSubresourceRedirect);
+}
+
+std::string LitePageSubresourceHost() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kLitePagesServerSubresourceHost)) {
+ return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kLitePagesServerSubresourceHost);
+ }
+ return "https://litepages.googlezip.net/";
+}
+
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.h b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.h
new file mode 100644
index 00000000000..b2555848f27
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_params.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_PARAMS_H_
+#define CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_PARAMS_H_
+
+#include <string>
+
+namespace subresource_redirect {
+
+// Returns true if Subresource Redirect is forced to be enabled from the
+// command line.
+bool ShouldForceEnableSubresourceRedirect();
+
+// Returns kLitePagesServerSubresourceHost if set, else returns the default.
+std::string LitePageSubresourceHost();
+
+} // namespace subresource_redirect
+
+#endif // CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_PARAMS_H_
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc
new file mode 100644
index 00000000000..1444e043051
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc
@@ -0,0 +1,143 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "chrome/renderer/subresource_redirect/subresource_redirect_experiments.h"
+#include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
+#include "content/public/common/resource_type.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/resource_response.h"
+
+namespace subresource_redirect {
+
+SubresourceRedirectURLLoaderThrottle::SubresourceRedirectURLLoaderThrottle() =
+ default;
+SubresourceRedirectURLLoaderThrottle::~SubresourceRedirectURLLoaderThrottle() =
+ default;
+
+void SubresourceRedirectURLLoaderThrottle::WillStartRequest(
+ network::ResourceRequest* request,
+ bool* defer) {
+ DCHECK_EQ(request->resource_type,
+ static_cast<int>(content::ResourceType::kImage));
+
+ DCHECK(request->url.SchemeIs(url::kHttpsScheme));
+
+ // Image subresources that have paths that do not end in one of the
+ // following common formats are commonly single pixel images that will not
+ // benefit from being sent to the compression server.
+ if (!ShouldIncludeMediaSuffix(request->url))
+ return;
+
+ request->url = GetSubresourceURLForURL(request->url);
+ *defer = false;
+}
+
+void SubresourceRedirectURLLoaderThrottle::WillRedirectRequest(
+ net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* defer,
+ std::vector<std::string>* to_be_removed_request_headers,
+ net::HttpRequestHeaders* modified_request_headers) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SubresourceRedirect.CompressionAttempt.ResponseCode",
+ static_cast<net::HttpStatusCode>(response_head.headers->response_code()),
+ net::HTTP_VERSION_NOT_SUPPORTED);
+}
+
+void SubresourceRedirectURLLoaderThrottle::BeforeWillProcessResponse(
+ const GURL& response_url,
+ const network::ResourceResponseHead& response_head,
+ bool* defer) {
+ // If response was not from the compression server, don't restart it.
+ if (!response_url.is_valid())
+ return;
+ GURL compression_server = GetLitePageSubresourceDomainURL();
+ if (!response_url.DomainIs(compression_server.host()))
+ return;
+ if (response_url.EffectiveIntPort() != compression_server.EffectiveIntPort())
+ return;
+ if (response_url.scheme() != compression_server.scheme())
+ return;
+
+ // Log all response codes, from the compression server.
+ UMA_HISTOGRAM_ENUMERATION(
+ "SubresourceRedirect.CompressionAttempt.ResponseCode",
+ static_cast<net::HttpStatusCode>(response_head.headers->response_code()),
+ net::HTTP_VERSION_NOT_SUPPORTED);
+
+ // Do nothing with 2XX responses, as these requests were handled
+ // correctly by the compression server.
+ if ((response_head.headers->response_code() >= 200 &&
+ response_head.headers->response_code() <= 299) ||
+ response_head.headers->response_code() == 304) {
+ return;
+ }
+
+ // Non 2XX responses from the compression server need to have unaltered
+ // requests sent to the original resource.
+ delegate_->RestartWithURLResetAndFlags(net::LOAD_NORMAL);
+}
+
+void SubresourceRedirectURLLoaderThrottle::WillProcessResponse(
+ const GURL& response_url,
+ network::ResourceResponseHead* response_head,
+ bool* defer) {
+ // If response was not from the compression server, don't record any metrics.
+ if (!response_url.is_valid())
+ return;
+ GURL compression_server = GetLitePageSubresourceDomainURL();
+ if (!response_url.DomainIs(compression_server.host()))
+ return;
+ if (response_url.EffectiveIntPort() != compression_server.EffectiveIntPort())
+ return;
+ if (response_url.scheme() != compression_server.scheme())
+ return;
+
+ // Record that the server responded.
+ UMA_HISTOGRAM_BOOLEAN(
+ "SubresourceRedirect.CompressionAttempt.ServerResponded", true);
+
+ // If compression was unsuccessful don't try and record compression percent.
+ if (response_head->headers->response_code() != 200)
+ return;
+
+ float content_length =
+ static_cast<float>(response_head->headers->GetContentLength());
+
+ float ofcl =
+ static_cast<float>(data_reduction_proxy::GetDataReductionProxyOFCL(
+ response_head->headers.get()));
+
+ // If either |content_length| or |ofcl| are missing don't compute compression
+ // percent.
+ if (content_length < 0.0 || ofcl <= 0.0)
+ return;
+
+ UMA_HISTOGRAM_PERCENTAGE(
+ "SubresourceRedirect.DidCompress.CompressionPercent",
+ static_cast<int>(100 - ((content_length / ofcl) * 100)));
+
+ UMA_HISTOGRAM_COUNTS_1M("SubresourceRedirect.DidCompress.BytesSaved",
+ static_cast<int>(ofcl - content_length));
+}
+
+void SubresourceRedirectURLLoaderThrottle::WillOnCompleteWithError(
+ const network::URLLoaderCompletionStatus& status,
+ bool* defer) {
+ // If the server fails, restart the request to the original resource, and
+ // record it.
+ delegate_->RestartWithURLResetAndFlags(net::LOAD_NORMAL);
+ UMA_HISTOGRAM_BOOLEAN(
+ "SubresourceRedirect.CompressionAttempt.ServerResponded", false);
+}
+
+void SubresourceRedirectURLLoaderThrottle::DetachFromCurrentSequence() {}
+
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h
new file mode 100644
index 00000000000..50cf816c0c5
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_URL_LOADER_THROTTLE_H_
+#define CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_URL_LOADER_THROTTLE_H_
+
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+
+namespace subresource_redirect {
+
+// This class handles internal redirects for subresouces on HTTPS sites to
+// compressed versions of subresources.
+class SubresourceRedirectURLLoaderThrottle : public blink::URLLoaderThrottle {
+ public:
+ SubresourceRedirectURLLoaderThrottle();
+ ~SubresourceRedirectURLLoaderThrottle() override;
+
+ // blink::URLLoaderThrottle:
+ void WillStartRequest(network::ResourceRequest* request,
+ bool* defer) override;
+ void WillRedirectRequest(
+ net::RedirectInfo* redirect_info,
+ const network::ResourceResponseHead& response_head,
+ bool* defer,
+ std::vector<std::string>* to_be_removed_request_headers,
+ net::HttpRequestHeaders* modified_request_headers) override;
+ void BeforeWillProcessResponse(
+ const GURL& response_url,
+ const network::ResourceResponseHead& response_head,
+ bool* defer) override;
+ void WillProcessResponse(const GURL& response_url,
+ network::ResourceResponseHead* response_head,
+ bool* defer) override;
+ void WillOnCompleteWithError(const network::URLLoaderCompletionStatus& status,
+ bool* defer) override;
+ // Overridden to do nothing as the default implementation is NOT_REACHED()
+ void DetachFromCurrentSequence() override;
+};
+
+} // namespace subresource_redirect
+
+#endif // CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_URL_LOADER_THROTTLE_H_
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle_unittest.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle_unittest.cc
new file mode 100644
index 00000000000..a45fa3a7374
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h"
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
+#include "content/public/common/resource_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace subresource_redirect {
+
+namespace {
+
+TEST(SubresourceRedirectURLLoaderThrottleTest, TestGetSubresourceURL) {
+ struct TestCase {
+ GURL original_url;
+ int resource_type;
+ GURL redirected_subresource_url;
+ };
+
+ const TestCase kTestCases[]{
+ {
+ GURL("https://www.test.com/test.jpg"),
+ static_cast<int>(content::ResourceType::kImage),
+ GetSubresourceURLForURL(GURL("https://www.test.com/test.jpg")),
+ },
+ {
+ GURL("https://www.test.com/test.jpg#test"),
+ static_cast<int>(content::ResourceType::kImage),
+ GetSubresourceURLForURL(GURL("https://www.test.com/test.jpg#test")),
+ },
+ };
+
+ for (const TestCase& test_case : kTestCases) {
+ network::ResourceRequest request;
+ request.url = test_case.original_url;
+ request.resource_type = test_case.resource_type;
+ bool defer = false;
+
+ SubresourceRedirectURLLoaderThrottle throttle;
+ throttle.WillStartRequest(&request, &defer);
+
+ EXPECT_FALSE(defer);
+ EXPECT_EQ(request.url, test_case.redirected_subresource_url);
+ }
+}
+
+TEST(SubresourceRedirectURLLoaderThrottleTest, DeferOverridenToFalse) {
+ SubresourceRedirectURLLoaderThrottle throttle;
+
+ network::ResourceRequest request;
+ request.url = GURL("https://www.test.com/test.jpg");
+ request.resource_type = static_cast<int>(content::ResourceType::kImage);
+ bool defer = true;
+
+ throttle.WillStartRequest(&request, &defer);
+ EXPECT_FALSE(defer);
+}
+
+} // namespace
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.cc
new file mode 100644
index 00000000000..5423c4fd5b6
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.cc
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/base32/base32.h"
+#include "content/public/common/content_switches.h"
+#include "crypto/sha2.h"
+#include "net/base/escape.h"
+#include "url/gurl.h"
+
+namespace subresource_redirect {
+
+GURL GetSubresourceURLForURL(const GURL& original_url) {
+ DCHECK(original_url.is_valid());
+ std::string fragment;
+ if (original_url.has_ref()) {
+ fragment = "#" + original_url.ref();
+ }
+
+ std::string origin_hash = base::ToLowerASCII(base32::Base32Encode(
+ crypto::SHA256HashString(
+ original_url.scheme() + "://" + original_url.host() + ":" +
+ base::NumberToString(original_url.EffectiveIntPort())),
+ base32::Base32EncodePolicy::OMIT_PADDING));
+ GURL subresource_host = GetLitePageSubresourceDomainURL();
+
+ GURL compressed_url(
+ subresource_host.scheme() + "://" + origin_hash + "." +
+ subresource_host.host() +
+ (subresource_host.has_port() ? (":" + subresource_host.port()) : "") +
+ "/i?u=" +
+ // Strip out the fragment so that it is not sent to the server.
+ net::EscapeQueryParamValue(original_url.GetAsReferrer().spec(),
+ true /* use_plus */) +
+ fragment);
+
+ DCHECK(compressed_url.is_valid());
+ DCHECK_EQ(subresource_host.scheme(), compressed_url.scheme());
+ return compressed_url;
+}
+
+GURL GetLitePageSubresourceDomainURL() {
+ // Command line options take highest precedence.
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kLitePagesServerSubresourceHost)) {
+ const std::string switch_value = command_line->GetSwitchValueASCII(
+ switches::kLitePagesServerSubresourceHost);
+ const GURL url(switch_value);
+ if (url.is_valid())
+ return url;
+ LOG(ERROR) << "The following litepages previews host URL specified at the "
+ << "command-line is invalid: " << switch_value;
+ }
+
+ // No override use the default litepages domain.
+ return GURL("https://litepages.googlezip.net/");
+}
+
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.h b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.h
new file mode 100644
index 00000000000..65be06e6857
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_UTIL_H_
+#define CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_UTIL_H_
+
+#include "url/gurl.h"
+
+namespace subresource_redirect {
+
+// Gets the new URL for the compressed version of the image resource to enable
+// internal redirects.
+GURL GetSubresourceURLForURL(const GURL& original_url);
+
+// Gets the URL for the compression server.
+GURL GetLitePageSubresourceDomainURL();
+
+} // namespace subresource_redirect
+
+#endif // CHROME_RENDERER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_UTIL_H_
diff --git a/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util_unittest.cc b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util_unittest.cc
new file mode 100644
index 00000000000..8b8753787dc
--- /dev/null
+++ b/chromium/chrome/renderer/subresource_redirect/subresource_redirect_util_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace subresource_redirect {
+
+TEST(SubresourceRedirectURL, ProperlyChangesURL) {
+ EXPECT_EQ(GetSubresourceURLForURL(GURL("https://www.test.com/test.jpg")),
+ GURL("https://"
+ "jy6r5d5zc3n6juvq35nveinxzyuk4n4wndppyli5x5ycmrza36fa."
+ "litepages.googlezip.net/"
+ "i?u=https%3A%2F%2Fwww.test.com%2Ftest.jpg"));
+}
+
+TEST(SubresourceRedirectURL, ProperlyHandlesFragment) {
+ EXPECT_EQ(GetSubresourceURLForURL(GURL("https://www.test.com/test.jpg#test")),
+ GURL("https://"
+ "jy6r5d5zc3n6juvq35nveinxzyuk4n4wndppyli5x5ycmrza36fa."
+ "litepages.googlezip.net/"
+ "i?u=https%3A%2F%2Fwww.test.com%2Ftest.jpg#test"));
+}
+
+TEST(SubresourceRedirectURL, ProperlyHandlesSetPort) {
+ EXPECT_EQ(GetSubresourceURLForURL(GURL("https://www.test.com:4444/test.jpg")),
+ GURL("https://flm6clfkawcjb2bw5cnrrcdf4fkoliileuljcc23lahdet75ouqq."
+ "litepages.googlezip.net/i?u=https%3A%2F%2Fwww.test."
+ "com%3A4444%2Ftest.jpg"));
+}
+
+TEST(SubresourceRedirectURL, ProperlyHandlesQueryParams) {
+ EXPECT_EQ(GetSubresourceURLForURL(
+ GURL("https://www.test.com/test.jpg?color=yellow")),
+ GURL("https://"
+ "jy6r5d5zc3n6juvq35nveinxzyuk4n4wndppyli5x5ycmrza36fa."
+ "litepages.googlezip.net/"
+ "i?u=https%3A%2F%2Fwww.test.com%2Ftest.jpg%3Fcolor%3Dyellow"));
+}
+
+TEST(SubresourceRedirectURL, ProperlyHandlesMultipleQueryParams) {
+ EXPECT_EQ(GetSubresourceURLForURL(
+ GURL("https://www.test.com/test.jpg?color=yellow&name=test")),
+ GURL("https://"
+ "jy6r5d5zc3n6juvq35nveinxzyuk4n4wndppyli5x5ycmrza36fa."
+ "litepages.googlezip.net/"
+ "i?u=https%3A%2F%2Fwww.test.com%2Ftest.jpg%3Fcolor%3Dyellow%"
+ "26name%3Dtest"));
+}
+
+TEST(SubresourceRedirectURL, ProperlyHandlesQueryParamsWithFragments) {
+ EXPECT_EQ(GetSubresourceURLForURL(
+ GURL("https://www.test.com/test.jpg?color=yellow#test")),
+ GURL("https://"
+ "jy6r5d5zc3n6juvq35nveinxzyuk4n4wndppyli5x5ycmrza36fa."
+ "litepages.googlezip.net/"
+ "i?u=https%3A%2F%2Fwww.test.com%2Ftest.jpg%3Fcolor%3Dyellow"
+ "#test"));
+}
+
+// Currently redirects are not supported for HTTP subresources, but there is
+// potential to add them in the future.
+TEST(SubresourceRedirectURL, ProperlyChangesHTTPURL) {
+ EXPECT_EQ(GetSubresourceURLForURL(GURL("http://www.test.com/test.jpg")),
+ GURL("https://bc6pgqmtr6nlicooao2zh77svcptncpwfolwlgbrop6gqnr6ck3q."
+ "litepages.googlezip.net/i?u=http%3A%2F%2Fwww.test.com%2Ftest."
+ "jpg"));
+}
+
+} // namespace subresource_redirect
diff --git a/chromium/chrome/renderer/supervised_user/OWNERS b/chromium/chrome/renderer/supervised_user/OWNERS
new file mode 100644
index 00000000000..33a7a97e428
--- /dev/null
+++ b/chromium/chrome/renderer/supervised_user/OWNERS
@@ -0,0 +1,6 @@
+escordeiro@chromium.org
+menegola@chromium.org
+pam@chromium.org
+treib@chromium.org
+
+# COMPONENT: Services>SupervisedUser
diff --git a/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.cc b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.cc
new file mode 100644
index 00000000000..5e3c28cd761
--- /dev/null
+++ b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.cc
@@ -0,0 +1,85 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/supervised_user/supervised_user_error_page_controller.h"
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h"
+#include "content/public/renderer/render_frame.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+gin::WrapperInfo SupervisedUserErrorPageController::kWrapperInfo = {
+ gin::kEmbedderNativeGin};
+
+void SupervisedUserErrorPageController::Install(
+ content::RenderFrame* render_frame,
+ base::WeakPtr<SupervisedUserErrorPageControllerDelegate> delegate) {
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ render_frame->GetWebFrame()->MainWorldScriptContext();
+ if (context.IsEmpty())
+ return;
+
+ v8::Context::Scope context_scope(context);
+
+ gin::Handle<SupervisedUserErrorPageController> controller = gin::CreateHandle(
+ isolate, new SupervisedUserErrorPageController(delegate, render_frame));
+ if (controller.IsEmpty())
+ return;
+
+ v8::Local<v8::Object> global = context->Global();
+ global
+ ->Set(context,
+ gin::StringToV8(isolate, "supervisedUserErrorPageController"),
+ controller.ToV8())
+ .Check();
+}
+
+SupervisedUserErrorPageController::SupervisedUserErrorPageController(
+ base::WeakPtr<SupervisedUserErrorPageControllerDelegate> delegate,
+ content::RenderFrame* render_frame)
+ : delegate_(delegate), render_frame_(render_frame) {}
+
+SupervisedUserErrorPageController::~SupervisedUserErrorPageController() {}
+
+void SupervisedUserErrorPageController::GoBack() {
+ if (delegate_)
+ delegate_->GoBack();
+}
+
+void SupervisedUserErrorPageController::RequestPermission() {
+ if (delegate_) {
+ delegate_->RequestPermission(base::BindOnce(
+ &SupervisedUserErrorPageController::RequestPermissionCallback,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void SupervisedUserErrorPageController::Feedback() {
+ if (delegate_)
+ delegate_->Feedback();
+}
+
+void SupervisedUserErrorPageController::RequestPermissionCallback(
+ bool success) {
+ std::string result = success ? "true" : "false";
+ std::string js = "setRequestStatus(" + result + ");";
+ render_frame_->ExecuteJavaScript(base::ASCIIToUTF16(js));
+}
+
+gin::ObjectTemplateBuilder
+SupervisedUserErrorPageController::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return gin::Wrappable<SupervisedUserErrorPageController>::
+ GetObjectTemplateBuilder(isolate)
+ .SetMethod("goBack", &SupervisedUserErrorPageController::GoBack)
+ .SetMethod("requestPermission",
+ &SupervisedUserErrorPageController::RequestPermission)
+ .SetMethod("feedback", &SupervisedUserErrorPageController::Feedback);
+}
diff --git a/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.h b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.h
new file mode 100644
index 00000000000..30d88d14376
--- /dev/null
+++ b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller.h
@@ -0,0 +1,62 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_H_
+#define CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "gin/wrappable.h"
+
+namespace content {
+class RenderFrame;
+}
+
+class SupervisedUserErrorPageControllerDelegate;
+
+// This class makes various helper functions available to supervised user
+// interstitials when committed interstitials are on. It is bound to the
+// JavaScript window.certificateErrorPageController object.
+class SupervisedUserErrorPageController
+ : public gin::Wrappable<SupervisedUserErrorPageController> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ // Will invoke methods on |delegate| in response to user actions taken on the
+ // interstitial. May call delegate methods even after the page has been
+ // navigated away from, so it is recommended consumers make sure the weak
+ // pointers are destroyed in response to navigations.
+ static void Install(
+ content::RenderFrame* render_frame,
+ base::WeakPtr<SupervisedUserErrorPageControllerDelegate> delegate);
+
+ private:
+ SupervisedUserErrorPageController(
+ base::WeakPtr<SupervisedUserErrorPageControllerDelegate> delegate,
+ content::RenderFrame* render_frame);
+ ~SupervisedUserErrorPageController() override;
+
+ void GoBack();
+ void RequestPermission();
+ void Feedback();
+
+ void RequestPermissionCallback(bool success);
+
+ // gin::WrappableBase
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override;
+
+ base::WeakPtr<SupervisedUserErrorPageControllerDelegate> const delegate_;
+
+ content::RenderFrame* render_frame_;
+
+ // This weak factory is used to generate weak pointers to the controller that
+ // are used for the request permission callback, so messages to no longer
+ // existing interstitials are ignored.
+ base::WeakPtrFactory<SupervisedUserErrorPageController> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(SupervisedUserErrorPageController);
+};
+
+#endif // CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_H_
diff --git a/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h
new file mode 100644
index 00000000000..135cf978833
--- /dev/null
+++ b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h
@@ -0,0 +1,21 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_DELEGATE_H_
+#define CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_DELEGATE_H_
+
+#include "base/callback_forward.h"
+
+class SupervisedUserErrorPageControllerDelegate {
+ public:
+ // Called when the interstitial calls the installed JS methods.
+ virtual void GoBack() = 0;
+ virtual void RequestPermission(base::OnceCallback<void(bool)> callback) = 0;
+ virtual void Feedback() = 0;
+
+ protected:
+ virtual ~SupervisedUserErrorPageControllerDelegate() {}
+};
+
+#endif // CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_DELEGATE_H_
diff --git a/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.cc b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.cc
new file mode 100644
index 00000000000..093f569a4e4
--- /dev/null
+++ b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.cc
@@ -0,0 +1,66 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h"
+
+#include "chrome/renderer/supervised_user/supervised_user_error_page_controller.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+
+SupervisedUserErrorPageControllerDelegateImpl::
+ SupervisedUserErrorPageControllerDelegateImpl(
+ content::RenderFrame* render_frame)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<
+ SupervisedUserErrorPageControllerDelegateImpl>(render_frame) {}
+
+SupervisedUserErrorPageControllerDelegateImpl::
+ ~SupervisedUserErrorPageControllerDelegateImpl() = default;
+
+void SupervisedUserErrorPageControllerDelegateImpl::PrepareForErrorPage() {
+ pending_error_ = true;
+}
+
+void SupervisedUserErrorPageControllerDelegateImpl::GoBack() {
+ if (supervised_user_interface_)
+ supervised_user_interface_->GoBack();
+}
+
+void SupervisedUserErrorPageControllerDelegateImpl::RequestPermission(
+ base::OnceCallback<void(bool)> callback) {
+ if (supervised_user_interface_)
+ supervised_user_interface_->RequestPermission(std::move(callback));
+}
+
+void SupervisedUserErrorPageControllerDelegateImpl::Feedback() {
+ if (supervised_user_interface_)
+ supervised_user_interface_->Feedback();
+}
+
+void SupervisedUserErrorPageControllerDelegateImpl::OnDestruct() {
+ delete this;
+}
+
+void SupervisedUserErrorPageControllerDelegateImpl::DidFinishLoad() {
+ if (committed_error_) {
+ if (!supervised_user_interface_) {
+ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
+ &supervised_user_interface_);
+ }
+
+ SupervisedUserErrorPageController::Install(
+ render_frame(),
+ weak_supervised_user_error_controller_delegate_factory_.GetWeakPtr());
+ }
+}
+
+void SupervisedUserErrorPageControllerDelegateImpl::ReadyToCommitNavigation(
+ blink::WebDocumentLoader* document_loader) {
+ // We are about to commit a new navigation in this render frame.
+ // Invalidate the weak pointer in previous error page controller, i.e.
+ // |SupervisedUserErrorPageController::delegate_|;
+ weak_supervised_user_error_controller_delegate_factory_.InvalidateWeakPtrs();
+ committed_error_ = pending_error_;
+ pending_error_ = false;
+}
diff --git a/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h
new file mode 100644
index 00000000000..43f47086688
--- /dev/null
+++ b/chromium/chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h
@@ -0,0 +1,61 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_DELEGATE_IMPL_H_
+#define CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_DELEGATE_IMPL_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/common/supervised_user_commands.mojom.h"
+#include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+
+namespace content {
+class RenderFrame;
+} // namespace content
+
+class SupervisedUserErrorPageControllerDelegateImpl
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<
+ SupervisedUserErrorPageControllerDelegateImpl>,
+ public SupervisedUserErrorPageControllerDelegate {
+ public:
+ explicit SupervisedUserErrorPageControllerDelegateImpl(
+ content::RenderFrame* render_frame);
+ ~SupervisedUserErrorPageControllerDelegateImpl() override;
+
+ // Notifies us that a navigation error has occurred and will be committed.
+ void PrepareForErrorPage();
+
+ // SupervisedUserErrorPageControllerDelegate:
+ void GoBack() override;
+ void RequestPermission(base::OnceCallback<void(bool)> callback) override;
+ void Feedback() override;
+
+ // content::RenderFrameObserver:
+ void OnDestruct() override;
+ void DidFinishLoad() override;
+ void ReadyToCommitNavigation(
+ blink::WebDocumentLoader* document_loader) override;
+
+ private:
+ mojo::AssociatedRemote<supervised_user::mojom::SupervisedUserCommands>
+ supervised_user_interface_;
+
+ // Whether there is an error page pending to be committed.
+ bool pending_error_ = false;
+
+ // Whether the committed page is an error page.
+ bool committed_error_ = false;
+
+ base::WeakPtrFactory<SupervisedUserErrorPageControllerDelegate>
+ weak_supervised_user_error_controller_delegate_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(SupervisedUserErrorPageControllerDelegateImpl);
+};
+
+#endif // CHROME_RENDERER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_CONTROLLER_DELEGATE_IMPL_H_
diff --git a/chromium/chrome/renderer/sync_encryption_keys_extension.cc b/chromium/chrome/renderer/sync_encryption_keys_extension.cc
new file mode 100644
index 00000000000..e429fff41f1
--- /dev/null
+++ b/chromium/chrome/renderer/sync_encryption_keys_extension.cc
@@ -0,0 +1,142 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/sync_encryption_keys_extension.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "content/public/renderer/chrome_object_extensions_utils.h"
+#include "content/public/renderer/render_frame.h"
+#include "gin/arguments.h"
+#include "gin/function_template.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "url/origin.h"
+
+namespace {
+
+const url::Origin& GetAllowedOrigin() {
+ static const base::NoDestructor<url::Origin> origin(
+ url::Origin::Create(GaiaUrls::GetInstance()->gaia_url()));
+ CHECK(!origin->opaque());
+ return *origin;
+}
+
+} // namespace
+
+// static
+void SyncEncryptionKeysExtension::Create(content::RenderFrame* frame) {
+ new SyncEncryptionKeysExtension(frame);
+}
+
+SyncEncryptionKeysExtension::SyncEncryptionKeysExtension(
+ content::RenderFrame* frame)
+ : content::RenderFrameObserver(frame) {}
+
+SyncEncryptionKeysExtension::~SyncEncryptionKeysExtension() {}
+
+void SyncEncryptionKeysExtension::OnDestruct() {
+ delete this;
+}
+
+void SyncEncryptionKeysExtension::DidClearWindowObject() {
+ if (!render_frame()) {
+ return;
+ }
+
+ url::Origin origin = render_frame()->GetWebFrame()->GetSecurityOrigin();
+ if (origin == GetAllowedOrigin()) {
+ Install();
+ }
+}
+
+void SyncEncryptionKeysExtension::Install() {
+ DCHECK(render_frame());
+
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ render_frame()->GetWebFrame()->MainWorldScriptContext();
+ if (context.IsEmpty())
+ return;
+
+ v8::Context::Scope context_scope(context);
+
+ v8::Local<v8::Object> chrome =
+ content::GetOrCreateChromeObject(isolate, context);
+ v8::Local<v8::Function> function =
+ gin::CreateFunctionTemplate(
+ isolate, base::BindRepeating(
+ &SyncEncryptionKeysExtension::SetSyncEncryptionKeys,
+ weak_ptr_factory_.GetWeakPtr()))
+ ->GetFunction(context)
+ .ToLocalChecked();
+ chrome
+ ->Set(context, gin::StringToSymbol(isolate, "setSyncEncryptionKeys"),
+ function)
+ .Check();
+}
+
+void SyncEncryptionKeysExtension::SetSyncEncryptionKeys(gin::Arguments* args) {
+ DCHECK(render_frame());
+
+ v8::HandleScope handle_scope(args->isolate());
+
+ std::vector<std::string> encryption_keys;
+ if (!args->GetNext(&encryption_keys)) {
+ DLOG(ERROR) << "Not array of strings";
+ args->ThrowError();
+ return;
+ }
+
+ if (encryption_keys.empty()) {
+ DLOG(ERROR) << "Array of strings empty";
+ args->ThrowError();
+ return;
+ }
+
+ std::string account_id;
+ if (!args->GetNext(&account_id)) {
+ DLOG(ERROR) << "No account ID";
+ args->ThrowError();
+ return;
+ }
+
+ v8::Local<v8::Function> callback;
+ if (!args->GetNext(&callback)) {
+ DLOG(ERROR) << "No callback";
+ args->ThrowError();
+ return;
+ }
+
+ auto global_callback =
+ std::make_unique<v8::Global<v8::Function>>(args->isolate(), callback);
+
+ // TODO(crbug.com/1000146): Pass along the encryption keys to the browser
+ // process.
+ NOTIMPLEMENTED();
+ RunCompletionCallback(std::move(global_callback));
+}
+
+void SyncEncryptionKeysExtension::RunCompletionCallback(
+ std::unique_ptr<v8::Global<v8::Function>> callback) {
+ if (!render_frame())
+ return;
+
+ v8::Isolate* isolate = blink::MainThreadIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ render_frame()->GetWebFrame()->MainWorldScriptContext();
+ v8::Context::Scope context_scope(context);
+ v8::Local<v8::Function> callback_local =
+ v8::Local<v8::Function>::New(isolate, *callback);
+
+ render_frame()->GetWebFrame()->CallFunctionEvenIfScriptDisabled(
+ callback_local, v8::Undefined(isolate), /*argc=*/0, /*argv=*/{});
+}
diff --git a/chromium/chrome/renderer/sync_encryption_keys_extension.h b/chromium/chrome/renderer/sync_encryption_keys_extension.h
new file mode 100644
index 00000000000..bb30a55a752
--- /dev/null
+++ b/chromium/chrome/renderer/sync_encryption_keys_extension.h
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_SYNC_ENCRYPTION_KEYS_EXTENSION_H_
+#define CHROME_RENDERER_SYNC_ENCRYPTION_KEYS_EXTENSION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "v8/include/v8.h"
+
+namespace gin {
+class Arguments;
+} // namespace gin
+
+// This class adds a function chrome.setSyncEncryptionKeys().
+class SyncEncryptionKeysExtension : public content::RenderFrameObserver {
+ public:
+ // Creates a new instance, with ownership transferred to |*frame|.
+ static void Create(content::RenderFrame* frame);
+ ~SyncEncryptionKeysExtension() override;
+
+ // content::RenderFrameObserver:
+ void OnDestruct() override;
+ void DidClearWindowObject() override;
+
+ private:
+ explicit SyncEncryptionKeysExtension(content::RenderFrame* frame);
+
+ void Install();
+ void SetSyncEncryptionKeys(gin::Arguments* args);
+ void RunCompletionCallback(
+ std::unique_ptr<v8::Global<v8::Function>> callback);
+
+ base::WeakPtrFactory<SyncEncryptionKeysExtension> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(SyncEncryptionKeysExtension);
+};
+
+#endif // CHROME_RENDERER_SYNC_ENCRYPTION_KEYS_EXTENSION_H_
diff --git a/chromium/chrome/renderer/translate/OWNERS b/chromium/chrome/renderer/translate/OWNERS
new file mode 100644
index 00000000000..f02b3309f32
--- /dev/null
+++ b/chromium/chrome/renderer/translate/OWNERS
@@ -0,0 +1,2 @@
+file://components/translate/OWNERS
+# COMPONENT: UI>Browser>Language>Translate
diff --git a/chromium/chrome/renderer/translate/translate_helper_browsertest.cc b/chromium/chrome/renderer/translate/translate_helper_browsertest.cc
new file mode 100644
index 00000000000..09ac5dc59d9
--- /dev/null
+++ b/chromium/chrome/renderer/translate/translate_helper_browsertest.cc
@@ -0,0 +1,523 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <tuple>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "chrome/common/chrome_isolated_world_ids.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/translate/content/common/translate.mojom.h"
+#include "components/translate/content/renderer/translate_helper.h"
+#include "components/translate/core/common/translate_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/constants.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+using testing::AtLeast;
+using testing::Return;
+using testing::_;
+
+namespace {
+
+class FakeContentTranslateDriver
+ : public translate::mojom::ContentTranslateDriver {
+ public:
+ FakeContentTranslateDriver()
+ : called_new_page_(false), page_needs_translation_(false) {}
+ ~FakeContentTranslateDriver() override {}
+
+ void BindHandle(mojo::ScopedMessagePipeHandle handle) {
+ receivers_.Add(
+ this, mojo::PendingReceiver<translate::mojom::ContentTranslateDriver>(
+ std::move(handle)));
+ }
+
+ // translate::mojom::ContentTranslateDriver implementation.
+ void RegisterPage(mojo::PendingRemote<translate::mojom::Page> page,
+ const translate::LanguageDetectionDetails& details,
+ bool page_needs_translation) override {
+ called_new_page_ = true;
+ details_ = details;
+ page_needs_translation_ = page_needs_translation;
+ }
+
+ void ResetNewPageValues() {
+ called_new_page_ = false;
+ details_ = base::nullopt;
+ page_needs_translation_ = false;
+ }
+
+ bool called_new_page_;
+ base::Optional<translate::LanguageDetectionDetails> details_;
+ bool page_needs_translation_;
+
+ private:
+ mojo::ReceiverSet<translate::mojom::ContentTranslateDriver> receivers_;
+};
+
+} // namespace
+
+class TestTranslateHelper : public translate::TranslateHelper {
+ public:
+ explicit TestTranslateHelper(content::RenderFrame* render_frame)
+ : translate::TranslateHelper(render_frame,
+ ISOLATED_WORLD_ID_TRANSLATE,
+ extensions::kExtensionScheme) {}
+
+ base::TimeDelta AdjustDelay(int delayInMs) override {
+ // Just returns base::TimeDelta() which has initial value 0.
+ // Tasks doesn't need to be delayed in tests.
+ return base::TimeDelta();
+ }
+
+ void TranslatePage(const std::string& source_lang,
+ const std::string& target_lang,
+ const std::string& translate_script) {
+ // Reset result values firstly.
+ page_translated_ = false;
+ trans_result_cancelled_ = false;
+ trans_result_original_lang_ = base::nullopt;
+ trans_result_translated_lang_ = base::nullopt;
+ trans_result_error_type_ = translate::TranslateErrors::NONE;
+
+ // Will get new result values via OnPageTranslated.
+ Translate(translate_script, source_lang, target_lang,
+ base::Bind(&TestTranslateHelper::OnPageTranslated,
+ base::Unretained(this)));
+ }
+
+ bool GetPageTranslatedResult(std::string* original_lang,
+ std::string* target_lang,
+ translate::TranslateErrors::Type* error) {
+ if (!page_translated_)
+ return false;
+ if (original_lang)
+ *original_lang = *trans_result_original_lang_;
+ if (target_lang)
+ *target_lang = *trans_result_translated_lang_;
+ if (error)
+ *error = trans_result_error_type_;
+ return true;
+ }
+
+ MOCK_METHOD0(IsTranslateLibAvailable, bool());
+ MOCK_METHOD0(IsTranslateLibReady, bool());
+ MOCK_METHOD0(HasTranslationFinished, bool());
+ MOCK_METHOD0(HasTranslationFailed, bool());
+ MOCK_METHOD0(GetOriginalPageLanguage, std::string());
+ MOCK_METHOD0(GetErrorCode, int64_t());
+ MOCK_METHOD0(StartTranslation, bool());
+ MOCK_METHOD1(ExecuteScript, void(const std::string&));
+ MOCK_METHOD2(ExecuteScriptAndGetBoolResult, bool(const std::string&, bool));
+ MOCK_METHOD1(ExecuteScriptAndGetStringResult,
+ std::string(const std::string&));
+ MOCK_METHOD1(ExecuteScriptAndGetDoubleResult, double(const std::string&));
+ MOCK_METHOD1(ExecuteScriptAndGetIntegerResult, int64_t(const std::string&));
+
+ private:
+ void OnPageTranslated(bool cancelled,
+ const std::string& original_lang,
+ const std::string& translated_lang,
+ translate::TranslateErrors::Type error_type) {
+ page_translated_ = true;
+ trans_result_cancelled_ = cancelled;
+ trans_result_original_lang_ = original_lang;
+ trans_result_translated_lang_ = translated_lang;
+ trans_result_error_type_ = error_type;
+ }
+
+ bool page_translated_;
+ bool trans_result_cancelled_;
+ base::Optional<std::string> trans_result_original_lang_;
+ base::Optional<std::string> trans_result_translated_lang_;
+ translate::TranslateErrors::Type trans_result_error_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTranslateHelper);
+};
+
+class TranslateHelperBrowserTest : public ChromeRenderViewTest {
+ public:
+ TranslateHelperBrowserTest() : translate_helper_(NULL) {}
+
+ protected:
+ void SetUp() override {
+ ChromeRenderViewTest::SetUp();
+ translate_helper_ = new TestTranslateHelper(view_->GetMainRenderFrame());
+
+ service_manager::InterfaceProvider* remote_interfaces =
+ view_->GetMainRenderFrame()->GetRemoteInterfaces();
+ service_manager::InterfaceProvider::TestApi test_api(remote_interfaces);
+ test_api.SetBinderForName(
+ translate::mojom::ContentTranslateDriver::Name_,
+ base::Bind(&FakeContentTranslateDriver::BindHandle,
+ base::Unretained(&fake_translate_driver_)));
+ }
+
+ void TearDown() override {
+ delete translate_helper_;
+ ChromeRenderViewTest::TearDown();
+ }
+
+ TestTranslateHelper* translate_helper_;
+ FakeContentTranslateDriver fake_translate_driver_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TranslateHelperBrowserTest);
+};
+
+// Tests that the browser gets notified of the translation failure if the
+// translate library fails/times-out during initialization.
+TEST_F(TranslateHelperBrowserTest, TranslateLibNeverReady) {
+ // We make IsTranslateLibAvailable true so we don't attempt to inject the
+ // library.
+ EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*translate_helper_, IsTranslateLibReady())
+ .Times(AtLeast(5)) // See kMaxTranslateInitCheckAttempts in
+ // translate_helper.cc
+ .WillRepeatedly(Return(false));
+
+ EXPECT_CALL(*translate_helper_, GetErrorCode())
+ .Times(AtLeast(5))
+ .WillRepeatedly(Return(translate::TranslateErrors::NONE));
+
+ translate_helper_->TranslatePage("en", "fr", std::string());
+ base::RunLoop().RunUntilIdle();
+
+ translate::TranslateErrors::Type error;
+ ASSERT_TRUE(translate_helper_->GetPageTranslatedResult(NULL, NULL, &error));
+ EXPECT_EQ(translate::TranslateErrors::TRANSLATION_TIMEOUT, error);
+}
+
+// Tests that the browser gets notified of the translation success when the
+// translation succeeds.
+TEST_F(TranslateHelperBrowserTest, TranslateSuccess) {
+ // We make IsTranslateLibAvailable true so we don't attempt to inject the
+ // library.
+ EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*translate_helper_, IsTranslateLibReady())
+ .WillOnce(Return(false))
+ .WillOnce(Return(true));
+
+ EXPECT_CALL(*translate_helper_, GetErrorCode())
+ .WillOnce(Return(translate::TranslateErrors::NONE));
+
+ EXPECT_CALL(*translate_helper_, StartTranslation()).WillOnce(Return(true));
+
+ // Succeed after few checks.
+ EXPECT_CALL(*translate_helper_, HasTranslationFailed())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*translate_helper_, HasTranslationFinished())
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(true));
+
+ // V8 call for performance monitoring should be ignored.
+ EXPECT_CALL(*translate_helper_,
+ ExecuteScriptAndGetDoubleResult(_)).Times(3);
+
+ std::string original_lang("en");
+ std::string target_lang("fr");
+ translate_helper_->TranslatePage(original_lang, target_lang, std::string());
+ base::RunLoop().RunUntilIdle();
+
+ std::string received_original_lang;
+ std::string received_target_lang;
+ translate::TranslateErrors::Type error;
+ ASSERT_TRUE(translate_helper_->GetPageTranslatedResult(
+ &received_original_lang, &received_target_lang, &error));
+ EXPECT_EQ(original_lang, received_original_lang);
+ EXPECT_EQ(target_lang, received_target_lang);
+ EXPECT_EQ(translate::TranslateErrors::NONE, error);
+}
+
+// Tests that the browser gets notified of the translation failure when the
+// translation fails.
+TEST_F(TranslateHelperBrowserTest, TranslateFailure) {
+ // We make IsTranslateLibAvailable true so we don't attempt to inject the
+ // library.
+ EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*translate_helper_, IsTranslateLibReady())
+ .WillOnce(Return(true));
+
+ EXPECT_CALL(*translate_helper_, StartTranslation()).WillOnce(Return(true));
+
+ // Fail after few checks.
+ EXPECT_CALL(*translate_helper_, HasTranslationFailed())
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(true));
+
+ EXPECT_CALL(*translate_helper_, HasTranslationFinished())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(false));
+
+ EXPECT_CALL(*translate_helper_, GetErrorCode())
+ .WillOnce(Return(translate::TranslateErrors::TRANSLATION_ERROR));
+
+ // V8 call for performance monitoring should be ignored.
+ EXPECT_CALL(*translate_helper_,
+ ExecuteScriptAndGetDoubleResult(_)).Times(2);
+
+ translate_helper_->TranslatePage("en", "fr", std::string());
+ base::RunLoop().RunUntilIdle();
+
+ translate::TranslateErrors::Type error;
+ ASSERT_TRUE(translate_helper_->GetPageTranslatedResult(NULL, NULL, &error));
+ EXPECT_EQ(translate::TranslateErrors::TRANSLATION_ERROR, error);
+}
+
+// Tests that when the browser translate a page for which the language is
+// undefined we query the translate element to get the language.
+TEST_F(TranslateHelperBrowserTest, UndefinedSourceLang) {
+ // We make IsTranslateLibAvailable true so we don't attempt to inject the
+ // library.
+ EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*translate_helper_, IsTranslateLibReady())
+ .WillOnce(Return(true));
+
+ EXPECT_CALL(*translate_helper_, GetOriginalPageLanguage())
+ .WillOnce(Return("de"));
+
+ EXPECT_CALL(*translate_helper_, StartTranslation()).WillOnce(Return(true));
+ EXPECT_CALL(*translate_helper_, HasTranslationFailed())
+ .WillOnce(Return(false));
+ EXPECT_CALL(*translate_helper_, HasTranslationFinished())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+
+ // V8 call for performance monitoring should be ignored.
+ EXPECT_CALL(*translate_helper_,
+ ExecuteScriptAndGetDoubleResult(_)).Times(3);
+
+ translate_helper_->TranslatePage(translate::kUnknownLanguageCode,
+ "fr",
+ std::string());
+ base::RunLoop().RunUntilIdle();
+
+ translate::TranslateErrors::Type error;
+ std::string original_lang;
+ std::string target_lang;
+ ASSERT_TRUE(translate_helper_->GetPageTranslatedResult(&original_lang,
+ &target_lang, &error));
+ EXPECT_EQ("de", original_lang);
+ EXPECT_EQ("fr", target_lang);
+ EXPECT_EQ(translate::TranslateErrors::NONE, error);
+}
+
+// Tests that starting a translation while a similar one is pending does not
+// break anything.
+TEST_F(TranslateHelperBrowserTest, MultipleSimilarTranslations) {
+ // We make IsTranslateLibAvailable true so we don't attempt to inject the
+ // library.
+ EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*translate_helper_, IsTranslateLibReady())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*translate_helper_, StartTranslation())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*translate_helper_, HasTranslationFailed())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*translate_helper_, HasTranslationFinished())
+ .WillOnce(Return(true));
+
+ // V8 call for performance monitoring should be ignored.
+ EXPECT_CALL(*translate_helper_,
+ ExecuteScriptAndGetDoubleResult(_)).Times(3);
+
+ std::string original_lang("en");
+ std::string target_lang("fr");
+ translate_helper_->TranslatePage(original_lang, target_lang, std::string());
+ // While this is running call again TranslatePage to make sure noting bad
+ // happens.
+ translate_helper_->TranslatePage(original_lang, target_lang, std::string());
+ base::RunLoop().RunUntilIdle();
+
+ std::string received_original_lang;
+ std::string received_target_lang;
+ translate::TranslateErrors::Type error;
+ ASSERT_TRUE(translate_helper_->GetPageTranslatedResult(
+ &received_original_lang, &received_target_lang, &error));
+ EXPECT_EQ(original_lang, received_original_lang);
+ EXPECT_EQ(target_lang, received_target_lang);
+ EXPECT_EQ(translate::TranslateErrors::NONE, error);
+}
+
+// Tests that starting a translation while a different one is pending works.
+TEST_F(TranslateHelperBrowserTest, MultipleDifferentTranslations) {
+ EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*translate_helper_, IsTranslateLibReady())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*translate_helper_, StartTranslation())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*translate_helper_, HasTranslationFailed())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*translate_helper_, HasTranslationFinished())
+ .WillOnce(Return(true));
+
+ // V8 call for performance monitoring should be ignored.
+ EXPECT_CALL(*translate_helper_,
+ ExecuteScriptAndGetDoubleResult(_)).Times(5);
+
+ std::string original_lang("en");
+ std::string target_lang("fr");
+ translate_helper_->TranslatePage(original_lang, target_lang, std::string());
+ // While this is running call again TranslatePage with a new target lang.
+ std::string new_target_lang("de");
+ translate_helper_->TranslatePage(
+ original_lang, new_target_lang, std::string());
+ base::RunLoop().RunUntilIdle();
+
+ std::string received_original_lang;
+ std::string received_target_lang;
+ translate::TranslateErrors::Type error;
+ ASSERT_TRUE(translate_helper_->GetPageTranslatedResult(
+ &received_original_lang, &received_target_lang, &error));
+ EXPECT_EQ(original_lang, received_original_lang);
+ EXPECT_EQ(new_target_lang, received_target_lang);
+ EXPECT_EQ(translate::TranslateErrors::NONE, error);
+}
+
+// Tests that we send the right translate language message for a page and that
+// we respect the "no translate" meta-tag.
+TEST_F(TranslateHelperBrowserTest, TranslatablePage) {
+ LoadHTML("<html><body>A random page with random content.</body></html>");
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_TRUE(fake_translate_driver_.page_needs_translation_)
+ << "Page should be translatable.";
+ fake_translate_driver_.ResetNewPageValues();
+
+ // Now the page specifies the META tag to prevent translation.
+ LoadHTML("<html><head><meta name=\"google\" value=\"notranslate\"></head>"
+ "<body>A random page with random content.</body></html>");
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_FALSE(fake_translate_driver_.page_needs_translation_)
+ << "Page should not be translatable.";
+ fake_translate_driver_.ResetNewPageValues();
+
+ // Try the alternate version of the META tag (content instead of value).
+ LoadHTML("<html><head><meta name=\"google\" content=\"notranslate\"></head>"
+ "<body>A random page with random content.</body></html>");
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_FALSE(fake_translate_driver_.page_needs_translation_)
+ << "Page should not be translatable.";
+}
+
+// Tests that the language meta tag takes precedence over the CLD when reporting
+// the page's language.
+TEST_F(TranslateHelperBrowserTest, LanguageMetaTag) {
+ LoadHTML("<html><head><meta http-equiv=\"content-language\" content=\"es\">"
+ "</head><body>A random page with random content.</body></html>");
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("es", fake_translate_driver_.details_->adopted_language);
+ fake_translate_driver_.ResetNewPageValues();
+
+ // Makes sure we support multiple languages specified.
+ LoadHTML("<html><head><meta http-equiv=\"content-language\" "
+ "content=\" fr , es,en \">"
+ "</head><body>A random page with random content.</body></html>");
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("fr", fake_translate_driver_.details_->adopted_language);
+}
+
+// Tests that the language meta tag works even with non-all-lower-case.
+// http://code.google.com/p/chromium/issues/detail?id=145689
+TEST_F(TranslateHelperBrowserTest, LanguageMetaTagCase) {
+ LoadHTML("<html><head><meta http-equiv=\"Content-Language\" content=\"es\">"
+ "</head><body>A random page with random content.</body></html>");
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("es", fake_translate_driver_.details_->adopted_language);
+ fake_translate_driver_.ResetNewPageValues();
+
+ // Makes sure we support multiple languages specified.
+ LoadHTML("<html><head><meta http-equiv=\"Content-Language\" "
+ "content=\" fr , es,en \">"
+ "</head><body>A random page with random content.</body></html>");
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("fr", fake_translate_driver_.details_->adopted_language);
+}
+
+// Tests that the language meta tag is converted to Chrome standard of dashes
+// instead of underscores and proper capitalization.
+// http://code.google.com/p/chromium/issues/detail?id=159487
+TEST_F(TranslateHelperBrowserTest, LanguageCommonMistakesAreCorrected) {
+ LoadHTML("<html><head><meta http-equiv='Content-Language' content='EN_us'>"
+ "</head><body>A random page with random content.</body></html>");
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("en", fake_translate_driver_.details_->adopted_language);
+ fake_translate_driver_.ResetNewPageValues();
+
+ LoadHTML("<html><head><meta http-equiv='Content-Language' content='ZH_tw'>"
+ "</head><body>A random page with random content.</body></html>");
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("zh-TW", fake_translate_driver_.details_->adopted_language);
+}
+
+// Tests that a back navigation gets a translate language message.
+TEST_F(TranslateHelperBrowserTest, BackToTranslatablePage) {
+ LoadHTML("<html><head><meta http-equiv=\"content-language\" content=\"es\">"
+ "</head><body>This page is in Spanish.</body></html>");
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("es", fake_translate_driver_.details_->adopted_language);
+ fake_translate_driver_.ResetNewPageValues();
+
+ content::PageState back_state = GetCurrentPageState();
+
+ LoadHTML("<html><head><meta http-equiv=\"content-language\" content=\"fr\">"
+ "</head><body>This page is in French.</body></html>");
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("fr", fake_translate_driver_.details_->adopted_language);
+ fake_translate_driver_.ResetNewPageValues();
+
+ GoBack(GURL("data:text/html;charset=utf-8,<html><head>"
+ "<meta http-equiv=\"content-language\" content=\"es\">"
+ "</head><body>This page is in Spanish.</body></html>"),
+ back_state);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(fake_translate_driver_.called_new_page_);
+ EXPECT_EQ("es", fake_translate_driver_.details_->adopted_language);
+}
diff --git a/chromium/chrome/renderer/translate/translate_script_browsertest.cc b/chromium/chrome/renderer/translate/translate_script_browsertest.cc
new file mode 100644
index 00000000000..69740d08a1c
--- /dev/null
+++ b/chromium/chrome/renderer/translate/translate_script_browsertest.cc
@@ -0,0 +1,237 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "components/grit/components_resources.h"
+#include "components/translate/core/common/translate_errors.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "v8/include/v8.h"
+
+using blink::WebScriptSource;
+
+namespace {
+
+// JavaScript code to set runtime test flags.
+const char kThrowInitializationError[] = "throwInitializationError = true";
+const char kThrowUnexpectedScriptError[] = "throwUnexpectedScriptError = true";
+const char kCallbackReturnBooleanError[] = "callbackReturnBooleanError = true";
+const char kCallbackReturnNumberError[] = "callbackReturnNumberError = true";
+const char kSetCallbackErrorCode[] = "callbackErrorCode = ";
+
+// JavaScript code to check if any error happens.
+const char kError[] = "cr.googleTranslate.error";
+
+// JavaScript code to get error code.
+const char kErrorCode[] = "cr.googleTranslate.errorCode";
+
+// JavaScript code to check if the library is ready.
+const char kLibReady[] = "cr.googleTranslate.libReady";
+
+// JavaScript code to perform translation.
+const char kTranslate[] = "cr.googleTranslate.translate('auto', 'en')";
+
+// JavaScript code to mimic element.js provided by a translate server.
+const char kElementJs[] =
+ "serverParams = '';"
+ "gtTimeInfo = {};"
+ "translateApiKey = '';"
+ "google = {};"
+ "google.translate = {};"
+ "google.translate.TranslateService = function() {"
+ " if (window['throwInitializationError']) {"
+ " throw 'API initialization error';"
+ " }"
+ " return {"
+ " isAvailable: function() { return true; },"
+ " restore: function() {},"
+ " translatePage: function(originalLang, targetLang, cb) {"
+ " if (window['throwUnexpectedScriptError']) {"
+ " throw 'all your base are belong to us';"
+ " }"
+ " if (window['callbackReturnBooleanError']) {"
+ " cb(0, false, true);"
+ " }"
+ " if (window['callbackReturnNumberError']) {"
+ " cb(0, false, callbackErrorCode);"
+ " }"
+ " }"
+ " };"
+ "};"
+ "cr.googleTranslate.onTranslateElementLoad();";
+
+std::string GenerateSetCallbackErrorCodeScript(int code) {
+ return base::StringPrintf("%s%d", kSetCallbackErrorCode, code);
+}
+
+} // namespace
+
+// This class is for testing resource/translate.js works and reports errors
+// correctly.
+class TranslateScriptBrowserTest : public ChromeRenderViewTest {
+ public:
+ TranslateScriptBrowserTest() {}
+
+ protected:
+ void InjectElementLibrary() {
+ std::string script =
+ ui::ResourceBundle::GetSharedInstance().DecompressDataResource(
+ IDR_TRANSLATE_JS);
+ script += kElementJs;
+ ExecuteScript(script);
+ }
+
+ void ExecuteScript(const std::string& script) {
+ WebScriptSource source =
+ WebScriptSource(blink::WebString::FromASCII(script));
+ GetMainFrame()->ExecuteScript(source);
+ }
+
+ bool GetError() {
+ return ExecuteScriptAndGetBoolResult(kError);
+ }
+
+ double GetErrorCode() {
+ return ExecuteScriptAndGetNumberResult(kErrorCode);
+ }
+
+ bool IsLibReady() {
+ return ExecuteScriptAndGetBoolResult(kLibReady);
+ }
+
+ private:
+ double ExecuteScriptAndGetNumberResult(const std::string& script) {
+ WebScriptSource source =
+ WebScriptSource(blink::WebString::FromASCII(script));
+ v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
+ v8::Local<v8::Value> result =
+ GetMainFrame()->ExecuteScriptAndReturnValue(source);
+ if (result.IsEmpty() || !result->IsNumber()) {
+ NOTREACHED();
+ // TODO(toyoshim): Return NaN here and the real implementation in
+ // TranslateHelper::ExecuteScriptAndGetDoubleResult().
+ return 0.0;
+ }
+ return result.As<v8::Number>()->Value();
+ }
+
+ bool ExecuteScriptAndGetBoolResult(const std::string& script) {
+ WebScriptSource source =
+ WebScriptSource(blink::WebString::FromASCII(script));
+ v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
+ v8::Local<v8::Value> result =
+ GetMainFrame()->ExecuteScriptAndReturnValue(source);
+ if (result.IsEmpty() || !result->IsBoolean()) {
+ NOTREACHED();
+ return false;
+ }
+ return result.As<v8::Boolean>()->Value();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TranslateScriptBrowserTest);
+};
+
+// Test if onTranslateElementLoad() succeeds to initialize the element library.
+TEST_F(TranslateScriptBrowserTest, ElementLoadSuccess) {
+ InjectElementLibrary();
+ EXPECT_TRUE(IsLibReady());
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+}
+
+// Test if onTranslateElementLoad() fails to initialize the element library and
+// report the right error code.
+TEST_F(TranslateScriptBrowserTest, ElementLoadFailure) {
+ ExecuteScript(kThrowInitializationError);
+
+ InjectElementLibrary();
+ EXPECT_FALSE(IsLibReady());
+ EXPECT_TRUE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::INITIALIZATION_ERROR, GetErrorCode());
+}
+
+// Test if cr.googleTranslate.translate() works.
+TEST_F(TranslateScriptBrowserTest, TranslateSuccess) {
+ InjectElementLibrary();
+ EXPECT_TRUE(IsLibReady());
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+
+ ExecuteScript(kTranslate);
+
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+}
+
+// Test if cr.googleTranslate.translate() handles library exception correctly.
+TEST_F(TranslateScriptBrowserTest, TranslateFail) {
+ ExecuteScript(kThrowUnexpectedScriptError);
+
+ InjectElementLibrary();
+ EXPECT_TRUE(IsLibReady());
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+
+ ExecuteScript(kTranslate);
+
+ EXPECT_TRUE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::UNEXPECTED_SCRIPT_ERROR,
+ GetErrorCode());
+}
+
+// Test if onTranslateProgress callback handles boolean type error correctly.
+// Remove this test once server side changes the API to return a number.
+TEST_F(TranslateScriptBrowserTest, CallbackGetBooleanError) {
+ ExecuteScript(kCallbackReturnBooleanError);
+
+ InjectElementLibrary();
+ EXPECT_TRUE(IsLibReady());
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+
+ ExecuteScript(kTranslate);
+
+ EXPECT_TRUE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::TRANSLATION_ERROR, GetErrorCode());
+}
+
+// Test if onTranslateProgress callback handles number type error correctly and
+// converts it to the proper error code.
+TEST_F(TranslateScriptBrowserTest, CallbackGetNumberError1) {
+ ExecuteScript(kCallbackReturnNumberError);
+ ExecuteScript(GenerateSetCallbackErrorCodeScript(1));
+
+ InjectElementLibrary();
+ EXPECT_TRUE(IsLibReady());
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+
+ ExecuteScript(kTranslate);
+
+ EXPECT_TRUE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::TRANSLATION_ERROR, GetErrorCode());
+}
+
+// Test if onTranslateProgress callback handles number type error correctly and
+// converts it to the proper error code.
+TEST_F(TranslateScriptBrowserTest, CallbackGetNumberError2) {
+ ExecuteScript(kCallbackReturnNumberError);
+ ExecuteScript(GenerateSetCallbackErrorCodeScript(2));
+
+ InjectElementLibrary();
+ EXPECT_TRUE(IsLibReady());
+ EXPECT_FALSE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::NONE, GetErrorCode());
+
+ ExecuteScript(kTranslate);
+
+ EXPECT_TRUE(GetError());
+ EXPECT_EQ(translate::TranslateErrors::UNSUPPORTED_LANGUAGE, GetErrorCode());
+}
+
+// TODO(toyoshim): Add test for onLoadJavaScript.
diff --git a/chromium/chrome/renderer/url_loader_throttle_provider_impl.cc b/chromium/chrome/renderer/url_loader_throttle_provider_impl.cc
new file mode 100644
index 00000000000..1642d5d5fa7
--- /dev/null
+++ b/chromium/chrome/renderer/url_loader_throttle_provider_impl.cc
@@ -0,0 +1,277 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/url_loader_throttle_provider_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/common/google_url_loader_throttle.h"
+#include "chrome/common/prerender.mojom.h"
+#include "chrome/common/prerender_url_loader_throttle.h"
+#include "chrome/renderer/chrome_content_renderer_client.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+#include "chrome/renderer/prerender/prerender_dispatcher.h"
+#include "chrome/renderer/prerender/prerender_helper.h"
+#include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
+#include "chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h"
+#include "components/data_reduction_proxy/content/common/data_reduction_proxy_url_loader_throttle.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_throttle_manager.h"
+#include "components/safe_browsing/features.h"
+#include "components/safe_browsing/renderer/renderer_url_loader_throttle.h"
+#include "content/public/common/content_features.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/common/switches.h"
+#include "extensions/renderer/extension_throttle_manager.h"
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "chrome/renderer/chromeos_merge_session_loader_throttle.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace {
+
+chrome::mojom::PrerenderCanceler* GetPrerenderCanceller(int render_frame_id) {
+ content::RenderFrame* render_frame =
+ content::RenderFrame::FromRoutingID(render_frame_id);
+ if (!render_frame)
+ return nullptr;
+ prerender::PrerenderHelper* helper =
+ prerender::PrerenderHelper::Get(render_frame);
+ if (!helper)
+ return nullptr;
+
+ auto* canceler = new mojo::Remote<chrome::mojom::PrerenderCanceler>;
+ render_frame->GetRemoteInterfaces()->GetInterface(
+ canceler->BindNewPipeAndPassReceiver());
+ base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, canceler);
+ return canceler->get();
+}
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+std::unique_ptr<extensions::ExtensionThrottleManager>
+CreateExtensionThrottleManager() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kDisableExtensionsHttpThrottling)) {
+ return nullptr;
+ }
+ return std::make_unique<extensions::ExtensionThrottleManager>();
+}
+
+void SetExtensionThrottleManagerTestPolicy(
+ extensions::ExtensionThrottleManager* extension_throttle_manager) {
+ std::unique_ptr<net::BackoffEntry::Policy> policy(
+ new net::BackoffEntry::Policy{
+ // Number of initial errors (in sequence) to ignore before
+ // applying exponential back-off rules.
+ 1,
+
+ // Initial delay for exponential back-off in ms.
+ 10 * 60 * 1000,
+
+ // Factor by which the waiting time will be multiplied.
+ 10,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.1,
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 15 * 60 * 1000,
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+ });
+ extension_throttle_manager->SetBackoffPolicyForTests(std::move(policy));
+}
+#endif
+
+} // namespace
+
+URLLoaderThrottleProviderImpl::URLLoaderThrottleProviderImpl(
+ blink::ThreadSafeBrowserInterfaceBrokerProxy* broker,
+ content::URLLoaderThrottleProviderType type,
+ ChromeContentRendererClient* chrome_content_renderer_client)
+ : type_(type),
+ chrome_content_renderer_client_(chrome_content_renderer_client) {
+ DETACH_FROM_THREAD(thread_checker_);
+ broker->GetInterface(safe_browsing_remote_.InitWithNewPipeAndPassReceiver());
+ if (data_reduction_proxy::params::IsEnabledWithNetworkService()) {
+ broker->GetInterface(
+ data_reduction_proxy_remote_.InitWithNewPipeAndPassReceiver());
+ }
+}
+
+URLLoaderThrottleProviderImpl::~URLLoaderThrottleProviderImpl() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+URLLoaderThrottleProviderImpl::URLLoaderThrottleProviderImpl(
+ const URLLoaderThrottleProviderImpl& other)
+ : type_(other.type_),
+ chrome_content_renderer_client_(other.chrome_content_renderer_client_) {
+ DETACH_FROM_THREAD(thread_checker_);
+ if (other.safe_browsing_) {
+ other.safe_browsing_->Clone(
+ safe_browsing_remote_.InitWithNewPipeAndPassReceiver());
+ }
+ if (other.data_reduction_proxy_) {
+ other.data_reduction_proxy_->Clone(
+ data_reduction_proxy_remote_.InitWithNewPipeAndPassReceiver());
+ }
+ // An ad_delay_factory_ is created, rather than cloning the existing one.
+}
+
+std::unique_ptr<content::URLLoaderThrottleProvider>
+URLLoaderThrottleProviderImpl::Clone() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (safe_browsing_remote_)
+ safe_browsing_.Bind(std::move(safe_browsing_remote_));
+ if (data_reduction_proxy_remote_)
+ data_reduction_proxy_.Bind(std::move(data_reduction_proxy_remote_));
+ return base::WrapUnique(new URLLoaderThrottleProviderImpl(*this));
+}
+
+std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
+URLLoaderThrottleProviderImpl::CreateThrottles(
+ int render_frame_id,
+ const blink::WebURLRequest& request,
+ content::ResourceType resource_type) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
+
+ // Some throttles have already been added in the browser for frame resources.
+ // Don't add them for frame requests.
+ bool is_frame_resource = content::IsResourceTypeFrame(resource_type);
+
+ DCHECK(!is_frame_resource ||
+ type_ == content::URLLoaderThrottleProviderType::kFrame);
+
+ if (data_reduction_proxy::params::IsEnabledWithNetworkService()) {
+ if (data_reduction_proxy_remote_)
+ data_reduction_proxy_.Bind(std::move(data_reduction_proxy_remote_));
+ if (!data_reduction_proxy_manager_) {
+ data_reduction_proxy_manager_ = std::make_unique<
+ data_reduction_proxy::DataReductionProxyThrottleManager>(
+ data_reduction_proxy_.get(),
+ data_reduction_proxy::mojom::DataReductionProxyThrottleConfigPtr());
+ }
+ throttles.push_back(
+ std::make_unique<
+ data_reduction_proxy::DataReductionProxyURLLoaderThrottle>(
+ net::HttpRequestHeaders(), data_reduction_proxy_manager_.get()));
+ }
+
+ if (!is_frame_resource) {
+ if (safe_browsing_remote_)
+ safe_browsing_.Bind(std::move(safe_browsing_remote_));
+ throttles.push_back(
+ std::make_unique<safe_browsing::RendererURLLoaderThrottle>(
+ safe_browsing_.get(), render_frame_id));
+ }
+
+ if (type_ == content::URLLoaderThrottleProviderType::kFrame &&
+ !is_frame_resource) {
+ content::RenderFrame* render_frame =
+ content::RenderFrame::FromRoutingID(render_frame_id);
+ auto* prerender_helper =
+ render_frame ? prerender::PrerenderHelper::Get(
+ render_frame->GetRenderView()->GetMainRenderFrame())
+ : nullptr;
+ if (prerender_helper) {
+ auto throttle = std::make_unique<prerender::PrerenderURLLoaderThrottle>(
+ prerender_helper->prerender_mode(),
+ prerender_helper->histogram_prefix(),
+ base::BindOnce(GetPrerenderCanceller, render_frame_id),
+ base::ThreadTaskRunnerHandle::Get());
+ prerender_helper->AddThrottle(throttle->AsWeakPtr());
+ if (prerender_helper->prerender_mode() == prerender::PREFETCH_ONLY) {
+ auto* prerender_dispatcher =
+ chrome_content_renderer_client_->prerender_dispatcher();
+ prerender_dispatcher->IncrementPrefetchCount();
+ throttle->set_destruction_closure(base::BindOnce(
+ &prerender::PrerenderDispatcher::DecrementPrefetchCount,
+ base::Unretained(prerender_dispatcher)));
+ }
+ throttles.push_back(std::move(throttle));
+ }
+ }
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (type_ == content::URLLoaderThrottleProviderType::kFrame &&
+ resource_type == content::ResourceType::kObject) {
+ content::RenderFrame* render_frame =
+ content::RenderFrame::FromRoutingID(render_frame_id);
+ auto mime_handlers =
+ extensions::MimeHandlerViewContainer::FromRenderFrame(render_frame);
+ GURL gurl(request.Url());
+ for (auto* handler : mime_handlers) {
+ auto throttle = handler->MaybeCreatePluginThrottle(gurl);
+ if (throttle) {
+ throttles.push_back(std::move(throttle));
+ break;
+ }
+ }
+ }
+
+ if (!extension_throttle_manager_)
+ extension_throttle_manager_ = CreateExtensionThrottleManager();
+
+ if (extension_throttle_manager_) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kSetExtensionThrottleTestParams)) {
+ SetExtensionThrottleManagerTestPolicy(extension_throttle_manager_.get());
+ }
+
+ std::unique_ptr<blink::URLLoaderThrottle> throttle =
+ extension_throttle_manager_->MaybeCreateURLLoaderThrottle(request);
+ if (throttle)
+ throttles.push_back(std::move(throttle));
+ }
+#endif
+
+ throttles.push_back(std::make_unique<GoogleURLLoaderThrottle>(
+ ChromeRenderThreadObserver::is_incognito_process(),
+ ChromeRenderThreadObserver::GetDynamicParams()));
+
+#if defined(OS_CHROMEOS)
+ throttles.push_back(std::make_unique<MergeSessionLoaderThrottle>(
+ chrome_content_renderer_client_->GetChromeObserver()
+ ->chromeos_listener()));
+#endif // defined(OS_CHROMEOS)
+
+ if (subresource_redirect::ShouldForceEnableSubresourceRedirect() &&
+ resource_type == content::ResourceType::kImage &&
+ GURL(request.Url()).SchemeIs(url::kHttpsScheme)) {
+ throttles.push_back(
+ std::make_unique<
+ subresource_redirect::SubresourceRedirectURLLoaderThrottle>());
+ }
+
+ return throttles;
+}
+
+void URLLoaderThrottleProviderImpl::SetOnline(bool is_online) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (extension_throttle_manager_)
+ extension_throttle_manager_->SetOnline(is_online);
+#endif
+}
diff --git a/chromium/chrome/renderer/url_loader_throttle_provider_impl.h b/chromium/chrome/renderer/url_loader_throttle_provider_impl.h
new file mode 100644
index 00000000000..ae5b86d9b69
--- /dev/null
+++ b/chromium/chrome/renderer/url_loader_throttle_provider_impl.h
@@ -0,0 +1,78 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_URL_LOADER_THROTTLE_PROVIDER_IMPL_H_
+#define CHROME_RENDERER_URL_LOADER_THROTTLE_PROVIDER_IMPL_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/threading/thread_checker.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy.mojom.h"
+#include "components/safe_browsing/common/safe_browsing.mojom.h"
+#include "content/public/renderer/url_loader_throttle_provider.h"
+#include "extensions/buildflags/buildflags.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "extensions/renderer/extension_throttle_manager.h"
+#endif
+
+namespace data_reduction_proxy {
+class DataReductionProxyThrottleManager;
+}
+
+class ChromeContentRendererClient;
+
+// Instances must be constructed on the render thread, and then used and
+// destructed on a single thread, which can be different from the render thread.
+class URLLoaderThrottleProviderImpl
+ : public content::URLLoaderThrottleProvider {
+ public:
+ URLLoaderThrottleProviderImpl(
+ blink::ThreadSafeBrowserInterfaceBrokerProxy* broker,
+ content::URLLoaderThrottleProviderType type,
+ ChromeContentRendererClient* chrome_content_renderer_client);
+
+ ~URLLoaderThrottleProviderImpl() override;
+
+ // content::URLLoaderThrottleProvider implementation.
+ std::unique_ptr<content::URLLoaderThrottleProvider> Clone() override;
+ std::vector<std::unique_ptr<blink::URLLoaderThrottle>> CreateThrottles(
+ int render_frame_id,
+ const blink::WebURLRequest& request,
+ content::ResourceType resource_type) override;
+ void SetOnline(bool is_online) override;
+
+ private:
+ // This copy constructor works in conjunction with Clone(), not intended for
+ // general use.
+ URLLoaderThrottleProviderImpl(const URLLoaderThrottleProviderImpl& other);
+
+ content::URLLoaderThrottleProviderType type_;
+ ChromeContentRendererClient* const chrome_content_renderer_client_;
+
+ mojo::PendingRemote<safe_browsing::mojom::SafeBrowsing> safe_browsing_remote_;
+ mojo::Remote<safe_browsing::mojom::SafeBrowsing> safe_browsing_;
+
+ mojo::PendingRemote<data_reduction_proxy::mojom::DataReductionProxy>
+ data_reduction_proxy_remote_;
+ mojo::Remote<data_reduction_proxy::mojom::DataReductionProxy>
+ data_reduction_proxy_;
+ std::unique_ptr<data_reduction_proxy::DataReductionProxyThrottleManager>
+ data_reduction_proxy_manager_;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ std::unique_ptr<extensions::ExtensionThrottleManager>
+ extension_throttle_manager_;
+#endif
+
+ THREAD_CHECKER(thread_checker_);
+
+ DISALLOW_ASSIGN(URLLoaderThrottleProviderImpl);
+};
+
+#endif // CHROME_RENDERER_URL_LOADER_THROTTLE_PROVIDER_IMPL_H_
diff --git a/chromium/chrome/renderer/v8_unwinder.cc b/chromium/chrome/renderer/v8_unwinder.cc
new file mode 100644
index 00000000000..af2fdbfce72
--- /dev/null
+++ b/chromium/chrome/renderer/v8_unwinder.cc
@@ -0,0 +1,112 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/v8_unwinder.h"
+
+#include <memory>
+#include <string>
+
+#include "base/strings/strcat.h"
+
+namespace {
+
+class V8Module : public base::ModuleCache::Module {
+ public:
+ V8Module(const v8::MemoryRange& memory_range,
+ const std::string& build_id,
+ const std::string& descriptor)
+ : memory_range_(memory_range),
+ build_id_(build_id),
+ descriptor_(descriptor) {}
+
+ V8Module(const V8Module&) = delete;
+ V8Module& operator=(const V8Module&) = delete;
+
+ // ModuleCache::Module
+ uintptr_t GetBaseAddress() const override {
+ return reinterpret_cast<uintptr_t>(memory_range_.start);
+ }
+
+ std::string GetId() const override { return build_id_; }
+
+ base::FilePath GetDebugBasename() const override {
+ return base::FilePath().AppendASCII(base::StrCat({"V8 ", descriptor_}));
+ }
+
+ size_t GetSize() const override { return memory_range_.length_in_bytes; }
+
+ bool IsNative() const override { return false; }
+
+ private:
+ const v8::MemoryRange memory_range_;
+ const std::string build_id_;
+ const std::string descriptor_;
+};
+
+} // namespace
+
+V8Unwinder::V8Unwinder(const v8::UnwindState& unwind_state)
+ : unwind_state_(unwind_state) {}
+
+V8Unwinder::~V8Unwinder() = default;
+
+void V8Unwinder::AddNonNativeModules(base::ModuleCache* module_cache) {
+ std::vector<std::unique_ptr<base::ModuleCache::Module>> modules;
+ modules.emplace_back(std::make_unique<V8Module>(
+ unwind_state_.embedded_code_range, kV8EmbeddedCodeRangeBuildId,
+ "Embedded Code Range"));
+ modules.emplace_back(std::make_unique<V8Module>(
+ unwind_state_.code_range, kV8CodeRangeBuildId, "Code Range"));
+ for (auto& module : modules) {
+ v8_modules_.insert(module.get());
+ module_cache->AddNonNativeModule(std::move(module));
+ }
+}
+
+bool V8Unwinder::CanUnwindFrom(const base::Frame* current_frame) const {
+ return v8_modules_.find(current_frame->module) != v8_modules_.end();
+}
+
+base::UnwindResult V8Unwinder::TryUnwind(
+ base::RegisterContext* thread_context,
+ uintptr_t stack_top,
+ base::ModuleCache* module_cache,
+ std::vector<base::Frame>* stack) const {
+ v8::RegisterState register_state;
+ register_state.pc = reinterpret_cast<void*>(
+ base::RegisterContextInstructionPointer(thread_context));
+ register_state.sp = reinterpret_cast<void*>(
+ base::RegisterContextStackPointer(thread_context));
+ register_state.fp = reinterpret_cast<void*>(
+ base::RegisterContextFramePointer(thread_context));
+
+ if (!v8::Unwinder::TryUnwindV8Frames(
+ unwind_state_, &register_state,
+ reinterpret_cast<const void*>(stack_top))) {
+ return base::UnwindResult::ABORTED;
+ }
+
+ base::RegisterContextInstructionPointer(thread_context) =
+ reinterpret_cast<uintptr_t>(register_state.pc);
+ base::RegisterContextStackPointer(thread_context) =
+ reinterpret_cast<uintptr_t>(register_state.sp);
+ base::RegisterContextFramePointer(thread_context) =
+ reinterpret_cast<uintptr_t>(register_state.fp);
+
+ stack->emplace_back(
+ base::RegisterContextInstructionPointer(thread_context),
+ module_cache->GetModuleForAddress(
+ base::RegisterContextInstructionPointer(thread_context)));
+
+ return base::UnwindResult::UNRECOGNIZED_FRAME;
+}
+
+// Synthetic build ids to use for V8 modules. The difference is in the digit
+// after the leading 5's.
+// clang-format off
+const char V8Unwinder::kV8EmbeddedCodeRangeBuildId[] =
+ "5555555507284E1E874EFA4EB754964B999";
+const char V8Unwinder::kV8CodeRangeBuildId[] =
+ "5555555517284E1E874EFA4EB754964B999";
+// clang-format on
diff --git a/chromium/chrome/renderer/v8_unwinder.h b/chromium/chrome/renderer/v8_unwinder.h
new file mode 100644
index 00000000000..944cd8e183a
--- /dev/null
+++ b/chromium/chrome/renderer/v8_unwinder.h
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_V8_UNWINDER_H_
+#define CHROME_RENDERER_V8_UNWINDER_H_
+
+#include "base/containers/flat_set.h"
+#include "base/profiler/unwinder.h"
+#include "v8/include/v8.h"
+
+// Implements stack frame unwinding for V8 generated code frames, for use with
+// the StackSamplingProfiler.
+class V8Unwinder : public base::Unwinder {
+ public:
+ explicit V8Unwinder(const v8::UnwindState& unwind_state);
+ ~V8Unwinder() override;
+
+ V8Unwinder(const V8Unwinder&) = delete;
+ V8Unwinder& operator=(const V8Unwinder&) = delete;
+
+ // Unwinder:
+ void AddNonNativeModules(base::ModuleCache* module_cache) override;
+ bool CanUnwindFrom(const base::Frame* current_frame) const override;
+ base::UnwindResult TryUnwind(base::RegisterContext* thread_context,
+ uintptr_t stack_top,
+ base::ModuleCache* module_cache,
+ std::vector<base::Frame>* stack) const override;
+
+ // Build ids generated by the unwinder. Exposed for test use.
+ static const char kV8EmbeddedCodeRangeBuildId[];
+ static const char kV8CodeRangeBuildId[];
+
+ private:
+ const v8::UnwindState unwind_state_;
+ base::flat_set<const base::ModuleCache::Module*> v8_modules_;
+};
+
+#endif // CHROME_RENDERER_V8_UNWINDER_H_
diff --git a/chromium/chrome/renderer/v8_unwinder_unittest.cc b/chromium/chrome/renderer/v8_unwinder_unittest.cc
new file mode 100644
index 00000000000..0d52efc25c6
--- /dev/null
+++ b/chromium/chrome/renderer/v8_unwinder_unittest.cc
@@ -0,0 +1,223 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/v8_unwinder.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/profiler/stack_sampling_profiler_test_util.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "gin/public/isolate_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "v8/include/v8.h"
+
+namespace {
+
+v8::Local<v8::String> ToV8String(const char* str) {
+ return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), str,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+}
+
+v8::Local<v8::Object> CreatePointerHolder(const void* ptr) {
+ v8::Local<v8::ObjectTemplate> object_template =
+ v8::ObjectTemplate::New(v8::Isolate::GetCurrent());
+ object_template->SetInternalFieldCount(1);
+ v8::Local<v8::Object> holder =
+ object_template
+ ->NewInstance(v8::Isolate::GetCurrent()->GetCurrentContext())
+ .ToLocalChecked();
+ holder->SetAlignedPointerInInternalField(0, const_cast<void*>(ptr));
+ return holder;
+}
+
+template <typename T>
+T* GetPointerFromHolder(v8::Local<v8::Object> holder) {
+ return reinterpret_cast<T*>(holder->GetAlignedPointerFromInternalField(0));
+}
+
+// Sets up the environment necessary to execute V8 code.
+class ScopedV8Environment {
+ public:
+ ScopedV8Environment()
+ : isolate_holder_(task_environment_.GetMainThreadTaskRunner(),
+ gin::IsolateHolder::IsolateType::kBlinkMainThread) {
+ isolate()->Enter();
+ v8::HandleScope handle_scope(isolate());
+ context_.Reset(isolate(), v8::Context::New(isolate()));
+ v8::Local<v8::Context>::New(isolate(), context_)->Enter();
+ }
+
+ ~ScopedV8Environment() {
+ {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context>::New(isolate(), context_)->Exit();
+ context_.Reset();
+ }
+ isolate()->Exit();
+ }
+
+ v8::Isolate* isolate() { return isolate_holder_.isolate(); }
+
+ private:
+ base::test::TaskEnvironment task_environment_;
+ gin::IsolateHolder isolate_holder_;
+ v8::Persistent<v8::Context> context_;
+};
+
+// C++ function to be invoked from V8 which calls back into the provided closure
+// pointer (passed via a holder object) to wait for a stack sample to be taken.
+void WaitForSampleNative(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ base::OnceClosure* wait_for_sample =
+ GetPointerFromHolder<base::OnceClosure>(info[0].As<v8::Object>());
+ if (wait_for_sample)
+ std::move(*wait_for_sample).Run();
+}
+
+// Causes a stack sample to be taken after setting up a call stack from C++ to
+// JavaScript and back into C++.
+base::FunctionAddressRange CallThroughV8(
+ const base::RepeatingCallback<void(const v8::UnwindState&)>&
+ report_unwind_state,
+ base::OnceClosure wait_for_sample) {
+ const void* start_program_counter = base::GetProgramCounter();
+
+ if (wait_for_sample) {
+ // Set up V8 runtime environment.
+ // Allows use of natives (functions starting with '%') within JavaScript
+ // code, which allows us to control compilation of the JavaScript function
+ // we define.
+ // TODO(wittman): The flag should be set only for the duration of this test
+ // but the V8 API currently doesn't support this. http://crbug.com/v8/9210
+ // covers adding the necessary functionality to V8.
+ v8::V8::SetFlagsFromString("--allow-natives-syntax");
+ ScopedV8Environment v8_environment;
+ v8::Isolate* isolate = v8_environment.isolate();
+ report_unwind_state.Run(isolate->GetUnwindState());
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+
+ // Define a V8 function WaitForSampleNative() backed by the C++ function
+ // WaitForSampleNative().
+ v8::Local<v8::FunctionTemplate> js_wait_for_sample_native_template =
+ v8::FunctionTemplate::New(isolate, WaitForSampleNative);
+ v8::Local<v8::Function> js_wait_for_sample_native =
+ js_wait_for_sample_native_template->GetFunction(context)
+ .ToLocalChecked();
+ js_wait_for_sample_native->SetName(ToV8String("WaitForSampleNative"));
+ context->Global()
+ ->Set(context, ToV8String("WaitForSampleNative"),
+ js_wait_for_sample_native)
+ .FromJust();
+
+ // Run a script to create the V8 function waitForSample() that invokes
+ // WaitForSampleNative(), and a function that ensures that waitForSample()
+ // gets compiled. waitForSample() just passes the holder object for the
+ // pointer to the wait_for_sample Closure back into the C++ code. We ensure
+ // that the function is compiled to test walking through both builtin and
+ // runtime-generated code.
+ const char kWaitForSampleJs[] = R"(
+ function waitForSample(closure_pointer_holder) {
+ if (closure_pointer_holder)
+ WaitForSampleNative(closure_pointer_holder);
+ }
+
+ // Set up the function to be compiled rather than interpreted.
+ function compileWaitForSample(closure_pointer_holder) {
+ %PrepareFunctionForOptimization(waitForSample);
+ waitForSample(closure_pointer_holder);
+ waitForSample(closure_pointer_holder);
+ %OptimizeFunctionOnNextCall(waitForSample);
+ }
+ )";
+ v8::Local<v8::Script> script =
+ v8::Script::Compile(context, ToV8String(kWaitForSampleJs))
+ .ToLocalChecked();
+ script->Run(context).ToLocalChecked();
+
+ // Run compileWaitForSample(), using a null closure pointer to avoid
+ // actually waiting.
+ v8::Local<v8::Function> js_compile_wait_for_sample =
+ v8::Local<v8::Function>::Cast(
+ context->Global()
+ ->Get(context, ToV8String("compileWaitForSample"))
+ .ToLocalChecked());
+ v8::Local<v8::Value> argv[] = {CreatePointerHolder(nullptr)};
+ js_compile_wait_for_sample
+ ->Call(context, v8::Undefined(isolate), base::size(argv), argv)
+ .ToLocalChecked();
+
+ // Run waitForSample() with the real closure pointer.
+ argv[0] = CreatePointerHolder(&wait_for_sample);
+ v8::Local<v8::Function> js_wait_for_sample = v8::Local<v8::Function>::Cast(
+ context->Global()
+ ->Get(context, ToV8String("waitForSample"))
+ .ToLocalChecked());
+ js_wait_for_sample
+ ->Call(context, v8::Undefined(isolate), base::size(argv), argv)
+ .ToLocalChecked();
+ }
+
+ // Volatile to prevent a tail call to GetProgramCounter().
+ const void* volatile end_program_counter = base::GetProgramCounter();
+ return {start_program_counter, end_program_counter};
+}
+
+} // namespace
+
+// Checks that unwinding from C++ through JavaScript and back into C++ succeeds.
+// NB: unwinding is only supported for 64 bit Windows and OS X.
+#if (defined(OS_WIN) && defined(ARCH_CPU_64_BITS)) || defined(OS_MACOSX)
+#define MAYBE_UnwindThroughV8Frames UnwindThroughV8Frames
+#else
+#define MAYBE_UnwindThroughV8Frames DISABLED_UnwindThroughV8Frames
+#endif
+TEST(V8UnwinderTest, MAYBE_UnwindThroughV8Frames) {
+ v8::UnwindState unwind_state;
+ base::WaitableEvent unwind_state_available;
+
+ const auto set_unwind_state = [&](const v8::UnwindState& state) {
+ unwind_state = state;
+ unwind_state_available.Signal();
+ };
+
+ const auto create_v8_unwinder = [&]() -> std::unique_ptr<base::Unwinder> {
+ unwind_state_available.Wait();
+ return std::make_unique<V8Unwinder>(unwind_state);
+ };
+
+ base::UnwindScenario scenario(base::BindRepeating(
+ &CallThroughV8, base::BindLambdaForTesting(set_unwind_state)));
+ base::ModuleCache module_cache;
+
+ std::vector<base::Frame> sample = SampleScenario(
+ &scenario, &module_cache, base::BindLambdaForTesting(create_v8_unwinder));
+
+ // The stack should contain a full unwind.
+ ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(),
+ scenario.GetSetupFunctionAddressRange(),
+ scenario.GetOuterFunctionAddressRange()});
+
+ // The stack should contain a frame from a JavaScript module.
+ auto loc =
+ std::find_if(sample.begin(), sample.end(), [&](const base::Frame& frame) {
+ return frame.module &&
+ (frame.module->GetId() ==
+ V8Unwinder::kV8EmbeddedCodeRangeBuildId ||
+ frame.module->GetId() == V8Unwinder::kV8CodeRangeBuildId);
+ });
+ EXPECT_NE(sample.end(), loc);
+}
diff --git a/chromium/chrome/renderer/web_apps.cc b/chromium/chrome/renderer/web_apps.cc
new file mode 100644
index 00000000000..b79fb0c6a64
--- /dev/null
+++ b/chromium/chrome/renderer/web_apps.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/web_apps.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "chrome/common/web_application_info.h"
+#include "third_party/blink/public/platform/web_icon_sizes_parser.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_node.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebLocalFrame;
+using blink::WebNode;
+using blink::WebString;
+
+namespace web_apps {
+namespace {
+
+void AddInstallIcon(const WebElement& link,
+ std::vector<WebApplicationIconInfo>* icons) {
+ WebString href = link.GetAttribute("href");
+ if (href.IsNull() || href.IsEmpty())
+ return;
+
+ // Get complete url.
+ GURL url = link.GetDocument().CompleteURL(href);
+ if (!url.is_valid())
+ return;
+
+ WebApplicationIconInfo icon_info;
+ if (link.HasAttribute("sizes")) {
+ blink::WebVector<blink::WebSize> icon_sizes =
+ blink::WebIconSizesParser::ParseIconSizes(link.GetAttribute("sizes"));
+ if (icon_sizes.size() == 1 &&
+ icon_sizes[0].width != 0 &&
+ icon_sizes[0].height != 0) {
+ icon_info.width = icon_sizes[0].width;
+ icon_info.height = icon_sizes[0].height;
+ }
+ }
+ icon_info.url = url;
+ icons->push_back(icon_info);
+}
+
+} // namespace
+
+void ParseWebAppFromWebDocument(WebLocalFrame* frame,
+ WebApplicationInfo* app_info) {
+ WebDocument document = frame->GetDocument();
+ if (document.IsNull())
+ return;
+
+ WebElement head = document.Head();
+ if (head.IsNull())
+ return;
+
+ GURL document_url = document.Url();
+ for (WebNode child = head.FirstChild(); !child.IsNull();
+ child = child.NextSibling()) {
+ if (!child.IsElementNode())
+ continue;
+ WebElement elem = child.To<WebElement>();
+
+ if (elem.HasHTMLTagName("link")) {
+ std::string rel = elem.GetAttribute("rel").Utf8();
+ // "rel" attribute may use either "icon" or "shortcut icon".
+ // see also
+ // <http://en.wikipedia.org/wiki/Favicon>
+ // <http://dev.w3.org/html5/spec/Overview.html#rel-icon>
+ //
+ // Bookmark apps also support "apple-touch-icon" and
+ // "apple-touch-icon-precomposed".
+ if (base::LowerCaseEqualsASCII(rel, "icon") ||
+ base::LowerCaseEqualsASCII(rel, "shortcut icon") ||
+ base::LowerCaseEqualsASCII(rel, "apple-touch-icon") ||
+ base::LowerCaseEqualsASCII(rel, "apple-touch-icon-precomposed")) {
+ AddInstallIcon(elem, &app_info->icons);
+ }
+ } else if (elem.HasHTMLTagName("meta") && elem.HasAttribute("name")) {
+ std::string name = elem.GetAttribute("name").Utf8();
+ WebString content = elem.GetAttribute("content");
+ if (name == "application-name") {
+ app_info->title = content.Utf16();
+ } else if (name == "description") {
+ app_info->description = content.Utf16();
+ } else if (name == "application-url") {
+ std::string url = content.Utf8();
+ app_info->app_url = document_url.is_valid() ?
+ document_url.Resolve(url) : GURL(url);
+ if (!app_info->app_url.is_valid())
+ app_info->app_url = GURL();
+ } else if (name == "mobile-web-app-capable" &&
+ base::LowerCaseEqualsASCII(content.Utf16(), "yes")) {
+ app_info->mobile_capable = WebApplicationInfo::MOBILE_CAPABLE;
+ } else if (name == "apple-mobile-web-app-capable" &&
+ base::LowerCaseEqualsASCII(content.Utf16(), "yes") &&
+ app_info->mobile_capable ==
+ WebApplicationInfo::MOBILE_CAPABLE_UNSPECIFIED) {
+ app_info->mobile_capable = WebApplicationInfo::MOBILE_CAPABLE_APPLE;
+ }
+ }
+ }
+}
+
+} // namespace web_apps
diff --git a/chromium/chrome/renderer/web_apps.h b/chromium/chrome/renderer/web_apps.h
new file mode 100644
index 00000000000..0437286b60b
--- /dev/null
+++ b/chromium/chrome/renderer/web_apps.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_WEB_APPS_H_
+#define CHROME_RENDERER_WEB_APPS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace blink {
+class WebLocalFrame;
+}
+
+struct WebApplicationInfo;
+
+namespace web_apps {
+
+// Parses the icon's size attribute as defined in the HTML 5 spec. Returns true
+// on success, false on errors. On success either all the sizes specified in
+// the attribute are added to sizes, or is_any is set to true.
+//
+// You shouldn't have a need to invoke this directly, it's public for testing.
+bool ParseIconSizes(const base::string16& text, std::vector<gfx::Size>* sizes,
+ bool* is_any);
+
+// Parses |app_info| information out of the document in WebFrame. Note that the
+// document may contain no web application information, in which case |app_info|
+// is unchanged.
+void ParseWebAppFromWebDocument(blink::WebLocalFrame* frame,
+ WebApplicationInfo* app_info);
+
+} // namespace web_apps
+
+#endif // CHROME_RENDERER_WEB_APPS_H_
diff --git a/chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.cc b/chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.cc
new file mode 100644
index 00000000000..8467900634e
--- /dev/null
+++ b/chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.cc
@@ -0,0 +1,53 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/websocket_handshake_throttle_provider_impl.h"
+
+#include <utility>
+
+#include "components/safe_browsing/renderer/websocket_sb_handshake_throttle.h"
+#include "content/public/common/service_names.mojom.h"
+#include "content/public/renderer/render_thread.h"
+#include "third_party/blink/public/platform/websocket_handshake_throttle.h"
+
+WebSocketHandshakeThrottleProviderImpl::WebSocketHandshakeThrottleProviderImpl(
+ blink::ThreadSafeBrowserInterfaceBrokerProxy* broker) {
+ DETACH_FROM_THREAD(thread_checker_);
+ broker->GetInterface(safe_browsing_remote_.InitWithNewPipeAndPassReceiver());
+}
+
+WebSocketHandshakeThrottleProviderImpl::
+ ~WebSocketHandshakeThrottleProviderImpl() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+WebSocketHandshakeThrottleProviderImpl::WebSocketHandshakeThrottleProviderImpl(
+ const WebSocketHandshakeThrottleProviderImpl& other) {
+ DETACH_FROM_THREAD(thread_checker_);
+ DCHECK(other.safe_browsing_);
+ other.safe_browsing_->Clone(
+ safe_browsing_remote_.InitWithNewPipeAndPassReceiver());
+}
+
+std::unique_ptr<content::WebSocketHandshakeThrottleProvider>
+WebSocketHandshakeThrottleProviderImpl::Clone(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (safe_browsing_remote_)
+ safe_browsing_.Bind(std::move(safe_browsing_remote_),
+ std::move(task_runner));
+ return base::WrapUnique(new WebSocketHandshakeThrottleProviderImpl(*this));
+}
+
+std::unique_ptr<blink::WebSocketHandshakeThrottle>
+WebSocketHandshakeThrottleProviderImpl::CreateThrottle(
+ int render_frame_id,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (safe_browsing_remote_)
+ safe_browsing_.Bind(std::move(safe_browsing_remote_),
+ std::move(task_runner));
+ return std::make_unique<safe_browsing::WebSocketSBHandshakeThrottle>(
+ safe_browsing_.get(), render_frame_id);
+}
diff --git a/chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.h b/chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.h
new file mode 100644
index 00000000000..06465beda09
--- /dev/null
+++ b/chromium/chrome/renderer/websocket_handshake_throttle_provider_impl.h
@@ -0,0 +1,48 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_WEBSOCKET_HANDSHAKE_THROTTLE_PROVIDER_IMPL_H_
+#define CHROME_RENDERER_WEBSOCKET_HANDSHAKE_THROTTLE_PROVIDER_IMPL_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "components/safe_browsing/common/safe_browsing.mojom.h"
+#include "content/public/renderer/websocket_handshake_throttle_provider.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+
+// This must be constructed on the render thread, and then used and destructed
+// on a single thread, which can be different from the render thread.
+class WebSocketHandshakeThrottleProviderImpl final
+ : public content::WebSocketHandshakeThrottleProvider {
+ public:
+ explicit WebSocketHandshakeThrottleProviderImpl(
+ blink::ThreadSafeBrowserInterfaceBrokerProxy* broker);
+ ~WebSocketHandshakeThrottleProviderImpl() override;
+
+ // Implements content::WebSocketHandshakeThrottleProvider.
+ std::unique_ptr<content::WebSocketHandshakeThrottleProvider> Clone(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) override;
+ std::unique_ptr<blink::WebSocketHandshakeThrottle> CreateThrottle(
+ int render_frame_id,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) override;
+
+ private:
+ // This copy constructor works in conjunction with Clone(), not intended for
+ // general use.
+ WebSocketHandshakeThrottleProviderImpl(
+ const WebSocketHandshakeThrottleProviderImpl& other);
+
+ mojo::PendingRemote<safe_browsing::mojom::SafeBrowsing> safe_browsing_remote_;
+ mojo::Remote<safe_browsing::mojom::SafeBrowsing> safe_browsing_;
+
+ THREAD_CHECKER(thread_checker_);
+
+ DISALLOW_ASSIGN(WebSocketHandshakeThrottleProviderImpl);
+};
+
+#endif // CHROME_RENDERER_WEBSOCKET_HANDSHAKE_THROTTLE_PROVIDER_IMPL_H_
diff --git a/chromium/chrome/renderer/worker_content_settings_client.cc b/chromium/chrome/renderer/worker_content_settings_client.cc
new file mode 100644
index 00000000000..0f4fb5bb6da
--- /dev/null
+++ b/chromium/chrome/renderer/worker_content_settings_client.cc
@@ -0,0 +1,123 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/worker_content_settings_client.h"
+
+#include "chrome/common/render_messages.h"
+#include "chrome/renderer/content_settings_observer.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "third_party/blink/public/platform/url_conversion.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "url/origin.h"
+
+WorkerContentSettingsClient::WorkerContentSettingsClient(
+ content::RenderFrame* render_frame)
+ : routing_id_(render_frame->GetRoutingID()), is_unique_origin_(false) {
+ blink::WebLocalFrame* frame = render_frame->GetWebFrame();
+ if (frame->GetDocument().GetSecurityOrigin().IsUnique() ||
+ frame->Top()->GetSecurityOrigin().IsUnique())
+ is_unique_origin_ = true;
+ sync_message_filter_ = content::RenderThread::Get()->GetSyncMessageFilter();
+ document_origin_ = frame->GetDocument().GetSecurityOrigin();
+ site_for_cookies_ = frame->GetDocument().SiteForCookies();
+ top_frame_origin_ = frame->GetDocument().TopFrameOrigin();
+ allow_running_insecure_content_ = ContentSettingsObserver::Get(render_frame)
+ ->allow_running_insecure_content();
+ content_setting_rules_ =
+ ContentSettingsObserver::Get(render_frame)->GetContentSettingRules();
+}
+
+WorkerContentSettingsClient::WorkerContentSettingsClient(
+ const WorkerContentSettingsClient& other)
+ : routing_id_(other.routing_id_),
+ is_unique_origin_(other.is_unique_origin_),
+ document_origin_(other.document_origin_),
+ site_for_cookies_(other.site_for_cookies_),
+ top_frame_origin_(other.top_frame_origin_),
+ allow_running_insecure_content_(other.allow_running_insecure_content_),
+ sync_message_filter_(other.sync_message_filter_),
+ content_setting_rules_(other.content_setting_rules_) {}
+
+WorkerContentSettingsClient::~WorkerContentSettingsClient() {}
+
+std::unique_ptr<blink::WebContentSettingsClient>
+WorkerContentSettingsClient::Clone() {
+ return base::WrapUnique(new WorkerContentSettingsClient(*this));
+}
+
+bool WorkerContentSettingsClient::RequestFileSystemAccessSync() {
+ if (is_unique_origin_)
+ return false;
+
+ bool result = false;
+ sync_message_filter_->Send(new ChromeViewHostMsg_RequestFileSystemAccessSync(
+ routing_id_, document_origin_, site_for_cookies_, top_frame_origin_,
+ &result));
+ return result;
+}
+
+bool WorkerContentSettingsClient::AllowIndexedDB(
+ const blink::WebSecurityOrigin&) {
+ if (is_unique_origin_)
+ return false;
+
+ bool result = false;
+ sync_message_filter_->Send(new ChromeViewHostMsg_AllowIndexedDB(
+ routing_id_, document_origin_, site_for_cookies_, top_frame_origin_,
+ &result));
+ return result;
+}
+
+bool WorkerContentSettingsClient::AllowCacheStorage(
+ const blink::WebSecurityOrigin&) {
+ if (is_unique_origin_)
+ return false;
+
+ bool result = false;
+ sync_message_filter_->Send(new ChromeViewHostMsg_AllowCacheStorage(
+ routing_id_, document_origin_, site_for_cookies_, top_frame_origin_,
+ &result));
+ return result;
+}
+
+bool WorkerContentSettingsClient::AllowRunningInsecureContent(
+ bool allowed_per_settings,
+ const blink::WebSecurityOrigin& context,
+ const blink::WebURL& url) {
+ if (!allow_running_insecure_content_ && !allowed_per_settings) {
+ sync_message_filter_->Send(new ChromeViewHostMsg_ContentBlocked(
+ routing_id_, CONTENT_SETTINGS_TYPE_MIXEDSCRIPT, base::string16()));
+ return false;
+ }
+
+ return true;
+}
+
+bool WorkerContentSettingsClient::AllowScriptFromSource(
+ bool enabled_per_settings,
+ const blink::WebURL& script_url) {
+ bool allow = enabled_per_settings;
+ if (allow && content_setting_rules_) {
+ GURL top_frame_origin_url = top_frame_origin_.GetURL();
+ for (const auto& rule : content_setting_rules_->script_rules) {
+ if (rule.primary_pattern.Matches(top_frame_origin_url) &&
+ rule.secondary_pattern.Matches(script_url)) {
+ allow = rule.GetContentSetting() != CONTENT_SETTING_BLOCK;
+ break;
+ }
+ }
+ }
+
+ if (!allow) {
+ sync_message_filter_->Send(new ChromeViewHostMsg_ContentBlocked(
+ routing_id_, CONTENT_SETTINGS_TYPE_JAVASCRIPT, base::string16()));
+ return false;
+ }
+
+ return true;
+}
diff --git a/chromium/chrome/renderer/worker_content_settings_client.h b/chromium/chrome/renderer/worker_content_settings_client.h
new file mode 100644
index 00000000000..7aeb81fd316
--- /dev/null
+++ b/chromium/chrome/renderer/worker_content_settings_client.h
@@ -0,0 +1,63 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_WORKER_CONTENT_SETTINGS_CLIENT_H_
+#define CHROME_RENDERER_WORKER_CONTENT_SETTINGS_CLIENT_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "third_party/blink/public/platform/web_content_settings_client.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace IPC {
+class SyncMessageFilter;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace blink {
+class WebSecurityOrigin;
+}
+
+struct RendererContentSettingRules;
+
+// This client is created on the main renderer thread then passed onto the
+// blink's worker thread.
+class WorkerContentSettingsClient : public blink::WebContentSettingsClient {
+ public:
+ explicit WorkerContentSettingsClient(content::RenderFrame* render_frame);
+ ~WorkerContentSettingsClient() override;
+
+ // WebContentSettingsClient overrides.
+ std::unique_ptr<blink::WebContentSettingsClient> Clone() override;
+ bool RequestFileSystemAccessSync() override;
+ bool AllowIndexedDB(const blink::WebSecurityOrigin&) override;
+ bool AllowCacheStorage(const blink::WebSecurityOrigin&) override;
+ bool AllowRunningInsecureContent(bool allowed_per_settings,
+ const blink::WebSecurityOrigin& context,
+ const blink::WebURL& url) override;
+ bool AllowScriptFromSource(bool enabled_per_settings,
+ const blink::WebURL& script_url) override;
+
+ private:
+ explicit WorkerContentSettingsClient(
+ const WorkerContentSettingsClient& other);
+
+ // Loading document context for this worker.
+ const int routing_id_;
+ bool is_unique_origin_;
+ url::Origin document_origin_;
+ GURL site_for_cookies_;
+ url::Origin top_frame_origin_;
+ bool allow_running_insecure_content_;
+ scoped_refptr<IPC::SyncMessageFilter> sync_message_filter_;
+ const RendererContentSettingRules* content_setting_rules_;
+
+ DISALLOW_ASSIGN(WorkerContentSettingsClient);
+};
+
+#endif // CHROME_RENDERER_WORKER_CONTENT_SETTINGS_CLIENT_H_
diff --git a/chromium/gpu/config/gpu_lists_version.h b/chromium/gpu/config/gpu_lists_version.h
index c0781d029c3..9dff8f2503f 100644
--- a/chromium/gpu/config/gpu_lists_version.h
+++ b/chromium/gpu/config/gpu_lists_version.h
@@ -3,6 +3,6 @@
#ifndef GPU_CONFIG_GPU_LISTS_VERSION_H_
#define GPU_CONFIG_GPU_LISTS_VERSION_H_
-#define GPU_LISTS_VERSION "cd3a886c28f57603dea2a9050d2aadef3e8c30a4"
+#define GPU_LISTS_VERSION "a92c93df20fcf33ca6a37962134389f0b85ec9ab"
#endif // GPU_CONFIG_GPU_LISTS_VERSION_H_